Removed "Inventory" from the GUI API names

This commit is contained in:
bluefireoly
2020-10-24 23:25:32 +02:00
parent 3c029eb292
commit 7ba75f3250
25 changed files with 262 additions and 262 deletions

View File

@@ -0,0 +1,182 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package net.axay.kspigot.gui
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
private const val DEFAULT_PAGE = 1
class GUIData<T : ForInventory>(
val guiType: GUIType<T>,
val title: String?,
internal val pages: Map<Int, GUIPage<T>>,
val transitionTo: InventoryChangeEffect?,
val transitionFrom: InventoryChangeEffect?,
internal val generalOnClick: ((GUIClickEvent<T>) -> Unit)?
)
abstract class GUI<T : ForInventory>(
val data: GUIData<T>
) {
var currentPageInt: Int = DEFAULT_PAGE; protected set
val currentPage
get() = getPage(currentPageInt)
?: throw IllegalStateException("The currentPageInt has no associated page!")
internal abstract val bukkitInventory: Inventory
internal var isInMove: Boolean = false
internal val currentElements = HashSet<GUIElement<*>>()
internal abstract fun loadPageUnsafe(
page: Int,
offsetHorizontally: Int = 0,
offsetVertically: Int = 0
)
internal abstract fun loadPageUnsafe(
page: GUIPage<*>,
offsetHorizontally: Int = 0,
offsetVertically: Int = 0
)
internal abstract fun loadContent(
content: Map<Int, GUISlot<*>>,
offsetHorizontally: Int = 0,
offsetVertically: Int = 0
)
/**
* @return True, if the [inventory] belongs to this GUI.
*/
abstract fun isThisInv(inventory: Inventory): Boolean
/**
* Registers this GUI.
* (KSpigot will listen for actions in the inventory.)
*/
@Suppress("UNCHECKED_CAST")
fun register() = GUIHolder.register(this as GUI<ForInventory>)
/**
* Stops KSpigot from listening to actions in this
* GUI anymore.
*/
@Suppress("UNCHECKED_CAST")
fun unregister() = GUIHolder.unregister(this as GUI<ForInventory>)
/**
* Loads the specified page in order to display it in the GUI.
*/
fun loadPage(page: GUIPage<T>) = loadPageUnsafe(page)
/**
* Temporarily sets the given item at the given slots.
*/
abstract operator fun set(slot: InventorySlotCompound<T>, value: ItemStack)
/**
* Searches for a page associated to the given [page] index.
*/
fun getPage(page: Int?) = data.pages[page]
/**
* Reloads the current page.
*/
fun reloadCurrentPage() {
if (!isInMove)
loadPage(currentPage)
}
}
// Inventory GUI implementations
class GUIShared<T : ForInventory>(
GUIData: GUIData<T>
) : GUI<T>(GUIData) {
override val bukkitInventory = data.guiType.createBukkitInv(null, data.title)
init {
loadPageUnsafe(DEFAULT_PAGE)
}
override fun isThisInv(inventory: Inventory) = inventory == bukkitInventory
override fun loadPageUnsafe(page: Int, offsetHorizontally: Int, offsetVertically: Int) {
data.pages[page]?.let { loadPageUnsafe(it, offsetHorizontally, offsetVertically) }
}
override fun loadPageUnsafe(page: GUIPage<*>, offsetHorizontally: Int, offsetVertically: Int) {
val ifOffset = offsetHorizontally != 0 || offsetVertically != 0
if (!ifOffset) {
// unregister this inv from all elements on the previous page
currentElements.forEach { it.stopUsing(this) }
currentElements.clear()
// register this inv for all new elements
HashSet(page.slots.values).forEach { if (it is GUIElement) {
currentElements += it
it.startUsing(this)
} }
currentPageInt = page.number
}
loadContent(page.slots, offsetHorizontally, offsetVertically)
}
override fun loadContent(
content: Map<Int, GUISlot<*>>,
offsetHorizontally: Int,
offsetVertically: Int
) {
val ifOffset = offsetHorizontally != 0 || offsetVertically != 0
val dimensions = data.guiType.dimensions
// clear the space which will be redefined
if (ifOffset) {
dimensions.invSlots.forEach {
val slotToClear = dimensions.invSlotsWithRealSlots[it.add(offsetHorizontally, offsetVertically)]
if (slotToClear != null) bukkitInventory.clear(slotToClear)
}
} else bukkitInventory.clear()
// render the given content
content.forEach {
val slot = it.value
if (slot is GUIElement) {
if (ifOffset) {
val invSlot = InventorySlot.fromRealSlot(it.key, dimensions)
if (invSlot != null) {
val offsetSlot = invSlot.add(offsetHorizontally, offsetVertically).realSlotIn(dimensions)
if (offsetSlot != null) bukkitInventory.setItem(offsetSlot, slot.getItemStack(offsetSlot))
}
} else bukkitInventory.setItem(it.key, slot.getItemStack(it.key))
}
}
}
override operator fun set(slot: InventorySlotCompound<T>, value: ItemStack) {
slot.realSlotsWithInvType(data.guiType).forEach {
bukkitInventory.setItem(it, value)
}
}
}

View File

@@ -0,0 +1,254 @@
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package net.axay.kspigot.gui
import net.axay.kspigot.gui.elements.*
import org.bukkit.inventory.ItemStack
import kotlin.math.absoluteValue
fun <T : ForInventory> kSpigotGUI(
type: GUIType<T>,
shared: Boolean = true,
builder: GUIBuilder<T>.() -> Unit,
) = GUIBuilder(type, shared).apply(builder).build()
class GUIBuilder<T : ForInventory>(
val type: GUIType<T>,
val shared: Boolean
) {
var title: String = ""
var transitionTo: InventoryChangeEffect? = null
var transitionFrom: InventoryChangeEffect? = null
private val guiSlots = HashMap<Int, GUIPage<T>>()
private var onClickElement: ((GUIClickEvent<T>) -> Unit)? = null
/**
* Opens the builder for a new page and adds
* the new page to the GUI.
* @param page The index of the page.
*/
fun page(page: Int, builder: GUIPageBuilder<T>.() -> Unit) {
guiSlots[page] = GUIPageBuilder(type, page).apply(builder).build()
}
fun onClickElement(onClick: (GUIClickEvent<T>) -> Unit) {
onClickElement = onClick
}
internal fun build(): GUI<T> {
val guiData = GUIData(type, title, guiSlots, transitionTo, transitionFrom, onClickElement)
val gui =
if (shared) GUIShared(guiData) else TODO("Currently, there is no non-shared GUI implementation available.")
return gui.apply { register() }
}
}
class GUIPageBuilder<T : ForInventory>(
private val type: GUIType<T>,
val page: Int
) {
private val guiSlots = HashMap<Int, GUISlot<T>>()
var transitionTo: PageChangeEffect? = null
var transitionFrom: PageChangeEffect? = null
internal fun build() = GUIPage(page, guiSlots, transitionTo, transitionFrom)
private fun defineSlots(slots: InventorySlotCompound<T>, element: GUISlot<T>) =
slots.withInvType(type).forEach { curSlot ->
curSlot.realSlotIn(type.dimensions)?.let { guiSlots[it] = element }
}
/**
* A button is an item protected from any player
* actions. If clicked, the specified [onClick]
* function is invoked.
*/
fun button(slots: InventorySlotCompound<T>, itemStack: ItemStack, onClick: (GUIClickEvent<T>) -> Unit) =
defineSlots(slots, GUIButton(itemStack, onClick))
/**
* An item protected from any player actions.
* This is not a button.
*/
fun placeholder(slots: InventorySlotCompound<T>, itemStack: ItemStack) =
defineSlots(slots, GUIPlaceholder(itemStack))
/**
* A free slot does not block any player actions.
* The player can put items in this slot or take
* items out of it.
*/
fun freeSlot(slots: InventorySlotCompound<T>) = defineSlots(slots, GUIFreeSlot())
/**
* This is a button which loads the specified
* [toPage] if clicked.
*/
fun pageChanger(
slots: InventorySlotCompound<T>,
icon: ItemStack,
toPage: Int,
onChange: ((GUIClickEvent<T>) -> Unit)? = null
) = defineSlots(
slots, GUIButtonPageChange(
icon,
GUIPageChangeCalculator.GUIConsistentPageCalculator(toPage),
onChange
)
)
/**
* This button always tries to find the previous
* page if clicked, and if a previous page
* exists it is loaded.
*/
fun previousPage(
slots: InventorySlotCompound<T>,
icon: ItemStack,
onChange: ((GUIClickEvent<T>) -> Unit)? = null
) = defineSlots(
slots, GUIButtonPageChange(
icon,
GUIPageChangeCalculator.GUIPreviousPageCalculator,
onChange
)
)
/**
* This button always tries to find the next
* page if clicked, and if a next page
* exists it is loaded.
*/
fun nextPage(
slots: InventorySlotCompound<T>,
icon: ItemStack,
onChange: ((GUIClickEvent<T>) -> Unit)? = null
) = defineSlots(
slots, GUIButtonPageChange(
icon,
GUIPageChangeCalculator.GUINextPageCalculator,
onChange
)
)
/**
* By pressing this button, the player switches to another
* GUI. The transition effect is applied.
*/
fun changeGUI(
slots: InventorySlotCompound<T>,
icon: ItemStack,
newGUI: () -> GUI<*>,
newPage: Int? = null,
onChange: ((GUIClickEvent<T>) -> Unit)? = null
) = defineSlots(
slots, GUIButtonInventoryChange(
icon,
newGUI,
newPage,
onChange
)
)
/**
* Creates a new compound, holding data which can be displayed
* in any compound space.
*/
fun <E> createCompound(
iconGenerator: (E) -> ItemStack,
onClick: (clickEvent: GUIClickEvent<T>, element: E) -> Unit
) = GUISpaceCompound(type, iconGenerator, onClick)
/**
* Creates a new compound, holding data which can be displayed
* in any compound space.
* This compound is strictly a rectangle.
* The space is automatically defined.
*/
fun <E> createCompound(
fromSlot: SingleInventorySlot<out T>,
toSlot: SingleInventorySlot<out T>,
iconGenerator: (E) -> ItemStack,
onClick: (clickEvent: GUIClickEvent<T>, element: E) -> Unit
): GUIRectSpaceCompound<T, E> {
val rectSlotCompound = fromSlot rectTo toSlot
return GUIRectSpaceCompound(
type,
iconGenerator,
onClick,
(rectSlotCompound.endInclusive.slotInRow - rectSlotCompound.start.slotInRow) + 1
).apply {
addSlots(rectSlotCompound)
defineSlots(
rectSlotCompound,
GUISpaceCompoundElement(this)
)
}
}
/**
* Defines an area where the content of the given compound
* is displayed.
*/
fun <E> compoundSpace(
slots: InventorySlotCompound<T>,
compound: AbstractGUISpaceCompound<T, E>
) {
compound.addSlots(slots)
defineSlots(
slots,
GUISpaceCompoundElement(compound)
)
}
/**
* By pressing this button,
* the user scrolls forwards or backwards in the compound.
*/
fun compoundScroll(
slots: InventorySlotCompound<T>,
icon: ItemStack,
compound: GUISpaceCompound<T, *>,
scrollDistance: Int = 1,
scrollTimes: Int = 1,
reverse: Boolean = false
) = defineSlots(
slots,
GUISpaceCompoundScrollButton(
icon,
compound,
scrollDistance.absoluteValue,
scrollTimes,
reverse
)
)
/**
* By pressing this button,
* the user scrolls forwards or backwards in the compound.
*/
fun compoundScroll(
slots: InventorySlotCompound<T>,
icon: ItemStack,
compound: GUIRectSpaceCompound<T, *>,
scrollTimes: Int = 1,
reverse: Boolean = false
) = defineSlots(
slots,
GUISpaceCompoundScrollButton(
icon,
compound,
compound.compoundWidth,
scrollTimes,
reverse
)
)
}

View File

@@ -0,0 +1,8 @@
package net.axay.kspigot.gui
import org.bukkit.event.inventory.InventoryClickEvent
class GUIClickEvent<T : ForInventory>(
val bukkitEvent: InventoryClickEvent,
val gui: GUI<T>
)

View File

@@ -0,0 +1,25 @@
package net.axay.kspigot.gui
import org.bukkit.inventory.ItemStack
abstract class GUISlot<T : ForInventory> {
abstract fun onClick(clickEvent: GUIClickEvent<T>)
}
// ELEMENT
abstract class GUIElement<T : ForInventory> : GUISlot<T>() {
abstract fun getItemStack(slot: Int): ItemStack
final override fun onClick(clickEvent: GUIClickEvent<T>) {
clickEvent.gui.data.generalOnClick?.invoke(clickEvent)
onClickElement(clickEvent)
}
protected abstract fun onClickElement(clickEvent: GUIClickEvent<T>)
internal open fun startUsing(gui: GUI<*>) { }
internal open fun stopUsing(gui: GUI<*>) { }
}

View File

@@ -0,0 +1,15 @@
package net.axay.kspigot.gui
import org.bukkit.entity.HumanEntity
import org.bukkit.inventory.InventoryView
fun HumanEntity.openGUI(gui: GUI<*>, page: Int? = null): InventoryView? {
closeInventory()
if (page != null)
gui.loadPageUnsafe(page)
return openInventory(gui.bukkitInventory)
}

View File

@@ -0,0 +1,50 @@
package net.axay.kspigot.gui
import net.axay.kspigot.event.listen
import org.bukkit.event.inventory.InventoryAction
import org.bukkit.event.inventory.InventoryClickEvent
object GUIHolder : AutoCloseable {
private val registered = HashSet<GUI<ForInventory>>()
fun register(GUI: GUI<ForInventory>) {
registered.add(GUI)
}
fun unregister(GUI: GUI<ForInventory>) {
registered.remove(GUI)
}
init {
listen<InventoryClickEvent> {
val clickedInv = it.clickedInventory ?: return@listen
val inv = registered.find { search -> search.isThisInv(clickedInv) } ?: return@listen
if (inv.isInMove) {
it.isCancelled = true
return@listen
}
if (it.action.isGUIClick)
inv.currentPage.slots[it.slot]?.onClick(GUIClickEvent(it, inv)) ?: kotlin.run {
it.isCancelled = true
}
else
it.isCancelled = true
}
}
override fun close() {
registered.forEach { inv -> inv.bukkitInventory.viewers.forEach { it.closeInventory() } }
registered.clear()
}
}
private val InventoryAction.isGUIClick
get() = this == InventoryAction.PICKUP_ALL || this == InventoryAction.PICKUP_HALF

View File

@@ -0,0 +1,8 @@
package net.axay.kspigot.gui
class GUIPage<T : ForInventory>(
val number: Int,
internal val slots: Map<Int, GUISlot<T>>,
val transitionTo: PageChangeEffect?,
val transitionFrom: PageChangeEffect?
)

View File

@@ -0,0 +1,143 @@
package net.axay.kspigot.gui
import net.axay.kspigot.runnables.task
abstract class GUIPageChangeCalculator {
abstract fun calculateNewPage(currentPage: Int, pages: Collection<Int>): Int?
object GUIPreviousPageCalculator : GUIPageChangeCalculator() {
override fun calculateNewPage(currentPage: Int, pages: Collection<Int>) =
pages.sortedDescending().find { it < currentPage }
}
object GUINextPageCalculator : GUIPageChangeCalculator() {
override fun calculateNewPage(currentPage: Int, pages: Collection<Int>) =
pages.sorted().find { it > currentPage }
}
class GUIConsistentPageCalculator(private val toPage: Int) : GUIPageChangeCalculator() {
override fun calculateNewPage(currentPage: Int, pages: Collection<Int>) = toPage
}
}
enum class PageChangeEffect {
INSTANT,
SLIDE_HORIZONTALLY,
SLIDE_VERTICALLY,
SWIPE_HORIZONTALLY,
SWIPE_VERTICALLY,
}
enum class InventoryChangeEffect(
val effect: PageChangeEffect
) {
INSTANT(PageChangeEffect.INSTANT)
}
internal fun GUI<*>.changePage(
effect: PageChangeEffect,
fromPage: GUIPage<*>,
toPage: GUIPage<*>
) {
val fromPageInt = fromPage.number
val toPageInt = toPage.number
when (effect) {
PageChangeEffect.INSTANT -> loadPageUnsafe(toPage)
PageChangeEffect.SLIDE_HORIZONTALLY -> {
val width = data.guiType.dimensions.width
changePageEffect(fromPageInt, toPageInt, width) { currentOffset, ifInverted ->
if (ifInverted) {
loadPageUnsafe(fromPage, offsetHorizontally = currentOffset)
loadPageUnsafe(toPage, offsetHorizontally = -(width - currentOffset))
} else {
loadPageUnsafe(fromPage, offsetHorizontally = -currentOffset)
loadPageUnsafe(toPage, offsetHorizontally = width - currentOffset)
}
}
}
PageChangeEffect.SLIDE_VERTICALLY -> {
val height = data.guiType.dimensions.height
changePageEffect(fromPageInt, toPageInt, height) { currentOffset, ifInverted ->
if (ifInverted) {
loadPageUnsafe(fromPage, offsetVertically = currentOffset)
loadPageUnsafe(toPage, offsetVertically = -(height - currentOffset))
} else {
loadPageUnsafe(fromPage, offsetVertically = -currentOffset)
loadPageUnsafe(toPage, offsetVertically = height - currentOffset)
}
}
}
PageChangeEffect.SWIPE_HORIZONTALLY -> {
val width = data.guiType.dimensions.width
changePageEffect(fromPageInt, toPageInt, width) { currentOffset, ifInverted ->
if (ifInverted) {
loadPageUnsafe(toPage, offsetHorizontally = -(width - currentOffset))
} else {
loadPageUnsafe(toPage, offsetHorizontally = width - currentOffset)
}
}
}
PageChangeEffect.SWIPE_VERTICALLY -> {
val height = data.guiType.dimensions.height
changePageEffect(fromPageInt, toPageInt, height) { currentOffset, ifInverted ->
if (ifInverted) {
loadPageUnsafe(toPage, offsetVertically = -(height - currentOffset))
} else {
loadPageUnsafe(toPage, offsetVertically = height - currentOffset)
}
}
}
}
}
internal fun GUI<*>.changeGUI(
effect: InventoryChangeEffect,
fromPage: GUIPage<*>,
toPage: GUIPage<*>
) = changePage(effect.effect, fromPage, toPage)
private inline fun changePageEffect(
fromPage: Int,
toPage: Int,
doFor: Int,
crossinline effect: (currentOffset: Int, ifInverted: Boolean) -> Unit,
) {
val ifInverted = fromPage >= toPage
var currentOffset = 1
task(
sync = true,
period = 1,
howOften = doFor.toLong()
) {
effect.invoke(currentOffset, ifInverted)
currentOffset++
}
}

View File

@@ -0,0 +1,377 @@
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package net.axay.kspigot.gui
import net.axay.kspigot.languageextensions.kotlinextensions.MinMaxPair
// INVENTORY
data class InventoryDimensions(val width: Int, val height: Int) {
val slotAmount = width * height
val invSlots by lazy {
ArrayList<InventorySlot>().apply {
(1..height).forEach { row ->
(1..width).forEach { slotInRow ->
this += InventorySlot(row, slotInRow)
}
}
}
}
val invSlotsWithRealSlots by lazy {
HashMap<InventorySlot, Int>().apply {
invSlots.forEach { curSlot ->
curSlot.realSlotIn(this@InventoryDimensions)?.let { this[curSlot] = it }
}
}
}
val realSlots by lazy { invSlotsWithRealSlots.values }
}
// SLOTS
data class InventorySlot(val row: Int, val slotInRow: Int) : Comparable<InventorySlot> {
companion object {
fun fromRealSlot(realSlot: Int, dimensions: InventoryDimensions) =
dimensions.invSlotsWithRealSlots.toList().find { it.second == realSlot }?.first
}
override fun compareTo(other: InventorySlot) = when {
row > other.row -> 1
row < other.row -> -1
else -> when {
slotInRow > other.slotInRow -> 1
slotInRow < other.slotInRow -> -1
else -> 0
}
}
fun realSlotIn(inventoryDimensions: InventoryDimensions): Int? {
if (!isInDimension(inventoryDimensions)) return null
val realRow = inventoryDimensions.height - (row - 1)
val rowsUnder = if (realRow - 1 >= 0) realRow - 1 else 0
return ((rowsUnder * inventoryDimensions.width) + slotInRow) - 1
}
fun isInDimension(inventoryDimensions: InventoryDimensions) =
(1..inventoryDimensions.width).contains(slotInRow) && (1..inventoryDimensions.height).contains(row)
fun add(offsetHorizontally: Int, offsetVertically: Int) = InventorySlot(
row + offsetVertically,
slotInRow + offsetHorizontally
)
}
interface InventorySlotCompound<out T : ForInventory> {
fun withInvType(invType: GUIType<T>): Collection<InventorySlot>
fun realSlotsWithInvType(invType: GUIType<T>) =
withInvType(invType).mapNotNull { it.realSlotIn(invType.dimensions) }
}
open class SingleInventorySlot<T : ForInventory> internal constructor(
val inventorySlot: InventorySlot
) : InventorySlotCompound<T> {
constructor(row: Int, slotInRow: Int) : this(InventorySlot(row, slotInRow))
private val slotAsList = listOf(inventorySlot)
override fun withInvType(invType: GUIType<T>) = slotAsList
}
internal enum class InventorySlotRangeType {
LINEAR,
RECTANGLE,
}
class InventorySlotRange<out T : ForInventory> internal constructor(
startSlot: SingleInventorySlot<out T>,
endSlot: SingleInventorySlot<out T>,
private val type: InventorySlotRangeType
) : InventorySlotCompound<T>, ClosedRange<InventorySlot> {
override val start: InventorySlot
override val endInclusive: InventorySlot
init {
val minMaxPair = MinMaxPair(startSlot.inventorySlot, endSlot.inventorySlot)
start = minMaxPair.min
endInclusive = minMaxPair.max
}
override fun withInvType(invType: GUIType<T>) = LinkedHashSet<InventorySlot>().apply {
when (type) {
InventorySlotRangeType.RECTANGLE -> {
// all possible combinations between the two slots
// -> form a rectangle
for (row in start.row..endInclusive.row)
for (slotInRow in start.slotInRow..endInclusive.slotInRow)
this += InventorySlot(row, slotInRow)
}
InventorySlotRangeType.LINEAR -> {
if (endInclusive.row > start.row) {
// from start --->| to end of row
for (slotInRow in start.slotInRow..invType.dimensions.width)
this += InventorySlot(start.row, slotInRow)
// all rows in between
if (endInclusive.row > start.row + 1)
for (row in start.row + 1 until endInclusive.row)
for (slotInRow in 1..invType.dimensions.width)
this += InventorySlot(row, slotInRow)
// from start of row |----> to endInclusive
for (slotInRow in 1..endInclusive.slotInRow)
this += InventorySlot(endInclusive.row, slotInRow)
} else if (endInclusive.row == start.row) {
// from start ---> to endInclusive in the same row
for (slotInRow in start.slotInRow..endInclusive.slotInRow)
this += InventorySlot(start.row, slotInRow)
}
}
}
}
}
/**
* This range contains all slots having an index between
* the indeces of the two given slots.
*/
infix fun <T : ForInventory> SingleInventorySlot<out T>.linTo(slot: SingleInventorySlot<out T>) =
InventorySlotRange(this, slot, InventorySlotRangeType.LINEAR)
/**
* This range contains all slots inside of a thought rectangle
* with the two given slots as two opposite corners of the rectangle.
*/
infix fun <T : ForInventory> SingleInventorySlot<out T>.rectTo(slot: SingleInventorySlot<out T>) =
InventorySlotRange(this, slot, InventorySlotRangeType.RECTANGLE)
class InventoryRowSlots<T : ForInventory> internal constructor(
val row: Int
) : InventorySlotCompound<T> {
override fun withInvType(invType: GUIType<T>) = HashSet<InventorySlot>().apply {
for (slotInRow in 1..invType.dimensions.width)
this += InventorySlot(row, slotInRow)
}
}
class InventoryColumnSlots<T : ForInventory> internal constructor(
val column: Int
) : InventorySlotCompound<T> {
override fun withInvType(invType: GUIType<T>) = HashSet<InventorySlot>().apply {
for (row in 1..invType.dimensions.height)
this += InventorySlot(row, column)
}
}
class InventoryBorderSlots<T : ForInventory> internal constructor(
val padding: Int
) : InventorySlotCompound<T> {
override fun withInvType(invType: GUIType<T>) = HashSet<InventorySlot>().apply {
val dimensions = invType.dimensions
for (currentPadding in 0 until padding) {
for (slotInRow in 1 + currentPadding..dimensions.width - currentPadding) {
this += InventorySlot(1, slotInRow)
this += InventorySlot(dimensions.height, slotInRow)
}
for (row in 2 + currentPadding until dimensions.height - currentPadding) {
this += InventorySlot(row, 1)
this += InventorySlot(row, dimensions.width)
}
}
}
}
class InventoryCornerSlots<T : ForInventory> internal constructor(
val ifBottomLeft: Boolean = false,
val ifBottomRight: Boolean = false,
val ifTopLeft: Boolean = false,
val ifTopRight: Boolean = false
) : InventorySlotCompound<T> {
override fun withInvType(invType: GUIType<T>) = HashSet<InventorySlot>().apply {
val dimensions = invType.dimensions
if (ifBottomLeft) this += InventorySlot(1, 1)
if (ifBottomRight) this += InventorySlot(1, dimensions.width)
if (ifTopLeft) this += InventorySlot(dimensions.height, 1)
if (ifTopRight) this += InventorySlot(dimensions.height, dimensions.width)
}
}
// SLOT TYPE SAFETY
// COLUMNS
interface ForColumnOne : ForInventoryWidthThree, ForInventoryWidthFive, ForInventoryWidthNine
interface ForColumnTwo : ForInventoryWidthThree, ForInventoryWidthFive, ForInventoryWidthNine
interface ForColumnThree : ForInventoryWidthThree, ForInventoryWidthFive, ForInventoryWidthNine
interface ForColumnFour : ForInventoryWidthFive, ForInventoryWidthNine
interface ForColumnFive : ForInventoryWidthFive, ForInventoryWidthNine
interface ForColumnSix : ForInventoryWidthNine
interface ForColumnSeven : ForInventoryWidthNine
interface ForColumnEight : ForInventoryWidthNine
interface ForColumnNine : ForInventoryWidthNine
// ROWS
interface ForRowOne : ForInventoryOneByNine, ForInventoryTwoByNine, ForInventoryThreeByNine, ForInventoryFourByNine,
ForInventoryFiveByNine, ForInventorySixByNine
interface ForRowTwo : ForInventoryTwoByNine, ForInventoryThreeByNine, ForInventoryFourByNine, ForInventoryFiveByNine,
ForInventorySixByNine
interface ForRowThree : ForInventoryThreeByNine, ForInventoryFourByNine, ForInventoryFiveByNine, ForInventorySixByNine
interface ForRowFour : ForInventoryFourByNine, ForInventoryFiveByNine, ForInventorySixByNine
interface ForRowFive : ForInventoryFiveByNine, ForInventorySixByNine
interface ForRowSix : ForInventorySixByNine
// EDGE CASES:
// ROW ONE
interface ForRowOneSlotOneToThree : ForRowOne, ForInventoryOneByFive, ForInventoryThreeByThree
interface ForRowOneSlotFourToFive : ForRowOne, ForInventoryOneByFive
// ROW TWO
interface ForRowTwoSlotOneToThree : ForRowTwo, ForInventoryThreeByThree
// ROW THREE
interface ForRowThreeSlotOneToThree : ForRowThree, ForInventoryThreeByThree
// COMPLETE ROWS (including the edge cases)
interface ForCompleteRowOne : ForRowOne, ForRowOneSlotOneToThree, ForRowOneSlotFourToFive
interface ForCompleteRowTwo : ForRowTwo, ForRowTwoSlotOneToThree
interface ForCompleteRowThree : ForRowThree, ForRowThreeSlotOneToThree
object Slots {
// ROW ONE
val RowOneSlotOne = SingleInventorySlot<ForRowOneSlotOneToThree>(1, 1)
val RowOneSlotTwo = SingleInventorySlot<ForRowOneSlotOneToThree>(1, 2)
val RowOneSlotThree = SingleInventorySlot<ForRowOneSlotOneToThree>(1, 3)
val RowOneSlotFour = SingleInventorySlot<ForRowOneSlotFourToFive>(1, 4)
val RowOneSlotFive = SingleInventorySlot<ForRowOneSlotFourToFive>(1, 5)
val RowOneSlotSix = SingleInventorySlot<ForRowOne>(1, 6)
val RowOneSlotSeven = SingleInventorySlot<ForRowOne>(1, 7)
val RowOneSlotEight = SingleInventorySlot<ForRowOne>(1, 8)
val RowOneSlotNine = SingleInventorySlot<ForRowOne>(1, 9)
// ROW TWO
val RowTwoSlotOne = SingleInventorySlot<ForRowTwoSlotOneToThree>(2, 1)
val RowTwoSlotTwo = SingleInventorySlot<ForRowTwoSlotOneToThree>(2, 2)
val RowTwoSlotThree = SingleInventorySlot<ForRowTwoSlotOneToThree>(2, 3)
val RowTwoSlotFour = SingleInventorySlot<ForRowTwo>(2, 4)
val RowTwoSlotFive = SingleInventorySlot<ForRowTwo>(2, 5)
val RowTwoSlotSix = SingleInventorySlot<ForRowTwo>(2, 6)
val RowTwoSlotSeven = SingleInventorySlot<ForRowTwo>(2, 7)
val RowTwoSlotEight = SingleInventorySlot<ForRowTwo>(2, 8)
val RowTwoSlotNine = SingleInventorySlot<ForRowTwo>(2, 9)
// ROW THREE
val RowThreeSlotOne = SingleInventorySlot<ForRowThreeSlotOneToThree>(3, 1)
val RowThreeSlotTwo = SingleInventorySlot<ForRowThreeSlotOneToThree>(3, 2)
val RowThreeSlotThree = SingleInventorySlot<ForRowThreeSlotOneToThree>(3, 3)
val RowThreeSlotFour = SingleInventorySlot<ForRowThree>(3, 4)
val RowThreeSlotFive = SingleInventorySlot<ForRowThree>(3, 5)
val RowThreeSlotSix = SingleInventorySlot<ForRowThree>(3, 6)
val RowThreeSlotSeven = SingleInventorySlot<ForRowThree>(3, 7)
val RowThreeSlotEight = SingleInventorySlot<ForRowThree>(3, 8)
val RowThreeSlotNine = SingleInventorySlot<ForRowThree>(3, 9)
// ROW FOUR
val RowFourSlotOne = SingleInventorySlot<ForRowFour>(4, 1)
val RowFourSlotTwo = SingleInventorySlot<ForRowFour>(4, 2)
val RowFourSlotThree = SingleInventorySlot<ForRowFour>(4, 3)
val RowFourSlotFour = SingleInventorySlot<ForRowFour>(4, 4)
val RowFourSlotFive = SingleInventorySlot<ForRowFour>(4, 5)
val RowFourSlotSix = SingleInventorySlot<ForRowFour>(4, 6)
val RowFourSlotSeven = SingleInventorySlot<ForRowFour>(4, 7)
val RowFourSlotEight = SingleInventorySlot<ForRowFour>(4, 8)
val RowFourSlotNine = SingleInventorySlot<ForRowFour>(4, 9)
// ROW FIVE
val RowFiveSlotOne = SingleInventorySlot<ForRowFive>(5, 1)
val RowFiveSlotTwo = SingleInventorySlot<ForRowFive>(5, 2)
val RowFiveSlotThree = SingleInventorySlot<ForRowFive>(5, 3)
val RowFiveSlotFour = SingleInventorySlot<ForRowFive>(5, 4)
val RowFiveSlotFive = SingleInventorySlot<ForRowFive>(5, 5)
val RowFiveSlotSix = SingleInventorySlot<ForRowFive>(5, 6)
val RowFiveSlotSeven = SingleInventorySlot<ForRowFive>(5, 7)
val RowFiveSlotEight = SingleInventorySlot<ForRowFive>(5, 8)
val RowFiveSlotNine = SingleInventorySlot<ForRowFive>(5, 9)
// ROW SIX
val RowSixSlotOne = SingleInventorySlot<ForRowSix>(6, 1)
val RowSixSlotTwo = SingleInventorySlot<ForRowSix>(6, 2)
val RowSixSlotThree = SingleInventorySlot<ForRowSix>(6, 3)
val RowSixSlotFour = SingleInventorySlot<ForRowSix>(6, 4)
val RowSixSlotFive = SingleInventorySlot<ForRowSix>(6, 5)
val RowSixSlotSix = SingleInventorySlot<ForRowSix>(6, 6)
val RowSixSlotSeven = SingleInventorySlot<ForRowSix>(6, 7)
val RowSixSlotEight = SingleInventorySlot<ForRowSix>(6, 8)
val RowSixSlotNine = SingleInventorySlot<ForRowSix>(6, 9)
// ROW
val RowOne = InventoryRowSlots<ForCompleteRowOne>(1)
val RowTwo = InventoryRowSlots<ForCompleteRowTwo>(2)
val RowThree = InventoryRowSlots<ForCompleteRowThree>(3)
val RowFour = InventoryRowSlots<ForRowFour>(4)
val RowFive = InventoryRowSlots<ForRowFive>(5)
val RowSix = InventoryRowSlots<ForRowSix>(6)
// COLUMN
val ColumnOne = InventoryColumnSlots<ForColumnOne>(1)
val ColumnTwo = InventoryColumnSlots<ForColumnTwo>(2)
val ColumnThree = InventoryColumnSlots<ForColumnThree>(3)
val ColumnFour = InventoryColumnSlots<ForColumnFour>(4)
val ColumnFive = InventoryColumnSlots<ForColumnFive>(5)
val ColumnSix = InventoryColumnSlots<ForColumnSix>(6)
val ColumnSeven = InventoryColumnSlots<ForColumnSeven>(7)
val ColumnEight = InventoryColumnSlots<ForColumnEight>(8)
val ColumnNine = InventoryColumnSlots<ForColumnNine>(9)
// BORDER
val BorderPaddingOne = InventoryBorderSlots<ForEveryInventory>(1)
val BorderPaddingTwo = InventoryBorderSlots<ForEveryInventory>(2)
val BorderPaddingThree = InventoryBorderSlots<ForEveryInventory>(3)
val Border = BorderPaddingOne
// CORNER
val Corners = InventoryCornerSlots<ForEveryInventory>()
val CornersLeft = InventoryCornerSlots<ForEveryInventory>(ifBottomLeft = true, ifTopLeft = true)
val CornersRight = InventoryCornerSlots<ForEveryInventory>(ifBottomRight = true, ifTopRight = true)
val CornersBottom = InventoryCornerSlots<ForEveryInventory>(ifBottomLeft = true, ifBottomRight = true)
val CornersTop = InventoryCornerSlots<ForEveryInventory>(ifTopLeft = true, ifTopRight = true)
val CornerBottomLeft = InventoryCornerSlots<ForEveryInventory>(ifBottomLeft = true)
val CornerBottomRight = InventoryCornerSlots<ForEveryInventory>(ifBottomRight = true)
val CornerTopLeft = InventoryCornerSlots<ForEveryInventory>(ifTopLeft = true)
val CornerTopRight = InventoryCornerSlots<ForEveryInventory>(ifTopRight = true)
}

View File

@@ -0,0 +1,61 @@
@file:Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
package net.axay.kspigot.gui
import org.bukkit.Bukkit
import org.bukkit.event.inventory.InventoryType
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.InventoryHolder
class GUIType<in T : ForInventory>(
val dimensions: InventoryDimensions,
val bukkitType: InventoryType? = null
) {
companion object {
val ONE_BY_NINE = GUIType<ForInventoryOneByNine>(InventoryDimensions(9, 1))
val TWO_BY_NINE = GUIType<ForInventoryTwoByNine>(InventoryDimensions(9, 2))
val THREE_BY_NINE = GUIType<ForInventoryThreeByNine>(InventoryDimensions(9, 3))
val FOUR_BY_NINE = GUIType<ForInventoryFourByNine>(InventoryDimensions(9, 4))
val FIVE_BY_NINE = GUIType<ForInventoryFiveByNine>(InventoryDimensions(9, 5))
val SIX_BY_NINE = GUIType<ForInventorySixByNine>(InventoryDimensions(9, 6))
val ONE_BY_FIVE =
GUIType<ForInventoryOneByFive>(InventoryDimensions(5, 1), bukkitType = InventoryType.HOPPER)
val THREE_BY_THREE =
GUIType<ForInventoryThreeByThree>(InventoryDimensions(3, 3), bukkitType = InventoryType.DROPPER)
}
fun createBukkitInv(holder: InventoryHolder? = null, title: String? = null): Inventory {
val realTitle = title ?: ""
return when {
bukkitType != null -> Bukkit.createInventory(holder, bukkitType, realTitle)
else -> Bukkit.createInventory(holder, dimensions.slotAmount, realTitle)
}
}
}
// INVENTORY TYPE SAFETY
interface ForInventory
interface ForInventoryThreeByThree : ForInventoryThreeByNine
interface ForInventoryOneByFive : ForInventoryOneByNine
interface ForInventoryOneByNine : ForInventoryTwoByNine
interface ForInventoryTwoByNine : ForInventoryThreeByNine
interface ForInventoryThreeByNine : ForInventoryFourByNine
interface ForInventoryFourByNine : ForInventoryFiveByNine
interface ForInventoryFiveByNine : ForInventorySixByNine
interface ForInventorySixByNine : ForInventory
interface ForEveryInventory
: ForInventoryOneByNine, ForInventoryTwoByNine, ForInventoryThreeByNine,
ForInventoryFourByNine, ForInventoryFiveByNine, ForInventorySixByNine,
ForInventoryThreeByThree, ForInventoryOneByFive
interface ForInventoryWidthThree : ForInventoryThreeByThree
interface ForInventoryWidthFive : ForInventoryOneByFive
interface ForInventoryWidthNine : ForInventoryOneByNine, ForInventoryTwoByNine, ForInventoryThreeByNine,
ForInventoryFourByNine, ForInventoryFiveByNine, ForInventorySixByNine

View File

@@ -0,0 +1,18 @@
package net.axay.kspigot.gui.elements
import net.axay.kspigot.gui.*
import org.bukkit.inventory.ItemStack
open class GUIButton<T : ForInventory>(
private val icon: ItemStack,
private val action: (GUIClickEvent<T>) -> Unit,
) : GUIElement<T>() {
final override fun getItemStack(slot: Int) = icon
override fun onClickElement(clickEvent: GUIClickEvent<T>) {
clickEvent.bukkitEvent.isCancelled = true
action(clickEvent)
}
}

View File

@@ -0,0 +1,26 @@
package net.axay.kspigot.gui.elements
import net.axay.kspigot.gui.*
import org.bukkit.inventory.ItemStack
class GUIButtonInventoryChange<T : ForInventory>(
icon: ItemStack,
changeToGUICallback: () -> GUI<*>,
changeToPageInt: Int?,
onChange: ((GUIClickEvent<T>) -> Unit)?
) : GUIButton<T>(icon, {
val changeToGUI = changeToGUICallback.invoke()
val effect = (changeToGUI.data.transitionTo ?: it.gui.data.transitionFrom)
?: InventoryChangeEffect.INSTANT
val changeToPage = changeToGUI.getPage(changeToPageInt) ?: changeToGUI.currentPage
changeToGUI.changeGUI(effect, it.gui.currentPage, changeToPage)
it.bukkitEvent.whoClicked.openGUI(changeToGUI)
onChange?.invoke(it)
})

View File

@@ -0,0 +1,25 @@
package net.axay.kspigot.gui.elements
import net.axay.kspigot.gui.*
import org.bukkit.inventory.ItemStack
class GUIButtonPageChange<T : ForInventory>(
icon: ItemStack,
calculator: GUIPageChangeCalculator,
onChange: ((GUIClickEvent<T>) -> Unit)?
) : GUIButton<T>(icon, {
val currentPage = it.gui.currentPage
val newPage = it.gui.getPage(calculator.calculateNewPage(it.gui.currentPageInt, it.gui.data.pages.keys))
if (newPage != null) {
val effect = (newPage.transitionTo ?: currentPage.transitionFrom)
?: PageChangeEffect.INSTANT
it.gui.changePage(effect, currentPage, newPage)
onChange?.invoke(it)
}
})

View File

@@ -0,0 +1,11 @@
package net.axay.kspigot.gui.elements
import net.axay.kspigot.gui.ForInventory
import net.axay.kspigot.gui.GUIClickEvent
import net.axay.kspigot.gui.GUISlot
class GUIFreeSlot<T : ForInventory> : GUISlot<T>() {
override fun onClick(clickEvent: GUIClickEvent<T>) {
/* do nothing */
}
}

View File

@@ -0,0 +1,16 @@
package net.axay.kspigot.gui.elements
import net.axay.kspigot.gui.*
import org.bukkit.inventory.ItemStack
class GUIPlaceholder<T : ForInventory>(
private val icon: ItemStack
) : GUIElement<T>() {
override fun getItemStack(slot: Int) = icon
override fun onClickElement(clickEvent: GUIClickEvent<T>) {
clickEvent.bukkitEvent.isCancelled = true
}
}

View File

@@ -0,0 +1,166 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package net.axay.kspigot.gui.elements
import net.axay.kspigot.gui.*
import org.bukkit.Material
import org.bukkit.inventory.ItemStack
class GUISpaceCompoundElement<T : ForInventory, E> internal constructor(
private val compound: AbstractGUISpaceCompound<T, E>
) : GUIElement<T>() {
override fun getItemStack(slot: Int) = compound.getItemStack(slot)
override fun onClickElement(clickEvent: GUIClickEvent<T>) {
compound.onClickElement(clickEvent)
}
override fun startUsing(gui: GUI<*>) = compound.registerGUI(gui)
override fun stopUsing(gui: GUI<*>) = compound.unregisterGUI(gui)
}
class GUIRectSpaceCompound<T : ForInventory, E>(
invType: GUIType<T>,
iconGenerator: (E) -> ItemStack,
onClick: (GUIClickEvent<T>, E) -> Unit,
internal val compoundWidth: Int
) : AbstractGUISpaceCompound<T, E>(invType, iconGenerator, onClick) {
override fun handleScrollEndReached(newProgress: Int, internalSlotsSize: Int, contentSize: Int) =
(internalSlotsSize + newProgress <= contentSize + (compoundWidth - (contentSize % compoundWidth)))
}
class GUISpaceCompound<T : ForInventory, E>(
invType: GUIType<T>,
iconGenerator: (E) -> ItemStack,
onClick: (GUIClickEvent<T>, E) -> Unit
) : AbstractGUISpaceCompound<T, E>(invType, iconGenerator, onClick) {
override fun handleScrollEndReached(newProgress: Int, internalSlotsSize: Int, contentSize: Int) = false
}
abstract class AbstractGUISpaceCompound<T : ForInventory, E> internal constructor(
val guiType: GUIType<T>,
private val iconGenerator: (E) -> ItemStack,
private val onClick: (GUIClickEvent<T>, E) -> Unit
) {
private val content = ArrayList<E>()
private var currentContent: List<E> = emptyList()
private val internalSlots: MutableList<Int> = ArrayList()
private var scrollProgress: Int = 0
private var contentSort: () -> Unit = { }
private val registeredGUIs = HashSet<GUI<*>>()
private fun contentAtSlot(slot: Int) = currentContent.getOrNull(internalSlots.indexOf(slot))
private fun recalculateCurrentContent() {
if (scrollProgress >= content.size)
throw IllegalStateException("The scrollProgress is greater than the content size.")
// avoid IndexOutOfBoundsException
var sliceUntil = internalSlots.size + scrollProgress
if (sliceUntil > content.lastIndex)
sliceUntil = content.size
currentContent = content.slice(scrollProgress until sliceUntil)
}
private fun updateOpenGUIs() {
registeredGUIs.forEach { it.reloadCurrentPage() }
}
internal fun scroll(distance: Int): Boolean {
val value = scrollProgress + distance
return if (value >= 0) {
// always scroll if the end of the content is not reached
val ifScroll = if (internalSlots.size + value <= content.size) true
// scroll further if the width of the compound is defined and the last line can be filled up
else handleScrollEndReached(value, internalSlots.size, content.size)
if (ifScroll) {
scrollProgress = value
recalculateCurrentContent()
updateOpenGUIs()
true
} else false
} else false
}
internal abstract fun handleScrollEndReached(newProgress: Int, internalSlotsSize: Int, contentSize: Int): Boolean
internal fun getItemStack(slot: Int): ItemStack {
return contentAtSlot(slot)?.let { return@let iconGenerator.invoke(it) }
?: ItemStack(Material.AIR)
}
internal fun onClickElement(clickEvent: GUIClickEvent<T>) {
val element = contentAtSlot(clickEvent.bukkitEvent.slot) ?: kotlin.run {
clickEvent.bukkitEvent.isCancelled = true
return
}
onClick.invoke(clickEvent, element)
}
internal fun addSlots(slots: InventorySlotCompound<T>) {
slots.realSlotsWithInvType(guiType).forEach {
if (!internalSlots.contains(it))
internalSlots.add(it)
}
internalSlots.sort()
}
internal fun registerGUI(gui: GUI<*>) {
registeredGUIs += gui
}
internal fun unregisterGUI(gui: GUI<*>) {
registeredGUIs -= gui
}
/**
* Defines the sort behaviour which gets applied to the content
* automatically.
*/
fun <R : Comparable<R>> sortContentBy(reverse: Boolean = false, selector: (E) -> R?) {
contentSort = {
if (!reverse) content.sortBy(selector) else content.sortByDescending(selector)
}
contentSort.invoke()
}
/**
* Adds a new element to the compound.
*/
fun addContent(element: E) {
addContent(listOf(element))
}
/**
* Adds new elements to the compound.
*/
fun addContent(elements: Collection<E>) {
content += elements
contentSort.invoke()
recalculateCurrentContent()
updateOpenGUIs()
}
}

View File

@@ -0,0 +1,24 @@
package net.axay.kspigot.gui.elements
import net.axay.kspigot.gui.ForInventory
import net.axay.kspigot.runnables.task
import org.bukkit.inventory.ItemStack
class GUISpaceCompoundScrollButton<T : ForInventory>(
icon: ItemStack,
private val compound: AbstractGUISpaceCompound<T, *>,
private val scrollDistance: Int,
private val scrollTimes: Int,
private val reverse: Boolean = false
) : GUIButton<T>(icon, {
if (scrollTimes > 1) {
task(
period = 1,
howOften = scrollTimes.toLong()
) {
val ifScrolled = if (reverse) compound.scroll(-scrollDistance) else compound.scroll(scrollDistance)
if (!ifScrolled) it.cancel()
}
} else if (scrollTimes == 1)
if (reverse) compound.scroll(-scrollDistance) else compound.scroll(scrollDistance)
})