diff --git a/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUI.kt b/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUI.kt index a6b968a6..154ea37c 100644 --- a/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUI.kt +++ b/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUI.kt @@ -17,7 +17,7 @@ fun HumanEntity.openGUI(gui: InventoryGUI<*>, page: Int? = null): InventoryView? closeInventory() if (page != null) - gui.loadPage(page) + gui.loadPageUnsafe(page) return openInventory(gui.bukkitInventory) @@ -42,7 +42,7 @@ class InventoryGUIHolder(kSpigot: KSpigot) : AutoCloseable { kSpigot.listen { val inv = registered.find { search -> search.isThisInv(it.inventory) } ?: return@listen - val invPage = inv.currentPage + val invPage = inv.currentPageInt val slot = inv.data.pages[invPage]?.slots?.get(it.slot) if (slot != null) @@ -77,28 +77,59 @@ class InventoryGUIData( val plugin: KSpigot, val inventoryType: InventoryType, val title: String?, - val pages: Map> + internal val pages: Map>, + val transitionTo: InventoryGUIPageChangeEffect?, + val transitionFrom: InventoryGUIPageChangeEffect?, + internal val generalOnClick: ((InventoryGUIClickEvent) -> Unit)? ) abstract class InventoryGUI( val data: InventoryGUIData ) { - var currentPage: Int = DEFAULT_PAGE; protected set + var currentPageInt: Int = DEFAULT_PAGE; protected set + val currentPage get() = getPage(currentPageInt) + ?: throw IllegalStateException("The currentPageInt has no associated page!") - abstract val bukkitInventory: Inventory + internal abstract val bukkitInventory: Inventory - abstract fun loadPage(page: Int, offsetHorizontally: Int = 0, offsetVertically: Int = 0) + internal abstract fun loadPageUnsafe(page: InventoryGUIPage<*>?, offsetHorizontally: Int = 0, offsetVertically: Int = 0) + internal abstract fun loadPageUnsafe(page: Int, offsetHorizontally: Int = 0, offsetVertically: Int = 0) + /** + * @return True, if the [inventory] belongs to this GUI. + */ abstract fun isThisInv(inventory: Inventory): Boolean - abstract operator fun set(slot: InventorySlotCompound, value: ItemStack) - + /** + * Registers this InventoryGUI. + * (KSpigot will listen for actions in the inventory.) + */ @Suppress("UNCHECKED_CAST") fun register() = data.plugin.inventoryGUIHolder.register(this as InventoryGUI) + + /** + * Stops KSpigot from listening to actions in this + * InventoryGUI anymore. + */ @Suppress("UNCHECKED_CAST") fun unregister() = data.plugin.inventoryGUIHolder.unregister(this as InventoryGUI) + /** + * Loads the specified page in order to display it in the GUI. + */ + fun loadPage(page: InventoryGUIPage) = loadPageUnsafe(page) + + /** + * Temporarily sets the given item at the given slots. + */ + abstract operator fun set(slot: InventorySlotCompound, value: ItemStack) + + /** + * Searches for a page associated to the given [page] index. + */ + fun getPage(page: Int?) = data.pages[page] + } // Inventory GUI implementations @@ -109,21 +140,24 @@ class InventoryGUIShared( override val bukkitInventory = data.inventoryType.createBukkitInv(null, data.title) - init { loadPage(DEFAULT_PAGE) } + init { loadPageUnsafe(DEFAULT_PAGE) } override fun isThisInv(inventory: Inventory) = inventory == bukkitInventory - override fun loadPage(page: Int, offsetHorizontally: Int, offsetVertically: Int) { + override fun loadPageUnsafe(page: InventoryGUIPage<*>?, offsetHorizontally: Int, offsetVertically: Int) { - fun ifOffset(): Boolean = offsetHorizontally != 0 || offsetVertically != 0 + if (page == null) return - currentPage = page + val ifOffset = offsetHorizontally != 0 || offsetVertically != 0 - data.pages[page]?.slots?.let { slots -> + if (!ifOffset) + currentPageInt = page.number + + page.slots.let { slots -> val dimensions = data.inventoryType.dimensions - if (ifOffset()) { + if (ifOffset) { dimensions.invSlots.forEach { dimensions.invSlotsWithRealSlots[it.add(offsetHorizontally, offsetVertically)]?.let { slotToClear -> if (dimensions.realSlots.contains(slotToClear)) @@ -138,7 +172,7 @@ class InventoryGUIShared( val slot = it.value if (slot is InventoryGUIElement) { - if (ifOffset()) { + if (ifOffset) { val invSlot = InventorySlot.fromRealSlot(it.key, dimensions) if (invSlot != null) { val offsetSlot = invSlot.add(offsetHorizontally, offsetVertically).realSlotIn(dimensions) @@ -157,6 +191,10 @@ class InventoryGUIShared( } + override fun loadPageUnsafe(page: Int, offsetHorizontally: Int, offsetVertically: Int) { + data.pages[page]?.let { loadPageUnsafe(it, offsetHorizontally, offsetVertically) } + } + override operator fun set(slot: InventorySlotCompound, value: ItemStack) { slot.realSlotsWithInvType(data.inventoryType).forEach { bukkitInventory.setItem(it, value) @@ -166,10 +204,8 @@ class InventoryGUIShared( } class InventoryGUIPage( - val slots: Map>, - transitionTo: InventoryGUIPageChangeEffect?, - transitionFrom: InventoryGUIPageChangeEffect? -) { - val pageChangerTo = transitionTo?.let { InventoryGUIPageChanger(it) } - val pageChangerFrom = transitionFrom?.let { InventoryGUIPageChanger(it) } -} \ No newline at end of file + val number: Int, + internal val slots: Map>, + val transitionTo: InventoryGUIPageChangeEffect?, + val transitionFrom: InventoryGUIPageChangeEffect? +) \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIBuilder.kt b/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIBuilder.kt index 78a2053c..c871aafb 100644 --- a/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIBuilder.kt +++ b/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIBuilder.kt @@ -5,7 +5,7 @@ package net.axay.kspigot.inventory import net.axay.kspigot.main.KSpigot import org.bukkit.inventory.ItemStack -inline fun KSpigot.inventoryGUI( +fun KSpigot.inventoryGUI( type: InventoryType, builder: InventoryGUIBuilder.() -> Unit, ) = InventoryGUIBuilder(this, type).apply(builder).build() @@ -17,16 +17,29 @@ class InventoryGUIBuilder( var title: String = "" + var transitionTo: InventoryGUIPageChangeEffect? = null + var transitionFrom: InventoryGUIPageChangeEffect? = null + private val guiSlots = HashMap>() + private var onClickElement: ((InventoryGUIClickEvent) -> 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: InventoryGUIPageBuilder.() -> Unit) { guiSlots[page] = InventoryGUIPageBuilder(type, page).apply(builder).build() } - fun build() = InventoryGUIShared(InventoryGUIData(kSpigot, type, title, guiSlots)).apply { register() } + fun onClickElement(onClick: (InventoryGUIClickEvent) -> Unit) { + onClickElement = onClick + } + + internal fun build() = InventoryGUIShared( + InventoryGUIData(kSpigot, type, title, guiSlots, transitionTo, transitionFrom, onClickElement) + ).apply { register() } } @@ -40,20 +53,22 @@ class InventoryGUIPageBuilder( var transitionTo: InventoryGUIPageChangeEffect? = null var transitionFrom: InventoryGUIPageChangeEffect? = null + internal fun build() = InventoryGUIPage(page, guiSlots, transitionTo, transitionFrom) + /** * A button is an item protected from any player * actions. If clicked, the specified [onClick] * function is invoked. */ fun button(slots: InventorySlotCompound, itemStack: ItemStack, onClick: (InventoryGUIClickEvent) -> Unit) - = slots(slots, InventoryGUIButton(InventoryGUIElementData(itemStack), onClick)) + = defineSlots(slots, InventoryGUIButton(InventoryGUIElementData(itemStack), onClick)) /** * An item protected from any player actions. * This is not a button. */ fun placeholder(slots: InventorySlotCompound, itemStack: ItemStack) - = slots(slots, InventoryGUIPlaceholder(InventoryGUIElementData(itemStack))) + = defineSlots(slots, InventoryGUIPlaceholder(InventoryGUIElementData(itemStack))) /** * A free slot does not block any player actions. @@ -61,14 +76,14 @@ class InventoryGUIPageBuilder( * items out of it. */ fun freeSlot(slots: InventorySlotCompound) - = slots(slots, InventoryGUIFreeSlot()) + = defineSlots(slots, InventoryGUIFreeSlot()) /** * This is a button which loads the specified * [toPage] if clicked. */ fun pageChanger(slots: InventorySlotCompound, itemStack: ItemStack, toPage: Int, onChange: ((InventoryGUIClickEvent) -> Unit)? = null) - = slots(slots, InventoryGUIButtonPageChange( + = defineSlots(slots, InventoryGUIButtonPageChange( InventoryGUIElementData(itemStack), InventoryGUIPageChangeCalculator.InventoryGUIConsistentPageCalculator(toPage), onChange @@ -80,7 +95,7 @@ class InventoryGUIPageBuilder( * exists it is loaded. */ fun previousPage(slots: InventorySlotCompound, itemStack: ItemStack, onChange: ((InventoryGUIClickEvent) -> Unit)? = null) - = slots(slots, InventoryGUIButtonPageChange( + = defineSlots(slots, InventoryGUIButtonPageChange( InventoryGUIElementData(itemStack), InventoryGUIPageChangeCalculator.InventoryGUIPreviousPageCalculator, onChange @@ -92,17 +107,27 @@ class InventoryGUIPageBuilder( * exists it is loaded. */ fun nextPage(slots: InventorySlotCompound, itemStack: ItemStack, onChange: ((InventoryGUIClickEvent) -> Unit)? = null) - = slots(slots, InventoryGUIButtonPageChange( + = defineSlots(slots, InventoryGUIButtonPageChange( InventoryGUIElementData(itemStack), InventoryGUIPageChangeCalculator.InventoryGUINextPageCalculator, onChange )) - private fun slots(slots: InventorySlotCompound, element: InventoryGUISlot) + /** + * By pressing this button, the player switches to another + * InventoryGUI. The transition effect is applied. + */ + fun changeGUI(slots: InventorySlotCompound, itemStack: ItemStack, newGUI: InventoryGUI<*>, newPage: Int? = null, onChange: ((InventoryGUIClickEvent) -> Unit)? = null) + = defineSlots(slots, InventoryGUIButtonInventoryChange( + InventoryGUIElementData(itemStack), + newGUI, + newPage, + onChange + )) + + private fun defineSlots(slots: InventorySlotCompound, element: InventoryGUISlot) = slots.withInvType(type).forEach { curSlot -> curSlot.realSlotIn(type.dimensions)?.let { guiSlots[it] = element } } - fun build() = InventoryGUIPage(guiSlots, transitionTo, transitionFrom) - } \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIElements.kt b/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIElements.kt index 7e3368a0..0f586cc8 100644 --- a/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIElements.kt +++ b/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIElements.kt @@ -2,8 +2,8 @@ package net.axay.kspigot.inventory import org.bukkit.inventory.ItemStack -interface InventoryGUISlot { - fun onClick(clickEvent: InventoryGUIClickEvent) +abstract class InventoryGUISlot { + abstract fun onClick(clickEvent: InventoryGUIClickEvent) } // ELEMENT @@ -14,7 +14,16 @@ class InventoryGUIElementData( abstract class InventoryGUIElement( val inventoryGUIElementData: InventoryGUIElementData -) : InventoryGUISlot +) : InventoryGUISlot() { + + final override fun onClick(clickEvent: InventoryGUIClickEvent) { + clickEvent.gui.data.generalOnClick?.invoke(clickEvent) + onClickElement(clickEvent) + } + + protected abstract fun onClickElement(clickEvent: InventoryGUIClickEvent) + +} // Element implementations @@ -23,7 +32,7 @@ open class InventoryGUIButton( val action: (InventoryGUIClickEvent) -> Unit, ) : InventoryGUIElement(inventoryGUIElementData) { - override fun onClick(clickEvent: InventoryGUIClickEvent) { + override fun onClickElement(clickEvent: InventoryGUIClickEvent) { clickEvent.bukkitEvent.isCancelled = true action(clickEvent) } @@ -34,7 +43,7 @@ class InventoryGUIPlaceholder( inventoryGUIElementData: InventoryGUIElementData ) : InventoryGUIElement(inventoryGUIElementData) { - override fun onClick(clickEvent: InventoryGUIClickEvent) { + override fun onClickElement(clickEvent: InventoryGUIClickEvent) { clickEvent.bukkitEvent.isCancelled = true } @@ -48,24 +57,44 @@ class InventoryGUIButtonPageChange( : InventoryGUIButton(inventoryGUIElementData, { val currentPage = it.gui.currentPage + val newPage = it.gui.getPage(calculator.calculateNewPage(it.gui.currentPageInt, it.gui.data.pages.keys)) + if (newPage != null) { - val newPageInt = calculator.calculateNewPage(currentPage, it.gui.data.pages.keys) - if (newPageInt != null) { + val effect = (newPage.transitionTo ?: currentPage.transitionFrom) + ?: InventoryGUIPageChangeEffect.INSTANT + + it.gui.changePage(effect, currentPage, newPage) onChange?.invoke(it) - val pageChanger - = (it.gui.data.pages[newPageInt]?.pageChangerTo ?: it.gui.data.pages[currentPage]?.pageChangerFrom) - ?: InventoryGUIPageChanger(InventoryGUIPageChangeEffect.INSTANT) - - pageChanger.changePage(it.gui, currentPage, newPageInt) - } }) +class InventoryGUIButtonInventoryChange( + inventoryGUIElementData: InventoryGUIElementData, + changeToGUI: InventoryGUI<*>, + changeToPageInt: Int?, + onChange: ((InventoryGUIClickEvent) -> Unit)? +) + : InventoryGUIButton(inventoryGUIElementData, { + + val effect = (changeToGUI.data.transitionTo ?: it.gui.data.transitionFrom) + ?: InventoryGUIPageChangeEffect.INSTANT + + val changeToPage = changeToGUI.getPage(changeToPageInt) ?: changeToGUI.currentPage + + changeToGUI.changePage(effect, it.gui.currentPage, changeToPage) + + it.bukkitEvent.whoClicked.openGUI(changeToGUI) + + onChange?.invoke(it) + + }) + + // FREE SLOT -class InventoryGUIFreeSlot : InventoryGUISlot { +class InventoryGUIFreeSlot : InventoryGUISlot() { override fun onClick(clickEvent: InventoryGUIClickEvent) { /* do nothing */ } } \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIPageChange.kt b/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIPageChange.kt index 0f21eb9d..f8c4b6bb 100644 --- a/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIPageChange.kt +++ b/src/main/kotlin/net/axay/kspigot/inventory/InventoryGUIPageChange.kt @@ -8,11 +8,13 @@ abstract class InventoryGUIPageChangeCalculator { abstract fun calculateNewPage(currentPage: Int, pages: Collection): Int? object InventoryGUIPreviousPageCalculator : InventoryGUIPageChangeCalculator() { - override fun calculateNewPage(currentPage: Int, pages: Collection) = pages.sortedDescending().find { it < currentPage } + override fun calculateNewPage(currentPage: Int, pages: Collection) + = pages.sortedDescending().find { it < currentPage } } object InventoryGUINextPageCalculator : InventoryGUIPageChangeCalculator() { - override fun calculateNewPage(currentPage: Int, pages: Collection) = pages.sorted().find { it > currentPage } + override fun calculateNewPage(currentPage: Int, pages: Collection) + = pages.sorted().find { it > currentPage } } class InventoryGUIConsistentPageCalculator(private val toPage: Int) : InventoryGUIPageChangeCalculator() { @@ -29,102 +31,98 @@ enum class InventoryGUIPageChangeEffect { SWIPE_VERTICALLY, } -class InventoryGUIPageChanger(private val effect: InventoryGUIPageChangeEffect) { +internal fun InventoryGUI<*>.changePage(effect: InventoryGUIPageChangeEffect, fromPage: InventoryGUIPage<*>?, toPage: InventoryGUIPage<*>?) { - fun changePage(gui: InventoryGUI<*>, fromPage: Int, toPage: Int) { - when (effect) { + val fromPageInt = fromPage?.number ?: 0 + val toPageInt = toPage?.number ?: 0 - InventoryGUIPageChangeEffect.INSTANT -> gui.loadPage(toPage) + when (effect) { - InventoryGUIPageChangeEffect.SLIDE_HORIZONTALLY -> { + InventoryGUIPageChangeEffect.INSTANT -> loadPageUnsafe(toPage) - val width = gui.data.inventoryType.dimensions.width + InventoryGUIPageChangeEffect.SLIDE_HORIZONTALLY -> { - changePageEffect(gui.data.plugin, fromPage, toPage, width) { currentOffset, ifInverted -> - if (ifInverted) { - gui.loadPage(fromPage, offsetHorizontally = currentOffset) - gui.loadPage(toPage, offsetHorizontally = -(width - currentOffset)) - } else { - gui.loadPage(fromPage, offsetHorizontally = -currentOffset) - gui.loadPage(toPage, offsetHorizontally = width - currentOffset) - } + val width = data.inventoryType.dimensions.width + + changePageEffect(data.plugin, 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) } - - } - - InventoryGUIPageChangeEffect.SLIDE_VERTICALLY -> { - - val height = gui.data.inventoryType.dimensions.heigth - - changePageEffect(gui.data.plugin, fromPage, toPage, height) { currentOffset, ifInverted -> - if (ifInverted) { - gui.loadPage(fromPage, offsetVertically = currentOffset) - gui.loadPage(toPage, offsetVertically = -(height - currentOffset)) - } else { - gui.loadPage(fromPage, offsetVertically = -currentOffset) - gui.loadPage(toPage, offsetVertically = height - currentOffset) - } - } - - } - - InventoryGUIPageChangeEffect.SWIPE_HORIZONTALLY -> { - - val width = gui.data.inventoryType.dimensions.width - - changePageEffect(gui.data.plugin, fromPage, toPage, width) { currentOffset, ifInverted -> - if (ifInverted) { - gui.loadPage(toPage, offsetHorizontally = -(width - currentOffset)) - } else { - gui.loadPage(toPage, offsetHorizontally = width - currentOffset) - } - } - - } - - InventoryGUIPageChangeEffect.SWIPE_VERTICALLY -> { - - val height = gui.data.inventoryType.dimensions.heigth - - changePageEffect(gui.data.plugin, fromPage, toPage, height) { currentOffset, ifInverted -> - if (ifInverted) { - gui.loadPage(toPage, offsetVertically = -(height - currentOffset)) - } else { - gui.loadPage(toPage, offsetVertically = height - currentOffset) - } - } - } } + + InventoryGUIPageChangeEffect.SLIDE_VERTICALLY -> { + + val height = data.inventoryType.dimensions.heigth + + changePageEffect(data.plugin, 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) + } + } + + } + + InventoryGUIPageChangeEffect.SWIPE_HORIZONTALLY -> { + + val width = data.inventoryType.dimensions.width + + changePageEffect(data.plugin, fromPageInt, toPageInt, width) { currentOffset, ifInverted -> + if (ifInverted) { + loadPageUnsafe(toPage, offsetHorizontally = -(width - currentOffset)) + } else { + loadPageUnsafe(toPage, offsetHorizontally = width - currentOffset) + } + } + + } + + InventoryGUIPageChangeEffect.SWIPE_VERTICALLY -> { + + val height = data.inventoryType.dimensions.heigth + + changePageEffect(data.plugin, fromPageInt, toPageInt, height) { currentOffset, ifInverted -> + if (ifInverted) { + loadPageUnsafe(toPage, offsetVertically = -(height - currentOffset)) + } else { + loadPageUnsafe(toPage, offsetVertically = height - currentOffset) + } + } + + } + } +} - companion object { +private inline fun changePageEffect( + kSpigot: KSpigot, + fromPage: Int, + toPage: Int, + doFor: Int, + crossinline effect: (currentOffset: Int, ifInverted: Boolean) -> Unit, +) { - private inline fun changePageEffect( - kSpigot: KSpigot, - fromPage: Int, - toPage: Int, - doFor: Int, - crossinline effect: (currentOffset: Int, ifInverted: Boolean) -> Unit, - ) { + val ifInverted = fromPage >= toPage - val ifInverted = fromPage >= toPage + var currentOffset = 1 + kSpigot.task( + sync = true, + period = 1, + howOften = doFor.toLong() + ) { - var currentOffset = 1 - kSpigot.task( - sync = true, - period = 1, - howOften = doFor.toLong() - ) { + effect.invoke(currentOffset, ifInverted) - effect.invoke(currentOffset, ifInverted) - - currentOffset++ - - } - - } + currentOffset++ }