From ade392b6d938d45854ab6739efa1617b35730ad3 Mon Sep 17 00:00:00 2001 From: xpncvr <166451092+xpncvr@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:11:23 +0100 Subject: [PATCH] Add Config setting to navigate the module search bar with the keyboard --- .../gui/screens/ModulesScreen.java | 64 ++++++++++++++++++- .../themes/meteor/widgets/WMeteorModule.java | 19 +++++- .../gui/utils/ISelectableModule.java | 10 +++ .../gui/widgets/containers/WView.java | 12 ++++ .../meteorclient/systems/config/Config.java | 7 ++ 5 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 src/main/java/meteordevelopment/meteorclient/gui/utils/ISelectableModule.java diff --git a/src/main/java/meteordevelopment/meteorclient/gui/screens/ModulesScreen.java b/src/main/java/meteordevelopment/meteorclient/gui/screens/ModulesScreen.java index 157e46be040..f69bbab074d 100644 --- a/src/main/java/meteordevelopment/meteorclient/gui/screens/ModulesScreen.java +++ b/src/main/java/meteordevelopment/meteorclient/gui/screens/ModulesScreen.java @@ -11,6 +11,8 @@ import meteordevelopment.meteorclient.gui.tabs.TabScreen; import meteordevelopment.meteorclient.gui.tabs.Tabs; import meteordevelopment.meteorclient.gui.utils.Cell; +import meteordevelopment.meteorclient.gui.utils.ISelectableModule; +import meteordevelopment.meteorclient.gui.widgets.WWidget; import meteordevelopment.meteorclient.gui.widgets.containers.WContainer; import meteordevelopment.meteorclient.gui.widgets.containers.WSection; import meteordevelopment.meteorclient.gui.widgets.containers.WVerticalList; @@ -30,6 +32,7 @@ import java.util.List; import java.util.Set; +import static meteordevelopment.meteorclient.MeteorClient.mc; import static meteordevelopment.meteorclient.utils.Utils.getWindowHeight; import static meteordevelopment.meteorclient.utils.Utils.getWindowWidth; import static com.mojang.blaze3d.platform.InputConstants.*; @@ -38,6 +41,8 @@ public class ModulesScreen extends TabScreen { private WCategoryController controller; private WWindow searchWindow; private WTextBox searchTextBox; + private final List searchResults = new ArrayList<>(); + private int selectedIndex; public ModulesScreen(GuiTheme theme) { super(theme, Tabs.get().getFirst()); @@ -59,6 +64,11 @@ public void initWidgets() { protected void init() { super.init(); controller.refresh(); + + if (Config.get().moduleKeyboardNavigation.get() && searchTextBox != null && !searchTextBox.get().isEmpty()) { + searchTextBox.setFocused(true); + searchTextBox.setCursorMax(); + } } // Category @@ -88,6 +98,8 @@ protected WWindow createCategory(WContainer c, Category category, List m // Search protected void createSearchW(WContainer w, String text) { + searchResults.clear(); + if (!text.isEmpty()) { // Titles List> modules = Modules.get().searchTitles(text); @@ -99,7 +111,8 @@ protected void createSearchW(WContainer w, String text) { int count = 0; for (Pair p : modules) { if (count >= Config.get().moduleSearchCount.get() || count >= modules.size()) break; - section.add(theme.module(p.getFirst(), p.getSecond())).expandX(); + WWidget widget = section.add(theme.module(p.getFirst(), p.getSecond())).expandX().widget(); + searchResults.add(new SearchResult(widget, p.getFirst())); count++; } } @@ -114,7 +127,8 @@ protected void createSearchW(WContainer w, String text) { int count = 0; for (Module module : settings) { if (count >= Config.get().moduleSearchCount.get() || count >= settings.size()) break; - section.add(theme.module(module)).expandX(); + WWidget widget = section.add(theme.module(module)).expandX().widget(); + searchResults.add(new SearchResult(widget, module)); count++; } } @@ -142,15 +156,40 @@ protected WWindow createSearch(WContainer c) { searchTextBox = text; text.action = () -> { l.clear(); + selectedIndex = 0; createSearchW(l, text.get()); + applySelection(); }; w.add(l).expandX(); createSearchW(l, text.get()); + applySelection(); return w; } + private record SearchResult(WWidget widget, Module module) {} + + private boolean keyboardNavigationActive() { + return Config.get().moduleKeyboardNavigation.get() && searchTextBox != null && searchTextBox.isFocused() && !searchResults.isEmpty(); + } + + private void applySelection() { + boolean active = Config.get().moduleKeyboardNavigation.get() && !searchResults.isEmpty(); + + for (int i = 0; i < searchResults.size(); i++) { + if (searchResults.get(i).widget() instanceof ISelectableModule selectable) selectable.setSelected(active && i == selectedIndex); + } + } + + private void moveSelection(int amount) { + int size = searchResults.size(); + selectedIndex = ((selectedIndex + amount) % size + size) % size; + + applySelection(); + if (searchWindow != null) searchWindow.view.scrollIntoView(searchResults.get(selectedIndex).widget()); + } + @Override public boolean keyPressed(@NonNull KeyEvent value) { if (locked) return false; @@ -167,6 +206,27 @@ public boolean keyPressed(@NonNull KeyEvent value) { return true; } + if (keyboardNavigationActive()) { + if (value.key() == KEY_DOWN) { + moveSelection(1); + return true; + } + + if (value.key() == KEY_UP) { + moveSelection(-1); + return true; + } + + if (value.key() == KEY_RETURN || value.key() == KEY_NUMPADENTER) { + Module module = searchResults.get(selectedIndex).module(); + + if ((value.modifiers() & MOD_SHIFT) != 0) mc.gui.setScreen(theme.moduleScreen(module)); + else module.toggle(); + + return true; + } + } + return super.keyPressed(value); } diff --git a/src/main/java/meteordevelopment/meteorclient/gui/themes/meteor/widgets/WMeteorModule.java b/src/main/java/meteordevelopment/meteorclient/gui/themes/meteor/widgets/WMeteorModule.java index ffbf194c944..653ac60bbdb 100644 --- a/src/main/java/meteordevelopment/meteorclient/gui/themes/meteor/widgets/WMeteorModule.java +++ b/src/main/java/meteordevelopment/meteorclient/gui/themes/meteor/widgets/WMeteorModule.java @@ -9,6 +9,7 @@ import meteordevelopment.meteorclient.gui.themes.meteor.MeteorGuiTheme; import meteordevelopment.meteorclient.gui.themes.meteor.MeteorWidget; import meteordevelopment.meteorclient.gui.utils.AlignmentX; +import meteordevelopment.meteorclient.gui.utils.ISelectableModule; import meteordevelopment.meteorclient.gui.widgets.pressable.WPressable; import meteordevelopment.meteorclient.systems.modules.Module; import net.minecraft.util.Mth; @@ -17,10 +18,12 @@ import static com.mojang.blaze3d.platform.InputConstants.MOUSE_BUTTON_LEFT; import static com.mojang.blaze3d.platform.InputConstants.MOUSE_BUTTON_RIGHT; -public class WMeteorModule extends WPressable implements MeteorWidget { +public class WMeteorModule extends WPressable implements MeteorWidget, ISelectableModule { private final Module module; private final String title; + private boolean selected; + private double titleWidth; private double animationProgress1; @@ -41,6 +44,11 @@ public WMeteorModule(Module module, String title) { } } + @Override + public void setSelected(boolean selected) { + this.selected = selected; + } + @Override public double pad() { return theme.scale(4); @@ -80,6 +88,15 @@ protected void onRender(GuiRenderer renderer, double mouseX, double mouseY, doub renderer.quad(x, y + height * (1 - animationProgress2), theme.scale(2), height * animationProgress2, theme.accentColor.get()); } + if (selected) { + double t = theme.scale(2); + + renderer.quad(this.x, this.y, width, t, theme.accentColor.get()); + renderer.quad(this.x, this.y + height - t, width, t, theme.accentColor.get()); + renderer.quad(this.x, this.y + t, t, height - t * 2, theme.accentColor.get()); + renderer.quad(this.x + width - t, this.y + t, t, height - t * 2, theme.accentColor.get()); + } + double x = this.x + pad; double w = width - pad * 2; diff --git a/src/main/java/meteordevelopment/meteorclient/gui/utils/ISelectableModule.java b/src/main/java/meteordevelopment/meteorclient/gui/utils/ISelectableModule.java new file mode 100644 index 00000000000..83eb0c1acde --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/gui/utils/ISelectableModule.java @@ -0,0 +1,10 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.gui.utils; + +public interface ISelectableModule { + void setSelected(boolean selected); +} diff --git a/src/main/java/meteordevelopment/meteorclient/gui/widgets/containers/WView.java b/src/main/java/meteordevelopment/meteorclient/gui/widgets/containers/WView.java index 44ea22245c8..e9f107e6a15 100644 --- a/src/main/java/meteordevelopment/meteorclient/gui/widgets/containers/WView.java +++ b/src/main/java/meteordevelopment/meteorclient/gui/widgets/containers/WView.java @@ -193,4 +193,16 @@ protected double handleY() { public boolean isWidgetInView(WWidget widget) { return widget.y < y + height && widget.y + widget.height > y; } + + public void scrollIntoView(WWidget widget) { + if (!canScroll) return; + + double max = actualHeight - height; + double delta = 0; + + if (widget.y < y) delta = widget.y - y; + else if (widget.y + widget.height > y + height) delta = widget.y + widget.height - (y + height); + + if (delta != 0) targetScroll = Mth.clamp(targetScroll + delta, 0, max); + } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/config/Config.java b/src/main/java/meteordevelopment/meteorclient/systems/config/Config.java index ba0d7db1543..ed498ef8a65 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/config/Config.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/config/Config.java @@ -154,6 +154,13 @@ public class Config extends System { .build() ); + public final Setting moduleKeyboardNavigation = sgModules.add(new BoolSetting.Builder() + .name("navigate-modules-with-keyboard") + .description("Navigate the module search bar with the arrow keys, toggle with enter and open settings with shift + enter.") + .defaultValue(false) + .build() + ); + // Chat public final Setting prefix = sgChat.add(new StringSetting.Builder()