From 04c283bed0186d31176f3aa03c16e8a65cc58609 Mon Sep 17 00:00:00 2001 From: SirKaiMartin Date: Tue, 1 Sep 2026 18:44:47 +0200 Subject: [PATCH 1/2] fix: restrict the emote picker keybind to active chat - ignore mouse and keyboard picker bindings while chat is closed - consume the triggering keyboard press when opening the picker - allow the bound key to behave as normal chat input while the picker is open - reopen the picker with the binding after it has been closed --- src/tel/eden/mod/EdenModClient.java | 18 ++++------------- src/tel/eden/mod/mixin/ChatScreenMixin.java | 22 ++++++++++++++++++--- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/tel/eden/mod/EdenModClient.java b/src/tel/eden/mod/EdenModClient.java index 75fe081..49d0942 100644 --- a/src/tel/eden/mod/EdenModClient.java +++ b/src/tel/eden/mod/EdenModClient.java @@ -307,16 +307,8 @@ public boolean matchesOpenEmotePickerMouse(net.minecraft.client.input.MouseButto return openEmotePickerKey.matchesMouse(event); } - public boolean isOpenEmotePickerMouseBound() { - return openEmotePickerKey.saveString().startsWith("key.mouse."); - } - - public boolean shouldOpenEmotePickerOnChatOpen() { - Minecraft mc = Minecraft.getInstance(); - if (isOpenEmotePickerMouseBound() || mc.options == null) { - return false; - } - return openEmotePickerKey.saveString().equals(mc.options.keyChat.saveString()) && openEmotePickerKey.isDown(); + public boolean matchesOpenEmotePickerKey(net.minecraft.client.input.KeyEvent event) { + return openEmotePickerKey.matches(event); } public void openCenteredEmotePicker() { @@ -555,10 +547,8 @@ private void onClientTick(Minecraft client) { } } while (openEmotePickerKey.consumeClick()) { - if (client.screen instanceof ChatScreen && isOpenEmotePickerMouseBound()) { - continue; - } - openCenteredEmotePicker(); + // Mouse and keyboard bindings are handled directly by ChatScreenMixin. Drain + // queued clicks here so input outside chat can never open the picker later. } pollCommandKeybinds(client); if (pendingUpdateNotification && client.player != null) { diff --git a/src/tel/eden/mod/mixin/ChatScreenMixin.java b/src/tel/eden/mod/mixin/ChatScreenMixin.java index 9444ba4..418c410 100644 --- a/src/tel/eden/mod/mixin/ChatScreenMixin.java +++ b/src/tel/eden/mod/mixin/ChatScreenMixin.java @@ -143,6 +143,9 @@ public abstract class ChatScreenMixin { @Unique private int edenmod$sliderPreviewRows = -1; + @Unique + private boolean edenmod$suppressPickerKeyChars; + @Inject(method = "init", at = @At("TAIL")) private void edenmod$addEmoteFormatter(CallbackInfo ci) { input.addFormatter((text, cursor) -> { @@ -152,9 +155,6 @@ public abstract class ChatScreenMixin { FormattedCharSequence formatted = ChatEmoteFormatter.format(text); return formatted; }); - if (EdenModClient.instance().shouldOpenEmotePickerOnChatOpen()) { - EdenModClient.instance().requestCenteredEmotePicker(); - } edenmod$openCenteredPickerIfRequested(); } @@ -164,6 +164,14 @@ public abstract class ChatScreenMixin { edenmod$resetOverlayState(); return; } + if (edenmod$isChatEmoteUiVisible() && !edenmod$pickerOpen && EdenModClient.instance().matchesOpenEmotePickerKey(event)) { + double[] mouse = edenmod$currentMousePosition(); + edenmod$openPickerForMode(mouse[0], mouse[1]); + edenmod$suppressPickerKeyChars = true; + input.setEditable(false); + cir.setReturnValue(true); + return; + } if (edenmod$isChatEmoteUiVisible() && edenmod$pickerOpen && event.key() == GLFW.GLFW_KEY_ESCAPE) { if (edenmod$pickerSettingsOpen) { edenmod$pickerSettingsOpen = false; @@ -261,6 +269,10 @@ public abstract class ChatScreenMixin { @Inject(method = "render", at = @At("TAIL")) private void edenmod$renderChatOverlays(GuiGraphics graphics, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (edenmod$suppressPickerKeyChars) { + input.setEditable(true); + edenmod$suppressPickerKeyChars = false; + } // Hovering an attack-timer head shows that player's IGN (works regardless of the // emote-tool settings, like the timer click). AttackTimerMenu.renderGoerTooltip(graphics, mouseX, mouseY); @@ -989,6 +1001,10 @@ public abstract class ChatScreenMixin { @Unique private void edenmod$resetOverlayState() { + if (edenmod$suppressPickerKeyChars) { + input.setEditable(true); + edenmod$suppressPickerKeyChars = false; + } edenmod$pickerOpen = false; edenmod$pickerSettingsOpen = false; edenmod$draggingSlider = null; From 0af702e3652ab765b6ce8e4cc2b7efd599e6af4c Mon Sep 17 00:00:00 2001 From: SirKaiMartin Date: Wed, 2 Sep 2026 14:36:01 +0200 Subject: [PATCH 2/2] feat: add a toggle for opening the emote picker outside chat - add a config setting that controls whether the emote-picker keybind can open chat from gameplay - restore the prior gameplay-entry logic when the setting is enabled - keep chat-only picker handling in the chat mixin and centralize gameplay checks in EdenModClient - preserve shared chat-key bindings so the picker can still open on the first press --- src/tel/eden/mod/EdenModClient.java | 44 +++++++++++++++++++- src/tel/eden/mod/config/BridgeConfig.java | 7 ++++ src/tel/eden/mod/gui/BridgeConfigScreen.java | 1 + src/tel/eden/mod/mixin/ChatScreenMixin.java | 5 +++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/tel/eden/mod/EdenModClient.java b/src/tel/eden/mod/EdenModClient.java index 49d0942..b87dafc 100644 --- a/src/tel/eden/mod/EdenModClient.java +++ b/src/tel/eden/mod/EdenModClient.java @@ -307,10 +307,28 @@ public boolean matchesOpenEmotePickerMouse(net.minecraft.client.input.MouseButto return openEmotePickerKey.matchesMouse(event); } + public boolean isOpenEmotePickerMouseBound() { + return openEmotePickerKey.saveString().startsWith("key.mouse."); + } + public boolean matchesOpenEmotePickerKey(net.minecraft.client.input.KeyEvent event) { return openEmotePickerKey.matches(event); } + /** + * If the picker shares chat's gameplay keybind, vanilla opens chat before the chat + * screen can inspect the triggering press. Detect that held key during chat init + * so the picker still opens without requiring a second press. + */ + public boolean shouldOpenEmotePickerOnChatOpen() { + Minecraft mc = Minecraft.getInstance(); + if (!shouldAllowGameplayEmotePickerOpen() || isOpenEmotePickerMouseBound() || mc.options == null) { + return false; + } + return edenmod$matchesGameplayChatKeybind(mc.options.keyChat) || edenmod$matchesGameplayChatKeybind(mc.options.keyCommand); + } + + /** Queue the emote picker for the next chat screen, opening chat if needed. */ public void openCenteredEmotePicker() { Minecraft mc = Minecraft.getInstance(); requestCenteredEmotePicker(); @@ -331,6 +349,24 @@ public java.util.List knownPendingAspects() { return knownPendingAspects; } + private boolean shouldAllowGameplayEmotePickerOpen() { + if (!config.emotePickerOpenFromGameplay) { + return false; + } + return switch (config.chatEmoteToolsMode) { + case UI, UI_AND_AUTO -> true; + case AUTO, NONE -> false; + }; + } + + private boolean shouldOpenEmotePickerFromGameplay(Minecraft client) { + return shouldAllowGameplayEmotePickerOpen() && client.screen == null; + } + + private boolean edenmod$matchesGameplayChatKeybind(KeyMapping vanillaKey) { + return openEmotePickerKey.saveString().equals(vanillaKey.saveString()) && openEmotePickerKey.isDown(); + } + /** The error from the last aspects-pending reply, or {@code null} if it succeeded. */ public String pendingAspectsError() { return pendingAspectsError; @@ -547,8 +583,12 @@ private void onClientTick(Minecraft client) { } } while (openEmotePickerKey.consumeClick()) { - // Mouse and keyboard bindings are handled directly by ChatScreenMixin. Drain - // queued clicks here so input outside chat can never open the picker later. + // ChatScreenMixin handles picker input while chat is already open. Only the + // gameplay-entry path goes through this tick hook, keeping the hot path to one + // cheap screen/config check and preventing stale queued clicks from replaying. + if (shouldOpenEmotePickerFromGameplay(client)) { + openCenteredEmotePicker(); + } } pollCommandKeybinds(client); if (pendingUpdateNotification && client.player != null) { diff --git a/src/tel/eden/mod/config/BridgeConfig.java b/src/tel/eden/mod/config/BridgeConfig.java index cfdb003..371fc19 100644 --- a/src/tel/eden/mod/config/BridgeConfig.java +++ b/src/tel/eden/mod/config/BridgeConfig.java @@ -196,6 +196,13 @@ public String label() { /** Which chat emote tools are enabled: inline/picker UI, autocomplete, both, or none. */ public ChatEmoteToolsMode chatEmoteToolsMode = ChatEmoteToolsMode.UI_AND_AUTO; + /** + * Whether the emote-picker keybind may open chat directly from gameplay when no + * other screen is open. When disabled, the binding only works while chat is + * already focused. + */ + public boolean emotePickerOpenFromGameplay = true; + /** Visible emote-picker columns in the chat overlay. */ public int emotePickerColumns = 5; diff --git a/src/tel/eden/mod/gui/BridgeConfigScreen.java b/src/tel/eden/mod/gui/BridgeConfigScreen.java index be11986..359fd03 100644 --- a/src/tel/eden/mod/gui/BridgeConfigScreen.java +++ b/src/tel/eden/mod/gui/BridgeConfigScreen.java @@ -84,6 +84,7 @@ protected void init() { addToggleRow("My login/logout messages", () -> config.announceSelfPresence, v -> config.announceSelfPresence = v, "On", "Off", true); addToggleRow("Party feed", () -> config.partyAnnounce, v -> config.partyAnnounce = v, "On", "Off", true); addCycleRow("Chat emote tools", () -> config.chatEmoteToolsMode.label(), () -> config.chatEmoteToolsMode = nextChatEmoteToolsMode(config.chatEmoteToolsMode), () -> config.chatEmoteToolsMode = BridgeConfig.ChatEmoteToolsMode.UI_AND_AUTO); + addToggleRow("Allow emote picker outside chat", () -> config.emotePickerOpenFromGameplay, v -> config.emotePickerOpenFromGameplay = v, "Allowed", "Chat only", true); addCycleRow("Game messages", () -> shortGameModeLabel(config.gameDisplayMode), () -> config.gameDisplayMode = nextGameMode(config.gameDisplayMode), () -> config.gameDisplayMode = BridgeConfig.GameDisplayMode.ALL); PreviewSizeSlider slider = new PreviewSizeSlider(CONTROL_W, 20); addSliderRow("Image preview size", slider, slider::syncFromConfig, () -> config.imagePreviewSize = 40); diff --git a/src/tel/eden/mod/mixin/ChatScreenMixin.java b/src/tel/eden/mod/mixin/ChatScreenMixin.java index 418c410..6bf0f5a 100644 --- a/src/tel/eden/mod/mixin/ChatScreenMixin.java +++ b/src/tel/eden/mod/mixin/ChatScreenMixin.java @@ -155,6 +155,11 @@ public abstract class ChatScreenMixin { FormattedCharSequence formatted = ChatEmoteFormatter.format(text); return formatted; }); + // Shared chat/picker bindings open the screen before this mixin sees the original + // key press, so init needs to revive the queued picker request for that case. + if (EdenModClient.instance().shouldOpenEmotePickerOnChatOpen()) { + EdenModClient.instance().requestCenteredEmotePicker(); + } edenmod$openCenteredPickerIfRequested(); }