Skip to content
This repository was archived by the owner on Aug 12, 2025. It is now read-only.

Commit 4da0a20

Browse files
committed
Add UI components and layout management for enhanced user interface
1 parent 46c2383 commit 4da0a20

23 files changed

Lines changed: 875 additions & 0 deletions

src/main/java/gg/nextforge/plugin/NextForgePlugin.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import gg.nextforge.NextCorePlugin;
44
import gg.nextforge.command.CommandManager;
55
import gg.nextforge.config.ConfigManager;
6+
import gg.nextforge.database.DatabaseManager;
67
import gg.nextforge.npc.NPCManager;
78
import gg.nextforge.protocol.ProtocolManager;
89
import gg.nextforge.scheduler.CoreScheduler;
910
import gg.nextforge.text.TextManager;
11+
import gg.nextforge.ui.UIManager;
1012
import lombok.Getter;
1113
import org.bstats.bukkit.Metrics;
1214
import org.bukkit.plugin.Plugin;
@@ -26,6 +28,8 @@ public abstract class NextForgePlugin extends JavaPlugin {
2628
TextManager textManager;
2729
NPCManager npcManager;
2830
ProtocolManager protocolManager;
31+
UIManager uiManager;
32+
DatabaseManager databaseManager;
2933
Metrics metrics;
3034

3135
public abstract int getMetricsId();
@@ -61,13 +65,22 @@ public void onEnable() {
6165
this.textManager = new TextManager(this);
6266
this.npcManager = new NPCManager(this);
6367
this.protocolManager = new ProtocolManager(this);
68+
this.uiManager = new UIManager();
69+
70+
this.uiManager.init(this);
6471

6572
boolean isReload = getServer().getPluginManager().isPluginEnabled("NextForge");
6673
enable(isReload);
6774
}
6875

6976
@Override
7077
public void onDisable() {
78+
if (instance != this) {
79+
getLogger().warning("Plugin instance mismatch! This should not happen.");
80+
return;
81+
}
82+
instance = null;
83+
this.uiManager.shutdown();
7184
disable();
7285
}
7386
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package gg.nextforge.ui;
2+
3+
import gg.nextforge.ui.context.UIContextManager;
4+
import gg.nextforge.ui.support.ChatPromptUI;
5+
import gg.nextforge.ui.support.HotbarUI;
6+
import org.bukkit.plugin.Plugin;
7+
8+
/**
9+
* Central UI bootstrapper and manager.
10+
*/
11+
public class UIManager {
12+
13+
private Plugin plugin;
14+
15+
public void init(Plugin pl) {
16+
plugin = pl;
17+
18+
ChatPromptUI.registerListener(plugin);
19+
HotbarUI.registerListener(plugin);
20+
21+
// Additional: Register Inventory click listeners etc.
22+
// e.g. InventoryInteractionHandler.register(plugin);
23+
}
24+
25+
public void shutdown() {
26+
UIContextManager.clearAll();
27+
}
28+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package gg.nextforge.ui.action;
2+
3+
import org.bukkit.entity.Player;
4+
import org.bukkit.event.inventory.ClickType;
5+
6+
/**
7+
* Encapsulates relevant data for a UI action event.
8+
*/
9+
public class ActionContext {
10+
11+
private final Player player;
12+
private final int slot;
13+
private final ClickType clickType;
14+
15+
/**
16+
* Constructs an ActionContext with the specified player, slot, and click type.
17+
*
18+
* @param player The player who performed the action.
19+
* @param slot The inventory slot that was interacted with.
20+
* @param clickType The type of click that triggered the action.
21+
*/
22+
public ActionContext(Player player, int slot, ClickType clickType) {
23+
this.player = player;
24+
this.slot = slot;
25+
this.clickType = clickType;
26+
}
27+
28+
/**
29+
* Gets the player who performed the action.
30+
*
31+
* @return The player associated with this action context.
32+
*/
33+
public Player getPlayer() {
34+
return player;
35+
}
36+
37+
/**
38+
* Gets the inventory slot that was interacted with.
39+
*
40+
* @return The slot number associated with this action context.
41+
*/
42+
public int getSlot() {
43+
return slot;
44+
}
45+
46+
/**
47+
* Gets the type of click that triggered the action.
48+
*
49+
* @return The ClickType associated with this action context.
50+
*/
51+
public ClickType getClickType() {
52+
return clickType;
53+
}
54+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package gg.nextforge.ui.action;
2+
3+
import org.bukkit.entity.Player;
4+
import org.bukkit.event.inventory.ClickType;
5+
6+
/**
7+
* Represents an action that can be executed when a player clicks on a UI element.
8+
* This interface defines a single method to handle click events with the player and the type of click.
9+
*/
10+
@FunctionalInterface
11+
public interface ClickAction {
12+
13+
/**
14+
* Executes the action when a player clicks on a UI element.
15+
*
16+
* @param player the player who clicked
17+
* @param clickType the type of click (e.g., LEFT, RIGHT)
18+
*/
19+
void execute(Player player, ClickType clickType);
20+
}
21+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package gg.nextforge.ui.action;
2+
3+
import org.bukkit.entity.Player;
4+
import org.bukkit.inventory.ItemStack;
5+
6+
/**
7+
* Optional interface for implementing item hover behavior,
8+
* such as tooltip updates based on player state or localization.
9+
*/
10+
@FunctionalInterface
11+
public interface HoverAction {
12+
13+
/**
14+
* Render the item stack for the given player.
15+
* @param viewer the player viewing the item
16+
* @return the ItemStack to be displayed
17+
*/
18+
ItemStack render(Player viewer);
19+
}
20+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package gg.nextforge.ui.builder;
2+
3+
import gg.nextforge.ui.component.UIComponent;
4+
import org.bukkit.Bukkit;
5+
import org.bukkit.entity.Player;
6+
import org.bukkit.inventory.Inventory;
7+
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.function.Function;
11+
12+
public class InventoryBuilder {
13+
14+
private final int size;
15+
private String title = "Inventory";
16+
private final Map<Integer, UIComponent> components = new HashMap<>();
17+
private Function<Player, String> dynamicTitle = null;
18+
19+
public InventoryBuilder(int size) {
20+
this.size = size;
21+
}
22+
23+
public InventoryBuilder title(String title) {
24+
this.title = title;
25+
return this;
26+
}
27+
28+
public InventoryBuilder title(Function<Player, String> titleFunction) {
29+
this.dynamicTitle = titleFunction;
30+
return this;
31+
}
32+
33+
public InventoryBuilder set(int slot, UIComponent component) {
34+
components.put(slot, component);
35+
return this;
36+
}
37+
38+
public Inventory build(Player player) {
39+
String finalTitle = dynamicTitle != null ? dynamicTitle.apply(player) : title;
40+
Inventory inventory = Bukkit.createInventory(null, size, finalTitle);
41+
42+
for (Map.Entry<Integer, UIComponent> entry : components.entrySet()) {
43+
inventory.setItem(entry.getKey(), entry.getValue().render(player));
44+
}
45+
46+
return inventory;
47+
}
48+
49+
public void open(Player player) {
50+
player.openInventory(build(player));
51+
}
52+
53+
public Map<Integer, UIComponent> getComponents() {
54+
return components;
55+
}
56+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package gg.nextforge.ui.component;
2+
3+
import gg.nextforge.ui.action.ClickAction;
4+
import org.bukkit.entity.Player;
5+
import org.bukkit.event.inventory.ClickType;
6+
import org.bukkit.inventory.ItemStack;
7+
8+
import java.util.function.Supplier;
9+
10+
public class UIButton implements UIComponent {
11+
12+
private final Supplier<ItemStack> itemSupplier;
13+
private final ClickAction clickAction;
14+
15+
public UIButton(Supplier<ItemStack> itemSupplier, ClickAction clickAction) {
16+
this.itemSupplier = itemSupplier;
17+
this.clickAction = clickAction;
18+
}
19+
20+
@Override
21+
public ItemStack render(Player player) {
22+
return itemSupplier.get();
23+
}
24+
25+
@Override
26+
public void onClick(Player player, ClickType clickType) {
27+
if (clickAction != null) {
28+
clickAction.execute(player, clickType);
29+
}
30+
}
31+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package gg.nextforge.ui.component;
2+
3+
import org.bukkit.entity.Player;
4+
import org.bukkit.event.inventory.ClickType;
5+
import org.bukkit.inventory.ItemStack;
6+
7+
public interface UIComponent {
8+
9+
ItemStack render(Player viewer);
10+
11+
void onClick(Player viewer, ClickType click);
12+
13+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package gg.nextforge.ui.component;
2+
3+
import org.bukkit.entity.Player;
4+
import org.bukkit.event.inventory.ClickType;
5+
import org.bukkit.inventory.ItemStack;
6+
7+
import java.util.function.Function;
8+
9+
/**
10+
* A non-interactive UI component used to display static or dynamic items.
11+
*/
12+
public class UIItemView implements UIComponent {
13+
14+
private final Function<Player, ItemStack> itemProvider;
15+
16+
public UIItemView(ItemStack item) {
17+
this(p -> item);
18+
}
19+
20+
public UIItemView(Function<Player, ItemStack> itemProvider) {
21+
this.itemProvider = itemProvider;
22+
}
23+
24+
@Override
25+
public ItemStack render(Player viewer) {
26+
return itemProvider.apply(viewer);
27+
}
28+
29+
@Override
30+
public void onClick(Player viewer, ClickType click) {
31+
// Intentionally no-op (passive component)
32+
}
33+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package gg.nextforge.ui.component;
2+
3+
import org.bukkit.entity.Player;
4+
import org.bukkit.event.inventory.ClickType;
5+
import org.bukkit.inventory.ItemStack;
6+
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
10+
/**
11+
* A composite component that holds other components in specific slots.
12+
*/
13+
public class UIPanel implements UIComponent {
14+
15+
private final Map<Integer, UIComponent> children = new HashMap<>();
16+
17+
public void setComponent(int slot, UIComponent component) {
18+
children.put(slot, component);
19+
}
20+
21+
public UIComponent getComponent(int slot) {
22+
return children.get(slot);
23+
}
24+
25+
public Map<Integer, UIComponent> getChildren() {
26+
return children;
27+
}
28+
29+
@Override
30+
public ItemStack render(Player viewer) {
31+
return null; // Panels don't render to a single item
32+
}
33+
34+
@Override
35+
public void onClick(Player viewer, ClickType click) {
36+
// Panels don't handle clicks directly
37+
}
38+
39+
public void renderToInventory(org.bukkit.inventory.Inventory inventory, Player viewer) {
40+
children.forEach((slot, component) -> {
41+
inventory.setItem(slot, component.render(viewer));
42+
});
43+
}
44+
45+
public void handleClick(Player player, int slot, ClickType clickType) {
46+
UIComponent comp = children.get(slot);
47+
if (comp != null) comp.onClick(player, clickType);
48+
}
49+
}

0 commit comments

Comments
 (0)