Skip to content

Commit 15f2b97

Browse files
authored
Feature: Mouse button and modifier binds (#159)
1 parent 4078c2c commit 15f2b97

File tree

16 files changed

+280
-224
lines changed

16 files changed

+280
-224
lines changed

src/main/java/com/lambda/mixin/input/KeyboardMixin.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import com.lambda.event.EventFlow;
2121
import com.lambda.event.events.KeyboardEvent;
2222
import com.lambda.module.modules.player.InventoryMove;
23+
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
24+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
2325
import net.minecraft.client.Keyboard;
2426
import net.minecraft.client.option.KeyBinding;
2527
import net.minecraft.client.util.InputUtil;
@@ -30,9 +32,10 @@
3032

3133
@Mixin(Keyboard.class)
3234
public class KeyboardMixin {
33-
@Inject(method = "onKey", at = @At("HEAD"))
34-
private void onKey(long window, int key, int scancode, int action, int modifiers, CallbackInfo ci) {
35+
@WrapMethod(method = "onKey")
36+
private void onKey(long window, int key, int scancode, int action, int modifiers, Operation<Void> original) {
3537
EventFlow.post(new KeyboardEvent.Press(key, scancode, action, modifiers));
38+
original.call(window, key, scancode, action, modifiers);
3639
}
3740

3841
@Inject(method = "onKey", at = @At("RETURN"))
@@ -42,12 +45,13 @@ private void onKeyTail(long window, int key, int scancode, int action, int modif
4245
KeyBinding.setKeyPressed(fromCode, action != 0);
4346
}
4447

45-
@Inject(method = "onChar", at = @At("HEAD"))
46-
private void onChar(long window, int codePoint, int modifiers, CallbackInfo ci) {
48+
@WrapMethod(method = "onChar")
49+
private void onChar(long window, int codePoint, int modifiers, Operation<Void> original) {
4750
char[] chars = Character.toChars(codePoint);
4851

49-
for (char c : chars) {
52+
for (char c : chars)
5053
EventFlow.post(new KeyboardEvent.Char(c));
51-
}
54+
55+
original.call(window, codePoint, modifiers);
5256
}
5357
}

src/main/java/com/lambda/mixin/input/MouseMixin.java

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,49 +21,44 @@
2121
import com.lambda.event.events.MouseEvent;
2222
import com.lambda.module.modules.render.Zoom;
2323
import com.lambda.util.math.Vec2d;
24+
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
25+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
2426
import net.minecraft.client.Mouse;
2527
import net.minecraft.client.option.GameOptions;
2628
import net.minecraft.client.option.SimpleOption;
2729
import org.spongepowered.asm.mixin.Mixin;
2830
import org.spongepowered.asm.mixin.Shadow;
2931
import org.spongepowered.asm.mixin.injection.At;
30-
import org.spongepowered.asm.mixin.injection.Inject;
3132
import org.spongepowered.asm.mixin.injection.Redirect;
32-
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
3333

3434
@Mixin(Mouse.class)
3535
public class MouseMixin {
3636
@Shadow private double x;
3737

3838
@Shadow private double y;
3939

40-
@Inject(method = "onMouseButton(JIII)V", at = @At("HEAD"), cancellable = true)
41-
private void onMouseButton(long window, int button, int action, int mods, CallbackInfo ci) {
42-
Vec2d position = new Vec2d(x, y);
43-
44-
if (EventFlow.post(new MouseEvent.Click(button, action, mods, position)).isCanceled()) {
45-
ci.cancel();
46-
}
40+
@WrapMethod(method = "onMouseButton(JIII)V")
41+
private void onMouseButton(long window, int button, int action, int mods, Operation<Void> original) {
42+
if (!EventFlow.post(new MouseEvent.Click(button, action, mods)).isCanceled())
43+
original.call(window, button, action, mods);
4744
}
4845

49-
@Inject(method = "onMouseScroll(JDD)V", at = @At("HEAD"), cancellable = true)
50-
private void onMouseScroll(long window, double horizontal, double vertical, CallbackInfo ci) {
46+
@WrapMethod(method = "onMouseScroll(JDD)V")
47+
private void onMouseScroll(long window, double horizontal, double vertical, Operation<Void> original) {
5148
Vec2d delta = new Vec2d(horizontal, vertical);
5249

53-
if (EventFlow.post(new MouseEvent.Scroll(delta)).isCanceled()) {
54-
ci.cancel();
55-
}
50+
if (!EventFlow.post(new MouseEvent.Scroll(delta)).isCanceled())
51+
original.call(window, horizontal, vertical);
5652
}
5753

58-
@Inject(method = "onCursorPos(JDD)V", at = @At("HEAD"), cancellable = true)
59-
private void onCursorPos(long window, double x, double y, CallbackInfo ci) {
54+
@WrapMethod(method = "onCursorPos(JDD)V")
55+
private void onCursorPos(long window, double x, double y, Operation<Void> original) {
6056
if (x + y == this.x + this.y) return;
6157

6258
Vec2d position = new Vec2d(x, y);
6359

64-
if (EventFlow.post(new MouseEvent.Move(position)).isCanceled()) {
65-
ci.cancel();
66-
}
60+
if (!EventFlow.post(new MouseEvent.Move(position)).isCanceled())
61+
original.call(window, x, y);
6762
}
6863

6964
@Redirect(method = "updateMouse", at = @At(value = "FIELD", target = "Lnet/minecraft/client/option/GameOptions;smoothCameraEnabled:Z"))

src/main/kotlin/com/lambda/config/Configurable.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.lambda.config.settings.collections.MapSetting
3030
import com.lambda.config.settings.collections.SetSetting
3131
import com.lambda.config.settings.comparable.BooleanSetting
3232
import com.lambda.config.settings.comparable.EnumSetting
33+
import com.lambda.config.settings.complex.Bind
3334
import com.lambda.config.settings.complex.BlockPosSetting
3435
import com.lambda.config.settings.complex.BlockSetting
3536
import com.lambda.config.settings.complex.ColorSetting
@@ -381,7 +382,7 @@ abstract class Configurable(
381382
*/
382383
fun setting(
383384
name: String,
384-
defaultValue: KeyCode,
385+
defaultValue: Bind,
385386
description: String = "",
386387
visibility: () -> Boolean = { true },
387388
) = KeybindSetting(name, defaultValue, description, visibility).register()

src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt

Lines changed: 109 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,42 +18,55 @@
1818
package com.lambda.config.settings.complex
1919

2020
import com.google.gson.reflect.TypeToken
21+
import com.lambda.brigadier.CommandResult.Companion.failure
22+
import com.lambda.brigadier.CommandResult.Companion.success
23+
import com.lambda.brigadier.argument.boolean
2124
import com.lambda.brigadier.argument.value
2225
import com.lambda.brigadier.argument.word
23-
import com.lambda.brigadier.execute
26+
import com.lambda.brigadier.executeWithResult
27+
import com.lambda.brigadier.optional
2428
import com.lambda.brigadier.required
2529
import com.lambda.config.AbstractSetting
30+
import com.lambda.config.settings.complex.Bind.Companion.mouseBind
2631
import com.lambda.gui.dsl.ImGuiBuilder
32+
import com.lambda.util.InputUtils
2733
import com.lambda.util.KeyCode
28-
import com.lambda.util.KeyboardUtils
34+
import com.lambda.util.Mouse
2935
import com.lambda.util.StringUtils.capitalize
3036
import com.lambda.util.extension.CommandBuilder
3137
import imgui.ImGui.isMouseClicked
3238
import imgui.flag.ImGuiCol
3339
import imgui.flag.ImGuiHoveredFlags
3440
import imgui.flag.ImGuiMouseButton
3541
import net.minecraft.command.CommandRegistryAccess
36-
import org.lwjgl.glfw.GLFW
42+
import org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_SHIFT
43+
import org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_SUPER
44+
import org.lwjgl.glfw.GLFW.GLFW_MOD_ALT
45+
import org.lwjgl.glfw.GLFW.GLFW_MOD_CAPS_LOCK
46+
import org.lwjgl.glfw.GLFW.GLFW_MOD_CONTROL
47+
import org.lwjgl.glfw.GLFW.GLFW_MOD_NUM_LOCK
48+
import org.lwjgl.glfw.GLFW.GLFW_MOD_SHIFT
49+
import org.lwjgl.glfw.GLFW.GLFW_MOD_SUPER
3750

3851
class KeybindSetting(
3952
override var name: String,
40-
defaultValue: KeyCode,
53+
defaultValue: Bind,
4154
description: String,
4255
visibility: () -> Boolean,
43-
) : AbstractSetting<KeyCode>(
56+
) : AbstractSetting<Bind>(
4457
name,
4558
defaultValue,
46-
TypeToken.get(KeyCode::class.java).type,
59+
TypeToken.get(Bind::class.java).type,
4760
description,
4861
visibility
4962
) {
5063
private var listening = false
5164

5265
override fun ImGuiBuilder.buildLayout() {
53-
val key = value
54-
val scancode = if (key == KeyCode.UNBOUND) -69 else GLFW.glfwGetKeyScancode(key.code)
55-
val translated = if (key == KeyCode.UNBOUND) key else KeyCode.virtualMapUS(key.code, scancode)
56-
val preview = if (listening) "$name: Press any key…" else "$name: $translated"
66+
val bind = value
67+
val preview =
68+
if (listening) "$name: Press any key…"
69+
else bind.name
5770

5871
if (listening) {
5972
withStyleColor(ImGuiCol.Button, 0.20f, 0.50f, 1.00f, 1.00f) {
@@ -68,16 +81,8 @@ class KeybindSetting(
6881
}
6982

7083
lambdaTooltip {
71-
if (!listening) {
72-
description.ifBlank { "Click to set. Right-click to unbind. Esc cancels. Backspace/Delete unbinds." }
73-
} else {
74-
"Listening… Press a key to bind. Esc to cancel. Backspace/Delete to unbind."
75-
}
76-
}
77-
78-
onItemClick(ImGuiMouseButton.Right) {
79-
value = KeyCode.UNBOUND
80-
listening = false
84+
if (!listening) description.ifBlank { "Click to set. Esc cancels. Backspace/Delete unbinds." }
85+
else "Listening… Press a key to bind. Esc to cancel. Backspace/Delete to unbind."
8186
}
8287

8388
if (listening && !isAnyItemHovered && isMouseClicked(ImGuiMouseButton.Left)) {
@@ -86,38 +91,108 @@ class KeybindSetting(
8691

8792
sameLine()
8893
smallButton("Unbind") {
89-
value = KeyCode.UNBOUND
94+
value = Bind.EMPTY
9095
listening = false
9196
}
9297
onItemHover(ImGuiHoveredFlags.Stationary) {
9398
lambdaTooltip("Clear binding")
9499
}
95100

96-
val poll = KeyboardUtils.lastEvent
97-
if (listening && poll.isPressed) {
98-
when (val key = poll.translated) {
99-
KeyCode.ESCAPE -> listening = false
100-
KeyCode.BACKSPACE, KeyCode.DELETE -> {
101-
value = KeyCode.UNBOUND
101+
if (listening) {
102+
InputUtils.newMouseEvent()
103+
?.let {
104+
value = Bind(0, it.modifiers, it.button)
102105
listening = false
106+
return
103107
}
104-
else -> {
105-
value = key
106-
listening = false
108+
109+
InputUtils.newKeyboardEvent()
110+
?.let {
111+
val isModKey = it.keyCode in GLFW_KEY_LEFT_SHIFT..GLFW_KEY_RIGHT_SUPER
112+
113+
// If a mod key is pressed first ignore it unless it was released without any other keys
114+
if ((it.isPressed && !isModKey) || (it.isReleased && isModKey)) {
115+
when (it.translated) {
116+
KeyCode.ESCAPE -> {}
117+
KeyCode.BACKSPACE, KeyCode.DELETE -> value = Bind.EMPTY
118+
else -> value = Bind(it.keyCode, it.modifiers, -1)
119+
}
120+
121+
listening = false
122+
}
123+
124+
return
107125
}
108-
}
109126
}
110127
}
111128

112129
override fun CommandBuilder.buildCommand(registry: CommandRegistryAccess) {
113-
required(word(name)) { parameter ->
130+
required(word(name)) { name ->
114131
suggests { _, builder ->
115132
KeyCode.entries.forEach { builder.suggest(it.name.capitalize()) }
133+
(1..10).forEach { builder.suggest(it) }
116134
builder.buildFuture()
117135
}
118-
execute {
119-
trySetValue(KeyCode.valueOf(parameter().value()))
136+
optional(boolean("mouse button")) { isMouseButton ->
137+
executeWithResult {
138+
val isMouse = if (isMouseButton != null) isMouseButton().value() else false
139+
var bind = Bind.EMPTY
140+
if (isMouse) {
141+
val num = try {
142+
name().value().toInt()
143+
} catch(_: NumberFormatException) {
144+
return@executeWithResult failure("${name().value()} doesn't match with a mouse button")
145+
}
146+
bind = mouseBind(num)
147+
} else {
148+
bind = try {
149+
Bind(KeyCode.valueOf(name().value()).code, 0)
150+
} catch(_: IllegalArgumentException) {
151+
return@executeWithResult failure("${name().value()} doesn't match with a bind")
152+
}
153+
}
154+
155+
trySetValue(bind)
156+
return@executeWithResult success()
157+
}
120158
}
121159
}
122160
}
123161
}
162+
163+
data class Bind(
164+
val key: Int,
165+
val modifiers: Int,
166+
val mouse: Int = -1,
167+
) {
168+
val truemods = buildList {
169+
if (modifiers and GLFW_MOD_SHIFT != 0) add(KeyCode.LEFT_SHIFT)
170+
if (modifiers and GLFW_MOD_CONTROL != 0) add(KeyCode.LEFT_CONTROL)
171+
if (modifiers and GLFW_MOD_ALT != 0) add(KeyCode.LEFT_ALT)
172+
if (modifiers and GLFW_MOD_SUPER != 0) add(KeyCode.LEFT_SUPER)
173+
if (modifiers and GLFW_MOD_CAPS_LOCK != 0) add(KeyCode.CAPS_LOCK)
174+
if (modifiers and GLFW_MOD_NUM_LOCK != 0) add(KeyCode.NUM_LOCK)
175+
}
176+
177+
val name: String
178+
get() {
179+
if (mouse < 0 && modifiers <= 0 && key <= 0) return "Unbound"
180+
181+
val list = mutableListOf<Any>()
182+
183+
if (mouse >= 0) list.add(Mouse.entries[mouse])
184+
if (modifiers > 0) list.add(truemods.joinToString(separator = "+") { it.name })
185+
if (key > 0) list.add(KeyCode.fromKeyCode(key))
186+
187+
return list.joinToString(separator = "+") { it.toString() }
188+
}
189+
190+
override fun toString() =
191+
"Key Code: $key, Modifiers: ${truemods.joinToString(separator = "+") { it.name }}, Mouse Button: ${Mouse.entries.getOrNull(mouse) ?: "None"}"
192+
193+
companion object {
194+
val EMPTY = Bind(0, 0)
195+
196+
fun mouseBind(code: Int) = Bind(0, 0, code)
197+
}
198+
}

src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,9 @@
1717

1818
package com.lambda.event.events
1919

20+
import com.lambda.config.settings.complex.Bind
2021
import com.lambda.event.Event
2122
import com.lambda.util.KeyCode
22-
import org.lwjgl.glfw.GLFW.GLFW_MOD_ALT
23-
import org.lwjgl.glfw.GLFW.GLFW_MOD_CAPS_LOCK
24-
import org.lwjgl.glfw.GLFW.GLFW_MOD_CONTROL
25-
import org.lwjgl.glfw.GLFW.GLFW_MOD_NUM_LOCK
26-
import org.lwjgl.glfw.GLFW.GLFW_MOD_SHIFT
27-
import org.lwjgl.glfw.GLFW.GLFW_MOD_SUPER
2823
import org.lwjgl.glfw.GLFW.GLFW_PRESS
2924
import org.lwjgl.glfw.GLFW.GLFW_RELEASE
3025

@@ -51,15 +46,10 @@ sealed class KeyboardEvent {
5146
val translated: KeyCode
5247
get() = KeyCode.virtualMapUS(keyCode, scanCode)
5348

54-
val isPressed = action == GLFW_PRESS
49+
val isPressed = action >= GLFW_PRESS
5550
val isReleased = action == GLFW_RELEASE
5651

57-
val hasShift = modifiers and GLFW_MOD_SHIFT != 0
58-
val hasControl = modifiers and GLFW_MOD_CONTROL != 0
59-
val hasAlt = modifiers and GLFW_MOD_ALT != 0
60-
val hasSuper = modifiers and GLFW_MOD_SUPER != 0
61-
val hasCapsLock = modifiers and GLFW_MOD_CAPS_LOCK != 0
62-
val hasNumLock = modifiers and GLFW_MOD_NUM_LOCK != 0
52+
fun satisfies(bind: Bind) = bind.key == keyCode && bind.modifiers and modifiers == bind.modifiers
6353
}
6454

6555
/**

0 commit comments

Comments
 (0)