diff --git a/src/main/java/dev/toolkitmc/guiapi/config/GuiApiConfig.java b/src/main/java/dev/toolkitmc/guiapi/config/GuiApiConfig.java index 80a57a4..c80cf78 100644 --- a/src/main/java/dev/toolkitmc/guiapi/config/GuiApiConfig.java +++ b/src/main/java/dev/toolkitmc/guiapi/config/GuiApiConfig.java @@ -31,7 +31,7 @@ public final class GuiApiConfig { private boolean debugMode = false; private boolean allowCloseOnMove = true; private boolean allowDelayedActions = true; - private boolean allowStatusEffects = true; // 1. New Config + private boolean allowStatusEffects = true; // 1. New Config private boolean logCommands = false; // 2. New Config private int defaultTickRate = 20; // 3. New Config @@ -41,8 +41,10 @@ public final class GuiApiConfig { private boolean enableCloseSound = true; // 7. New Config private String chatPrefix = "§8[§6GuiAPI§8] §f"; // 8. New Config - private int soundVolume = 100; // 9. New Config - private String commandExecuteMode = "CHAT"; // 10. New Config + private int soundVolume = 100; // 9. New Config + private String commandExecuteMode = "CHAT"; // 10. New Config + + private boolean enableNoneAction = false; // 11. Experimental private GuiApiConfig() {} @@ -92,6 +94,8 @@ public void load() { soundVolume = Math.clamp(obj.get("sound_volume").getAsInt(), 0, 100); if (obj.has("command_execute_mode")) commandExecuteMode = obj.get("command_execute_mode").getAsString(); + if (obj.has("enable_none_action")) + enableNoneAction = obj.get("enable_none_action").getAsBoolean(); } catch (IOException e) { GuiApiMod.LOGGER.error("[GuiAPI] Failed to load config: {}", e.getMessage()); @@ -112,11 +116,12 @@ public void save() { obj.addProperty("default_tick_rate", defaultTickRate); obj.addProperty("enable_button_glint", enableButtonGlint); obj.addProperty("show_item_ids_developer", showItemIdsDeveloper); - obj.addProperty("mute_click_errors", muteClickErrors); - obj.addProperty("enable_close_sound", enableCloseSound); + obj.addProperty("mute_click_errors", muteClickErrors); + obj.addProperty("enable_close_sound", enableCloseSound); obj.addProperty("chat_prefix", chatPrefix); obj.addProperty("sound_volume", soundVolume); obj.addProperty("command_execute_mode", commandExecuteMode); + obj.addProperty("enable_none_action", enableNoneAction); try { Files.writeString(CONFIG_PATH, GSON.toJson(obj)); } catch (IOException e) { @@ -126,14 +131,16 @@ public void save() { // ── Getters / Setters ──────────────────────────────────────────────────── - public boolean isAllowConsoleRunWith() { return allowConsoleRunWith; } - public boolean isLogUnknownItems() { return logUnknownItems; } - public boolean isLogUnknownSounds() { return logUnknownSounds; } - public int getPermissionLevel() { return permissionLevel; } - + public boolean isAllowConsoleRunWith() { return allowConsoleRunWith; } public void setAllowConsoleRunWith(boolean v) { allowConsoleRunWith = v; } + + public boolean isLogUnknownItems() { return logUnknownItems; } public void setLogUnknownItems(boolean v) { logUnknownItems = v; } + + public boolean isLogUnknownSounds() { return logUnknownSounds; } public void setLogUnknownSounds(boolean v) { logUnknownSounds = v; } + + public int getPermissionLevel() { return permissionLevel; } public void setPermissionLevel(int v) { permissionLevel = Math.clamp(v, 0, 4); } public boolean isDebugMode() { return debugMode; } @@ -142,8 +149,8 @@ public void save() { public boolean isAllowCloseOnMove() { return allowCloseOnMove; } public void setAllowCloseOnMove(boolean v) { allowCloseOnMove = v; } - public boolean isAllowDelayedActions() { return allowDelayedActions; } - public void setAllowDelayedActions(boolean v) { allowDelayedActions = v; } + public boolean isAllowDelayedActions() { return allowDelayedActions; } + public void setAllowDelayedActions(boolean v) { allowDelayedActions = v; } public boolean isAllowStatusEffects() { return allowStatusEffects; } public void setAllowStatusEffects(boolean v) { allowStatusEffects = v; } @@ -151,27 +158,30 @@ public void save() { public boolean isLogCommands() { return logCommands; } public void setLogCommands(boolean v) { logCommands = v; } - public int getDefaultTickRate() { return defaultTickRate; } + public int getDefaultTickRate() { return defaultTickRate; } public void setDefaultTickRate(int v) { defaultTickRate = v; } - public boolean isEnableButtonGlint() { return enableButtonGlint; } - public void setEnableButtonGlint(boolean v) { enableButtonGlint = v; } + public boolean isEnableButtonGlint() { return enableButtonGlint; } + public void setEnableButtonGlint(boolean v) { enableButtonGlint = v; } + + public boolean isShowItemIdsDeveloper() { return showItemIdsDeveloper; } + public void setShowItemIdsDeveloper(boolean v) { showItemIdsDeveloper = v; } - public boolean isShowItemIdsDeveloper() { return showItemIdsDeveloper; } - public void setShowItemIdsDeveloper(boolean v) { showItemIdsDeveloper = v; } + public boolean isMuteClickErrors() { return muteClickErrors; } + public void setMuteClickErrors(boolean v) { muteClickErrors = v; } - public boolean isMuteClickErrors() { return muteClickErrors; } - public void setMuteClickErrors(boolean v) { muteClickErrors = v; } + public boolean isEnableCloseSound() { return enableCloseSound; } + public void setEnableCloseSound(boolean v) { enableCloseSound = v; } - public boolean isEnableCloseSound() { return enableCloseSound; } - public void setEnableCloseSound(boolean v) { enableCloseSound = v; } + public String getChatPrefix() { return chatPrefix; } + public void setChatPrefix(String v) { chatPrefix = v; } - public String getChatPrefix() { return chatPrefix; } - public void setChatPrefix(String v) { chatPrefix = v; } + public int getSoundVolume() { return soundVolume; } + public void setSoundVolume(int v) { soundVolume = Math.clamp(v, 0, 100); } - public int getSoundVolume() { return soundVolume; } - public void setSoundVolume(int v) { soundVolume = Math.clamp(v, 0, 100); } + public String getCommandExecuteMode() { return commandExecuteMode; } + public void setCommandExecuteMode(String v) { commandExecuteMode = v; } - public String getCommandExecuteMode() { return commandExecuteMode; } - public void setCommandExecuteMode(String v) { commandExecuteMode = v; } + public boolean isEnableNoneAction() { return enableNoneAction; } + public void setEnableNoneAction(boolean v) { enableNoneAction = v; } } diff --git a/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java b/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java index 7531fe8..bf81068 100644 --- a/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java +++ b/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java @@ -27,135 +27,7 @@ public class GuiApiModMenuEntry implements ModMenuApi { @Override public ConfigScreenFactory getModConfigScreenFactory() { - return parent -> { - me.shedaniel.clothconfig2.api.ConfigBuilder builder = me.shedaniel.clothconfig2.api.ConfigBuilder.create() - .setParentScreen(parent) - .setTitle(Text.literal("GUI API Settings")); - - me.shedaniel.clothconfig2.api.ConfigCategory generalCategory = builder.getOrCreateCategory(Text.literal("Settings")); - me.shedaniel.clothconfig2.api.ConfigCategory otherCategory = builder.getOrCreateCategory(Text.literal("Other")); - me.shedaniel.clothconfig2.api.ConfigCategory guisCategory = builder.getOrCreateCategory(Text.literal("Loaded GUIs")); - - me.shedaniel.clothconfig2.api.ConfigEntryBuilder entryBuilder = builder.entryBuilder(); - - GuiApiConfig cfg = GuiApiConfig.INSTANCE; - - // --- CATEGORY 1: Settings --- - generalCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Allow run_with: console"), cfg.isAllowConsoleRunWith()) - .setDefaultValue(true) - .setSaveConsumer(cfg::setAllowConsoleRunWith) - .setTooltip(Text.literal("Permit buttons to run commands with console (OP-level) permission.")) - .build()); - - generalCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Log unknown item IDs"), cfg.isLogUnknownItems()) - .setDefaultValue(true) - .setSaveConsumer(cfg::setLogUnknownItems) - .setTooltip(Text.literal("Print a WARN to the log when a button uses an unrecognized item ID.")) - .build()); - - generalCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Log unknown sound IDs"), cfg.isLogUnknownSounds()) - .setDefaultValue(true) - .setSaveConsumer(cfg::setLogUnknownSounds) - .setTooltip(Text.literal("Print a WARN to the log when a sound action uses an unrecognized sound ID.")) - .build()); - - generalCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Debug mode"), cfg.isDebugMode()) - .setDefaultValue(false) - .setSaveConsumer(cfg::setDebugMode) - .setTooltip(Text.literal("Log GUI open/close, action execution and placeholder resolution to console.")) - .build()); - - generalCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Allow close_on_move"), cfg.isAllowCloseOnMove()) - .setDefaultValue(true) - .setSaveConsumer(cfg::setAllowCloseOnMove) - .setTooltip(Text.literal("Globally permit menus to close automatically when players walk away.")) - .build()); - - generalCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Allow action delays"), cfg.isAllowDelayedActions()) - .setDefaultValue(true) - .setSaveConsumer(cfg::setAllowDelayedActions) - .setTooltip(Text.literal("Globally permit action chains to execute with tick delays.")) - .build()); - - generalCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Allow status effects"), cfg.isAllowStatusEffects()) - .setDefaultValue(true) - .setSaveConsumer(cfg::setAllowStatusEffects) - .setTooltip(Text.literal("Globally permit buttons and click actions to manage player potion effects.")) - .build()); - - generalCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Log command executions"), cfg.isLogCommands()) - .setDefaultValue(false) - .setSaveConsumer(cfg::setLogCommands) - .setTooltip(Text.literal("Write a message to log console every time a GUI button runs a command.")) - .build()); - - generalCategory.addEntry(entryBuilder.startIntSlider(Text.literal("Default Tick Rate"), cfg.getDefaultTickRate(), 1, 100) - .setDefaultValue(20) - .setSaveConsumer(cfg::setDefaultTickRate) - .setTooltip(Text.literal("Default tick rate for auto-refreshing menus.")) - .build()); - - generalCategory.addEntry(entryBuilder.startIntSlider(Text.literal("Command permission level"), cfg.getPermissionLevel(), 0, 4) - .setDefaultValue(2) - .setSaveConsumer(cfg::setPermissionLevel) - .setTooltip(Text.literal("Permission level for running standard commands.")) - .build()); - - - // --- CATEGORY 2: Other --- - otherCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Enable button glint"), cfg.isEnableButtonGlint()) - .setDefaultValue(true) - .setSaveConsumer(cfg::setEnableButtonGlint) - .setTooltip(Text.literal("Toggles whether to show the glowing enchantment shine on buttons.")) - .build()); - - otherCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Show developer item IDs"), cfg.isShowItemIdsDeveloper()) - .setDefaultValue(false) - .setSaveConsumer(cfg::setShowItemIdsDeveloper) - .setTooltip(Text.literal("Show technical item IDs in tooltips for designers.")) - .build()); - - otherCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Mute click errors"), cfg.isMuteClickErrors()) - .setDefaultValue(false) - .setSaveConsumer(cfg::setMuteClickErrors) - .setTooltip(Text.literal("Silence warn messages when clicks fail to meet conditions or permissions.")) - .build()); - - otherCategory.addEntry(entryBuilder.startBooleanToggle(Text.literal("Play close sound"), cfg.isEnableCloseSound()) - .setDefaultValue(true) - .setSaveConsumer(cfg::setEnableCloseSound) - .setTooltip(Text.literal("Play a clean chest close sound when closing virtual GUIs.")) - .build()); - - otherCategory.addEntry(entryBuilder.startTextField(Text.literal("Chat Prefix"), cfg.getChatPrefix()) - .setDefaultValue("§8[§6GuiAPI§8] §f") - .setSaveConsumer(cfg::setChatPrefix) - .setTooltip(Text.literal("Custom prefix for all chat messages sent by GuiAPI.")) - .build()); - - otherCategory.addEntry(entryBuilder.startIntSlider(Text.literal("Sound Volume (%)"), cfg.getSoundVolume(), 0, 100) - .setDefaultValue(100) - .setSaveConsumer(cfg::setSoundVolume) - .setTooltip(Text.literal("Global multiplier for mod UI sound volumes.")) - .build()); - - otherCategory.addEntry(entryBuilder.startSelector(Text.literal("Message Execute Mode"), new String[]{"CHAT", "SYSTEM", "SILENT"}, cfg.getCommandExecuteMode()) - .setDefaultValue("CHAT") - .setSaveConsumer(cfg::setCommandExecuteMode) - .setTooltip(Text.literal("Where button message feedback is routed.")) - .build()); - - - // --- CATEGORY 3: Loaded GUIs --- - guisCategory.addEntry(entryBuilder.startTextDescription(Text.literal("§eClick [Loaded GUIs List] below to open the Visual Dashboard!")) - .build()); - - builder.setSavingRunnable(() -> { - cfg.save(); - }); - - return builder.build(); - }; + return GuiApiConfigScreen::new; } // ── Helper methods for Actions string parsing & serialization ──────────── @@ -244,6 +116,8 @@ public ScrollableElement(net.minecraft.client.gui.Drawable drawable, net.minecra private boolean showItemIdsDeveloper; private boolean muteClickErrors; private boolean enableCloseSound; + // guiapi:experimental + private boolean enableNoneAction; // Our 3 New Interactive Input Features private String chatPrefix; @@ -273,6 +147,7 @@ public ScrollableElement(net.minecraft.client.gui.Drawable drawable, net.minecra this.chatPrefix = cfg.getChatPrefix(); this.soundVolume = cfg.getSoundVolume(); this.commandExecuteMode = cfg.getCommandExecuteMode(); + this.enableNoneAction = cfg.isEnableNoneAction(); } private static String nextExecuteMode(String current) { @@ -304,9 +179,12 @@ protected void init() { scrollableElements.clear(); // 1. Add persistent Tab Selectors (Fixed at Top) - ButtonWidget configTabBtn = ButtonWidget.builder(Text.literal("§eSettings Screen"), btn -> { - MinecraftClient.getInstance().setScreen(new GuiApiModMenuEntry().getModConfigScreenFactory().create(parent)); + ButtonWidget configTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.CONFIG ? "§aSettings" : "§7Settings"), btn -> { + currentTab = Tab.CONFIG; + scrollY = 0; + this.init(); }).dimensions(cx - 125, 22, 80, 18).build(); + configTabBtn.active = (currentTab != Tab.CONFIG); addDrawableChild(configTabBtn); ButtonWidget guisTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.GUIS ? "§aLoaded GUIs" : "§7Loaded GUIs"), btn -> { @@ -348,6 +226,7 @@ protected void init() { cfg.setChatPrefix(chatPrefix); cfg.setSoundVolume(soundVolume); cfg.setCommandExecuteMode(commandExecuteMode); + cfg.setEnableNoneAction(enableNoneAction); cfg.save(); MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 105, height - 25, 100, 20).build()); @@ -403,7 +282,7 @@ protected void init() { logCommands, v -> logCommands = v); y += 22; - // Default Tick Rate Slider (0, 15 etc. number slider input!) + // Default Tick Rate Slider IntegerSliderWidget tickRateSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Default Tick Rate", 1, 100, defaultTickRate, val -> defaultTickRate = val); addDrawableChild(tickRateSlider); scrollableElements.add(new ScrollableElement(tickRateSlider, tickRateSlider, y, 20)); @@ -434,7 +313,9 @@ protected void init() { enableCloseSound, v -> enableCloseSound = v); y += 22; - // ── 3 New Interactive Input Features ── + addScrollableToggle(cx, y, "§6[Experimental] §fEnable 'none' action", + enableNoneAction, v -> enableNoneAction = v); + y += 22; // Input Type 1: Text Field for Chat Prefix TextWidget chatPrefixLabel = new TextWidget(cx - 150, y, 300, 10, Text.literal("§eChat Prefix"), textRenderer); @@ -500,7 +381,6 @@ protected void init() { for (ScrollableElement se : scrollableElements) { maxContentY = Math.max(maxContentY, se.originalY + se.height); } - int viewportHeight = (height - 35) - startY; maxScrollY = Math.max(0, maxContentY - (height - 35)); if (scrollY > maxScrollY) { @@ -512,7 +392,7 @@ protected void init() { private void addScrollableToggle(int cx, int y, String label, boolean initial, java.util.function.Consumer onChange) { TextWidget labelWidget = new TextWidget(cx - 150, y + 4, 200, 10, Text.literal("§f" + label), textRenderer); - + ButtonWidget[] ref = new ButtonWidget[1]; ref[0] = ButtonWidget.builder(toggleText(initial), btn -> { boolean next = !btn.getMessage().getString().contains("ON"); @@ -589,8 +469,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { if (maxScrollY > 0) { int rx = width / 2 + 155; int trackHeight = bottomBoundary - topBoundary; - int viewportHeight = trackHeight; - int thumbHeight = Math.max(15, (int)((double)viewportHeight / (viewportHeight + maxScrollY) * trackHeight)); + int thumbHeight = Math.max(15, (int)((double)trackHeight / (trackHeight + maxScrollY) * trackHeight)); int thumbY = topBoundary + (int)(scrollY / maxScrollY * (trackHeight - thumbHeight)); ctx.fill(rx, topBoundary, rx + 4, bottomBoundary, 0x22FFFFFF); @@ -621,7 +500,8 @@ private static Text permLevelText(int level) { return Text.literal(color + level); } } - // ── GUI Editor Screen ──────────────────────────────────────────────────── + + // ── GUI Editor Screen ──────────────────────────────────────────────────── static class GuiEditorScreen extends Screen { private final Screen parent; @@ -655,10 +535,6 @@ public void updateFiller(Optional newFiller) { this.filler = newFiller; } - - - - @Override protected void init() { int cx = width / 2; @@ -723,8 +599,7 @@ protected void init() { ).dimensions(cx - 150, y, 300, 18).build()); y += 28; - // Action Buttons - // Save & Back — Open loading screen to search datapack and write JSON directly + // Apply & Back addDrawableChild(ButtonWidget.builder(Text.literal("Apply & Back"), btn -> { GuiDefinition newDef = GuiDefinition.create( def.getId(), @@ -775,7 +650,7 @@ private static Text toggleText(boolean on) { } } - // ── GUI Save Loading Screen (Client Feature: Persists Datapack on Disk) ── + // ── GUI Save Loading Screen ─────────────────────────────────────────────── static class GuiSaveProcessScreen extends Screen { enum State { @@ -856,12 +731,11 @@ public void tick() { @Override public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) { - // Safe No-Op: Bypasses vanilla background blur completely to avoid "Can only blur once per frame" IllegalStateException crash! + // Safe No-Op: Bypasses vanilla background blur to avoid "Can only blur once per frame" crash } @Override public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { - // Draw a highly professional solid opaque dark background manually ctx.fill(0, 0, width, height, 0xFF0A0A0A); if (state == State.CONFIRM_SAVE || state == State.CONFIRM_RELOAD) { @@ -880,10 +754,7 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§fAre you sure you want to write changes to disk?"), cx, cy - 10, 0xAAAAAA); } case SAVING_PROGRESS -> { - String status = "Locating Datapack Folder..."; - if (ticksElapsed >= 20) { - status = "Overwriting Datapack JSON File..."; - } + String status = ticksElapsed >= 20 ? "Overwriting Datapack JSON File..." : "Locating Datapack Folder..."; ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ Writing Datapack ★"), cx, cy - 40, 0xFFFFFF); ctx.drawCenteredTextWithShadow(textRenderer, Text.literal(status), cx, cy - 10, rainbowColor); @@ -907,7 +778,6 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { double angle = angleSpeed + (i * 2.0 * Math.PI / numDots); int dotX = cx + (int)(radius * Math.cos(angle)); int dotY = cy + 15 + (int)(radius * Math.sin(angle)); - int alpha = (int)(255 * ((double)i / numDots)); int color = (alpha << 24) | (0xFFFFFF & rainbowColor); ctx.fill(dotX - 2, dotY - 2, dotX + 2, dotY + 2, color); @@ -920,7 +790,8 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { } } } - // ── Filler Editor Screen ───────────────────────────────────────────────── + + // ── Filler Editor Screen ───────────────────────────────────────────────── static class FillerEditorScreen extends Screen { private final GuiEditorScreen parent; @@ -945,10 +816,6 @@ static class FillerEditorScreen extends Screen { this.hideTooltip = fill.hideTooltip(); } - - - - @Override protected void init() { int cx = width / 2; @@ -996,7 +863,7 @@ protected void init() { addDrawableChild(hideTooltipBtnRef[0]); y += 35; - // Save + // Apply Filler addDrawableChild(ButtonWidget.builder(Text.literal("Apply Filler"), btn -> { GuiDefinition.FillerConfig newFiller = new GuiDefinition.FillerConfig( itemField.getText(), @@ -1008,7 +875,7 @@ protected void init() { MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 105, height - 25, 100, 20).build()); - // Remove Filler completely + // Disable Filler addDrawableChild(ButtonWidget.builder(Text.literal("Disable Filler"), btn -> { parent.updateFiller(Optional.empty()); MinecraftClient.getInstance().setScreen(parent); @@ -1052,7 +919,7 @@ static class ButtonListScreen extends Screen { private final GuiEditorScreen parent; private final net.minecraft.util.Identifier id; private final GuiDefinition def; - private final List buttonsList; + final List buttonsList; private int listPage = 0; ButtonListScreen(GuiEditorScreen parent, net.minecraft.util.Identifier id, GuiDefinition def) { @@ -1090,16 +957,14 @@ protected void init() { (btn.name().isEmpty() ? btn.item() : btn.name()); addDrawableChild(ButtonWidget.builder(Text.literal(labelText), b -> { - MinecraftClient.getInstance().setScreen(createButtonEditorScreen(this, index, btn, this)); + MinecraftClient.getInstance().setScreen(new ButtonEditorScreen(this, index, btn)); }).dimensions(cx - 150, y, 300, 18).build()); y += 20; } - // Space out for Prev / Next controls y = 186; - // Pagination Controls for Buttons list if (totalPages > 1) { ButtonWidget prevBtn = ButtonWidget.builder(Text.literal("§e← Prev"), btn -> { listPage = Math.max(0, listPage - 1); @@ -1126,7 +991,7 @@ protected void init() { Optional.empty(), Optional.empty(), "1", false, false ); buttonsList.add(newBtn); - MinecraftClient.getInstance().setScreen(createButtonEditorScreen(this, buttonsList.size() - 1, newBtn, this)); + MinecraftClient.getInstance().setScreen(new ButtonEditorScreen(this, buttonsList.size() - 1, newBtn)); }).dimensions(cx - 155, y, 100, 20).build()); // Save & Back @@ -1159,168 +1024,8 @@ public void deleteButton(int index) { } } } - // ── Button Properties Editor Screen ────────────────────────────────────── - - public static Screen createButtonEditorScreen(Screen parent, int index, GuiDefinition.Button btn, ButtonListScreen listScreen) { - me.shedaniel.clothconfig2.api.ConfigBuilder builder = me.shedaniel.clothconfig2.api.ConfigBuilder.create() - .setParentScreen(parent) - .setTitle(Text.literal("Edit Button Properties")); - - me.shedaniel.clothconfig2.api.ConfigCategory basicCat = builder.getOrCreateCategory(Text.literal("Basic")); - me.shedaniel.clothconfig2.api.ConfigCategory appearanceCat = builder.getOrCreateCategory(Text.literal("Appearance")); - me.shedaniel.clothconfig2.api.ConfigCategory logicCat = builder.getOrCreateCategory(Text.literal("Logic")); - - me.shedaniel.clothconfig2.api.ConfigEntryBuilder entryBuilder = builder.entryBuilder(); - - final int[] slot = {btn.slot()}; - final int[] amount = {1}; - try { amount[0] = Integer.parseInt(btn.amount()); } catch (Exception ignored) {} - final int[] pageVal = {btn.page()}; - final String[] itemText = {btn.item()}; - final String[] nameText = {btn.name()}; - final boolean[] glint = {btn.glint()}; - final GuiDefinition.ClickType[] clickType = {btn.clickType()}; - final String[] loreText = {String.join(";", btn.lore())}; - final String[] actionsText = {serializeActionsToString(btn.actions())}; - final String[] conditionText = {btn.condition().isPresent() ? btn.condition().get().type().name().toLowerCase() + ":" + btn.condition().get().value() : ""}; - - // --- Basic Category --- - basicCat.addEntry(entryBuilder.startIntSlider(Text.literal("Page (1-10)"), pageVal[0], 0, 9) - .setDefaultValue(0) - .setSaveConsumer(v -> pageVal[0] = v) - .build()); - - basicCat.addEntry(entryBuilder.startIntSlider(Text.literal("Slot Position"), slot[0], 0, 53) - .setDefaultValue(0) - .setSaveConsumer(v -> slot[0] = v) - .build()); - - basicCat.addEntry(entryBuilder.startIntSlider(Text.literal("Amount"), amount[0], 1, 99) - .setDefaultValue(1) - .setSaveConsumer(v -> amount[0] = v) - .build()); - - basicCat.addEntry(entryBuilder.startTextField(Text.literal("Item ID"), itemText[0]) - .setDefaultValue("minecraft:stone") - .setSaveConsumer(v -> itemText[0] = v) - .build()); - - basicCat.addEntry(entryBuilder.startTextField(Text.literal("Display Name"), nameText[0]) - .setDefaultValue("") - .setSaveConsumer(v -> nameText[0] = v) - .build()); - - - // --- Appearance Category --- - appearanceCat.addEntry(entryBuilder.startBooleanToggle(Text.literal("Enable Glint"), glint[0]) - .setDefaultValue(false) - .setSaveConsumer(v -> glint[0] = v) - .build()); - - appearanceCat.addEntry(entryBuilder.startTextField(Text.literal("Lore Lines (separated by ';')"), loreText[0]) - .setDefaultValue("") - .setSaveConsumer(v -> loreText[0] = v) - .build()); - - - // --- Logic Category --- - logicCat.addEntry(entryBuilder.startSelector(Text.literal("Click Type"), new String[]{"ANY", "LEFT", "RIGHT", "SHIFT"}, clickType[0].name()) - .setDefaultValue("ANY") - .setSaveConsumer(v -> clickType[0] = GuiDefinition.ClickType.valueOf(v)) - .build()); - - logicCat.addEntry(entryBuilder.startTextField(Text.literal("Condition (type:value)"), conditionText[0]) - .setDefaultValue("") - .setSaveConsumer(v -> conditionText[0] = v) - .build()); - - logicCat.addEntry(entryBuilder.startTextField(Text.literal("Actions (separated by ';')"), actionsText[0]) - .setDefaultValue("") - .setSaveConsumer(v -> actionsText[0] = v) - .build()); - - builder.setSavingRunnable(() -> { - int targetSlot = slot[0]; - boolean duplicate = false; - for (int i = 0; i < listScreen.buttonsList.size(); i++) { - if (i == index) continue; - GuiDefinition.Button existing = listScreen.buttonsList.get(i); - if (existing.slot() == targetSlot && existing.page() == pageVal[0]) { - duplicate = true; - break; - } - } - if (duplicate) { - int emptySlot = targetSlot; - for (int s = 0; s < 54; s++) { - boolean slotUsed = false; - for (int i = 0; i < listScreen.buttonsList.size(); i++) { - if (i == index) continue; - GuiDefinition.Button existing = listScreen.buttonsList.get(i); - if (existing.slot() == s && existing.page() == pageVal[0]) { - slotUsed = true; - break; - } - } - if (!slotUsed) { - emptySlot = s; - break; - } - } - targetSlot = emptySlot; - } - - List finalLore = new ArrayList<>(); - if (!loreText[0].isEmpty()) { - for (String s : loreText[0].split(";")) { - finalLore.add(s); - } - } - - List finalActions = new ArrayList<>(); - if (!actionsText[0].isEmpty()) { - for (String s : actionsText[0].split(";")) { - finalActions.add(parseActionFromString(s)); - } - } - if (finalActions.isEmpty()) { - finalActions.add(new GuiDefinition.ButtonAction(GuiDefinition.ActionType.NONE, "")); - } - - Optional finalCondition = Optional.empty(); - if (!conditionText[0].isEmpty()) { - String[] condParts = conditionText[0].split(":", 2); - if (condParts.length == 2) { - try { - GuiDefinition.ConditionType ct = GuiDefinition.ConditionType.fromString(condParts[0]); - finalCondition = Optional.of(new GuiDefinition.ButtonCondition(ct, condParts[1])); - } catch (Exception ignored) {} - } - } - GuiDefinition.Button newBtn = new GuiDefinition.Button( - targetSlot, - pageVal[0], - itemText[0], - nameText[0], - finalLore, - glint[0], - clickType[0], - finalCondition, - finalActions, - btn.toggle(), - btn.customModelData(), - btn.itemModel(), - String.valueOf(amount[0]), - btn.hideTooltip(), - btn.hideAdditionalTooltip() - ); - - listScreen.updateButton(index, newBtn); - }); - - return builder.build(); - } + // ── Button Editor Screen ───────────────────────────────────────────────── static class ButtonEditorScreen extends Screen { private enum Tab { @@ -1335,7 +1040,7 @@ private enum Tab { private TextFieldWidget itemField; private TextFieldWidget nameField; - private TextFieldWidget loreField; + private TextFieldWidget loreField; private TextFieldWidget actionsField; private TextFieldWidget conditionField; @@ -1346,7 +1051,7 @@ private enum Tab { private GuiDefinition.ClickType clickType; private Optional toggle; - // Caching fields to fix wipe-on-tab-switch bug! + // Caching fields to fix wipe-on-tab-switch bug private String itemText; private String nameText; private String loreText; @@ -1370,12 +1075,13 @@ private enum Tab { } catch (NumberFormatException ignored) {} this.amount = parsedAmount; - // Load initial texts this.itemText = btn.item(); this.nameText = btn.name(); this.loreText = String.join(";", btn.lore()); this.actionsText = serializeActionsToString(btn.actions()); - this.conditionText = btn.condition().isPresent() ? btn.condition().get().type().name().toLowerCase() + ":" + btn.condition().get().value() : ""; + this.conditionText = btn.condition().isPresent() + ? btn.condition().get().type().name().toLowerCase() + ":" + btn.condition().get().value() + : ""; } public void updateToggle(Optional newToggle) { @@ -1395,12 +1101,40 @@ private void saveCurrentTabFields() { if (conditionField != null) conditionText = conditionField.getText(); } + /** Slot çakışması varsa aynı page'deki ilk boş slotu döndürür. */ + private int resolveSlot(int targetSlot, int targetPage) { + boolean duplicate = false; + for (int i = 0; i < parent.buttonsList.size(); i++) { + if (i == index) continue; + GuiDefinition.Button existing = parent.buttonsList.get(i); + if (existing.slot() == targetSlot && existing.page() == targetPage) { + duplicate = true; + break; + } + } + if (!duplicate) return targetSlot; + + for (int s = 0; s < 54; s++) { + boolean slotUsed = false; + for (int i = 0; i < parent.buttonsList.size(); i++) { + if (i == index) continue; + GuiDefinition.Button existing = parent.buttonsList.get(i); + if (existing.slot() == s && existing.page() == targetPage) { + slotUsed = true; + break; + } + } + if (!slotUsed) return s; + } + return targetSlot; // son çare: tüm slotlar dolu + } + @Override protected void init() { int cx = width / 2; this.clearChildren(); - // 1. Add Tab buttons + // Tab buttons ButtonWidget basicTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.BASIC ? "§aBasic" : "§7Basic"), btn -> { saveCurrentTabFields(); currentTab = Tab.BASIC; @@ -1425,41 +1159,12 @@ protected void init() { logicTabBtn.active = (currentTab != Tab.LOGIC); addDrawableChild(logicTabBtn); - // 2. Add bottom buttons + // Bottom buttons addDrawableChild(ButtonWidget.builder(Text.literal("Apply"), b -> { saveCurrentTabFields(); - // Prevent duplicate slot on the same page by finding the next empty slot - boolean duplicate = false; - for (int i = 0; i < parent.buttonsList.size(); i++) { - if (i == index) continue; - GuiDefinition.Button existing = parent.buttonsList.get(i); - if (existing.slot() == slot && existing.page() == pageVal) { - duplicate = true; - break; - } - } - if (duplicate) { - int emptySlot = slot; - for (int s = 0; s < 54; s++) { - boolean slotUsed = false; - for (int i = 0; i < parent.buttonsList.size(); i++) { - if (i == index) continue; - GuiDefinition.Button existing = parent.buttonsList.get(i); - if (existing.slot() == s && existing.page() == pageVal) { - slotUsed = true; - break; - } - } - if (!slotUsed) { - emptySlot = s; - break; - } - } - slot = emptySlot; - } + int finalSlot = resolveSlot(slot, pageVal); - // Build Lore list List finalLore = new ArrayList<>(); if (!loreText.isEmpty()) { for (String s : loreText.split(";")) { @@ -1467,7 +1172,6 @@ protected void init() { } } - // Build Actions list from semicolon-separated string List finalActions = new ArrayList<>(); if (!actionsText.isEmpty()) { for (String s : actionsText.split(";")) { @@ -1475,10 +1179,9 @@ protected void init() { } } if (finalActions.isEmpty()) { - finalActions.add(new GuiDefinition.ButtonAction(GuiDefinition.ActionType.NONE, "")); + finalActions.add(new GuiDefinition.ButtonAction(GuiDefinition.ActionType.CLOSE, "")); } - // Build Condition Optional finalCondition = Optional.empty(); if (!conditionText.isEmpty()) { String[] condParts = conditionText.split(":", 2); @@ -1491,7 +1194,7 @@ protected void init() { } GuiDefinition.Button newBtn = new GuiDefinition.Button( - slot, + finalSlot, pageVal, itemText, nameText, @@ -1520,27 +1223,23 @@ protected void init() { MinecraftClient.getInstance().setScreen(parent)) .dimensions(cx + 55, height - 25, 100, 20).build()); - // 3. Tab Specific Inputs + // Tab content int startY = 48; if (currentTab == Tab.BASIC) { int y = startY; - // Page selection slider (Page 1-10) IntegerSliderWidget pageSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Page (1-10)", 0, 9, pageVal, val -> pageVal = val); addDrawableChild(pageSlider); y += 24; - // Slot selection slider (Slot 0-53) IntegerSliderWidget slotSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Slot Position", 0, 53, slot, val -> slot = val); addDrawableChild(slotSlider); y += 24; - // Amount selection slider (Amount 1-99) IntegerSliderWidget amountSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Amount", 1, 99, amount, val -> amount = val); addDrawableChild(amountSlider); y += 24; - // Item ID Input (Text Field) addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eItem ID"), textRenderer)); itemField = new TextFieldWidget(textRenderer, cx - 150, y + 12, 300, 18, Text.literal("Item ID")); itemField.setMaxLength(256); @@ -1548,7 +1247,6 @@ protected void init() { addDrawableChild(itemField); y += 34; - // Display Name Input (Text Field) addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eDisplay Name"), textRenderer)); nameField = new TextFieldWidget(textRenderer, cx - 150, y + 12, 300, 18, Text.literal("Display Name")); nameField.setMaxLength(128); @@ -1558,7 +1256,6 @@ protected void init() { } else if (currentTab == Tab.APPEARANCE) { int y = startY; - // Glint Toggle addDrawableChild(new TextWidget(cx - 150, y + 4, 200, 10, Text.literal("§eEnable Glint"), textRenderer)); ButtonWidget glintBtn = ButtonWidget.builder(toggleText(glint), b -> { glint = !glint; @@ -1567,7 +1264,6 @@ protected void init() { addDrawableChild(glintBtn); y += 24; - // Toggle Button Navigation addDrawableChild(new TextWidget(cx - 150, y + 4, 200, 10, Text.literal("§eToggle Properties"), textRenderer)); ButtonWidget toggleBtn = ButtonWidget.builder(Text.literal(toggle.isPresent() ? "§aCONFIGURED" : "§cDISABLED"), b -> { MinecraftClient.getInstance().setScreen(new ToggleEditorScreen(this, toggle)); @@ -1575,7 +1271,6 @@ protected void init() { addDrawableChild(toggleBtn); y += 26; - // Lore Input addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eLore Lines (Separate by semicolon ';')"), textRenderer)); loreField = new TextFieldWidget(textRenderer, cx - 150, y + 12, 300, 18, Text.literal("Lore")); loreField.setMaxLength(512); @@ -1585,7 +1280,6 @@ protected void init() { } else if (currentTab == Tab.LOGIC) { int y = startY; - // Click Type addDrawableChild(new TextWidget(cx - 150, y + 4, 200, 10, Text.literal("§eClick Type"), textRenderer)); ButtonWidget clickTypeBtn = ButtonWidget.builder(Text.literal(clickType.name()), b -> { clickType = nextClickType(clickType); @@ -1594,7 +1288,6 @@ protected void init() { addDrawableChild(clickTypeBtn); y += 24; - // Condition addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eCondition"), textRenderer)); conditionField = new TextFieldWidget(textRenderer, cx - 150, y + 12, 300, 18, Text.literal("Condition")); conditionField.setMaxLength(128); @@ -1602,7 +1295,6 @@ protected void init() { addDrawableChild(conditionField); y += 34; - // Actions addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eActions (Separate by semicolon ';')"), textRenderer)); actionsField = new TextFieldWidget(textRenderer, cx - 150, y + 12, 300, 18, Text.literal("Actions")); actionsField.setMaxLength(512); @@ -1647,7 +1339,8 @@ private static Text toggleText(boolean on) { return on ? Text.literal("§aON") : Text.literal("§cOFF"); } } - // ── Toggle Editor Screen ───────────────────────────────────────────────── + + // ── Toggle Editor Screen ───────────────────────────────────────────────── static class ToggleEditorScreen extends Screen { private final ButtonEditorScreen parent; @@ -1658,8 +1351,8 @@ static class ToggleEditorScreen extends Screen { private TextFieldWidget itemOffField; private TextFieldWidget nameOnField; private TextFieldWidget nameOffField; - private TextFieldWidget actionsOnField; // Multiple Actions ON (separated by ;) - private TextFieldWidget actionsOffField; // Multiple Actions OFF (separated by ;) + private TextFieldWidget actionsOnField; + private TextFieldWidget actionsOffField; ToggleEditorScreen(ButtonEditorScreen parent, Optional currentToggle) { super(Text.literal("Edit Toggle Properties")); @@ -1667,10 +1360,6 @@ static class ToggleEditorScreen extends Screen { this.currentToggle = currentToggle; } - - - - @Override protected void init() { int cx = width / 2; @@ -1694,7 +1383,7 @@ protected void init() { addDrawableChild(tagField); y += 22; - // Item ON / OFF Inputs + // Item ON / OFF addDrawableChild(new TextWidget(cx - 150, y, 145, 10, Text.literal("§eItem ON"), textRenderer)); addDrawableChild(new TextWidget(cx + 5, y, 145, 10, Text.literal("§eItem OFF"), textRenderer)); y += 11; @@ -1708,7 +1397,7 @@ protected void init() { addDrawableChild(itemOffField); y += 22; - // Name ON / OFF Inputs + // Name ON / OFF addDrawableChild(new TextWidget(cx - 150, y, 145, 10, Text.literal("§eDisplay Name ON"), textRenderer)); addDrawableChild(new TextWidget(cx + 5, y, 145, 10, Text.literal("§eDisplay Name OFF"), textRenderer)); y += 11; @@ -1722,7 +1411,7 @@ protected void init() { addDrawableChild(nameOffField); y += 22; - // Actions ON / OFF Inputs (Separate by ;) + // Actions ON addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eActions ON (Separate by ';')"), textRenderer)); y += 11; actionsOnField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 17, Text.literal("Actions ON")); @@ -1731,6 +1420,7 @@ protected void init() { addDrawableChild(actionsOnField); y += 22; + // Actions OFF addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eActions OFF (Separate by ';')"), textRenderer)); y += 11; actionsOffField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 17, Text.literal("Actions OFF")); @@ -1739,9 +1429,8 @@ protected void init() { addDrawableChild(actionsOffField); y += 26; - // Save / Apply Toggle properties + // Apply Toggle addDrawableChild(ButtonWidget.builder(Text.literal("Apply Toggle"), btn -> { - // Build Actions ON list List finalOnActions = new ArrayList<>(); String onActionsTxt = actionsOnField.getText(); if (!onActionsTxt.isEmpty()) { @@ -1753,7 +1442,6 @@ protected void init() { finalOnActions.add(new GuiDefinition.ButtonAction(GuiDefinition.ActionType.RUN_COMMAND, "tag @s remove " + tagField.getText(), GuiDefinition.RunWith.CONSOLE)); } - // Build Actions OFF list List finalOffActions = new ArrayList<>(); String offActionsTxt = actionsOffField.getText(); if (!offActionsTxt.isEmpty()) { @@ -1792,7 +1480,7 @@ protected void init() { MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 105, height - 25, 100, 20).build()); - // Disable Toggle Completely + // Disable Toggle addDrawableChild(ButtonWidget.builder(Text.literal("Disable Toggle"), btn -> { parent.updateToggle(Optional.empty()); MinecraftClient.getInstance().setScreen(parent);