Skip to content

Commit c6494ca

Browse files
committed
Add option to block held movement key input
1 parent 162f578 commit c6494ca

10 files changed

Lines changed: 186 additions & 3 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Added an option to disable saving when exiting using the 'Escape' key.
88
- Added a 'Revert' GUI button.
99
- Added an option to conditionally prevent opening the sign editor.
10+
- Added an option to block movement key input until key release when opening the sign editor.
1011

1112
## 1.2.0
1213

common/src/main/java/dev/terminalmc/signedit/SignEdit.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
import net.minecraft.network.chat.Component;
2525
import org.apache.logging.log4j.Logger;
2626

27+
import java.util.ArrayList;
28+
import java.util.HashSet;
2729
import java.util.List;
30+
import java.util.Set;
2831

2932
public class SignEdit {
3033

@@ -38,6 +41,9 @@ public class SignEdit {
3841
.withStyle(ChatFormatting.GRAY);
3942
public static final List<KeyMapping> KEYBINDS = List.of();
4043

44+
public static final List<KeyMapping> checkKeys = new ArrayList<>();
45+
public static final Set<KeyMapping> downKeys = new HashSet<>();
46+
4147
public static boolean enhancedEditing;
4248

4349
public static String[] copiedLines;

common/src/main/java/dev/terminalmc/signedit/config/Config.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ public static class Options {
7474
public static final boolean showLineBreakIndicatorDefault = false;
7575
public boolean showLineBreakIndicator = showLineBreakIndicatorDefault;
7676

77+
public static final boolean blockMovementKeysDefault = true;
78+
public boolean blockMovementKeys = blockMovementKeysDefault;
79+
7780
public static final boolean saveOnEscapeDefault = true;
7881
public boolean saveOnEscape = saveOnEscapeDefault;
7982

common/src/main/java/dev/terminalmc/signedit/gui/screen/ClothScreenProvider.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package dev.terminalmc.signedit.gui.screen;
1818

1919
import dev.terminalmc.signedit.config.Config;
20+
import dev.terminalmc.signedit.config.Config.Options;
2021
import me.shedaniel.clothconfig2.api.ConfigBuilder;
2122
import me.shedaniel.clothconfig2.api.ConfigCategory;
2223
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
@@ -95,6 +96,15 @@ static Screen getConfigScreen(Screen parent) {
9596
.setSaveConsumer(val -> options.showLineBreakIndicator = val)
9697
.build());
9798

99+
general.addEntry(eb.startBooleanToggle(
100+
localized("option", "general.blockMovementKeys"),
101+
options.blockMovementKeys
102+
)
103+
.setTooltip(localized("option", "general.blockMovementKeys.tooltip"))
104+
.setDefaultValue(Options.blockMovementKeysDefault)
105+
.setSaveConsumer(val -> options.blockMovementKeys = val)
106+
.build());
107+
98108
general.addEntry(eb.startBooleanToggle(
99109
localized("option", "general.saveOnEscape"),
100110
options.saveOnEscape

common/src/main/java/dev/terminalmc/signedit/mixin/edit/AbstractSignEditScreenMixin.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@
2222
import dev.terminalmc.signedit.SignEdit;
2323
import dev.terminalmc.signedit.helper.FieldHelper;
2424
import dev.terminalmc.signedit.helper.ScreenHelper;
25+
import dev.terminalmc.signedit.mixin.input.ContainerEventHandlerMixin;
26+
import dev.terminalmc.signedit.mixin.input.LocalPlayerMixin;
27+
import net.minecraft.client.KeyMapping;
2528
import net.minecraft.client.Minecraft;
2629
import net.minecraft.client.gui.GuiGraphicsExtractor;
2730
import net.minecraft.client.gui.font.TextFieldHelper;
2831
import net.minecraft.client.gui.screens.Screen;
2932
import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen;
33+
import net.minecraft.client.input.CharacterEvent;
3034
import net.minecraft.client.input.KeyEvent;
3135
import net.minecraft.network.chat.Component;
3236
import net.minecraft.world.level.block.entity.SignBlockEntity;
@@ -44,6 +48,7 @@
4448

4549
@Debug(export = true)
4650
@Mixin(AbstractSignEditScreen.class)
51+
@SuppressWarnings("JavadocReference")
4752
public abstract class AbstractSignEditScreenMixin extends Screen {
4853

4954
protected AbstractSignEditScreenMixin(Component title) {
@@ -75,6 +80,12 @@ protected AbstractSignEditScreenMixin(Component title) {
7580
@Final
7681
private boolean isFrontText;
7782

83+
@Unique
84+
private static boolean signEdit$cancelKeyPressed;
85+
86+
@Unique
87+
private static long signEdit$cancelKeyPressedTime;
88+
7889
/**
7990
* Replaces the existing {@link TextFieldHelper} with a new {@link FieldHelper}.
8091
*/
@@ -122,10 +133,24 @@ instance, new FieldHelper(
122133
}
123134

124135
/**
125-
* Diverts key-presses to {@link ScreenHelper#keyPressed}.
136+
* Blocks key-presses if required, otherwise redirects to {@link ScreenHelper#keyPressed}.
137+
*
138+
* @see LocalPlayerMixin#onOpenTextEdit
139+
* @see ContainerEventHandlerMixin#wrapKeyReleased
140+
* @see #wrapCharTyped
126141
*/
127142
@WrapMethod(method = "keyPressed")
128143
private boolean wrapKeyPressed(KeyEvent event, Operation<Boolean> original) {
144+
if (options().blockMovementKeys) {
145+
for (KeyMapping keyMapping : SignEdit.downKeys) {
146+
if (keyMapping.matches(event)) {
147+
signEdit$cancelKeyPressed = true;
148+
signEdit$cancelKeyPressedTime = System.nanoTime();
149+
return false;
150+
}
151+
}
152+
}
153+
129154
if (!SignEdit.enhancedEditing)
130155
return original.call(event);
131156

@@ -141,6 +166,24 @@ private boolean wrapKeyPressed(KeyEvent event, Operation<Boolean> original) {
141166
return false;
142167
}
143168

169+
/**
170+
* Blocks the {@link AbstractSignEditScreen#charTyped} event associated with a blocked key
171+
* press.
172+
*
173+
* @see #wrapKeyPressed
174+
*/
175+
@WrapMethod(method = "charTyped")
176+
private boolean wrapCharTyped(CharacterEvent event, Operation<Boolean> original) {
177+
if (signEdit$cancelKeyPressed) {
178+
signEdit$cancelKeyPressed = false;
179+
// Cancel only if the most recent canceled press
180+
// was less than 5 milliseconds ago
181+
if (System.nanoTime() - signEdit$cancelKeyPressedTime < 5_000_000)
182+
return false;
183+
}
184+
return original.call(event);
185+
}
186+
144187
/**
145188
* At the start of the render pass, sets the active line to the line that the cursor is
146189
* currently on.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2026 TerminalMC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dev.terminalmc.signedit.mixin.input;
18+
19+
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
20+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
21+
import dev.terminalmc.signedit.SignEdit;
22+
import net.minecraft.client.gui.components.events.ContainerEventHandler;
23+
import net.minecraft.client.input.KeyEvent;
24+
import org.spongepowered.asm.mixin.Mixin;
25+
26+
import static dev.terminalmc.signedit.config.Config.options;
27+
28+
@Mixin(ContainerEventHandler.class)
29+
@SuppressWarnings("JavadocReference")
30+
public interface ContainerEventHandlerMixin {
31+
32+
/**
33+
* Removes released keys from the 'down' list.
34+
*
35+
* @see LocalPlayerMixin#onOpenTextEdit
36+
* @see dev.terminalmc.signedit.mixin.edit.AbstractSignEditScreenMixin#wrapKeyPressed
37+
*/
38+
@WrapMethod(method = "keyReleased")
39+
default boolean wrapKeyReleased(KeyEvent event, Operation<Boolean> original) {
40+
if (options().blockMovementKeys) {
41+
SignEdit.downKeys.removeIf(keyMapping -> keyMapping.matches(event));
42+
}
43+
return original.call(event);
44+
}
45+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2026 TerminalMC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dev.terminalmc.signedit.mixin.input;
18+
19+
import com.mojang.blaze3d.platform.Window;
20+
import dev.terminalmc.signedit.SignEdit;
21+
import net.minecraft.client.KeyMapping;
22+
import net.minecraft.client.Minecraft;
23+
import net.minecraft.client.Options;
24+
import net.minecraft.client.player.LocalPlayer;
25+
import net.minecraft.world.level.block.entity.SignBlockEntity;
26+
import org.spongepowered.asm.mixin.Mixin;
27+
import org.spongepowered.asm.mixin.injection.At;
28+
import org.spongepowered.asm.mixin.injection.Inject;
29+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
30+
31+
import static dev.terminalmc.signedit.config.Config.options;
32+
33+
@Mixin(LocalPlayer.class)
34+
@SuppressWarnings("JavadocReference")
35+
public abstract class LocalPlayerMixin {
36+
37+
/**
38+
* Records the keys that are pressed on opening the sign editor, to allow subsequent blocking.
39+
*
40+
* @see ContainerEventHandlerMixin#wrapKeyReleased
41+
* @see dev.terminalmc.signedit.mixin.edit.AbstractSignEditScreenMixin#wrapKeyPressed
42+
*/
43+
@Inject(
44+
method = "openTextEdit",
45+
at = @At("HEAD")
46+
)
47+
private void onOpenTextEdit(SignBlockEntity sign, boolean isFrontText, CallbackInfo ci) {
48+
Window window = Minecraft.getInstance().getWindow();
49+
SignEdit.downKeys.clear();
50+
if (options().blockMovementKeys) {
51+
52+
if (SignEdit.checkKeys.isEmpty()) {
53+
Options options = Minecraft.getInstance().options;
54+
SignEdit.checkKeys.add(options.keyUp);
55+
SignEdit.checkKeys.add(options.keyLeft);
56+
SignEdit.checkKeys.add(options.keyDown);
57+
SignEdit.checkKeys.add(options.keyRight);
58+
SignEdit.checkKeys.add(options.keyJump);
59+
SignEdit.checkKeys.add(options.keyShift);
60+
SignEdit.checkKeys.add(options.keySprint);
61+
SignEdit.checkKeys.add(options.keyUse);
62+
}
63+
64+
for (KeyMapping keyMapping : SignEdit.checkKeys) {
65+
if (keyMapping.isDown()) {
66+
SignEdit.downKeys.add(keyMapping);
67+
}
68+
}
69+
}
70+
}
71+
}

common/src/main/java/dev/terminalmc/signedit/mixin/interact/ClientPacketListenerMixin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import static dev.terminalmc.signedit.config.Config.options;
2929

3030
@Mixin(ClientPacketListener.class)
31-
public class ClientPacketListenerMixin {
31+
public abstract class ClientPacketListenerMixin {
3232

3333
/**
3434
* Alters the {@link ClientboundOpenSignEditorPacket} handler to optionally prevent opening the

common/src/main/resources/assets/signedit/lang/en_us.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
"option.signedit.general.actionButtonsCloseUi.tooltip": "Whether clicking the quick action buttons should close the sign editor.",
2525
"option.signedit.general.showLineBreakIndicator": "Show Line Breaks",
2626
"option.signedit.general.showLineBreakIndicator.tooltip": "Whether to show a symbol where a line is broken manually rather than automatically.",
27+
"option.signedit.general.blockMovementKeys": "Block Movement Keys",
28+
"option.signedit.general.blockMovementKeys.tooltip": "Whether to block input from movement keys that are held down when opening the sign editor, until the keys are released.",
2729
"option.signedit.general.saveOnEscape": "Save On Escape",
2830
"option.signedit.general.saveOnEscape.tooltip": "Whether to save the sign content when exiting by pressing the '%s' key rather than clicking the '%s' button.",
2931
"option.signedit.general.editCondition": "Edit Condition",
30-
"option.signedit.general.editCondition.tooltip": "Additional condition to open the sign editor screen when right-clicking.",
32+
"option.signedit.general.editCondition.tooltip": "Additional condition to open the sign editor when right-clicking.",
3133
"option.signedit.general.editCondition.ALWAYS": "No Condition",
3234
"option.signedit.general.editCondition.SNEAKING": "Only if Sneaking",
3335
"option.signedit.general.editCondition.NOT_SNEAKING": "Only if not Sneaking",

common/src/main/resources/signedit.mixins.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"edit.AbstractSignEditScreenMixin",
1515
"gui.AbstractSignEditScreenMixin",
1616
"gui.ScreenMixin",
17+
"input.ContainerEventHandlerMixin",
18+
"input.LocalPlayerMixin",
1719
"interact.ClientPacketListenerMixin"
1820
],
1921
"server": [

0 commit comments

Comments
 (0)