Skip to content

Commit

Permalink
Added search functionality for all paginated GUIs
Browse files Browse the repository at this point in the history
Bumped version
  • Loading branch information
hopperelec committed Aug 9, 2024
1 parent 0bba448 commit 956db27
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 27 deletions.
24 changes: 23 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>uk.co.hopperelec.mc</groupId>
<artifactId>itemrace</artifactId>
<version>1.3.0</version>
<version>1.4.0</version>
<packaging>jar</packaging>

<name>ItemRace</name>
Expand Down Expand Up @@ -53,7 +53,20 @@
<pattern>co.aikar.locales</pattern>
<shadedPattern>uk.co.hopperelec.mc.itemrace.locales</shadedPattern>
</relocation>
<relocation>
<pattern>net.wesjd.anvilgui</pattern>
<shadedPattern>uk.co.hopperelec.mc.anvilgui</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>*:*</artifact>
<excludeDefaults>false</excludeDefaults>
<includes>
<include>net/wesjd/anvilgui/version/Wrapper1_21_R1</include>
</includes>
</filter>
</filters>
</configuration>
<executions>
<execution>
Expand Down Expand Up @@ -82,6 +95,10 @@
<id>aikar</id>
<url>https://repo.aikar.co/content/groups/aikar/</url>
</repository>
<repository>
<id>codemc-snapshots</id>
<url>https://repo.codemc.io/repository/maven-snapshots/</url>
</repository>
</repositories>

<dependencies>
Expand All @@ -101,5 +118,10 @@
<artifactId>acf-paper</artifactId>
<version>0.5.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.wesjd</groupId>
<artifactId>anvilgui</artifactId>
<version>1.10.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
11 changes: 11 additions & 0 deletions src/main/java/uk/co/hopperelec/mc/itemrace/ItemRaceUtils.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package uk.co.hopperelec.mc.itemrace;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.jetbrains.annotations.NotNull;
Expand All @@ -19,4 +21,13 @@ public final class ItemRaceUtils {
public static boolean isDamaged(@NotNull ItemStack item) {
return item.getItemMeta() instanceof Damageable itemMeta && itemMeta.hasDamage();
}

// Returns true if it couldn't translate the item name
public static boolean itemNameContains(@NotNull Material itemType, @NotNull String query, @NotNull Locale locale) {
final String translationKey = itemType.getItemTranslationKey();
if (translationKey == null) throw new IllegalArgumentException("Item type " + itemType + " has no translation key");
return serializeTranslatable(
Component.translatable(translationKey), locale
).toLowerCase(locale).contains(query.toLowerCase(locale));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@
import java.util.Arrays;
import java.util.Set;

import static uk.co.hopperelec.mc.itemrace.ItemRaceUtils.itemNameContains;

public class AutoDepositItemsGUI extends PaginatedGUI {
private final @NotNull ManualDepositHandler manualDepositHandler;

public AutoDepositItemsGUI(
@NotNull ItemRacePlugin plugin,
@NotNull Player viewer
) {
super(plugin, viewer, Component.translatable("gui.auto_deposit_items.title"));
super(
plugin, viewer,
Component.translatable("gui.auto_deposit_items.title"),
Component.translatable("gui.items.search.title")
);
if (!(plugin.pointsHandler instanceof ManualDepositHandler))
throw new IllegalStateException("Auto-deposit items GUI is only available when using a manual points award mode");
this.manualDepositHandler = (ManualDepositHandler) plugin.pointsHandler;
Expand Down Expand Up @@ -55,4 +61,9 @@ public void onClick(@NotNull InventoryClickEvent event) {
}
}
}

@Override
public boolean filter(@NotNull ItemStack itemStack, @NotNull String query) {
return itemNameContains(itemStack.getType(), query, viewer.locale());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

import java.util.*;

import static uk.co.hopperelec.mc.itemrace.ItemRaceUtils.itemNameContains;

public class DepositedItemsGUI extends PaginatedGUI {
public final @NotNull OfflinePlayer player;
public final static int PLAYERS_BUTTON_SLOT = 45;
Expand All @@ -22,9 +24,12 @@ public DepositedItemsGUI(
@NotNull Player viewer,
@NotNull OfflinePlayer player
) {
super(plugin, viewer, Component.translatable("gui.deposited_items.title",
Component.text(Objects.requireNonNull(player.getName()))
));
super(
plugin, viewer,
Component.translatable("gui.deposited_items.title",
Component.text(Objects.requireNonNull(player.getName()))
), Component.translatable("gui.items.search.title")
);
this.player = player;

final boolean canViewOtherInventories = viewer.hasPermission("itemrace.inventory");
Expand All @@ -49,6 +54,11 @@ public void onRefreshDepositedItems(@NotNull OfflinePlayer player) {
if (this.player == player) setItems();
}

@Override
public boolean filter(@NotNull ItemStack itemStack, @NotNull String query) {
return itemNameContains(itemStack.getType(), query, viewer.locale());
}

private void setItems() {
final List<ItemStack> items = new ArrayList<>();
plugin.pointsHandler.getItems(player).entrySet().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ public abstract class ItemRaceGUI implements InventoryHolder {
public ItemRaceGUI(@NotNull ItemRacePlugin plugin, @NotNull Player viewer, int slots, @NotNull Component title) {
this.plugin = plugin;
this.viewer = viewer;
inventory = plugin.getServer().createInventory(this, slots, title);
this.inventory = plugin.getServer().createInventory(this, slots, title);
show();
}

public void show() {
viewer.openInventory(inventory);
plugin.guiListener.registerGUI(this);
}
Expand Down
126 changes: 107 additions & 19 deletions src/main/java/uk/co/hopperelec/mc/itemrace/gui/PaginatedGUI.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package uk.co.hopperelec.mc.itemrace.gui;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.translation.GlobalTranslator;
import net.wesjd.anvilgui.AnvilGUI;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import uk.co.hopperelec.mc.itemrace.ItemRacePlugin;

import java.util.ArrayList;
Expand All @@ -19,50 +22,87 @@ public abstract class PaginatedGUI extends ItemRaceGUI {
public final static int NUM_ITEMS_PER_PAGE = 45;
public final static int PREVIOUS_BUTTON_SLOT = 48;
public final static int NEXT_BUTTON_SLOT = 50;
public final static int SEARCH_BUTTON_SLOT = 53;
private @Nullable String searchQuery;
public final @NotNull Component searchTitle;

private int page = 1;
private int numPages = 1;
private List<ItemStack> items;
private List<ItemStack> unfilteredItems;
private List<ItemStack> filteredItems;

public PaginatedGUI(@NotNull ItemRacePlugin plugin, @NotNull Player viewer, @NotNull Component title) {
public PaginatedGUI(@NotNull ItemRacePlugin plugin, @NotNull Player viewer, @NotNull Component title, @NotNull Component searchTitle) {
super(plugin, viewer, SLOTS, title);
this.searchTitle = searchTitle;
}

public List<ItemStack> getItems() {
return new ArrayList<>(items);
public int getCurrentPage() {
return page;
}

public int getNumItems() {
return items.size();
public int getNumPages() {
return numPages;
}

public void setItems(@NotNull Collection<ItemStack> items) {
this.items = new ArrayList<>(items);
numPages = Math.max(1, Math.ceilDiv(items.size(), NUM_ITEMS_PER_PAGE));
public @NotNull List<ItemStack> getItems() {
return new ArrayList<>(unfilteredItems);
}

public @NotNull List<ItemStack> getItemsShown() {
return new ArrayList<>(filteredItems);
}

public int getTotalNumItems() {
return unfilteredItems.size();
}

public int getNumItemsShown() {
return filteredItems.size();
}

private void refreshItems() {
filteredItems = searchQuery == null ? unfilteredItems : unfilteredItems.stream()
.filter(itemStack -> filter(itemStack, searchQuery))
.toList();
numPages = Math.max(1, Math.ceilDiv(filteredItems.size(), NUM_ITEMS_PER_PAGE));
if (page > numPages) page = numPages;
draw();
}

public void setItems(@NotNull Collection<ItemStack> items) {
this.unfilteredItems = new ArrayList<>(items);
refreshItems();
}

public void addItem(@NotNull ItemStack item) {
items.add(item);
if (items.size() != 1 && numPages % NUM_ITEMS_PER_PAGE == 1) {
if (page == numPages++) drawNextArrow();
} else if (page == numPages) getInventory().addItem(item);
unfilteredItems.add(item);
if (searchQuery == null || filter(item, searchQuery)) {
filteredItems.add(item);
if (filteredItems.size() != 1 && numPages % NUM_ITEMS_PER_PAGE == 1) {
if (page == numPages++) drawNextArrow();
} else if (page == numPages) getInventory().addItem(item);
}
}

public void replaceItem(int index, @NotNull ItemStack item) {
items.set(index, item);
if (index >= (page - 1) * NUM_ITEMS_PER_PAGE && index < page * NUM_ITEMS_PER_PAGE)
getInventory().setItem(index % NUM_ITEMS_PER_PAGE, item);
final ItemStack oldItem = unfilteredItems.set(index, item);
for (int j = 0; j < filteredItems.size(); j++) {
if (filteredItems.get(j).equals(oldItem)) {
filteredItems.set(j, item);
if (j >= (page - 1) * NUM_ITEMS_PER_PAGE && j < page * NUM_ITEMS_PER_PAGE)
getInventory().setItem(j % NUM_ITEMS_PER_PAGE, item);
return;
}
}
}

private void draw() {
public void draw() {
// Add paginated items
final int startOfPage = (page - 1) * NUM_ITEMS_PER_PAGE;
int pagedIndex = 0;
int itemsIndex = startOfPage + pagedIndex;
while (pagedIndex < NUM_ITEMS_PER_PAGE && itemsIndex < items.size()) {
getInventory().setItem(pagedIndex++, items.get(itemsIndex++));
while (pagedIndex < NUM_ITEMS_PER_PAGE && itemsIndex < filteredItems.size()) {
getInventory().setItem(pagedIndex++, filteredItems.get(itemsIndex++));
}
while (pagedIndex < NUM_ITEMS_PER_PAGE) {
getInventory().clear(pagedIndex++);
Expand All @@ -73,6 +113,24 @@ private void draw() {
else getInventory().setItem(PREVIOUS_BUTTON_SLOT, createArrow("gui.button.previous"));
if (page == numPages) getInventory().clear(NEXT_BUTTON_SLOT);
else drawNextArrow();

final ItemStack searchButton = new ItemStack(Material.ARROW);
final ItemMeta searchButtonMeta = searchButton.getItemMeta();
searchButtonMeta.itemName(
GlobalTranslator.render(
Component.translatable("gui.button.search"),
viewer.locale()
)
);
if (searchQuery != null)
searchButtonMeta.lore(List.of(
GlobalTranslator.render(
Component.translatable("gui.button.search.lore", Component.text(searchQuery)),
viewer.locale()
)
));
searchButton.setItemMeta(searchButtonMeta);
getInventory().setItem(SEARCH_BUTTON_SLOT, searchButton);
}

private void drawNextArrow() {
Expand All @@ -87,6 +145,18 @@ private void drawNextArrow() {
return itemStack;
}

public @Nullable String getSearchQuery() {
return searchQuery;
}

public void search(@Nullable String query) {
page = 1;
searchQuery = query == null || query.isEmpty() ? null : query.toLowerCase();
refreshItems();
}

public abstract boolean filter(@NotNull ItemStack itemStack, @NotNull String query);

@Override
public void onClick(@NotNull InventoryClickEvent event) {
if (event.getClick().isMouseClick()) {
Expand All @@ -100,6 +170,24 @@ public void onClick(@NotNull InventoryClickEvent event) {
page++;
draw();
}
} else if (event.getSlot() == SEARCH_BUTTON_SLOT) {
final ItemStack barrier = new ItemStack(Material.BARRIER);
final ItemMeta barrierMeta = barrier.getItemMeta();
barrierMeta.displayName(Component.empty());
barrier.setItemMeta(barrierMeta);
new AnvilGUI.Builder()
.plugin(plugin)
.jsonTitle(GsonComponentSerializer.gson().serialize(searchTitle))
.itemLeft(barrier)
.itemRight(barrier)
.itemOutput(barrier)
.onClick((slot, stateSnapshot) -> List.of())
.onClose(stateSnapshot -> {
search(stateSnapshot.getText());
// Not sure why but, if I don't delay this, then GUIListener isn't able to register itself
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, this::show);
})
.open(viewer);
}
}
event.setCancelled(true);
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/uk/co/hopperelec/mc/itemrace/gui/PlayersGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

public class PlayersGUI extends PaginatedGUI {
public PlayersGUI(@NotNull ItemRacePlugin plugin, @NotNull Player viewer) {
super(plugin, viewer, Component.translatable("gui.players.title"));
super(
plugin, viewer,
Component.translatable("gui.players.title"),
Component.translatable("gui.players.search.title")
);
final AtomicInteger index = new AtomicInteger();
setItems(
plugin.pointsHandler.getEligiblePlayers().stream()
Expand All @@ -39,7 +43,7 @@ public void onClick(@NotNull InventoryClickEvent event) {

@Override
public void onAddEligiblePlayer(@NotNull OfflinePlayer player) {
addItem(createSkull(player, getNumItems()));
addItem(createSkull(player, getTotalNumItems()));
}

@Override
Expand All @@ -57,6 +61,15 @@ public void onRemoveEligiblePlayer(@NotNull OfflinePlayer player) {
setItems(newItems);
}

@Override
public boolean filter(@NotNull ItemStack itemStack, @NotNull String query) {
final OfflinePlayer skullOwner = ((SkullMeta) itemStack.getItemMeta()).getOwningPlayer();
if (skullOwner == null) return true;
final String skullOwnerName = skullOwner.getName();
if (skullOwnerName == null) return true;
return skullOwnerName.toLowerCase().contains(query);
}

private @NotNull ItemStack createSkull(@NotNull OfflinePlayer player, int index) {
final ItemStack itemStack = new ItemStack(Material.PLAYER_HEAD);
final SkullMeta itemMeta = (SkullMeta) itemStack.getItemMeta();
Expand Down
Loading

0 comments on commit 956db27

Please sign in to comment.