From a325c5c618639ca22c2c870b5fca45a72867130b Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 4 Feb 2026 12:34:26 -0500 Subject: [PATCH] Full Screen Directory Mode for Cloner Tab Handling --- .../request/clone/CloneFolderCrudPacket.java | 6 +- .../java/noppes/npcs/client/ClientProxy.java | 5 +- .../npcs/client/gui/GuiNpcMobSpawner.java | 387 +++---------- .../gui/GuiNpcMobSpawnerFullscreen.java | 534 +++++++++++++----- .../npcs/client/gui/SubGuiCloneMove.java | 100 ---- .../npcs/client/gui/SubGuiMoveConfirm.java | 57 ++ .../npcs/client/gui/util/GuiCustomScroll.java | 7 + .../gui/util/GuiCustomScrollCloner.java | 7 +- .../client/gui/util/GuiCustomScrollIcons.java | 51 +- .../npcs/client/gui/util/GuiDirectory.java | 206 +++++++ .../client/gui/util/GuiMenuSideButton.java | 13 +- .../controllers/ServerCloneController.java | 62 +- .../controllers/ServerTagMapController.java | 2 +- .../npcs/controllers/data/CloneFolder.java | 10 +- .../customnpcs/textures/gui/cloner/folder.png | Bin 0 -> 924 bytes .../customnpcs/textures/gui/cloner/tab.png | Bin 0 -> 932 bytes 16 files changed, 826 insertions(+), 621 deletions(-) delete mode 100644 src/main/java/noppes/npcs/client/gui/SubGuiCloneMove.java create mode 100644 src/main/java/noppes/npcs/client/gui/SubGuiMoveConfirm.java create mode 100644 src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java create mode 100644 src/main/resources/assets/customnpcs/textures/gui/cloner/folder.png create mode 100644 src/main/resources/assets/customnpcs/textures/gui/cloner/tab.png diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderCrudPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderCrudPacket.java index afaa5e4ed..061700e35 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderCrudPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderCrudPacket.java @@ -85,11 +85,13 @@ public final class CloneFolderCrudPacket extends AbstractPacket { boolean success = false; switch (action) { case ACTION_CREATE: - success = ServerCloneController.Instance.createFolder(name) != null; + if (CloneFolder.isValidName(name)) + success = ServerCloneController.Instance.createFolder(name) != null; break; case ACTION_RENAME: String newName = ByteBufUtils.readString(in); - success = ServerCloneController.Instance.renameFolder(name, newName); + if (CloneFolder.isValidName(newName)) + success = ServerCloneController.Instance.renameFolder(name, newName); break; case ACTION_DELETE: success = ServerCloneController.Instance.deleteFolder(name); diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index 3bc05aa41..c0c88a433 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -76,6 +76,7 @@ import noppes.npcs.client.gui.GuiBorderBlock; import noppes.npcs.client.gui.GuiMerchantAdd; import noppes.npcs.client.gui.GuiNpcDimension; import noppes.npcs.client.gui.GuiNpcMobSpawner; +import noppes.npcs.client.gui.GuiNpcMobSpawnerFullscreen; import noppes.npcs.client.gui.GuiNpcMobSpawnerMounter; import noppes.npcs.client.gui.GuiNpcPather; import noppes.npcs.client.gui.GuiNpcRedstoneBlock; @@ -602,7 +603,9 @@ public class ClientProxy extends CommonProxy { return new GuiNpcRedstoneBlock(x, y, z); else if (gui == EnumGuiType.Cloner) - return new GuiNpcMobSpawner(x, y, z); + return GuiNpcMobSpawner.isFullscreen + ? new GuiNpcMobSpawnerFullscreen(x, y, z) + : new GuiNpcMobSpawner(x, y, z); else if (gui == EnumGuiType.MobSpawnerMounter) return new GuiNpcMobSpawnerMounter(x, y, z); diff --git a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawner.java b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawner.java index 05174404d..7fd5c174e 100644 --- a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawner.java +++ b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawner.java @@ -2,10 +2,8 @@ package noppes.npcs.client.gui; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.clone.CloneAllTagsPacket; -import kamkeel.npcs.network.packets.request.clone.CloneFolderCrudPacket; import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket; import kamkeel.npcs.network.packets.request.clone.CloneListPacket; -import kamkeel.npcs.network.packets.request.clone.CloneMovePacket; import kamkeel.npcs.network.packets.request.clone.CloneRemovePacket; import kamkeel.npcs.network.packets.request.clone.CloneTagListPacket; import kamkeel.npcs.network.packets.request.clone.MobSpawnerPacket; @@ -19,7 +17,6 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.util.StatCollector; import net.minecraft.world.World; -import noppes.npcs.client.NoppesUtil; import noppes.npcs.client.controllers.ClientCloneController; import noppes.npcs.client.controllers.ClientTagMapController; import noppes.npcs.client.gui.util.GuiCustomScroll; @@ -33,13 +30,8 @@ import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.IClonerGui; import noppes.npcs.client.gui.util.ICustomScrollListener; import noppes.npcs.client.gui.util.IGuiData; -import noppes.npcs.client.gui.util.ISubGuiListener; -import noppes.npcs.client.gui.util.SubGuiInterface; -import noppes.npcs.controllers.data.CloneFolder; import noppes.npcs.controllers.data.Tag; import noppes.npcs.controllers.data.TagMap; -import noppes.npcs.util.ValueUtil; -import org.lwjgl.input.Mouse; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -49,10 +41,10 @@ import java.util.List; import java.util.Map; import java.util.UUID; -public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICustomScrollListener, ISubGuiListener, IClonerGui { +public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICustomScrollListener, IClonerGui { // ==================== STATIC STATE (persists across GUI opens, shared with fullscreen) ==================== - private static int sidebarMode = 0; // 0=Tabs, 1=Folders + public static boolean isFullscreen = false; public static int activeTab = 1; public static String activeFolder = null; public static int showingClones = 0; // 0=Clones, 1=Entities, 2=Server, 3=Filters @@ -69,6 +61,10 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust // 0 - By Name, 1 - By Date public static byte sortType = 0; + // Scroll position preservation (shared with fullscreen) + public static int savedContentScrollY = 0; + public static int savedNavScrollY = 0; + // ==================== INSTANCE STATE ==================== public TagMap tagMap; private final GuiCustomScrollCloner scroll = new GuiCustomScrollCloner(this, 0); @@ -78,12 +74,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust private List list; private List rawList; private List tagList; - private List folderNames = new ArrayList<>(); - - // Scrollable folder side buttons (folders mode only) - private HashMap folderSideButtons = new HashMap<>(); - private float folderScroll = 0; - private float destFolderScroll = 0; // ==================== CONSTRUCTOR ==================== public GuiNpcMobSpawner(int i, int j, int k) { @@ -95,15 +85,16 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust xSize = 354; setBackground("menubg.png"); PacketClient.sendClient(new CloneAllTagsPacket()); - loadFolderNames(); - } + if (showingClones == 2) { + PacketClient.sendClient(new CloneFolderListPacket()); + } - private void loadFolderNames() { - folderNames.clear(); - if (ClientCloneController.Instance != null) { - for (CloneFolder folder : ClientCloneController.Instance.getFolderList()) { - folderNames.add(folder.name); + // Minimized view doesn't support folders — ensure we're on a tab + if (activeFolder != null) { + if (activeTab < 1 || activeTab > 15) { + activeTab = 1; } + activeFolder = null; } } @@ -125,6 +116,9 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust // ==================== INIT GUI ==================== public void initGui() { + // Save scroll position before clear + savedContentScrollY = scroll.scrollY; + super.initGui(); guiTop += 10; guiLeft += 30; @@ -141,7 +135,23 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust filterScroll.multipleSelection = true; filterScroll.setSelectedList(tagFilters); - // Top buttons + // Top buttons — build right-to-left to avoid overlap + GuiMenuTopButton closeBtn = new GuiMenuTopButton(17, guiLeft + (xSize - 22), guiTop - 17, "X"); + addTopButton(closeBtn); + + // Position "+" dynamically to the left of "X" + int plusX = closeBtn.xPosition - (fontRendererObj.getStringWidth("+") + 12) - 2; + GuiMenuTopButton fullscreenBtn = new GuiMenuTopButton(61, plusX, guiTop - 17, "+"); + addTopButton(fullscreenBtn); + + // Position "Filters" dynamically to the left of "+" + int filterBtnW = fontRendererObj.getStringWidth(StatCollector.translateToLocal("gui.filters")) + 12; + int filterX = fullscreenBtn.xPosition - filterBtnW - 2; + GuiMenuTopButton filterBtn = new GuiMenuTopButton(16, filterX, guiTop - 17, "gui.filters"); + filterBtn.active = showingClones == 3; + addTopButton(filterBtn); + + // Mode buttons from left GuiMenuTopButton btn; addTopButton(btn = new GuiMenuTopButton(3, guiLeft + 4, guiTop - 17, "spawner.clones")); btn.active = showingClones == 0; @@ -149,33 +159,25 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust btn.active = showingClones == 2; addTopButton(btn = new GuiMenuTopButton(4, btn, "spawner.entities")); btn.active = showingClones == 1; - addTopButton(btn = new GuiMenuTopButton(16, guiLeft + (xSize - 82), guiTop - 17, "gui.filters")); - btn.active = showingClones == 3; - addTopButton(new GuiMenuTopButton(61, guiLeft + (xSize - 37), guiTop - 17, "+")); - addTopButton(new GuiMenuTopButton(17, guiLeft + (xSize - 22), guiTop - 17, "X")); if (showingClones < 3) { addScroll(scroll); addTextField(new GuiNpcTextField(1, this, fontRendererObj, guiLeft + 4, guiTop + 4, 293, 20, search)); + addButton(new GuiNpcButton(1, guiLeft + 298, guiTop + 6, 52, 20, "item.monsterPlacer.name")); addButton(new GuiNpcButton(2, guiLeft + 298, guiTop + 140, 52, 20, "spawner.mobspawner")); if (showingClones == 0 || showingClones == 2) { addButton(new GuiNpcButton(6, guiLeft + 298, guiTop + 190, 52, 20, "gui.remove")); - // Tabs/Folders toggle above side buttons - addButton(new GuiNpcButton(60, guiLeft - 70, guiTop - 18, 70, 16, - new String[]{"Tabs", "Folders"}, sidebarMode)); - - if (sidebarMode == 0) { - initTabSideButtons(); - } else { - initFolderSideButtons(); - } - + initTabSideButtons(); showClones(); + + // Restore scroll position + scroll.scrollY = Math.min(savedContentScrollY, Math.max(0, scroll.maxScrollY)); } else { showEntities(); + scroll.scrollY = Math.min(savedContentScrollY, Math.max(0, scroll.maxScrollY)); } } else { showFiltersPage(); @@ -201,37 +203,12 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust addSideButton(new GuiMenuSideButton(34, guiLeft - 70, guiTop + 191, 35, 22, "14")); addSideButton(new GuiMenuSideButton(35, guiLeft - 35, guiTop + 191, 35, 22, "15")); - if (activeFolder == null && activeTab >= 1 && activeTab <= 15) { + if (activeTab >= 1 && activeTab <= 15) { GuiMenuSideButton active = getSideButton(20 + activeTab); if (active != null) active.active = true; } } - private void initFolderSideButtons() { - folderSideButtons.clear(); - for (int i = 0; i < folderNames.size(); i++) { - String name = folderNames.get(i); - GuiMenuSideButton btn = new GuiMenuSideButton(i, guiLeft - 70, guiTop + 2 + i * 21, 70, 22, name); - if (name.equals(activeFolder)) { - btn.active = true; - } - folderSideButtons.put(i, btn); - } - - // Folder CRUD buttons below visible scroll area (8 slots) - int crudY = guiTop + 2 + 8 * 21; - addButton(new GuiNpcButton(40, guiLeft - 70, crudY, 22, 20, "+")); - if (activeFolder != null) { - addButton(new GuiNpcButton(41, guiLeft - 47, crudY, 28, 20, "Ren")); - GuiNpcButton delBtn = new GuiNpcButton(42, guiLeft - 18, crudY, 22, 20, "-"); - if (ClientCloneController.Instance != null) { - List clones = ClientCloneController.Instance.getClones(activeFolder); - delBtn.enabled = clones == null || clones.isEmpty(); - } - addButton(delBtn); - } - } - // ==================== FILTERS PAGE ==================== private void showFiltersPage() { int baseY = guiTop; @@ -286,11 +263,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust // ==================== CLONE / ENTITY DISPLAY ==================== private void showClones() { - if (activeFolder != null) { - showFolderClones(); - return; - } - if (showingClones == 2) { PacketClient.sendClient(new CloneTagListPacket(activeTab)); PacketClient.sendClient(new CloneListPacket(activeTab)); @@ -308,29 +280,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust scroll.setList(getSearchList(), ascending == 0, sortType == 0); } - private void showFolderClones() { - if (showingClones == 2) { - PacketClient.sendClient(new CloneTagListPacket(activeFolder)); - PacketClient.sendClient(new CloneListPacket(activeFolder)); - return; - } - - if (ClientCloneController.Instance != null) { - if (sortType == 0) { - this.list = ClientCloneController.Instance.getClones(activeFolder); - } else { - this.list = ClientCloneController.Instance.getClonesDate(activeFolder); - } - this.tagMap = ClientTagMapController.Instance.getTagMap(activeFolder); - } else { - this.list = new ArrayList<>(); - this.tagMap = null; - } - - populateRawListWithTags(); - scroll.setList(getSearchList(), ascending == 0, sortType == 0); - } - private void showEntities() { Map data = EntityList.stringToClassMapping; ArrayList list = new ArrayList(); @@ -379,9 +328,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust return null; if (showingClones == 0) { - if (activeFolder != null && ClientCloneController.Instance != null) { - return ClientCloneController.Instance.getCloneData(player, sel, activeFolder); - } return ClientCloneController.Instance.getCloneData(player, sel, activeTab); } else { Entity entity = EntityList.createEntityByName(sel, Minecraft.getMinecraft().theWorld); @@ -492,73 +438,7 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust return list; } - // ==================== RENDERING ==================== - @Override - public void drawScreen(int i, int j, float f) { - super.drawScreen(i, j, f); - - // Render scrollable folder side buttons (folders mode only) - if (sidebarMode == 1 && (showingClones == 0 || showingClones == 2)) { - renderFolderSideButtons(i, j); - } - } - - private void renderFolderSideButtons(int mouseX, int mouseY) { - final float SMOOTHING_FACTOR = 0.1F; - int maxScroll = Math.max(0, folderSideButtons.size() - 8) * 21; - - if (isMouseInFolderScrollZone(mouseX, mouseY)) { - int wheel = Mouse.getDWheel(); - if (wheel != 0) { - destFolderScroll = ValueUtil.clamp( - destFolderScroll - Math.signum(wheel) * 21, -maxScroll, 0); - } - } - - folderScroll += (destFolderScroll - folderScroll) * SMOOTHING_FACTOR; - - for (Map.Entry entry : folderSideButtons.entrySet()) { - int idx = entry.getKey(); - GuiMenuSideButton button = entry.getValue(); - - float rawY = guiTop + 2 + idx * 21 + folderScroll; - int smoothedY = Math.round(rawY); - - if (smoothedY >= guiTop && smoothedY < guiTop + 8 * 21) { - button.yPosition = smoothedY; - button.drawButton(mc, mouseX, mouseY); - } - } - } - - private boolean isMouseInFolderScrollZone(int x, int y) { - return x >= guiLeft - 70 && x <= guiLeft - && y >= guiTop + 2 && y <= guiTop + 2 + 8 * 21; - } - // ==================== MOUSE / KEYBOARD EVENTS ==================== - @Override - public void mouseClicked(int i, int j, int k) { - super.mouseClicked(i, j, k); - - // Handle folder side button clicks (folders mode) - if (sidebarMode == 1 && (showingClones == 0 || showingClones == 2) && k == 0) { - for (GuiMenuSideButton button : new ArrayList<>(folderSideButtons.values())) { - if (button.mousePressed(mc, i, j)) { - folderSideButtonPressed(button); - } - } - } - } - - private void folderSideButtonPressed(GuiMenuSideButton button) { - if (button.active) return; - NoppesUtil.clickSound(); - activeFolder = button.displayString; - activeTab = -1; - initGui(); - } - @Override public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { } @@ -569,17 +449,23 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust public void keyTyped(char c, int i) { super.keyTyped(c, i); - if (getTextField(1) != null) { - if (search.equals(getTextField(1).getText())) - return; - search = getTextField(1).getText().toLowerCase(); - scroll.setList(getSearchList(), ascending == 0, sortType == 0); + + GuiNpcTextField searchField = getTextField(1); + if (searchField != null) { + String newText = searchField.getText().toLowerCase(); + if (!search.equals(newText)) { + search = newText; + scroll.setList(getSearchList(), ascending == 0, sortType == 0); + } } - if (getTextField(2) != null) { - if (tagSearch.equals(getTextField(2).getText())) - return; - tagSearch = getTextField(2).getText().toLowerCase(); - filterScroll.setList(getTagList()); + + GuiNpcTextField tagSearchField = getTextField(2); + if (tagSearchField != null) { + String newText = tagSearchField.getText().toLowerCase(); + if (!tagSearch.equals(newText)) { + tagSearch = newText; + filterScroll.setList(getTagList()); + } } } @@ -598,18 +484,23 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust // Mode buttons if (id == 3) { + savedContentScrollY = 0; showingClones = 0; initGui(); } if (id == 4) { + savedContentScrollY = 0; showingClones = 1; initGui(); } if (id == 5) { + savedContentScrollY = 0; showingClones = 2; + PacketClient.sendClient(new CloneFolderListPacket()); initGui(); } if (id == 16) { + savedContentScrollY = 0; showingClones = 3; initGui(); } @@ -619,33 +510,17 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust // Open fullscreen GUI if (id == 61) { + isFullscreen = true; Minecraft.getMinecraft().displayGuiScreen( new GuiNpcMobSpawnerFullscreen(posX, posY, posZ)); } - // Tabs/Folders toggle - if (id == 60) { - sidebarMode = ((GuiNpcButton) guibutton).getValue(); - if (sidebarMode == 1 && activeFolder == null && !folderNames.isEmpty()) { - activeFolder = folderNames.get(0); - activeTab = -1; - } - if (sidebarMode == 0 && (activeTab < 1 || activeTab > 15)) { - activeTab = 1; - activeFolder = null; - } - initGui(); - } - // Spawn if (id == 1) { if (showingClones == 2) { String sel = scroll.getSelected(); if (sel == null) return; - if (activeFolder != null) - SpawnMobPacket.ServerFolder(posX, posY, posZ, sel, activeFolder); - else - SpawnMobPacket.Server(posX, posY, posZ, sel, activeTab); + SpawnMobPacket.Server(posX, posY, posZ, sel, activeTab); close(); } else { NBTTagCompound compound = getCompound(); @@ -660,10 +535,7 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust if (showingClones == 2) { String sel = scroll.getSelected(); if (sel == null) return; - if (activeFolder != null) - MobSpawnerPacket.ServerFolder(posX, posY, posZ, sel, activeFolder); - else - MobSpawnerPacket.Server(posX, posY, posZ, sel, activeTab); + MobSpawnerPacket.Server(posX, posY, posZ, sel, activeTab); close(); } else { NBTTagCompound compound = getCompound(); @@ -676,26 +548,18 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust // Remove if (id == 6) { if (scroll.getSelected() != null) { + int prevSelected = scroll.selected; if (showingClones == 2) { - if (activeFolder != null) - PacketClient.sendClient(new CloneRemovePacket(activeFolder, scroll.getSelected())); - else - PacketClient.sendClient(new CloneRemovePacket(activeTab, scroll.getSelected())); + PacketClient.sendClient(new CloneRemovePacket(activeTab, scroll.getSelected())); return; } - if (activeFolder != null && ClientCloneController.Instance != null) - ClientCloneController.Instance.removeClone(scroll.getSelected(), activeFolder); - else - ClientCloneController.Instance.removeClone(scroll.getSelected(), activeTab); - scroll.selected = -1; + ClientCloneController.Instance.removeClone(scroll.getSelected(), activeTab); initGui(); - } - } - - // Move - if (id == 50) { - if (scroll.getSelected() != null) { - setSubGui(new SubGuiCloneMove()); + if (scroll.list != null && !scroll.list.isEmpty()) { + scroll.selected = Math.min(prevSelected, scroll.list.size() - 1); + } else { + scroll.selected = -1; + } } } @@ -717,95 +581,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust if (id == 15) { sortType = (byte) ((GuiNpcButton) guibutton).getValue(); } - - // Create folder - if (id == 40) { - setSubGui(new SubGuiCloneFolderName("")); - } - // Rename folder - if (id == 41) { - if (activeFolder != null) - setSubGui(new SubGuiCloneFolderName(activeFolder)); - } - // Delete folder - if (id == 42) { - if (activeFolder != null && ClientCloneController.Instance != null) { - if (showingClones == 2) { - PacketClient.sendClient(new CloneFolderCrudPacket( - CloneFolderCrudPacket.ACTION_DELETE, activeFolder)); - } else { - ClientCloneController.Instance.deleteFolder(activeFolder); - loadFolderNames(); - } - activeFolder = null; - activeTab = 1; - initGui(); - } - } - } - - // ==================== SUB GUI ==================== - @Override - public void subGuiClosed(SubGuiInterface subgui) { - if (subgui instanceof SubGuiCloneFolderName) { - SubGuiCloneFolderName folderGui = (SubGuiCloneFolderName) subgui; - if (folderGui.cancelled) return; - - String newName = folderGui.getFolderName(); - if (newName == null || newName.isEmpty()) return; - - if (folderGui.isRename()) { - if (showingClones == 2) { - PacketClient.sendClient(new CloneFolderCrudPacket( - CloneFolderCrudPacket.ACTION_RENAME, folderGui.getOriginalName(), newName)); - } else if (ClientCloneController.Instance != null) { - ClientCloneController.Instance.renameFolder(folderGui.getOriginalName(), newName); - loadFolderNames(); - } - activeFolder = newName; - activeTab = -1; - } else { - if (showingClones == 2) { - PacketClient.sendClient(new CloneFolderCrudPacket( - CloneFolderCrudPacket.ACTION_CREATE, newName)); - } else if (ClientCloneController.Instance != null) { - ClientCloneController.Instance.createFolder(newName); - loadFolderNames(); - } - activeFolder = newName; - activeTab = -1; - } - initGui(); - } - - if (subgui instanceof SubGuiCloneMove) { - SubGuiCloneMove moveGui = (SubGuiCloneMove) subgui; - if (!moveGui.cancelled) { - String cloneName = scroll.getSelected(); - if (cloneName != null) { - int fromTab = activeFolder != null ? -1 : activeTab; - String fromFolder = activeFolder; - int toTab = moveGui.getDestTab(); - String toFolder = moveGui.getDestFolder(); - - if (showingClones == 2) { - PacketClient.sendClient(new CloneMovePacket( - cloneName, fromTab, fromFolder, toTab, toFolder)); - } else if (ClientCloneController.Instance != null) { - if (fromFolder != null && toFolder != null) { - ClientCloneController.Instance.moveClone(cloneName, fromFolder, toFolder); - } else if (fromFolder != null) { - ClientCloneController.Instance.moveClone(cloneName, fromFolder, toTab); - } else if (toFolder != null) { - ClientCloneController.Instance.moveClone(cloneName, fromTab, toFolder); - } else { - ClientCloneController.Instance.moveClone(cloneName, fromTab, toTab); - } - } - initGui(); - } - } - } } // ==================== DATA FROM SERVER ==================== @@ -816,22 +591,10 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICust @Override public void setGuiData(NBTTagCompound compound) { if (compound.hasKey("CloneFolders")) { - NBTTagList folderList = compound.getTagList("CloneFolders", 10); - folderNames.clear(); - for (int i = 0; i < folderList.tagCount(); i++) { - CloneFolder folder = new CloneFolder(); - folder.readNBT(folderList.getCompoundTagAt(i)); - folderNames.add(folder.name); - } - initGui(); - } else if (compound.hasKey("MoveSuccess")) { + // Minimized doesn't use folders, but still handle the packet initGui(); } else if (compound.hasKey("CloneTags")) { - if (activeFolder != null) { - this.tagMap = new TagMap(activeFolder); - } else { - this.tagMap = new TagMap(activeTab); - } + this.tagMap = new TagMap(activeTab); NBTTagCompound cloneTags = compound.getCompoundTag("CloneTags"); this.tagMap.readNBT(cloneTags); } else if (compound.hasKey("AllTags")) { diff --git a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java index 5199a9051..4ee6a8ff0 100644 --- a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java +++ b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java @@ -3,6 +3,7 @@ package noppes.npcs.client.gui; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.clone.CloneAllTagsPacket; import kamkeel.npcs.network.packets.request.clone.CloneFolderCrudPacket; +import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket; import kamkeel.npcs.network.packets.request.clone.CloneListPacket; import kamkeel.npcs.network.packets.request.clone.CloneMovePacket; import kamkeel.npcs.network.packets.request.clone.CloneRemovePacket; @@ -23,15 +24,12 @@ import noppes.npcs.client.controllers.ClientTagMapController; import noppes.npcs.client.gui.util.GuiCustomScroll; import noppes.npcs.client.gui.util.GuiCustomScrollCloner; import noppes.npcs.client.gui.util.GuiCustomScrollIcons; -import noppes.npcs.client.gui.util.GuiMenuTopButton; -import noppes.npcs.client.gui.util.GuiNPCInterface; +import noppes.npcs.client.gui.util.GuiDirectory; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.IClonerGui; -import noppes.npcs.client.gui.util.ICustomScrollListener; import noppes.npcs.client.gui.util.IGuiData; -import noppes.npcs.client.gui.util.ISubGuiListener; import noppes.npcs.client.gui.util.SubGuiInterface; import noppes.npcs.controllers.data.CloneFolder; import noppes.npcs.controllers.data.Tag; @@ -45,14 +43,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; -public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiData, ICustomScrollListener, ISubGuiListener, IClonerGui { - - // ==================== LAYOUT CONSTANTS ==================== - private static final int PAD = 10; - private static final int LEFT_NAV_W = 120; - private static final int RIGHT_ACT_W = 60; - private static final int TOP_BAR_H = 22; - private static final int GAP = 2; +public class GuiNpcMobSpawnerFullscreen extends GuiDirectory implements IGuiData, IClonerGui { // ==================== INSTANCE STATE ==================== public TagMap tagMap; @@ -71,16 +62,27 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD private List navIcons = new ArrayList<>(); private String navSearch = ""; + // Move workflow state + private int movePhase = 0; // 0=off, 1=selecting clones, 2=selecting destination + private HashSet moveSelection = new HashSet<>(); + // ==================== CONSTRUCTOR ==================== public GuiNpcMobSpawnerFullscreen(int posX, int posY, int posZ) { super(); this.posX = posX; this.posY = posY; this.posZ = posZ; - closeOnEsc = true; - drawDefaultBackground = true; - // No setBackground() call — no menubg.png tiling + + // Configure GuiDirectory layout percentages + leftPanelPercent = 0.15f; + rightPanelPercent = 0.08f; + minLeftPanelW = 120; + minRightPanelW = 62; + PacketClient.sendClient(new CloneAllTagsPacket()); + if (GuiNpcMobSpawner.showingClones == 2) { + PacketClient.sendClient(new CloneFolderListPacket()); + } loadFolderNames(); } @@ -109,83 +111,230 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD return tagMap; } - // ==================== INIT GUI ==================== + // ==================== GuiDirectory HOOKS ==================== @Override public void initGui() { + // Save scroll positions before super.initGui() clears everything + GuiNpcMobSpawner.savedContentScrollY = scroll.scrollY; + GuiNpcMobSpawner.savedNavScrollY = navScroll.scrollY; + super.initGui(); + } - int originX = PAD; - int originY = PAD; - int usableW = width - 2 * PAD; - int usableH = height - 2 * PAD; - - int contentX = originX + LEFT_NAV_W + GAP; - int contentY = originY + TOP_BAR_H + GAP; - int contentW = usableW - LEFT_NAV_W - RIGHT_ACT_W - 3 * GAP; - int contentH = usableH - TOP_BAR_H - GAP; - int rightX = contentX + contentW + GAP; - - // Top bar — mode buttons - GuiMenuTopButton btn; - addTopButton(btn = new GuiMenuTopButton(3, originX, originY, "spawner.clones")); - btn.active = GuiNpcMobSpawner.showingClones == 0; - addTopButton(btn = new GuiMenuTopButton(5, btn, "gui.server")); - btn.active = GuiNpcMobSpawner.showingClones == 2; - addTopButton(btn = new GuiMenuTopButton(4, btn, "spawner.entities")); - btn.active = GuiNpcMobSpawner.showingClones == 1; - - // Top bar — right side - int topRight = originX + usableW; - addTopButton(new GuiMenuTopButton(17, topRight - 22, originY, "X")); - addTopButton(new GuiMenuTopButton(61, topRight - 38, originY, "-")); - addTopButton(btn = new GuiMenuTopButton(16, topRight - 82, originY, "gui.filters")); - btn.active = GuiNpcMobSpawner.showingClones == 3; - + @Override + protected void drawPanels() { if (GuiNpcMobSpawner.showingClones < 3) { - // Search field in top bar - addTextField(new GuiNpcTextField(1, this, fontRendererObj, - contentX, originY + 2, contentW, 20, GuiNpcMobSpawner.search)); - - // Left navigation panel - buildNavList(); - navScroll.clear(); - navScroll.setSize(LEFT_NAV_W, contentH - 44); - navScroll.guiLeft = originX; - navScroll.guiTop = contentY; - navScroll.setListWithIcons(navNames, navIcons); - navScroll.setSelected(getNavSelection()); - addScroll(navScroll); - - // Nav search field - addTextField(new GuiNpcTextField(3, this, fontRendererObj, - originX, contentY + contentH - 42, LEFT_NAV_W, 20, navSearch)); - - // Add folder button - addButton(new GuiNpcButton(40, originX, contentY + contentH - 20, LEFT_NAV_W, 20, "+ Add Folder")); - - // Center content — clone/entity list - scroll.clear(); - scroll.setSize(contentW, contentH); - scroll.guiLeft = contentX; - scroll.guiTop = contentY; - addScroll(scroll); - - // Right action panel - int btnW = RIGHT_ACT_W; - int btnH = 20; - int btnGap = 2; - addButton(new GuiNpcButton(1, rightX, contentY, btnW, btnH, "item.monsterPlacer.name")); - addButton(new GuiNpcButton(2, rightX, contentY + btnH + btnGap, btnW, btnH, "spawner.mobspawner")); - - if (GuiNpcMobSpawner.showingClones == 0 || GuiNpcMobSpawner.showingClones == 2) { - addButton(new GuiNpcButton(6, rightX, contentY + 2 * (btnH + btnGap), btnW, btnH, "gui.remove")); - addButton(new GuiNpcButton(50, rightX, contentY + 3 * (btnH + btnGap), btnW, btnH, "Move")); - showClones(); - } else { - showEntities(); - } + super.drawPanels(); } else { + // Filters page: only draw top bar, no side panels + } + } + + @Override + protected void initTopBar(int topBtnY) { + int topBtnW = 55; + int x = originX + 2; + + GuiNpcButton clonesBtn = new GuiNpcButton(3, x, topBtnY, topBtnW, btnH, "spawner.clones"); + clonesBtn.enabled = GuiNpcMobSpawner.showingClones != 0; + addButton(clonesBtn); + x += topBtnW + 2; + + GuiNpcButton serverBtn = new GuiNpcButton(5, x, topBtnY, topBtnW, btnH, "gui.server"); + serverBtn.enabled = GuiNpcMobSpawner.showingClones != 2; + addButton(serverBtn); + x += topBtnW + 2; + + GuiNpcButton entitiesBtn = new GuiNpcButton(4, x, topBtnY, topBtnW, btnH, "spawner.entities"); + entitiesBtn.enabled = GuiNpcMobSpawner.showingClones != 1; + addButton(entitiesBtn); + + // Top bar right side + int closeX = originX + usableW - btnH - 2; + addButton(new GuiNpcButton(17, closeX, topBtnY, btnH, btnH, "X")); + int minimizeX = closeX - btnH - 2; + addButton(new GuiNpcButton(61, minimizeX, topBtnY, btnH, btnH, "-")); + int filterBtnW = 50; + int filterX = minimizeX - filterBtnW - 2; + GuiNpcButton filterBtn = new GuiNpcButton(16, filterX, topBtnY, filterBtnW, btnH, "gui.filters"); + filterBtn.enabled = GuiNpcMobSpawner.showingClones != 3; + addButton(filterBtn); + + // Clone search field in top bar (only when not on filters page) + if (GuiNpcMobSpawner.showingClones < 3) { + int searchX = x + topBtnW + 6; + int searchW = filterX - searchX - 4; + if (searchW > 20) { + addTextField(new GuiNpcTextField(1, this, fontRendererObj, + searchX, topBtnY, searchW, btnH, GuiNpcMobSpawner.search)); + } + } + + // Disable mode buttons during move + if (movePhase > 0) { + clonesBtn.enabled = false; + serverBtn.enabled = false; + entitiesBtn.enabled = false; + filterBtn.enabled = false; + } + } + + @Override + protected void initLeftPanel() { + if (GuiNpcMobSpawner.showingClones >= 3) return; + + boolean folderSelected = GuiNpcMobSpawner.activeFolder != null; + int bottomRows = folderSelected ? 3 : 2; + int bottomH = bottomRows * (btnH + gap); + int navH = contentH - bottomH; + buildNavList(); + + int savedNavScroll = GuiNpcMobSpawner.savedNavScrollY; + navScroll.clear(); + navScroll.colors.clear(); + navScroll.setSize(leftPanelW, navH); + navScroll.guiLeft = originX; + navScroll.guiTop = contentY; + navScroll.setListWithIcons(navNames, navIcons); + navScroll.setSelected(getNavSelection()); + navScroll.scrollY = Math.min(savedNavScroll, Math.max(0, navScroll.maxScrollY)); + + // Phase 2: color nav items orange (selectable) or red (current location, not selectable) + if (movePhase == 2) { + String currentNav = getNavSelection(); + for (String name : navNames) { + navScroll.colors.put(name, name.equals(currentNav) ? 0xFF5555 : 0xFFAA00); + } + } + + addScroll(navScroll); + + // Nav search field below the scroll + int navSearchY = contentY + navH + gap; + addTextField(new GuiNpcTextField(3, this, fontRendererObj, + originX, navSearchY, leftPanelW, btnH, navSearch)); + + // Add folder button + int addFolderY = navSearchY + btnH + gap; + GuiNpcButton addFolderBtn = new GuiNpcButton(40, originX, addFolderY, leftPanelW, btnH, "+ Add Folder"); + if (movePhase > 0) addFolderBtn.enabled = false; + addButton(addFolderBtn); + + // Rename/Delete folder buttons (only when a folder is selected) + if (folderSelected) { + int folderCrudY = addFolderY + btnH + gap; + int halfW = (leftPanelW - gap) / 2; + GuiNpcButton renameBtn = new GuiNpcButton(41, originX, folderCrudY, halfW, btnH, "Rename"); + if (movePhase > 0) renameBtn.enabled = false; + addButton(renameBtn); + GuiNpcButton delBtn = new GuiNpcButton(42, originX + halfW + gap, folderCrudY, halfW, btnH, "gui.remove"); + if (ClientCloneController.Instance != null) { + List clones = ClientCloneController.Instance.getClones(GuiNpcMobSpawner.activeFolder); + delBtn.enabled = clones == null || clones.isEmpty(); + } + if (movePhase > 0) delBtn.enabled = false; + addButton(delBtn); + } + } + + @Override + protected void initCenterPanel() { + if (GuiNpcMobSpawner.showingClones >= 3) { showFiltersPage(); + return; + } + + int savedScroll = GuiNpcMobSpawner.savedContentScrollY; + scroll.clear(); + scroll.setSize(contentW, contentH); + scroll.guiLeft = contentX; + scroll.guiTop = contentY; + addScroll(scroll); + + if (GuiNpcMobSpawner.showingClones == 0 || GuiNpcMobSpawner.showingClones == 2) { + // Always clear colors — only phase 2 re-populates them + scroll.colors.clear(); + + // Multi-select mode during move phase 1 + if (movePhase == 1) { + scroll.multipleSelection = true; + scroll.setSelectable(true); + scroll.setSelectedList(moveSelection); + } else if (movePhase == 2) { + // Phase 2: show selection as yellow, freeze scroll + scroll.multipleSelection = true; + scroll.setSelectedList(moveSelection); + scroll.setSelectable(false); + } else { + scroll.multipleSelection = false; + scroll.setSelectable(true); + } + + showClones(); + + // Phase 2: color selected clones yellow + if (movePhase == 2) { + for (String name : moveSelection) { + scroll.colors.put(name, 0xFFFF55); + } + } + + // Restore scroll position after list is populated + scroll.scrollY = Math.min(savedScroll, Math.max(0, scroll.maxScrollY)); + } else { + showEntities(); + scroll.scrollY = Math.min(savedScroll, Math.max(0, scroll.maxScrollY)); + } + } + + @Override + protected void initRightPanel(int startY) { + if (GuiNpcMobSpawner.showingClones >= 3) return; + + int btnY = startY; + int btnGap = 4; + + GuiNpcButton spawnBtn = new GuiNpcButton(1, rightX, btnY, rightPanelW, btnH, "item.monsterPlacer.name"); + if (movePhase > 0) spawnBtn.enabled = false; + addButton(spawnBtn); + btnY += btnH + btnGap; + + GuiNpcButton spawnerBtn = new GuiNpcButton(2, rightX, btnY, rightPanelW, btnH, "spawner.mobspawner"); + if (movePhase > 0) spawnerBtn.enabled = false; + addButton(spawnerBtn); + btnY += btnH + btnGap; + + if (GuiNpcMobSpawner.showingClones == 0 || GuiNpcMobSpawner.showingClones == 2) { + GuiNpcButton removeBtn = new GuiNpcButton(6, rightX, btnY, rightPanelW, btnH, "gui.remove"); + if (movePhase > 0) removeBtn.enabled = false; + addButton(removeBtn); + btnY += btnH + btnGap; + + // Move button + String moveLabel = movePhase > 0 ? "Moving" : "Move"; + addButton(new GuiNpcButton(50, rightX, btnY, rightPanelW, btnH, moveLabel)); + btnY += btnH + btnGap; + + // Confirm button (only visible during phase 1) + if (movePhase == 1) { + GuiNpcButton confirmBtn = new GuiNpcButton(51, rightX, btnY, rightPanelW, btnH, "Confirm"); + confirmBtn.enabled = !moveSelection.isEmpty(); + addButton(confirmBtn); + } + } + } + + // ==================== OVERLAY DRAWING ==================== + @Override + protected void drawOverlay(int mouseX, int mouseY, float partialTicks) { + if (movePhase == 1) { + String text = "Select Clones to Move"; + int textW = fontRendererObj.getStringWidth(text); + fontRendererObj.drawStringWithShadow(text, width / 2 - textW / 2, height - pad - 10, 0x55FF55); + } else if (movePhase == 2) { + String text = "Select a Tab or Folder"; + int textW = fontRendererObj.getStringWidth(text); + fontRendererObj.drawStringWithShadow(text, width / 2 - textW / 2, height - pad - 10, 0xFF5555); } } @@ -222,7 +371,6 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD private void handleNavClick(String selected) { if (selected == null) return; - // Check if it's a tab if (selected.startsWith("Tab ")) { try { int tabNum = Integer.parseInt(selected.substring(4)); @@ -236,7 +384,6 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD } } - // It's a folder name GuiNpcMobSpawner.activeFolder = selected; GuiNpcMobSpawner.activeTab = -1; initGui(); @@ -244,55 +391,58 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD // ==================== FILTERS PAGE ==================== private void showFiltersPage() { - int originX = PAD; - int originY = PAD; - int baseY = originY + TOP_BAR_H + GAP; + int scrollW = Math.min(usableW / 2 - gap * 2, 240); + int scrollX = originX + gap; + int scrollTopY = contentY + 16; + int scrollH = contentH - 16 - btnH - gap * 2; addLabel(new GuiNpcLabel(1, StatCollector.translateToLocal("cloner.tagFilters"), - originX + 7, baseY + 7)); + scrollX + 2, contentY + 4, 0xFFFFFF)); filterScroll.clear(); - filterScroll.setSize(140, 166); - filterScroll.guiLeft = originX + 4; - filterScroll.guiTop = baseY + 19; + filterScroll.setSize(scrollW, scrollH); + filterScroll.guiLeft = scrollX; + filterScroll.guiTop = scrollTopY; filterScroll.multipleSelection = true; filterScroll.setSelectedList(GuiNpcMobSpawner.tagFilters); addScroll(filterScroll); addTextField(new GuiNpcTextField(2, this, fontRendererObj, - originX + 4, baseY + 190, 140, 20, GuiNpcMobSpawner.tagSearch)); + scrollX, scrollTopY + scrollH + gap, scrollW, btnH, GuiNpcMobSpawner.tagSearch)); + + // Settings column + int col2X = scrollX + scrollW + gap * 4; + int controlW = 90; + int controlX = col2X + 80; + int rowH = 28; + int rowY = scrollTopY; addLabel(new GuiNpcLabel(2, StatCollector.translateToLocal("cloner.tagVisibility"), - originX + 150, baseY + 27)); - addButton(new GuiNpcButton(12, originX + 215, baseY + 20, + col2X, rowY + 5, 0xFFFFFF)); + addButton(new GuiNpcButton(12, controlX, rowY, controlW, btnH, new String[]{"display.show", "display.all", "display.hide"}, GuiNpcMobSpawner.displayTags)); + rowY += rowH; addLabel(new GuiNpcLabel(3, StatCollector.translateToLocal("filter.contains"), - originX + 150, baseY + 50)); - addButton(new GuiNpcButton(13, originX + 215, baseY + 43, + col2X, rowY + 5, 0xFFFFFF)); + addButton(new GuiNpcButton(13, controlX, rowY, controlW, btnH, new String[]{"filter.any", "filter.all", "filter.notany", "filter.notall"}, GuiNpcMobSpawner.filterCondition)); - addButton(new GuiNpcButton(11, originX + 150, baseY + 66, 130, 20, "gui.deselectAll")); + rowY += rowH + 4; + addButton(new GuiNpcButton(11, col2X, rowY, controlW + 80, btnH, "gui.deselectAll")); + rowY += rowH + 12; addLabel(new GuiNpcLabel(4, StatCollector.translateToLocal("cloner.order"), - originX + 150, baseY + 112)); - addButton(new GuiNpcButton(14, originX + 215, baseY + 105, + col2X, rowY + 5, 0xFFFFFF)); + addButton(new GuiNpcButton(14, controlX, rowY, controlW, btnH, new String[]{"cloner.ascending", "cloner.descending"}, GuiNpcMobSpawner.ascending)); + rowY += rowH; addLabel(new GuiNpcLabel(5, StatCollector.translateToLocal("cloner.type"), - originX + 150, baseY + 135)); - addButton(new GuiNpcButton(15, originX + 215, baseY + 128, + col2X, rowY + 5, 0xFFFFFF)); + addButton(new GuiNpcButton(15, controlX, rowY, controlW, btnH, new String[]{"cloner.name", "cloner.date"}, GuiNpcMobSpawner.sortType)); - getButton(12).width = 65; - getButton(12).height = 20; - getButton(13).width = 65; - getButton(13).height = 20; - getButton(14).width = 65; - getButton(14).height = 20; - getButton(15).width = 65; - getButton(15).height = 20; - filterScroll.setList(getTagList()); } @@ -505,8 +655,47 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD // ==================== SCROLL EVENTS ==================== @Override public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { + // Phase 2: ignore clicks on the center clone scroll + if (guiCustomScroll.id == 0 && movePhase == 2) { + return; + } + + if (guiCustomScroll.id == 0 && movePhase == 1) { + moveSelection = scroll.getSelectedList(); + GuiNpcButton confirmBtn = getButton(51); + if (confirmBtn != null) { + confirmBtn.enabled = !moveSelection.isEmpty(); + } + return; + } + if (guiCustomScroll.id == 2) { String selected = navScroll.getSelected(); + if (selected == null) return; + + if (movePhase == 2) { + // Cannot move to the same location + if (selected.equals(getNavSelection())) { + return; + } + // Selecting destination from nav + if (selected.startsWith("Tab ")) { + try { + int tabNum = Integer.parseInt(selected.substring(4)); + setSubGui(new SubGuiMoveConfirm(moveSelection.size(), tabNum, null)); + } catch (NumberFormatException ignored) { + } + } else { + setSubGui(new SubGuiMoveConfirm(moveSelection.size(), -1, selected)); + } + return; + } + + if (movePhase == 1) { + movePhase = 0; + moveSelection.clear(); + } + handleNavClick(selected); } } @@ -516,22 +705,31 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD } // ==================== KEYBOARD ==================== + @Override public void keyTyped(char c, int i) { super.keyTyped(c, i); - if (getTextField(1) != null) { - if (GuiNpcMobSpawner.search.equals(getTextField(1).getText())) - return; - GuiNpcMobSpawner.search = getTextField(1).getText().toLowerCase(); - scroll.setList(getSearchList(), GuiNpcMobSpawner.ascending == 0, GuiNpcMobSpawner.sortType == 0); + + GuiNpcTextField searchField = getTextField(1); + if (searchField != null) { + String newText = searchField.getText().toLowerCase(); + if (!GuiNpcMobSpawner.search.equals(newText)) { + GuiNpcMobSpawner.search = newText; + scroll.setList(getSearchList(), GuiNpcMobSpawner.ascending == 0, GuiNpcMobSpawner.sortType == 0); + } } - if (getTextField(2) != null) { - if (GuiNpcMobSpawner.tagSearch.equals(getTextField(2).getText())) - return; - GuiNpcMobSpawner.tagSearch = getTextField(2).getText().toLowerCase(); - filterScroll.setList(getTagList()); + + GuiNpcTextField tagSearchField = getTextField(2); + if (tagSearchField != null) { + String newText = tagSearchField.getText().toLowerCase(); + if (!GuiNpcMobSpawner.tagSearch.equals(newText)) { + GuiNpcMobSpawner.tagSearch = newText; + filterScroll.setList(getTagList()); + } } - if (getTextField(3) != null) { - String newNavSearch = getTextField(3).getText().toLowerCase(); + + GuiNpcTextField navSearchField = getTextField(3); + if (navSearchField != null) { + String newNavSearch = navSearchField.getText().toLowerCase(); if (!navSearch.equals(newNavSearch)) { navSearch = newNavSearch; buildNavList(); @@ -546,20 +744,37 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD protected void actionPerformed(GuiButton guibutton) { int id = guibutton.id; - // Mode buttons if (id == 3) { + movePhase = 0; + moveSelection.clear(); + GuiNpcMobSpawner.savedContentScrollY = 0; + GuiNpcMobSpawner.savedNavScrollY = 0; GuiNpcMobSpawner.showingClones = 0; + loadFolderNames(); initGui(); } if (id == 4) { + movePhase = 0; + moveSelection.clear(); + GuiNpcMobSpawner.savedContentScrollY = 0; + GuiNpcMobSpawner.savedNavScrollY = 0; GuiNpcMobSpawner.showingClones = 1; initGui(); } if (id == 5) { + movePhase = 0; + moveSelection.clear(); + GuiNpcMobSpawner.savedContentScrollY = 0; + GuiNpcMobSpawner.savedNavScrollY = 0; GuiNpcMobSpawner.showingClones = 2; + PacketClient.sendClient(new CloneFolderListPacket()); initGui(); } if (id == 16) { + movePhase = 0; + moveSelection.clear(); + GuiNpcMobSpawner.savedContentScrollY = 0; + GuiNpcMobSpawner.savedNavScrollY = 0; GuiNpcMobSpawner.showingClones = 3; initGui(); } @@ -569,6 +784,7 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD // Minimize — return to normal GUI if (id == 61) { + GuiNpcMobSpawner.isFullscreen = false; Minecraft.getMinecraft().displayGuiScreen( new GuiNpcMobSpawner(posX, posY, posZ)); } @@ -612,6 +828,7 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD // Remove if (id == 6) { if (scroll.getSelected() != null) { + int prevSelected = scroll.selected; if (GuiNpcMobSpawner.showingClones == 2) { if (GuiNpcMobSpawner.activeFolder != null) PacketClient.sendClient(new CloneRemovePacket(GuiNpcMobSpawner.activeFolder, scroll.getSelected())); @@ -623,15 +840,31 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD ClientCloneController.Instance.removeClone(scroll.getSelected(), GuiNpcMobSpawner.activeFolder); else ClientCloneController.Instance.removeClone(scroll.getSelected(), GuiNpcMobSpawner.activeTab); - scroll.selected = -1; initGui(); + if (scroll.list != null && !scroll.list.isEmpty()) { + scroll.selected = Math.min(prevSelected, scroll.list.size() - 1); + } else { + scroll.selected = -1; + } } } - // Move + // Move toggle if (id == 50) { - if (scroll.getSelected() != null) { - setSubGui(new SubGuiCloneMove()); + if (movePhase == 0) { + movePhase = 1; + moveSelection.clear(); + } else { + movePhase = 0; + moveSelection.clear(); + } + initGui(); + } + // Confirm move selection + if (id == 51) { + if (movePhase == 1 && !moveSelection.isEmpty()) { + movePhase = 2; + initGui(); } } @@ -658,6 +891,26 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD if (id == 40) { setSubGui(new SubGuiCloneFolderName("")); } + // Rename folder + if (id == 41) { + if (GuiNpcMobSpawner.activeFolder != null) + setSubGui(new SubGuiCloneFolderName(GuiNpcMobSpawner.activeFolder)); + } + // Delete folder + if (id == 42) { + if (GuiNpcMobSpawner.activeFolder != null) { + if (GuiNpcMobSpawner.showingClones == 2) { + PacketClient.sendClient(new CloneFolderCrudPacket( + CloneFolderCrudPacket.ACTION_DELETE, GuiNpcMobSpawner.activeFolder)); + } else if (ClientCloneController.Instance != null) { + ClientCloneController.Instance.deleteFolder(GuiNpcMobSpawner.activeFolder); + loadFolderNames(); + } + GuiNpcMobSpawner.activeFolder = null; + GuiNpcMobSpawner.activeTab = 1; + initGui(); + } + } } // ==================== SUB GUI ==================== @@ -694,16 +947,15 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD initGui(); } - if (subgui instanceof SubGuiCloneMove) { - SubGuiCloneMove moveGui = (SubGuiCloneMove) subgui; - if (!moveGui.cancelled) { - String cloneName = scroll.getSelected(); - if (cloneName != null) { - int fromTab = GuiNpcMobSpawner.activeFolder != null ? -1 : GuiNpcMobSpawner.activeTab; - String fromFolder = GuiNpcMobSpawner.activeFolder; - int toTab = moveGui.getDestTab(); - String toFolder = moveGui.getDestFolder(); + if (subgui instanceof SubGuiMoveConfirm) { + SubGuiMoveConfirm confirm = (SubGuiMoveConfirm) subgui; + if (confirm.confirmed && !moveSelection.isEmpty()) { + int fromTab = GuiNpcMobSpawner.activeFolder != null ? -1 : GuiNpcMobSpawner.activeTab; + String fromFolder = GuiNpcMobSpawner.activeFolder; + int toTab = confirm.destTab; + String toFolder = confirm.destFolder; + for (String cloneName : moveSelection) { if (GuiNpcMobSpawner.showingClones == 2) { PacketClient.sendClient(new CloneMovePacket( cloneName, fromTab, fromFolder, toTab, toFolder)); @@ -718,9 +970,11 @@ public class GuiNpcMobSpawnerFullscreen extends GuiNPCInterface implements IGuiD ClientCloneController.Instance.moveClone(cloneName, fromTab, toTab); } } - initGui(); } } + movePhase = 0; + moveSelection.clear(); + initGui(); } } diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiCloneMove.java b/src/main/java/noppes/npcs/client/gui/SubGuiCloneMove.java deleted file mode 100644 index 5f2575cd6..000000000 --- a/src/main/java/noppes/npcs/client/gui/SubGuiCloneMove.java +++ /dev/null @@ -1,100 +0,0 @@ -package noppes.npcs.client.gui; - -import net.minecraft.client.gui.GuiButton; -import noppes.npcs.client.controllers.ClientCloneController; -import noppes.npcs.client.gui.util.GuiButtonBiDirectional; -import noppes.npcs.client.gui.util.GuiNpcButton; -import noppes.npcs.client.gui.util.GuiNpcLabel; -import noppes.npcs.client.gui.util.SubGuiInterface; -import noppes.npcs.controllers.data.CloneFolder; - -import java.util.ArrayList; - -public class SubGuiCloneMove extends SubGuiInterface { - public boolean cancelled = true; - private int destTab = 1; - private String destFolder = null; - - private int[] tabValues; - private String[] folderValues; - - public SubGuiCloneMove() { - xSize = 200; - ySize = 80; - closeOnEsc = true; - } - - @Override - public void initGui() { - super.initGui(); - - ArrayList labels = new ArrayList<>(); - ArrayList tabs = new ArrayList<>(); - ArrayList folders = new ArrayList<>(); - - for (int i = 1; i <= 15; i++) { - labels.add("Tab " + i); - tabs.add(i); - folders.add(null); - } - - if (ClientCloneController.Instance != null) { - for (CloneFolder f : ClientCloneController.Instance.getFolderList()) { - labels.add(f.name); - tabs.add(-1); - folders.add(f.name); - } - } - - tabValues = new int[tabs.size()]; - for (int i = 0; i < tabs.size(); i++) tabValues[i] = tabs.get(i); - folderValues = folders.toArray(new String[0]); - - String[] labelArr = labels.toArray(new String[0]); - int selectedIndex = 0; - if (destFolder != null) { - for (int i = 0; i < folderValues.length; i++) { - if (destFolder.equals(folderValues[i])) { - selectedIndex = i; - break; - } - } - } else if (destTab >= 1 && destTab <= 15) { - selectedIndex = destTab - 1; - } - - addLabel(new GuiNpcLabel(0, "Move to:", guiLeft + 5, guiTop + 5)); - addButton(new GuiButtonBiDirectional(2, guiLeft + 5, guiTop + 20, 190, 20, labelArr, selectedIndex)); - addButton(new GuiNpcButton(0, guiLeft + 5, guiTop + 50, 90, 20, "gui.done")); - addButton(new GuiNpcButton(1, guiLeft + 100, guiTop + 50, 90, 20, "gui.cancel")); - } - - @Override - protected void actionPerformed(GuiButton guibutton) { - if (guibutton.id == 0) { - int index = getButton(2).getValue(); - if (index >= 0 && index < tabValues.length) { - destTab = tabValues[index]; - destFolder = folderValues[index]; - } - cancelled = false; - close(); - } - if (guibutton.id == 1) { - cancelled = true; - close(); - } - } - - public int getDestTab() { - return destTab; - } - - public String getDestFolder() { - return destFolder; - } - - @Override - public void save() { - } -} diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiMoveConfirm.java b/src/main/java/noppes/npcs/client/gui/SubGuiMoveConfirm.java new file mode 100644 index 000000000..ee70367f7 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/SubGuiMoveConfirm.java @@ -0,0 +1,57 @@ +package noppes.npcs.client.gui; + +import net.minecraft.client.gui.GuiButton; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; +import noppes.npcs.client.gui.util.SubGuiInterface; + +public class SubGuiMoveConfirm extends SubGuiInterface { + public boolean confirmed = false; + + private final int count; + public final int destTab; + public final String destFolder; + + public SubGuiMoveConfirm(int count, int destTab, String destFolder) { + this.count = count; + this.destTab = destTab; + this.destFolder = destFolder; + xSize = 220; + ySize = 90; + closeOnEsc = true; + } + + @Override + public void initGui() { + super.initGui(); + + String line1 = "Move " + count + " clone" + (count != 1 ? "s" : "") + " to:"; + addLabel(new GuiNpcLabel(0, line1, guiLeft + 10, guiTop + 10, 0xFFFFFF)); + + String dest; + if (destFolder != null) { + dest = "Folder: \"" + destFolder + "\""; + } else { + dest = "Tab: " + destTab; + } + addLabel(new GuiNpcLabel(1, " " + dest, guiLeft + 10, guiTop + 25, 0xFFFFFF)); + + addButton(new GuiNpcButton(0, guiLeft + 10, guiTop + 55, 95, 20, "gui.yes")); + addButton(new GuiNpcButton(1, guiLeft + 115, guiTop + 55, 95, 20, "gui.cancel")); + } + + @Override + protected void actionPerformed(GuiButton guibutton) { + if (guibutton.id == 0) { + confirmed = true; + close(); + } + if (guibutton.id == 1) { + close(); + } + } + + @Override + public void save() { + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScroll.java b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScroll.java index 3f3ca9223..e726239b1 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScroll.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScroll.java @@ -367,6 +367,13 @@ public class GuiCustomScroll extends GuiScreen { return this; } + public void setSelectable(boolean selectable) { + this.selectable = selectable; + if (!selectable) { + hover = -1; + } + } + public boolean isMouseOver(int x, int y) { return x >= this.guiLeft && x <= this.guiLeft + this.xSize && y >= this.guiTop && y <= this.guiTop + this.ySize; } diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollCloner.java b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollCloner.java index ef0c48ae9..b147a3af9 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollCloner.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollCloner.java @@ -39,16 +39,17 @@ public class GuiCustomScrollCloner extends GuiCustomScroll { text += "..."; } else text = displayString; - if ((multipleSelection && selectedList.contains(text)) || (!multipleSelection && selected == i)) { + int itemColor = this.colors.getOrDefault(list.get(i), 0xffffff); + if ((multipleSelection && selectedList.contains(list.get(i))) || (!multipleSelection && selected == i)) { drawVerticalLine(j - 2, k - 4, k + 10, 0xffffffff); drawVerticalLine(j + xSize - 18 + xOffset, k - 4, k + 10, 0xffffffff); drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k - 3, 0xffffffff); drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k + 10, 0xffffffff); - fontRendererObj.drawString(text, j, k, this.colors.getOrDefault(text, 0xffffff)); + fontRendererObj.drawString(text, j, k, itemColor); } else if (i == hover) { fontRendererObj.drawString(text, j, k, 0x00ff00); } else { - fontRendererObj.drawString(text, j, k, this.colors.getOrDefault(text, 0xffffff)); + fontRendererObj.drawString(text, j, k, itemColor); } if ((parent.getShowingClones() == 0 || parent.getShowingClones() == 2) && parent.getTagMap() != null) { diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollIcons.java b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollIcons.java index d2b2a64b8..78686dac0 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollIcons.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollIcons.java @@ -1,7 +1,11 @@ package noppes.npcs.client.gui.util; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.StatCollector; +import org.lwjgl.opengl.GL11; import java.util.ArrayList; import java.util.List; @@ -11,9 +15,11 @@ public class GuiCustomScrollIcons extends GuiCustomScroll { public static final int ICON_TAB = 1; public static final int ICON_FOLDER = 2; - private static final int ICON_WIDTH = 14; - private static final int FOLDER_COLOR = 0xFFDDAA00; - private static final int TAB_COLOR = 0xFF6688AA; + private static final ResourceLocation TAB_TEXTURE = new ResourceLocation("customnpcs", "textures/gui/cloner/tab.png"); + private static final ResourceLocation FOLDER_TEXTURE = new ResourceLocation("customnpcs", "textures/gui/cloner/folder.png"); + + private static final int ICON_RENDER_SIZE = 10; + private static final int ICON_PADDING = 14; private List iconTypes = new ArrayList<>(); @@ -28,6 +34,7 @@ public class GuiCustomScrollIcons extends GuiCustomScroll { @Override protected void drawItems() { + Minecraft mc = Minecraft.getMinecraft(); for (int i = 0; i < list.size(); i++) { int j = 4; int k = (14 * i + 4) - scrollY; @@ -37,20 +44,24 @@ public class GuiCustomScrollIcons extends GuiCustomScroll { int iconType = (i < iconTypes.size()) ? iconTypes.get(i) : ICON_NONE; int textX = j; if (iconType != ICON_NONE) { - textX = j + ICON_WIDTH; + textX = j + ICON_PADDING; } - // Draw icon - if (iconType == ICON_FOLDER) { - drawRect(j, k - 1, j + 10, k + 7, FOLDER_COLOR); - } else if (iconType == ICON_TAB) { - drawRect(j, k - 1, j + 10, k + 7, TAB_COLOR); + // Draw icon texture + if (iconType == ICON_FOLDER || iconType == ICON_TAB) { + ResourceLocation tex = (iconType == ICON_FOLDER) ? FOLDER_TEXTURE : TAB_TEXTURE; + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + mc.getTextureManager().bindTexture(tex); + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + drawFullTexture(j, k - 1, ICON_RENDER_SIZE, ICON_RENDER_SIZE); + GL11.glDisable(GL11.GL_BLEND); } // Draw text String displayString = StatCollector.translateToLocal(list.get(i)); String text = ""; - float maxWidth = (xSize + xOffset - 8 - (iconType != ICON_NONE ? ICON_WIDTH : 0)) * 0.8f; + float maxWidth = (xSize + xOffset - 8 - (iconType != ICON_NONE ? ICON_PADDING : 0)) * 0.8f; if (fontRendererObj.getStringWidth(displayString) > maxWidth) { for (int h = 0; h < displayString.length(); h++) { char c = displayString.charAt(h); @@ -64,18 +75,34 @@ public class GuiCustomScrollIcons extends GuiCustomScroll { text = displayString; } + int itemColor = this.colors.getOrDefault(list.get(i), 0xffffff); + if ((multipleSelection && selectedList.contains(list.get(i))) || (!multipleSelection && selected == i)) { drawVerticalLine(j - 2, k - 4, k + 10, 0xffffffff); drawVerticalLine(j + xSize - 18 + xOffset, k - 4, k + 10, 0xffffffff); drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k - 3, 0xffffffff); drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k + 10, 0xffffffff); - fontRendererObj.drawString(text, textX, k, 0xffffff); + fontRendererObj.drawString(text, textX, k, itemColor); } else if (i == hover) { fontRendererObj.drawString(text, textX, k, 0x00ff00); } else { - fontRendererObj.drawString(text, textX, k, 0xffffff); + fontRendererObj.drawString(text, textX, k, itemColor); } } } } + + /** + * Draw a full texture (UV 0,0 to 1,1) scaled to the given width/height. + * Works with any texture size (not just 256x256). + */ + private void drawFullTexture(int x, int y, int w, int h) { + Tessellator tessellator = Tessellator.instance; + tessellator.startDrawingQuads(); + tessellator.addVertexWithUV(x, y + h, 0, 0, 1); + tessellator.addVertexWithUV(x + w, y + h, 0, 1, 1); + tessellator.addVertexWithUV(x + w, y, 0, 1, 0); + tessellator.addVertexWithUV(x, y, 0, 0, 0); + tessellator.draw(); + } } diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java b/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java new file mode 100644 index 000000000..750c60668 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java @@ -0,0 +1,206 @@ +package noppes.npcs.client.gui.util; + +/** + * Abstract base class for fullscreen directory-browsing GUIs. + * Provides a configurable three-panel layout (left nav, center content, right actions) + * with percentage-based sizing, panel backgrounds/borders, and an optional resizable divider. + * + * Subclasses override abstract hooks to populate each panel region. + * Designed for reuse across Cloner, Quest Manager, Dialog Manager, etc. + */ +public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScrollListener, ISubGuiListener { + + // ===== LAYOUT CONFIG (subclasses may override in constructor) ===== + protected float leftPanelPercent = 0.20f; + protected float rightPanelPercent = 0.10f; + protected int minLeftPanelW = 80; + protected int minRightPanelW = 40; + protected int minCenterW = 100; + + // ===== LAYOUT CONSTANTS ===== + protected int pad = 10; + protected int topBarH = 24; + protected int gap = 4; + protected int btnH = 20; + + // ===== PANEL COLORS ===== + protected int panelBg = 0xC0101010; + protected int panelBorder = 0xFF333333; + protected int topBarBg = 0xC0181818; + + // ===== COMPUTED LAYOUT (populated by computeLayout) ===== + protected int originX, originY, usableW, usableH; + protected int leftPanelW, rightPanelW; + protected int contentX, contentY, contentW, contentH; + protected int rightX; + + // ===== OPTIONAL RESIZABLE DIVIDER (between left and center) ===== + protected boolean enableDivider = false; + protected int dividerWidth = 5; + protected int dividerLineHeight = 20; + protected int minDividerPanelW = 50; + private int dividerOffset; + private boolean isDragging = false; + private int dragStartX; + + // ===== CONSTRUCTOR ===== + public GuiDirectory() { + super(); + closeOnEsc = true; + drawDefaultBackground = true; + } + + // ===== LAYOUT COMPUTATION ===== + protected void computeLayout() { + originX = pad; + originY = pad; + usableW = width - 2 * pad; + usableH = height - 2 * pad; + + leftPanelW = Math.max(minLeftPanelW, (int)(usableW * leftPanelPercent)); + rightPanelW = Math.max(minRightPanelW, (int)(usableW * rightPanelPercent)); + + if (rightPanelPercent <= 0) { + rightPanelW = 0; + } + + int gapCount = rightPanelW > 0 ? 3 : 2; + contentW = usableW - leftPanelW - rightPanelW - gapCount * gap; + if (contentW < minCenterW) { + contentW = minCenterW; + } + + contentX = originX + leftPanelW + gap; + contentY = originY + topBarH + gap; + contentH = usableH - topBarH - gap; + rightX = contentX + contentW + gap; + + if (enableDivider && dividerOffset == 0) { + dividerOffset = leftPanelW; + } + } + + // ===== INIT GUI ===== + @Override + public void initGui() { + super.initGui(); + computeLayout(); + initTopBar(originY + 2); + initLeftPanel(); + initCenterPanel(); + if (rightPanelW > 0) { + initRightPanel(contentY + 4); + } + } + + // ===== DRAWING ===== + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + drawTopBar(); + drawPanels(); + if (enableDivider) { + drawDivider(mouseX, mouseY); + } + super.drawScreen(mouseX, mouseY, partialTicks); + drawOverlay(mouseX, mouseY, partialTicks); + } + + protected void drawTopBar() { + GuiUtil.drawRectD(originX, originY, originX + usableW, originY + topBarH, topBarBg); + } + + protected void drawPanels() { + // Left nav panel border + GuiUtil.drawRectD(originX - 1, contentY - 1, originX + leftPanelW + 1, originY + usableH + 1, panelBorder); + + // Right action panel border (if present) + if (rightPanelW > 0) { + GuiUtil.drawRectD(rightX - 1, contentY - 1, rightX + rightPanelW + 1, originY + usableH + 1, panelBorder); + } + } + + protected void drawDivider(int mouseX, int mouseY) { + if (!enableDivider) return; + int divX = originX + dividerOffset; + int regionTop = contentY; + int regionHeight = contentH; + int handleTop = regionTop + (regionHeight - dividerLineHeight) / 2; + drawRect(divX + 1, handleTop, divX + dividerWidth - 1, handleTop + dividerLineHeight, 0xFF707070); + } + + /** Override for extra drawing after super.drawScreen (e.g. status text) */ + protected void drawOverlay(int mouseX, int mouseY, float partialTicks) { + } + + // ===== DIVIDER MOUSE HANDLING ===== + @Override + public void mouseClicked(int mouseX, int mouseY, int mouseButton) { + if (enableDivider && !hasSubGui()) { + int divX = originX + dividerOffset; + int regionTop = contentY; + int handleTop = regionTop + (contentH - dividerLineHeight) / 2; + int handleBottom = handleTop + dividerLineHeight; + if (mouseX >= divX && mouseX <= divX + dividerWidth && + mouseY >= handleTop && mouseY <= handleBottom) { + isDragging = true; + resizingActive = true; + dragStartX = mouseX; + return; + } + } + super.mouseClicked(mouseX, mouseY, mouseButton); + } + + @Override + public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + if (isDragging) { + int dx = mouseX - dragStartX; + dragStartX = mouseX; + dividerOffset += dx; + + int maxOffset = (usableW - (rightPanelW > 0 ? rightPanelW + gap : 0)) - dividerWidth - minDividerPanelW; + dividerOffset = Math.max(minDividerPanelW, Math.min(dividerOffset, maxOffset)); + + onDividerMoved(dividerOffset); + return; + } + super.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + } + + @Override + protected void mouseMovedOrUp(int mouseX, int mouseY, int state) { + if (isDragging) { + isDragging = false; + resizingActive = false; + return; + } + super.mouseMovedOrUp(mouseX, mouseY, state); + } + + /** Called when divider is dragged. Override to update scroll sizes. */ + protected void onDividerMoved(int newOffset) { + } + + // Accessors for divider offset + protected int getDividerOffset() { + return dividerOffset; + } + + protected void setDividerOffset(int offset) { + this.dividerOffset = offset; + } + + // ===== ABSTRACT HOOKS (subclass must implement) ===== + + /** Populate top bar buttons. topBtnY is the y-coordinate for the button row. */ + protected abstract void initTopBar(int topBtnY); + + /** Populate the left navigation panel (scrolls, search fields, buttons). */ + protected abstract void initLeftPanel(); + + /** Populate the center content panel (main scroll list). */ + protected abstract void initCenterPanel(); + + /** Populate the right action panel. startY is the first button y-coordinate. */ + protected abstract void initRightPanel(int startY); +} diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiMenuSideButton.java b/src/main/java/noppes/npcs/client/gui/util/GuiMenuSideButton.java index 0aa6c7d3d..94398bb60 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiMenuSideButton.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiMenuSideButton.java @@ -15,6 +15,7 @@ public class GuiMenuSideButton extends GuiNpcButton { public boolean active; public boolean rightSided; + public int textColor = -1; public ItemStack renderStack; public ResourceLocation renderResource; public int renderIconPosX = 0; @@ -96,13 +97,15 @@ public class GuiMenuSideButton extends GuiNpcButton { text += "..."; } else text = displayString; - if (active) { - drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, 0xffffa0); - } else if (field_146123_n) { - drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, 0xffffa0); + int color; + if (textColor != -1) { + color = textColor; + } else if (active || field_146123_n) { + color = 0xffffa0; } else { - drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, 0xe0e0e0); + color = 0xe0e0e0; } + drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, color); } @Override diff --git a/src/main/java/noppes/npcs/controllers/ServerCloneController.java b/src/main/java/noppes/npcs/controllers/ServerCloneController.java index 5a0744089..32bd0e15c 100644 --- a/src/main/java/noppes/npcs/controllers/ServerCloneController.java +++ b/src/main/java/noppes/npcs/controllers/ServerCloneController.java @@ -205,42 +205,27 @@ public class ServerCloneController implements ICloneHandler { public void loadFolders() { folders.clear(); - try { - File file = new File(getDir(), "___folders.json"); - if (file.exists()) { - NBTTagCompound compound = NBTJsonUtil.LoadFile(file); - NBTTagList list = compound.getTagList("Folders", 10); - for (int i = 0; i < list.tagCount(); i++) { - CloneFolder folder = new CloneFolder(); - folder.readNBT(list.getCompoundTagAt(i)); - if (CloneFolder.isValidName(folder.name)) { - folders.put(folder.name, folder); - } + File dir = getDir(); + File[] files = dir.listFiles(); + if (files != null) { + List folderDirs = new ArrayList<>(); + for (File f : files) { + if (!f.isDirectory()) continue; + String name = f.getName(); + if (name.startsWith("___")) continue; + try { + Integer.parseInt(name); + continue; + } catch (NumberFormatException ignored) { } + folderDirs.add(f); } - } catch (Exception e) { - LogWriter.except(e); - } - } - - public void saveFolders() { - try { - NBTTagCompound compound = new NBTTagCompound(); - NBTTagList list = new NBTTagList(); - for (CloneFolder folder : folders.values()) { - list.appendTag(folder.writeNBT(new NBTTagCompound())); + folderDirs.sort(Comparator.comparing(File::getName)); + for (File f : folderDirs) { + CloneFolder folder = new CloneFolder(f.getName()); + folder.createdDate = f.lastModified(); + folders.put(f.getName(), folder); } - compound.setTag("Folders", list); - - File file = new File(getDir(), "___folders.json_new"); - File file2 = new File(getDir(), "___folders.json"); - NBTJsonUtil.SaveFile(file, compound); - if (file2.exists()) { - file2.delete(); - } - file.renameTo(file2); - } catch (Exception e) { - LogWriter.except(e); } } @@ -268,7 +253,6 @@ public class ServerCloneController implements ICloneHandler { dir.mkdir(); } folders.put(name, folder); - saveFolders(); return folder; } @@ -278,14 +262,13 @@ public class ServerCloneController implements ICloneHandler { } CloneFolder folder = folders.get(oldName); File oldDir = getFolderDir(oldName); - File newDir = new File(getDir(), "folder_" + newName); + File newDir = new File(getDir(), newName); if (!oldDir.renameTo(newDir)) { return false; } folders.remove(oldName); folder.name = newName; folders.put(newName, folder); - saveFolders(); return true; } @@ -308,17 +291,13 @@ public class ServerCloneController implements ICloneHandler { dir.delete(); } folders.remove(name); - saveFolders(); return true; } // ==================== Folder-Based Clone Operations ==================== public File getFolderDir(String folderName) { - File dir = new File(getDir(), "folder_" + folderName); - if (!dir.exists()) - dir.mkdir(); - return dir; + return new File(getDir(), folderName); } public List getClones(String folderName) { @@ -373,6 +352,7 @@ public class ServerCloneController implements ICloneHandler { public void saveClone(String folderName, String name, NBTTagCompound compound) { try { File dir = getFolderDir(folderName); + if (!dir.exists()) dir.mkdir(); String filename = name + ".json"; File file = new File(dir, filename + "_new"); File file2 = new File(dir, filename); diff --git a/src/main/java/noppes/npcs/controllers/ServerTagMapController.java b/src/main/java/noppes/npcs/controllers/ServerTagMapController.java index ce71cc089..42ca6afeb 100644 --- a/src/main/java/noppes/npcs/controllers/ServerTagMapController.java +++ b/src/main/java/noppes/npcs/controllers/ServerTagMapController.java @@ -36,7 +36,7 @@ public class ServerTagMapController { } public File getCloneFolderDir(String folderName) { - File dir = new File(getDir(), "folder_" + folderName); + File dir = new File(getDir(), folderName); if (!dir.exists()) dir.mkdir(); return dir; diff --git a/src/main/java/noppes/npcs/controllers/data/CloneFolder.java b/src/main/java/noppes/npcs/controllers/data/CloneFolder.java index ceca2e1ca..d2d0c7bcf 100644 --- a/src/main/java/noppes/npcs/controllers/data/CloneFolder.java +++ b/src/main/java/noppes/npcs/controllers/data/CloneFolder.java @@ -27,10 +27,6 @@ public class CloneFolder { return compound; } - public String getDirName() { - return "folder_" + name; - } - public static boolean isValidName(String name) { if (name == null || name.isEmpty()) { return false; @@ -41,6 +37,12 @@ public class CloneFolder { if (!name.equals(name.trim())) { return false; } + if (name.equals(".") || name.equals("..")) { + return false; + } + if (name.startsWith("___")) { + return false; + } try { Integer.parseInt(name); return false; diff --git a/src/main/resources/assets/customnpcs/textures/gui/cloner/folder.png b/src/main/resources/assets/customnpcs/textures/gui/cloner/folder.png new file mode 100644 index 0000000000000000000000000000000000000000..d98408b92a8a8efcb103017f6ad591361dc7973f GIT binary patch literal 924 zcmV;N17rM&P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D12IWNK~zXf?Umna zBUKc~Kj+Smc5PZ0Yjullnv(ulwJzd5_?RN1;QF^H6j@yV0bz@@;6EYQXMGVC!DYdv z3aheaC*7t=adoM66Pq?oGWYl}lS$I1QS?dZ2QJ*X^W}W+J@?ERIOqR{v?s+67Su@c zm|&#F)Xm+~Fa8Aru)ngXVQvX9(W-U{BxLBTJfolf^q&nAv*z*@F3x7aL@^~cw8pX@ z{7m@mW5ihWum*iH(8h!=FRb0`E9fyB%Vt0X5#b=e1%Sn+e?k8D3UkjtjUc_b?St3!X$Ci_1_cgoZVZ)n@Y?ag=(hqBuCQ-U>8bq3$TDY$hJ*u6bwl z8Vvy?MxlWrD~vQ+9SMMVf@&nHtW0rX`6{XT=$q@3or&X)A-GsQ0-&Xq`hya+JNFo0 z50WZ~q8v9s6bl?1zY`^9P|b*Vgkc@4he?Yw8lUjQCzotlRQTn?EMD&QL{oiG#8Eg3 zN;pkRwXlOxI1F|XpmM*6Z8fMC3bD^5TO_@t^Ig1#5w2uJ%0ge6I8YUoQYom0000Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D135`VK~zXf?Uh}N zTSXYhf6vTGwrjK5ZA6xAg3vW72x1GO7e?0I%8FGG3w{Ls7GCsHZ+rm-_XE_MdQ+q{ z(3N$qUIL%$fZC=hiUYz9Qtp=r6!v0}6XXZRJzj^sTbKpq-H%b3x z<)tesVn)9L_YUBj>*vC_2ay1*o!{0c6CQn*4h5(WH(PM+V(x)~A?(bS6`d}j=(bM1pt2jslo@Jtg`cGgUsSQf{bnMq#~UtAH98(-Mu=yjRyHk zljl6)h+oRL`S9WzVxovUPSiz-xCjv!omvohtTa$D6#qCt5q8RTIvtYDmL5z0000