From 40b4c26e0f1ebf69d01998898ef5d210bc6e00a5 Mon Sep 17 00:00:00 2001 From: Auxilor Date: Thu, 29 Sep 2022 17:17:13 +0100 Subject: [PATCH] Added menu pagination --- .../com/willfp/eco/core/gui/GUIFactory.java | 14 +++ .../eco/core/gui/component/GUIComponent.java | 17 ++- .../willfp/eco/core/gui/menu/MenuBuilder.java | 36 +++++- .../com/willfp/eco/core/gui/page/Page.java | 115 +++++++++++++++++ .../willfp/eco/core/gui/page/PageChanger.java | 116 ++++++++++++++++++ .../com/willfp/eco/core/gui/GUIHelpers.kt | 5 + .../willfp/eco/internal/gui/EcoGUIFactory.kt | 8 +- .../willfp/eco/internal/gui/menu/EcoMenu.kt | 13 +- .../eco/internal/gui/menu/EcoMenuBuilder.kt | 12 +- .../eco/internal/gui/page/DelegateMenu.kt | 25 ++++ .../willfp/eco/internal/gui/slot/EcoSlot.kt | 9 -- 11 files changed, 336 insertions(+), 34 deletions(-) create mode 100644 eco-api/src/main/java/com/willfp/eco/core/gui/page/Page.java create mode 100644 eco-api/src/main/java/com/willfp/eco/core/gui/page/PageChanger.java create mode 100644 eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/page/DelegateMenu.kt diff --git a/eco-api/src/main/java/com/willfp/eco/core/gui/GUIFactory.java b/eco-api/src/main/java/com/willfp/eco/core/gui/GUIFactory.java index 6f7ad705..d98c3868 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/gui/GUIFactory.java +++ b/eco-api/src/main/java/com/willfp/eco/core/gui/GUIFactory.java @@ -1,6 +1,7 @@ package com.willfp.eco.core.gui; import com.willfp.eco.core.Eco; +import com.willfp.eco.core.gui.menu.Menu; import com.willfp.eco.core.gui.menu.MenuBuilder; import com.willfp.eco.core.gui.slot.SlotBuilder; import com.willfp.eco.core.gui.slot.functional.SlotProvider; @@ -21,6 +22,7 @@ public interface GUIFactory { * @param provider The provider. * @return The builder. */ + @NotNull SlotBuilder createSlotBuilder(@NotNull SlotProvider provider); /** @@ -29,5 +31,17 @@ public interface GUIFactory { * @param rows The amount of rows. * @return The builder. */ + @NotNull MenuBuilder createMenuBuilder(int rows); + + /** + * Combine the state of two menus together. + * + * @param base The base menu. + * @param additional The additional state. + * @return The menu. + */ + @NotNull + Menu blendMenuState(@NotNull Menu base, + @NotNull Menu additional); } diff --git a/eco-api/src/main/java/com/willfp/eco/core/gui/component/GUIComponent.java b/eco-api/src/main/java/com/willfp/eco/core/gui/component/GUIComponent.java index fef5dfcc..997d0e1a 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/gui/component/GUIComponent.java +++ b/eco-api/src/main/java/com/willfp/eco/core/gui/component/GUIComponent.java @@ -1,11 +1,8 @@ package com.willfp.eco.core.gui.component; import com.willfp.eco.core.gui.menu.Menu; -import com.willfp.eco.core.gui.slot.FillerSlot; import com.willfp.eco.core.gui.slot.Slot; -import org.bukkit.Material; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,9 +35,9 @@ public interface GUIComponent { * @return The slot, or null if no slot at the location. */ @Nullable - default Slot getSlotAt(int row, - int column) { - return new FillerSlot(new ItemStack(Material.AIR)); + default Slot getSlotAt(final int row, + final int column) { + return null; } /** @@ -58,10 +55,10 @@ public interface GUIComponent { * @return The slot, or null if no slot at the location. */ @Nullable - default Slot getSlotAt(int row, - int column, - @NotNull Player player, - @NotNull Menu menu) { + default Slot getSlotAt(final int row, + final int column, + @NotNull final Player player, + @NotNull final Menu menu) { return getSlotAt(row, column); } } diff --git a/eco-api/src/main/java/com/willfp/eco/core/gui/menu/MenuBuilder.java b/eco-api/src/main/java/com/willfp/eco/core/gui/menu/MenuBuilder.java index 2be26ccb..7db36d17 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/gui/menu/MenuBuilder.java +++ b/eco-api/src/main/java/com/willfp/eco/core/gui/menu/MenuBuilder.java @@ -1,6 +1,7 @@ package com.willfp.eco.core.gui.menu; import com.willfp.eco.core.gui.component.GUIComponent; +import com.willfp.eco.core.gui.page.Page; import com.willfp.eco.core.gui.slot.FillerMask; import com.willfp.eco.core.gui.slot.Slot; import org.bukkit.entity.Player; @@ -9,6 +10,7 @@ import org.jetbrains.annotations.NotNull; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Function; /** * Builder to create menus. @@ -70,17 +72,47 @@ public interface MenuBuilder { * @param mask The mask. * @return The builder. */ - default MenuBuilder setMask(@NotNull FillerMask mask) { + default MenuBuilder setMask(@NotNull final FillerMask mask) { return this.addComponent(0, 0, mask); } + /** + * Add a page. + * + * @param page The page. + * @return The builder. + */ + default MenuBuilder addPage(@NotNull final Page page) { + return this.addComponent(0, 0, page); + } + + /** + * Set the max pages. + * + * @param pages The max pages. + * @return The builder. + */ + default MenuBuilder maxPages(final int pages) { + return this.maxPages(player -> pages); + } + + /** + * Set the max pages dynamically for a player. + * + * @param pages The max pages. + * @return The builder. + */ + default MenuBuilder maxPages(@NotNull final Function pages) { + return onOpen((player, menu) -> menu.addState(player, Page.MAX_PAGE_KEY, pages.apply(player))); + } + /** * Set the menu close handler. * * @param action The handler. * @return The builder. */ - default MenuBuilder onClose(@NotNull Consumer action) { + default MenuBuilder onClose(@NotNull final Consumer action) { return this.onClose((event, menu) -> action.accept(event)); } diff --git a/eco-api/src/main/java/com/willfp/eco/core/gui/page/Page.java b/eco-api/src/main/java/com/willfp/eco/core/gui/page/Page.java new file mode 100644 index 00000000..83591807 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/gui/page/Page.java @@ -0,0 +1,115 @@ +package com.willfp.eco.core.gui.page; + +import com.willfp.eco.core.Eco; +import com.willfp.eco.core.gui.component.GUIComponent; +import com.willfp.eco.core.gui.menu.Menu; +import com.willfp.eco.core.gui.slot.Slot; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/** + * A page is a component representing another menu. + * This allows full component support in pagination. + */ +public final class Page implements GUIComponent { + /** + * The Menu state key for the current page. + */ + public static final String PAGE_KEY = "page"; + + /** + * The Menu state key for the amount of pages. + */ + public static final String MAX_PAGE_KEY = "max_page"; + + /** + * The page number. + */ + private final int pageNumber; + + /** + * The base menu. + */ + private final Menu page; + + /** + * The delegate menu. + */ + private Menu delegate = null; + + /** + * Create a new page. + * + * @param pageNumber The page number. + * @param page The base menu. + */ + public Page(final int pageNumber, + @NotNull final Menu page) { + this.pageNumber = pageNumber; + this.page = page; + } + + /** + * Get the current page number. + * + * @return The page number. + */ + public int getPageNumber() { + return this.pageNumber; + } + + @Override + public @Nullable Slot getSlotAt(final int row, + final int column, + @NotNull final Player player, + @NotNull final Menu menu) { + if (getPage(player, menu) != pageNumber) { + return null; + } + + if (delegate == null) { + delegate = Eco.getHandler().getGUIFactory().blendMenuState(page, menu); + } + + return page.getSlot(row, column, player, delegate); + } + + @Override + public int getRows() { + return page.getRows(); + } + + @Override + public int getColumns() { + return 9; + } + + /** + * Get the page. + * + * @param player The player. + * @param menu The menu. + * @return The page. + */ + public static int getPage(@NotNull final Player player, + @NotNull final Menu menu) { + Integer pageState = menu.getState(player, Page.PAGE_KEY); + return Objects.requireNonNullElse(pageState, 1); + } + + /** + * Get the page. + * + * @param player The player. + * @param menu The menu. + * @return The page. + */ + public static int getMaxPage(@NotNull final Player player, + @NotNull final Menu menu) { + Integer pageState = menu.getState(player, Page.MAX_PAGE_KEY); + return Objects.requireNonNullElse(pageState, Integer.MAX_VALUE); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/gui/page/PageChanger.java b/eco-api/src/main/java/com/willfp/eco/core/gui/page/PageChanger.java new file mode 100644 index 00000000..c978cbbc --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/gui/page/PageChanger.java @@ -0,0 +1,116 @@ +package com.willfp.eco.core.gui.page; + +import com.willfp.eco.core.gui.component.GUIComponent; +import com.willfp.eco.core.gui.menu.Menu; +import com.willfp.eco.core.gui.slot.Slot; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A slot loaded in from config. + */ +public final class PageChanger implements GUIComponent { + /** + * The slot to be shown. + */ + private final Slot slot; + + /** + * The direction to turn the page. + */ + private final Direction direction; + + /** + * Create a new page change slot. + * + * @param direction The direction. + */ + public PageChanger(@NotNull final ItemStack itemStack, + @NotNull final Direction direction) { + this.direction = direction; + + slot = Slot.builder(itemStack) + .onLeftClick((event, slot, menu) -> { + Player player = (Player) event.getWhoClicked(); + int page = Page.getPage(player, menu); + int newPage = Math.max( + 0, + Math.min( + page + direction.getChange(), + Page.getMaxPage(player, menu) + ) + ); + menu.addState(player, Page.PAGE_KEY, newPage); + }) + .build(); + } + + @Override + public int getRows() { + return 1; + } + + @Override + public int getColumns() { + return 1; + } + + @Override + public @Nullable Slot getSlotAt(final int row, + final int column, + @NotNull final Player player, + @NotNull final Menu menu) { + int page = Page.getPage(player, menu); + int maxPage = Page.getMaxPage(player, menu); + + if (page <= 1 && this.direction == Direction.BACKWARDS) { + return null; + } + + if (page >= maxPage - 1 && this.direction == Direction.FORWARDS) { + return null; + } + + return slot; + } + + /** + * The direction to change the page. + */ + public enum Direction { + /** + * Increment the page by 1. + */ + FORWARDS(1), + + /** + * Decrement the page by 1. + */ + BACKWARDS(-1); + + /** + * The amount of pages to change by. + */ + private final int change; + + /** + * Create a new direction. + * + * @param change The amount of pages to change by. + */ + Direction(final int change) { + this.change = change; + } + + /** + * Get the amount of pages to change by. + * + * @return The change. + */ + public int getChange() { + return change; + } + } +} diff --git a/eco-api/src/main/kotlin/com/willfp/eco/core/gui/GUIHelpers.kt b/eco-api/src/main/kotlin/com/willfp/eco/core/gui/GUIHelpers.kt index fbfee141..83e456cb 100644 --- a/eco-api/src/main/kotlin/com/willfp/eco/core/gui/GUIHelpers.kt +++ b/eco-api/src/main/kotlin/com/willfp/eco/core/gui/GUIHelpers.kt @@ -4,6 +4,7 @@ package com.willfp.eco.core.gui import com.willfp.eco.core.gui.menu.Menu import com.willfp.eco.core.gui.menu.MenuBuilder +import com.willfp.eco.core.gui.page.Page import com.willfp.eco.core.gui.slot.Slot import com.willfp.eco.core.gui.slot.SlotBuilder import com.willfp.eco.core.items.TestableItem @@ -131,6 +132,10 @@ fun MenuBuilder.modify(modifier: (MenuBuilder) -> Unit): MenuBuilder = fun MenuBuilder.onRender(action: (Player, Menu) -> Unit): MenuBuilder = this.onRender { a, b -> action(a, b) } +/** @see MenuBuilder.addPage */ +fun MenuBuilder.addPage(page: Int, creation: MenuBuilder.() -> MenuBuilder): MenuBuilder = + this.addPage(Page(page, Menu.builder(this.rows).creation().build())) + /** Kotlin builder for menus. */ fun menu( rows: Int, diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/EcoGUIFactory.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/EcoGUIFactory.kt index f308a87f..a6c7605b 100644 --- a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/EcoGUIFactory.kt +++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/EcoGUIFactory.kt @@ -1,10 +1,12 @@ package com.willfp.eco.internal.gui import com.willfp.eco.core.gui.GUIFactory +import com.willfp.eco.core.gui.menu.Menu import com.willfp.eco.core.gui.menu.MenuBuilder import com.willfp.eco.core.gui.slot.SlotBuilder import com.willfp.eco.core.gui.slot.functional.SlotProvider import com.willfp.eco.internal.gui.menu.EcoMenuBuilder +import com.willfp.eco.internal.gui.page.DelegateMenu import com.willfp.eco.internal.gui.slot.EcoSlotBuilder object EcoGUIFactory : GUIFactory { @@ -15,4 +17,8 @@ object EcoGUIFactory : GUIFactory { override fun createMenuBuilder(rows: Int): MenuBuilder { return EcoMenuBuilder(rows) } -} \ No newline at end of file + + override fun blendMenuState(base: Menu, additional: Menu): Menu { + return DelegateMenu(base, additional) + } +} diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/menu/EcoMenu.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/menu/EcoMenu.kt index 65968598..3e18749e 100644 --- a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/menu/EcoMenu.kt +++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/menu/EcoMenu.kt @@ -21,9 +21,9 @@ class EcoMenu( private val rows: Int, private val componentsAtPoints: Map>, private val title: String, - private val onClose: CloseHandler, - private val onRender: (Player, Menu) -> Unit, - private val onOpen: OpenHandler + private val onClose: List, + private val onRender: List<(Player, Menu) -> Unit>, + private val onOpen: List ) : Menu { private fun getPossiblyReactiveSlot(row: Int, column: Int, player: Player?, menu: Menu?): Slot { if (row < 1 || row > this.rows || column < 1 || column > 9) { @@ -65,14 +65,14 @@ class EcoMenu( player.openInventory(inventory) - onOpen.handle(player, this) + onOpen.forEach { it.handle(player, this) } inventory.asRenderedInventory()?.generateCaptive() return inventory } fun handleClose(event: InventoryCloseEvent) { - onClose.handle(event, this) + onClose.forEach { it.handle(event, this) } event.inventory.asRenderedInventory()?.generateCaptive() MenuHandler.unregisterInventory(event.inventory) } @@ -137,7 +137,8 @@ class EcoMenu( player.openInventory.topInventory.asRenderedInventory()?.render() } - fun runOnRender(player: Player) = onRender(player, this) + fun runOnRender(player: Player) = + onRender.forEach { it(player, this) } } data class OffsetComponent( diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/menu/EcoMenuBuilder.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/menu/EcoMenuBuilder.kt index 638b79dd..eedf9dbe 100644 --- a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/menu/EcoMenuBuilder.kt +++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/menu/EcoMenuBuilder.kt @@ -13,9 +13,9 @@ import java.util.function.Consumer class EcoMenuBuilder(private val rows: Int) : MenuBuilder { private var title = "Menu" private val components = mutableMapOf>() - private var onClose = CloseHandler { _, _ -> } - private var onOpen = OpenHandler { _, _ -> } - private var onRender: (Player, Menu) -> Unit = { _, _ -> } + private var onClose = mutableListOf() + private var onOpen = mutableListOf() + private var onRender = mutableListOf<(Player, Menu) -> Unit>() override fun getRows() = rows @@ -42,17 +42,17 @@ class EcoMenuBuilder(private val rows: Int) : MenuBuilder { } override fun onClose(action: CloseHandler): MenuBuilder { - onClose = action + onClose += action return this } override fun onOpen(action: OpenHandler): MenuBuilder { - onOpen = action + onOpen += action return this } override fun onRender(action: BiConsumer): MenuBuilder { - onRender = { a, b -> action.accept(a, b) } + onRender += { a, b -> action.accept(a, b) } return this } diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/page/DelegateMenu.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/page/DelegateMenu.kt new file mode 100644 index 00000000..3168ba73 --- /dev/null +++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/page/DelegateMenu.kt @@ -0,0 +1,25 @@ +package com.willfp.eco.internal.gui.page + +import com.willfp.eco.core.gui.menu.Menu +import org.bukkit.entity.Player + +class DelegateMenu( + private val base: Menu, + private val additional: Menu +) : Menu by base { + override fun getState(player: Player): Map { + return base.getState(player) + additional.getState(player) + } + + override fun addState(player: Player, key: String, value: Any?) { + base.addState(player, key, value) + } + + override fun clearState(player: Player) { + base.clearState(player) + } + + override fun removeState(player: Player, key: String) { + base.removeState(player, key) + } +} diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/slot/EcoSlot.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/slot/EcoSlot.kt index 2707211d..0d48c74b 100644 --- a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/slot/EcoSlot.kt +++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/slot/EcoSlot.kt @@ -33,15 +33,6 @@ open class EcoSlot( return updater.update(player, menu, prev) ?: ItemStack(Material.AIR) } - fun getItemStack( - player: Player, - menu: Menu - ): ItemStack { - val prev = provider.provide(player, menu) - val updated = updater.update(player, menu, prev) - return updated ?: ItemStack(Material.AIR) - } - override fun isCaptive(): Boolean { return false }