From 6c49658548543df2f4b7178dacb673069e1118ab Mon Sep 17 00:00:00 2001 From: Legends11 <235496468+tickwarden@users.noreply.github.com> Date: Tue, 23 Jun 2026 11:59:22 +0300 Subject: [PATCH 1/3] Update GuiApiModMenuEntry.java --- .../guiapi/modmenu/GuiApiModMenuEntry.java | 1362 ++++------------- 1 file changed, 322 insertions(+), 1040 deletions(-) diff --git a/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java b/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java index 7531fe8..739391e 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 ──────────── @@ -168,57 +40,9 @@ public static String serializeActionsToString(List a return GuiActionParser.serializeActionsToString(actions); } - public static class IntegerSliderWidget extends net.minecraft.client.gui.widget.SliderWidget { - private final int min; - private final int max; - private final String prefix; - private final java.util.function.Consumer onChange; - - public IntegerSliderWidget(int x, int y, int width, int height, String prefix, int min, int max, int currentValue, java.util.function.Consumer onChange) { - super(x, y, width, height, Text.literal(prefix + ": " + currentValue), (double)(currentValue - min) / (max - min)); - this.min = min; - this.max = max; - this.prefix = prefix; - this.onChange = onChange; - } - - @Override - protected void updateMessage() { - int intVal = getIntValue(); - this.setMessage(Text.literal(prefix + ": " + intVal)); - } - - @Override - protected void applyValue() { - int intVal = getIntValue(); - onChange.accept(intVal); - } - - public int getIntValue() { - return min + (int)Math.round(this.value * (max - min)); - } - } - // ── Config screen ──────────────────────────────────────────────────────── static class GuiApiConfigScreen extends Screen { - private enum Tab { - CONFIG, GUIS, OTHER - } - - private static class ScrollableElement { - public final net.minecraft.client.gui.Drawable drawable; - public final net.minecraft.client.gui.Element element; - public final int originalY; - public final int height; - - public ScrollableElement(net.minecraft.client.gui.Drawable drawable, net.minecraft.client.gui.Element element, int originalY, int height) { - this.drawable = drawable; - this.element = element; - this.originalY = originalY; - this.height = height; - } - } private final Screen parent; @@ -234,23 +58,7 @@ public ScrollableElement(net.minecraft.client.gui.Drawable drawable, net.minecra private boolean logCommands; private int defaultTickRate; - private Tab currentTab = Tab.CONFIG; - private final List scrollableElements = new ArrayList<>(); - private double scrollY = 0; - private int maxScrollY = 0; - - // Our 4 Choice Features - private boolean enableButtonGlint; - private boolean showItemIdsDeveloper; - private boolean muteClickErrors; - private boolean enableCloseSound; - - // Our 3 New Interactive Input Features - private String chatPrefix; - private int soundVolume; - private String commandExecuteMode; - - private TextFieldWidget chatPrefixField; + private int guiListPage = 0; GuiApiConfigScreen(Screen parent) { super(Text.literal("GUI API — Settings")); @@ -266,66 +74,147 @@ public ScrollableElement(net.minecraft.client.gui.Drawable drawable, net.minecra this.allowStatusEffects = cfg.isAllowStatusEffects(); this.logCommands = cfg.isLogCommands(); this.defaultTickRate = cfg.getDefaultTickRate(); - this.enableButtonGlint = cfg.isEnableButtonGlint(); - this.showItemIdsDeveloper = cfg.isShowItemIdsDeveloper(); - this.muteClickErrors = cfg.isMuteClickErrors(); - this.enableCloseSound = cfg.isEnableCloseSound(); - this.chatPrefix = cfg.getChatPrefix(); - this.soundVolume = cfg.getSoundVolume(); - this.commandExecuteMode = cfg.getCommandExecuteMode(); - } - - private static String nextExecuteMode(String current) { - if ("CHAT".equalsIgnoreCase(current)) return "SYSTEM"; - if ("SYSTEM".equalsIgnoreCase(current)) return "SILENT"; - return "CHAT"; } @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (chatPrefixField != null && chatPrefixField.visible && chatPrefixField.keyPressed(keyCode, scanCode, modifiers)) { - return true; + protected void init() { + int cx = width / 2; + int y = 30; + + // ── Settings ───────────────────────────────────────────────────── + + addToggle(cx, y, "allow_console_run_with", + "Allow run_with: console", + "Permit buttons to run commands with console (OP-level) permission.", + allowConsoleRunWith, + v -> allowConsoleRunWith = v); + y += 22; + + addToggle(cx, y, "log_unknown_items", + "Log unknown item IDs", + "Print a WARN to the log when a button uses an unrecognized item ID.", + logUnknownItems, + v -> logUnknownItems = v); + y += 22; + + addToggle(cx, y, "log_unknown_sounds", + "Log unknown sound IDs", + "Print a WARN to the log when a sound action uses an unrecognized sound ID.", + logUnknownSounds, + v -> logUnknownSounds = v); + y += 22; + + addToggle(cx, y, "debug_mode", + "Debug mode", + "Log GUI open/close, action execution and placeholder resolution to console.", + debugMode, + v -> debugMode = v); + y += 22; + + addToggle(cx, y, "allow_close_on_move", + "Allow close_on_move", + "Globally permit menus to close automatically when players walk away.", + allowCloseOnMove, + v -> allowCloseOnMove = v); + y += 22; + + addToggle(cx, y, "allow_delayed_actions", + "Allow action delays", + "Globally permit action chains to execute with tick delays.", + allowDelayedActions, + v -> allowDelayedActions = v); + y += 22; + + addToggle(cx, y, "allow_status_effects", + "Allow status effects", + "Globally permit buttons and click actions to manage player potion effects.", + allowStatusEffects, + v -> allowStatusEffects = v); + y += 22; + + addToggle(cx, y, "log_commands", + "Log command executions", + "Write a message to log console every time a GUI button runs a command.", + logCommands, + v -> logCommands = v); + y += 22; + + // Default Tick Rate Controls + addDrawableChild(new TextWidget(cx - 150, y + 4, 150, 10, Text.literal("§fDefault Tick Rate"), textRenderer)); + TextWidget[] defaultTickRateTextRef = new TextWidget[1]; + defaultTickRateTextRef[0] = new TextWidget(cx + 10, y + 4, 40, 10, Text.literal("§e" + defaultTickRate), textRenderer); + addDrawableChild(defaultTickRateTextRef[0]); + + addDrawableChild(ButtonWidget.builder(Text.literal("-5"), btn -> { + defaultTickRate = Math.max(0, defaultTickRate - 5); + defaultTickRateTextRef[0].setMessage(Text.literal("§e" + defaultTickRate)); + }).dimensions(cx + 50, y, 20, 18).build()); + + addDrawableChild(ButtonWidget.builder(Text.literal("+5"), btn -> { + defaultTickRate = Math.min(2400, defaultTickRate + 5); + defaultTickRateTextRef[0].setMessage(Text.literal("§e" + defaultTickRate)); + }).dimensions(cx + 75, y, 20, 18).build()); + y += 22; + + // Permission level — cycle 0-4 + addDrawableChild(new TextWidget(cx - 150, y + 4, 200, 10, + Text.literal("§fCommand permission level"), textRenderer)); + addDrawableChild(ButtonWidget.builder(permLevelText(permissionLevel), btn -> { + permissionLevel = (permissionLevel + 1) % 5; + btn.setMessage(permLevelText(permissionLevel)); + }).dimensions(cx + 60, y, 40, 20).build()); + y += 26; + + // ── Loaded GUI list (Completed Scrollable/Paginated GUI List) ───── + var all = GuiRegistry.INSTANCE.getAll(); + int count = all.size(); + + int guisPerPage = 3; + final int totalPages = Math.max(1, (count + guisPerPage - 1) / guisPerPage); + if (guiListPage >= totalPages) guiListPage = totalPages - 1; + + addDrawableChild(new TextWidget(cx - 150, y, 300, 10, + Text.literal("§7Loaded GUIs: §f" + count + " (Page " + (guiListPage + 1) + "/" + totalPages + ")"), + textRenderer)); + y += 12; + + List> list = new ArrayList<>(all.entrySet()); + int startIdx = guiListPage * guisPerPage; + int endIdx = Math.min(startIdx + guisPerPage, count); + + for (int i = startIdx; i < endIdx; i++) { + var entry = list.get(i); + var id = entry.getKey(); + var def = entry.getValue(); + + // Client Feature: Clickable GUI list entries to open GUI Editor Screen + addDrawableChild(ButtonWidget.builder( + Text.literal("Edit: " + id.getPath() + " (" + def.getRows() + " rows)"), + btn -> MinecraftClient.getInstance().setScreen(new GuiEditorScreen(this, id, def)) + ).dimensions(cx - 150, y, 300, 18).build()); + + y += 20; } - return super.keyPressed(keyCode, scanCode, modifiers); - } - @Override - public boolean charTyped(char chr, int modifiers) { - if (chatPrefixField != null && chatPrefixField.visible && chatPrefixField.charTyped(chr, modifiers)) { - return true; + // Pagination Controls for Loaded GUIs list + if (totalPages > 1) { + ButtonWidget prevBtn = ButtonWidget.builder(Text.literal("§e← Prev"), btn -> { + guiListPage = Math.max(0, guiListPage - 1); + this.init(); // Re-initialize list view + }).dimensions(cx - 150, y, 145, 18).build(); + prevBtn.active = (guiListPage > 0); + addDrawableChild(prevBtn); + + ButtonWidget nextBtn = ButtonWidget.builder(Text.literal("§eNext →"), btn -> { + guiListPage = Math.min(totalPages - 1, guiListPage + 1); + this.init(); // Re-initialize list view + }).dimensions(cx + 5, y, 145, 18).build(); + nextBtn.active = (guiListPage < totalPages - 1); + addDrawableChild(nextBtn); + y += 22; } - return super.charTyped(chr, modifiers); - } - @Override - protected void init() { - int cx = width / 2; - this.clearChildren(); - 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)); - }).dimensions(cx - 125, 22, 80, 18).build(); - addDrawableChild(configTabBtn); - - ButtonWidget guisTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.GUIS ? "§aLoaded GUIs" : "§7Loaded GUIs"), btn -> { - currentTab = Tab.GUIS; - scrollY = 0; - this.init(); - }).dimensions(cx - 40, 22, 80, 18).build(); - guisTabBtn.active = (currentTab != Tab.GUIS); - addDrawableChild(guisTabBtn); - - ButtonWidget otherTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.OTHER ? "§aOther" : "§7Other"), btn -> { - currentTab = Tab.OTHER; - scrollY = 0; - this.init(); - }).dimensions(cx + 45, 22, 80, 18).build(); - otherTabBtn.active = (currentTab != Tab.OTHER); - addDrawableChild(otherTabBtn); - - // 2. Add persistent Action Buttons (Fixed at Bottom) + // ── Buttons ─────────────────────────────────────────────────────── addDrawableChild(ButtonWidget.builder(Text.literal("Save & Close"), btn -> { GuiApiConfig cfg = GuiApiConfig.INSTANCE; cfg.setAllowConsoleRunWith(allowConsoleRunWith); @@ -338,16 +227,6 @@ protected void init() { cfg.setAllowStatusEffects(allowStatusEffects); cfg.setLogCommands(logCommands); cfg.setDefaultTickRate(defaultTickRate); - cfg.setEnableButtonGlint(enableButtonGlint); - cfg.setShowItemIdsDeveloper(showItemIdsDeveloper); - cfg.setMuteClickErrors(muteClickErrors); - cfg.setEnableCloseSound(enableCloseSound); - if (chatPrefixField != null) { - chatPrefix = chatPrefixField.getText(); - } - cfg.setChatPrefix(chatPrefix); - cfg.setSoundVolume(soundVolume); - cfg.setCommandExecuteMode(commandExecuteMode); cfg.save(); MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 105, height - 25, 100, 20).build()); @@ -360,159 +239,35 @@ protected void init() { } else { btn.setMessage(Text.literal("§cNot in-game")); } - }).dimensions(cx, height - 25, 100, 20).build()); + }).dimensions(cx - 0, height - 25, 100, 20).build()); addDrawableChild(ButtonWidget.builder(Text.literal("Cancel"), btn -> MinecraftClient.getInstance().setScreen(parent)) .dimensions(cx + 105, height - 25, 100, 20).build()); + } - // 3. Populate Scrollable Elements based on selected tab - int startY = 48; - if (currentTab == Tab.CONFIG) { - int y = startY; - - addScrollableToggle(cx, y, "Allow run_with: console", - allowConsoleRunWith, v -> allowConsoleRunWith = v); - y += 22; - - addScrollableToggle(cx, y, "Log unknown item IDs", - logUnknownItems, v -> logUnknownItems = v); - y += 22; - - addScrollableToggle(cx, y, "Log unknown sound IDs", - logUnknownSounds, v -> logUnknownSounds = v); - y += 22; - - addScrollableToggle(cx, y, "Debug mode", - debugMode, v -> debugMode = v); - y += 22; - - addScrollableToggle(cx, y, "Allow close_on_move", - allowCloseOnMove, v -> allowCloseOnMove = v); - y += 22; - - addScrollableToggle(cx, y, "Allow action delays", - allowDelayedActions, v -> allowDelayedActions = v); - y += 22; - - addScrollableToggle(cx, y, "Allow status effects", - allowStatusEffects, v -> allowStatusEffects = v); - y += 22; - - addScrollableToggle(cx, y, "Log command executions", - logCommands, v -> logCommands = v); - y += 22; - - // Default Tick Rate Slider (0, 15 etc. number slider input!) - 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)); - y += 24; - - // Command permission level Slider - IntegerSliderWidget permSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Command permission level", 0, 4, permissionLevel, val -> permissionLevel = val); - addDrawableChild(permSlider); - scrollableElements.add(new ScrollableElement(permSlider, permSlider, y, 20)); - y += 26; - - } else if (currentTab == Tab.OTHER) { - int y = startY; - - addScrollableToggle(cx, y, "Enable button glint", - enableButtonGlint, v -> enableButtonGlint = v); - y += 22; - - addScrollableToggle(cx, y, "Show developer item IDs", - showItemIdsDeveloper, v -> showItemIdsDeveloper = v); - y += 22; - - addScrollableToggle(cx, y, "Mute click errors", - muteClickErrors, v -> muteClickErrors = v); - y += 22; - - addScrollableToggle(cx, y, "Play close sound", - enableCloseSound, v -> enableCloseSound = v); - y += 22; - - // ── 3 New Interactive Input Features ── - - // Input Type 1: Text Field for Chat Prefix - TextWidget chatPrefixLabel = new TextWidget(cx - 150, y, 300, 10, Text.literal("§eChat Prefix"), textRenderer); - chatPrefixField = new TextFieldWidget(textRenderer, cx - 150, y + 12, 300, 18, Text.literal("Prefix")); - chatPrefixField.setMaxLength(128); - chatPrefixField.setText(chatPrefix); - addDrawableChild(chatPrefixLabel); - addDrawableChild(chatPrefixField); - scrollableElements.add(new ScrollableElement(chatPrefixLabel, chatPrefixLabel, y, 10)); - scrollableElements.add(new ScrollableElement(chatPrefixField, chatPrefixField, y + 12, 18)); - y += 32; - - // Input Type 2: Slider for Sound Volume - IntegerSliderWidget volumeSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Sound Volume (%)", 0, 100, soundVolume, val -> soundVolume = val); - addDrawableChild(volumeSlider); - scrollableElements.add(new ScrollableElement(volumeSlider, volumeSlider, y, 20)); - y += 24; - - // Input Type 3: Cycle Button for Message Execute Mode - TextWidget execModeLabel = new TextWidget(cx - 150, y + 4, 200, 10, Text.literal("§fMessage Execute Mode"), textRenderer); - ButtonWidget execModeBtn = ButtonWidget.builder(Text.literal(commandExecuteMode), btn -> { - commandExecuteMode = nextExecuteMode(commandExecuteMode); - btn.setMessage(Text.literal(commandExecuteMode)); - }).dimensions(cx + 60, y, 60, 20).build(); - addDrawableChild(execModeLabel); - addDrawableChild(execModeBtn); - scrollableElements.add(new ScrollableElement(execModeLabel, execModeLabel, y, 10)); - scrollableElements.add(new ScrollableElement(execModeBtn, execModeBtn, y, 20)); - y += 26; - - } else { - // Tab.GUIS - int y = startY; - var all = GuiRegistry.INSTANCE.getAll(); - int count = all.size(); - - TextWidget headerWidget = new TextWidget(cx - 150, y, 300, 10, - Text.literal("§7Loaded GUIs: §f" + count), - textRenderer); - addDrawableChild(headerWidget); - scrollableElements.add(new ScrollableElement(headerWidget, headerWidget, y, 10)); - y += 16; - - List> list = new ArrayList<>(all.entrySet()); - for (int i = 0; i < count; i++) { - var entry = list.get(i); - var id = entry.getKey(); - var def = entry.getValue(); - - ButtonWidget btnWidget = ButtonWidget.builder( - Text.literal("Edit: " + id.getPath() + " (" + def.getRows() + " rows)"), - btn -> MinecraftClient.getInstance().setScreen(new GuiEditorScreen(this, id, def)) - ).dimensions(cx - 150, y, 300, 18).build(); - - addDrawableChild(btnWidget); - scrollableElements.add(new ScrollableElement(btnWidget, btnWidget, y, 18)); - y += 22; - } - } + @Override + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + super.render(ctx, mouseX, mouseY, delta); + // Title + ctx.drawCenteredTextWithShadow(textRenderer, + Text.literal("§6GUI API §7Settings"), width / 2, 10, 0xFFFFFF); + // Divider above buttons + ctx.fill(width / 2 - 150, height - 32, width / 2 + 150, height - 31, 0x44FFFFFF); + } - // 4. Calculate max scroll Y - int maxContentY = startY; - for (ScrollableElement se : scrollableElements) { - maxContentY = Math.max(maxContentY, se.originalY + se.height); - } - int viewportHeight = (height - 35) - startY; - maxScrollY = Math.max(0, maxContentY - (height - 35)); + @Override + public void close() { + MinecraftClient.getInstance().setScreen(parent); + } - if (scrollY > maxScrollY) { - scrollY = maxScrollY; - } + // ── Toggle helper ───────────────────────────────────────────────────── - updateScrollPositions(); - } + private void addToggle(int cx, int y, String key, String label, String tooltip, + boolean initial, java.util.function.Consumer onChange) { + addDrawableChild(new TextWidget(cx - 150, y + 4, 200, 10, + Text.literal("§f" + label), textRenderer)); - 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"); @@ -520,89 +275,7 @@ private void addScrollableToggle(int cx, int y, String label, boolean initial, j btn.setMessage(toggleText(next)); }).dimensions(cx + 60, y, 40, 20).build(); - addDrawableChild(labelWidget); addDrawableChild(ref[0]); - - scrollableElements.add(new ScrollableElement(labelWidget, labelWidget, y, 10)); - scrollableElements.add(new ScrollableElement(ref[0], ref[0], y, 20)); - } - - private void updateScrollPositions() { - int topBoundary = 44; - int bottomBoundary = height - 35; - - for (ScrollableElement se : scrollableElements) { - int newY = se.originalY - (int)scrollY; - if (se.drawable instanceof net.minecraft.client.gui.widget.ClickableWidget widget) { - widget.setY(newY); - boolean inViewport = (newY + se.height > topBoundary && newY < bottomBoundary); - widget.visible = inViewport; - widget.active = inViewport; - } - } - } - - @Override - public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { - scrollY -= verticalAmount * 12; - if (scrollY < 0) scrollY = 0; - if (scrollY > maxScrollY) scrollY = maxScrollY; - updateScrollPositions(); - return true; - } - - @Override - public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { - for (ScrollableElement se : scrollableElements) { - if (se.drawable instanceof net.minecraft.client.gui.widget.ClickableWidget widget) { - widget.visible = false; - } - } - - super.render(ctx, mouseX, mouseY, delta); - - int topBoundary = 44; - int bottomBoundary = height - 35; - for (ScrollableElement se : scrollableElements) { - int newY = se.originalY - (int)scrollY; - if (se.drawable instanceof net.minecraft.client.gui.widget.ClickableWidget widget) { - boolean inViewport = (newY + se.height > topBoundary && newY < bottomBoundary); - widget.visible = inViewport; - } - } - - ctx.drawCenteredTextWithShadow(textRenderer, - Text.literal("§6GUI API §7Settings"), width / 2, 8, 0xFFFFFF); - - ctx.fill(width / 2 - 150, 42, width / 2 + 150, 43, 0x44FFFFFF); - - ctx.enableScissor(0, topBoundary, width, bottomBoundary); - for (ScrollableElement se : scrollableElements) { - if (se.drawable instanceof net.minecraft.client.gui.widget.ClickableWidget widget) { - if (widget.visible) { - widget.render(ctx, mouseX, mouseY, delta); - } - } - } - ctx.disableScissor(); - - 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 thumbY = topBoundary + (int)(scrollY / maxScrollY * (trackHeight - thumbHeight)); - - ctx.fill(rx, topBoundary, rx + 4, bottomBoundary, 0x22FFFFFF); - ctx.fill(rx, thumbY, rx + 4, thumbY + thumbHeight, 0x88FFFFFF); - } - - ctx.fill(width / 2 - 150, height - 32, width / 2 + 150, height - 31, 0x44FFFFFF); - } - - @Override - public void close() { - MinecraftClient.getInstance().setScreen(parent); } private static Text toggleText(boolean on) { @@ -621,7 +294,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 +329,6 @@ public void updateFiller(Optional newFiller) { this.filler = newFiller; } - - - - @Override protected void init() { int cx = width / 2; @@ -737,7 +407,7 @@ protected void init() { tickRate, closeOnMove ); - MinecraftClient.getInstance().setScreen(new GuiSaveProcessScreen(parent, this, id, newDef)); + MinecraftClient.getInstance().setScreen(new GuiSaveLoadingScreen(parent, id, newDef)); }).dimensions(cx - 105, height - 25, 100, 20).build()); // Cancel @@ -777,96 +447,48 @@ private static Text toggleText(boolean on) { // ── GUI Save Loading Screen (Client Feature: Persists Datapack on Disk) ── - static class GuiSaveProcessScreen extends Screen { - enum State { - CONFIRM_SAVE, - SAVING_PROGRESS, - CONFIRM_RELOAD, - RELOADING_SPINNER - } - - private final Screen settingsScreen; - private final Screen editorScreen; + static class GuiSaveLoadingScreen extends Screen { + private final Screen parent; private final net.minecraft.util.Identifier id; private final GuiDefinition newDef; - - private State state = State.CONFIRM_SAVE; private int ticksElapsed = 0; - GuiSaveProcessScreen(Screen settingsScreen, Screen editorScreen, net.minecraft.util.Identifier id, GuiDefinition newDef) { - super(Text.literal("Save Progress")); - this.settingsScreen = settingsScreen; - this.editorScreen = editorScreen; + GuiSaveLoadingScreen(Screen parent, net.minecraft.util.Identifier id, GuiDefinition newDef) { + super(Text.literal("Saving GUI...")); + this.parent = parent; this.id = id; this.newDef = newDef; } @Override protected void init() { - this.clearChildren(); ticksElapsed = 0; - - int cx = width / 2; - int cy = height / 2; - - if (state == State.CONFIRM_SAVE) { - addDrawableChild(ButtonWidget.builder(Text.literal("Yes, Save Changes"), btn -> { - state = State.SAVING_PROGRESS; - this.init(); - }).dimensions(cx - 110, cy + 20, 100, 20).build()); - - addDrawableChild(ButtonWidget.builder(Text.literal("No, Back"), btn -> { - MinecraftClient.getInstance().setScreen(editorScreen); - }).dimensions(cx + 10, cy + 20, 100, 20).build()); - } else if (state == State.CONFIRM_RELOAD) { - addDrawableChild(ButtonWidget.builder(Text.literal("Yes, Reload"), btn -> { - if (MinecraftClient.getInstance().player != null) { - MinecraftClient.getInstance().player.networkHandler.sendChatCommand("guiapi reload"); - } - state = State.RELOADING_SPINNER; - this.init(); - }).dimensions(cx - 110, cy + 20, 100, 20).build()); - - addDrawableChild(ButtonWidget.builder(Text.literal("No, Skip"), btn -> { - MinecraftClient.getInstance().setScreen(settingsScreen); - }).dimensions(cx + 10, cy + 20, 100, 20).build()); - } } @Override public void tick() { - if (state == State.SAVING_PROGRESS) { - ticksElapsed++; - if (ticksElapsed >= 40) { - MinecraftServer server = MinecraftClient.getInstance().getServer(); - if (server != null) { - dev.toolkitmc.guiapi.loader.GuiRegistry.INSTANCE.put(id, newDef); - dev.toolkitmc.guiapi.loader.GuiRegistry.INSTANCE.saveToDisk(server, id, newDef); + ticksElapsed++; + if (ticksElapsed == 40) { // After 2 seconds, write to disk and trigger reload + MinecraftServer server = MinecraftClient.getInstance().getServer(); + if (server != null) { + // 1. Update in-memory + dev.toolkitmc.guiapi.loader.GuiRegistry.INSTANCE.put(id, newDef); + // 2. Save directly to the datapack JSON file on disk + dev.toolkitmc.guiapi.loader.GuiRegistry.INSTANCE.saveToDisk(server, id, newDef); + + // 3. Reload datapacks so everything syncs perfectly + if (MinecraftClient.getInstance().player != null) { + MinecraftClient.getInstance().player.networkHandler.sendChatCommand("guiapi reload"); } - state = State.CONFIRM_RELOAD; - this.init(); - } - } else if (state == State.RELOADING_SPINNER) { - ticksElapsed++; - if (ticksElapsed >= 30) { - MinecraftClient.getInstance().setScreen(settingsScreen); } + } else if (ticksElapsed >= 55) { // Return to settings screen + MinecraftClient.getInstance().setScreen(parent); } } - @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! - } - @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) { - super.render(ctx, mouseX, mouseY, delta); - } + super.render(ctx, mouseX, mouseY, delta); int cx = width / 2; int cy = height / 2; @@ -874,53 +496,28 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { long time = System.currentTimeMillis(); int rainbowColor = java.awt.Color.HSBtoRGB((time % 1500) / 1500f, 0.8f, 0.8f); - switch (state) { - case CONFIRM_SAVE -> { - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ Save Confirmation ★"), cx, cy - 30, 0xFFFFFF); - 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..."; - } - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ Writing Datapack ★"), cx, cy - 40, 0xFFFFFF); - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal(status), cx, cy - 10, rainbowColor); - - int barWidth = 160; - int progress = Math.min(barWidth, (ticksElapsed * barWidth) / 40); - ctx.fill(cx - barWidth / 2, cy + 15, cx + barWidth / 2, cy + 19, 0x44FFFFFF); - ctx.fill(cx - barWidth / 2, cy + 15, cx - barWidth / 2 + progress, cy + 19, rainbowColor); - } - case CONFIRM_RELOAD -> { - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ Save Complete! ★"), cx, cy - 30, 0xFFFFFF); - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§fDo you want to reload GUIs now to sync in-game?"), cx, cy - 10, 0xAAAAAA); - } - case RELOADING_SPINNER -> { - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ Reloading Resources ★"), cx, cy - 40, 0xFFFFFF); - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("Please wait, reloading..."), cx, cy - 15, 0x88FFFFFF); - - double angleSpeed = (time % 1000) / 1000.0 * 2.0 * Math.PI; - int numDots = 8; - int radius = 12; - for (int i = 0; i < numDots; i++) { - 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); - } + // Draw a solid professional background + ctx.fill(0, 0, width, height, 0xDD050505); - String[] spinner = {"|", "/", "-", "\\"}; - String spinChar = spinner[(ticksElapsed / 3) % 4]; - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§e" + spinChar), cx, cy + 35, 0xFFFFFF); - } + String status = "Locating Datapack Folder..."; + if (ticksElapsed >= 20 && ticksElapsed < 40) { + status = "Overwriting Datapack JSON File..."; + } else if (ticksElapsed >= 40) { + status = "Reloading GUI API Resources..."; } + + ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ GUI API Datapack Writer ★"), cx, cy - 40, 0xFFFFFF); + ctx.drawCenteredTextWithShadow(textRenderer, Text.literal(status), cx, cy - 10, rainbowColor); + + // Draw progress bar + int barWidth = 160; + int progress = Math.min(barWidth, (ticksElapsed * barWidth) / 55); + ctx.fill(cx - barWidth / 2, cy + 15, cx + barWidth / 2, cy + 19, 0x44FFFFFF); + ctx.fill(cx - barWidth / 2, cy + 15, cx - barWidth / 2 + progress, cy + 19, rainbowColor); } } - // ── Filler Editor Screen ───────────────────────────────────────────────── + + // ── Filler Editor Screen ───────────────────────────────────────────────── static class FillerEditorScreen extends Screen { private final GuiEditorScreen parent; @@ -945,10 +542,6 @@ static class FillerEditorScreen extends Screen { this.hideTooltip = fill.hideTooltip(); } - - - - @Override protected void init() { int cx = width / 2; @@ -1053,7 +646,6 @@ static class ButtonListScreen extends Screen { private final net.minecraft.util.Identifier id; private final GuiDefinition def; private final List buttonsList; - private int listPage = 0; ButtonListScreen(GuiEditorScreen parent, net.minecraft.util.Identifier id, GuiDefinition def) { super(Text.literal("Buttons List")); @@ -1066,54 +658,30 @@ static class ButtonListScreen extends Screen { @Override protected void init() { int cx = width / 2; - int y = 30; + int y = 35; addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eSelect a button to edit or delete:"), textRenderer)); - y += 12; - - int buttonsPerPage = 6; - final int totalPages = Math.max(1, (buttonsList.size() + buttonsPerPage - 1) / buttonsPerPage); - if (listPage >= totalPages) listPage = totalPages - 1; - - addDrawableChild(new TextWidget(cx - 150, y, 300, 10, - Text.literal("§7Loaded Buttons: §f" + buttonsList.size() + " (Page " + (listPage + 1) + "/" + totalPages + ")"), - textRenderer)); y += 14; - int startIdx = listPage * buttonsPerPage; - int endIdx = Math.min(startIdx + buttonsPerPage, buttonsList.size()); - - for (int i = startIdx; i < endIdx; i++) { + // List of existing buttons (limit to first 7 to fit, or provide navigation) + int shown = 0; + for (int i = 0; i < buttonsList.size(); i++) { + if (shown >= 7) { + addDrawableChild(new TextWidget(cx - 150, y, 300, 10, + Text.literal("§8... and " + (buttonsList.size() - 7) + " more buttons"), textRenderer)); + break; + } final int index = i; GuiDefinition.Button btn = buttonsList.get(index); String labelText = "Slot " + btn.slot() + ": " + (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); - this.init(); - }).dimensions(cx - 150, y, 145, 18).build(); - prevBtn.active = (listPage > 0); - addDrawableChild(prevBtn); - - ButtonWidget nextBtn = ButtonWidget.builder(Text.literal("§eNext →"), btn -> { - listPage = Math.min(totalPages - 1, listPage + 1); - this.init(); - }).dimensions(cx + 5, y, 145, 18).build(); - nextBtn.active = (listPage < totalPages - 1); - addDrawableChild(nextBtn); + shown++; } y = height - 30; @@ -1121,16 +689,17 @@ protected void init() { // Add New Button addDrawableChild(ButtonWidget.builder(Text.literal("§aAdd New Button"), btn -> { GuiDefinition.Button newBtn = new GuiDefinition.Button( - 0, listPage, "minecraft:stone", "New Button", List.of(), false, + 0, 0, "minecraft:stone", "New Button", List.of(), false, GuiDefinition.ClickType.ANY, Optional.empty(), List.of(), Optional.empty(), 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 addDrawableChild(ButtonWidget.builder(Text.literal("Save Buttons"), btn -> { + // Update parent editor screen copy! parent.updateButtons(buttonsList); MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 50, y, 100, 20).build()); @@ -1159,351 +728,153 @@ 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 Properties Editor Screen ────────────────────────────────────── static class ButtonEditorScreen extends Screen { - private enum Tab { - BASIC, APPEARANCE, LOGIC - } - private final ButtonListScreen parent; private final int index; private final GuiDefinition.Button btn; - private Tab currentTab = Tab.BASIC; - + private TextFieldWidget slotField; private TextFieldWidget itemField; private TextFieldWidget nameField; - private TextFieldWidget loreField; + private TextFieldWidget amountField; + private TextFieldWidget loreField; // Combined Lore input field (separated by ;) + + // Combined Actions input field (separated by ;) private TextFieldWidget actionsField; - private TextFieldWidget conditionField; - private int slot; - private int amount; - private int pageVal; private boolean glint; - private GuiDefinition.ClickType clickType; private Optional toggle; - // Caching fields to fix wipe-on-tab-switch bug! - private String itemText; - private String nameText; - private String loreText; - private String actionsText; - private String conditionText; - ButtonEditorScreen(ButtonListScreen parent, int index, GuiDefinition.Button btn) { super(Text.literal("Edit Button")); this.parent = parent; this.index = index; this.btn = btn; - this.slot = btn.slot(); - this.pageVal = btn.page(); this.glint = btn.glint(); this.toggle = btn.toggle(); - this.clickType = btn.clickType(); - - int parsedAmount = 1; - try { - parsedAmount = Integer.parseInt(btn.amount()); - } 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() : ""; } public void updateToggle(Optional newToggle) { this.toggle = newToggle; } - private static GuiDefinition.ClickType nextClickType(GuiDefinition.ClickType current) { - GuiDefinition.ClickType[] vals = GuiDefinition.ClickType.values(); - return vals[(current.ordinal() + 1) % vals.length]; - } - - private void saveCurrentTabFields() { - if (itemField != null) itemText = itemField.getText(); - if (nameField != null) nameText = nameField.getText(); - if (loreField != null) loreText = loreField.getText(); - if (actionsField != null) actionsText = actionsField.getText(); - if (conditionField != null) conditionText = conditionField.getText(); - } - @Override protected void init() { int cx = width / 2; - this.clearChildren(); - - // 1. Add Tab buttons - ButtonWidget basicTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.BASIC ? "§aBasic" : "§7Basic"), btn -> { - saveCurrentTabFields(); - currentTab = Tab.BASIC; - this.init(); - }).dimensions(cx - 125, 22, 80, 18).build(); - basicTabBtn.active = (currentTab != Tab.BASIC); - addDrawableChild(basicTabBtn); - - ButtonWidget appTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.APPEARANCE ? "§aAppearance" : "§7Appearance"), btn -> { - saveCurrentTabFields(); - currentTab = Tab.APPEARANCE; - this.init(); - }).dimensions(cx - 40, 22, 80, 18).build(); - appTabBtn.active = (currentTab != Tab.APPEARANCE); - addDrawableChild(appTabBtn); - - ButtonWidget logicTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.LOGIC ? "§aLogic" : "§7Logic"), btn -> { - saveCurrentTabFields(); - currentTab = Tab.LOGIC; - this.init(); - }).dimensions(cx + 45, 22, 80, 18).build(); - logicTabBtn.active = (currentTab != Tab.LOGIC); - addDrawableChild(logicTabBtn); - - // 2. Add bottom buttons + int y = 25; + + // Slot Input + addDrawableChild(new TextWidget(cx - 150, y, 100, 10, Text.literal("§eSlot (0-53)"), textRenderer)); + slotField = new TextFieldWidget(textRenderer, cx - 150, y + 12, 60, 18, Text.literal("Slot")); + slotField.setText(String.valueOf(btn.slot())); + addDrawableChild(slotField); + + // Amount Input + addDrawableChild(new TextWidget(cx - 80, y, 100, 10, Text.literal("§eAmount"), textRenderer)); + amountField = new TextFieldWidget(textRenderer, cx - 80, y + 12, 60, 18, Text.literal("Amount")); + amountField.setText(btn.amount()); + addDrawableChild(amountField); + + // Glint Toggle + addDrawableChild(new TextWidget(cx - 10, y, 100, 10, Text.literal("§eGlint"), textRenderer)); + ButtonWidget[] glintBtnRef = new ButtonWidget[1]; + glintBtnRef[0] = ButtonWidget.builder(toggleText(glint), b -> { + glint = !glint; + b.setMessage(toggleText(glint)); + }).dimensions(cx - 10, y + 12, 40, 18).build(); + addDrawableChild(glintBtnRef[0]); + + // Edit Toggle Properties Navigation + addDrawableChild(new TextWidget(cx + 40, y, 110, 10, Text.literal("§eToggle Button"), textRenderer)); + ButtonWidget[] toggleBtnRef = new ButtonWidget[1]; + toggleBtnRef[0] = ButtonWidget.builder(Text.literal(toggle.isPresent() ? "§aCONFIGURED" : "§cDISABLED"), b -> { + MinecraftClient.getInstance().setScreen(new ToggleEditorScreen(this, toggle)); + }).dimensions(cx + 40, y + 12, 110, 18).build(); + addDrawableChild(toggleBtnRef[0]); + y += 38; + + // Item ID Input + addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eItem ID"), textRenderer)); + y += 12; + itemField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 18, Text.literal("Item ID")); + itemField.setMaxLength(256); + itemField.setText(btn.item()); + addDrawableChild(itemField); + y += 24; + + // Display Name Input + addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eDisplay Name"), textRenderer)); + y += 12; + nameField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 18, Text.literal("Display Name")); + nameField.setMaxLength(128); + nameField.setText(btn.name()); + addDrawableChild(nameField); + y += 24; + + // Lore Input (joined by ;) + addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eLore Lines (Separate by semicolon ';')"), textRenderer)); + y += 12; + loreField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 18, Text.literal("Lore")); + loreField.setMaxLength(512); + loreField.setText(String.join(";", btn.lore())); + addDrawableChild(loreField); + y += 24; + + // Multiple Actions Input (joined by ;) + addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eActions (Format: 'type:value' or 'type:varKey:value', separate by ';')"), textRenderer)); + y += 12; + actionsField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 18, Text.literal("Actions")); + actionsField.setMaxLength(512); + actionsField.setText(serializeActionsToString(btn.actions())); + addDrawableChild(actionsField); + y += 32; + + // Actions + // Save/Apply 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 slotVal = 0; + try { + slotVal = Math.max(0, Integer.parseInt(slotField.getText())); + } catch (NumberFormatException ignored) {} // Build Lore list List finalLore = new ArrayList<>(); - if (!loreText.isEmpty()) { - for (String s : loreText.split(";")) { + String loreTxt = loreField.getText(); + if (!loreTxt.isEmpty()) { + for (String s : loreTxt.split(";")) { finalLore.add(s); } } // Build Actions list from semicolon-separated string List finalActions = new ArrayList<>(); - if (!actionsText.isEmpty()) { - for (String s : actionsText.split(";")) { + String actionsTxt = actionsField.getText(); + if (!actionsTxt.isEmpty()) { + for (String s : actionsTxt.split(";")) { finalActions.add(parseActionFromString(s)); } } if (finalActions.isEmpty()) { - finalActions.add(new GuiDefinition.ButtonAction(GuiDefinition.ActionType.NONE, "")); - } - - // Build Condition - Optional finalCondition = Optional.empty(); - if (!conditionText.isEmpty()) { - String[] condParts = conditionText.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) {} - } + finalActions.add(new GuiDefinition.ButtonAction(GuiDefinition.ActionType.CLOSE, "")); } GuiDefinition.Button newBtn = new GuiDefinition.Button( - slot, - pageVal, - itemText, - nameText, + slotVal, + btn.page(), + itemField.getText(), + nameField.getText(), finalLore, glint, - clickType, - finalCondition, + btn.clickType(), + btn.condition(), finalActions, toggle, btn.customModelData(), btn.itemModel(), - String.valueOf(amount), + amountField.getText(), btn.hideTooltip(), btn.hideAdditionalTooltip() ); @@ -1511,121 +882,34 @@ protected void init() { MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 155, height - 25, 100, 20).build()); + // Delete Button addDrawableChild(ButtonWidget.builder(Text.literal("§cDelete Button"), b -> { parent.deleteButton(index); MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 50, height - 25, 100, 20).build()); + // Cancel addDrawableChild(ButtonWidget.builder(Text.literal("Cancel"), b -> MinecraftClient.getInstance().setScreen(parent)) .dimensions(cx + 55, height - 25, 100, 20).build()); - - // 3. Tab Specific Inputs - 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); - itemField.setText(itemText); - 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); - nameField.setText(nameText); - addDrawableChild(nameField); - - } 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; - b.setMessage(toggleText(glint)); - }).dimensions(cx + 60, y, 40, 18).build(); - 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)); - }).dimensions(cx + 40, y, 110, 18).build(); - 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); - loreField.setText(loreText); - addDrawableChild(loreField); - - } 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); - b.setMessage(Text.literal(clickType.name())); - }).dimensions(cx + 60, y, 60, 18).build(); - 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); - conditionField.setText(conditionText); - 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); - actionsField.setText(actionsText); - addDrawableChild(actionsField); - } } @Override public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { super.render(ctx, mouseX, mouseY, delta); ctx.drawCenteredTextWithShadow(textRenderer, - Text.literal("§6Edit Button Properties"), width / 2, 8, 0xFFFFFF); + Text.literal("§6Edit Button Properties"), width / 2, 10, 0xFFFFFF); ctx.fill(width / 2 - 150, height - 32, width / 2 + 150, height - 31, 0x44FFFFFF); } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if ((itemField != null && itemField.keyPressed(keyCode, scanCode, modifiers)) || - (nameField != null && nameField.keyPressed(keyCode, scanCode, modifiers)) || - (conditionField != null && conditionField.keyPressed(keyCode, scanCode, modifiers)) || - (loreField != null && loreField.keyPressed(keyCode, scanCode, modifiers)) || - (actionsField != null && actionsField.keyPressed(keyCode, scanCode, modifiers))) { + if (slotField.keyPressed(keyCode, scanCode, modifiers) || + amountField.keyPressed(keyCode, scanCode, modifiers) || + itemField.keyPressed(keyCode, scanCode, modifiers) || + nameField.keyPressed(keyCode, scanCode, modifiers) || + loreField.keyPressed(keyCode, scanCode, modifiers) || + actionsField.keyPressed(keyCode, scanCode, modifiers)) { return true; } return super.keyPressed(keyCode, scanCode, modifiers); @@ -1633,11 +917,12 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { @Override public boolean charTyped(char chr, int modifiers) { - if ((itemField != null && itemField.charTyped(chr, modifiers)) || - (nameField != null && nameField.charTyped(chr, modifiers)) || - (conditionField != null && conditionField.charTyped(chr, modifiers)) || - (loreField != null && loreField.charTyped(chr, modifiers)) || - (actionsField != null && actionsField.charTyped(chr, modifiers))) { + if (slotField.charTyped(chr, modifiers) || + amountField.charTyped(chr, modifiers) || + itemField.charTyped(chr, modifiers) || + nameField.charTyped(chr, modifiers) || + loreField.charTyped(chr, modifiers) || + actionsField.charTyped(chr, modifiers)) { return true; } return super.charTyped(chr, modifiers); @@ -1647,7 +932,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; @@ -1667,10 +953,6 @@ static class ToggleEditorScreen extends Screen { this.currentToggle = currentToggle; } - - - - @Override protected void init() { int cx = width / 2; From 01ad9bf8cfaca52a780de9e3814b618a314d763b Mon Sep 17 00:00:00 2001 From: Legends11 <235496468+tickwarden@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:54:52 +0300 Subject: [PATCH 2/3] Add files via upload --- .../data/example/gui/new_features_demo.json | 281 +++++++++--------- .../toolkitmc/guiapi/command/GuiCommand.java | 72 ++++- .../toolkitmc/guiapi/config/GuiApiConfig.java | 15 + .../guiapi/gui/BarrelGuiHandler.java | 79 +++++ .../toolkitmc/guiapi/gui/GuiDefinition.java | 5 +- 5 files changed, 307 insertions(+), 145 deletions(-) diff --git a/example-datapack/data/example/gui/new_features_demo.json b/example-datapack/data/example/gui/new_features_demo.json index 7c6c527..e1284a2 100644 --- a/example-datapack/data/example/gui/new_features_demo.json +++ b/example-datapack/data/example/gui/new_features_demo.json @@ -1,215 +1,210 @@ { - "title": "§dMaster GUI Demo §8— {player}", + "title": "§6New Features §8— {player} §7[{page1}/{pages}]", "rows": 4, "tick_rate": 20, - "close_on_move": true, "filler": { - "item": "minecraft:gray_stained_glass_pane", + "item": "minecraft:black_stained_glass_pane", "name": " ", "glint": false, "hide_tooltip": true }, "on_open": [ - { "type": "set_var", "var": "counter", "value": "1" } + { "type": "sound", "value": "minecraft:block.note_block.pling:1.0:1.2" } ], "buttons": [ { - "slot": 4, - "item": "minecraft:player_head", - "name": "§d★ Master GUI API Demo ★", + "slot": 4, "page": 0, + "item": "minecraft:nether_star", + "name": "§6New Features Demo", + "glint": true, "lore": [ - "§7Current value: §f{var:counter}", - "§7Current Score (Coins): §e{score:coins}", - "§7XP Level: §a{player}'s level", - "§7Auto-refreshing every 1 second (tick_rate: 20)", - "§7Closes automatically if you walk away (close_on_move: true)", - "§7Empty slots are automatically filled (filler object)" - ] + "§7This GUI demonstrates the 3 new features:", + "§a► give_item §8— item ver", + "§b► broadcast §8— tüm sunucuya mesaj", + "§e► Yeni placeholder'lar", + " ", + "§8{gui} §7Page {page1}/{pages}" + ], + "actions": [{ "type": "sound", "value": "minecraft:entity.experience_orb.pickup" }] }, + { - "slot": 10, - "item": "minecraft:lime_concrete", - "name": "§a1. Dynamic Stack Size & Action Delays", - "amount": "{var:counter}", + "slot": 10, "page": 0, + "item": "minecraft:diamond", + "name": "§b► give_item §7— Tek Item", "lore": [ - "§7Updates slot item count based on a variable,", - "§7completely flicker-free!", - "§7Next count: §f{var:counter}", - "§7Increments by 1, waits 20 ticks (1s), then plays sound!" + "§7Envantere 1 diamond ver.", + "§8Syntax: give_item:", + " ", + "§aKliğa bas!" ], "actions": [ - { "type": "add_var", "var": "counter", "value": "1" }, - { "type": "action_bar", "value": "§aCounter updated to {var:counter}! Sound playing in 1s..." }, - { "type": "refresh" }, - { "type": "sound", "value": "minecraft:entity.experience_orb.pickup:0.5:1.2", "delay": 20 } + { "type": "give_item", "value": "minecraft:diamond" }, + { "type": "sound", "value": "minecraft:entity.item.pickup" }, + { "type": "action_bar", "value": "§b+1 Diamond alındı!" } ] }, + { - "slot": 12, - "item": "minecraft:diamond_sword", - "name": "§b2. Item Shop §a(Buy Iron)", + "slot": 12, "page": 0, + "item": "minecraft:emerald", + "name": "§a► give_item §7— Miktar ile", "lore": [ - "§7Cost: §e5 Gold Nuggets", - "§7Requires gold in inventory.", - "§aClick to purchase!" + "§7Envantere 8 emerald ver.", + "§8Syntax: give_item::", + " ", + "§aKliğa bas!" ], - "condition": { "type": "has_item", "value": "minecraft:gold_nugget:5" }, "actions": [ - { "type": "take_item", "value": "minecraft:gold_nugget:5" }, - { "type": "run_command", "value": "give @s minecraft:iron_ingot", "run_with": "console" }, - { "type": "sound", "value": "minecraft:entity.villager.yes" }, - { "type": "action_bar", "value": "§aPurchase successful!" }, - { "type": "refresh" } + { "type": "give_item", "value": "minecraft:emerald:8" }, + { "type": "sound", "value": "minecraft:entity.item.pickup" }, + { "type": "action_bar", "value": "§a+8 Emerald alındı!" } ] }, + { - "slot": 12, - "item": "minecraft:barrier", - "name": "§c2. Item Shop §7(Locked)", + "slot": 14, "page": 0, + "item": "minecraft:beacon", + "name": "§e► broadcast §7— Chat", "lore": [ - "§7Cost: §e5 Gold Nuggets", - "§cYou do not have enough Gold Nuggets!" + "§7Tüm oyunculara chat mesajı gönder.", + "§8Syntax: broadcast:", + " ", + "§eKliğa bas!" ], - "condition": { "type": "not_item", "value": "minecraft:gold_nugget:5" }, "actions": [ - { "type": "sound", "value": "minecraft:entity.villager.no" }, - { "type": "action_bar", "value": "§cInsufficient funds!" } + { "type": "broadcast", "value": "§6[Duyuru] §f{player} §7bir GUI'den mesaj gönderdi!" }, + { "type": "sound", "value": "minecraft:block.note_block.bell:1.0:1.0" }, + { "type": "action_bar", "value": "§eMesaj tüm oyunculara gönderildi." } ] }, + { - "slot": 14, - "item": "minecraft:gold_block", - "name": "§e5. Scoreboard & ActionBar Support", + "slot": 16, "page": 0, + "item": "minecraft:end_crystal", + "name": "§d► broadcast §7— Action Bar", + "glint": true, "lore": [ - "§7Directly modifies your scoreboard!", - "§7Left-Click: §a+10 Coins", - "§7Right-Click: §c-10 Coins", - "§7Current Coins: §e{score:coins}" + "§7Tüm oyuncuların action bar'ına mesaj.", + "§8Syntax: broadcast:actionbar:", + " ", + "§dKliğa bas!" ], "actions": [ - { "type": "add_score", "value": "coins:10" }, - { "type": "sound", "value": "minecraft:entity.experience_orb.pickup" }, - { "type": "action_bar", "value": "§a+10 Coins added!" }, - { "type": "refresh" } - ], - "click_type": "left" + { "type": "broadcast", "value": "actionbar:§d✦ §f{player} §7bir şey yaptı! §d✦" }, + { "type": "sound", "value": "minecraft:entity.ender_dragon.growl:0.3:1.8" }, + { "type": "action_bar", "value": "§dAction bar mesajı gönderildi." } + ] }, + { - "slot": 14, - "item": "minecraft:gold_block", - "name": "§e5. Scoreboard & ActionBar Support", - "lore": [ - "§7Directly modifies your scoreboard!", - "§7Left-Click: §a+10 Coins", - "§7Right-Click: §c-10 Coins", - "§7Current Coins: §e{score:coins}" - ], + "slot": 31, "page": 0, + "item": "minecraft:arrow", + "name": "§7Sonraki Sayfa →", + "lore": ["§8Page {page1}/{pages}"], "actions": [ - { "type": "sub_score", "value": "coins:10" }, - { "type": "sound", "value": "minecraft:block.anvil.land:0.2:1.5" }, - { "type": "action_bar", "value": "§c-10 Coins removed!" }, - { "type": "refresh" } - ], - "click_type": "right" + { "type": "sound", "value": "minecraft:item.book.page_turn" }, + { "type": "next_page" } + ] }, + { - "slot": 16, - "item": "minecraft:netherite_sword", - "name": "§c6. Hiding Additional Tooltips", + "slot": 4, "page": 1, + "item": "minecraft:experience_bottle", + "name": "§eYeni Placeholder'lar", "lore": [ - "§7Cleans up sword damage modifiers", - "§7using hide_additional_tooltip: true!" - ], - "hide_additional_tooltip": true + "§7Bu sayfa yeni {health}/{food}/{level}/{xp}", + "§7placeholder'larını gösterir.", + " ", + "§c❤ Can: §f{health} / {max_health}", + "§6🍗 Açlık: §f{food} / 20", + "§a✦ Level: §f{level}", + "§b★ XP: §f{xp}", + " ", + "§8tick_rate:20 — değerler otomatik güncellenir." + ], + "actions": [{ "type": "sound", "value": "minecraft:entity.experience_orb.pickup:0.5:1.5" }] }, + { - "slot": 20, - "item": "minecraft:brewing_stand", - "name": "§d8. Status Effect Controller", + "slot": 10, "page": 1, + "item": "minecraft:red_dye", + "name": "§c❤ Can: §f{health} / {max_health}", "lore": [ - "§7Left-Click: §bReceive Speed II (15s)", - "§7Right-Click: §cCure Effects (Drink Milk)" - ], - "actions": [ - { "type": "add_effect", "value": "minecraft:speed:15:1:false" }, - { "type": "sound", "value": "minecraft:entity.generic.drink" }, - { "type": "action_bar", "value": "§bSpeed II Effect granted!" } + "§7Anlık HP durumu.", + "§8Placeholder: §c{health} §8ve §c{max_health}" ], - "click_type": "left" + "actions": [{ "type": "action_bar", "value": "§c❤ Can: {health}/{max_health}" }] }, + { - "slot": 20, - "item": "minecraft:brewing_stand", - "name": "§d8. Status Effect Controller", + "slot": 12, "page": 1, + "item": "minecraft:cooked_beef", + "name": "§6🍗 Açlık: §f{food} / 20", "lore": [ - "§7Left-Click: §bReceive Speed II (15s)", - "§7Right-Click: §cCure Effects (Drink Milk)" + "§7Anlık açlık seviyesi.", + "§8Placeholder: §6{food}" ], - "actions": [ - { "type": "clear_effects" }, - { "type": "sound", "value": "minecraft:entity.cow.milk" }, - { "type": "action_bar", "value": "§cAll effects cleared!" } - ], - "click_type": "right" + "actions": [{ "type": "action_bar", "value": "§6🍗 Açlık: {food}/20" }] }, + { - "slot": 22, + "slot": 14, "page": 1, "item": "minecraft:experience_bottle", - "name": "§a7. Experience Check §7(Level >= 30)", + "name": "§a✦ Level: §f{level}", "lore": [ - "§7Only players with XP Level >= 30", - "§7can click this button!" + "§7Anlık XP level.", + "§8Placeholder: §a{level}" ], - "condition": { "type": "level_gt", "value": "29" }, - "actions": [ - { "type": "sound", "value": "minecraft:entity.player.levelup" }, - { "type": "action_bar", "value": "§aCongratulations! Level check passed." } - ] + "actions": [{ "type": "action_bar", "value": "§a✦ Level: {level}" }] }, + { - "slot": 22, - "item": "minecraft:glass_bottle", - "name": "§c7. Experience Check §7(Locked)", + "slot": 16, "page": 1, + "item": "minecraft:green_dye", + "name": "§b★ XP: §f{xp}", "lore": [ - "§cYour level is below 30!", - "§7This feature is locked." + "§7Toplam XP puanı.", + "§8Placeholder: §b{xp}" ], - "condition": { "type": "level_lt", "value": "30" }, - "actions": [ - { "type": "sound", "value": "minecraft:entity.enderman.teleport:0.5:0.5" }, - { "type": "action_bar", "value": "§cYou must be at least level 30!" } - ] + "actions": [{ "type": "action_bar", "value": "§b★ Total XP: {xp}" }] }, + { - "slot": 24, - "item": "minecraft:book", - "name": "§b3. Multi-Value Custom Model Data", + "slot": 22, "page": 1, + "item": "minecraft:golden_apple", + "name": "§c► give_item §8+ Placeholder Combo", "lore": [ - "§7Advanced 1.21.4+ floats/flags/strings/colors" + "§7Give item ve placeholder birlikte:", + "§7Can durumuna göre mesaj + apple ver.", + " ", + "§c❤ Şu anki can: {health}/{max_health}", + "§aKliğa bas — apple al!" ], - "custom_model_data": { - "floats": [42.5], - "flags": [true, false], - "strings": ["demo_tag"], - "colors": [16711680] - } + "actions": [ + { "type": "give_item", "value": "minecraft:golden_apple" }, + { "type": "sound", "value": "minecraft:entity.item.pickup" }, + { "type": "action_bar", "value": "§c❤ {health}/{max_health} §7can ile golden apple aldın!" } + ] }, + { - "slot": 26, - "item": "minecraft:stick", - "name": "§b3. Custom Item Model", - "lore": [ - "§7Applies custom item_model ID", - "§7directly to the stick item." - ], - "item_model": "my_pack:item/custom_sword" + "slot": 27, "page": 1, + "item": "minecraft:arrow", + "name": "§7← Önceki Sayfa", + "lore": ["§8Page {page1}/{pages}"], + "actions": [ + { "type": "sound", "value": "minecraft:item.book.page_turn" }, + { "type": "prev_page" } + ] }, + { - "slot": 31, + "slot": 35, "page": 1, "item": "minecraft:barrier", - "name": "§cClose", + "name": "§cKapat", "actions": [ - { "type": "sound", "value": "minecraft:ui.button.click" }, + { "type": "sound", "value": "minecraft:block.wooden_button.click_off" }, { "type": "close" } ] } diff --git a/src/main/java/dev/toolkitmc/guiapi/command/GuiCommand.java b/src/main/java/dev/toolkitmc/guiapi/command/GuiCommand.java index cd77250..eb6739a 100644 --- a/src/main/java/dev/toolkitmc/guiapi/command/GuiCommand.java +++ b/src/main/java/dev/toolkitmc/guiapi/command/GuiCommand.java @@ -72,6 +72,19 @@ public static void register(CommandDispatcher dispatcher) { .then(CommandManager.literal("help") .executes(GuiCommand::showHelp)) + .then(CommandManager.literal("info") + .then(CommandManager.argument("id", IdentifierArgumentType.identifier()) + .suggests((ctx, builder) -> { + String input = builder.getRemainingLowerCase(); + GuiRegistry.INSTANCE.getAll().keySet().stream() + .map(Identifier::toString) + .filter(s -> s.contains(input)) + .forEach(builder::suggest); + return builder.buildFuture(); + }) + .executes(GuiCommand::infoGui)) + ) + .then(CommandManager.literal("var") .then(CommandManager.literal("get") .then(CommandManager.argument("target", EntityArgumentType.player()) @@ -175,10 +188,61 @@ private static int varClear(CommandContext ctx) throws com. return count; } + private static int infoGui(CommandContext ctx) { + Identifier id = IdentifierArgumentType.getIdentifier(ctx, "id"); + GuiDefinition def = GuiRegistry.INSTANCE.get(id).orElse(null); + if (def == null) { + ctx.getSource().sendError(Text.literal("[GuiAPI] GUI not found: " + id)); + return 0; + } + + StringBuilder sb = new StringBuilder(); + sb.append("[GuiAPI] Info: ").append(id).append("\n"); + sb.append(" rows=").append(def.getRows()) + .append(" pages=").append(def.getPageCount()) + .append(" buttons=").append(def.getButtons().size()) + .append(" tick_rate=").append(def.getTickRate()) + .append(" close_on_move=").append(def.isCloseOnMove()) + .append(" container=").append(def.getContainerType().name().toLowerCase()) + .append("\n"); + sb.append(" title: ").append(def.getTitle()).append("\n"); + sb.append(" on_open actions: ").append(def.getOnOpen().size()).append("\n"); + sb.append(" on_close actions: ").append(def.getOnClose().size()).append("\n"); + def.getFiller().ifPresent(f -> + sb.append(" filler: ").append(f.item()).append("\n")); + + for (int p = 0; p < def.getPageCount(); p++) { + java.util.List pageBtns = def.getButtonsForPage(p); + sb.append(" [page ").append(p).append("] ").append(pageBtns.size()).append(" button(s)"); + for (GuiDefinition.Button b : pageBtns) { + sb.append("\n slot=").append(b.slot()); + if (b.toggle().isPresent()) { + sb.append(" [toggle tag=").append(b.toggle().get().tag()).append("]"); + } else { + sb.append(" item=").append(b.item()); + if (!b.actions().isEmpty()) { + sb.append(" actions=["); + b.actions().forEach(a -> sb.append(a.type().name().toLowerCase()).append(",")); + sb.deleteCharAt(sb.length() - 1); + sb.append("]"); + } + } + b.condition().ifPresent(c -> + sb.append(" cond=").append(c.type().name().toLowerCase()).append(":").append(c.value())); + } + sb.append("\n"); + } + + String out = sb.toString().trim(); + ctx.getSource().sendFeedback(() -> Text.literal(out), false); + return 1; + } + private static int showHelp(CommandContext ctx) { String help = "[GuiAPI] Commands (permission level 2):\n" + " /guiapi open [targets] - Open a GUI for yourself or target players\n" + + " /guiapi info - Show detailed info about a loaded GUI\n" + " /guiapi list - List all loaded GUI definitions\n" + " /guiapi reload - Reload all datapack resources (including GUIs)\n" + " /guiapi var get - Get a runtime variable\n" + @@ -197,7 +261,13 @@ private static int showHelp(CommandContext ctx) { " var_eq | var_gt | var_lt | var_set\n" + " actions: run_command | close | open_gui | message | sound\n" + " next_page | prev_page | goto_page\n" + - " set_var | add_var | sub_var | reset_var | clear_vars"; + " set_var | add_var | sub_var | reset_var | clear_vars\n" + + " give_item | broadcast\n" + + "\n" + + "give_item value: [:] e.g. minecraft:diamond:3\n" + + "broadcast value: or actionbar:\n" + + "\n" + + "Extra placeholders: {health} {max_health} {food} {level} {xp}"; ctx.getSource().sendFeedback(() -> Text.literal(help), false); return 1; } diff --git a/src/main/java/dev/toolkitmc/guiapi/config/GuiApiConfig.java b/src/main/java/dev/toolkitmc/guiapi/config/GuiApiConfig.java index 80a57a4..b7bfc95 100644 --- a/src/main/java/dev/toolkitmc/guiapi/config/GuiApiConfig.java +++ b/src/main/java/dev/toolkitmc/guiapi/config/GuiApiConfig.java @@ -44,6 +44,9 @@ public final class GuiApiConfig { private int soundVolume = 100; // 9. New Config private String commandExecuteMode = "CHAT"; // 10. New Config + private boolean allowGiveItem = true; // 11. give_item action guard + private boolean allowBroadcast = true; // 12. broadcast action guard + private GuiApiConfig() {} // ── Load / Save ────────────────────────────────────────────────────────── @@ -92,6 +95,10 @@ 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("allow_give_item")) + allowGiveItem = obj.get("allow_give_item").getAsBoolean(); + if (obj.has("allow_broadcast")) + allowBroadcast = obj.get("allow_broadcast").getAsBoolean(); } catch (IOException e) { GuiApiMod.LOGGER.error("[GuiAPI] Failed to load config: {}", e.getMessage()); @@ -117,6 +124,8 @@ public void save() { obj.addProperty("chat_prefix", chatPrefix); obj.addProperty("sound_volume", soundVolume); obj.addProperty("command_execute_mode", commandExecuteMode); + obj.addProperty("allow_give_item", allowGiveItem); + obj.addProperty("allow_broadcast", allowBroadcast); try { Files.writeString(CONFIG_PATH, GSON.toJson(obj)); } catch (IOException e) { @@ -174,4 +183,10 @@ public void save() { public String getCommandExecuteMode() { return commandExecuteMode; } public void setCommandExecuteMode(String v) { commandExecuteMode = v; } + + public boolean isAllowGiveItem() { return allowGiveItem; } + public void setAllowGiveItem(boolean v) { allowGiveItem = v; } + + public boolean isAllowBroadcast() { return allowBroadcast; } + public void setAllowBroadcast(boolean v) { allowBroadcast = v; } } diff --git a/src/main/java/dev/toolkitmc/guiapi/gui/BarrelGuiHandler.java b/src/main/java/dev/toolkitmc/guiapi/gui/BarrelGuiHandler.java index aae8b28..bda5a72 100644 --- a/src/main/java/dev/toolkitmc/guiapi/gui/BarrelGuiHandler.java +++ b/src/main/java/dev/toolkitmc/guiapi/gui/BarrelGuiHandler.java @@ -517,6 +517,11 @@ static String resolve(String text, ServerPlayerEntity player, text = text.replace("{page}", String.valueOf(page)); text = text.replace("{page1}", String.valueOf(page + 1)); text = text.replace("{pages}", String.valueOf(def.getPageCount())); + text = text.replace("{health}", String.valueOf((int) player.getHealth())); + text = text.replace("{max_health}", String.valueOf((int) player.getMaxHealth())); + text = text.replace("{food}", String.valueOf(player.getHungerManager().getFoodLevel())); + text = text.replace("{level}", String.valueOf(player.experienceLevel)); + text = text.replace("{xp}", String.valueOf(player.totalExperience)); // {score:objective} int idx; @@ -930,10 +935,84 @@ static boolean executeAction(ServerPlayerEntity player, GuiDefinition def, } player.clearStatusEffects(); } + case GIVE_ITEM -> { + // Syntax: [:] + // Example: "minecraft:diamond:3" or "minecraft:diamond" + if (!dev.toolkitmc.guiapi.config.GuiApiConfig.INSTANCE.isAllowGiveItem()) { + if (!dev.toolkitmc.guiapi.config.GuiApiConfig.INSTANCE.isMuteClickErrors()) + GuiApiMod.LOGGER.warn("[GuiAPI] give_item action is disabled in config."); + break; + } + String resolved = resolve(action.value(), player, def, currentPage); + giveItemToPlayer(player, resolved); + } + case BROADCAST -> { + // Syntax: [actionbar:] + // Prefix "actionbar:" → send to every player's action bar + // No prefix → send to every player's chat + if (!dev.toolkitmc.guiapi.config.GuiApiConfig.INSTANCE.isAllowBroadcast()) { + if (!dev.toolkitmc.guiapi.config.GuiApiConfig.INSTANCE.isMuteClickErrors()) + GuiApiMod.LOGGER.warn("[GuiAPI] broadcast action is disabled in config."); + break; + } + String resolved = resolve(action.value(), player, def, currentPage); + boolean toActionBar = resolved.startsWith("actionbar:"); + String msg = toActionBar ? resolved.substring("actionbar:".length()) : resolved; + Text broadcastText = Text.literal(msg); + for (ServerPlayerEntity online : server.getPlayerManager().getPlayerList()) { + online.sendMessage(broadcastText, toActionBar); + } + } } return false; } + // ── Give item helper ────────────────────────────────────────────────────── + + /** + * Parses "{item_id}" or "{item_id}:{amount}" and gives the item to the player. + * Falls back to /give command for complex item strings containing NBT/components. + * Uses the Minecraft item registry for simple items. + */ + private static void giveItemToPlayer(ServerPlayerEntity player, String value) { + // Split off trailing amount if the last colon segment is a pure integer + // e.g. "minecraft:diamond:3" → item="minecraft:diamond", amount=3 + // "minecraft:stone" → item="minecraft:stone", amount=1 + String itemId = value; + int amount = 1; + int lastColon = value.lastIndexOf(':'); + if (lastColon > 0) { + String possibleAmount = value.substring(lastColon + 1); + // Make sure the segment before it still looks like a namespace:path + // (i.e., there is at least one earlier colon for the namespace) + int firstColon = value.indexOf(':'); + if (firstColon != lastColon) { + try { + amount = Math.clamp(Integer.parseInt(possibleAmount), 1, 64); + itemId = value.substring(0, lastColon); + } catch (NumberFormatException ignored) { + // The last segment is not a number — treat the whole string as item id + } + } + } + + Identifier id = Identifier.tryParse(itemId); + if (id != null && Registries.ITEM.containsId(id)) { + ItemStack stack = new ItemStack(Registries.ITEM.get(id), amount); + boolean inserted = player.getInventory().insertStack(stack); + if (!inserted) { + // Inventory full — drop at player's feet + player.dropItem(stack, false); + } + player.currentScreenHandler.sendContentUpdates(); + debug("give_item: player={} item={} x{}", player.getNameForScoreboard(), itemId, amount); + } else { + if (!dev.toolkitmc.guiapi.config.GuiApiConfig.INSTANCE.isMuteClickErrors()) { + GuiApiMod.LOGGER.warn("[GuiAPI] give_item: unknown item '{}', skipping.", itemId); + } + } + } + // ── Score helpers ───────────────────────────────────────────────────────── private static void modifyScore(ServerPlayerEntity player, String objectiveName, int val, ScoreModType modType) { diff --git a/src/main/java/dev/toolkitmc/guiapi/gui/GuiDefinition.java b/src/main/java/dev/toolkitmc/guiapi/gui/GuiDefinition.java index e50c1d1..034ed15 100644 --- a/src/main/java/dev/toolkitmc/guiapi/gui/GuiDefinition.java +++ b/src/main/java/dev/toolkitmc/guiapi/gui/GuiDefinition.java @@ -52,7 +52,8 @@ public enum ActionType { RUN_COMMAND, CLOSE, OPEN_GUI, MESSAGE, NEXT_PAGE, PREV_PAGE, GOTO_PAGE, SOUND, SET_VAR, ADD_VAR, SUB_VAR, RESET_VAR, CLEAR_VARS, REFRESH, TAKE_ITEM, SET_SCORE, ADD_SCORE, SUB_SCORE, ACTION_BAR, - ADD_EFFECT, REMOVE_EFFECT, CLEAR_EFFECTS, NONE, ANVIL_INPUT; + ADD_EFFECT, REMOVE_EFFECT, CLEAR_EFFECTS, NONE, ANVIL_INPUT, + GIVE_ITEM, BROADCAST; public static ActionType fromString(String s) { return switch (s.toLowerCase()) { @@ -80,6 +81,8 @@ public static ActionType fromString(String s) { case "clear_effects" -> CLEAR_EFFECTS; case "none" -> NONE; case "anvil_input" -> ANVIL_INPUT; + case "give_item" -> GIVE_ITEM; + case "broadcast" -> BROADCAST; default -> NONE; }; } From d89870f7bae3d18bf06e3d6244607547e9023e74 Mon Sep 17 00:00:00 2001 From: Legends11 <235496468+tickwarden@users.noreply.github.com> Date: Tue, 23 Jun 2026 13:04:14 +0300 Subject: [PATCH 3/3] Update GuiApiModMenuEntry.java --- .../guiapi/modmenu/GuiApiModMenuEntry.java | 1064 ++++++++++++----- 1 file changed, 735 insertions(+), 329 deletions(-) diff --git a/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java b/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java index 739391e..bf81068 100644 --- a/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java +++ b/src/main/java/dev/toolkitmc/guiapi/modmenu/GuiApiModMenuEntry.java @@ -40,9 +40,57 @@ public static String serializeActionsToString(List a return GuiActionParser.serializeActionsToString(actions); } + public static class IntegerSliderWidget extends net.minecraft.client.gui.widget.SliderWidget { + private final int min; + private final int max; + private final String prefix; + private final java.util.function.Consumer onChange; + + public IntegerSliderWidget(int x, int y, int width, int height, String prefix, int min, int max, int currentValue, java.util.function.Consumer onChange) { + super(x, y, width, height, Text.literal(prefix + ": " + currentValue), (double)(currentValue - min) / (max - min)); + this.min = min; + this.max = max; + this.prefix = prefix; + this.onChange = onChange; + } + + @Override + protected void updateMessage() { + int intVal = getIntValue(); + this.setMessage(Text.literal(prefix + ": " + intVal)); + } + + @Override + protected void applyValue() { + int intVal = getIntValue(); + onChange.accept(intVal); + } + + public int getIntValue() { + return min + (int)Math.round(this.value * (max - min)); + } + } + // ── Config screen ──────────────────────────────────────────────────────── static class GuiApiConfigScreen extends Screen { + private enum Tab { + CONFIG, GUIS, OTHER + } + + private static class ScrollableElement { + public final net.minecraft.client.gui.Drawable drawable; + public final net.minecraft.client.gui.Element element; + public final int originalY; + public final int height; + + public ScrollableElement(net.minecraft.client.gui.Drawable drawable, net.minecraft.client.gui.Element element, int originalY, int height) { + this.drawable = drawable; + this.element = element; + this.originalY = originalY; + this.height = height; + } + } private final Screen parent; @@ -58,7 +106,25 @@ static class GuiApiConfigScreen extends Screen { private boolean logCommands; private int defaultTickRate; - private int guiListPage = 0; + private Tab currentTab = Tab.CONFIG; + private final List scrollableElements = new ArrayList<>(); + private double scrollY = 0; + private int maxScrollY = 0; + + // Our 4 Choice Features + private boolean enableButtonGlint; + private boolean showItemIdsDeveloper; + private boolean muteClickErrors; + private boolean enableCloseSound; + // guiapi:experimental + private boolean enableNoneAction; + + // Our 3 New Interactive Input Features + private String chatPrefix; + private int soundVolume; + private String commandExecuteMode; + + private TextFieldWidget chatPrefixField; GuiApiConfigScreen(Screen parent) { super(Text.literal("GUI API — Settings")); @@ -74,147 +140,70 @@ static class GuiApiConfigScreen extends Screen { this.allowStatusEffects = cfg.isAllowStatusEffects(); this.logCommands = cfg.isLogCommands(); this.defaultTickRate = cfg.getDefaultTickRate(); + this.enableButtonGlint = cfg.isEnableButtonGlint(); + this.showItemIdsDeveloper = cfg.isShowItemIdsDeveloper(); + this.muteClickErrors = cfg.isMuteClickErrors(); + this.enableCloseSound = cfg.isEnableCloseSound(); + this.chatPrefix = cfg.getChatPrefix(); + this.soundVolume = cfg.getSoundVolume(); + this.commandExecuteMode = cfg.getCommandExecuteMode(); + this.enableNoneAction = cfg.isEnableNoneAction(); } - @Override - protected void init() { - int cx = width / 2; - int y = 30; - - // ── Settings ───────────────────────────────────────────────────── - - addToggle(cx, y, "allow_console_run_with", - "Allow run_with: console", - "Permit buttons to run commands with console (OP-level) permission.", - allowConsoleRunWith, - v -> allowConsoleRunWith = v); - y += 22; - - addToggle(cx, y, "log_unknown_items", - "Log unknown item IDs", - "Print a WARN to the log when a button uses an unrecognized item ID.", - logUnknownItems, - v -> logUnknownItems = v); - y += 22; - - addToggle(cx, y, "log_unknown_sounds", - "Log unknown sound IDs", - "Print a WARN to the log when a sound action uses an unrecognized sound ID.", - logUnknownSounds, - v -> logUnknownSounds = v); - y += 22; - - addToggle(cx, y, "debug_mode", - "Debug mode", - "Log GUI open/close, action execution and placeholder resolution to console.", - debugMode, - v -> debugMode = v); - y += 22; - - addToggle(cx, y, "allow_close_on_move", - "Allow close_on_move", - "Globally permit menus to close automatically when players walk away.", - allowCloseOnMove, - v -> allowCloseOnMove = v); - y += 22; - - addToggle(cx, y, "allow_delayed_actions", - "Allow action delays", - "Globally permit action chains to execute with tick delays.", - allowDelayedActions, - v -> allowDelayedActions = v); - y += 22; - - addToggle(cx, y, "allow_status_effects", - "Allow status effects", - "Globally permit buttons and click actions to manage player potion effects.", - allowStatusEffects, - v -> allowStatusEffects = v); - y += 22; - - addToggle(cx, y, "log_commands", - "Log command executions", - "Write a message to log console every time a GUI button runs a command.", - logCommands, - v -> logCommands = v); - y += 22; - - // Default Tick Rate Controls - addDrawableChild(new TextWidget(cx - 150, y + 4, 150, 10, Text.literal("§fDefault Tick Rate"), textRenderer)); - TextWidget[] defaultTickRateTextRef = new TextWidget[1]; - defaultTickRateTextRef[0] = new TextWidget(cx + 10, y + 4, 40, 10, Text.literal("§e" + defaultTickRate), textRenderer); - addDrawableChild(defaultTickRateTextRef[0]); - - addDrawableChild(ButtonWidget.builder(Text.literal("-5"), btn -> { - defaultTickRate = Math.max(0, defaultTickRate - 5); - defaultTickRateTextRef[0].setMessage(Text.literal("§e" + defaultTickRate)); - }).dimensions(cx + 50, y, 20, 18).build()); - - addDrawableChild(ButtonWidget.builder(Text.literal("+5"), btn -> { - defaultTickRate = Math.min(2400, defaultTickRate + 5); - defaultTickRateTextRef[0].setMessage(Text.literal("§e" + defaultTickRate)); - }).dimensions(cx + 75, y, 20, 18).build()); - y += 22; - - // Permission level — cycle 0-4 - addDrawableChild(new TextWidget(cx - 150, y + 4, 200, 10, - Text.literal("§fCommand permission level"), textRenderer)); - addDrawableChild(ButtonWidget.builder(permLevelText(permissionLevel), btn -> { - permissionLevel = (permissionLevel + 1) % 5; - btn.setMessage(permLevelText(permissionLevel)); - }).dimensions(cx + 60, y, 40, 20).build()); - y += 26; - - // ── Loaded GUI list (Completed Scrollable/Paginated GUI List) ───── - var all = GuiRegistry.INSTANCE.getAll(); - int count = all.size(); - - int guisPerPage = 3; - final int totalPages = Math.max(1, (count + guisPerPage - 1) / guisPerPage); - if (guiListPage >= totalPages) guiListPage = totalPages - 1; - - addDrawableChild(new TextWidget(cx - 150, y, 300, 10, - Text.literal("§7Loaded GUIs: §f" + count + " (Page " + (guiListPage + 1) + "/" + totalPages + ")"), - textRenderer)); - y += 12; - - List> list = new ArrayList<>(all.entrySet()); - int startIdx = guiListPage * guisPerPage; - int endIdx = Math.min(startIdx + guisPerPage, count); - - for (int i = startIdx; i < endIdx; i++) { - var entry = list.get(i); - var id = entry.getKey(); - var def = entry.getValue(); - - // Client Feature: Clickable GUI list entries to open GUI Editor Screen - addDrawableChild(ButtonWidget.builder( - Text.literal("Edit: " + id.getPath() + " (" + def.getRows() + " rows)"), - btn -> MinecraftClient.getInstance().setScreen(new GuiEditorScreen(this, id, def)) - ).dimensions(cx - 150, y, 300, 18).build()); + private static String nextExecuteMode(String current) { + if ("CHAT".equalsIgnoreCase(current)) return "SYSTEM"; + if ("SYSTEM".equalsIgnoreCase(current)) return "SILENT"; + return "CHAT"; + } - y += 20; + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (chatPrefixField != null && chatPrefixField.visible && chatPrefixField.keyPressed(keyCode, scanCode, modifiers)) { + return true; } + return super.keyPressed(keyCode, scanCode, modifiers); + } - // Pagination Controls for Loaded GUIs list - if (totalPages > 1) { - ButtonWidget prevBtn = ButtonWidget.builder(Text.literal("§e← Prev"), btn -> { - guiListPage = Math.max(0, guiListPage - 1); - this.init(); // Re-initialize list view - }).dimensions(cx - 150, y, 145, 18).build(); - prevBtn.active = (guiListPage > 0); - addDrawableChild(prevBtn); - - ButtonWidget nextBtn = ButtonWidget.builder(Text.literal("§eNext →"), btn -> { - guiListPage = Math.min(totalPages - 1, guiListPage + 1); - this.init(); // Re-initialize list view - }).dimensions(cx + 5, y, 145, 18).build(); - nextBtn.active = (guiListPage < totalPages - 1); - addDrawableChild(nextBtn); - y += 22; + @Override + public boolean charTyped(char chr, int modifiers) { + if (chatPrefixField != null && chatPrefixField.visible && chatPrefixField.charTyped(chr, modifiers)) { + return true; } + return super.charTyped(chr, modifiers); + } - // ── Buttons ─────────────────────────────────────────────────────── + @Override + protected void init() { + int cx = width / 2; + this.clearChildren(); + scrollableElements.clear(); + + // 1. Add persistent Tab Selectors (Fixed at Top) + 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 -> { + currentTab = Tab.GUIS; + scrollY = 0; + this.init(); + }).dimensions(cx - 40, 22, 80, 18).build(); + guisTabBtn.active = (currentTab != Tab.GUIS); + addDrawableChild(guisTabBtn); + + ButtonWidget otherTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.OTHER ? "§aOther" : "§7Other"), btn -> { + currentTab = Tab.OTHER; + scrollY = 0; + this.init(); + }).dimensions(cx + 45, 22, 80, 18).build(); + otherTabBtn.active = (currentTab != Tab.OTHER); + addDrawableChild(otherTabBtn); + + // 2. Add persistent Action Buttons (Fixed at Bottom) addDrawableChild(ButtonWidget.builder(Text.literal("Save & Close"), btn -> { GuiApiConfig cfg = GuiApiConfig.INSTANCE; cfg.setAllowConsoleRunWith(allowConsoleRunWith); @@ -227,6 +216,17 @@ protected void init() { cfg.setAllowStatusEffects(allowStatusEffects); cfg.setLogCommands(logCommands); cfg.setDefaultTickRate(defaultTickRate); + cfg.setEnableButtonGlint(enableButtonGlint); + cfg.setShowItemIdsDeveloper(showItemIdsDeveloper); + cfg.setMuteClickErrors(muteClickErrors); + cfg.setEnableCloseSound(enableCloseSound); + if (chatPrefixField != null) { + chatPrefix = chatPrefixField.getText(); + } + 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()); @@ -239,34 +239,159 @@ protected void init() { } else { btn.setMessage(Text.literal("§cNot in-game")); } - }).dimensions(cx - 0, height - 25, 100, 20).build()); + }).dimensions(cx, height - 25, 100, 20).build()); addDrawableChild(ButtonWidget.builder(Text.literal("Cancel"), btn -> MinecraftClient.getInstance().setScreen(parent)) .dimensions(cx + 105, height - 25, 100, 20).build()); - } - @Override - public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { - super.render(ctx, mouseX, mouseY, delta); - // Title - ctx.drawCenteredTextWithShadow(textRenderer, - Text.literal("§6GUI API §7Settings"), width / 2, 10, 0xFFFFFF); - // Divider above buttons - ctx.fill(width / 2 - 150, height - 32, width / 2 + 150, height - 31, 0x44FFFFFF); - } + // 3. Populate Scrollable Elements based on selected tab + int startY = 48; + if (currentTab == Tab.CONFIG) { + int y = startY; - @Override - public void close() { - MinecraftClient.getInstance().setScreen(parent); - } + addScrollableToggle(cx, y, "Allow run_with: console", + allowConsoleRunWith, v -> allowConsoleRunWith = v); + y += 22; + + addScrollableToggle(cx, y, "Log unknown item IDs", + logUnknownItems, v -> logUnknownItems = v); + y += 22; + + addScrollableToggle(cx, y, "Log unknown sound IDs", + logUnknownSounds, v -> logUnknownSounds = v); + y += 22; + + addScrollableToggle(cx, y, "Debug mode", + debugMode, v -> debugMode = v); + y += 22; + + addScrollableToggle(cx, y, "Allow close_on_move", + allowCloseOnMove, v -> allowCloseOnMove = v); + y += 22; - // ── Toggle helper ───────────────────────────────────────────────────── + addScrollableToggle(cx, y, "Allow action delays", + allowDelayedActions, v -> allowDelayedActions = v); + y += 22; + + addScrollableToggle(cx, y, "Allow status effects", + allowStatusEffects, v -> allowStatusEffects = v); + y += 22; + + addScrollableToggle(cx, y, "Log command executions", + logCommands, v -> logCommands = v); + y += 22; + + // 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)); + y += 24; + + // Command permission level Slider + IntegerSliderWidget permSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Command permission level", 0, 4, permissionLevel, val -> permissionLevel = val); + addDrawableChild(permSlider); + scrollableElements.add(new ScrollableElement(permSlider, permSlider, y, 20)); + y += 26; + + } else if (currentTab == Tab.OTHER) { + int y = startY; + + addScrollableToggle(cx, y, "Enable button glint", + enableButtonGlint, v -> enableButtonGlint = v); + y += 22; + + addScrollableToggle(cx, y, "Show developer item IDs", + showItemIdsDeveloper, v -> showItemIdsDeveloper = v); + y += 22; + + addScrollableToggle(cx, y, "Mute click errors", + muteClickErrors, v -> muteClickErrors = v); + y += 22; + + addScrollableToggle(cx, y, "Play close sound", + enableCloseSound, v -> enableCloseSound = v); + y += 22; + + addScrollableToggle(cx, y, "§6[Experimental] §fEnable 'none' action", + enableNoneAction, v -> enableNoneAction = v); + y += 22; - private void addToggle(int cx, int y, String key, String label, String tooltip, - boolean initial, java.util.function.Consumer onChange) { - addDrawableChild(new TextWidget(cx - 150, y + 4, 200, 10, - Text.literal("§f" + label), textRenderer)); + // Input Type 1: Text Field for Chat Prefix + TextWidget chatPrefixLabel = new TextWidget(cx - 150, y, 300, 10, Text.literal("§eChat Prefix"), textRenderer); + chatPrefixField = new TextFieldWidget(textRenderer, cx - 150, y + 12, 300, 18, Text.literal("Prefix")); + chatPrefixField.setMaxLength(128); + chatPrefixField.setText(chatPrefix); + addDrawableChild(chatPrefixLabel); + addDrawableChild(chatPrefixField); + scrollableElements.add(new ScrollableElement(chatPrefixLabel, chatPrefixLabel, y, 10)); + scrollableElements.add(new ScrollableElement(chatPrefixField, chatPrefixField, y + 12, 18)); + y += 32; + + // Input Type 2: Slider for Sound Volume + IntegerSliderWidget volumeSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Sound Volume (%)", 0, 100, soundVolume, val -> soundVolume = val); + addDrawableChild(volumeSlider); + scrollableElements.add(new ScrollableElement(volumeSlider, volumeSlider, y, 20)); + y += 24; + + // Input Type 3: Cycle Button for Message Execute Mode + TextWidget execModeLabel = new TextWidget(cx - 150, y + 4, 200, 10, Text.literal("§fMessage Execute Mode"), textRenderer); + ButtonWidget execModeBtn = ButtonWidget.builder(Text.literal(commandExecuteMode), btn -> { + commandExecuteMode = nextExecuteMode(commandExecuteMode); + btn.setMessage(Text.literal(commandExecuteMode)); + }).dimensions(cx + 60, y, 60, 20).build(); + addDrawableChild(execModeLabel); + addDrawableChild(execModeBtn); + scrollableElements.add(new ScrollableElement(execModeLabel, execModeLabel, y, 10)); + scrollableElements.add(new ScrollableElement(execModeBtn, execModeBtn, y, 20)); + y += 26; + + } else { + // Tab.GUIS + int y = startY; + var all = GuiRegistry.INSTANCE.getAll(); + int count = all.size(); + + TextWidget headerWidget = new TextWidget(cx - 150, y, 300, 10, + Text.literal("§7Loaded GUIs: §f" + count), + textRenderer); + addDrawableChild(headerWidget); + scrollableElements.add(new ScrollableElement(headerWidget, headerWidget, y, 10)); + y += 16; + + List> list = new ArrayList<>(all.entrySet()); + for (int i = 0; i < count; i++) { + var entry = list.get(i); + var id = entry.getKey(); + var def = entry.getValue(); + + ButtonWidget btnWidget = ButtonWidget.builder( + Text.literal("Edit: " + id.getPath() + " (" + def.getRows() + " rows)"), + btn -> MinecraftClient.getInstance().setScreen(new GuiEditorScreen(this, id, def)) + ).dimensions(cx - 150, y, 300, 18).build(); + + addDrawableChild(btnWidget); + scrollableElements.add(new ScrollableElement(btnWidget, btnWidget, y, 18)); + y += 22; + } + } + + // 4. Calculate max scroll Y + int maxContentY = startY; + for (ScrollableElement se : scrollableElements) { + maxContentY = Math.max(maxContentY, se.originalY + se.height); + } + maxScrollY = Math.max(0, maxContentY - (height - 35)); + + if (scrollY > maxScrollY) { + scrollY = maxScrollY; + } + + updateScrollPositions(); + } + + 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 -> { @@ -275,7 +400,88 @@ private void addToggle(int cx, int y, String key, String label, String tooltip, btn.setMessage(toggleText(next)); }).dimensions(cx + 60, y, 40, 20).build(); + addDrawableChild(labelWidget); addDrawableChild(ref[0]); + + scrollableElements.add(new ScrollableElement(labelWidget, labelWidget, y, 10)); + scrollableElements.add(new ScrollableElement(ref[0], ref[0], y, 20)); + } + + private void updateScrollPositions() { + int topBoundary = 44; + int bottomBoundary = height - 35; + + for (ScrollableElement se : scrollableElements) { + int newY = se.originalY - (int)scrollY; + if (se.drawable instanceof net.minecraft.client.gui.widget.ClickableWidget widget) { + widget.setY(newY); + boolean inViewport = (newY + se.height > topBoundary && newY < bottomBoundary); + widget.visible = inViewport; + widget.active = inViewport; + } + } + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { + scrollY -= verticalAmount * 12; + if (scrollY < 0) scrollY = 0; + if (scrollY > maxScrollY) scrollY = maxScrollY; + updateScrollPositions(); + return true; + } + + @Override + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + for (ScrollableElement se : scrollableElements) { + if (se.drawable instanceof net.minecraft.client.gui.widget.ClickableWidget widget) { + widget.visible = false; + } + } + + super.render(ctx, mouseX, mouseY, delta); + + int topBoundary = 44; + int bottomBoundary = height - 35; + for (ScrollableElement se : scrollableElements) { + int newY = se.originalY - (int)scrollY; + if (se.drawable instanceof net.minecraft.client.gui.widget.ClickableWidget widget) { + boolean inViewport = (newY + se.height > topBoundary && newY < bottomBoundary); + widget.visible = inViewport; + } + } + + ctx.drawCenteredTextWithShadow(textRenderer, + Text.literal("§6GUI API §7Settings"), width / 2, 8, 0xFFFFFF); + + ctx.fill(width / 2 - 150, 42, width / 2 + 150, 43, 0x44FFFFFF); + + ctx.enableScissor(0, topBoundary, width, bottomBoundary); + for (ScrollableElement se : scrollableElements) { + if (se.drawable instanceof net.minecraft.client.gui.widget.ClickableWidget widget) { + if (widget.visible) { + widget.render(ctx, mouseX, mouseY, delta); + } + } + } + ctx.disableScissor(); + + if (maxScrollY > 0) { + int rx = width / 2 + 155; + int trackHeight = bottomBoundary - topBoundary; + 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); + ctx.fill(rx, thumbY, rx + 4, thumbY + thumbHeight, 0x88FFFFFF); + } + + ctx.fill(width / 2 - 150, height - 32, width / 2 + 150, height - 31, 0x44FFFFFF); + } + + @Override + public void close() { + MinecraftClient.getInstance().setScreen(parent); } private static Text toggleText(boolean on) { @@ -393,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(), @@ -407,7 +612,7 @@ protected void init() { tickRate, closeOnMove ); - MinecraftClient.getInstance().setScreen(new GuiSaveLoadingScreen(parent, id, newDef)); + MinecraftClient.getInstance().setScreen(new GuiSaveProcessScreen(parent, this, id, newDef)); }).dimensions(cx - 105, height - 25, 100, 20).build()); // Cancel @@ -445,50 +650,97 @@ private static Text toggleText(boolean on) { } } - // ── GUI Save Loading Screen (Client Feature: Persists Datapack on Disk) ── + // ── GUI Save Loading Screen ─────────────────────────────────────────────── - static class GuiSaveLoadingScreen extends Screen { - private final Screen parent; + static class GuiSaveProcessScreen extends Screen { + enum State { + CONFIRM_SAVE, + SAVING_PROGRESS, + CONFIRM_RELOAD, + RELOADING_SPINNER + } + + private final Screen settingsScreen; + private final Screen editorScreen; private final net.minecraft.util.Identifier id; private final GuiDefinition newDef; + + private State state = State.CONFIRM_SAVE; private int ticksElapsed = 0; - GuiSaveLoadingScreen(Screen parent, net.minecraft.util.Identifier id, GuiDefinition newDef) { - super(Text.literal("Saving GUI...")); - this.parent = parent; + GuiSaveProcessScreen(Screen settingsScreen, Screen editorScreen, net.minecraft.util.Identifier id, GuiDefinition newDef) { + super(Text.literal("Save Progress")); + this.settingsScreen = settingsScreen; + this.editorScreen = editorScreen; this.id = id; this.newDef = newDef; } @Override protected void init() { + this.clearChildren(); ticksElapsed = 0; + + int cx = width / 2; + int cy = height / 2; + + if (state == State.CONFIRM_SAVE) { + addDrawableChild(ButtonWidget.builder(Text.literal("Yes, Save Changes"), btn -> { + state = State.SAVING_PROGRESS; + this.init(); + }).dimensions(cx - 110, cy + 20, 100, 20).build()); + + addDrawableChild(ButtonWidget.builder(Text.literal("No, Back"), btn -> { + MinecraftClient.getInstance().setScreen(editorScreen); + }).dimensions(cx + 10, cy + 20, 100, 20).build()); + } else if (state == State.CONFIRM_RELOAD) { + addDrawableChild(ButtonWidget.builder(Text.literal("Yes, Reload"), btn -> { + if (MinecraftClient.getInstance().player != null) { + MinecraftClient.getInstance().player.networkHandler.sendChatCommand("guiapi reload"); + } + state = State.RELOADING_SPINNER; + this.init(); + }).dimensions(cx - 110, cy + 20, 100, 20).build()); + + addDrawableChild(ButtonWidget.builder(Text.literal("No, Skip"), btn -> { + MinecraftClient.getInstance().setScreen(settingsScreen); + }).dimensions(cx + 10, cy + 20, 100, 20).build()); + } } @Override public void tick() { - ticksElapsed++; - if (ticksElapsed == 40) { // After 2 seconds, write to disk and trigger reload - MinecraftServer server = MinecraftClient.getInstance().getServer(); - if (server != null) { - // 1. Update in-memory - dev.toolkitmc.guiapi.loader.GuiRegistry.INSTANCE.put(id, newDef); - // 2. Save directly to the datapack JSON file on disk - dev.toolkitmc.guiapi.loader.GuiRegistry.INSTANCE.saveToDisk(server, id, newDef); - - // 3. Reload datapacks so everything syncs perfectly - if (MinecraftClient.getInstance().player != null) { - MinecraftClient.getInstance().player.networkHandler.sendChatCommand("guiapi reload"); + if (state == State.SAVING_PROGRESS) { + ticksElapsed++; + if (ticksElapsed >= 40) { + MinecraftServer server = MinecraftClient.getInstance().getServer(); + if (server != null) { + dev.toolkitmc.guiapi.loader.GuiRegistry.INSTANCE.put(id, newDef); + dev.toolkitmc.guiapi.loader.GuiRegistry.INSTANCE.saveToDisk(server, id, newDef); } + state = State.CONFIRM_RELOAD; + this.init(); + } + } else if (state == State.RELOADING_SPINNER) { + ticksElapsed++; + if (ticksElapsed >= 30) { + MinecraftClient.getInstance().setScreen(settingsScreen); } - } else if (ticksElapsed >= 55) { // Return to settings screen - MinecraftClient.getInstance().setScreen(parent); } } + @Override + public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) { + // 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) { - super.render(ctx, mouseX, mouseY, delta); + ctx.fill(0, 0, width, height, 0xFF0A0A0A); + + if (state == State.CONFIRM_SAVE || state == State.CONFIRM_RELOAD) { + super.render(ctx, mouseX, mouseY, delta); + } int cx = width / 2; int cy = height / 2; @@ -496,24 +748,46 @@ public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { long time = System.currentTimeMillis(); int rainbowColor = java.awt.Color.HSBtoRGB((time % 1500) / 1500f, 0.8f, 0.8f); - // Draw a solid professional background - ctx.fill(0, 0, width, height, 0xDD050505); + switch (state) { + case CONFIRM_SAVE -> { + ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ Save Confirmation ★"), cx, cy - 30, 0xFFFFFF); + ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§fAre you sure you want to write changes to disk?"), cx, cy - 10, 0xAAAAAA); + } + case SAVING_PROGRESS -> { + 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); + + int barWidth = 160; + int progress = Math.min(barWidth, (ticksElapsed * barWidth) / 40); + ctx.fill(cx - barWidth / 2, cy + 15, cx + barWidth / 2, cy + 19, 0x44FFFFFF); + ctx.fill(cx - barWidth / 2, cy + 15, cx - barWidth / 2 + progress, cy + 19, rainbowColor); + } + case CONFIRM_RELOAD -> { + ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ Save Complete! ★"), cx, cy - 30, 0xFFFFFF); + ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§fDo you want to reload GUIs now to sync in-game?"), cx, cy - 10, 0xAAAAAA); + } + case RELOADING_SPINNER -> { + ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ Reloading Resources ★"), cx, cy - 40, 0xFFFFFF); + ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("Please wait, reloading..."), cx, cy - 15, 0x88FFFFFF); + + double angleSpeed = (time % 1000) / 1000.0 * 2.0 * Math.PI; + int numDots = 8; + int radius = 12; + for (int i = 0; i < numDots; i++) { + 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); + } - String status = "Locating Datapack Folder..."; - if (ticksElapsed >= 20 && ticksElapsed < 40) { - status = "Overwriting Datapack JSON File..."; - } else if (ticksElapsed >= 40) { - status = "Reloading GUI API Resources..."; + String[] spinner = {"|", "/", "-", "\\"}; + String spinChar = spinner[(ticksElapsed / 3) % 4]; + ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§e" + spinChar), cx, cy + 35, 0xFFFFFF); + } } - - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal("§6★ GUI API Datapack Writer ★"), cx, cy - 40, 0xFFFFFF); - ctx.drawCenteredTextWithShadow(textRenderer, Text.literal(status), cx, cy - 10, rainbowColor); - - // Draw progress bar - int barWidth = 160; - int progress = Math.min(barWidth, (ticksElapsed * barWidth) / 55); - ctx.fill(cx - barWidth / 2, cy + 15, cx + barWidth / 2, cy + 19, 0x44FFFFFF); - ctx.fill(cx - barWidth / 2, cy + 15, cx - barWidth / 2 + progress, cy + 19, rainbowColor); } } @@ -589,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(), @@ -601,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); @@ -645,7 +919,8 @@ 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) { super(Text.literal("Buttons List")); @@ -658,19 +933,24 @@ static class ButtonListScreen extends Screen { @Override protected void init() { int cx = width / 2; - int y = 35; + int y = 30; addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eSelect a button to edit or delete:"), textRenderer)); + y += 12; + + int buttonsPerPage = 6; + final int totalPages = Math.max(1, (buttonsList.size() + buttonsPerPage - 1) / buttonsPerPage); + if (listPage >= totalPages) listPage = totalPages - 1; + + addDrawableChild(new TextWidget(cx - 150, y, 300, 10, + Text.literal("§7Loaded Buttons: §f" + buttonsList.size() + " (Page " + (listPage + 1) + "/" + totalPages + ")"), + textRenderer)); y += 14; - // List of existing buttons (limit to first 7 to fit, or provide navigation) - int shown = 0; - for (int i = 0; i < buttonsList.size(); i++) { - if (shown >= 7) { - addDrawableChild(new TextWidget(cx - 150, y, 300, 10, - Text.literal("§8... and " + (buttonsList.size() - 7) + " more buttons"), textRenderer)); - break; - } + int startIdx = listPage * buttonsPerPage; + int endIdx = Math.min(startIdx + buttonsPerPage, buttonsList.size()); + + for (int i = startIdx; i < endIdx; i++) { final int index = i; GuiDefinition.Button btn = buttonsList.get(index); String labelText = "Slot " + btn.slot() + ": " + @@ -681,7 +961,24 @@ protected void init() { }).dimensions(cx - 150, y, 300, 18).build()); y += 20; - shown++; + } + + y = 186; + + if (totalPages > 1) { + ButtonWidget prevBtn = ButtonWidget.builder(Text.literal("§e← Prev"), btn -> { + listPage = Math.max(0, listPage - 1); + this.init(); + }).dimensions(cx - 150, y, 145, 18).build(); + prevBtn.active = (listPage > 0); + addDrawableChild(prevBtn); + + ButtonWidget nextBtn = ButtonWidget.builder(Text.literal("§eNext →"), btn -> { + listPage = Math.min(totalPages - 1, listPage + 1); + this.init(); + }).dimensions(cx + 5, y, 145, 18).build(); + nextBtn.active = (listPage < totalPages - 1); + addDrawableChild(nextBtn); } y = height - 30; @@ -689,7 +986,7 @@ protected void init() { // Add New Button addDrawableChild(ButtonWidget.builder(Text.literal("§aAdd New Button"), btn -> { GuiDefinition.Button newBtn = new GuiDefinition.Button( - 0, 0, "minecraft:stone", "New Button", List.of(), false, + 0, listPage, "minecraft:stone", "New Button", List.of(), false, GuiDefinition.ClickType.ANY, Optional.empty(), List.of(), Optional.empty(), Optional.empty(), Optional.empty(), "1", false, false ); @@ -699,7 +996,6 @@ protected void init() { // Save & Back addDrawableChild(ButtonWidget.builder(Text.literal("Save Buttons"), btn -> { - // Update parent editor screen copy! parent.updateButtons(buttonsList); MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 50, y, 100, 20).build()); @@ -729,131 +1025,156 @@ public void deleteButton(int index) { } } - // ── Button Properties Editor Screen ────────────────────────────────────── + // ── Button Editor Screen ───────────────────────────────────────────────── static class ButtonEditorScreen extends Screen { + private enum Tab { + BASIC, APPEARANCE, LOGIC + } + private final ButtonListScreen parent; private final int index; private final GuiDefinition.Button btn; - private TextFieldWidget slotField; + private Tab currentTab = Tab.BASIC; + private TextFieldWidget itemField; private TextFieldWidget nameField; - private TextFieldWidget amountField; - private TextFieldWidget loreField; // Combined Lore input field (separated by ;) - - // Combined Actions input field (separated by ;) + private TextFieldWidget loreField; private TextFieldWidget actionsField; + private TextFieldWidget conditionField; + private int slot; + private int amount; + private int pageVal; private boolean glint; + private GuiDefinition.ClickType clickType; private Optional toggle; + // Caching fields to fix wipe-on-tab-switch bug + private String itemText; + private String nameText; + private String loreText; + private String actionsText; + private String conditionText; + ButtonEditorScreen(ButtonListScreen parent, int index, GuiDefinition.Button btn) { super(Text.literal("Edit Button")); this.parent = parent; this.index = index; this.btn = btn; + this.slot = btn.slot(); + this.pageVal = btn.page(); this.glint = btn.glint(); this.toggle = btn.toggle(); + this.clickType = btn.clickType(); + + int parsedAmount = 1; + try { + parsedAmount = Integer.parseInt(btn.amount()); + } catch (NumberFormatException ignored) {} + this.amount = parsedAmount; + + 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() + : ""; } public void updateToggle(Optional newToggle) { this.toggle = newToggle; } - @Override - protected void init() { - int cx = width / 2; - int y = 25; - - // Slot Input - addDrawableChild(new TextWidget(cx - 150, y, 100, 10, Text.literal("§eSlot (0-53)"), textRenderer)); - slotField = new TextFieldWidget(textRenderer, cx - 150, y + 12, 60, 18, Text.literal("Slot")); - slotField.setText(String.valueOf(btn.slot())); - addDrawableChild(slotField); - - // Amount Input - addDrawableChild(new TextWidget(cx - 80, y, 100, 10, Text.literal("§eAmount"), textRenderer)); - amountField = new TextFieldWidget(textRenderer, cx - 80, y + 12, 60, 18, Text.literal("Amount")); - amountField.setText(btn.amount()); - addDrawableChild(amountField); - - // Glint Toggle - addDrawableChild(new TextWidget(cx - 10, y, 100, 10, Text.literal("§eGlint"), textRenderer)); - ButtonWidget[] glintBtnRef = new ButtonWidget[1]; - glintBtnRef[0] = ButtonWidget.builder(toggleText(glint), b -> { - glint = !glint; - b.setMessage(toggleText(glint)); - }).dimensions(cx - 10, y + 12, 40, 18).build(); - addDrawableChild(glintBtnRef[0]); - - // Edit Toggle Properties Navigation - addDrawableChild(new TextWidget(cx + 40, y, 110, 10, Text.literal("§eToggle Button"), textRenderer)); - ButtonWidget[] toggleBtnRef = new ButtonWidget[1]; - toggleBtnRef[0] = ButtonWidget.builder(Text.literal(toggle.isPresent() ? "§aCONFIGURED" : "§cDISABLED"), b -> { - MinecraftClient.getInstance().setScreen(new ToggleEditorScreen(this, toggle)); - }).dimensions(cx + 40, y + 12, 110, 18).build(); - addDrawableChild(toggleBtnRef[0]); - y += 38; - - // Item ID Input - addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eItem ID"), textRenderer)); - y += 12; - itemField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 18, Text.literal("Item ID")); - itemField.setMaxLength(256); - itemField.setText(btn.item()); - addDrawableChild(itemField); - y += 24; + private static GuiDefinition.ClickType nextClickType(GuiDefinition.ClickType current) { + GuiDefinition.ClickType[] vals = GuiDefinition.ClickType.values(); + return vals[(current.ordinal() + 1) % vals.length]; + } - // Display Name Input - addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eDisplay Name"), textRenderer)); - y += 12; - nameField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 18, Text.literal("Display Name")); - nameField.setMaxLength(128); - nameField.setText(btn.name()); - addDrawableChild(nameField); - y += 24; + private void saveCurrentTabFields() { + if (itemField != null) itemText = itemField.getText(); + if (nameField != null) nameText = nameField.getText(); + if (loreField != null) loreText = loreField.getText(); + if (actionsField != null) actionsText = actionsField.getText(); + if (conditionField != null) conditionText = conditionField.getText(); + } - // Lore Input (joined by ;) - addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eLore Lines (Separate by semicolon ';')"), textRenderer)); - y += 12; - loreField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 18, Text.literal("Lore")); - loreField.setMaxLength(512); - loreField.setText(String.join(";", btn.lore())); - addDrawableChild(loreField); - y += 24; + /** 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 + } - // Multiple Actions Input (joined by ;) - addDrawableChild(new TextWidget(cx - 150, y, 300, 10, Text.literal("§eActions (Format: 'type:value' or 'type:varKey:value', separate by ';')"), textRenderer)); - y += 12; - actionsField = new TextFieldWidget(textRenderer, cx - 150, y, 300, 18, Text.literal("Actions")); - actionsField.setMaxLength(512); - actionsField.setText(serializeActionsToString(btn.actions())); - addDrawableChild(actionsField); - y += 32; - - // Actions - // Save/Apply + @Override + protected void init() { + int cx = width / 2; + this.clearChildren(); + + // Tab buttons + ButtonWidget basicTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.BASIC ? "§aBasic" : "§7Basic"), btn -> { + saveCurrentTabFields(); + currentTab = Tab.BASIC; + this.init(); + }).dimensions(cx - 125, 22, 80, 18).build(); + basicTabBtn.active = (currentTab != Tab.BASIC); + addDrawableChild(basicTabBtn); + + ButtonWidget appTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.APPEARANCE ? "§aAppearance" : "§7Appearance"), btn -> { + saveCurrentTabFields(); + currentTab = Tab.APPEARANCE; + this.init(); + }).dimensions(cx - 40, 22, 80, 18).build(); + appTabBtn.active = (currentTab != Tab.APPEARANCE); + addDrawableChild(appTabBtn); + + ButtonWidget logicTabBtn = ButtonWidget.builder(Text.literal(currentTab == Tab.LOGIC ? "§aLogic" : "§7Logic"), btn -> { + saveCurrentTabFields(); + currentTab = Tab.LOGIC; + this.init(); + }).dimensions(cx + 45, 22, 80, 18).build(); + logicTabBtn.active = (currentTab != Tab.LOGIC); + addDrawableChild(logicTabBtn); + + // Bottom buttons addDrawableChild(ButtonWidget.builder(Text.literal("Apply"), b -> { - int slotVal = 0; - try { - slotVal = Math.max(0, Integer.parseInt(slotField.getText())); - } catch (NumberFormatException ignored) {} + saveCurrentTabFields(); + + int finalSlot = resolveSlot(slot, pageVal); - // Build Lore list List finalLore = new ArrayList<>(); - String loreTxt = loreField.getText(); - if (!loreTxt.isEmpty()) { - for (String s : loreTxt.split(";")) { + if (!loreText.isEmpty()) { + for (String s : loreText.split(";")) { finalLore.add(s); } } - // Build Actions list from semicolon-separated string List finalActions = new ArrayList<>(); - String actionsTxt = actionsField.getText(); - if (!actionsTxt.isEmpty()) { - for (String s : actionsTxt.split(";")) { + if (!actionsText.isEmpty()) { + for (String s : actionsText.split(";")) { finalActions.add(parseActionFromString(s)); } } @@ -861,20 +1182,31 @@ protected void init() { finalActions.add(new GuiDefinition.ButtonAction(GuiDefinition.ActionType.CLOSE, "")); } + Optional finalCondition = Optional.empty(); + if (!conditionText.isEmpty()) { + String[] condParts = conditionText.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( - slotVal, - btn.page(), - itemField.getText(), - nameField.getText(), + finalSlot, + pageVal, + itemText, + nameText, finalLore, glint, - btn.clickType(), - btn.condition(), + clickType, + finalCondition, finalActions, toggle, btn.customModelData(), btn.itemModel(), - amountField.getText(), + String.valueOf(amount), btn.hideTooltip(), btn.hideAdditionalTooltip() ); @@ -882,34 +1214,110 @@ protected void init() { MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 155, height - 25, 100, 20).build()); - // Delete Button addDrawableChild(ButtonWidget.builder(Text.literal("§cDelete Button"), b -> { parent.deleteButton(index); MinecraftClient.getInstance().setScreen(parent); }).dimensions(cx - 50, height - 25, 100, 20).build()); - // Cancel addDrawableChild(ButtonWidget.builder(Text.literal("Cancel"), b -> MinecraftClient.getInstance().setScreen(parent)) .dimensions(cx + 55, height - 25, 100, 20).build()); + + // Tab content + int startY = 48; + if (currentTab == Tab.BASIC) { + int y = startY; + + IntegerSliderWidget pageSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Page (1-10)", 0, 9, pageVal, val -> pageVal = val); + addDrawableChild(pageSlider); + y += 24; + + IntegerSliderWidget slotSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Slot Position", 0, 53, slot, val -> slot = val); + addDrawableChild(slotSlider); + y += 24; + + IntegerSliderWidget amountSlider = new IntegerSliderWidget(cx - 150, y, 300, 20, "Amount", 1, 99, amount, val -> amount = val); + addDrawableChild(amountSlider); + y += 24; + + 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); + itemField.setText(itemText); + addDrawableChild(itemField); + y += 34; + + 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); + nameField.setText(nameText); + addDrawableChild(nameField); + + } else if (currentTab == Tab.APPEARANCE) { + int y = startY; + + addDrawableChild(new TextWidget(cx - 150, y + 4, 200, 10, Text.literal("§eEnable Glint"), textRenderer)); + ButtonWidget glintBtn = ButtonWidget.builder(toggleText(glint), b -> { + glint = !glint; + b.setMessage(toggleText(glint)); + }).dimensions(cx + 60, y, 40, 18).build(); + addDrawableChild(glintBtn); + y += 24; + + 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)); + }).dimensions(cx + 40, y, 110, 18).build(); + addDrawableChild(toggleBtn); + y += 26; + + 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); + loreField.setText(loreText); + addDrawableChild(loreField); + + } else if (currentTab == Tab.LOGIC) { + int y = startY; + + 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); + b.setMessage(Text.literal(clickType.name())); + }).dimensions(cx + 60, y, 60, 18).build(); + addDrawableChild(clickTypeBtn); + y += 24; + + 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); + conditionField.setText(conditionText); + addDrawableChild(conditionField); + y += 34; + + 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); + actionsField.setText(actionsText); + addDrawableChild(actionsField); + } } @Override public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { super.render(ctx, mouseX, mouseY, delta); ctx.drawCenteredTextWithShadow(textRenderer, - Text.literal("§6Edit Button Properties"), width / 2, 10, 0xFFFFFF); + Text.literal("§6Edit Button Properties"), width / 2, 8, 0xFFFFFF); ctx.fill(width / 2 - 150, height - 32, width / 2 + 150, height - 31, 0x44FFFFFF); } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (slotField.keyPressed(keyCode, scanCode, modifiers) || - amountField.keyPressed(keyCode, scanCode, modifiers) || - itemField.keyPressed(keyCode, scanCode, modifiers) || - nameField.keyPressed(keyCode, scanCode, modifiers) || - loreField.keyPressed(keyCode, scanCode, modifiers) || - actionsField.keyPressed(keyCode, scanCode, modifiers)) { + if ((itemField != null && itemField.keyPressed(keyCode, scanCode, modifiers)) || + (nameField != null && nameField.keyPressed(keyCode, scanCode, modifiers)) || + (conditionField != null && conditionField.keyPressed(keyCode, scanCode, modifiers)) || + (loreField != null && loreField.keyPressed(keyCode, scanCode, modifiers)) || + (actionsField != null && actionsField.keyPressed(keyCode, scanCode, modifiers))) { return true; } return super.keyPressed(keyCode, scanCode, modifiers); @@ -917,12 +1325,11 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { @Override public boolean charTyped(char chr, int modifiers) { - if (slotField.charTyped(chr, modifiers) || - amountField.charTyped(chr, modifiers) || - itemField.charTyped(chr, modifiers) || - nameField.charTyped(chr, modifiers) || - loreField.charTyped(chr, modifiers) || - actionsField.charTyped(chr, modifiers)) { + if ((itemField != null && itemField.charTyped(chr, modifiers)) || + (nameField != null && nameField.charTyped(chr, modifiers)) || + (conditionField != null && conditionField.charTyped(chr, modifiers)) || + (loreField != null && loreField.charTyped(chr, modifiers)) || + (actionsField != null && actionsField.charTyped(chr, modifiers))) { return true; } return super.charTyped(chr, modifiers); @@ -944,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")); @@ -976,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; @@ -990,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; @@ -1004,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")); @@ -1013,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")); @@ -1021,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()) { @@ -1035,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()) { @@ -1074,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);