Skip to content
Xernas Dev edited this page Dec 30, 2023 · 7 revisions

Menus

So this library is for creating menus right? So here we are! We are going to create menus

Menus are GUIs based on the Bukkit inventory system, there are two types of them, the simple Menu and the Paginated Menu

  • If you want to make a simple one-page menu, for example, a game selection menu, use the simple Menu
  • If you want to make a menu with a varying number of pages, for example, a player selection menu, use the Paginated Menu

Menu

You can use this Menu for multiple things like a /sell GUI, a staff-only GUI with the permission system, and many other things. This type of Menu is a simple menu with one page, you can put a maximum of 54 items inside

Create a Menu

We already saw how to create a Menu on the Getting Started page, go check it out!

How does it work?

The Menu is an abstract class with multiple methods that other classes can override, here is the Menu class:

public abstract class Menu implements InventoryHolder {

    private final Player owner; // Getting the owner is important

    public Menu(Player owner) {
        this.owner = owner;
    }

    @NotNull
    public abstract String getName();
    @NotNull
    public abstract InventorySize getInventorySize();

    public String getPermission() { // You can override permission methods, it's just not required.
        return null;
    }
    public String getNoPermissionMessage() {
        return "";
    }

    public abstract void onInventoryClick(InventoryClickEvent e); // This event is fired when we click on the menu

    @NotNull
    public abstract Map<Integer, ItemStack> getContent(); // This method gets the entire content of the menu in a map

    public final void open() { // Just opens the inventory
        if (getPermission() != null && !getPermission().isEmpty()) {
            if (!owner.hasPermission(getPermission())) {
                owner.sendMessage(getNoPermissionMessage());
                return;
            }
        }
        Inventory inventory = getInventory();
        getContent().forEach(inventory::setItem); // Fills the inventory with the content of the getContent() method
        owner.openInventory(inventory);
    }

    public final Map<Integer, ItemStack> fill(Material material) { // Util (Can't override)
        Map<Integer, ItemStack> map = new HashMap<>();
        for (int i = 0; i < getInventorySize().getSize(); i++) {
            ItemStack filler = ItemUtils.createItem(" ", material);
            map.put(i, filler);
        }
        return map;
    }

    public final boolean isItem(ItemStack item, String itemId) { // Util
        PersistentDataContainer dataContainer = Objects.requireNonNull(item.getItemMeta()).getPersistentDataContainer();
        if (dataContainer.has(MenuLib.getItemIdKey(), PersistentDataType.STRING)) {
            return Objects.equals(dataContainer.get(MenuLib.getItemIdKey(), PersistentDataType.STRING), itemId);
        }
        return false;
    }

    public final void back() { // Util
        Menu lastMenu = MenuLib.getLastMenu(owner);
        lastMenu.open();
    }

    @NotNull
    @Override
    public final Inventory getInventory() { // I advise you not to call this method, you don't need it
        return Bukkit.createInventory(this, getInventorySize().getSize(), getName());
    }

    public final Player getOwner() {
        return owner;
    }
}

To call the onInventoryClick method, I used a small trick, I implemented InventoryHolder in the class so it was considered as an inventory holder by bukkit, so in the main class of the lib (MenuLib), I could listen for the event and call it:

@EventHandler
public void onInventoryClick(InventoryClickEvent e) {
    if (e.getInventory().getHolder() instanceof Menu menu) {
        e.setCancelled(true);
        if (e.getCurrentItem() == null) {
            return;
        }

        menu.onInventoryClick(e);
        // Some other things we can see later
    }
}

Note: If you want the items in the menu to be pickable, just do e.setCancelled(false) in the onInventoryClick method

For a more detailed idea of how it works, I'd recommend just looking at the code

Utilities

As we've seen earlier, there are a lot of util methods, so we are going to take a look at them, we are also going to take a look at util classes like ItemUtils or InventorySize tho we are going to save the ItemBuilder for later.

Menu class:

fill(material)

The fill method is used to fill an inventory with a certain filler material, it is useful for almost all GUIs just to fill space.

isItem(item, itemId)

The isItem method is used to verify if a certain item uses this id, this id can be set using the ItemBuilder.

back()

The back method is used to get back to the previous menu you were on, it can be used in a lot of cases.

ItemUtils class

I'm going to add other things to this class in future updates.

createItem(String name, Material material)

Can create a simple ItemStack with a name, used for little menu items that aren't important, for more important items, use the ItemBuilder class.

isItem(item, itemId)

It's the same as the Menu class one, I'm probably going to replace it.

InventorySizes

In this class, there are all the possible inventory sizes for the menus, so here they are:

  • SMALLEST: 9
  • SMALL: 18
  • NORMAL: 27
  • LARGE: 36
  • LARGER: 45
  • LARGEST: 54

Paginated Menu

You can use a Paginated Menu for heavier menus like a player selection menu or an auction house menu, the only limit is your imagination. You can put A LOT of items in a Paginated Menu, I have tested it for 1k+ items (36 pages).

Create a Paginated Menu

To create one, it is as simple as a normal menu, you have to create a class that extends PaginatedMenu like this:

public class PaginatedWikiMenu extends PaginatedMenu {

    public PaginatedWikiMenu(Player owner) {
        super(owner);
    }

    @Override
    public @Nullable Material getBorderMaterial() {
        return Material.GRAY_STAINED_GLASS_PANE;
    }

    @Override
    public @NotNull List<Integer> getStaticSlots() {
        return StaticSlots.BOTTOM;
    }

    @Override
    public @NotNull List<ItemStack> getItems() {
        List<ItemStack> items = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
           items.add(ItemUtils.createItem("Item " + i, Material.IRON_INGOT));
        }
        return items;
    }

    @Override
    public Map<Integer, ItemStack> getButtons() {
        Map<Integer, ItemStack> map = new HashMap<>();
        map.put(49, new ItemBuilder(this, Material.BARRIER, itemMeta -> itemMeta.setDisplayName(ChatColor.GRAY + "Close"))
                .setCloseButton());
        map.put(48, new ItemBuilder(this, Material.RED_CONCRETE, itemMeta -> itemMeta.setDisplayName(ChatColor.RED + "Previous"))
                .setPreviousPageButton());
        map.put(50, new ItemBuilder(this, Material.GREEN_CONCRETE, itemMeta -> itemMeta.setDisplayName(ChatColor.GREEN + "Next"))
                .setNextPageButton());
        return map;
    }

    @Override
    public @NotNull String getName() {
        return ChatColor.GREEN + "Hey Wiki ! (page " + getPage() + ")";
    }

    @Override
    public void onInventoryClick(InventoryClickEvent inventoryClickEvent) {

    }
}

The most important methods are getStaticSlots() getItems() and getButtons().

  • Static slots are slots that don't move, they are going to be the same regardless of the page, so you can choose those slots using different presets or with your slots if you want.
  • The items are all the items that will go in the menu, so for a player selection menu, it's gonna be the player heads, and for an auction house menu, it's gonna be the items that people are selling.
  • The buttons are the different useful buttons that we find on each page, so of course the buttons must be on a static slot.

The getBorderMaterial() method sets the material of static slots but of course, the buttons override them.

Have fun!

How does it work?

The PaginatedMenu class extends Menu and is also an abstract class:

public abstract class PaginatedMenu extends Menu {
    private int page = 0;
    private int numberOfPages;

    public PaginatedMenu(Player owner) {
        super(owner);
    }

    @Nullable
    public abstract Material getBorderMaterial();

    @NotNull
    public abstract List<Integer> getStaticSlots();

    @NotNull
    public abstract List<ItemStack> getItems();

    public abstract Map<Integer, ItemStack> getButtons();

    @Override
    @NotNull
    public final Map<Integer, ItemStack> getContent() {
        Map<Integer, ItemStack> map = new HashMap<>();
        for (Integer staticSlot : getStaticSlots()) {
            map.put(staticSlot, ItemUtils.createItem(" ", getBorderMaterial() == null ? Material.AIR : getBorderMaterial()));
        }
        List<Integer> staticSlots = removeRecurringIntegers(getStaticSlots());
        int maxItems = getInventorySize().getSize() - staticSlots.size();
        numberOfPages = (int) Math.ceil((double) getItems().size() / maxItems) - 1;

        // Pagination
        int index = 0;
        for (int i = 0; i < getInventory().getSize(); i++) {
            if (!staticSlots.contains(i)) {
                if (index + maxItems * page < getItems().size()) {
                    map.put(i, getItems().get(index + maxItems * page));
                    index++;
                }
            }
        }
        // Pagination

        if (getButtons() != null) {
            getButtons().forEach((integer, itemStack) -> {
                if (staticSlots.contains(integer)) {
                    map.put(integer, itemStack);
                }
            });
        }
        return map;
    }

    @Override
    public final @NotNull InventorySize getInventorySize() {
        return InventorySize.LARGEST;
    }
    public final void setPage(int page) {
        this.page = page;
    }
    public final int getPage() {
        return page;
    }

    public final boolean isLastPage() {
        return page == numberOfPages;
    }
}

Here it is, all the page stuff is taken care of inside of the getContent() method.

Utilities

The main utility class used here is the StaticSlots class along with ItemBuilder

Static Slots Static Slots are slots that stay the same regardless of the page, they are stored in a list of integers, and the integers are the id of the slots. I made some presets of static slots, they are all in the StaticSlots class, here are some examples of them:

  • STANDARD: All border slots
  • BOTTOM/TOP/LEFT/RIGHT: Only the bottom/top/left/right line of slots
  • MIDDLE_BUTTONS: Only slots with ID 48, 49, 50

And what happens if I don't like any of the presets?

  • If you don't like the presets, you can merge multiple presets or your list of slots using the combine(list, list2) method.
  • If you don't like any of the lists and want to make your own, use the getStaticSlots method and just give your slots as a parameter!
  • It has all the utilities of the Menu

More ?

Now that you can create perfectly your menus, go check out the ItemBuilder if it's not already done

Have fun using MenuLib!

Clone this wiki locally