3 Commits

Author SHA1 Message Date
ea0bb44416 Add memoization support and implement equals/hashCode methods
All checks were successful
SteamWarCI Build successful
2025-04-21 23:20:15 +02:00
4cb0ff292e Add support for player inventory integration in UI system
All checks were successful
SteamWarCI Build successful
2025-04-21 16:02:18 +02:00
ee9eb995f5 Add inventory UI framework with layout and state features
All checks were successful
SteamWarCI Build successful
2025-04-21 14:48:42 +02:00
25 changed files with 1804 additions and 0 deletions

View File

@@ -24,6 +24,7 @@ import de.steamwar.Reflection;
import de.steamwar.command.*;
import de.steamwar.core.authlib.AuthlibInjector;
import de.steamwar.core.events.*;
import de.steamwar.inventory.ui.test.TestInvCommand;
import de.steamwar.message.Message;
import de.steamwar.network.NetworkReceiver;
import de.steamwar.network.handlers.ServerDataHandler;
@@ -67,6 +68,7 @@ public class Core extends JavaPlugin{
@Override
public void onEnable() {
new TestInvCommand();
errorHandler = new ErrorHandler();
crashDetector = new CrashDetector();

View File

@@ -0,0 +1,76 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui;
import de.steamwar.inventory.ui.util.UpdateStream;
import org.bukkit.entity.Player;
public abstract class StatefulUIInventory<T> extends UIInventory implements UpdateStream.Subscriber<T> {
private T state;
private final UpdateStream<T> stream;
private UpdateStream.Unsubscriber unsubscriber;
protected abstract UIComponent render(T state);
protected abstract void update(T state);
public StatefulUIInventory(UpdateStream<T> stateObservable, int playerSlots) {
super(playerSlots);
this.stream = stateObservable;
}
public StatefulUIInventory(UpdateStream<T> stateObservable) {
super();
this.stream = stateObservable;
}
public void dispose() {
unsubscriber.unsubscribe();
this.unsubscriber = null;
}
@Override
public UIComponent render() {
return render(state);
}
@Override
public void onUpdate(T current) {
state = current;
update(current);
updateUI();
}
@Override
void onClose() {
if (viewers.isEmpty()) {
dispose();
}
}
@Override
public void addViewer(Player... players) {
if (viewers.isEmpty()) {
unsubscriber = stream.subscribe(this);
}
super.addViewer(players);
}
}

View File

@@ -0,0 +1,82 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui;
import de.steamwar.inventory.ui.item.Enchanted;
import de.steamwar.inventory.ui.item.OnClick;
import de.steamwar.inventory.ui.layout.FixedSizeContainer;
import de.steamwar.inventory.ui.util.*;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.function.Consumer;
import java.util.function.Function;
public interface UIComponent {
ComponentRender render(RenderContext context);
default UIComponent onClick(Consumer<InventoryClickEvent> event) {
return onClick(false, event);
}
default UIComponent onClick(boolean allowDoubleClick, Consumer<InventoryClickEvent> event) {
return new OnClick(this, allowDoubleClick, inventoryClickEvent -> {
event.accept(inventoryClickEvent);
return true;
});
}
default UIComponent onClickWithUpdate(Function<InventoryClickEvent, Boolean> event) {
return onClickWithUpdate(false, event);
}
default UIComponent onClickWithUpdate(boolean allowDoubleClick, Function<InventoryClickEvent, Boolean> event) {
return new OnClick(this, allowDoubleClick, event);
}
default UIComponent onClickNoUpdate(Consumer<InventoryClickEvent> event) {
return onClickNoUpdate(false, event);
}
default UIComponent onClickNoUpdate(boolean allowDoubleClick, Consumer<InventoryClickEvent> event) {
return new OnClick(this, allowDoubleClick, inventoryClickEvent -> {
event.accept(inventoryClickEvent);
return false;
});
}
default UIComponent cancelOnClick() {
return new OnClick(this, true, inventoryClickEvent -> false);
}
default UIComponent enchanted() {
return new Enchanted(this);
}
default UIComponent enchanted(boolean enchanted) {
if (enchanted)
return enchanted();
else
return this;
}
default UIComponent size(Size size) {
return new FixedSizeContainer(size, this);
}
}

View File

@@ -0,0 +1,33 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
public abstract class UIFragment implements UIComponent {
abstract public UIComponent build(RenderContext context);
@Override
public ComponentRender render(RenderContext context) {
return build(context).render(context);
}
}

View File

@@ -0,0 +1,242 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui;
import de.steamwar.core.Core;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.RenderItem;
import de.steamwar.inventory.ui.util.Size;
import de.steamwar.message.Message;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public abstract class UIInventory {
protected final List<Player> viewers = new ArrayList<>();
private final Map<Player, OpenPlayerInventory> callbacks = new HashMap<>();
private final Map<Player, ItemStack[]> originalItems = new HashMap<>();
private UIInventory parent;
private Player currentRender;
private final int playerSlots;
private List<MemoHookMeta<?>> memoHooks = new ArrayList<>();
private int buildHookCount = 0;
protected UIInventory() {
this(0);
}
protected UIInventory(int playerSlots) {
this.playerSlots = playerSlots;
}
public abstract UIComponent render();
public abstract Inventory createInventory(Player player);
public abstract Message getMessages();
void onClose() { }
public <T> T useMemo(Supplier<T> supplier, List<Object> dependencies) {
int currentHook = buildHookCount++;
if (currentHook >= memoHooks.size()) {
T value = supplier.get();
memoHooks.add(new MemoHookMeta<>(dependencies, value));
return value;
}
MemoHookMeta<T> hook = (MemoHookMeta<T>) memoHooks.get(currentHook);
if (!hook.getDependencies().equals(dependencies)) {
hook.setDependencies(dependencies);
T value = supplier.get();
hook.setValue(value);
return value;
}
return hook.getValue();
}
public <T> T useMemo(Supplier<T> supplier) {
return useMemo(supplier, Collections.emptyList());
}
public void addViewer(Player... players) {
viewers.addAll(Arrays.asList(players));
for (Player player : players) {
openInventory(player);
}
}
private void openInventory(Player player) {
if (playerSlots > 0) {
originalItems.put(player, player.getInventory().getContents());
}
Inventory inventory = createInventory(player);
renderPlayer(player, inventory);
InventoryView view = player.openInventory(inventory);
Core.getInstance().getLogger().info("[UIINV] Opened " + view.getTitle() + " for " + player.getName());
}
private void renderPlayer(Player player, Inventory inventory) {
int width = Math.min(inventory.getSize(), 9);
if (width < 9 && playerSlots > 0) {
throw new IllegalArgumentException("Player Inventory is only supported in Chests");
}
int inventoryHeight = Math.max(inventory.getSize() / 9, 1);
int height = inventoryHeight + playerSlots;
int invSize = inventoryHeight * width;
currentRender = player;
buildHookCount = 0;
ComponentRender render = render().render(new RenderContext(new Size(width, height), new Size(width, height), this));
buildHookCount = -1;
currentRender = null;
if (render.getSize().getWidth() > width || render.getSize().getHeight() > height) {
throw new IllegalArgumentException("The UIComponent does not fit in the inventory");
}
Message message = getMessages();
ItemStack[] itemStacks = new ItemStack[inventory.getSize()];
ItemStack[] playerStacks = new ItemStack[playerSlots > 0 ? 4 * 9 : 0];
for (RenderItem item : render.getItems()) {
int slot = item.getX() + item.getY() * 9;
if (slot < invSize) {
itemStacks[item.getX() + item.getY() * 9] = item.getItemStack().message(message).build(player);
} else {
playerStacks[(item.getX() + item.getY() * 9) - invSize] = item.getItemStack().message(message).build(player);
}
}
inventory.setContents(itemStacks);
if (playerSlots > 0) {
ItemStack[] lastRow = new ItemStack[9];
System.arraycopy(playerStacks, playerStacks.length - 9, lastRow, 0, 9);
System.arraycopy(playerStacks, 0, playerStacks, 9, playerStacks.length - 9);
System.arraycopy(lastRow, 0, playerStacks, 0, 9);
// TODO: Update to Client-Side Only Update
player.getInventory().setContents(playerStacks);
}
Map<Integer, Function<InventoryClickEvent, Boolean>> clickEventMap = render.getItems().stream()
.filter(item -> item.getClickEvent() != null)
.collect(Collectors.toMap(renderItem -> renderItem.getX() + renderItem.getY() * 9, RenderItem::getClickEvent));
callbacks.computeIfAbsent(player, p -> new OpenPlayerInventory(p, clickEventMap).register()).setCallbacks(clickEventMap);
}
protected void updateTitle(String title, Object... params) {
if (currentRender != null) {
currentRender.getOpenInventory().setTitle(getMessages().parse(title, currentRender, params));
}
}
protected void updateUI() {
viewers.forEach(player -> renderPlayer(player, player.getOpenInventory().getTopInventory()));
}
protected void push(Player player, UIInventory inventory) {
inventory.parent = this;
inventory.addViewer(player);
viewers.remove(player);
}
protected void pop(Player player) {
if(parent == null)
return;
parent.addViewer(player);
}
@AllArgsConstructor
@Setter
private class OpenPlayerInventory implements Listener {
private final Player player;
private Map<Integer, Function<InventoryClickEvent, Boolean>> callbacks;
OpenPlayerInventory register() {
Core.getInstance().getServer().getPluginManager().registerEvents(this, Core.getInstance());
return this;
}
@EventHandler
public void onInventoryClick(InventoryClickEvent e) {
if (!player.equals(e.getWhoClicked()))
return;
if (callbacks.containsKey(e.getRawSlot()) && callbacks.get(e.getRawSlot()) != null) {
e.setCancelled(true);
Core.getInstance().getLogger().info("[UIINV] " + e.getWhoClicked().getName() + " " + e.getClick().name() + " clicked " + e.getRawSlot() + " on " + (e.getCurrentItem() != null ? e.getCurrentItem().getItemMeta().getDisplayName() : "[EMPTY]") + " in " + e.getView().getTitle());
if (callbacks.get(e.getRawSlot()).apply(e)) {
updateUI();
}
}
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent e){
if(!player.equals(e.getPlayer()))
return;
InventoryClickEvent.getHandlerList().unregister(this);
InventoryCloseEvent.getHandlerList().unregister(this);
Core.getInstance().getLogger().info("[UIINV] " + player.getName() + " closed " + e.getView().getTitle());
if(callbacks.containsKey(-1))
callbacks.get(-1).apply(null);
viewers.remove(player);
UIInventory.this.callbacks.remove(player);
if (playerSlots > 0) {
player.getInventory().setContents(originalItems.remove(player));
}
onClose();
}
}
@Getter
@Setter
@AllArgsConstructor
private class MemoHookMeta<T> {
private List<Object> dependencies;
private T value;
}
}

View File

@@ -0,0 +1,66 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.fragments;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.UIFragment;
import de.steamwar.inventory.ui.item.ItemBuilder;
import de.steamwar.inventory.ui.item.UIItem;
import de.steamwar.inventory.ui.layout.Row;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.Size;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Material;
import java.util.Arrays;
import java.util.Map;
@Getter
@Setter
public class ActionBar extends UIFragment {
private static final UIComponent DEFAULT_FILLER = new UIItem(ItemBuilder.of(Material.GRAY_STAINED_GLASS_PANE).displayName("§e")).cancelOnClick();
private final Map<Integer, UIComponent> actionBar;
private UIComponent filler = DEFAULT_FILLER;
public ActionBar(Map<Integer, UIComponent> actionBar) {
this.actionBar = actionBar;
}
public ActionBar filler(UIComponent filler) {
this.filler = filler;
return this;
}
@Override
public UIComponent build(RenderContext context) {
UIComponent[] components = context.useMemo(() -> {
UIComponent[] cmp = new UIComponent[context.getContainerSize().getWidth()];
for (int i = 0; i < cmp.length; i++) {
cmp[i] = actionBar.getOrDefault(i, filler);
}
return cmp;
}, Arrays.asList(actionBar));
return new Row(Arrays.asList(components)).size(new Size(context.getContainerSize().getWidth(), 1));
}
}

View File

@@ -0,0 +1,97 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.fragments;
import de.steamwar.core.Core;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.UIFragment;
import de.steamwar.inventory.ui.item.ItemBuilder;
import de.steamwar.inventory.ui.item.UIItem;
import de.steamwar.inventory.ui.layout.Column;
import de.steamwar.inventory.ui.layout.FixedSizeContainer;
import de.steamwar.inventory.ui.layout.Row;
import de.steamwar.inventory.ui.util.RenderContext;
import lombok.Setter;
import org.bukkit.DyeColor;
import java.util.*;
@Setter
public class HorizontalScrollLayout extends UIFragment {
private List<UIComponent> data;
private int scroll = 0;
private final Map<Integer, UIComponent> actionBar = new HashMap<>();
public HorizontalScrollLayout(List<UIComponent> data) {
this.data = data;
}
private int maxScroll() {
return Math.max(0, Math.min(scroll, data.size() - 9 + 1));
}
public HorizontalScrollLayout actionBar(Map<Integer, UIComponent> actionBar) {
this.actionBar.clear();
this.actionBar.putAll(actionBar);
return this;
}
public HorizontalScrollLayout data(List<UIComponent> data) {
this.data = data;
return this;
}
@Override
public UIComponent build(RenderContext context) {
scroll = maxScroll();
boolean hasPrev = scroll > 0;
boolean hasNext = data.size() - scroll > 9;
Map<Integer, UIComponent> actionBar = context.useMemo(() -> {
Map<Integer, UIComponent> content = new HashMap<>(this.actionBar);
content.put(0, new UIItem(ItemBuilder.color(hasPrev ? DyeColor.LIME : DyeColor.GRAY).message(Core.MESSAGE).translateName(hasPrev ? "SWLISINV_PREVIOUS_PAGE_ACTIVE" : "SWLISINV_PREVIOUS_PAGE_INACTIVE")).onClickWithUpdate(inventoryClickEvent -> {
if (scroll > 0) {
scroll = Math.max(0, scroll - 9);
return true;
} else {
return false;
}
}));
content.put(8, new UIItem(ItemBuilder.color(hasNext ? DyeColor.LIME : DyeColor.GRAY).message(Core.MESSAGE).translateName(hasNext ? "SWLISINV_NEXT_PAGE_ACTIVE" : "SWLISINV_NEXT_PAGE_INACTIVE")).onClickWithUpdate(inventoryClickEvent -> {
if (hasNext) {
scroll = Math.min(scroll + 9, data.size() - 9);
return true;
} else {
return false;
}
}));
return content;
}, Arrays.asList(hasPrev, hasNext));
return new Column(
new FixedSizeContainer(context.getContainerSize().shrink(0, 1),
new Row(data.subList(scroll, Math.min(data.size(), scroll + 9)))
),
new ActionBar(actionBar)
);
}
}

View File

@@ -0,0 +1,114 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.fragments;
import de.steamwar.core.Core;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.UIFragment;
import de.steamwar.inventory.ui.item.ItemBuilder;
import de.steamwar.inventory.ui.item.UIItem;
import de.steamwar.inventory.ui.layout.Column;
import de.steamwar.inventory.ui.layout.FixedSizeContainer;
import de.steamwar.inventory.ui.layout.Grid;
import de.steamwar.inventory.ui.util.RenderContext;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.DyeColor;
import org.bukkit.event.inventory.ClickType;
import java.util.*;
import java.util.function.Consumer;
@Getter
@Setter
public class PageLayout<T> extends UIFragment {
private final List<UIListEntry<T>> items;
private final UIListCallback<T> callback;
private int page = 0;
private Consumer<Integer> onPageChange = null;
private Map<Integer, UIItem> itemsOnBar = Collections.emptyMap();
public PageLayout(List<UIListEntry<T>> items, UIListCallback<T> callback) {
System.out.println(items.size());
this.items = items;
this.callback = callback;
}
@Override
public UIComponent build(RenderContext context) {
int itemsOnPage = context.getContainerSize().getWidth() * (context.getContainerSize().getHeight() - 1);
List<UIComponent> uiItems = context.useMemo(() -> {
int i = page * itemsOnPage;
int ipageLimit = items.size() - page * itemsOnPage;
if (ipageLimit > itemsOnPage) {
ipageLimit = itemsOnPage;
}
List<UIComponent> pageItems = new ArrayList<>();
for (int ipage = 0; ipage < ipageLimit; ipage++) {
UIListEntry<T> e = items.get(i);
pageItems.add(new UIItem(e.item).onClickNoUpdate(inventoryClickEvent -> callback.clicked(inventoryClickEvent.getClick(), e.object)));
i++;
}
return pageItems;
}, Arrays.asList(page, items.size(), itemsOnPage));
Map<Integer, UIComponent> uiBar = context.useMemo(() -> {
Map<Integer, UIComponent> content = new HashMap<>(itemsOnBar);
if (page != 0) {
content.put(0, new UIItem(ItemBuilder.color(DyeColor.LIME).message(Core.MESSAGE).translateName("SWLISINV_PREVIOUS_PAGE_ACTIVE")).onClick(inventoryClickEvent -> page--));
} else {
content.put(0, new UIItem(ItemBuilder.color(DyeColor.GRAY).message(Core.MESSAGE).translateName("SWLISINV_PREVIOUS_PAGE_INACTIVE")).cancelOnClick());
}
if (page < items.size() / itemsOnPage - (items.size() % itemsOnPage == 0 ? 1 : 0)) {
content.put(8, new UIItem(ItemBuilder.color(DyeColor.LIME).message(Core.MESSAGE).translateName("SWLISINV_NEXT_PAGE_ACTIVE")).onClick(inventoryClickEvent -> page++));
} else {
content.put(8, new UIItem(ItemBuilder.color(DyeColor.GRAY).message(Core.MESSAGE).translateName("SWLISINV_NEXT_PAGE_INACTIVE")).cancelOnClick());
}
return content;
}, Arrays.asList(page, items.size(), itemsOnPage));
return new Column(
new FixedSizeContainer(context.getContainerSize().shrink(0, 1),
new Grid(uiItems)),
new ActionBar(uiBar)
);
}
public interface UIListCallback<T>{
void clicked(ClickType click, T element);
}
@Data
public static class UIListEntry<T>{
final ItemBuilder item;
final T object;
}
}

View File

@@ -0,0 +1,44 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.RenderItem;
public class Enchanted implements UIComponent {
private final UIComponent children;
public Enchanted(UIComponent children) {
this.children = children;
}
@Override
public ComponentRender render(RenderContext context) {
ComponentRender render = children.render(context);
for (RenderItem item : render.getItems()) {
item.getItemStack().enchanted();
}
return render;
}
}

View File

@@ -0,0 +1,205 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.core.FlatteningWrapper;
import de.steamwar.core.TrickyTrialsWrapper;
import de.steamwar.inventory.SWItem;
import de.steamwar.message.Message;
import de.steamwar.message.SubMessage;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.*;
import java.util.stream.Collectors;
public class ItemBuilder {
private static final Map<DyeColor, Integer> DYE_COLOR_INTEGER_MAP = new EnumMap<>(DyeColor.class);
static {
DYE_COLOR_INTEGER_MAP.put(DyeColor.BLACK, 16);
DYE_COLOR_INTEGER_MAP.put(DyeColor.RED, 1);
DYE_COLOR_INTEGER_MAP.put(DyeColor.GREEN, 2);
DYE_COLOR_INTEGER_MAP.put(DyeColor.BROWN, 3);
DYE_COLOR_INTEGER_MAP.put(DyeColor.BLUE, 4);
DYE_COLOR_INTEGER_MAP.put(DyeColor.PURPLE, 5);
DYE_COLOR_INTEGER_MAP.put(DyeColor.CYAN, 6);
DYE_COLOR_INTEGER_MAP.put(DyeColor.LIGHT_GRAY, 7);
DYE_COLOR_INTEGER_MAP.put(DyeColor.GRAY, 8);
DYE_COLOR_INTEGER_MAP.put(DyeColor.PINK, 9);
DYE_COLOR_INTEGER_MAP.put(DyeColor.LIME, 10);
DYE_COLOR_INTEGER_MAP.put(DyeColor.YELLOW, 11);
DYE_COLOR_INTEGER_MAP.put(DyeColor.LIGHT_BLUE, 12);
DYE_COLOR_INTEGER_MAP.put(DyeColor.MAGENTA, 13);
DYE_COLOR_INTEGER_MAP.put(DyeColor.ORANGE, 14);
DYE_COLOR_INTEGER_MAP.put(DyeColor.WHITE, 15);
}
private final ItemStack itemStack;
private final ItemMeta itemMeta;
private Message message = null;
private SubMessage translateName = null;
private List<SubMessage> translateLore = null;
private ItemBuilder(Material material) {
this(new ItemStack(material));
}
private ItemBuilder(ItemStack item) {
this.itemStack = item;
this.itemMeta = itemStack.getItemMeta();
}
public static ItemBuilder of(Material material) {
return new ItemBuilder(material);
}
public static ItemBuilder of(ItemStack itemStack) {
return new ItemBuilder(itemStack.getType()).amount(itemStack.getAmount());
}
private static ItemBuilder of(Material material, Byte meta) {
ItemStack itemStack;
try {
itemStack = new ItemStack(material, 1, (short)0, meta);
} catch (IllegalArgumentException e) {
itemStack = new ItemStack(material, 1);
}
return new ItemBuilder(itemStack);
}
public static ItemBuilder color(DyeColor color) {
return ItemBuilder.of(SWItem.getDye(DYE_COLOR_INTEGER_MAP.get(color)), DYE_COLOR_INTEGER_MAP.get(color).byteValue());
}
public static ItemBuilder skull(OfflinePlayer player) {
return skull(player.getName());
}
public static ItemBuilder skull(String name) {
return ItemBuilder.of(FlatteningWrapper.impl.setSkullOwner(name));
}
public ItemBuilder message(Message message) {
if (this.message == null) {
this.message = message;
}
return this;
}
public ItemStack build(Player player) {
if (message != null) {
if (translateName != null) {
itemMeta.setDisplayName(message.parse(translateName.getMessage(), player, translateName.getParams()));
}
if (translateLore != null) {
itemMeta.setLore(translateLore.stream().map(subMessage -> message.parse(subMessage.getMessage(), player, subMessage.getParams())).collect(Collectors.toList()));
}
}
itemStack.setItemMeta(itemMeta);
return itemStack;
}
public ItemBuilder amount(int amount) {
itemStack.setAmount(amount);
return this;
}
public ItemBuilder displayName(String name) {
itemMeta.setDisplayName(name);
return this;
}
public ItemBuilder lore(String... lore) {
if (itemMeta.hasLore()) {
List<String> loreList = itemMeta.getLore();
loreList.addAll(Arrays.asList(lore));
itemMeta.setLore(loreList);
} else {
itemMeta.setLore(Arrays.asList(lore));
}
return this;
}
public ItemBuilder translateName(String name, Object... params) {
this.translateName = new SubMessage(name, params);
return this;
}
public ItemBuilder addTranslateLore(SubMessage... lore) {
if (this.translateLore == null) {
this.translateLore = Arrays.asList(lore);
} else {
this.translateLore.addAll(Arrays.asList(lore));
}
return this;
}
public ItemBuilder enchanted() {
itemMeta.addEnchant(TrickyTrialsWrapper.impl.getUnbreakingEnchantment(), 10, true);
return this;
}
public ItemBuilder enchanted(boolean enchanted) {
if(enchanted) {
return enchanted();
} else {
return this;
}
}
public ItemBuilder hideFlags() {
for (ItemFlag flag : EnumSet.allOf(ItemFlag.class)) {
itemMeta.addItemFlags(flag);
}
return this;
}
public ItemBuilder customModel(int modelData) {
itemMeta.setCustomModelData(modelData);
return this;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
ItemBuilder that = (ItemBuilder) o;
return Objects.equals(itemStack, that.itemStack) && Objects.equals(itemMeta, that.itemMeta) && Objects.equals(message, that.message) && Objects.equals(translateName, that.translateName) && Objects.equals(translateLore, that.translateLore);
}
@Override
public String toString() {
return "ItemBuilder{" +
"itemStack=" + itemStack +
", itemMeta=" + itemMeta +
", message=" + message +
", translateName=" + translateName +
", translateLore=" + translateLore +
'}';
}
}

View File

@@ -0,0 +1,74 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.RenderItem;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.function.Function;
public class OnClick implements UIComponent {
private final UIComponent children;
private final boolean allowDoubleClick;
private final Function<InventoryClickEvent, Boolean> event;
public OnClick(UIComponent children, boolean allowDoubleClick, Function<InventoryClickEvent, Boolean> event) {
this.children = children;
this.allowDoubleClick = allowDoubleClick;
this.event = event;
}
@Override
public ComponentRender render(RenderContext context) {
ComponentRender render = children.render(context);
for (RenderItem item : render.getItems()) {
item.setClickEvent(ev -> {
if (ev.getClick() != ClickType.DOUBLE_CLICK || allowDoubleClick) {
return event.apply(ev);
}
return false;
});
}
return render;
}
@Override
public String toString() {
return "OnClick{" +
"children=" + children +
", allowDoubleClick=" + allowDoubleClick +
'}';
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
OnClick onClick = (OnClick) o;
return allowDoubleClick == onClick.allowDoubleClick && children.equals(onClick.children);
}
}

View File

@@ -0,0 +1,42 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.Size;
import java.util.ArrayList;
public class Space implements UIComponent {
@Override
public ComponentRender render(RenderContext context) {
return new ComponentRender(
new ArrayList<>(),
new Size(1, 1)
);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Space;
}
}

View File

@@ -0,0 +1,64 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.*;
import lombok.EqualsAndHashCode;
import org.bukkit.inventory.ItemStack;
import java.util.Arrays;
import java.util.Objects;
public class UIItem implements UIComponent {
private final ItemBuilder itemStack;
public UIItem(ItemStack itemStack) {
this(ItemBuilder.of(itemStack));
}
public UIItem(ItemBuilder itemStack) {
this.itemStack = itemStack;
}
@Override
public ComponentRender render(RenderContext context) {
return new ComponentRender(
Arrays.asList(new RenderItem(0, 0, itemStack, null)),
new Size(1, 1)
);
}
@Override
public String toString() {
return "UIItem{" +
"itemStack=" + itemStack +
'}';
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
UIItem uiItem = (UIItem) o;
return Objects.equals(itemStack, uiItem.itemStack);
}
}

View File

@@ -0,0 +1,27 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
public enum Alignment {
START,
CENTER,
END,
BETWEEN
}

View File

@@ -0,0 +1,93 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Column implements UIComponent {
private final List<UIComponent> components;
private Alignment alignment;
private boolean shrinkWrap = false;
public Column(UIComponent... components) {
this(Arrays.asList(components));
}
public Column(List<UIComponent> components) {
this.components = components;
}
public Column centered() {
this.alignment = Alignment.CENTER;
return this;
}
public Column setShrinkWrap(boolean shrinkWrap) {
this.shrinkWrap = shrinkWrap;
return this;
}
@Override
public ComponentRender render(RenderContext context) {
List<RenderItem> renderItems = new ArrayList<>();
int height = 0;
int maxWidth = 0;
for (UIComponent component : components) {
ComponentRender render = component.render(context.withContainerSize(context.getContainerSize().withHeight(context.getContainerSize().getHeight() - height)));
for(RenderItem item : render.getItems()){
renderItems.add(item.shift(0, height));
}
maxWidth = Math.max(maxWidth, render.getSize().getWidth());
height += render.getSize().getHeight();
}
if (alignment == Alignment.CENTER) {
int shiftAmount = (context.getContainerSize().getHeight() - height) / 2;
for (RenderItem item : renderItems) {
item.shift(0, shiftAmount);
}
} else if (alignment == Alignment.BETWEEN) {
int shiftAmount = (context.getContainerSize().getHeight() - height) / (components.size() - 1);
for (int i = 0; i < components.size() - 1; i++) {
RenderItem item = renderItems.get(i);
item.shift(0, shiftAmount * i);
}
} else if (alignment == Alignment.END) {
int shiftAmount = (context.getContainerSize().getHeight() - height);
for (RenderItem item : renderItems) {
item.shift(0, shiftAmount);
}
}
return new ComponentRender(
renderItems,
new Size(maxWidth, shrinkWrap ? height : context.getContainerSize().getHeight())
);
}
}

View File

@@ -0,0 +1,40 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.Size;
public class FixedSizeContainer implements UIComponent {
private final UIComponent children;
private final Size size;
public FixedSizeContainer(Size size, UIComponent children) {
this.children = children;
this.size = size;
}
@Override
public ComponentRender render(RenderContext context) {
return children.render(context.withContainerSize(size)).withSize(size);
}
}

View File

@@ -0,0 +1,68 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.RenderItem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Grid implements UIComponent {
private final List<UIComponent> children;
public Grid(UIComponent... children) {
this(Arrays.asList(children));
}
public Grid(List<UIComponent> children) {
this.children = children;
}
@Override
public ComponentRender render(RenderContext context) {
List<RenderItem> renderItems = new ArrayList<>();
int x = 0;
int y = 0;
int currentRowMaxHeight = 0;
for (UIComponent child : children) {
ComponentRender render = child.render(context.withContainerSize(
context.getContainerSize()
.withWidth(context.getContainerSize().getWidth() - x)
.withHeight(context.getContainerSize().getHeight() - y)));
currentRowMaxHeight = Math.max(currentRowMaxHeight, render.getSize().getHeight());
for (RenderItem item : render.getItems()) {
renderItems.add(item.shift(x, y));
}
x += render.getSize().getWidth();
if(x >= context.getContainerSize().getWidth()){
x = 0;
y += currentRowMaxHeight;
currentRowMaxHeight = 0;
}
}
return new ComponentRender(renderItems, context.getContainerSize());
}
}

View File

@@ -0,0 +1,98 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Row implements UIComponent {
private final List<UIComponent> components;
private Alignment alignment;
private boolean shrinkWrap = false;
public Row(UIComponent... components) {
this(Arrays.asList(components));
}
public Row(List<UIComponent> components) {
this.components = components;
}
public Row centered() {
this.alignment = Alignment.CENTER;
return this;
}
public Row alignment(Alignment alignment) {
this.alignment = alignment;
return this;
}
public Row setShrinkWrap(boolean shrinkWrap) {
this.shrinkWrap = shrinkWrap;
return this;
}
@Override
public ComponentRender render(RenderContext context) {
List<RenderItem> renderItems = new ArrayList<>();
int width = 0;
int maxHeight = 0;
for (UIComponent component : components) {
ComponentRender render = component.render(context.withContainerSize(context.getContainerSize().withWidth(context.getContainerSize().getWidth() - width)));
for(RenderItem item : render.getItems()){
renderItems.add(item.shift(width, 0));
}
maxHeight = Math.max(maxHeight, render.getSize().getHeight());
width += render.getSize().getWidth();
}
if (alignment == Alignment.CENTER) {
int shiftAmount = (context.getContainerSize().getWidth() - width) / 2;
for (RenderItem item : renderItems) {
item.shift(shiftAmount, 0);
}
} else if (alignment == Alignment.BETWEEN) {
int shiftAmount = (context.getContainerSize().getWidth() - width) / (components.size() - 1);
for (int i = 0; i < components.size() - 1; i++) {
RenderItem item = renderItems.get(i);
item.shift(shiftAmount * i, 0);
}
} else if (alignment == Alignment.END) {
int shiftAmount = (context.getContainerSize().getWidth() - width);
for (RenderItem item : renderItems) {
item.shift(shiftAmount, 0);
}
}
return new ComponentRender(
renderItems,
new Size(shrinkWrap ? width : context.getContainerSize().getWidth(), maxHeight)
);
}
}

View File

@@ -0,0 +1,41 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.test;
import de.steamwar.command.SWCommand;
import de.steamwar.inventory.ui.util.UpdateStream;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
public class TestInvCommand extends SWCommand {
public static final UpdateStream<List<Integer>> STREAM = new UpdateStream<>(new ArrayList<>());
public TestInvCommand() {
super("testinv");
}
@Register
public void run(Player player) {
new TestInventory(STREAM).addViewer(player);
}
}

View File

@@ -0,0 +1,79 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.test;
import de.steamwar.core.Core;
import de.steamwar.inventory.ui.StatefulUIInventory;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.fragments.HorizontalScrollLayout;
import de.steamwar.inventory.ui.item.ItemBuilder;
import de.steamwar.inventory.ui.item.UIItem;
import de.steamwar.inventory.ui.util.UpdateStream;
import de.steamwar.message.Message;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static de.steamwar.inventory.ui.test.TestInvCommand.STREAM;
public class TestInventory extends StatefulUIInventory<List<Integer>> {
private HorizontalScrollLayout scrollLayout;
private static int counter = 1;
public TestInventory(UpdateStream<List<Integer>> stateObservable) {
super(stateObservable);
scrollLayout = new HorizontalScrollLayout(new ArrayList<>());
}
@Override
protected UIComponent render(List<Integer> state) {
return scrollLayout;
}
@Override
protected void update(List<Integer> state) {
System.out.println("Update");
List<UIComponent> items = state.stream().map(integer -> new UIItem(ItemBuilder.of(Material.ACACIA_PLANKS).displayName(String.valueOf(integer))).onClickNoUpdate(inventoryClickEvent -> STREAM.update(current -> {
current.remove(integer);
return current;
}))).collect(Collectors.toList());
items.add(new UIItem(ItemBuilder.of(Material.BUCKET).displayName("Add")).onClickNoUpdate(inventoryClickEvent -> STREAM.update(current -> {
current.add(counter++);
return current;
})));
scrollLayout = scrollLayout.data(items);
}
@Override
public Inventory createInventory(Player player) {
return Bukkit.createInventory(null, 9 * 6, "Test Inventory");
}
@Override
public Message getMessages() {
return Core.MESSAGE;
}
}

View File

@@ -0,0 +1,32 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import lombok.Data;
import lombok.With;
import java.util.List;
@Data
@With
public class ComponentRender {
private final List<RenderItem> items;
private final Size size;
}

View File

@@ -0,0 +1,43 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import de.steamwar.inventory.ui.UIInventory;
import lombok.Data;
import lombok.With;
import java.util.List;
import java.util.function.Supplier;
@Data
@With
public class RenderContext {
private final Size inventorySize;
private final Size containerSize;
private final UIInventory inventory;
public <T> T useMemo(Supplier<T> supplier, List<Object> dependencies) {
return inventory.useMemo(supplier, dependencies);
}
public <T> T useMemo(Supplier<T> supplier) {
return inventory.useMemo(supplier);
}
}

View File

@@ -0,0 +1,42 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import de.steamwar.inventory.ui.item.ItemBuilder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.With;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.function.Function;
@Data
@With
@AllArgsConstructor
public class RenderItem {
private int x;
private int y;
private ItemBuilder itemStack;
private Function<InventoryClickEvent, Boolean> clickEvent;
public RenderItem shift(int deltaX, int deltaY) {
return withX(x + deltaX).withY(y + deltaY);
}
}

View File

@@ -0,0 +1,34 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import lombok.Data;
import lombok.With;
@Data
@With
public class Size {
private final int width;
private final int height;
public Size shrink(int width, int height) {
return new Size(this.width - width, this.height - height);
}
}

View File

@@ -0,0 +1,66 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import java.util.ArrayList;
import java.util.List;
public class UpdateStream<T> {
private T current;
private final List<Subscriber<T>> subscribers = new ArrayList<>();
public UpdateStream(T current) {
this.current = current;
}
public Unsubscriber subscribe(Subscriber<T> subscriber) {
subscribers.add(subscriber);
subscriber.onUpdate(current);
return () -> subscribers.remove(subscriber);
}
public T get() {
return current;
}
public void emit(T newCurrent) {
current = newCurrent;
subscribers.forEach(subscriber -> subscriber.onUpdate(newCurrent));
}
public void update(Updater<T> updater) {
current = updater.update(current);
subscribers.forEach(subscriber -> subscriber.onUpdate(current));
}
public interface Subscriber<T> {
void onUpdate(T current);
}
public interface Unsubscriber {
void unsubscribe();
}
public interface Updater<T> {
T update(T current);
}
}