-
Notifications
You must be signed in to change notification settings - Fork 2
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
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
We already saw how to create a Menu on the Getting Started page, go check it out!
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
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
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).
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!
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.
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
getStaticSlotsmethod and just give your slots as a parameter!
- It has all the utilities of the Menu
Now that you can create perfectly your menus, go check out the ItemBuilder if it's not already done
Have fun using MenuLib!