├── .idea ├── .name ├── .gitignore ├── vcs.xml ├── inspectionProfiles │ └── Project_Default.xml ├── discord.xml ├── misc.xml ├── gradle.xml └── uiDesigner.xml ├── settings.gradle ├── img.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── test │ ├── resources │ │ └── plugin.yml │ └── java │ │ └── io │ │ └── github │ │ └── mqzen │ │ └── menus │ │ ├── ExampleMenuComponent.java │ │ ├── ExampleMenu2.java │ │ ├── ExampleAutoAnimatedButton.java │ │ ├── ExamplePlainPage.java │ │ ├── ExampleMenu.java │ │ ├── ExampleAutoPage.java │ │ └── LotusExamplePlugin.java └── main │ └── java │ └── io │ └── github │ └── mqzen │ └── menus │ ├── base │ ├── pagination │ │ ├── exception │ │ │ └── InvalidPageException.java │ │ ├── PageComponentsProvider.java │ │ ├── PageViewFactory.java │ │ ├── PageComponent.java │ │ ├── PageView.java │ │ ├── FillRange.java │ │ ├── Page.java │ │ ├── Pagination.java │ │ └── PaginationImpl.java │ ├── style │ │ ├── TextLayoutProvider.java │ │ ├── Pane.java │ │ ├── TextLayout.java │ │ └── TextLayoutPane.java │ ├── serialization │ │ ├── SerializedMenuIO.java │ │ ├── SimpleMenuSerializer.java │ │ ├── MenuSerializer.java │ │ ├── impl │ │ │ └── SerializedMenuYaml.java │ │ └── SerializableMenu.java │ ├── animation │ │ ├── AnimationTaskData.java │ │ ├── ButtonAnimationTask.java │ │ ├── AnimatedButton.java │ │ └── TransformingButton.java │ ├── ViewOpener.java │ ├── iterator │ │ ├── SlotIterator.java │ │ └── Direction.java │ ├── Menu.java │ ├── MenuContentImpl.java │ └── BaseMenuView.java │ ├── titles │ ├── LegacyTitle.java │ ├── ModernTitle.java │ ├── MenuTitle.java │ └── MenuTitles.java │ ├── misc │ ├── button │ │ ├── ButtonUpdater.java │ │ ├── actions │ │ │ ├── impl │ │ │ │ ├── CloseMenuAction.java │ │ │ │ └── OpenMenuAction.java │ │ │ ├── ButtonClickActions.java │ │ │ ├── ButtonActionRegistry.java │ │ │ └── ButtonClickAction.java │ │ ├── ButtonCondition.java │ │ └── Button.java │ ├── itembuilder │ │ ├── LegacyItemBuilder.java │ │ ├── ComponentItemBuilder.java │ │ └── ItemBuilder.java │ ├── Slots.java │ ├── ViewData.java │ ├── Slot.java │ ├── DataRegistry.java │ └── Capacity.java │ ├── LotusDebugger.java │ ├── FieldAccessor.java │ ├── util │ └── InventoryUtil.java │ ├── openers │ └── DefaultViewOpener.java │ ├── MenuUpdateTask.java │ ├── Reflections.java │ └── Lotus.java ├── .gitignore ├── LICENSE ├── README.md ├── gradlew.bat └── gradlew /.idea/.name: -------------------------------------------------------------------------------- 1 | Lotus -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "Lotus" 2 | 3 | -------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mqzn/Lotus/HEAD/img.png -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mqzn/Lotus/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: Lotus-bukkit-test 2 | main: io.github.mqzen.menus.LotusExamplePlugin 3 | version: 1.0 4 | commands: 5 | test: 6 | load: 7 | save: -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/pagination/exception/InvalidPageException.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.pagination.exception; 2 | 3 | public final class InvalidPageException extends Exception { 4 | 5 | public InvalidPageException(int page) { 6 | super("PageView '#" + page + "' doesn't exist !"); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/titles/LegacyTitle.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.titles; 2 | 3 | import org.bukkit.ChatColor; 4 | 5 | final class LegacyTitle implements MenuTitle { 6 | 7 | private final String text; 8 | 9 | LegacyTitle(String text) { 10 | this.text = text; 11 | } 12 | 13 | @Override 14 | public String asString() { 15 | return ChatColor.translateAlternateColorCodes('&', text); 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/pagination/PageComponentsProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.pagination; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Provides a list of components that will be used in a paginated UI. 7 | * Implementations of this interface are responsible for supplying the 8 | * necessary components to be displayed on each page. 9 | */ 10 | public interface PageComponentsProvider { 11 | 12 | /** 13 | * @return the components of the page 14 | */ 15 | List provide(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/style/TextLayoutProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.style; 2 | 3 | 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * Provides a method to supply a TextLayout, which maps characters to Button instances. 8 | */ 9 | public interface TextLayoutProvider { 10 | 11 | /** 12 | * Provides a TextLayout object which maps characters to Button instances. 13 | * 14 | * @return a TextLayout containing the mapping of characters to buttons. 15 | */ 16 | @NotNull TextLayout provide(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/titles/ModernTitle.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.titles; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 5 | 6 | final class ModernTitle implements MenuTitle { 7 | 8 | private final Component component; 9 | 10 | ModernTitle(Component component) { 11 | this.component = component; 12 | } 13 | 14 | @Override 15 | public String asString() { 16 | return LegacyComponentSerializer.legacySection().serialize(component); 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/style/Pane.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.style; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | 7 | /** 8 | * Represents a pane that will be applied 9 | * to an {@link Content} through the method 10 | * {@link Pane#applyOn(Content)} 11 | */ 12 | public interface Pane { 13 | 14 | /** 15 | * Applies the pane on the content 16 | * @param content the content to apply the pane on 17 | */ 18 | void applyOn(@NotNull Content content); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/titles/MenuTitle.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.titles; 2 | 3 | /** 4 | * The MenuTitle interface provides a contract for representing 5 | * menu titles in different formats, such as components or strings. 6 | * Classes implementing this interface can convert the title to either 7 | * a Component or a String. 8 | */ 9 | public interface MenuTitle { 10 | 11 | /** 12 | * Converts the menu title to its String representation. 13 | * 14 | * @return the title as a String 15 | */ 16 | String asString(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/button/ButtonUpdater.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.button; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | /** 6 | * Functional interface for updating a {@link Button}. 7 | * This can be used to modify properties of a button in a menu view. 8 | */ 9 | @FunctionalInterface 10 | public interface ButtonUpdater { 11 | 12 | /** 13 | * Updates the given button by modifying its properties. 14 | * 15 | * @param button the button to be updated, must not be null 16 | */ 17 | void update(@NotNull Button button); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/serialization/SerializedMenuIO.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.serialization; 2 | 3 | import io.github.mqzen.menus.misc.DataRegistry; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * Interface for handling input and output operations for serialized menus. 8 | * 9 | * @param the file type for serialization 10 | */ 11 | public interface SerializedMenuIO { 12 | 13 | Class fileType(); 14 | 15 | void write(@NotNull DataRegistry registry, @NotNull F file); 16 | 17 | @NotNull DataRegistry read(@NotNull F file); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/animation/AnimationTaskData.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.animation; 2 | 3 | 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | @Builder 8 | @Getter 9 | public final class AnimationTaskData { 10 | 11 | private long ticks, delay; 12 | private boolean async; 13 | 14 | private final static AnimationTaskData DEFAULT = AnimationTaskData.builder() 15 | .ticks(5L) 16 | .delay(1L) 17 | .async(false) 18 | .build(); 19 | 20 | public static AnimationTaskData defaultData() { 21 | return DEFAULT; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/button/actions/impl/CloseMenuAction.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.button.actions.impl; 2 | 3 | import io.github.mqzen.menus.base.MenuView; 4 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 5 | import org.bukkit.event.inventory.InventoryClickEvent; 6 | 7 | /** 8 | * @author Cobeine 9 | */ 10 | public final class CloseMenuAction implements ButtonClickAction { 11 | 12 | @Override 13 | public String tag() { 14 | return "CLOSE"; 15 | } 16 | 17 | @Override 18 | public void execute(MenuView menu, InventoryClickEvent event) { 19 | event.getWhoClicked().closeInventory(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store 43 | /gradle.properties 44 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/button/actions/ButtonClickActions.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.button.actions; 2 | 3 | import io.github.mqzen.menus.misc.button.actions.impl.CloseMenuAction; 4 | import io.github.mqzen.menus.misc.button.actions.impl.OpenMenuAction; 5 | 6 | /** 7 | * @author Cobeine 8 | * @author Mqzen (modifed after Cobeine) 9 | */ 10 | 11 | public final class ButtonClickActions { 12 | 13 | private ButtonClickActions() { 14 | throw new IllegalAccessError(); 15 | } 16 | 17 | public final static ButtonClickAction CLOSE_MENU = new CloseMenuAction(); 18 | 19 | public static ButtonClickAction openMenu(String menuName) { 20 | return new OpenMenuAction(menuName); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/itembuilder/LegacyItemBuilder.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.itembuilder; 2 | 3 | import org.bukkit.ChatColor; 4 | import org.bukkit.Material; 5 | import org.bukkit.inventory.ItemStack; 6 | 7 | public final class LegacyItemBuilder extends ItemBuilder { 8 | 9 | LegacyItemBuilder(ItemStack itemStack) { 10 | super(itemStack); 11 | } 12 | 13 | LegacyItemBuilder(Material material, int amount, short data) { 14 | super(material, amount, data); 15 | } 16 | 17 | LegacyItemBuilder(Material material, int amount) { 18 | super(material, amount); 19 | } 20 | 21 | LegacyItemBuilder(Material material) { 22 | super(material); 23 | } 24 | 25 | @Override 26 | protected String toString(String str) { 27 | return ChatColor.translateAlternateColorCodes('&', str); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/pagination/PageViewFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.pagination; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | @ApiStatus.Internal 7 | final class PageViewFactory { 8 | 9 | private PageViewFactory() { 10 | throw new UnsupportedOperationException(); 11 | } 12 | 13 | static PageView createAuto(Pagination pagination, int index) { 14 | return new PageView(pagination, index); 15 | } 16 | 17 | static PageView createPlain(Pagination pagination, Page model, int index) { 18 | return new PageView(pagination, model, index); 19 | } 20 | 21 | static PageView createView(Pagination pagination, @NotNull Page model, int index) { 22 | return pagination.isAutomatic() ? createAuto(pagination, index) : createPlain(pagination, model, index); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/button/ButtonCondition.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.button; 2 | 3 | import io.github.mqzen.menus.misc.Slot; 4 | 5 | /** 6 | * A functional interface used to define whether a given {@link Button} in a specific {@link Slot} 7 | * satisfies certain conditions. Implementations of this interface provide the logic to determine 8 | * if a {@code Slot} and {@code Button} combination meets the desired criteria. 9 | */ 10 | @FunctionalInterface 11 | public interface ButtonCondition { 12 | 13 | /** 14 | * Determines whether a given {@link Button} in a specific {@link Slot} satisfies certain conditions. 15 | * 16 | * @param slot the slot in which the button is located 17 | * @param button the button to be evaluated 18 | * @return {@code true} if the button in the specified slot meets the conditions, otherwise {@code false} 19 | */ 20 | boolean accepts(Slot slot, Button button); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/Slots.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public final class Slots { 7 | 8 | private final Slot[] slots; 9 | 10 | private Slots(Slot[] slots) { 11 | this.slots = slots; 12 | } 13 | 14 | private Slots(int[] slots) { 15 | this.slots = new Slot[slots.length]; 16 | for (int i = 0; i < slots.length; i++) { 17 | this.slots[i] = Slot.of(slots[i]); 18 | } 19 | } 20 | 21 | public static Slots of(int... slots) { 22 | return new Slots(slots); 23 | } 24 | 25 | public static Slots of(Slot... slots) { 26 | return new Slots(slots); 27 | } 28 | 29 | public static Slots ofRows(int[] rows) { 30 | Slot[] slots = new Slot[rows.length * 9]; 31 | for (int i = 0; i < rows.length; i++) { 32 | int row = rows[i]; 33 | for (int column = 0; column < 9; column++) { 34 | slots[i + column] = Slot.of(row, column); 35 | } 36 | } 37 | return new Slots(slots); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/LotusDebugger.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import java.util.logging.Level; 4 | import java.util.logging.Logger; 5 | 6 | public final class LotusDebugger { 7 | 8 | static LotusDebugger EMPTY = new LotusDebugger(null); 9 | 10 | private final Logger logger; 11 | 12 | LotusDebugger(Logger logger) { 13 | this.logger = logger; 14 | } 15 | 16 | public boolean isEmpty() { 17 | return this == EMPTY || logger == null; 18 | } 19 | 20 | public void debug(String msg, Object... args) { 21 | if(logger == null) return; 22 | logger.info(String.format(msg, args)); 23 | } 24 | 25 | public void warn(String msg, Object... args) { 26 | if(logger == null) return; 27 | logger.warning(String.format(msg, args)); 28 | } 29 | public void error(String msg, Throwable ex){ 30 | if(logger == null) return; 31 | logger.log(Level.SEVERE, msg, ex); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/ViewData.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.titles.MenuTitle; 5 | 6 | /** 7 | * ViewData is a record that encapsulates information about a menu view. It includes 8 | * a menu title, capacity, and content. This record serves as a container to hold 9 | * all necessary details for rendering a menu view in a user interface. 10 | */ 11 | public final class ViewData { 12 | 13 | final MenuTitle title; 14 | final Capacity capacity; 15 | final Content content; 16 | 17 | 18 | public ViewData(MenuTitle title, Capacity capacity, Content content) { 19 | this.title = title; 20 | this.capacity = capacity; 21 | this.content = content; 22 | } 23 | 24 | public Capacity capacity() { 25 | return capacity; 26 | } 27 | 28 | public Content content() { 29 | return content; 30 | } 31 | 32 | public MenuTitle title() { 33 | return title; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/itembuilder/ComponentItemBuilder.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.itembuilder; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 5 | import org.bukkit.Material; 6 | import org.bukkit.inventory.ItemStack; 7 | 8 | public final class ComponentItemBuilder extends ItemBuilder { 9 | 10 | ComponentItemBuilder(ItemStack itemStack) { 11 | super(itemStack); 12 | } 13 | ComponentItemBuilder(Material material, 14 | int amount, short data) { 15 | super(material, amount, data); 16 | } 17 | 18 | 19 | ComponentItemBuilder(Material material, 20 | int amount) { 21 | super(material, amount); 22 | } 23 | 24 | 25 | ComponentItemBuilder(Material material) { 26 | super(material); 27 | } 28 | 29 | 30 | @Override 31 | protected String toString(Component component) { 32 | return LegacyComponentSerializer.legacySection().serialize(component); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/FieldAccessor.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | /** 4 | * An interface for retrieving the field content. (Credits: TinyProtocol) 5 | * 6 | * @param - field type. 7 | * @author Kristian 8 | */ 9 | interface FieldAccessor { 10 | 11 | /** 12 | * Retrieve the content of a field. 13 | * 14 | * @param target - the targetToLoad object, or NULL for a static field. 15 | * @return The value of the field. 16 | */ 17 | T get(final Object target); 18 | 19 | /** 20 | * Set the content of a field. 21 | * 22 | * @param target - the targetToLoad object, or NULL for a static field. 23 | * @param value - the new value of the field. 24 | */ 25 | void set(final Object target, final Object value); 26 | 27 | /** 28 | * Determine if the given object has this field. 29 | * 30 | * @param target - the object to test. 31 | * @return TRUE if it does, FALSE otherwise. 32 | */ 33 | @SuppressWarnings("unused") 34 | boolean hasField(final Object target); 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/ViewOpener.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base; 2 | 3 | import io.github.mqzen.menus.Lotus; 4 | import io.github.mqzen.menus.misc.ViewData; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.inventory.Inventory; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | /** 10 | * The ViewOpener interface defines a contract for creating and opening an 11 | * inventory for a player using dynamic data from a cached menu within 'ViewData'. 12 | */ 13 | public interface ViewOpener { 14 | 15 | /** 16 | * Creates an inventory , opens it for the player using the dynamic data 17 | * of the menu that is cached within 'ViewData' 18 | * 19 | * @param manager the manager 20 | * @param player the player opening this menu 21 | * @param baseMenuView the menu view to open 22 | * @param viewData the data of the menu to open 23 | * @return the menu inventory opened for this player 24 | */ 25 | @NotNull Inventory openMenu(Lotus manager, Player player, 26 | MenuView baseMenuView, ViewData viewData); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/button/actions/impl/OpenMenuAction.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.button.actions.impl; 2 | 3 | import io.github.mqzen.menus.base.MenuView; 4 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.inventory.InventoryClickEvent; 7 | 8 | /** 9 | * Defines an action to open menus based on a registered menu name 10 | * through the main api class {@link io.github.mqzen.menus.Lotus} 11 | */ 12 | public final class OpenMenuAction implements ButtonClickAction { 13 | 14 | private final String toOpen; 15 | 16 | public OpenMenuAction(String toOpen) { 17 | this.toOpen = toOpen; 18 | } 19 | 20 | @Override 21 | public String tag() { 22 | return "OPEN"; 23 | } 24 | 25 | @Override 26 | public void execute(MenuView view, InventoryClickEvent event) { 27 | view.getAPI().getRegisteredMenu(toOpen) 28 | .ifPresent((menu)-> view.getAPI().openMenu((Player) event.getWhoClicked(), menu)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mazen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/serialization/SimpleMenuSerializer.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.serialization; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.misc.Capacity; 5 | import io.github.mqzen.menus.misc.DataRegistry; 6 | 7 | final class SimpleMenuSerializer implements MenuSerializer { 8 | 9 | public final static String PLAYER_PLACEHOLDER = ""; 10 | 11 | SimpleMenuSerializer() {} 12 | 13 | @Override 14 | public DataRegistry serialize(SerializableMenu menu) { 15 | DataRegistry dataRegistry = new DataRegistry(); 16 | dataRegistry.setData("name", menu.getName()); 17 | 18 | String title = menu.titleString(); 19 | dataRegistry.setData("title", title); 20 | 21 | Capacity capacity = menu.capacity(); 22 | dataRegistry.setData("capacity", capacity); 23 | 24 | Content content = menu.content(); 25 | dataRegistry.setData("content", content); 26 | 27 | return dataRegistry; 28 | } 29 | 30 | @Override 31 | public SerializableMenu deserialize(DataRegistry dataRegistry) { 32 | return new SerializableMenu(dataRegistry); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/util/InventoryUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.util; 2 | 3 | import org.bukkit.event.inventory.InventoryEvent; 4 | import org.bukkit.inventory.Inventory; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public final class InventoryUtil { 9 | 10 | private static final Method GET_INVENTORY_VIEW, GET_TOP_INVENTORY; 11 | 12 | static { 13 | try { 14 | GET_INVENTORY_VIEW = InventoryEvent.class.getMethod("getView"); 15 | 16 | Class inventoryViewClass = Class.forName("org.bukkit.inventory.InventoryView"); 17 | GET_TOP_INVENTORY = inventoryViewClass.getMethod("getTopInventory"); 18 | 19 | } catch (ClassNotFoundException | NoSuchMethodException e) { 20 | throw new RuntimeException(e); 21 | } 22 | } 23 | 24 | public static Inventory getTopInventory(final InventoryEvent event) { 25 | try { 26 | Object view = GET_INVENTORY_VIEW.invoke(event); 27 | return (Inventory) GET_TOP_INVENTORY.invoke(view); 28 | } catch (Exception e) { 29 | throw new RuntimeException("Failed to get top inventory from event", e); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/io/github/mqzen/menus/ExampleMenuComponent.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import io.github.mqzen.menus.base.pagination.PageComponent; 4 | import io.github.mqzen.menus.base.pagination.PageView; 5 | import io.github.mqzen.menus.misc.itembuilder.ItemBuilder; 6 | import net.kyori.adventure.text.Component; 7 | import net.kyori.adventure.text.format.NamedTextColor; 8 | import org.bukkit.Material; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.inventory.InventoryClickEvent; 11 | import org.bukkit.inventory.ItemStack; 12 | 13 | public class ExampleMenuComponent implements PageComponent { 14 | 15 | private final String name; 16 | public ExampleMenuComponent(String name) { 17 | this.name = name; 18 | } 19 | @Override 20 | public ItemStack toItem() { 21 | return ItemBuilder.modern(Material.GRASS) 22 | .setDisplay(Component.text(name, NamedTextColor.GRAY)).build(); 23 | } 24 | 25 | @Override 26 | public void onClick(PageView pageView, InventoryClickEvent event) { 27 | event.setCancelled(true); 28 | Player player = (Player) event.getWhoClicked(); 29 | player.sendMessage("Clicking on component '" + name + "'"); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/serialization/MenuSerializer.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.serialization; 2 | 3 | import io.github.mqzen.menus.misc.DataRegistry; 4 | 5 | /** 6 | * Interface for serializing and deserializing menu objects 7 | * into {@link DataRegistry} or from {@link DataRegistry} 8 | */ 9 | public interface MenuSerializer { 10 | 11 | /** 12 | * Serializes a {@link SerializableMenu} object into a {@link DataRegistry}. 13 | * 14 | * @param menu the menu to be serialized 15 | * @return the data registry containing the serialized menu data 16 | */ 17 | DataRegistry serialize(SerializableMenu menu); 18 | 19 | /** 20 | * Deserializes a {@link DataRegistry} into a {@link SerializableMenu} object. 21 | * 22 | * @param dataRegistry The data registry containing the serialized menu data to convert. 23 | * @return The deserialized {@link SerializableMenu} object. 24 | */ 25 | SerializableMenu deserialize(DataRegistry dataRegistry); 26 | 27 | /** 28 | * Creates a new default implementation of the MenuSerializer. 29 | * 30 | * @return a new instance of SimpleMenuSerializer 31 | */ 32 | static MenuSerializer newDefaultSerializer() { 33 | return new SimpleMenuSerializer(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/openers/DefaultViewOpener.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.openers; 2 | 3 | import io.github.mqzen.menus.Lotus; 4 | import io.github.mqzen.menus.base.MenuView; 5 | import io.github.mqzen.menus.base.ViewOpener; 6 | import io.github.mqzen.menus.misc.ViewData; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.inventory.Inventory; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public final class DefaultViewOpener implements ViewOpener { 13 | 14 | /** 15 | * Creates an inventory , opens it for the player using the dynamic data 16 | * of the menu that is cached within 'ViewData' 17 | * 18 | * @param manager the manager 19 | * @param player the player opening this menu 20 | * @param menu the menu to open 21 | * @param viewData the data of the menu to open 22 | * @return the menu inventory opened for this player 23 | */ 24 | @Override 25 | public @NotNull Inventory openMenu(Lotus manager, Player player, 26 | MenuView menu, ViewData viewData) { 27 | int size = viewData.capacity().getTotalSize(); 28 | String title = viewData.title().asString(); 29 | 30 | Inventory inv = Bukkit.createInventory(menu, size, title); 31 | 32 | viewData.content().forEachItem((slot, button) -> 33 | inv.setItem(slot.getSlot(), button.getItem())); 34 | 35 | player.openInventory(inv); 36 | return inv; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/style/TextLayout.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.style; 2 | 3 | import io.github.mqzen.menus.misc.button.Button; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.HashMap; 7 | import java.util.Iterator; 8 | import java.util.Map; 9 | 10 | /** 11 | * The TextLayout class is a customized map that links characters to Button instances. 12 | * It extends HashMap and implements Iterable to allow iteration over its character-button mappings. 13 | */ 14 | public final class TextLayout extends HashMap implements Iterable> { 15 | 16 | private TextLayout() { 17 | super(); 18 | } 19 | 20 | public static Builder builder() { 21 | return new Builder(); 22 | } 23 | 24 | /** 25 | * Returns an iterator over elements of type {@code T}. 26 | * 27 | * @return an Iterator. 28 | */ 29 | @NotNull 30 | @Override 31 | public Iterator> iterator() { 32 | return this.entrySet().iterator(); 33 | } 34 | 35 | public static class Builder { 36 | 37 | private final TextLayout layout = new TextLayout(); 38 | 39 | public static final char DEFAULT_CHARACTER = '#'; 40 | 41 | public Builder set(Character character, Button button) { 42 | this.layout.put(character, button); 43 | return this; 44 | } 45 | public Builder setDefault(Button button) { 46 | return set(DEFAULT_CHARACTER, button); 47 | } 48 | 49 | public TextLayout build() { 50 | return layout; 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/pagination/PageComponent.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.pagination; 2 | 3 | import io.github.mqzen.menus.misc.button.Button; 4 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 5 | import org.bukkit.event.inventory.InventoryClickEvent; 6 | import org.bukkit.inventory.ItemStack; 7 | 8 | /** 9 | * Represents a component in a paginated view. 10 | * This interface provides methods to transform the component to an {@link ItemStack} and handle 11 | * click events on this component. 12 | */ 13 | public interface PageComponent { 14 | 15 | /** 16 | * Converts the PageComponent into an ItemStack for use in the paginated view. 17 | * 18 | * @return the ItemStack representation of this PageComponent 19 | */ 20 | ItemStack toItem(); 21 | 22 | /** 23 | * Handles the click event for a component in a paginated view. 24 | * 25 | * @param pageView The current view of the paginated menu that received the click. 26 | * @param event The inventory click event that was triggered. 27 | */ 28 | void onClick(PageView pageView, InventoryClickEvent event); 29 | 30 | /** 31 | * Converts the current PageComponent to a clickable Button. 32 | * The Button is created with the ItemStack obtained from the toItem() method 33 | * and a ButtonClickAction that executes the onClick() method when the Button is clicked. 34 | * 35 | * @return a clickable Button created from the current PageComponent 36 | */ 37 | default Button toButton() { 38 | return Button.clickable(toItem(), 39 | ButtonClickAction.plain((menu, clickEvent) -> onClick((PageView) menu, clickEvent))); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/animation/ButtonAnimationTask.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.animation; 2 | 3 | import io.github.mqzen.menus.base.MenuView; 4 | import io.github.mqzen.menus.misc.Slot; 5 | import org.bukkit.plugin.Plugin; 6 | import org.bukkit.scheduler.BukkitRunnable; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public final class ButtonAnimationTask extends BukkitRunnable { 10 | 11 | private final @NotNull Slot slot; 12 | private final @NotNull AnimatedButton button; 13 | private final @NotNull MenuView view; 14 | 15 | private ButtonAnimationTask ( 16 | @NotNull Slot slot, 17 | @NotNull AnimatedButton button, 18 | @NotNull MenuView view 19 | ) { 20 | this.slot = slot; 21 | this.button = button; 22 | this.view = view; 23 | } 24 | 25 | @Override 26 | public void run() { 27 | if(!view.isOpen()) { 28 | this.cancel(); 29 | return; 30 | } 31 | 32 | button.animate(slot, view); 33 | } 34 | 35 | public static ButtonAnimationTask of( 36 | Slot slot, 37 | AnimatedButton button, 38 | MenuView view 39 | ) { 40 | return new ButtonAnimationTask(slot, button, view); 41 | } 42 | 43 | 44 | public void start(Plugin plugin) { 45 | 46 | AnimationTaskData taskData = button.getAnimationTaskData(); 47 | if (taskData.isAsync()) { 48 | runTaskTimerAsynchronously(plugin, taskData.getDelay(), taskData.getTicks()); 49 | } else { 50 | runTaskTimer(plugin, taskData.getDelay(), taskData.getTicks()); 51 | } 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/button/actions/ButtonActionRegistry.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.button.actions; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * The ButtonActionRegistry class manages the registration and retrieval of button click actions. 10 | * This singleton class ensures that only one instance of the registry exists throughout the application. 11 | */ 12 | public final class ButtonActionRegistry { 13 | 14 | private static ButtonActionRegistry registry; 15 | 16 | /** 17 | * Returns the singleton instance of the ButtonActionRegistry class. 18 | * 19 | * @return The singleton instance of ButtonActionRegistry. 20 | */ 21 | public static ButtonActionRegistry getInstance() { 22 | if(registry == null) { 23 | registry = new ButtonActionRegistry(); 24 | } 25 | return registry; 26 | } 27 | 28 | private final Map actions = new HashMap<>(); 29 | private ButtonActionRegistry() { 30 | registerAction(ButtonClickActions.CLOSE_MENU); 31 | } 32 | 33 | /** 34 | * Registers a new button click action. 35 | * 36 | * @param action The ButtonClickAction to register. The action is identified by its unique tag. 37 | */ 38 | public void registerAction(ButtonClickAction action) { 39 | actions.put(action.tag(), action); 40 | } 41 | 42 | /** 43 | * Retrieves the ButtonClickAction associated with the specified tag. 44 | * If no action is found with the given tag, this method returns null. 45 | * 46 | * @param tag The tag identifying the ButtonClickAction to retrieve. 47 | * @return The ButtonClickAction associated with the specified tag, or null if no matching action is found. 48 | */ 49 | public @Nullable ButtonClickAction getAction(String tag) { 50 | return actions.get(tag); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/titles/MenuTitles.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.titles; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.minimessage.MiniMessage; 5 | 6 | public final class MenuTitles { 7 | 8 | /** 9 | * Creates a legacy menu title based on the provided string. 10 | * 11 | * @param title the title text to be used. It may contain legacy color codes 12 | * @return a MenuTitle object that represents the legacy title. 13 | */ 14 | public static MenuTitle createLegacy(String title) { 15 | return new LegacyTitle(title); 16 | } 17 | 18 | /** 19 | * Creates a modern menu title using the provided Component. 20 | * 21 | * @param component the Component to be used as the modern title 22 | * @return an instance of ModernTitle containing the provided Component 23 | */ 24 | public static MenuTitle createModern(Component component) { 25 | return new ModernTitle(component); 26 | } 27 | 28 | /** 29 | * Creates a modern MenuTitle using the specified MiniMessage API object and a MiniMessage string. 30 | * 31 | * @param apiObject the MiniMessage API object used for deserialization 32 | * @param miniMessage the MiniMessage string to be deserialized and converted to a MenuTitle 33 | * @return a MenuTitle object representing the deserialized MiniMessage string 34 | */ 35 | public static MenuTitle createModern(MiniMessage apiObject, String miniMessage) { 36 | return createModern(apiObject.deserialize(miniMessage)); 37 | } 38 | 39 | /** 40 | * Creates a modern MenuTitle using a mini message string. 41 | * 42 | * @param miniMessage the mini message string to be deserialized into a Component 43 | * @return a MenuTitle object representing the deserialized Component 44 | */ 45 | public static MenuTitle createModern(String miniMessage) { 46 | return createModern(MiniMessage.miniMessage(), miniMessage); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/io/github/mqzen/menus/ExampleMenu2.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.base.Menu; 5 | import io.github.mqzen.menus.misc.Capacity; 6 | import io.github.mqzen.menus.misc.DataRegistry; 7 | import io.github.mqzen.menus.misc.button.Button; 8 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 9 | import io.github.mqzen.menus.misc.itembuilder.ItemBuilder; 10 | import io.github.mqzen.menus.titles.MenuTitle; 11 | import io.github.mqzen.menus.titles.MenuTitles; 12 | import org.bukkit.ChatColor; 13 | import org.bukkit.Material; 14 | import org.bukkit.entity.Player; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | public final class ExampleMenu2 implements Menu { 18 | @Override 19 | public String getName() { 20 | return "menu2"; 21 | } 22 | 23 | @Override 24 | public @NotNull MenuTitle getTitle(DataRegistry extraData, Player opener) { 25 | return MenuTitles.createLegacy("Hii"); 26 | } 27 | 28 | @Override 29 | public @NotNull Capacity getCapacity(DataRegistry extraData, Player opener) { 30 | return Capacity.ofRows(3); 31 | } 32 | 33 | @Override 34 | public @NotNull Content getContent(DataRegistry extraData, Player opener, Capacity capacity) { 35 | Content.Builder b = Content.builder(capacity); 36 | b.apply(content -> { 37 | for(ChatColor color : ChatColor.values()) { 38 | content.addButton(Button.clickable(ItemBuilder.legacy(Material.NAME_TAG).setDisplay(color.name()).build(), ButtonClickAction.plain((menu, event)-> { 39 | event.setCancelled(true); 40 | opener.closeInventory(); 41 | opener.sendMessage("Color clicked= " + color + color.name()); 42 | }))); 43 | } 44 | }); 45 | return b.build(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/Slot.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc; 2 | 3 | import com.google.common.base.Objects; 4 | import lombok.Getter; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | 8 | /** 9 | * Represents a Slot in a grid-like structure with a fixed width. 10 | *

11 | * This class provides methods to create and manipulate slots using their slot numbers or 12 | * their row and column coordinates. 13 | *

14 | */ 15 | @Getter 16 | public final class Slot implements Comparable { 17 | 18 | private final static int WIDTH = 9; 19 | 20 | private final int slot, row, column; 21 | 22 | private Slot(int slot) { 23 | this.slot = slot; 24 | this.row = (int) Math.floor((float) (slot / WIDTH)); 25 | this.column = slot % WIDTH; 26 | } 27 | 28 | private Slot(int row, int column) { 29 | this.slot = row * WIDTH + column; 30 | this.row = row; 31 | this.column = column; 32 | } 33 | 34 | public static Slot of(int slot) { 35 | return new Slot(slot); 36 | } 37 | 38 | public static Slot of(int row, int column) { 39 | return new Slot(row, column); 40 | } 41 | 42 | public static Slot last(Capacity capacity) { 43 | return new Slot(capacity.getTotalSize() - 1); 44 | } 45 | 46 | @Override 47 | public boolean equals(Object o) { 48 | if (this == o) return true; 49 | if (!(o instanceof Slot)) return false; 50 | Slot slot1 = (Slot) o; 51 | return slot == slot1.slot; 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return Objects.hashCode(slot); 57 | } 58 | 59 | public Slot subtractBy(int num) { 60 | return Slot.of(slot - num); 61 | } 62 | 63 | @Override 64 | public int compareTo(@NotNull Slot o) { 65 | return this.slot - o.slot; 66 | } 67 | 68 | public Slot copy() { 69 | return new Slot(slot); 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "Slot{" + 75 | "slot=" + slot + 76 | ", row=" + row + 77 | ", column=" + column + 78 | '}'; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/DataRegistry.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * The DataRegistry class is a container for storing and retrieving key-value pairs, 7 | * where keys are strings, and values are objects of any type. 8 | */ 9 | public final class DataRegistry { 10 | 11 | private final Map objectMap = new HashMap<>(); 12 | 13 | /** 14 | * Creates a new, empty instance of the DataRegistry class. 15 | * 16 | * @return A new instance of DataRegistry with no key-value pairs. 17 | */ 18 | public static DataRegistry empty() { 19 | return new DataRegistry(); 20 | } 21 | 22 | /** 23 | * Associates the specified object with the given key in the data registry. 24 | * 25 | * @param key the key with which the specified object is to be associated 26 | * @param object the object to be associated with the specified key 27 | */ 28 | public void setData(String key, Object object) { 29 | objectMap.put(key, object); 30 | } 31 | 32 | /** 33 | * Retrieves a set of all keys present in the data registry. 34 | * 35 | * @return A set containing all keys currently stored in the data registry. 36 | */ 37 | public Set keys() { 38 | return objectMap.keySet(); 39 | } 40 | 41 | /** 42 | * Retrieves a collection of all values stored in the data registry. 43 | * 44 | * @return A collection containing all values currently stored in the data registry. 45 | */ 46 | public Collection values() { 47 | return objectMap.values(); 48 | } 49 | 50 | 51 | /** 52 | * Retrieves data associated with the specified key from the data registry. 53 | * 54 | * @param The type of the data to be returned. 55 | * @param key The key associated with the data to be retrieved. 56 | * @return The data associated with the given key, or null if no data is found. 57 | */ 58 | @SuppressWarnings("unchecked") 59 | public T getData(String key) { 60 | return (T) objectMap.get(key); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/MenuUpdateTask.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import io.github.mqzen.menus.base.MenuView; 4 | import io.github.mqzen.menus.base.animation.AnimatedButton; 5 | import io.github.mqzen.menus.misc.Slot; 6 | import io.github.mqzen.menus.misc.button.Button; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import org.bukkit.scheduler.BukkitRunnable; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | final class MenuUpdateTask extends BukkitRunnable { 15 | private final Lotus lotus; 16 | private MenuUpdateTask(Lotus lotus) { 17 | this.lotus = lotus; 18 | } 19 | 20 | static MenuUpdateTask newTask(Lotus lotus) { 21 | return new MenuUpdateTask(lotus); 22 | } 23 | 24 | @Override 25 | public void run() { 26 | try { 27 | for (MenuView menuView : lotus.getOpenViews()) { 28 | Set buttons = getAnimatedButtons(menuView); 29 | buttons.parallelStream().forEach((animatedButton) -> 30 | menuView.updateButton(animatedButton.slot, (b) -> ((AnimatedButton) b).animate(animatedButton.slot, menuView))); 31 | } 32 | }catch (Throwable ex) { 33 | lotus.debugger.error("Failed to continue the menu update task", ex); 34 | this.cancel(); 35 | } 36 | } 37 | private Set getAnimatedButtons(MenuView view) { 38 | Set buttons = new HashSet<>(); 39 | for(Map.Entry entry : view.getContent().getButtonMap().entrySet()) { 40 | if(entry.getValue() instanceof AnimatedButton) { 41 | AnimatedButton animatedButton = (AnimatedButton)entry.getValue(); 42 | buttons.add(new AnimatedButtonEntry(entry.getKey(), animatedButton)); 43 | } 44 | } 45 | return buttons; 46 | } 47 | 48 | @Data @AllArgsConstructor 49 | static final class AnimatedButtonEntry { 50 | final Slot slot; 51 | final AnimatedButton button; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |


2 | 3 | # Lotus [![](https://jitpack.io/v/Mqzn/Lotus.svg)](https://jitpack.io/#Mqzn/Lotus) 4 | Simple but yet powerful spigot menus api created using OOP principles 5 | 6 | ## Features 7 | 8 | - High performance 9 | - High quality user-friendly API 10 | - Scalable and extendable 11 | - 99% Customizable menus 12 | - Pagination 13 | - Menu Iterators 14 | - Highly Dynamic creation of menus 15 | 16 | ## Documentation 17 | 18 | **Lotus Wiki:** https://github.com/Mqzn/Lotus/wiki 19 | 20 | ## Requirements 21 | 22 | This library depends mainly on the following : 23 | 24 | - Java 8 25 | - **_(OPTIONAL)_** [Adventure Components](https://docs.advntr.dev/getting-started.html) 26 | 27 | ## Installation 28 | 29 | Lotus API is being hosted on jitpack. Here's an example on how to setup 30 | Lotus dependency on your project. 31 | 32 | ### Gradle 33 | 34 | If you're using Gradle as your build tool for your project, this is quick example how to add it as a dependency in 35 | your `build.gradle` file 36 | 37 | ```groovy 38 | repositories { 39 | mavenCentral() 40 | maven { url 'https://jitpack.io' } 41 | } 42 | 43 | dependencies { 44 | implementation 'com.github.Mqzn:Lotus:VERSION' 45 | } 46 | ``` 47 | 48 | ### Maven 49 | 50 | if you're using maven, it's basically the same information regarding the API dependency but there will be small changes 51 | to the syntax as following: 52 | 53 | ```xml 54 | 55 | 56 | jitpack.io 57 | https://jitpack.io 58 | 59 | 60 | 61 | 62 | 63 | com.github.mqzn 64 | Lotus 65 | VERSION 66 | 67 | 68 | 69 | ``` 70 | 71 | ## Usage/Examples 72 | 73 | There are currently no real examples, but you can be the first to contribute to the project by using the api to create 74 | examples for other to be inspired by. 75 | 76 | If you want to add your example here, contact me via discord 77 | 78 | **Discord:** mqzen 79 | 80 | ## License 81 | 82 | [![MIT License](https://img.shields.io/badge/License-MIT-green.svg)](https://choosealicense.com/licenses/mit/) 83 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/animation/AnimatedButton.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.animation; 2 | 3 | import io.github.mqzen.menus.base.MenuView; 4 | import io.github.mqzen.menus.misc.DataRegistry; 5 | import io.github.mqzen.menus.misc.Slot; 6 | import io.github.mqzen.menus.misc.button.Button; 7 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 8 | import lombok.Getter; 9 | import org.bukkit.inventory.ItemStack; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | /** 14 | * Interface representing an animated button within a menu interface. 15 | * @see AnimationTaskData 16 | */ 17 | public abstract class AnimatedButton extends Button { 18 | 19 | @Getter 20 | private final AnimationTaskData animationTaskData; 21 | 22 | protected AnimatedButton( 23 | AnimationTaskData animationTaskData, 24 | @Nullable ItemStack item, 25 | ButtonClickAction clickAction 26 | ) { 27 | super(item, clickAction); 28 | this.animationTaskData = animationTaskData; 29 | } 30 | 31 | protected AnimatedButton( 32 | AnimationTaskData animationTaskData, 33 | @Nullable ItemStack item 34 | ) { 35 | super(item); 36 | this.animationTaskData = animationTaskData; 37 | } 38 | 39 | protected AnimatedButton( 40 | @Nullable ItemStack item, 41 | ButtonClickAction clickAction 42 | ) { 43 | this(AnimationTaskData.defaultData(), item, clickAction); 44 | } 45 | 46 | protected AnimatedButton( 47 | @Nullable ItemStack item 48 | ) { 49 | this(AnimationTaskData.defaultData(), item); 50 | } 51 | 52 | protected AnimatedButton( 53 | AnimationTaskData animationTaskData, 54 | @Nullable ItemStack item, 55 | @Nullable ButtonClickAction clickAction, 56 | @NotNull DataRegistry data 57 | ) { 58 | super(item, clickAction, data); 59 | this.animationTaskData = animationTaskData; 60 | } 61 | 62 | /** 63 | * Animates the button inside a menu view 64 | * @param slot the slot 65 | * @param view the menu view 66 | */ 67 | public abstract void animate(Slot slot, @NotNull MenuView view); 68 | 69 | /** 70 | * Creates a copy of the current Button instance. The copied Button retains the same item and action as the original. 71 | * 72 | * @return A new Button instance with identical item and action properties as this Button. 73 | */ 74 | @Override 75 | public Button copy() { 76 | return this; 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/io/github/mqzen/menus/ExampleAutoAnimatedButton.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import io.github.mqzen.menus.base.MenuView; 4 | import io.github.mqzen.menus.base.animation.AnimatedButton; 5 | import io.github.mqzen.menus.base.animation.AnimationTaskData; 6 | import io.github.mqzen.menus.misc.DataRegistry; 7 | import io.github.mqzen.menus.misc.Slot; 8 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 9 | import io.github.mqzen.menus.misc.itembuilder.ItemBuilder; 10 | import org.bukkit.Material; 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.inventory.ItemStack; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | import java.util.Collections; 16 | 17 | public final class ExampleAutoAnimatedButton extends AnimatedButton { 18 | 19 | public ExampleAutoAnimatedButton() { 20 | super( 21 | AnimationTaskData.builder() 22 | .delay(0L) 23 | .ticks(1L) 24 | .async(true) 25 | .build(), 26 | 27 | ItemBuilder.legacy(Material.EMERALD) 28 | .setDisplay(frames[0]) 29 | .setLore(Collections.singletonList(loreFrames[0])) 30 | .build() 31 | ); 32 | } 33 | 34 | public ExampleAutoAnimatedButton(@Nullable ItemStack item, @Nullable ButtonClickAction action, DataRegistry data) { 35 | super(AnimationTaskData.defaultData(), item, action, data); 36 | } 37 | 38 | private final static String[] frames = { 39 | "§a❈ §b§lMAGIC §a❈", 40 | "§b❈ §a§lMAGIC §b❈", 41 | "§e❈ §6§lMAGIC §e❈", 42 | "§6❈ §e§lMAGIC §6❈" 43 | }; 44 | 45 | private final static String[] loreFrames = { 46 | "§7✧ Click to activate", 47 | "§7✧ Click to enchant", 48 | "§7✧ Click to cast", 49 | "§7✧ Click to conjure" 50 | }; 51 | 52 | private int frame = 0; 53 | 54 | 55 | public static ItemStack newItem(String[] frames, int frame) { 56 | return ItemBuilder.legacy(Material.EMERALD) 57 | .setDisplay(frames[frame]) 58 | .setLore(Collections.singletonList(loreFrames[frame])).build(); 59 | } 60 | 61 | @Override 62 | public void animate(Slot slot, @NotNull MenuView view) { 63 | frame = (frame + 1) % frames.length; 64 | this.setItem(newItem(frames, frame)); 65 | 66 | view.replaceButton(slot, this); 67 | System.out.println("EXAMPLE animated BUTTON FOR PLAYER " + view.getPlayer().map(Player::getName).orElse("N/A") + ", Running !"); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/io/github/mqzen/menus/ExamplePlainPage.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.base.pagination.FillRange; 5 | import io.github.mqzen.menus.base.pagination.Page; 6 | import io.github.mqzen.menus.misc.Capacity; 7 | import io.github.mqzen.menus.misc.DataRegistry; 8 | import io.github.mqzen.menus.misc.Slot; 9 | import io.github.mqzen.menus.misc.itembuilder.ItemBuilder; 10 | import io.github.mqzen.menus.titles.MenuTitle; 11 | import io.github.mqzen.menus.titles.MenuTitles; 12 | import org.bukkit.Material; 13 | import org.bukkit.entity.Player; 14 | import org.bukkit.inventory.ItemStack; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | public final class ExamplePlainPage extends Page { 18 | 19 | private final int amountOfItems; 20 | 21 | public ExamplePlainPage(int amountOfItems) { 22 | this.amountOfItems = amountOfItems; 23 | } 24 | 25 | 26 | /** 27 | * The number of buttons this pageView should have 28 | * 29 | * @param capacity the capacity for the page 30 | * @param opener opener of this pagination 31 | * @return The number of buttons this pageView should have 32 | */ 33 | @Override 34 | public FillRange getFillRange(Capacity capacity, Player opener) { 35 | return FillRange.start(capacity).end(Slot.of(amountOfItems)); 36 | } 37 | 38 | @Override 39 | public ItemStack nextPageItem(Player player) { 40 | return ItemBuilder.legacy(Material.PAPER) 41 | .setDisplay("&aNext page") 42 | .build(); 43 | } 44 | 45 | @Override 46 | public ItemStack previousPageItem(Player player) { 47 | return ItemBuilder.legacy(Material.PAPER) 48 | .setDisplay("&ePrevious page") 49 | .build(); 50 | } 51 | 52 | @Override 53 | public String getName() { 54 | return "Example plain-pagination"; 55 | } 56 | 57 | @Override 58 | public @NotNull MenuTitle getTitle(DataRegistry dataRegistry, Player player) { 59 | int index = dataRegistry.getData("index"); 60 | return MenuTitles.createLegacy("&6Example Plain page #" + (index + 1)); 61 | } 62 | 63 | @Override 64 | public @NotNull Capacity getCapacity(DataRegistry dataRegistry, Player player) { 65 | return Capacity.ofRows(3); 66 | } 67 | 68 | 69 | @Override 70 | public @NotNull Content getContent(DataRegistry extraData, Player opener, Capacity capacity) { 71 | Content.Builder builder = Content.builder(capacity); 72 | for (int i = 0; i < amountOfItems; i++) { 73 | builder.setButton(i, new ExampleMenuComponent("" + i).toButton()); 74 | } 75 | return builder.build(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/iterator/SlotIterator.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.iterator; 2 | 3 | import io.github.mqzen.menus.misc.Capacity; 4 | import io.github.mqzen.menus.misc.Slot; 5 | import lombok.Getter; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.NoSuchElementException; 9 | 10 | @Getter 11 | public final class SlotIterator { 12 | 13 | private final Capacity capacity; 14 | private final Direction direction; 15 | private final Slot endSlot; 16 | private @NotNull Slot current; 17 | 18 | SlotIterator(@NotNull Slot startingSlot, Slot endSlot, Capacity capacity, Direction direction) { 19 | this.current = startingSlot; 20 | this.endSlot = endSlot; 21 | this.capacity = capacity; 22 | this.direction = direction; 23 | } 24 | 25 | SlotIterator(Capacity capacity, Direction direction) { 26 | this.current = Slot.of(0); 27 | this.endSlot = Slot.of(capacity.getTotalSize() - 1); 28 | this.capacity = capacity; 29 | this.direction = direction; 30 | } 31 | 32 | public static SlotIterator create(Capacity capacity, Direction direction) { 33 | return new SlotIterator(capacity, direction); 34 | } 35 | 36 | public static SlotIterator create(Slot start, Capacity capacity, Direction direction) { 37 | return new SlotIterator(start, Slot.of(capacity.getTotalSize() - 1), capacity, direction); 38 | } 39 | 40 | public static SlotIterator create(Slot start, Slot end, Capacity capacity, Direction direction) { 41 | return new SlotIterator(start, end, capacity, direction); 42 | } 43 | 44 | /** 45 | * Returns {@code true} if the iteration has more elements. 46 | * (In other words, returns {@code true} if {@link SlotIterator#current()} would 47 | * return an element rather than throwing an exception.) 48 | * 49 | * @return {@code true} if the iteration has more elements 50 | */ 51 | public boolean canContinue() { 52 | return current.getSlot() >= 0 && !direction.isOutsideBoundarySlot(current, endSlot) 53 | && verify(current.getRow(), capacity.getRows()) 54 | && verify(current.getColumn(), capacity.getColumns()); 55 | } 56 | 57 | private boolean verify(int value, int max) { 58 | return value >= 0 && value < max; 59 | } 60 | 61 | /** 62 | * Returns the current element in the iteration. 63 | * 64 | * @return the current element in the iteration 65 | * @throws NoSuchElementException if the iteration has no more elements 66 | */ 67 | public Slot current() { 68 | if(!this.canContinue()) { 69 | throw new NoSuchElementException(); 70 | } 71 | return current.copy(); 72 | } 73 | 74 | /** 75 | * Shifts the current slot to the next target slot 76 | * depending on the {@link Direction} of this iterator 77 | */ 78 | public void shift() { 79 | current = direction.modify(current); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/Capacity.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * Represents a capacity configuration characterized by rows and columns. 7 | * The class provides multiple factory methods to create instances either from the total size or 8 | * by specifying rows and columns directly. 9 | */ 10 | @Getter 11 | public final class Capacity { 12 | 13 | private final int totalSize; 14 | private final int rows, columns; 15 | 16 | private Capacity(int totalSize) { 17 | this.totalSize = totalSize; 18 | this.columns = 9; 19 | this.rows = totalSize / columns; 20 | } 21 | 22 | private Capacity(int rows, int columns) { 23 | this.totalSize = rows * columns; 24 | this.rows = rows; 25 | this.columns = columns; 26 | } 27 | 28 | /** 29 | * Factory method to create a Capacity instance based on the given total size. 30 | * 31 | * @param size the total size to set for the capacity 32 | * @return a new Capacity instance with the specified total size 33 | */ 34 | public static Capacity of(int size) { 35 | return new Capacity(size); 36 | } 37 | 38 | /** 39 | * Creates an instance of Capacity based on the specified number of rows and columns. 40 | * 41 | * @param rows the number of rows in the Capacity 42 | * @param columns the number of columns in the Capacity 43 | * @return a new Capacity instance defined by the provided rows and columns 44 | */ 45 | public static Capacity of(int rows, int columns) { 46 | return new Capacity(rows, columns); 47 | } 48 | 49 | /** 50 | * Creates a {@link Capacity} instance with the specified number of rows 51 | * and a fixed number of columns set to 9. 52 | * 53 | * @param rows the number of rows to set for the capacity 54 | * @return a new {@link Capacity} instance with the specified number of rows and 9 columns 55 | */ 56 | public static Capacity ofRows(int rows) { 57 | return new Capacity(rows, 9); 58 | } 59 | 60 | 61 | /** 62 | * Creates a flexible/dynamic {@link Capacity} instance that calculates the size according to 63 | * the specified number of buttons and a max size of the gui 64 | * 65 | * @param numberOfButtons the number of buttons to set for the capacity 66 | * @param maxSize the max size of the gui 67 | * @return a new {@link Capacity} instance 68 | */ 69 | public static Capacity flexible(final int numberOfButtons, final int maxSize) { 70 | // Calculate required size, rounding up to the nearest multiple of 9 71 | int requiredSize = (numberOfButtons + 8) / 9 * 9; 72 | 73 | // Limit to the maximum size that is also a multiple of 9 74 | int limitedSize = Math.min(requiredSize, maxSize); 75 | 76 | // Ensure that we return a valid GUI size (must be a multiple of 9) 77 | return Capacity.of(limitedSize - (limitedSize % 9)); 78 | } 79 | /** 80 | * Creates a flexible/dynamic {@link Capacity} instance that calculates the size according to 81 | * the specified number of buttons, where the max size of the gui can be up to 54 slots 82 | * 83 | * @param numberOfButtons the number of buttons to set for the capacity 84 | * @return a new {@link Capacity} instance 85 | */ 86 | public static Capacity flexible(final int numberOfButtons) { 87 | return flexible(numberOfButtons, 54); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/style/TextLayoutPane.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.style; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.misc.Capacity; 5 | import io.github.mqzen.menus.misc.button.Button; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | /** 9 | * The TextLayoutPane class is responsible for applying a pattern of text layout onto a content pane. 10 | * This class implements the Pane interface. 11 | */ 12 | public final class TextLayoutPane implements Pane { 13 | 14 | /** 15 | * Holds the pattern strings that define the layout of text within the pane. 16 | * Each string represents a row in the layout, and each character in the string 17 | * represents a different element to be laid out. 18 | * Must be initialized at construction time and is immutable thereafter. 19 | */ 20 | private final String[] pattern; 21 | /** 22 | * Represents the layout pattern of text, mapping characters to Button instances. 23 | * Used within the TextLayoutPane to format content based on a pre-defined design. 24 | */ 25 | private final TextLayout layout; 26 | /** 27 | * The capacity configuration for the TextLayoutPane. 28 | * This defines the number of rows and columns that the pane can handle. 29 | */ 30 | private final Capacity capacity; 31 | 32 | /** 33 | * Constructs a new TextLayoutPane. 34 | * 35 | * @param capacity the capacity configuration of the pane, encapsulating rows and columns 36 | * @param provider the provider for supplying a TextLayout mapping characters to buttons 37 | * @param pattern the pattern of the text layout to be applied within the pane 38 | */ 39 | public TextLayoutPane(Capacity capacity, TextLayoutProvider provider, String... pattern) { 40 | this.capacity = capacity; 41 | this.layout = provider.provide(); 42 | this.pattern = pattern; 43 | } 44 | 45 | /** 46 | * Constructs a TextLayoutPane with a specified capacity, text layout, and pattern. 47 | * 48 | * @param capacity the capacity configuration for the pane 49 | * @param layout the text layout that maps characters to Button instances 50 | * @param pattern the pattern to be applied to the content pane 51 | */ 52 | public TextLayoutPane(Capacity capacity, 53 | TextLayout layout, String... pattern) { 54 | this.capacity = capacity; 55 | this.pattern = pattern; 56 | this.layout = layout; 57 | } 58 | 59 | /** 60 | * Applies the pane on the content 61 | * @param content the content to apply the pane on 62 | */ 63 | @Override 64 | public void applyOn(@NotNull Content content) { 65 | if(pattern.length > capacity.getRows()) { 66 | throw new IllegalArgumentException( 67 | String.format("Pattern-rows(%s) exceeds that of capacity(%s)", pattern.length, capacity.getRows()) 68 | ); 69 | } 70 | for (int i = 0; i < pattern.length; i++) { 71 | String row = pattern[i]; 72 | if(row.length() != 9) { 73 | throw new IllegalArgumentException("Row #" + (i+1)+ "is not of length 9"); 74 | } 75 | replaceCharsWithButtons(i, row, content); 76 | } 77 | 78 | } 79 | 80 | private void replaceCharsWithButtons(int rowIndex, String row, Content content) { 81 | final char[] chars = row.toCharArray(); 82 | for (int column = 0; column < chars.length; column++) { 83 | Button button = layout.get(chars[column]); 84 | if(button == null)continue; 85 | content.setButton(rowIndex, column, button); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/io/github/mqzen/menus/ExampleMenu.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.base.Menu; 5 | import io.github.mqzen.menus.base.MenuView; 6 | import io.github.mqzen.menus.base.animation.AnimationTaskData; 7 | import io.github.mqzen.menus.base.animation.TransformingButton; 8 | import io.github.mqzen.menus.misc.Capacity; 9 | import io.github.mqzen.menus.misc.DataRegistry; 10 | import io.github.mqzen.menus.misc.itembuilder.ItemBuilder; 11 | import io.github.mqzen.menus.titles.MenuTitle; 12 | import io.github.mqzen.menus.titles.MenuTitles; 13 | import org.bukkit.Material; 14 | import org.bukkit.entity.Player; 15 | import org.bukkit.event.inventory.InventoryClickEvent; 16 | import org.bukkit.inventory.ItemStack; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | public final class ExampleMenu implements Menu { 20 | 21 | /** 22 | * @return The unique name for this menu 23 | */ 24 | @Override 25 | public String getName() { 26 | return "example menu"; 27 | } 28 | 29 | /** 30 | * @param extraData the data container for this menu for extra data 31 | * @param opener the player who is opening this menu 32 | * @return the title for this menu 33 | */ 34 | @Override 35 | public @NotNull MenuTitle getTitle(DataRegistry extraData, Player opener) { 36 | return MenuTitles.createLegacy("&cExample Menu"); 37 | } 38 | 39 | /** 40 | * @param extraData the data container for this menu for extra data 41 | * @param opener the player who is opening this menu 42 | * @return the capacity/size for this menu 43 | */ 44 | @Override 45 | public @NotNull Capacity getCapacity(DataRegistry extraData, Player opener) { 46 | return Capacity.ofRows(3);//Alternative approach: Capacity.ofRows(3) 47 | } 48 | 49 | 50 | private final static ItemStack[] ITEM_FRAMES = { 51 | ItemBuilder.legacy(Material.FIREWORK).build(), 52 | ItemBuilder.legacy(Material.TNT).build(), 53 | ItemBuilder.legacy(Material.SKULL_ITEM, 1, (short)1).build(), 54 | ItemBuilder.legacy(Material.SNOW_BALL).build() 55 | }; 56 | 57 | @Override 58 | public @NotNull Content getContent( 59 | DataRegistry extraData, 60 | Player opener, 61 | Capacity capacity 62 | ) { 63 | 64 | TransformingButton randomGameButton = new TransformingButton( 65 | AnimationTaskData 66 | .builder() 67 | .delay(10L) 68 | .ticks(12L) // Change the delay to 20 ticks (1 second) 69 | .async(true) 70 | .build(), 71 | 72 | (item) -> ItemBuilder.legacy(item) 73 | .setDisplay("&aRandom Game") 74 | .setLore("&7Click to play a random game") 75 | .build(), 76 | ITEM_FRAMES 77 | ).click((view, event) -> { 78 | // Handle the click event here 79 | Player player = (Player) event.getWhoClicked(); 80 | player.sendMessage("You clicked the random game button!"); 81 | }); 82 | 83 | return Content.builder(capacity) 84 | .setButton(1, new ExampleAutoAnimatedButton()) 85 | .setButton(2, randomGameButton) 86 | .build(); 87 | } 88 | 89 | @Override 90 | public void onPostClick(MenuView playerMenuView, InventoryClickEvent event) { 91 | //happens after button click is executed 92 | event.setCancelled(true); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/animation/TransformingButton.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.animation; 2 | 3 | import io.github.mqzen.menus.base.MenuView; 4 | import io.github.mqzen.menus.misc.DataRegistry; 5 | import io.github.mqzen.menus.misc.Slot; 6 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 7 | import lombok.Getter; 8 | import org.bukkit.inventory.ItemStack; 9 | import org.jetbrains.annotations.NotNull; 10 | import java.util.function.UnaryOperator; 11 | 12 | /** 13 | * The TransformingButton class is an extension of Button that also implements the {@link AnimatedButton} interface. 14 | * It allows for a button that can transform through a series of ItemStacks. 15 | */ 16 | @Getter 17 | public final class TransformingButton extends AnimatedButton { 18 | 19 | private final ItemStack[] transformingItems; 20 | private UnaryOperator itemTransformer; 21 | private int current = 0; 22 | 23 | public TransformingButton(AnimationTaskData animationTaskData, UnaryOperator itemTransformer, @NotNull ItemStack[] transformingItems) { 24 | super(animationTaskData, transformingItems.length == 0 ? null : itemTransformer.apply(transformingItems[0])); 25 | this.itemTransformer = itemTransformer; 26 | this.transformingItems = transformingItems; 27 | } 28 | 29 | public TransformingButton(UnaryOperator itemTransformer, @NotNull ItemStack[] transformingItems) { 30 | this(AnimationTaskData.defaultData(), itemTransformer, transformingItems); 31 | } 32 | 33 | public TransformingButton(AnimationTaskData animationTaskData, @NotNull ItemStack[] transformingItems, DataRegistry dataRegistry) { 34 | this(animationTaskData, item -> item, transformingItems, dataRegistry); 35 | } 36 | 37 | public TransformingButton(AnimationTaskData animationTaskData, @NotNull ItemStack[] transformingItems) { 38 | this(animationTaskData, item -> item, transformingItems); 39 | } 40 | 41 | public TransformingButton(@NotNull ItemStack[] transformingItems) { 42 | this(item -> item, transformingItems); 43 | } 44 | 45 | public TransformingButton(AnimationTaskData animationTaskData, UnaryOperator itemTransformer, @NotNull ItemStack[] transformingItems, DataRegistry dataRegistry) { 46 | super(animationTaskData, transformingItems.length == 0 ? null : itemTransformer.apply(transformingItems[0]), null, dataRegistry); 47 | this.itemTransformer = itemTransformer; 48 | this.transformingItems = transformingItems; 49 | } 50 | 51 | public TransformingButton itemTransformer(UnaryOperator itemTransformer) { 52 | this.itemTransformer = itemTransformer; 53 | if (transformingItems.length > 0) { 54 | this.setItem(itemTransformer.apply(transformingItems[0])); 55 | } 56 | return this; 57 | } 58 | 59 | public TransformingButton click(ButtonClickAction action) { 60 | setAction(action); 61 | return this; 62 | } 63 | 64 | public TransformingButton click(ButtonClickAction.ActionExecutor action) { 65 | setAction(ButtonClickAction.plain(action)); 66 | return this; 67 | } 68 | 69 | @Override 70 | public void animate(Slot slot, @NotNull MenuView view) { 71 | if(current+1 >= transformingItems.length) 72 | current = 0; 73 | else 74 | current++; 75 | 76 | ItemStack next = transformingItems[current]; 77 | assert next != null; 78 | 79 | this.setItem(itemTransformer.apply(next)); 80 | view.replaceButton(slot, this); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/Menu.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base; 2 | 3 | 4 | import io.github.mqzen.menus.misc.Capacity; 5 | import io.github.mqzen.menus.misc.DataRegistry; 6 | import io.github.mqzen.menus.titles.MenuTitle; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.event.inventory.*; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | /** 12 | * The Menu interface provides the structure for creating custom menus 13 | * in an inventory system. It allows you to define the name, type, title, 14 | * capacity, and content of the menu. Additionally, it provides hooks 15 | * for handling events such as clicks, opening, and closing of the menu. 16 | */ 17 | public interface Menu { 18 | 19 | /** 20 | * @return The unique name for this menu 21 | */ 22 | String getName(); 23 | 24 | /** 25 | * Type of inventory you're creating 26 | * @return the type of inventory 27 | */ 28 | default InventoryType getMenuType() { 29 | return InventoryType.CHEST; 30 | } 31 | 32 | /** 33 | * @param extraData the data container for this menu for extra data 34 | * @param opener the player who is opening this menu 35 | * @return the title for this menu 36 | */ 37 | @NotNull MenuTitle getTitle(DataRegistry extraData, Player opener); 38 | 39 | /** 40 | * @param extraData the data container for this menu for extra data 41 | * @param opener the player who is opening this menu 42 | * @return the capacity/size for this menu 43 | */ 44 | @NotNull Capacity getCapacity(DataRegistry extraData, Player opener); 45 | 46 | /** 47 | * Creates the content for the menu 48 | * 49 | * @param extraData the data container for this menu for extra data 50 | * @param opener the player opening this menu 51 | * @param capacity the capacity set by the user above 52 | * @return the content of the menu to add (this includes items) 53 | */ 54 | @NotNull Content getContent(DataRegistry extraData, Player opener, Capacity capacity); 55 | 56 | /** 57 | * What's going to happen before the click 58 | * @param playerMenuView the menu view that the player has clicked on. 59 | * @param event the click event 60 | * @return if true, it continues to initiate the click actions of the button, otherwise it doesn't 61 | */ 62 | default boolean onPreClick(MenuView playerMenuView, InventoryClickEvent event) { 63 | return true; 64 | } 65 | 66 | /** 67 | * What's going to happen after the click 68 | * @param playerMenuView the menu view that the player has clicked on. 69 | * @param event the click event 70 | */ 71 | default void onPostClick(MenuView playerMenuView, InventoryClickEvent event) { 72 | } 73 | 74 | /** 75 | * What's going to happen on close 76 | * 77 | * @param playerMenuView the menu closing 78 | * @param event the event of closing the inventory of this menu 79 | */ 80 | default void onClose(MenuView playerMenuView, InventoryCloseEvent event) { 81 | } 82 | 83 | /** 84 | * What's going to happen on opening of the menu's inventory 85 | * 86 | * @param playerMenuView the holder of the inventory opening 87 | * @param event the inventory open event 88 | */ 89 | default void onOpen(MenuView playerMenuView, InventoryOpenEvent event) { 90 | } 91 | 92 | /** 93 | * Handles the inventory drag event within the menu. This method is called when a player 94 | * drags an item within the inventory associated with this menu. 95 | * 96 | * @param playerMenuView the menu view that the player is interacting with. 97 | * @param event the drag event that occurred in the inventory. 98 | */ 99 | default void onDrag(MenuView playerMenuView, InventoryDragEvent event) { 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/serialization/impl/SerializedMenuYaml.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.serialization.impl; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.base.serialization.SerializedMenuIO; 5 | import io.github.mqzen.menus.misc.Capacity; 6 | import io.github.mqzen.menus.misc.DataRegistry; 7 | import io.github.mqzen.menus.misc.Slot; 8 | import io.github.mqzen.menus.misc.button.Button; 9 | import io.github.mqzen.menus.misc.button.actions.ButtonActionRegistry; 10 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 11 | import org.bukkit.configuration.ConfigurationSection; 12 | import org.bukkit.configuration.file.YamlConfiguration; 13 | import org.bukkit.inventory.ItemStack; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | public final class SerializedMenuYaml implements SerializedMenuIO { 20 | 21 | 22 | @Override 23 | public Class fileType() { 24 | return YamlConfiguration.class; 25 | } 26 | 27 | @Override 28 | public void write(@NotNull DataRegistry registry, 29 | @NotNull YamlConfiguration configuration) { 30 | 31 | String name = registry.getData("name"); 32 | Capacity capacity = registry.getData("capacity"); 33 | String title = registry.getData("title"); 34 | 35 | configuration.set("name", name); 36 | configuration.set("properties.capacity", capacity.getRows()); 37 | configuration.set("properties.title", title); 38 | 39 | YamlConfiguration buttonsSection = new YamlConfiguration(); 40 | 41 | Content content = registry.getData("content"); 42 | if(content == null) { 43 | return; 44 | } 45 | Map buttonMap = content.getButtonMap(); 46 | for (Slot slot : buttonMap.keySet()) { 47 | //since there's no input, the default button name will be lowercase ("slot_{num}"); 48 | String key = "slot_" + slot.getSlot(); 49 | buttonsSection.set(key + ".slot",slot.getSlot()); 50 | buttonsSection.set(key + ".item", buttonMap.get(slot).getItem()); 51 | buttonsSection.set(key + ".actions", registry.getData("BTN:" + slot.getSlot())); 52 | } 53 | configuration.set("buttons", buttonsSection); 54 | } 55 | 56 | @Override 57 | public @NotNull DataRegistry read(@NotNull YamlConfiguration file) { 58 | DataRegistry registry = new DataRegistry(); 59 | 60 | String name = file.getString("name"); 61 | registry.setData("name", name); 62 | 63 | Capacity capacity = Capacity.ofRows(file.getInt("properties.capacity")); 64 | registry.setData("capacity", capacity); 65 | 66 | String menuTitle = file.getString("properties.title"); 67 | registry.setData("title", menuTitle); 68 | 69 | Content content = Content.builder(capacity).build(); 70 | ConfigurationSection section = file.getConfigurationSection("buttons"); 71 | 72 | if(section == null) { 73 | registry.setData("content", content); 74 | return registry; 75 | } 76 | 77 | for(String key : section.getKeys(false)) { 78 | int slotPosition = section.getInt(key + ".slot"); 79 | ItemStack itemStack = section.getItemStack(key + ".item"); 80 | List actions = section.getStringList(key + ".actions"); 81 | registry.setData("BTN:" + slotPosition, actions); 82 | content.setButton(slotPosition, Button.clickable(itemStack, 83 | ButtonClickAction.plain((menu, event)-> { 84 | for(String action : actions) { 85 | int index = action.indexOf("("); 86 | String tag = action.substring(0, index); 87 | ButtonClickAction clickAction = ButtonActionRegistry.getInstance().getAction(tag); 88 | if(clickAction != null) 89 | clickAction.execute(menu, event); 90 | } 91 | }))); 92 | } 93 | registry.setData("content", content); 94 | return registry; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/test/java/io/github/mqzen/menus/ExampleAutoPage.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.base.pagination.*; 5 | import io.github.mqzen.menus.misc.Capacity; 6 | import io.github.mqzen.menus.misc.DataRegistry; 7 | import io.github.mqzen.menus.misc.Slot; 8 | import io.github.mqzen.menus.misc.button.Button; 9 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 10 | import io.github.mqzen.menus.misc.itembuilder.ItemBuilder; 11 | import io.github.mqzen.menus.titles.MenuTitle; 12 | import io.github.mqzen.menus.titles.MenuTitles; 13 | import org.bukkit.Material; 14 | import org.bukkit.Sound; 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.event.inventory.InventoryClickEvent; 17 | import org.bukkit.inventory.ItemStack; 18 | import org.jetbrains.annotations.NotNull; 19 | 20 | public class ExampleAutoPage extends Page { 21 | 22 | 23 | /** 24 | * The range of button-filling of {@link PageComponent} 25 | * this page should have 26 | * 27 | * @param capacity the capacity for the page 28 | * @param opener opener of this pagination 29 | * @return The range of button-filling of {@link PageComponent} 30 | * @see FillRange 31 | */ 32 | @Override 33 | public FillRange getFillRange(Capacity capacity, Player opener) { 34 | return FillRange.start(capacity, Slot.of(10)) //here, the start is 0 automatically 35 | .end(Slot.of(25)) 36 | .except(Slot.of(17), Slot.of(18)); 37 | } 38 | 39 | @Override 40 | public ItemStack nextPageItem(Player player) { 41 | return ItemBuilder.legacy(Material.PAPER) 42 | .setDisplay("&aNext page") 43 | .build(); 44 | } 45 | 46 | @Override 47 | public ItemStack previousPageItem(Player player) { 48 | return ItemBuilder.legacy(Material.PAPER) 49 | .setDisplay("&ePrevious page") 50 | .build(); 51 | } 52 | 53 | @Override 54 | public String getName() { 55 | return "Example auto-pagination"; 56 | } 57 | 58 | @Override 59 | public @NotNull MenuTitle getTitle(DataRegistry dataRegistry, Player player) { 60 | int index = dataRegistry.getData("index"); 61 | Pagination pagination = dataRegistry.getData("pagination"); 62 | int max = pagination.getMaximumPages(); 63 | return MenuTitles.createModern("Example Page " + (index+1) + "/" + max); 64 | } 65 | 66 | @Override 67 | public @NotNull Capacity getCapacity(DataRegistry dataRegistry, Player player) { 68 | return Capacity.ofRows(4); 69 | } 70 | 71 | @Override 72 | public @NotNull Content getContent(DataRegistry dataRegistry, Player player, Capacity capacity) { 73 | Content.Builder builder = Content 74 | .builder(capacity) 75 | .setButton(31, Button.clickable(new ItemStack(Material.BARRIER), ButtonClickAction.plain((menu, event) -> { 76 | event.setCancelled(true); 77 | player.closeInventory(); 78 | }))); 79 | return builder.build(); 80 | } 81 | 82 | @Override 83 | protected void onSwitchingToNextPage( 84 | @NotNull Pagination pagination, 85 | @NotNull Capacity capacity, 86 | @NotNull Slot slot, 87 | @NotNull PageView clickedView, 88 | @NotNull InventoryClickEvent event 89 | ) { 90 | Player player = (Player) event.getWhoClicked(); 91 | player.playSound(player.getLocation(), Sound.ARROW_HIT, 1, 1); 92 | } 93 | 94 | @Override 95 | protected void onSwitchingToPreviousPage( 96 | @NotNull Pagination pagination, 97 | @NotNull Capacity capacity, 98 | @NotNull Slot slot, 99 | @NotNull PageView clickedView, 100 | @NotNull InventoryClickEvent event 101 | ) { 102 | Player player = (Player) event.getWhoClicked(); 103 | player.playSound(player.getLocation(), Sound.VILLAGER_NO, 1, 1); 104 | } 105 | } -------------------------------------------------------------------------------- /src/test/java/io/github/mqzen/menus/LotusExamplePlugin.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import io.github.mqzen.menus.base.Menu; 4 | import io.github.mqzen.menus.base.pagination.Pagination; 5 | import io.github.mqzen.menus.base.pagination.exception.InvalidPageException; 6 | import io.github.mqzen.menus.base.serialization.SerializableMenu; 7 | import io.github.mqzen.menus.base.serialization.impl.SerializedMenuYaml; 8 | import io.github.mqzen.menus.misc.DataRegistry; 9 | import org.bukkit.command.Command; 10 | import org.bukkit.command.CommandExecutor; 11 | import org.bukkit.command.CommandSender; 12 | import org.bukkit.configuration.file.YamlConfiguration; 13 | import org.bukkit.entity.Player; 14 | import org.bukkit.plugin.java.JavaPlugin; 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | public final class LotusExamplePlugin extends JavaPlugin implements CommandExecutor { 21 | 22 | private Lotus lotus; 23 | 24 | @Override 25 | public void onEnable() { 26 | lotus = Lotus.load(this); 27 | lotus.enableDebugger(); 28 | 29 | this.getCommand("test").setExecutor(this); 30 | getDataFolder().mkdirs(); 31 | getCommand("save").setExecutor((commandSender, command, s, strings) -> { 32 | File exampleFile = new File(getDataFolder(), "example.yml"); 33 | if (!exampleFile.exists()) { 34 | try { 35 | exampleFile.createNewFile(); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | ExampleMenu exampleMenu = new ExampleMenu(); 41 | DataRegistry registry = DataRegistry.empty(); 42 | SerializableMenu menu = new SerializableMenu(exampleMenu.getName(), exampleMenu.getTitle(registry, null).asString(), 43 | exampleMenu.getCapacity(registry, null), exampleMenu.getContent(registry, null, exampleMenu.getCapacity(registry, null))); 44 | 45 | YamlConfiguration configuration = YamlConfiguration.loadConfiguration(exampleFile); 46 | SerializedMenuYaml yaml = (SerializedMenuYaml) lotus.getMenuIO(); 47 | yaml.write(lotus.getMenuSerializer().serialize(menu), configuration); 48 | try { 49 | configuration.save(exampleFile); 50 | } catch (IOException e) { 51 | throw new RuntimeException(e); 52 | } 53 | commandSender.sendMessage("saved"); 54 | return true; 55 | }); 56 | getCommand("load").setExecutor((commandSender, command, s, strings) -> { 57 | File exampleFile = new File(getDataFolder(), "example.yml"); 58 | if (!exampleFile.exists()) { 59 | return true; 60 | } 61 | YamlConfiguration configuration = YamlConfiguration.loadConfiguration(exampleFile); 62 | SerializedMenuYaml yaml = (SerializedMenuYaml) lotus.getMenuIO(); 63 | 64 | DataRegistry registry = yaml.read(configuration); 65 | Menu menu = lotus.getMenuSerializer().deserialize(registry); 66 | lotus.openMenu((Player) commandSender,menu); 67 | commandSender.sendMessage("loaded"); 68 | return true; 69 | }); 70 | } 71 | 72 | 73 | public final static List islands = new ArrayList<>(); 74 | static { 75 | for (int i = 0; i < 50; i++) { 76 | islands.add(new ExampleMenuComponent(i + "")); 77 | } 78 | } 79 | @Override 80 | public boolean onCommand(CommandSender sender, Command command, 81 | String label, String[] args) { 82 | 83 | if(args.length == 0) { 84 | lotus.openMenu((Player) sender, new ExampleMenu()); 85 | return true; 86 | } 87 | if(args.length != 1) { 88 | return false; 89 | } 90 | 91 | 92 | String sub = args[0]; 93 | Pagination pagination; 94 | if(sub.equalsIgnoreCase("auto")) { 95 | pagination = Pagination.auto(lotus) 96 | .creator(new ExampleAutoPage()) 97 | .componentProvider(()-> islands) 98 | .build(); 99 | }else { 100 | pagination = Pagination.plain(lotus) 101 | .page(0, new ExamplePlainPage(10)) 102 | .page(1, new ExamplePlainPage(20)) 103 | .build(); 104 | } 105 | 106 | 107 | try { 108 | pagination.open((Player) sender); 109 | } catch (InvalidPageException e) { 110 | throw new RuntimeException(e); 111 | } 112 | return true; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/pagination/PageView.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.pagination; 2 | 3 | import io.github.mqzen.menus.base.BaseMenuView; 4 | import io.github.mqzen.menus.base.Content; 5 | import io.github.mqzen.menus.base.ViewOpener; 6 | import io.github.mqzen.menus.misc.Capacity; 7 | import io.github.mqzen.menus.misc.ViewData; 8 | import lombok.Getter; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.inventory.InventoryCloseEvent; 11 | import org.jetbrains.annotations.ApiStatus; 12 | 13 | /** 14 | * Represents a view for a paginated menu system in the Lotus framework. 15 | * This class extends BaseMenuView and is internally used to handle the 16 | * visualization and interaction of paginated content. 17 | */ 18 | @Getter 19 | @ApiStatus.Internal 20 | public final class PageView extends BaseMenuView { 21 | 22 | private final Pagination pagination; 23 | private final int index; 24 | 25 | private Capacity capacity; 26 | 27 | PageView(Pagination pagination, Page creator, int index) { 28 | super(pagination.getLotusAPI(), creator); 29 | this.pagination = pagination; 30 | this.index = index; 31 | this.dataRegistry.setData("index", index); 32 | this.dataRegistry.setData("pagination", pagination); 33 | } 34 | 35 | PageView(Pagination pagination, int index) { 36 | super(pagination.getLotusAPI(), pagination.getPageCreator()); 37 | this.pagination = pagination; 38 | this.index = index; 39 | this.dataRegistry.setData("index", index); 40 | this.dataRegistry.setData("pagination", pagination); 41 | } 42 | 43 | /** 44 | * Initializes the menu-view's data during pre-opening phase 45 | * 46 | * @param page the menu to be used in creating the menu view's data 47 | * @param opener the player opening 48 | * @see ViewData 49 | */ 50 | @Override 51 | public void initialize(Page page, Player opener) { 52 | capacity = page.getCapacity(this.dataRegistry, opener); 53 | 54 | int maxButtonsCount = page.getFillRange(capacity, opener).getCount(); 55 | 56 | if(maxButtonsCount > capacity.getTotalSize()) { 57 | maxButtonsCount = capacity.getTotalSize(); 58 | } 59 | 60 | final Content playerDefaultContent = page.getContent(this.dataRegistry, opener, capacity); 61 | final Content pageDefaultContent = page.defaultContent(pagination, this, capacity, opener); 62 | 63 | if(pagination.trimExtraContent()) { 64 | playerDefaultContent.trim(maxButtonsCount); 65 | } 66 | 67 | final Content totalDefaultContent = playerDefaultContent.mergeWith(pageDefaultContent); 68 | final int defaultContentExpectedCapacity = capacity.getTotalSize()-maxButtonsCount; 69 | if(pagination.isAutomatic() && totalDefaultContent.size() > defaultContentExpectedCapacity) { 70 | throw new IllegalStateException( 71 | String.format("Exceeded expected default capacity of the page \n " + 72 | "Expected= %s, Actual= %s", defaultContentExpectedCapacity, totalDefaultContent.size()) 73 | ); 74 | } 75 | if(!pagination.isAutomatic() && totalDefaultContent.size() > capacity.getTotalSize()) { 76 | throw new IllegalStateException( 77 | String.format("Content of plain page #%s has exceeded the page's capacity(%s)", (index+1), capacity.getTotalSize()) 78 | ); 79 | } 80 | 81 | currentOpenedData = new ViewData( 82 | page.getTitle(this.dataRegistry, opener), capacity, totalDefaultContent 83 | ); 84 | } 85 | 86 | /** 87 | * Opens the menu view using some information 88 | * such as the way on how to open this view (ViewOpener) and the 89 | * player who is opening and in which the view opened will be displayed for 90 | * 91 | * @param viewOpener Interface defining how to open this view 92 | * @param player the player opening this view 93 | */ 94 | @Override 95 | public void openView(ViewOpener viewOpener, Player player) { 96 | //in automatic page view , we MUSTN'T initialize the data that was already pre-initialized internally in Pagination 97 | if (!pagination.isAutomatic()) 98 | initialize(menu, player); 99 | 100 | currentOpenInventory = viewOpener.openMenu(api, player, this, currentOpenedData); 101 | currentOpener = player; 102 | } 103 | 104 | /** 105 | * What occurs/is executed on closing of this menu view 106 | * 107 | * @param event the close event for the view's internal inventory 108 | */ 109 | @Override 110 | public void onClose(InventoryCloseEvent event) { 111 | super.onClose(event); 112 | currentOpener = null; 113 | currentOpenInventory = null; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/pagination/FillRange.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.pagination; 2 | 3 | import io.github.mqzen.menus.misc.Capacity; 4 | import io.github.mqzen.menus.misc.Slot; 5 | import lombok.Getter; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import java.util.*; 9 | 10 | /** 11 | * FillRange is a utility class used to define a range of slots to be filled in a paginated context. 12 | * It helps in setting ranges and excluding certain slots. 13 | */ 14 | @Getter 15 | public final class FillRange { 16 | 17 | private final Capacity capacity; 18 | private final @NotNull Slot start; 19 | private @Nullable Slot end = null; 20 | 21 | private final Set forbiddenSlots = new HashSet<>(); 22 | 23 | private FillRange(Capacity capacity, @NotNull Slot start) { 24 | this.capacity = capacity; 25 | this.start = start; 26 | } 27 | 28 | /** 29 | * Initializes a FillRange object with the given capacity and a starting slot of zero. 30 | * 31 | * @param capacity the capacity defining the total size for the range. 32 | * @return a new FillRange object starting from slot 0 with the specified capacity. 33 | */ 34 | public static FillRange start(Capacity capacity) { 35 | return start(capacity, Slot.of(0)); 36 | } 37 | 38 | /** 39 | * Creates a new instance of FillRange starting from the specified slot. 40 | * 41 | * @param capacity the capacity defining the total size and structure. 42 | * @param start the starting slot for this range. 43 | * @return a new instance of FillRange initialized with the given capacity and starting slot. 44 | */ 45 | public static FillRange start(Capacity capacity, Slot start) { 46 | return new FillRange(capacity, start); 47 | } 48 | 49 | /** 50 | * Sets the end slot for the range. Ensures the end slot is greater than the start slot. 51 | * 52 | * @param end the slot to set as the end of the range; can be null. 53 | * @return the FillRange instance with the updated end slot. 54 | * @throws IllegalStateException if the provided end slot is less than or equal to the start slot. 55 | */ 56 | public FillRange end(@Nullable Slot end) { 57 | if(end != null && end.getSlot() <= start.getSlot()) { 58 | throw new IllegalStateException("End slot '" + end.getSlot() + "' is less than the start slot"); 59 | } 60 | this.end = end; 61 | return this; 62 | } 63 | 64 | /** 65 | * Excludes the specified slots from being filled. 66 | * 67 | * @param slots the slots to be excluded from the fill range 68 | * @return the updated FillRange instance with the specified slots excluded 69 | */ 70 | public FillRange except(Slot... slots) { 71 | forbiddenSlots.addAll(Arrays.asList(slots)); 72 | return this; 73 | } 74 | 75 | /** 76 | * Adds a collection of forbidden slots to the current list of forbidden slots. 77 | * 78 | * @param slots a collection of slots to be marked as forbidden 79 | * @return the current FillRange instance with updated forbidden slots 80 | */ 81 | public FillRange except(Collection slots) { 82 | forbiddenSlots.addAll(slots); 83 | return this; 84 | } 85 | 86 | /** 87 | * Checks if a given slot is marked as forbidden. 88 | * 89 | * @param slot the slot to check for being forbidden 90 | * @return true if the slot is forbidden, false otherwise 91 | */ 92 | public boolean isForbiddenSlot(Slot slot) { 93 | return forbiddenSlots.contains(slot); 94 | } 95 | 96 | /** 97 | * Calculates the count of available slots within the specified range that are not forbidden. 98 | * The range is defined from the starting slot to the ending slot. If the ending slot 99 | * is not set, it defaults to the last slot within the given capacity. 100 | * 101 | * @return the count of available slots excluding the forbidden slots within the specified range. 102 | * @throws IllegalStateException if the count of forbidden slots is greater than or equal to the total range. 103 | */ 104 | public int getCount() { 105 | if(end == null) { 106 | end = Slot.last(capacity); 107 | } 108 | int range = end.getSlot()-start.getSlot(); 109 | int diff = range-forbiddenSlots.size(); 110 | if(diff <= 0) { 111 | throw new IllegalStateException("Forbidden slots count is greater than or equal to the range provided"); 112 | } 113 | return diff; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/pagination/Page.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.pagination; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.base.Menu; 5 | import io.github.mqzen.menus.misc.Capacity; 6 | import io.github.mqzen.menus.misc.Slot; 7 | import io.github.mqzen.menus.misc.button.Button; 8 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.inventory.InventoryClickEvent; 11 | import org.bukkit.inventory.ItemStack; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | /** 15 | * Abstract class representing a Page in a paginated menu system. 16 | * A Page provides methods to define its button layout and interaction behavior. 17 | */ 18 | public abstract class Page implements Menu { 19 | 20 | protected Page() { 21 | 22 | } 23 | 24 | /** 25 | * The range of button-filling of {@link PageComponent} 26 | * this page should have 27 | * 28 | * @param capacity the capacity for the page 29 | * @param opener opener of this pagination 30 | * 31 | * @see FillRange 32 | * 33 | * @return The range of button-filling of {@link PageComponent} 34 | */ 35 | public abstract FillRange getFillRange(Capacity capacity, Player opener); 36 | 37 | /** 38 | * Determines and returns the slot designated for the "next page" button in a paginated menu system. 39 | * 40 | * @param capacity the capacity of the current page 41 | * @return the slot designated for the "next page" button 42 | */ 43 | public Slot nextPageSlot(Capacity capacity) { 44 | return Slot.last(capacity); 45 | } 46 | 47 | /** 48 | * Calculates the slot position for the "previous page" button on a paginated menu. 49 | * 50 | * @param capacity the total capacity of slots available in the menu 51 | * @return the slot position for the "previous page" button 52 | */ 53 | public Slot previousPageSlot(Capacity capacity) { 54 | return nextPageSlot(capacity).subtractBy(8); 55 | } 56 | 57 | /** 58 | * Retrieves the item stack representing the button for navigating to the next page in a paginated menu. 59 | * 60 | * @param player the player interacting with the menu and for whom the item stack is being retrieved 61 | * @return the item stack representing the next page button for the specified player 62 | */ 63 | public abstract ItemStack nextPageItem(Player player); 64 | 65 | /** 66 | * Provides the item stack representation for the "previous page" button in the paginated menu. 67 | * 68 | * @param player the player for whom the previous page item is being created 69 | * @return the ItemStack representing the previous page button 70 | */ 71 | public abstract ItemStack previousPageItem(Player player); 72 | 73 | /** 74 | * Generates the default content layout for a paginated menu system. 75 | * This includes setting up the next and previous page buttons if applicable. 76 | * 77 | * @param pagination the pagination object to control page navigation 78 | * @param pageView the current page view state 79 | * @param capacity the capacity of the menu 80 | * @param player the player interacting with the menu 81 | * @return the content of the menu with navigation buttons set 82 | */ 83 | public final Content defaultContent( 84 | Pagination pagination, 85 | PageView pageView, 86 | Capacity capacity, 87 | Player player 88 | ) { 89 | Slot previousButtonSlot = previousPageSlot(capacity); 90 | Slot nextButtonSlot = nextPageSlot(capacity); 91 | 92 | Content content = Content.empty(capacity); 93 | 94 | if (!pagination.isLast(pageView)) 95 | content.setButton(nextButtonSlot, Button.clickable(nextPageItem(player), 96 | ButtonClickAction.plain((menu, event) -> { 97 | event.setCancelled(true); 98 | onSwitchingToNextPage(pagination, capacity, nextButtonSlot, (PageView) menu, event); 99 | pagination.next(); 100 | }))); 101 | 102 | if (!pagination.isFirst(pageView)) 103 | content.setButton(previousButtonSlot, Button.clickable(previousPageItem(player), 104 | ButtonClickAction.plain((menu, event) -> { 105 | event.setCancelled(true); 106 | onSwitchingToPreviousPage(pagination, capacity, previousButtonSlot, (PageView) menu, event); 107 | pagination.previous(); 108 | }))); 109 | 110 | return content; 111 | } 112 | 113 | protected void onSwitchingToNextPage( 114 | @NotNull Pagination pagination, 115 | @NotNull Capacity capacity, 116 | @NotNull Slot clickedSlot, 117 | @NotNull PageView clickedView, 118 | @NotNull InventoryClickEvent event 119 | ) { 120 | 121 | } 122 | 123 | protected void onSwitchingToPreviousPage( 124 | @NotNull Pagination pagination, 125 | @NotNull Capacity capacity, 126 | @NotNull Slot clickedSlot, 127 | @NotNull PageView clickedView, 128 | @NotNull InventoryClickEvent event 129 | ) { 130 | 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/serialization/SerializableMenu.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.serialization; 2 | 3 | import io.github.mqzen.menus.base.Content; 4 | import io.github.mqzen.menus.base.Menu; 5 | import io.github.mqzen.menus.misc.Capacity; 6 | import io.github.mqzen.menus.misc.DataRegistry; 7 | import io.github.mqzen.menus.misc.Slot; 8 | import io.github.mqzen.menus.misc.itembuilder.LegacyItemBuilder; 9 | import io.github.mqzen.menus.titles.MenuTitle; 10 | import io.github.mqzen.menus.titles.MenuTitles; 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.inventory.ItemStack; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Objects; 18 | 19 | import static io.github.mqzen.menus.base.serialization.SimpleMenuSerializer.PLAYER_PLACEHOLDER; 20 | 21 | public final class SerializableMenu implements Menu { 22 | 23 | private final String name; 24 | private final String titleString; 25 | private final Capacity capacity; 26 | private final Content content; 27 | 28 | public SerializableMenu( 29 | String name, String titleString, 30 | Capacity capacity, Content content 31 | ) { 32 | this.name = name; 33 | this.titleString = titleString; 34 | this.capacity = capacity; 35 | this.content = content; 36 | } 37 | 38 | public SerializableMenu(DataRegistry dataRegistry) { 39 | this( 40 | dataRegistry.getData("name"), 41 | dataRegistry.getData("title"), 42 | dataRegistry.getData("capacity"), 43 | dataRegistry.getData("content") 44 | ); 45 | } 46 | 47 | /** 48 | * @return The unique name for this menu 49 | */ 50 | @Override 51 | public String getName() { 52 | return name; 53 | } 54 | 55 | /** 56 | * @param extraData the data container for this menu for extra data 57 | * @param opener the player who is opening this menu 58 | * @return the title for this menu 59 | */ 60 | @Override 61 | public @NotNull MenuTitle getTitle(DataRegistry extraData, Player opener) { 62 | return MenuTitles.createLegacy(titleString() 63 | .replace(PLAYER_PLACEHOLDER, opener.getName())); 64 | } 65 | 66 | /** 67 | * @param extraData the data container for this menu for extra data 68 | * @param opener the player who is opening this menu 69 | * @return the capacity/size for this menu 70 | */ 71 | @Override 72 | public @NotNull Capacity getCapacity(DataRegistry extraData, Player opener) { 73 | return capacity; 74 | } 75 | 76 | /** 77 | * Creates the content for the menu 78 | * 79 | * @param extraData the data container for this menu for extra data 80 | * @param opener the player opening this menu 81 | * @param capacity the capacity set by the user above 82 | * @return the content of the menu to add (this includes items) 83 | */ 84 | @Override 85 | public @NotNull Content getContent(DataRegistry extraData, Player opener, Capacity capacity) { 86 | 87 | Content modified = content(); 88 | for (Slot slot : modified.getButtonMap().keySet()) { 89 | modified.setButton(slot, modified.getButton(slot).map((b) -> { 90 | if (b.getItem() == null) return b; 91 | ItemStack oldItem = b.getItem(); 92 | String oldDisplay = ""; 93 | if (oldItem.getItemMeta().hasDisplayName()) { 94 | oldDisplay = oldItem.getItemMeta().getDisplayName(); 95 | } 96 | List oldLore = oldItem.getItemMeta().getLore(); 97 | if (oldLore == null) oldLore = new ArrayList<>(); 98 | oldLore.replaceAll((str) -> str.replace(PLAYER_PLACEHOLDER, opener.getName())); 99 | 100 | return b.setItem(LegacyItemBuilder 101 | .legacy(oldItem) 102 | .setDisplay(oldDisplay.replace(PLAYER_PLACEHOLDER, opener.getName())) 103 | .setLore(oldLore) 104 | .build()); 105 | }).orElse(null)); 106 | } 107 | 108 | return modified; 109 | } 110 | 111 | public String titleString() { 112 | return titleString; 113 | } 114 | 115 | public Capacity capacity() { 116 | return capacity; 117 | } 118 | 119 | public Content content() { 120 | return content; 121 | } 122 | 123 | @Override 124 | public boolean equals(Object obj) { 125 | if (obj == this) return true; 126 | if (obj == null || obj.getClass() != this.getClass()) return false; 127 | SerializableMenu that = (SerializableMenu) obj; 128 | return Objects.equals(this.name, that.name) && 129 | Objects.equals(this.titleString, that.titleString) && 130 | Objects.equals(this.capacity, that.capacity) && 131 | Objects.equals(this.content, that.content); 132 | } 133 | 134 | @Override 135 | public int hashCode() { 136 | return Objects.hash(name, titleString, capacity, content); 137 | } 138 | 139 | @Override 140 | public String toString() { 141 | return "SerializableMenu[" + 142 | "name=" + name + ", " + 143 | "titleString=" + titleString + ", " + 144 | "capacity=" + capacity + ", " + 145 | "content=" + content + ']'; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/iterator/Direction.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base.iterator; 2 | 3 | import io.github.mqzen.menus.misc.Slot; 4 | 5 | import java.util.function.Function; 6 | 7 | /** 8 | * Enum representing possible movement directions on a grid. 9 | * Each direction is associated with a function that modifies a given Slot 10 | * (a position in the grid) to step in that direction. 11 | */ 12 | public enum Direction { 13 | /** 14 | * Represents the upward movement direction on a grid. This direction is 15 | * associated with a function that modifies a given {@code Slot} to step upwards. 16 | * The upward movement subtracts 1 from the row of the slot. 17 | */ 18 | UPWARDS(slot -> Slot.of(slot.getRow() - 1, slot.getColumn())) { 19 | @Override 20 | boolean isOutsideBoundarySlot(Slot slot, Slot boundary) { 21 | return slot.getSlot() < boundary.getSlot(); 22 | } 23 | }, 24 | 25 | /** 26 | * Represents the downwards direction in a grid. 27 | * The associated function advances a given slot to the slot directly below it. 28 | * This direction is used to move from a higher row to a lower row in the same column. 29 | *

30 | * The `isOutsideBoundarySlot` method checks if the slot is beyond the boundary when moving downwards. 31 | *

32 | * @see Slot 33 | */ 34 | DOWNWARDS(slot -> Slot.of(slot.getRow() + 1, slot.getColumn())) { 35 | @Override 36 | boolean isOutsideBoundarySlot(Slot slot, Slot boundary) { 37 | return slot.getSlot() > boundary.getSlot(); 38 | } 39 | }, 40 | 41 | /** 42 | * Represents the direction of movement to the right on a grid. 43 | * This direction increments the column value of a given slot. 44 | * 45 | * @see Slot#of(int, int) 46 | */ 47 | RIGHT(slot -> Slot.of(slot.getRow(), slot.getColumn() + 1)) { 48 | @Override 49 | boolean isOutsideBoundarySlot(Slot slot, Slot boundary) { 50 | return slot.getSlot() > boundary.getSlot(); 51 | } 52 | }, 53 | 54 | /** 55 | * Moves the Slot one unit to the left by decrementing the column value by 1. 56 | * Overrides the isOutsideBoundarySlot method to check if the Slot's current position 57 | * is outside the boundary by comparing the Slot's value to the boundary Slot's value. 58 | */ 59 | LEFT(slot -> Slot.of(slot.getRow(), slot.getColumn() - 1)) { 60 | @Override 61 | boolean isOutsideBoundarySlot(Slot slot, Slot boundary) { 62 | return slot.getSlot() < boundary.getSlot(); 63 | } 64 | }, 65 | 66 | /** 67 | * Enum constant representing the right-upwards direction on a grid. 68 | * This direction is associated with a function that modifies a given Slot 69 | * to move one step up and one step to the right. 70 | * 71 | */ 72 | RIGHT_UPWARDS(slot -> Slot.of(slot.getRow() - 1, slot.getColumn() + 1)) { 73 | @Override 74 | boolean isOutsideBoundarySlot(Slot slot, Slot boundary) { 75 | return slot.getSlot() < boundary.getSlot(); 76 | } 77 | }, 78 | 79 | /** 80 | * Represents the LEFT_UPWARDS direction in a grid. When applied, 81 | * it modifies a given Slot by decreasing both its row and column values by 1 unit. 82 | * An example of the transformation would be converting a Slot with coordinates (i, j) 83 | * to another Slot with (i-1, j-1). 84 | * Additionally, it provides a method to determine whether a slot is outside the 85 | * boundary in this direction by comparing the slot's position with a boundary slot. 86 | * 87 | */ 88 | LEFT_UPWARDS(slot -> Slot.of(slot.getRow() - 1, slot.getColumn() - 1)) { 89 | @Override 90 | boolean isOutsideBoundarySlot(Slot slot, Slot boundary) { 91 | return slot.getSlot() < boundary.getSlot(); 92 | } 93 | }, 94 | 95 | /** 96 | * Represents the movement direction "right downwards" on a grid. 97 | * When applied, it moves the given Slot one row down and one column to the right. 98 | *

99 | * The movement is encapsulated in a functional mapping from a Slot 100 | * to a new Slot with modified row and column indices. 101 | *

102 | */ 103 | RIGHT_DOWNWARDS(slot -> Slot.of(slot.getRow() + 1, slot.getColumn() + 1)) { 104 | @Override 105 | boolean isOutsideBoundarySlot(Slot slot, Slot boundary) { 106 | return slot.getSlot() > boundary.getSlot(); 107 | } 108 | }, 109 | 110 | /** 111 | * Represents the direction of movement in a grid, specifically moving towards the left and downwards. 112 | * This direction changes the position of a given {@link Slot} by incrementing its row and decrementing its column. 113 | * 114 | */ 115 | LEFT_DOWNWARDS(slot -> Slot.of(slot.getRow() + 1, slot.getColumn() - 1)) { 116 | @Override 117 | boolean isOutsideBoundarySlot(Slot slot, Slot boundary) { 118 | return slot.getSlot() > boundary.getSlot(); 119 | } 120 | }; 121 | 122 | private final Function slotModifier; 123 | 124 | Direction(Function slotModifier) { 125 | this.slotModifier = slotModifier; 126 | } 127 | 128 | Slot modify(Slot input) { 129 | return slotModifier.apply(input); 130 | } 131 | 132 | /** 133 | * Checks if the given slot is outside the boundary slot. 134 | * 135 | * @param slot the slot to check 136 | * @param boundary the boundary against which the slot is checked 137 | * @return true if the slot is outside the boundary, false otherwise 138 | */ 139 | abstract boolean isOutsideBoundarySlot(Slot slot, Slot boundary); 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/button/actions/ButtonClickAction.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.button.actions; 2 | 3 | import io.github.mqzen.menus.base.MenuView; 4 | import io.github.mqzen.menus.misc.DataRegistry; 5 | import org.bukkit.event.inventory.InventoryClickEvent; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * Interface defining actions triggered by button clicks in a menu. 16 | *

17 | * A ButtonClickAction is identified by a distinct tag and executed by providing 18 | * a specific implementation of the {@link ActionExecutor}. 19 | *

20 | * The interface offers utilities to: 21 | * - Check if a given action string is targeted to the current action 22 | * - Retrieve preferable actions based on click events and data registry 23 | * - Extract action-specific data from a given action string 24 | * 25 | * @author Cobeine 26 | * @author Mqzen (modified after Cobeine) 27 | */ 28 | public interface ButtonClickAction { 29 | 30 | /** 31 | * Pattern for capturing content inside parentheses. 32 | * Used to extract action-specific data from a given action string. 33 | */ 34 | Pattern PATTERN = Pattern.compile("\\((.*?)\\)"); 35 | 36 | /** 37 | * Creates a ButtonClickAction with the specified tag and executor. 38 | * 39 | * @param tag the tag identifying the button click action 40 | * @param executor the ActionExecutor responsible for executing the action 41 | * @return a new ButtonClickAction with the specified tag and executor 42 | */ 43 | static ButtonClickAction of(String tag, ActionExecutor executor) { 44 | return new ButtonClickAction() { 45 | @Override 46 | public String tag() { 47 | return tag; 48 | } 49 | 50 | @Override 51 | public void execute(MenuView menu, InventoryClickEvent event) { 52 | executor.execute(menu, event); 53 | } 54 | }; 55 | } 56 | 57 | /** 58 | * Retrieves the tag that identifies this ButtonClickAction. 59 | * 60 | * @return The tag associated with this action. 61 | */ 62 | String tag(); 63 | /** 64 | * Executes an action triggered by a button click in a menu. 65 | * 66 | * @param menu the MenuView instance where the click event occurred 67 | * @param event the InventoryClickEvent instance representing the click event 68 | */ 69 | void execute(MenuView menu, InventoryClickEvent event); 70 | 71 | /** 72 | * Checks if a given action string matches the current action based on its tag. 73 | * 74 | * @param e the action string to be checked 75 | * @return true if the action string starts with the tag of the current action, false otherwise 76 | */ 77 | default boolean isTargetedAction(String e) { 78 | return e.startsWith(tag()); 79 | } 80 | 81 | /** 82 | * Retrieves a list of preferable actions based on the given click event and data registry. 83 | * 84 | * @param menu The inventory click event, which provides context such as the slot clicked. 85 | * @param data The data registry containing potential actions associated with the inventory. 86 | * @return A list of preferable action strings extracted and processed from the registry. 87 | */ 88 | default List getPreferableActions(InventoryClickEvent menu, DataRegistry data) { 89 | 90 | List actions = new ArrayList<>(data.getData("BTN:" + menu.getSlot())); 91 | actions.removeIf(e -> !isTargetedAction(e)); 92 | return actions.stream().map(e-> { 93 | Matcher matcher = PATTERN.matcher(e); 94 | if (!matcher.find()) return null; 95 | return matcher.group(1); 96 | }).collect(Collectors.toList()); 97 | 98 | } 99 | 100 | /** 101 | * Extracts specific data from the given action string based on a predefined pattern. 102 | * 103 | * @param action the action string from which data is to be extracted 104 | * @return the extracted data if the pattern matches, or null otherwise 105 | */ 106 | default @Nullable String getData(String action) { 107 | Matcher matcher = PATTERN.matcher(action); 108 | if (matcher.find()) { 109 | return matcher.group(1); 110 | } 111 | return null; 112 | } 113 | /** 114 | * Creates a ButtonClickAction with an empty tag and the specified ActionExecutor. 115 | * 116 | * @param executor The ActionExecutor to be executed when the button is clicked. 117 | * @return A new ButtonClickAction instance with an empty tag and the provided ActionExecutor. 118 | */ 119 | static ButtonClickAction plain(ActionExecutor executor) { 120 | return ButtonClickAction.of("", executor); 121 | } 122 | 123 | /** 124 | * Represents an executor that defines the action to be performed when a button 125 | * click event occurs within a menu interface. 126 | */ 127 | interface ActionExecutor { 128 | /** 129 | * Executes an action associated with a button click event within a menu interface. 130 | * 131 | * @param menu the menu interface where the button click event occurred 132 | * @param event the event representing the button click 133 | */ 134 | void execute(MenuView menu, InventoryClickEvent event); 135 | } 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/Reflections.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.Field; 7 | 8 | final class Reflections { 9 | 10 | /** 11 | * Retrieve a field accessor for a specific field type and name. 12 | * 13 | * @param target - the targetToLoad type. 14 | * @param name - the name of the field, or NULL to ignore. 15 | * @param fieldType - a compatible field type. 16 | * @return The field accessor. 17 | */ 18 | public static FieldAccessor getField(Class target, String name, Class fieldType) { 19 | return getField(target, name, fieldType, 0); 20 | } 21 | 22 | /** 23 | * Retrieve a field accessor for a specific field type and name. 24 | * 25 | * @param className - lookup name of the class, see {@link #getClass(String)}. 26 | * @param name - the name of the field, or NULL to ignore. 27 | * @param fieldType - a compatible field type. 28 | * @return The field accessor. 29 | */ 30 | public static FieldAccessor getField(String className, String name, Class fieldType) { 31 | return getField(getClass(className), name, fieldType, 0); 32 | } 33 | 34 | /** 35 | * Retrieve a field accessor for a specific field type and name. 36 | * 37 | * @param target - the targetToLoad type. 38 | * @param fieldType - a compatible field type. 39 | * @return The field accessor. 40 | */ 41 | public static FieldAccessor getField(Class target, Class fieldType) { 42 | return getField(target, null, fieldType, 0); 43 | } 44 | 45 | /** 46 | * Retrieve a field accessor for a specific field type and name. 47 | * 48 | * @param target - the targetToLoad type. 49 | * @param fieldType - a compatible field type. 50 | * @param index - the number of compatible fields to skip. 51 | * @return The field accessor. 52 | */ 53 | public static FieldAccessor getField(Class target, Class fieldType, int index) { 54 | return getField(target, null, fieldType, index); 55 | } 56 | 57 | /** 58 | * Retrieve a field accessor for a specific field type and name. 59 | * 60 | * @param className - lookup name of the class, see {@link #getClass(String)}. 61 | * @param fieldType - a compatible field type. 62 | * @param index - the number of compatible fields to skip. 63 | * @return The field accessor. 64 | */ 65 | public static FieldAccessor getField(String className, Class fieldType, int index) { 66 | return getField(getClass(className), fieldType, index); 67 | } 68 | 69 | private static FieldAccessor getField( 70 | final Class target, 71 | final String name, 72 | final Class fieldType, 73 | int index 74 | ) { 75 | if (target == null) { 76 | throw new IllegalArgumentException("Target class is null"); 77 | } 78 | for (final Field field : target.getDeclaredFields()) { 79 | if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) { 80 | field.setAccessible(true); 81 | 82 | // A function for retrieving a specific field value 83 | return new FieldAccessor() { 84 | 85 | @Override 86 | @SuppressWarnings("unchecked") 87 | public T get(final Object target) { 88 | try { 89 | return (T) field.get(target); 90 | } catch (IllegalAccessException e) { 91 | throw new RuntimeException("Cannot access reflection.", e); 92 | } 93 | } 94 | 95 | @Override 96 | public void set(final Object target, final Object value) { 97 | try { 98 | field.set(target, value); 99 | } catch (IllegalAccessException e) { 100 | throw new RuntimeException("Cannot access reflection.", e); 101 | } 102 | } 103 | 104 | @Override 105 | public boolean hasField(final Object target) { 106 | // targetToLoad instanceof DeclaringClass 107 | return field.getDeclaringClass().isAssignableFrom(target.getClass()); 108 | } 109 | }; 110 | } 111 | } 112 | 113 | // Search in parent classes 114 | if (target.getSuperclass() != null) 115 | return getField(target.getSuperclass(), name, fieldType, index); 116 | 117 | throw new IllegalArgumentException("Cannot find field with type " + fieldType); 118 | } 119 | 120 | /** 121 | * Finds any {@link Class} of the provided paths 122 | * 123 | * @param paths all possible class paths 124 | * @return false if the {@link Class} was NOT found 125 | */ 126 | public static boolean findClass(final String... paths) { 127 | for (final String path : paths) { 128 | if (getClass(path) != null) return true; 129 | } 130 | return false; 131 | } 132 | 133 | /** 134 | * A nullable {@link Class#forName(String)} instead of throwing exceptions 135 | * 136 | * @return null if the {@link Class} was NOT found 137 | */ 138 | public static Class getClass(@NotNull final String path) { 139 | try { 140 | return Class.forName(path); 141 | } catch (final Exception ignored) { 142 | return null; 143 | } 144 | } 145 | 146 | /** 147 | * A nullable {@link Class#getDeclaredConstructor(Class[])} instead of throwing exceptions 148 | * 149 | * @return null if the {@link Constructor} was NOT found 150 | */ 151 | public static Constructor getConstructor(@NotNull final Class clazz, final Class... classes) { 152 | try { 153 | return clazz.getDeclaredConstructor(classes); 154 | } catch (final Exception ignored) { 155 | return null; 156 | } 157 | } 158 | 159 | } -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/itembuilder/ItemBuilder.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.itembuilder; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.enchantments.Enchantment; 5 | import org.bukkit.inventory.ItemFlag; 6 | import org.bukkit.inventory.ItemStack; 7 | import org.bukkit.inventory.meta.ItemMeta; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.function.Consumer; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * An abstract builder class for creating and modifying ItemStack objects with a fluent API. 16 | * 17 | * @param the type of text object used for display names and lore 18 | * @param the type of the builder itself, used for fluent method chaining 19 | */ 20 | @SuppressWarnings("unchecked") 21 | public abstract class ItemBuilder> { 22 | 23 | private final ItemStack itemStack; 24 | protected ItemMeta itemMeta; 25 | 26 | protected ItemBuilder(ItemStack itemStack) { 27 | this.itemStack = itemStack; 28 | this.itemMeta = itemStack.getItemMeta(); 29 | } 30 | 31 | protected ItemBuilder(Material material, int amount, short data) { 32 | if(amount < 0 || amount > 64) { 33 | throw new IllegalArgumentException("Invalid ItemStack amount '" + amount + "'"); 34 | } 35 | this.itemStack = new ItemStack(material, amount, data); 36 | this.itemMeta = itemStack.getItemMeta(); 37 | } 38 | 39 | protected ItemBuilder(Material material, int amount) { 40 | this(material, amount, (short) 0); 41 | } 42 | 43 | 44 | protected ItemBuilder(Material material) { 45 | this(material, 1); 46 | } 47 | 48 | /** 49 | * Converts the given text of generic type T to its string representation. 50 | * 51 | * @param textType The text of generic type T that needs to be converted to a string. 52 | * @return The string representation of the given text. 53 | */ 54 | protected abstract String toString(T textType); 55 | 56 | /** 57 | * Sets the display name for the item represented by this builder. 58 | * 59 | * @param display The display name to be set, represented by a generic type T. 60 | * @return The updated builder instance for chaining method calls. 61 | */ 62 | public B setDisplay(T display) { 63 | itemMeta.setDisplayName(toString(display)); 64 | return (B) this; 65 | } 66 | 67 | /** 68 | * Sets the lore of the item. 69 | * 70 | * @param lore the array of lore entries to set, where each entry is converted 71 | * to a string using the {@link #toString(Object)} method. 72 | * @return the current instance of the builder for method chaining. 73 | */ 74 | public B setLore(T... lore) { 75 | itemMeta.setLore(Arrays.stream(lore).map(this::toString).collect(Collectors.toList())); 76 | return (B) this; 77 | } 78 | 79 | /** 80 | * Sets the lore (description) of the item with the given list of lore entries. 81 | * 82 | * @param lore A list of lore entries to set for the item, where each entry represents a line in the lore. 83 | * @return The current instance of the item builder for method chaining. 84 | */ 85 | public B setLore(List lore) { 86 | itemMeta.setLore(lore.stream().map(this::toString).collect(Collectors.toList())); 87 | return (B) this; 88 | } 89 | 90 | /** 91 | * Modifies the metadata of the item based on the provided meta class and consumer. 92 | * 93 | * @param the type of the item metadata 94 | * @param metaClass the class of the item metadata to modify 95 | * @param metaConsumer the consumer that defines how the metadata should be modified 96 | * @return the updated ItemBuilder instance 97 | */ 98 | public B modifyMeta(Class metaClass, Consumer metaConsumer) { 99 | try { 100 | M meta = metaClass.cast(itemMeta); 101 | metaConsumer.accept(meta); 102 | this.itemMeta = meta; 103 | }catch (ClassCastException ignored) {} 104 | return (B) this; 105 | } 106 | 107 | /** 108 | * Adds one or more {@link ItemFlag} to the item. 109 | * 110 | * @param flags the item flags to be added 111 | * @return this {@link ItemBuilder} with the added item flags 112 | */ 113 | public B addFlags(ItemFlag... flags) { 114 | this.itemMeta.addItemFlags(flags); 115 | return (B) this; 116 | } 117 | 118 | /** 119 | * Adds an enchantment to the item with the specified level. 120 | * 121 | * @param enchantment The enchantment to add. 122 | * @param level The level of the enchantment. 123 | * @return The current ItemBuilder instance with the added enchantment. 124 | */ 125 | public B enchant(Enchantment enchantment, int level) { 126 | this.itemMeta.addEnchant(enchantment, level, true); 127 | return (B) this; 128 | } 129 | 130 | /** 131 | * Removes a specified enchantment from the item. 132 | * 133 | * @param enchantment the enchantment to remove from the item 134 | * @return the current instance of {@code ItemBuilder} for chaining 135 | */ 136 | public B unEnchant(Enchantment enchantment) { 137 | this.itemMeta.removeEnchant(enchantment); 138 | return (B) this; 139 | } 140 | 141 | /** 142 | * Finalizes the construction of the ItemStack by applying the itemMeta. 143 | * 144 | * @return The constructed ItemStack with the applied meta. 145 | */ 146 | public final ItemStack build() { 147 | this.itemStack.setItemMeta(itemMeta); 148 | return itemStack; 149 | } 150 | 151 | public static LegacyItemBuilder legacy(ItemStack itemStack) { 152 | return new LegacyItemBuilder(itemStack); 153 | } 154 | 155 | public static LegacyItemBuilder legacy(Material material, int amount, short data) { 156 | return new LegacyItemBuilder(material, amount, data); 157 | } 158 | 159 | public static LegacyItemBuilder legacy(Material material, int amount) { 160 | return new LegacyItemBuilder(material, amount); 161 | } 162 | 163 | public static LegacyItemBuilder legacy(Material material) { 164 | return new LegacyItemBuilder(material); 165 | } 166 | 167 | public static ComponentItemBuilder modern(ItemStack itemStack) { 168 | return new ComponentItemBuilder(itemStack); 169 | } 170 | 171 | public static ComponentItemBuilder modern(Material material, int amount, short data) { 172 | return new ComponentItemBuilder(material, amount, data); 173 | } 174 | 175 | public static ComponentItemBuilder modern(Material material, int amount) { 176 | return new ComponentItemBuilder(material, amount); 177 | } 178 | 179 | public static ComponentItemBuilder modern(Material material) { 180 | return new ComponentItemBuilder(material); 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/misc/button/Button.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.misc.button; 2 | 3 | import io.github.mqzen.menus.base.MenuView; 4 | import io.github.mqzen.menus.misc.DataRegistry; 5 | import io.github.mqzen.menus.misc.button.actions.ButtonClickAction; 6 | import lombok.Getter; 7 | import org.bukkit.event.inventory.InventoryClickEvent; 8 | import org.bukkit.inventory.ItemStack; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.util.function.BiFunction; 13 | 14 | /** 15 | * Represents a button that can be displayed in a menu view. A button can have an item associated with it and 16 | * can perform an action when clicked. 17 | */ 18 | @Getter 19 | public class Button { 20 | 21 | private @Nullable ItemStack item; 22 | private @Nullable ButtonClickAction action = null; 23 | 24 | protected DataRegistry data = DataRegistry.empty(); 25 | 26 | protected Button(@Nullable ItemStack item) { 27 | this.item = item; 28 | } 29 | 30 | //create constructor for each possible combination of field 31 | protected Button(@Nullable ItemStack item, @Nullable ButtonClickAction action) { 32 | this(item); 33 | this.action = action; 34 | } 35 | 36 | protected Button(@Nullable ItemStack item, @Nullable ButtonClickAction action, @NotNull DataRegistry data) { 37 | this(item, action); 38 | this.data = data; 39 | } 40 | 41 | /** 42 | * Creates a button with the provided item and no action. 43 | * 44 | * @param item the ItemStack associated with the button 45 | * @return a new Button instance with the specified item 46 | */ 47 | public static Button empty(ItemStack item) { 48 | return new Button(item); 49 | } 50 | 51 | /** 52 | * Creates a clickable Button with the provided ItemStack and a ButtonClickAction. 53 | * This method facilitates the creation of a Button that performs a specific action when clicked in a menu view. 54 | * 55 | * @param item The ItemStack to be associated with the Button. It represents the visual representation of the Button in the menu. 56 | * @param action The ButtonClickAction to be executed when the Button is clicked. It can be null if no action is required. 57 | * @return A new Button configured with the specified ItemStack and ButtonClickAction. 58 | */ 59 | public static Button clickable(ItemStack item, @Nullable ButtonClickAction action) { 60 | return new Button(item, action); 61 | } 62 | 63 | /** 64 | * Creates a new transformer button. 65 | * The transformer button changes its behavior or properties based on user interactions. 66 | * 67 | * @param item the initial ItemStack represented by this button 68 | * @param transformer a BiFunction that takes in a MenuView and an InventoryClickEvent, and returns a new Button 69 | * @return the newly created transformer button 70 | */ 71 | public static Button transformerButton(ItemStack item, BiFunction, InventoryClickEvent, Button> transformer) { 72 | return new Button(item, ButtonClickAction.plain((menu, click) -> menu.replaceClickedButton(click, transformer.apply(menu, click)))); 73 | } 74 | 75 | 76 | /** 77 | * Creates a new Button instance with a specified item and a transformer function. The transformer function 78 | * allows dynamic modification of the ItemStack when the button is clicked. 79 | * 80 | * @param item the ItemStack associated with the Button. 81 | * @param transformer a BiFunction that takes a MenuView and an InventoryClickEvent and returns an 82 | * ItemStack. This function defines the transformation to be applied to the ItemStack 83 | * when the button is clicked. 84 | * @return a new Button instance configured with the specified item and transformer function. 85 | */ 86 | public static Button transformerItem(ItemStack item, 87 | BiFunction, InventoryClickEvent, ItemStack> transformer) { 88 | return new Button(item, ButtonClickAction.plain((view, click) -> 89 | view.replaceClickedItemStack(click, transformer.apply(view, click), false))); 90 | } 91 | 92 | /** 93 | * Determines if the button is clickable by checking if an action is associated with it. 94 | * 95 | * @return {@code true} if the button has an associated action and can be clicked, 96 | * {@code false} otherwise. 97 | */ 98 | public boolean isClickable() { 99 | return action != null; 100 | } 101 | 102 | 103 | /** 104 | * Sets the action to be performed when the button is clicked. 105 | * 106 | * @param action the action to be performed on button click; can be {@code null} if no action is needed 107 | * @return the current instance of the Button for method chaining 108 | */ 109 | public Button setAction(@Nullable ButtonClickAction action) { 110 | this.action = action; 111 | return this; 112 | } 113 | 114 | /** 115 | * Sets the item associated with the button. 116 | * 117 | * @param item the {@link ItemStack} to set; can be null to unset the item 118 | * @return the current instance of {@link Button} for chaining 119 | */ 120 | public Button setItem(@Nullable ItemStack item) { 121 | this.item = item; 122 | return this; 123 | } 124 | 125 | /** 126 | * Executes the click action associated with this button, if any. 127 | * 128 | * @param menu the menu view in which the button resides. 129 | * @param event the inventory click event triggered when the button is clicked. 130 | */ 131 | public void executeOnClick(MenuView menu, InventoryClickEvent event) { 132 | //if (action != null) action.execute(menu, event); 133 | if (action != null) action.execute(menu, event); 134 | } 135 | 136 | /** 137 | * Creates a copy of the current Button instance. The copied Button retains the same item and action as the original. 138 | * 139 | * @return A new Button instance with identical item and action properties as this Button. 140 | */ 141 | public Button copy() { 142 | return new Button(this.item, this.action, this.data); 143 | } 144 | 145 | /** 146 | * Sets a named piece of data associated with this button. 147 | * 148 | * @param name the name of the data to be set 149 | * @param data the data to associate with the given name 150 | * @return the current instance of the Button, allowing for chaining of method calls 151 | */ 152 | public Button setNamedData(String name, Object data) { 153 | this.data.setData(name, data); 154 | return this; 155 | } 156 | 157 | /** 158 | * Retrieves the data associated with the specified name. 159 | * 160 | * @param The type of the data to be returned. 161 | * @param name The name of the data to retrieve. 162 | * @return The data associated with the given name, or null if no data is found. 163 | */ 164 | public T getNamedData(String name) { 165 | return data.getData(name); 166 | } 167 | 168 | /** 169 | * Retrieves the data registry associated with this button. 170 | * 171 | * @return the data registry containing data tied to this button 172 | */ 173 | public DataRegistry getDataRegistry() { 174 | return data; 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/io/github/mqzen/menus/base/MenuContentImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.mqzen.menus.base; 2 | 3 | import com.google.common.collect.Lists; 4 | import io.github.mqzen.menus.misc.Capacity; 5 | import io.github.mqzen.menus.misc.Slot; 6 | import io.github.mqzen.menus.misc.Slots; 7 | import io.github.mqzen.menus.misc.button.Button; 8 | import io.github.mqzen.menus.misc.button.ButtonCondition; 9 | import org.bukkit.inventory.ItemStack; 10 | import org.jetbrains.annotations.NotNull; 11 | import java.util.*; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | import java.util.function.BiConsumer; 14 | import java.util.function.Consumer; 15 | import java.util.stream.Stream; 16 | 17 | final class MenuContentImpl implements Content { 18 | 19 | private final Capacity capacity; 20 | private final ConcurrentHashMap map = new ConcurrentHashMap<>(); 21 | 22 | public MenuContentImpl(Capacity capacity) { 23 | this.capacity = capacity; 24 | } 25 | 26 | @Override 27 | public Capacity capacity() { 28 | return capacity; 29 | } 30 | 31 | @Override 32 | public Optional