package com.github.stefvanschie.inventoryframework.pane; import com.github.stefvanschie.inventoryframework.Gui; import com.github.stefvanschie.inventoryframework.GuiItem; import com.github.stefvanschie.inventoryframework.exception.XMLLoadException; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.*; import java.util.stream.Collectors; /** * A pane for panes that should be spread out over multiple pages */ public class PaginatedPane extends Pane { /** * A set of panes for the different pages */ @NotNull private final Map<Integer, List<Pane>> panes = new HashMap<>(); /** * The current page */ private int page; public PaginatedPane(int x, int y, int length, int height, @NotNull Priority priority) { super(x, y, length, height, priority); } public PaginatedPane(int x, int y, int length, int height) { super(x, y, length, height); } public PaginatedPane(int length, int height) { super(length, height); } /** * Returns the current page * * @return the current page */ public int getPage() { return page; } /** * Returns the amount of pages * * @return the amount of pages */ public int getPages() { return panes.size(); } /** * Assigns a pane to a selected page * * @param page the page to assign the pane to * @param pane the new pane */ public void addPane(int page, @NotNull Pane pane) { if (!this.panes.containsKey(page)) this.panes.put(page, new ArrayList<>()); this.panes.get(page).add(pane); this.panes.get(page).sort(Comparator.comparing(Pane::getPriority)); } /** * Sets the current displayed page * * @param page the page */ public void setPage(int page) { if (!panes.containsKey(page)) throw new ArrayIndexOutOfBoundsException("page outside range"); this.page = page; } /** * Populates the PaginatedPane based on the provided list by adding new pages until all items can fit. * This can be helpful when dealing with lists of unknown size. * * @param items The list to populate the pane with */ @Contract("null -> fail") public void populateWithItemStacks(@NotNull List<ItemStack> items) { //Don't do anything if the list is empty if (items.isEmpty()) { return; } int itemsPerPage = this.height * this.length; int pagesNeeded = (int) Math.max(Math.ceil(items.size() / (double) itemsPerPage), 1); for (int i = 0; i < pagesNeeded; i++) { OutlinePane page = new OutlinePane(0, 0, this.length, this.height); for (int j = 0; j < itemsPerPage; j++) { //Check if the loop reached the end of the list int index = i * itemsPerPage + j; if (index >= items.size()) { break; } page.addItem(new GuiItem(items.get(index))); } this.addPane(i, page); } } /** * Populates the PaginatedPane based on the provided list by adding new pages until all items can fit. * This can be helpful when dealing with lists of unknown size. * * @param items The list to populate the pane with */ @Contract("null -> fail") public void populateWithGuiItems(@NotNull List<GuiItem> items) { //Don't do anything if the list is empty if (items.isEmpty()) { return; } int itemsPerPage = this.height * this.length; int pagesNeeded = (int) Math.max(Math.ceil(items.size() / (double) itemsPerPage), 1); for (int i = 0; i < pagesNeeded; i++) { OutlinePane page = new OutlinePane(0, 0, this.length, this.height); for (int j = 0; j < itemsPerPage; j++) { int index = i * itemsPerPage + j; //Check if the loop reached the end of the list if (index >= items.size()) { break; } page.addItem(items.get(index)); } this.addPane(i, page); } } /** * This method creates a list of ItemStacks all with the given {@code material} and the display names. * After that it calls {@link #populateWithItemStacks(List)} * This method also translates the color char {@code &} for all names. * * @param displayNames The display names for all the items * @param material The material to use for the {@link org.bukkit.inventory.ItemStack}s */ @Contract("null, _ -> fail") public void populateWithNames(@NotNull List<String> displayNames, @Nullable Material material) { if(material == null || material == Material.AIR) return; populateWithItemStacks(displayNames.stream().map(name -> { ItemStack itemStack = new ItemStack(material); ItemMeta itemMeta = itemStack.getItemMeta(); itemMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name)); itemStack.setItemMeta(itemMeta); return itemStack; }).collect(Collectors.toList())); } @Override public void display(@NotNull Gui gui, @NotNull Inventory inventory, @NotNull PlayerInventory playerInventory, int paneOffsetX, int paneOffsetY, int maxLength, int maxHeight) { List<Pane> panes = this.panes.get(page); if (panes == null) { return; } panes.forEach(pane -> pane.display(gui, inventory, playerInventory, paneOffsetX + getX(), paneOffsetY + getY(), Math.min(length, maxLength), Math.min(height, maxHeight))); } @Override public boolean click(@NotNull Gui gui, @NotNull InventoryClickEvent event, int paneOffsetX, int paneOffsetY, int maxLength, int maxHeight) { int length = Math.min(this.length, maxLength); int height = Math.min(this.height, maxHeight); int slot = event.getSlot(); InventoryView view = event.getView(); Inventory inventory = view.getInventory(event.getRawSlot()); int x, y; if (inventory != null && inventory.equals(view.getBottomInventory())) { x = (slot % 9) - getX() - paneOffsetX; y = ((slot / 9) + gui.getRows() - 1) - getY() - paneOffsetY; if (slot / 9 == 0) { y = (gui.getRows() + 3) - getY() - paneOffsetY; } } else { x = (slot % 9) - getX() - paneOffsetX; y = (slot / 9) - getY() - paneOffsetY; } //this isn't our item if (x < 0 || x >= length || y < 0 || y >= height) { return false; } callOnClick(event); List<Pane> panes = this.panes.get(page); if (panes == null) { return false; } boolean success = false; for (Pane pane : panes) { success = success || pane.click(gui, event, paneOffsetX + getX(), paneOffsetY + getY(), length, height); } return success; } @NotNull @Contract(pure = true) @Override public Collection<Pane> getPanes() { Collection<Pane> panes = new HashSet<>(); this.panes.forEach((integer, p) -> { p.forEach(pane -> panes.addAll(pane.getPanes())); panes.addAll(p); }); return panes; } /** * Gets all the panes from inside the specified page of this pane. If the specified page is not existent, this * method will throw an {@link IllegalArgumentException}. If the specified page is existent, but doesn't * have any panes, the returned collection will be empty. The returned collection is unmodifiable. The returned * collection is not synchronized and no guarantees should be made as to the safety of concurrently accessing the * returned collection. If synchronized behaviour should be allowed, the returned collection must be synchronized * externally. * * @param page the panes of this page will be returned * @return a collection of panes belonging to the specified page * @since 0.5.13 * @throws IllegalArgumentException if the page does not exist */ @NotNull @Contract(pure = true) public Collection<Pane> getPanes(int page) { Collection<Pane> panes = this.panes.get(page); if (panes == null) { throw new IllegalArgumentException("Invalid page"); } return Collections.unmodifiableCollection(panes); } @NotNull @Contract(pure = true) @Override public Collection<GuiItem> getItems() { return getPanes().stream().flatMap(pane -> pane.getItems().stream()).collect(Collectors.toList()); } @Override public void clear() { panes.clear(); } /** * Loads a paginated pane from a given element * * @param instance the instance class * @param element the element * @return the paginated pane */ @NotNull public static PaginatedPane load(@NotNull Object instance, @NotNull Element element) { try { PaginatedPane paginatedPane = new PaginatedPane( Integer.parseInt(element.getAttribute("length")), Integer.parseInt(element.getAttribute("height")) ); Pane.load(paginatedPane, instance, element); if (element.hasAttribute("populate")) return paginatedPane; int pageCount = 0; NodeList childNodes = element.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node item = childNodes.item(i); if (item.getNodeType() != Node.ELEMENT_NODE) continue; NodeList innerNodes = item.getChildNodes(); for (int j = 0; j < innerNodes.getLength(); j++) { Node pane = innerNodes.item(j); if (pane.getNodeType() != Node.ELEMENT_NODE) { continue; } paginatedPane.addPane(pageCount, Gui.loadPane(instance, pane)); } pageCount++; } return paginatedPane; } catch (NumberFormatException exception) { throw new XMLLoadException(exception); } } }