Skip to content

Commit 1322676

Browse files
authored
Improve universal terminal switching UX (#375)
1 parent 2eb950e commit 1322676

15 files changed

Lines changed: 426 additions & 15 deletions

File tree

ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPI.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import appeng.api.upgrades.IUpgradeInventory;
1717
import appeng.menu.locator.ItemMenuHostLocator;
1818

19+
import de.mari_023.ae2wtlib.api.registration.WTDefinition;
1920
import de.mari_023.ae2wtlib.api.terminal.ItemWUT;
2021

2122
public class AE2wtlibAPI {
@@ -62,6 +63,16 @@ public static void cycleTerminal(boolean isHandlingRightClick) {
6263
AE2wtlibAPIImpl.instance().cycleTerminal(isHandlingRightClick);
6364
}
6465

66+
@ApiStatus.Internal
67+
public static void selectTerminal(WTDefinition terminal) {
68+
AE2wtlibAPIImpl.instance().selectTerminal(terminal);
69+
}
70+
71+
@ApiStatus.Internal
72+
public static boolean alwaysShowTerminalSelector() {
73+
return AE2wtlibAPIImpl.instance().alwaysShowTerminalSelector();
74+
}
75+
6576
/**
6677
* Sends an update to the client about the current terminal. This is only relevant for Universal Terminals, and only
6778
* sent when ae2wtlib is present.

ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/AE2wtlibAPIImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import appeng.api.upgrades.IUpgradeInventory;
1515
import appeng.menu.locator.ItemMenuHostLocator;
1616

17+
import de.mari_023.ae2wtlib.api.registration.WTDefinition;
18+
1719
@ApiStatus.Internal
1820
public class AE2wtlibAPIImpl {
1921
@Nullable
@@ -49,5 +51,13 @@ public Item getWUT() {
4951
@ApiStatus.Internal
5052
public void cycleTerminal(boolean isHandlingRightClick) {}
5153

54+
@ApiStatus.Internal
55+
public void selectTerminal(WTDefinition terminal) {}
56+
57+
@ApiStatus.Internal
58+
public boolean alwaysShowTerminalSelector() {
59+
return false;
60+
}
61+
5262
public void updateClientTerminal(ServerPlayer player, ItemMenuHostLocator locator, ItemStack stack) {}
5363
}

ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/TextConstants.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ public static MutableComponent formatTerminalName(String terminal) {
4040
}
4141

4242
public static final Component TERMINAL_EMPTY = Component
43-
.literal("This terminal does not contain any other Terminals");
43+
.translatable("gui.ae2wtlib.terminal_empty");
44+
45+
public static Component currentTerminal(WTDefinition terminal) {
46+
return Component.translatable("gui.ae2wtlib.current_terminal",
47+
Component.translatable(terminal.translationKey()).withStyle(STYLE_GRAY));
48+
}
4449

4550
public static Component cycleNext(WTDefinition terminal) {
4651
return Component.translatable("gui.ae2wtlib.cycle_terminal.desc",

ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/IUniversalTerminalCapable.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package de.mari_023.ae2wtlib.api.terminal;
22

33
import java.util.ArrayList;
4-
import java.util.List;
54

65
import org.jetbrains.annotations.Contract;
76

@@ -14,7 +13,6 @@
1413
import appeng.menu.SlotSemantics;
1514

1615
import de.mari_023.ae2wtlib.api.AE2wtlibAPI;
17-
import de.mari_023.ae2wtlib.api.TextConstants;
1816
import de.mari_023.ae2wtlib.api.gui.AE2wtlibSlotSemantics;
1917
import de.mari_023.ae2wtlib.api.gui.IconButton;
2018
import de.mari_023.ae2wtlib.api.gui.ScrollingUpgradesPanel;
@@ -52,17 +50,14 @@ default boolean checkForTerminalKeys(int keyCode, int scanCode) {
5250
}
5351

5452
/**
55-
* creates the button that switches to the next terminal. you are responsible for adding this to the leftToolbar
56-
* when appropriate
57-
*
58-
* @return CycleTerminalButton
53+
* creates the button that opens the terminal selector. you are responsible for adding this to the leftToolbar when
54+
* appropriate
55+
*
56+
* @return TerminalSelectionButton
5957
*/
6058
@Contract(value = "-> new", pure = true)
6159
default IconButton cycleTerminalButton() {
62-
var next = WUTHandler.nextTerminal(getHost().getItemStack(), false);
63-
var previous = WUTHandler.nextTerminal(getHost().getItemStack(), true);
64-
return IconButton.withAE2Background(btn -> cycleTerminal(), next.icon())
65-
.withTooltip(List.of(TextConstants.cycleNext(next), TextConstants.cyclePrevious(previous)));
60+
return new TerminalSelectionButton(getHost(), this::storeState);
6661
}
6762

6863
/**

ae2wtlib_api/src/main/java/de/mari_023/ae2wtlib/api/terminal/ItemWUT.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ public MenuType<?> getMenuType(ItemMenuHostLocator locator, Player player) {
6464
@OnlyIn(Dist.CLIENT)
6565
public void appendHoverText(final ItemStack stack, final TooltipContext context, final List<Component> lines,
6666
final TooltipFlag advancedTooltips) {
67+
var currentTerminal = WTDefinition.ofOrNull(stack);
68+
if (currentTerminal != null) {
69+
lines.add(TextConstants.currentTerminal(currentTerminal));
70+
lines.add(Component.empty());
71+
}
6772
lines.add(TextConstants.UNIVERSAL);
6873
for (var terminal : WTDefinition.wirelessTerminals()) {
6974
if (stack.get(terminal.componentType()) != null)
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package de.mari_023.ae2wtlib.api.terminal;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
import net.minecraft.client.gui.GuiGraphics;
7+
import net.minecraft.client.renderer.Rect2i;
8+
import net.minecraft.network.chat.Component;
9+
10+
import appeng.client.gui.style.BackgroundGenerator;
11+
12+
import de.mari_023.ae2wtlib.api.AE2wtlibAPI;
13+
import de.mari_023.ae2wtlib.api.TextConstants;
14+
import de.mari_023.ae2wtlib.api.gui.Icon;
15+
import de.mari_023.ae2wtlib.api.gui.IconButton;
16+
import de.mari_023.ae2wtlib.api.registration.WTDefinition;
17+
18+
/**
19+
* Provides a button that opens a terminal selector panel for the wireless universal terminal.
20+
*/
21+
final class TerminalSelectionButton extends IconButton {
22+
private static final int BUTTON_WIDTH = 18;
23+
private static final int BUTTON_HEIGHT = 20;
24+
private static final int MAX_ROWS = 3;
25+
private static final int GAP = 2;
26+
private static final int PANEL_GAP = 5;
27+
private static final int ALWAYS_VISIBLE_PANEL_X_OFFSET = -15;
28+
private static final int ALWAYS_VISIBLE_PANEL_Y_OFFSET = 3;
29+
private static final int PANEL_PADDING = 3;
30+
private static final int ICON_OFFSET = 1;
31+
private static final int TOOLBAR_BUTTON_SPACING = 6;
32+
33+
private final WTMenuHost host;
34+
private final List<WTDefinition> terminals;
35+
private final Runnable storeState;
36+
private boolean menuOpen;
37+
private List<Component> tooltip = List.of();
38+
39+
TerminalSelectionButton(WTMenuHost host, Runnable storeState) {
40+
super(btn -> {
41+
}, Icon.CRAFTING, Icon.TOOLBAR_BUTTON_BACKGROUND, Icon.TOOLBAR_BUTTON_BACKGROUND_HOVERED,
42+
Icon.TOOLBAR_BUTTON_BACKGROUND_FOCUSED);
43+
this.host = host;
44+
this.storeState = storeState;
45+
this.terminals = installedTerminals(host);
46+
}
47+
48+
@Override
49+
public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partial) {
50+
if (!visible)
51+
return;
52+
53+
boolean alwaysVisible = alwaysVisible();
54+
tooltip = List.of();
55+
if (!alwaysVisible) {
56+
super.renderWidget(guiGraphics, mouseX, mouseY, partial);
57+
if (isHovered())
58+
tooltip = currentTooltip();
59+
}
60+
61+
if (menuOpen || alwaysVisible)
62+
renderMenu(guiGraphics, mouseX, mouseY);
63+
}
64+
65+
@Override
66+
protected Icon getIcon() {
67+
WTDefinition terminal = WTDefinition.ofOrNull(host.getItemStack());
68+
return terminal == null ? Icon.CRAFTING : terminal.icon();
69+
}
70+
71+
@Override
72+
public void onPress() {
73+
if (!alwaysVisible() && !terminals.isEmpty())
74+
menuOpen = !menuOpen;
75+
}
76+
77+
@Override
78+
public boolean mouseClicked(double mouseX, double mouseY, int button) {
79+
boolean alwaysVisible = alwaysVisible();
80+
if ((menuOpen || alwaysVisible) && button == 0) {
81+
WTDefinition terminal = terminalAt(mouseX, mouseY);
82+
if (terminal != null) {
83+
if (!alwaysVisible)
84+
menuOpen = false;
85+
storeState.run();
86+
AE2wtlibAPI.selectTerminal(terminal);
87+
return true;
88+
}
89+
}
90+
91+
if (alwaysVisible)
92+
return false;
93+
94+
if (super.mouseClicked(mouseX, mouseY, button))
95+
return true;
96+
97+
if (menuOpen) {
98+
menuOpen = false;
99+
}
100+
return false;
101+
}
102+
103+
@Override
104+
public int getWidth() {
105+
return alwaysVisible() ? 0 : super.getWidth();
106+
}
107+
108+
@Override
109+
public int getHeight() {
110+
return alwaysVisible() ? -TOOLBAR_BUTTON_SPACING : super.getHeight();
111+
}
112+
113+
@Override
114+
public List<Component> getTooltipMessage() {
115+
return tooltip;
116+
}
117+
118+
@Override
119+
public Rect2i getTooltipArea() {
120+
if (!menuOpen && !alwaysVisible())
121+
return super.getTooltipArea();
122+
123+
int x = panelX() - 1;
124+
int y = panelY() - 1;
125+
int right = Math.max(getX() + getWidth(), panelX() + panelWidth() + 1);
126+
int bottom = Math.max(getY() + getHeight(), panelY() + panelHeight() + 1);
127+
return new Rect2i(x, y, right - x, bottom - y);
128+
}
129+
130+
private static List<WTDefinition> installedTerminals(WTMenuHost host) {
131+
List<WTDefinition> terminals = new ArrayList<>();
132+
for (var terminal : WTDefinition.wirelessTerminalList) {
133+
if (WUTHandler.hasTerminal(host.getItemStack(), terminal))
134+
terminals.add(terminal);
135+
}
136+
return terminals;
137+
}
138+
139+
private boolean alwaysVisible() {
140+
return AE2wtlibAPI.alwaysShowTerminalSelector() && !terminals.isEmpty();
141+
}
142+
143+
private List<Component> currentTooltip() {
144+
WTDefinition terminal = WTDefinition.ofOrNull(host.getItemStack());
145+
if (terminal == null)
146+
return List.of(TextConstants.TERMINAL_EMPTY);
147+
return List.of(TextConstants.currentTerminal(terminal));
148+
}
149+
150+
private void renderMenu(GuiGraphics guiGraphics, int mouseX, int mouseY) {
151+
int panelX = panelX();
152+
int panelY = panelY();
153+
int width = panelWidth();
154+
int height = panelHeight();
155+
WTDefinition currentTerminal = WTDefinition.ofOrNull(host.getItemStack());
156+
157+
BackgroundGenerator.draw(width, height, guiGraphics, panelX, panelY);
158+
159+
for (int i = 0; i < terminals.size(); i++) {
160+
WTDefinition terminal = terminals.get(i);
161+
int x = buttonX(i);
162+
int y = buttonY(i);
163+
boolean selected = terminal.equals(currentTerminal);
164+
boolean hovered = mouseX >= x && mouseY >= y && mouseX < x + BUTTON_WIDTH && mouseY < y + BUTTON_HEIGHT;
165+
int yOffset = hovered ? 1 : 0;
166+
Icon background = hovered ? Icon.TOOLBAR_BUTTON_BACKGROUND_HOVERED
167+
: selected ? Icon.TOOLBAR_BUTTON_BACKGROUND_FOCUSED : Icon.TOOLBAR_BUTTON_BACKGROUND;
168+
169+
background.getBlitter().dest(x, y + yOffset, background.width(), background.height()).zOffset(2)
170+
.blit(guiGraphics);
171+
terminal.icon().getBlitter().dest(x + ICON_OFFSET, y + ICON_OFFSET + yOffset).zOffset(3).blit(guiGraphics);
172+
173+
if (hovered)
174+
tooltip = List.of(terminal.formattedName());
175+
}
176+
}
177+
178+
private WTDefinition terminalAt(double mouseX, double mouseY) {
179+
for (int i = 0; i < terminals.size(); i++) {
180+
int x = buttonX(i);
181+
int y = buttonY(i);
182+
if (mouseX >= x && mouseY >= y && mouseX < x + BUTTON_WIDTH && mouseY < y + BUTTON_HEIGHT)
183+
return terminals.get(i);
184+
}
185+
return null;
186+
}
187+
188+
private int panelX() {
189+
int x = getX();
190+
int gap = PANEL_GAP;
191+
if (alwaysVisible()) {
192+
x -= super.getWidth();
193+
gap = ALWAYS_VISIBLE_PANEL_X_OFFSET;
194+
}
195+
return x - gap - panelWidth();
196+
}
197+
198+
private int panelY() {
199+
return getY() + (alwaysVisible() ? ALWAYS_VISIBLE_PANEL_Y_OFFSET : 0);
200+
}
201+
202+
private int panelWidth() {
203+
return PANEL_PADDING * 2 + columns() * BUTTON_WIDTH + Math.max(0, columns() - 1) * GAP;
204+
}
205+
206+
private int panelHeight() {
207+
return PANEL_PADDING * 2 + 2 + rows() * BUTTON_HEIGHT + Math.max(0, rows() - 1) * GAP;
208+
}
209+
210+
private int buttonX(int index) {
211+
return panelX() + PANEL_PADDING + (index / rows()) * (BUTTON_WIDTH + GAP);
212+
}
213+
214+
private int buttonY(int index) {
215+
return panelY() + PANEL_PADDING + (index % rows()) * (BUTTON_HEIGHT + GAP) + lastColumnYOffset(index);
216+
}
217+
218+
private int columns() {
219+
return Math.max(1, (terminals.size() + rows() - 1) / rows());
220+
}
221+
222+
private int rows() {
223+
return Math.max(1, Math.min(MAX_ROWS, terminals.size()));
224+
}
225+
226+
private int lastColumnYOffset(int index) {
227+
int entries = terminals.size() % rows();
228+
if (entries == 0 || index / rows() != columns() - 1)
229+
return 0;
230+
return (rows() - entries) * (BUTTON_HEIGHT + GAP) / 2;
231+
}
232+
}

src/main/java/de/mari_023/ae2wtlib/AE2wtlibAPIImplementation.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import appeng.menu.locator.ItemMenuHostLocator;
1212

1313
import de.mari_023.ae2wtlib.api.AE2wtlibAPIImpl;
14+
import de.mari_023.ae2wtlib.api.registration.WTDefinition;
1415
import de.mari_023.ae2wtlib.networking.CycleTerminalPacket;
16+
import de.mari_023.ae2wtlib.networking.SelectTerminalPacket;
1517
import de.mari_023.ae2wtlib.networking.UpdateWUTPackage;
1618

1719
public class AE2wtlibAPIImplementation extends AE2wtlibAPIImpl {
@@ -30,6 +32,16 @@ public void cycleTerminal(boolean isHandlingRightClick) {
3032
PacketDistributor.sendToServer(new CycleTerminalPacket(isHandlingRightClick));
3133
}
3234

35+
@Override
36+
public void selectTerminal(WTDefinition terminal) {
37+
PacketDistributor.sendToServer(new SelectTerminalPacket(terminal));
38+
}
39+
40+
@Override
41+
public boolean alwaysShowTerminalSelector() {
42+
return AE2wtlibClientConfig.CONFIG.alwaysShowTerminalSelector();
43+
}
44+
3345
@Override
3446
public void updateClientTerminal(ServerPlayer player, ItemMenuHostLocator locator, ItemStack stack) {
3547
PacketDistributor.sendToPlayer(player, new UpdateWUTPackage(locator, stack));

0 commit comments

Comments
 (0)