From ff678be28396e76d87e2c92647e0d92aa2a75a8a Mon Sep 17 00:00:00 2001 From: NuanKi Date: Wed, 29 Apr 2026 23:39:32 -0600 Subject: [PATCH 1/6] Improve universal terminal switching UX --- .../de/mari_023/ae2wtlib/api/AE2wtlibAPI.java | 6 + .../ae2wtlib/api/AE2wtlibAPIImpl.java | 5 + .../mari_023/ae2wtlib/api/TextConstants.java | 5 + .../terminal/IUniversalTerminalCapable.java | 11 +- .../ae2wtlib/api/terminal/ItemWUT.java | 5 + .../api/terminal/TerminalSelectionButton.java | 194 ++++++++++++++++++ .../ae2wtlib/AE2wtlibAPIImplementation.java | 7 + .../de/mari_023/ae2wtlib/AE2wtlibClient.java | 19 ++ .../de/mari_023/ae2wtlib/AE2wtlibForge.java | 7 + .../networking/CycleTerminalPacket.java | 33 ++- .../networking/SelectTerminalPacket.java | 47 +++++ .../resources/assets/ae2wtlib/lang/en_us.json | 1 + .../resources/assets/ae2wtlib/lang/es_es.json | 1 + .../resources/assets/ae2wtlib/lang/es_mx.json | 1 + 14 files changed, 333 insertions(+), 9 deletions(-) create mode 100644 ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java create mode 100644 src/main/java/de/mari_023/ae2wtlib/networking/SelectTerminalPacket.java diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPI.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPI.java index fef42983..14780f5f 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPI.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPI.java @@ -16,6 +16,7 @@ import appeng.api.upgrades.IUpgradeInventory; import appeng.menu.locator.ItemMenuHostLocator; +import de.mari_023.ae2wtlib.api.registration.WTDefinition; import de.mari_023.ae2wtlib.api.terminal.ItemWUT; public class AE2wtlibAPI { @@ -62,6 +63,11 @@ public static void cycleTerminal(boolean isHandlingRightClick) { AE2wtlibAPIImpl.instance().cycleTerminal(isHandlingRightClick); } + @ApiStatus.Internal + public static void selectTerminal(WTDefinition terminal) { + AE2wtlibAPIImpl.instance().selectTerminal(terminal); + } + /** * Sends an update to the client about the current terminal. This is only relevant for Universal Terminals, and only * sent when ae2wtlib is present. diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPIImpl.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPIImpl.java index 95b9fc98..ee202be9 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPIImpl.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPIImpl.java @@ -14,6 +14,8 @@ import appeng.api.upgrades.IUpgradeInventory; import appeng.menu.locator.ItemMenuHostLocator; +import de.mari_023.ae2wtlib.api.registration.WTDefinition; + @ApiStatus.Internal public class AE2wtlibAPIImpl { @Nullable @@ -49,5 +51,8 @@ public Item getWUT() { @ApiStatus.Internal public void cycleTerminal(boolean isHandlingRightClick) {} + @ApiStatus.Internal + public void selectTerminal(WTDefinition terminal) {} + public void updateClientTerminal(ServerPlayer player, ItemMenuHostLocator locator, ItemStack stack) {} } diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java index 49be9ffe..dba08589 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java @@ -42,6 +42,11 @@ public static MutableComponent formatTerminalName(String terminal) { public static final Component TERMINAL_EMPTY = Component .literal("This terminal does not contain any other Terminals"); + public static Component currentTerminal(WTDefinition terminal) { + return Component.translatable("gui.ae2wtlib.current_terminal", + Component.translatable(terminal.translationKey()).withStyle(STYLE_GRAY)); + } + public static Component cycleNext(WTDefinition terminal) { return Component.translatable("gui.ae2wtlib.cycle_terminal.desc", Component.translatable(terminal.translationKey())); diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/IUniversalTerminalCapable.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/IUniversalTerminalCapable.java index d524cfb5..0f4a4c75 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/IUniversalTerminalCapable.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/IUniversalTerminalCapable.java @@ -1,7 +1,6 @@ package de.mari_023.ae2wtlib.api.terminal; import java.util.ArrayList; -import java.util.List; import org.jetbrains.annotations.Contract; @@ -14,7 +13,6 @@ import appeng.menu.SlotSemantics; import de.mari_023.ae2wtlib.api.AE2wtlibAPI; -import de.mari_023.ae2wtlib.api.TextConstants; import de.mari_023.ae2wtlib.api.gui.AE2wtlibSlotSemantics; import de.mari_023.ae2wtlib.api.gui.IconButton; import de.mari_023.ae2wtlib.api.gui.ScrollingUpgradesPanel; @@ -52,17 +50,14 @@ default boolean checkForTerminalKeys(int keyCode, int scanCode) { } /** - * creates the button that switches to the next terminal. you are responsible for adding this to the leftToolbar + * Creates the button that opens the terminal selector. You are responsible for adding this to the leftToolbar * when appropriate * - * @return CycleTerminalButton + * @return TerminalSelectionButton */ @Contract(value = "-> new", pure = true) default IconButton cycleTerminalButton() { - var next = WUTHandler.nextTerminal(getHost().getItemStack(), false); - var previous = WUTHandler.nextTerminal(getHost().getItemStack(), true); - return IconButton.withAE2Background(btn -> cycleTerminal(), next.icon()) - .withTooltip(List.of(TextConstants.cycleNext(next), TextConstants.cyclePrevious(previous))); + return new TerminalSelectionButton(getHost(), this::storeState); } /** diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/ItemWUT.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/ItemWUT.java index db37ecb3..efd3180a 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/ItemWUT.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/ItemWUT.java @@ -64,6 +64,11 @@ public MenuType getMenuType(ItemMenuHostLocator locator, Player player) { @OnlyIn(Dist.CLIENT) public void appendHoverText(final ItemStack stack, final TooltipContext context, final List lines, final TooltipFlag advancedTooltips) { + var currentTerminal = WTDefinition.ofOrNull(stack); + if (currentTerminal != null) { + lines.add(TextConstants.currentTerminal(currentTerminal)); + lines.add(Component.empty()); + } lines.add(TextConstants.UNIVERSAL); for (var terminal : WTDefinition.wirelessTerminals()) { if (stack.get(terminal.componentType()) != null) diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java new file mode 100644 index 00000000..d9ddb614 --- /dev/null +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java @@ -0,0 +1,194 @@ +package de.mari_023.ae2wtlib.api.terminal; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.Rect2i; +import net.minecraft.network.chat.Component; + +import appeng.client.gui.style.BackgroundGenerator; + +import de.mari_023.ae2wtlib.api.AE2wtlibAPI; +import de.mari_023.ae2wtlib.api.TextConstants; +import de.mari_023.ae2wtlib.api.gui.Icon; +import de.mari_023.ae2wtlib.api.gui.IconButton; +import de.mari_023.ae2wtlib.api.registration.WTDefinition; + +/** + * Provides a button that opens a terminal selector panel for the wireless universal terminal. + */ +final class TerminalSelectionButton extends IconButton { + private static final int BUTTON_WIDTH = 18; + private static final int BUTTON_HEIGHT = 20; + private static final int MAX_ROWS = 3; + private static final int GAP = 2; + private static final int PANEL_GAP = 5; + private static final int PANEL_PADDING = 5; + private static final int ICON_OFFSET = 1; + + private final WTMenuHost host; + private final List terminals; + private final Runnable storeState; + private boolean menuOpen; + private List tooltip = List.of(); + + TerminalSelectionButton(WTMenuHost host, Runnable storeState) { + super(btn -> { + }, Icon.CRAFTING, Icon.TOOLBAR_BUTTON_BACKGROUND, Icon.TOOLBAR_BUTTON_BACKGROUND_HOVERED, + Icon.TOOLBAR_BUTTON_BACKGROUND_FOCUSED); + this.host = host; + this.storeState = storeState; + this.terminals = installedTerminals(host); + } + + @Override + public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partial) { + if (!visible) + return; + + tooltip = List.of(); + super.renderWidget(guiGraphics, mouseX, mouseY, partial); + if (isHovered()) + tooltip = currentTooltip(); + + if (menuOpen) + renderMenu(guiGraphics, mouseX, mouseY); + } + + @Override + protected Icon getIcon() { + WTDefinition terminal = WTDefinition.ofOrNull(host.getItemStack()); + return terminal == null ? Icon.CRAFTING : terminal.icon(); + } + + @Override + public void onPress() { + if (!terminals.isEmpty()) + menuOpen = !menuOpen; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (menuOpen && button == 0) { + WTDefinition terminal = terminalAt(mouseX, mouseY); + if (terminal != null) { + menuOpen = false; + storeState.run(); + AE2wtlibAPI.selectTerminal(terminal); + return true; + } + } + + if (super.mouseClicked(mouseX, mouseY, button)) + return true; + + if (menuOpen) { + menuOpen = false; + } + return false; + } + + @Override + public List getTooltipMessage() { + return tooltip; + } + + @Override + public Rect2i getTooltipArea() { + if (!menuOpen) + return super.getTooltipArea(); + + int x = panelX() - 1; + int y = panelY() - 1; + int right = getX() + getWidth(); + int bottom = Math.max(getY() + getHeight(), panelY() + panelHeight() + 1); + return new Rect2i(x, y, right - x, bottom - y); + } + + private static List installedTerminals(WTMenuHost host) { + List terminals = new ArrayList<>(); + for (var terminal : WTDefinition.wirelessTerminalList) { + if (WUTHandler.hasTerminal(host.getItemStack(), terminal)) + terminals.add(terminal); + } + return terminals; + } + + private List currentTooltip() { + WTDefinition terminal = WTDefinition.ofOrNull(host.getItemStack()); + if (terminal == null) + return List.of(TextConstants.TERMINAL_EMPTY); + return List.of(TextConstants.currentTerminal(terminal)); + } + + private void renderMenu(GuiGraphics guiGraphics, int mouseX, int mouseY) { + int panelX = panelX(); + int panelY = panelY(); + int width = panelWidth(); + int height = panelHeight(); + WTDefinition currentTerminal = WTDefinition.ofOrNull(host.getItemStack()); + + BackgroundGenerator.draw(width, height, guiGraphics, panelX, panelY); + + for (int i = 0; i < terminals.size(); i++) { + WTDefinition terminal = terminals.get(i); + int x = buttonX(i); + int y = buttonY(i); + boolean selected = terminal.equals(currentTerminal); + boolean hovered = mouseX >= x && mouseY >= y && mouseX < x + BUTTON_WIDTH && mouseY < y + BUTTON_HEIGHT; + int yOffset = hovered ? 1 : 0; + Icon background = hovered ? Icon.TOOLBAR_BUTTON_BACKGROUND_HOVERED + : selected ? Icon.TOOLBAR_BUTTON_BACKGROUND_FOCUSED : Icon.TOOLBAR_BUTTON_BACKGROUND; + + background.getBlitter().dest(x, y + yOffset, background.width(), background.height()).zOffset(2) + .blit(guiGraphics); + terminal.icon().getBlitter().dest(x + ICON_OFFSET, y + ICON_OFFSET + yOffset).zOffset(3).blit(guiGraphics); + + if (hovered) + tooltip = List.of(terminal.formattedName()); + } + } + + private WTDefinition terminalAt(double mouseX, double mouseY) { + for (int i = 0; i < terminals.size(); i++) { + int x = buttonX(i); + int y = buttonY(i); + if (mouseX >= x && mouseY >= y && mouseX < x + BUTTON_WIDTH && mouseY < y + BUTTON_HEIGHT) + return terminals.get(i); + } + return null; + } + + private int panelX() { + return getX() - PANEL_GAP - panelWidth(); + } + + private int panelY() { + return getY(); + } + + private int panelWidth() { + return PANEL_PADDING * 2 + columns() * BUTTON_WIDTH + Math.max(0, columns() - 1) * GAP; + } + + private int panelHeight() { + return PANEL_PADDING * 2 + rows() * BUTTON_HEIGHT + Math.max(0, rows() - 1) * GAP; + } + + private int buttonX(int index) { + return panelX() + PANEL_PADDING + (index % columns()) * (BUTTON_WIDTH + GAP); + } + + private int buttonY(int index) { + return panelY() + PANEL_PADDING + (index / columns()) * (BUTTON_HEIGHT + GAP); + } + + private int columns() { + return Math.max(1, (terminals.size() + MAX_ROWS - 1) / MAX_ROWS); + } + + private int rows() { + return Math.max(1, (terminals.size() + columns() - 1) / columns()); + } +} diff --git a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibAPIImplementation.java b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibAPIImplementation.java index 6e190736..57f35bd7 100644 --- a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibAPIImplementation.java +++ b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibAPIImplementation.java @@ -11,7 +11,9 @@ import appeng.menu.locator.ItemMenuHostLocator; import de.mari_023.ae2wtlib.api.AE2wtlibAPIImpl; +import de.mari_023.ae2wtlib.api.registration.WTDefinition; import de.mari_023.ae2wtlib.networking.CycleTerminalPacket; +import de.mari_023.ae2wtlib.networking.SelectTerminalPacket; import de.mari_023.ae2wtlib.networking.UpdateWUTPackage; public class AE2wtlibAPIImplementation extends AE2wtlibAPIImpl { @@ -30,6 +32,11 @@ public void cycleTerminal(boolean isHandlingRightClick) { PacketDistributor.sendToServer(new CycleTerminalPacket(isHandlingRightClick)); } + @Override + public void selectTerminal(WTDefinition terminal) { + PacketDistributor.sendToServer(new SelectTerminalPacket(terminal)); + } + @Override public void updateClientTerminal(ServerPlayer player, ItemMenuHostLocator locator, ItemStack stack) { PacketDistributor.sendToPlayer(player, new UpdateWUTPackage(locator, stack)); diff --git a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClient.java b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClient.java index 2658b404..a8456844 100644 --- a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClient.java +++ b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClient.java @@ -1,14 +1,19 @@ package de.mari_023.ae2wtlib; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.client.event.InputEvent; import net.neoforged.neoforge.client.gui.ConfigurationScreen; import net.neoforged.neoforge.client.gui.IConfigScreenFactory; +import net.neoforged.neoforge.network.PacketDistributor; import de.mari_023.ae2wtlib.api.AE2wtlibAPI; +import de.mari_023.ae2wtlib.api.terminal.ItemWUT; +import de.mari_023.ae2wtlib.networking.CycleTerminalPacket; import de.mari_023.ae2wtlib.wct.CraftingTerminalHandler; @Mod(value = AE2wtlibAPI.MOD_NAME, dist = Dist.CLIENT) @@ -22,4 +27,18 @@ public static void clientTick() { return; CraftingTerminalHandler.getCraftingTerminalHandler(Minecraft.getInstance().player).checkTerminal(); } + + public static void mouseScroll(InputEvent.MouseScrollingEvent event) { + var minecraft = Minecraft.getInstance(); + var player = minecraft.player; + if (player == null || minecraft.screen != null || !Screen.hasShiftDown() || event.getScrollDeltaY() == 0) + return; + + if (!(player.getMainHandItem().getItem() instanceof ItemWUT) + && !(player.getOffhandItem().getItem() instanceof ItemWUT)) + return; + + PacketDistributor.sendToServer(new CycleTerminalPacket(event.getScrollDeltaY() < 0)); + event.setCanceled(true); + } } diff --git a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibForge.java b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibForge.java index 8d2d9b65..c668979c 100644 --- a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibForge.java +++ b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibForge.java @@ -16,6 +16,7 @@ import net.neoforged.neoforge.capabilities.Capabilities; import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent; import net.neoforged.neoforge.client.event.ClientTickEvent; +import net.neoforged.neoforge.client.event.InputEvent; import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; import net.neoforged.neoforge.event.entity.living.LivingEntityUseItemEvent; import net.neoforged.neoforge.event.entity.player.ArrowLooseEvent; @@ -54,6 +55,7 @@ public AE2wtlibForge(IEventBus modEventBus, ModContainer modContainer) { modEventBus.addListener((RegisterPayloadHandlersEvent event) -> { PayloadRegistrar registrar = event.registrar(AE2wtlibAPI.MOD_NAME); registerPacket(registrar, CycleTerminalPacket.ID, CycleTerminalPacket.STREAM_CODEC); + registerPacket(registrar, SelectTerminalPacket.ID, SelectTerminalPacket.STREAM_CODEC); registerPacket(registrar, PickBlockPacket.ID, PickBlockPacket.STREAM_CODEC); registerPacket(registrar, TerminalSettingsPacket.ID, TerminalSettingsPacket.STREAM_CODEC); registerPacket(registrar, UpdateWUTPackage.ID, UpdateWUTPackage.STREAM_CODEC); @@ -149,4 +151,9 @@ public static void handle(ArrowLooseEvent event) { public static void handle(ClientTickEvent.Post event) { AE2wtlibClient.clientTick(); } + + @SubscribeEvent + public static void handle(InputEvent.MouseScrollingEvent event) { + AE2wtlibClient.mouseScroll(event); + } } diff --git a/src/main/java/de/mari_023/ae2wtlib/networking/CycleTerminalPacket.java b/src/main/java/de/mari_023/ae2wtlib/networking/CycleTerminalPacket.java index 114e516a..83406bb6 100644 --- a/src/main/java/de/mari_023/ae2wtlib/networking/CycleTerminalPacket.java +++ b/src/main/java/de/mari_023/ae2wtlib/networking/CycleTerminalPacket.java @@ -6,13 +6,17 @@ import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.ItemStack; import appeng.menu.AEBaseMenu; import appeng.menu.locator.ItemMenuHostLocator; +import appeng.menu.locator.MenuLocators; import de.mari_023.ae2wtlib.api.AE2wtlibAPI; +import de.mari_023.ae2wtlib.api.AE2wtlibComponents; +import de.mari_023.ae2wtlib.api.TextConstants; import de.mari_023.ae2wtlib.api.terminal.ItemWUT; import de.mari_023.ae2wtlib.api.terminal.WUTHandler; @@ -24,8 +28,10 @@ public record CycleTerminalPacket(boolean isRightClick) implements AE2wtlibPacke public void processPacketData(Player player) { final AbstractContainerMenu containerMenu = player.containerMenu; - if (!(containerMenu instanceof AEBaseMenu aeMenu)) + if (!(containerMenu instanceof AEBaseMenu aeMenu)) { + cycleHeldUniversalTerminal(player); return; + } if (!(aeMenu.getLocator() instanceof ItemMenuHostLocator locator)) return; @@ -39,6 +45,31 @@ public void processPacketData(Player player) { WUTHandler.open(player, locator, true); } + private void cycleHeldUniversalTerminal(Player player) { + ItemMenuHostLocator locator = getHeldUniversalTerminalLocator(player); + if (locator == null) + return; + + ItemStack item = locator.locateItem(player); + + if (!(item.getItem() instanceof ItemWUT)) + return; + + WUTHandler.cycle(player, locator, item, isRightClick()); + + var terminal = item.get(AE2wtlibComponents.CURRENT_TERMINAL); + if (terminal != null) + player.displayClientMessage(TextConstants.currentTerminal(terminal), true); + } + + private static ItemMenuHostLocator getHeldUniversalTerminalLocator(Player player) { + if (player.getMainHandItem().getItem() instanceof ItemWUT) + return MenuLocators.forInventorySlot(player.getInventory().selected); + if (player.getOffhandItem().getItem() instanceof ItemWUT) + return MenuLocators.forInventorySlot(Inventory.SLOT_OFFHAND); + return null; + } + @Override public Type type() { return ID; diff --git a/src/main/java/de/mari_023/ae2wtlib/networking/SelectTerminalPacket.java b/src/main/java/de/mari_023/ae2wtlib/networking/SelectTerminalPacket.java new file mode 100644 index 00000000..9bab023b --- /dev/null +++ b/src/main/java/de/mari_023/ae2wtlib/networking/SelectTerminalPacket.java @@ -0,0 +1,47 @@ +package de.mari_023.ae2wtlib.networking; + +import io.netty.buffer.ByteBuf; + +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; + +import appeng.menu.AEBaseMenu; +import appeng.menu.locator.ItemMenuHostLocator; + +import de.mari_023.ae2wtlib.api.AE2wtlibAPI; +import de.mari_023.ae2wtlib.api.registration.WTDefinition; +import de.mari_023.ae2wtlib.api.terminal.ItemWUT; +import de.mari_023.ae2wtlib.api.terminal.WUTHandler; + +public record SelectTerminalPacket(WTDefinition terminal) implements AE2wtlibPacket { + public static final Type ID = new Type<>(AE2wtlibAPI.id("select_terminal")); + public static final StreamCodec STREAM_CODEC = WTDefinition.STREAM_CODEC + .map(SelectTerminalPacket::new, SelectTerminalPacket::terminal); + + public void processPacketData(Player player) { + final AbstractContainerMenu containerMenu = player.containerMenu; + + if (!(containerMenu instanceof AEBaseMenu aeMenu)) + return; + + if (!(aeMenu.getLocator() instanceof ItemMenuHostLocator locator)) + return; + + ItemStack item = locator.locateItem(player); + if (!(item.getItem() instanceof ItemWUT)) + return; + if (!WUTHandler.hasTerminal(item, terminal())) + return; + + WUTHandler.setCurrentTerminal(player, locator, item, terminal()); + WUTHandler.open(player, locator, true); + } + + @Override + public Type type() { + return ID; + } +} diff --git a/src/main/resources/assets/ae2wtlib/lang/en_us.json b/src/main/resources/assets/ae2wtlib/lang/en_us.json index 82a59e85..d6b0e097 100644 --- a/src/main/resources/assets/ae2wtlib/lang/en_us.json +++ b/src/main/resources/assets/ae2wtlib/lang/en_us.json @@ -15,6 +15,7 @@ "gui.ae2wtlib.magnetcard.desc.me_no_magnet": "Magnet Off, Pickup to ME", "gui.ae2wtlib.cycle_terminal.desc": "Open %s", "gui.ae2wtlib.cycle_terminal.desc1": "Right click to open %s", + "gui.ae2wtlib.current_terminal": "Current Terminal: %s", "gui.ae2wtlib.magnetcard.hotkey": "Magnet Card: %s", "gui.ae2wtlib.restock": "Restock: %s", "gui.ae2wtlib.off": "Off", diff --git a/src/main/resources/assets/ae2wtlib/lang/es_es.json b/src/main/resources/assets/ae2wtlib/lang/es_es.json index 6d39544a..aea6b9de 100644 --- a/src/main/resources/assets/ae2wtlib/lang/es_es.json +++ b/src/main/resources/assets/ae2wtlib/lang/es_es.json @@ -15,6 +15,7 @@ "gui.ae2wtlib.magnetcard.desc.me_no_magnet": "Imán apagado, recoger a ME", "gui.ae2wtlib.cycle_terminal.desc": "Abrir %s", "gui.ae2wtlib.cycle_terminal.desc1": "Haz clic derecho para abrir %s", + "gui.ae2wtlib.current_terminal": "Terminal actual: %s", "gui.ae2wtlib.magnetcard.hotkey": "Tarjeta de imán: %s", "gui.ae2wtlib.restock": "Reabastecer: %s", "gui.ae2wtlib.off": "Apagado", diff --git a/src/main/resources/assets/ae2wtlib/lang/es_mx.json b/src/main/resources/assets/ae2wtlib/lang/es_mx.json index 6d39544a..aea6b9de 100644 --- a/src/main/resources/assets/ae2wtlib/lang/es_mx.json +++ b/src/main/resources/assets/ae2wtlib/lang/es_mx.json @@ -15,6 +15,7 @@ "gui.ae2wtlib.magnetcard.desc.me_no_magnet": "Imán apagado, recoger a ME", "gui.ae2wtlib.cycle_terminal.desc": "Abrir %s", "gui.ae2wtlib.cycle_terminal.desc1": "Haz clic derecho para abrir %s", + "gui.ae2wtlib.current_terminal": "Terminal actual: %s", "gui.ae2wtlib.magnetcard.hotkey": "Tarjeta de imán: %s", "gui.ae2wtlib.restock": "Reabastecer: %s", "gui.ae2wtlib.off": "Apagado", From fa6bef5549575b3a3bed0ab6ba9835bfaf4c9815 Mon Sep 17 00:00:00 2001 From: NuanKi Date: Wed, 29 Apr 2026 23:50:23 -0600 Subject: [PATCH 2/6] Add localization for empty terminal message --- .../src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java | 2 +- src/main/resources/assets/ae2wtlib/lang/en_us.json | 1 + src/main/resources/assets/ae2wtlib/lang/es_es.json | 1 + src/main/resources/assets/ae2wtlib/lang/es_mx.json | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java index dba08589..0a946c7f 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java @@ -40,7 +40,7 @@ public static MutableComponent formatTerminalName(String terminal) { } public static final Component TERMINAL_EMPTY = Component - .literal("This terminal does not contain any other Terminals"); + .translatable("gui.ae2wtlib.terminal_empty"); public static Component currentTerminal(WTDefinition terminal) { return Component.translatable("gui.ae2wtlib.current_terminal", diff --git a/src/main/resources/assets/ae2wtlib/lang/en_us.json b/src/main/resources/assets/ae2wtlib/lang/en_us.json index d6b0e097..0007e198 100644 --- a/src/main/resources/assets/ae2wtlib/lang/en_us.json +++ b/src/main/resources/assets/ae2wtlib/lang/en_us.json @@ -16,6 +16,7 @@ "gui.ae2wtlib.cycle_terminal.desc": "Open %s", "gui.ae2wtlib.cycle_terminal.desc1": "Right click to open %s", "gui.ae2wtlib.current_terminal": "Current Terminal: %s", + "gui.ae2wtlib.terminal_empty": "This terminal does not contain any other terminals", "gui.ae2wtlib.magnetcard.hotkey": "Magnet Card: %s", "gui.ae2wtlib.restock": "Restock: %s", "gui.ae2wtlib.off": "Off", diff --git a/src/main/resources/assets/ae2wtlib/lang/es_es.json b/src/main/resources/assets/ae2wtlib/lang/es_es.json index aea6b9de..2bad1309 100644 --- a/src/main/resources/assets/ae2wtlib/lang/es_es.json +++ b/src/main/resources/assets/ae2wtlib/lang/es_es.json @@ -16,6 +16,7 @@ "gui.ae2wtlib.cycle_terminal.desc": "Abrir %s", "gui.ae2wtlib.cycle_terminal.desc1": "Haz clic derecho para abrir %s", "gui.ae2wtlib.current_terminal": "Terminal actual: %s", + "gui.ae2wtlib.terminal_empty": "Esta terminal no contiene otras terminales", "gui.ae2wtlib.magnetcard.hotkey": "Tarjeta de imán: %s", "gui.ae2wtlib.restock": "Reabastecer: %s", "gui.ae2wtlib.off": "Apagado", diff --git a/src/main/resources/assets/ae2wtlib/lang/es_mx.json b/src/main/resources/assets/ae2wtlib/lang/es_mx.json index aea6b9de..2bad1309 100644 --- a/src/main/resources/assets/ae2wtlib/lang/es_mx.json +++ b/src/main/resources/assets/ae2wtlib/lang/es_mx.json @@ -16,6 +16,7 @@ "gui.ae2wtlib.cycle_terminal.desc": "Abrir %s", "gui.ae2wtlib.cycle_terminal.desc1": "Haz clic derecho para abrir %s", "gui.ae2wtlib.current_terminal": "Terminal actual: %s", + "gui.ae2wtlib.terminal_empty": "Esta terminal no contiene otras terminales", "gui.ae2wtlib.magnetcard.hotkey": "Tarjeta de imán: %s", "gui.ae2wtlib.restock": "Reabastecer: %s", "gui.ae2wtlib.off": "Apagado", From d4d4551f1dd955ba52dc3fd1be05e434f0d5a2b4 Mon Sep 17 00:00:00 2001 From: NuanKi Date: Thu, 30 Apr 2026 11:57:17 -0600 Subject: [PATCH 3/6] Add "always show terminal selector" configuration and related functionality --- .../de/mari_023/ae2wtlib/api/AE2wtlibAPI.java | 5 ++ .../ae2wtlib/api/AE2wtlibAPIImpl.java | 5 ++ .../api/terminal/TerminalSelectionButton.java | 46 +++++++++++++++---- .../ae2wtlib/AE2wtlibAPIImplementation.java | 5 ++ .../de/mari_023/ae2wtlib/AE2wtlibClient.java | 3 ++ .../ae2wtlib/AE2wtlibClientConfig.java | 25 ++++++++++ .../resources/assets/ae2wtlib/lang/en_us.json | 3 +- .../resources/assets/ae2wtlib/lang/es_es.json | 3 +- .../resources/assets/ae2wtlib/lang/es_mx.json | 3 +- 9 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 src/main/java/de/mari_023/ae2wtlib/AE2wtlibClientConfig.java diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPI.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPI.java index 14780f5f..e842fa82 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPI.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPI.java @@ -68,6 +68,11 @@ public static void selectTerminal(WTDefinition terminal) { AE2wtlibAPIImpl.instance().selectTerminal(terminal); } + @ApiStatus.Internal + public static boolean alwaysShowTerminalSelector() { + return AE2wtlibAPIImpl.instance().alwaysShowTerminalSelector(); + } + /** * Sends an update to the client about the current terminal. This is only relevant for Universal Terminals, and only * sent when ae2wtlib is present. diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPIImpl.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPIImpl.java index ee202be9..77fc5d60 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPIImpl.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPIImpl.java @@ -54,5 +54,10 @@ public void cycleTerminal(boolean isHandlingRightClick) {} @ApiStatus.Internal public void selectTerminal(WTDefinition terminal) {} + @ApiStatus.Internal + public boolean alwaysShowTerminalSelector() { + return false; + } + public void updateClientTerminal(ServerPlayer player, ItemMenuHostLocator locator, ItemStack stack) {} } diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java index d9ddb614..3a97108f 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java @@ -26,6 +26,7 @@ final class TerminalSelectionButton extends IconButton { private static final int PANEL_GAP = 5; private static final int PANEL_PADDING = 5; private static final int ICON_OFFSET = 1; + private static final int TOOLBAR_BUTTON_SPACING = 6; private final WTMenuHost host; private final List terminals; @@ -47,12 +48,15 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float if (!visible) return; + boolean alwaysVisible = alwaysVisible(); tooltip = List.of(); - super.renderWidget(guiGraphics, mouseX, mouseY, partial); - if (isHovered()) - tooltip = currentTooltip(); + if (!alwaysVisible) { + super.renderWidget(guiGraphics, mouseX, mouseY, partial); + if (isHovered()) + tooltip = currentTooltip(); + } - if (menuOpen) + if (menuOpen || alwaysVisible) renderMenu(guiGraphics, mouseX, mouseY); } @@ -64,22 +68,27 @@ protected Icon getIcon() { @Override public void onPress() { - if (!terminals.isEmpty()) + if (!alwaysVisible() && !terminals.isEmpty()) menuOpen = !menuOpen; } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (menuOpen && button == 0) { + boolean alwaysVisible = alwaysVisible(); + if ((menuOpen || alwaysVisible) && button == 0) { WTDefinition terminal = terminalAt(mouseX, mouseY); if (terminal != null) { - menuOpen = false; + if (!alwaysVisible) + menuOpen = false; storeState.run(); AE2wtlibAPI.selectTerminal(terminal); return true; } } + if (alwaysVisible) + return false; + if (super.mouseClicked(mouseX, mouseY, button)) return true; @@ -89,6 +98,16 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return false; } + @Override + public int getWidth() { + return alwaysVisible() ? 0 : super.getWidth(); + } + + @Override + public int getHeight() { + return alwaysVisible() ? -TOOLBAR_BUTTON_SPACING : super.getHeight(); + } + @Override public List getTooltipMessage() { return tooltip; @@ -96,12 +115,12 @@ public List getTooltipMessage() { @Override public Rect2i getTooltipArea() { - if (!menuOpen) + if (!menuOpen && !alwaysVisible()) return super.getTooltipArea(); int x = panelX() - 1; int y = panelY() - 1; - int right = getX() + getWidth(); + int right = Math.max(getX() + getWidth(), panelX() + panelWidth() + 1); int bottom = Math.max(getY() + getHeight(), panelY() + panelHeight() + 1); return new Rect2i(x, y, right - x, bottom - y); } @@ -115,6 +134,10 @@ private static List installedTerminals(WTMenuHost host) { return terminals; } + private boolean alwaysVisible() { + return AE2wtlibAPI.alwaysShowTerminalSelector() && !terminals.isEmpty(); + } + private List currentTooltip() { WTDefinition terminal = WTDefinition.ofOrNull(host.getItemStack()); if (terminal == null) @@ -161,7 +184,10 @@ private WTDefinition terminalAt(double mouseX, double mouseY) { } private int panelX() { - return getX() - PANEL_GAP - panelWidth(); + int x = getX(); + if (alwaysVisible()) + x -= super.getWidth(); + return x - PANEL_GAP - panelWidth(); } private int panelY() { diff --git a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibAPIImplementation.java b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibAPIImplementation.java index 57f35bd7..47a97699 100644 --- a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibAPIImplementation.java +++ b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibAPIImplementation.java @@ -37,6 +37,11 @@ public void selectTerminal(WTDefinition terminal) { PacketDistributor.sendToServer(new SelectTerminalPacket(terminal)); } + @Override + public boolean alwaysShowTerminalSelector() { + return AE2wtlibClientConfig.CONFIG.alwaysShowTerminalSelector(); + } + @Override public void updateClientTerminal(ServerPlayer player, ItemMenuHostLocator locator, ItemStack stack) { PacketDistributor.sendToPlayer(player, new UpdateWUTPackage(locator, stack)); diff --git a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClient.java b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClient.java index a8456844..a358026d 100644 --- a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClient.java +++ b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClient.java @@ -6,6 +6,7 @@ import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; +import net.neoforged.fml.config.ModConfig; import net.neoforged.neoforge.client.event.InputEvent; import net.neoforged.neoforge.client.gui.ConfigurationScreen; import net.neoforged.neoforge.client.gui.IConfigScreenFactory; @@ -19,6 +20,8 @@ @Mod(value = AE2wtlibAPI.MOD_NAME, dist = Dist.CLIENT) public class AE2wtlibClient { public AE2wtlibClient(IEventBus modEventBus, ModContainer modContainer) { + modContainer.registerConfig(ModConfig.Type.CLIENT, AE2wtlibClientConfig.SPEC, + AE2wtlibAPI.MOD_NAME + "-client.toml"); modContainer.registerExtensionPoint(IConfigScreenFactory.class, ConfigurationScreen::new); } diff --git a/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClientConfig.java b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClientConfig.java new file mode 100644 index 00000000..0bcbd490 --- /dev/null +++ b/src/main/java/de/mari_023/ae2wtlib/AE2wtlibClientConfig.java @@ -0,0 +1,25 @@ +package de.mari_023.ae2wtlib; + +import org.apache.commons.lang3.tuple.Pair; + +import net.neoforged.neoforge.common.ModConfigSpec; + +public record AE2wtlibClientConfig(ModConfigSpec.BooleanValue alwaysShowTerminalSelectorValue) { + public static final AE2wtlibClientConfig CONFIG; + public static final ModConfigSpec SPEC; + + static { + Pair pair = new ModConfigSpec.Builder() + .configure(AE2wtlibClientConfig::new); + CONFIG = pair.getLeft(); + SPEC = pair.getRight(); + } + + AE2wtlibClientConfig(ModConfigSpec.Builder builder) { + this(builder.define("always_show_terminal_selector", false)); + } + + public boolean alwaysShowTerminalSelector() { + return alwaysShowTerminalSelectorValue().get(); + } +} diff --git a/src/main/resources/assets/ae2wtlib/lang/en_us.json b/src/main/resources/assets/ae2wtlib/lang/en_us.json index 0007e198..9e4e9fe4 100644 --- a/src/main/resources/assets/ae2wtlib/lang/en_us.json +++ b/src/main/resources/assets/ae2wtlib/lang/en_us.json @@ -51,5 +51,6 @@ "key.ae2.ae2wtlib_magnet": "Toggle Magnet Card", "key.ae2.ae2wtlib_stow": "Stow stack in hand into ME System", - "ae2wtlib.configuration.magnet_card_range": "Magnet Card Range" + "ae2wtlib.configuration.magnet_card_range": "Magnet Card Range", + "ae2wtlib.configuration.always_show_terminal_selector": "Always Show Universal Terminal Selector" } diff --git a/src/main/resources/assets/ae2wtlib/lang/es_es.json b/src/main/resources/assets/ae2wtlib/lang/es_es.json index 2bad1309..be7247f9 100644 --- a/src/main/resources/assets/ae2wtlib/lang/es_es.json +++ b/src/main/resources/assets/ae2wtlib/lang/es_es.json @@ -50,5 +50,6 @@ "key.ae2.ae2wtlib_magnet": "Alternar tarjeta de imán", "key.ae2.ae2wtlib_stow": "Guardar stack en mano en el sistema ME", - "ae2wtlib.configuration.magnet_card_range": "Rango de tarjeta de imán" + "ae2wtlib.configuration.magnet_card_range": "Rango de tarjeta de imán", + "ae2wtlib.configuration.always_show_terminal_selector": "Mostrar siempre el selector de terminal universal" } diff --git a/src/main/resources/assets/ae2wtlib/lang/es_mx.json b/src/main/resources/assets/ae2wtlib/lang/es_mx.json index 2bad1309..be7247f9 100644 --- a/src/main/resources/assets/ae2wtlib/lang/es_mx.json +++ b/src/main/resources/assets/ae2wtlib/lang/es_mx.json @@ -50,5 +50,6 @@ "key.ae2.ae2wtlib_magnet": "Alternar tarjeta de imán", "key.ae2.ae2wtlib_stow": "Guardar stack en mano en el sistema ME", - "ae2wtlib.configuration.magnet_card_range": "Rango de tarjeta de imán" + "ae2wtlib.configuration.magnet_card_range": "Rango de tarjeta de imán", + "ae2wtlib.configuration.always_show_terminal_selector": "Mostrar siempre el selector de terminal universal" } From 7d392586997d949286e138bbc157a5b0856c96dc Mon Sep 17 00:00:00 2001 From: NuanKi Date: Thu, 30 Apr 2026 11:58:12 -0600 Subject: [PATCH 4/6] Standardize Javadoc formatting in `IUniversalTerminalCapable` for cycleTerminalButton. ./gradlew :ae2wtlib_api:spotlessApply showed no problems --- .../ae2wtlib/api/terminal/IUniversalTerminalCapable.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/IUniversalTerminalCapable.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/IUniversalTerminalCapable.java index 0f4a4c75..232c988c 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/IUniversalTerminalCapable.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/IUniversalTerminalCapable.java @@ -50,9 +50,9 @@ default boolean checkForTerminalKeys(int keyCode, int scanCode) { } /** - * Creates the button that opens the terminal selector. You are responsible for adding this to the leftToolbar - * when appropriate - * + * creates the button that opens the terminal selector. you are responsible for adding this to the leftToolbar when + * appropriate + * * @return TerminalSelectionButton */ @Contract(value = "-> new", pure = true) From e556b50c5b613716a7e3a298b70d41a22105d121 Mon Sep 17 00:00:00 2001 From: NuanKi Date: Thu, 30 Apr 2026 12:25:32 -0600 Subject: [PATCH 5/6] Organize imports in `CycleTerminalPacket.java` for ./gradlew spotlessCheck to not fail --- .../de/mari_023/ae2wtlib/networking/CycleTerminalPacket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/mari_023/ae2wtlib/networking/CycleTerminalPacket.java b/src/main/java/de/mari_023/ae2wtlib/networking/CycleTerminalPacket.java index 83406bb6..9b0fb419 100644 --- a/src/main/java/de/mari_023/ae2wtlib/networking/CycleTerminalPacket.java +++ b/src/main/java/de/mari_023/ae2wtlib/networking/CycleTerminalPacket.java @@ -5,8 +5,8 @@ import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.ItemStack; From cd2cb5ffaa6fdc857afa7c20cb4a9b3819e7994e Mon Sep 17 00:00:00 2001 From: NuanKi Date: Thu, 30 Apr 2026 18:10:38 -0600 Subject: [PATCH 6/6] Refactor terminal button layout calculations for improved positioning and spacing --- .../api/terminal/TerminalSelectionButton.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java index 3a97108f..02965d91 100644 --- a/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java +++ b/ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/TerminalSelectionButton.java @@ -24,7 +24,9 @@ final class TerminalSelectionButton extends IconButton { private static final int MAX_ROWS = 3; private static final int GAP = 2; private static final int PANEL_GAP = 5; - private static final int PANEL_PADDING = 5; + private static final int ALWAYS_VISIBLE_PANEL_X_OFFSET = -15; + private static final int ALWAYS_VISIBLE_PANEL_Y_OFFSET = 3; + private static final int PANEL_PADDING = 3; private static final int ICON_OFFSET = 1; private static final int TOOLBAR_BUTTON_SPACING = 6; @@ -185,13 +187,16 @@ private WTDefinition terminalAt(double mouseX, double mouseY) { private int panelX() { int x = getX(); - if (alwaysVisible()) + int gap = PANEL_GAP; + if (alwaysVisible()) { x -= super.getWidth(); - return x - PANEL_GAP - panelWidth(); + gap = ALWAYS_VISIBLE_PANEL_X_OFFSET; + } + return x - gap - panelWidth(); } private int panelY() { - return getY(); + return getY() + (alwaysVisible() ? ALWAYS_VISIBLE_PANEL_Y_OFFSET : 0); } private int panelWidth() { @@ -199,22 +204,29 @@ private int panelWidth() { } private int panelHeight() { - return PANEL_PADDING * 2 + rows() * BUTTON_HEIGHT + Math.max(0, rows() - 1) * GAP; + return PANEL_PADDING * 2 + 2 + rows() * BUTTON_HEIGHT + Math.max(0, rows() - 1) * GAP; } private int buttonX(int index) { - return panelX() + PANEL_PADDING + (index % columns()) * (BUTTON_WIDTH + GAP); + return panelX() + PANEL_PADDING + (index / rows()) * (BUTTON_WIDTH + GAP); } private int buttonY(int index) { - return panelY() + PANEL_PADDING + (index / columns()) * (BUTTON_HEIGHT + GAP); + return panelY() + PANEL_PADDING + (index % rows()) * (BUTTON_HEIGHT + GAP) + lastColumnYOffset(index); } private int columns() { - return Math.max(1, (terminals.size() + MAX_ROWS - 1) / MAX_ROWS); + return Math.max(1, (terminals.size() + rows() - 1) / rows()); } private int rows() { - return Math.max(1, (terminals.size() + columns() - 1) / columns()); + return Math.max(1, Math.min(MAX_ROWS, terminals.size())); + } + + private int lastColumnYOffset(int index) { + int entries = terminals.size() % rows(); + if (entries == 0 || index / rows() != columns() - 1) + return 0; + return (rows() - entries) * (BUTTON_HEIGHT + GAP) / 2; } }