From 1ccca47237923a59cf433d56073dd1ceae1aeaab Mon Sep 17 00:00:00 2001 From: deirn Date: Tue, 29 Aug 2023 00:20:03 +0700 Subject: [PATCH 01/15] key binding context --- .../client/keybinding/KeyBindingRegistry.java | 2 +- .../keybinding/v1/KeyBindingContext.java | 74 +++++++++++ .../keybinding/v1/KeyBindingHelper.java | 14 +- .../keybinding/KeyBindingExtensions.java | 25 ++++ .../keybinding/KeyBindingRegistryImpl.java | 14 +- .../client/keybinding/KeyBindingMixin.java | 125 ++++++++++++++++++ .../fabric-key-binding-api-v1.mixins.json | 3 +- .../client/keybinding/KeyBindingsTest.java | 9 ++ .../lang/en_us.json | 6 +- 9 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java create mode 100644 fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingExtensions.java create mode 100644 fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java diff --git a/deprecated/fabric-keybindings-v0/src/client/java/net/fabricmc/fabric/api/client/keybinding/KeyBindingRegistry.java b/deprecated/fabric-keybindings-v0/src/client/java/net/fabricmc/fabric/api/client/keybinding/KeyBindingRegistry.java index 14e1f3869a..3372fa3cb6 100644 --- a/deprecated/fabric-keybindings-v0/src/client/java/net/fabricmc/fabric/api/client/keybinding/KeyBindingRegistry.java +++ b/deprecated/fabric-keybindings-v0/src/client/java/net/fabricmc/fabric/api/client/keybinding/KeyBindingRegistry.java @@ -37,7 +37,7 @@ public boolean addCategory(String categoryName) { @Override public boolean register(FabricKeyBinding binding) { - return KeyBindingRegistryImpl.registerKeyBinding(binding) != null; + return KeyBindingHelper.registerKeyBinding(binding) != null; } }; diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java new file mode 100644 index 0000000000..bb56fa8239 --- /dev/null +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.keybinding.v1; + +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.KeyBinding; + +import net.fabricmc.fabric.impl.client.keybinding.KeyBindingExtensions; + +/** + * {@link KeyBindingContext} decides how {@link KeyBinding} with same bounded key behaves in regard to each other. + *

+ * Bindings with different context will not conflict with each other even if they have the same bounded key. + */ +public interface KeyBindingContext { + /** + * {@link KeyBinding} that used in-game. All vanilla key binds have this context. + */ + KeyBindingContext IN_GAME = () -> MinecraftClient.getInstance().currentScreen == null; + + /** + * {@link KeyBinding} that used when a screen is open. + */ + KeyBindingContext IN_SCREEN = () -> MinecraftClient.getInstance().currentScreen != null; + + /** + * {@link KeyBinding} that might be used in any context. This context conflicts with any other context. + */ + KeyBindingContext ALL = new KeyBindingContext() { + @Override + public boolean isActive() { + return true; + } + + @Override + public boolean conflictsWith(KeyBindingContext other) { + return true; + } + }; + + static KeyBindingContext of(KeyBinding binding) { + return ((KeyBindingExtensions) binding).fabric_getContext(); + } + + static boolean conflicts(KeyBindingContext left, KeyBindingContext right) { + return left.conflictsWith(right) || right.conflictsWith(left); + } + + boolean isActive(); + + /** + * Use {@link #conflicts(KeyBindingContext, KeyBindingContext)} for checking if two context conflicts. + */ + @ApiStatus.OverrideOnly + default boolean conflictsWith(KeyBindingContext other) { + return this == other; + } +} diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.java index e87b50ff11..ee5b9fc607 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.java @@ -47,8 +47,20 @@ private KeyBindingHelper() { * @throws IllegalArgumentException when a key binding with the same ID is already registered */ public static KeyBinding registerKeyBinding(KeyBinding keyBinding) { + return registerKeyBinding(keyBinding, KeyBindingContext.IN_GAME); + } + + /** + * Registers the keybinding and add the keybinding category if required. + * + * @param keyBinding the keybinding + * @param context the keybinding context + * @return the keybinding itself + * @throws IllegalArgumentException when a key binding with the same ID is already registered + */ + public static KeyBinding registerKeyBinding(KeyBinding keyBinding, KeyBindingContext context) { Objects.requireNonNull(keyBinding, "key binding cannot be null"); - return KeyBindingRegistryImpl.registerKeyBinding(keyBinding); + return KeyBindingRegistryImpl.registerKeyBinding(keyBinding, context); } /** diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingExtensions.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingExtensions.java new file mode 100644 index 0000000000..bdd0012d35 --- /dev/null +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingExtensions.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.keybinding; + +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingContext; + +public interface KeyBindingExtensions { + KeyBindingContext fabric_getContext(); + + void fabric_setContext(KeyBindingContext context); +} diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java index 5ce55c776b..ddd3d37b3a 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java @@ -16,6 +16,10 @@ package net.fabricmc.fabric.impl.client.keybinding; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -25,10 +29,13 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingContext; import net.fabricmc.fabric.mixin.client.keybinding.KeyBindingAccessor; public final class KeyBindingRegistryImpl { + public static final Map> KEY_TO_BINDINGS = new HashMap<>(); private static final List MODDED_KEY_BINDINGS = new ReferenceArrayList<>(); // ArrayList with identity based comparisons for contains/remove/indexOf etc., required for correctly handling duplicate keybinds private KeyBindingRegistryImpl() { @@ -51,7 +58,7 @@ public static boolean addCategory(String categoryTranslationKey) { return true; } - public static KeyBinding registerKeyBinding(KeyBinding binding) { + public static KeyBinding registerKeyBinding(KeyBinding binding, KeyBindingContext context) { if (MinecraftClient.getInstance().options != null) { throw new IllegalStateException("GameOptions has already been initialised"); } @@ -66,6 +73,7 @@ public static KeyBinding registerKeyBinding(KeyBinding binding) { // This will do nothing if the category already exists. addCategory(binding.getCategory()); + ((KeyBindingExtensions) binding).fabric_setContext(context); MODDED_KEY_BINDINGS.add(binding); return binding; } @@ -80,4 +88,8 @@ public static KeyBinding[] process(KeyBinding[] keysAll) { newKeysAll.addAll(MODDED_KEY_BINDINGS); return newKeysAll.toArray(new KeyBinding[0]); } + + public static void putToMap(InputUtil.Key key, KeyBinding binding) { + KEY_TO_BINDINGS.computeIfAbsent(key, k -> new ArrayList<>()).add(binding); + } } diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java new file mode 100644 index 0000000000..99a7ac96ad --- /dev/null +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.keybinding; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; + +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingContext; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.impl.client.keybinding.KeyBindingExtensions; +import net.fabricmc.fabric.impl.client.keybinding.KeyBindingRegistryImpl; + +@Mixin(KeyBinding.class) +public abstract class KeyBindingMixin implements KeyBindingExtensions { + @Shadow + private int timesPressed; + + @Shadow + @Final + private static Map KEYS_BY_ID; + + @Shadow + private InputUtil.Key boundKey; + + @Unique + private KeyBindingContext context = KeyBindingContext.IN_GAME; + + @Override + public KeyBindingContext fabric_getContext() { + return context; + } + + @Override + public void fabric_setContext(KeyBindingContext context) { + this.context = context; + } + + @Inject(method = "onKeyPressed", at = @At("HEAD")) + private static void onKeyPressed(InputUtil.Key key, CallbackInfo ci) { + List list = KeyBindingRegistryImpl.KEY_TO_BINDINGS.get(key); + if (list == null) return; + + for (KeyBinding binding : list) { + if (KeyBindingContext.of(binding).isActive()) { + ((KeyBindingMixin) (Object) binding).timesPressed++; + break; + } + } + } + + @Inject(method = "setKeyPressed", at = @At("HEAD")) + private static void setKeyPressed(InputUtil.Key key, boolean pressed, CallbackInfo ci) { + List list = KeyBindingRegistryImpl.KEY_TO_BINDINGS.get(key); + if (list == null) return; + + for (KeyBinding binding : list) { + if (KeyBindingContext.of(binding).isActive()) { + binding.setPressed(pressed); + break; + } + } + } + + @Inject(method = "updateKeysByCode", at = @At("HEAD")) + private static void updateKeysByCode(CallbackInfo ci) { + KeyBindingRegistryImpl.KEY_TO_BINDINGS.clear(); + + for (KeyBinding binding : KEYS_BY_ID.values()) { + KeyBindingRegistryImpl.putToMap(KeyBindingHelper.getBoundKeyOf(binding), binding); + } + } + + @Inject(method = "(Ljava/lang/String;Lnet/minecraft/client/util/InputUtil$Type;ILjava/lang/String;)V", at = @At("TAIL")) + private void init(String translationKey, InputUtil.Type type, int code, String category, CallbackInfo ci) { + KeyBindingRegistryImpl.putToMap(boundKey, (KeyBinding) (Object) this); + } + + @Inject(method = "equals", at = @At("RETURN"), cancellable = true) + private void equals(KeyBinding other, CallbackInfoReturnable cir) { + if (!KeyBindingContext.conflicts(context, KeyBindingContext.of(other))) { + cir.setReturnValue(false); + } + } + + // Return empty set, skipping the loop + @Redirect(method = "updateKeysByCode", at = @At(value = "INVOKE", target = "Ljava/util/Map;values()Ljava/util/Collection;")) + private static Collection skipVanillaLoop(Map instance) { + return Collections.emptySet(); + } + + // Skip putting this to KEY_TO_BINDINGS + @Redirect(method = "(Ljava/lang/String;Lnet/minecraft/client/util/InputUtil$Type;ILjava/lang/String;)V", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 1)) + private Object skipVanillaMapping(Map instance, Object k, Object v) { + return null; + } +} diff --git a/fabric-key-binding-api-v1/src/client/resources/fabric-key-binding-api-v1.mixins.json b/fabric-key-binding-api-v1/src/client/resources/fabric-key-binding-api-v1.mixins.json index 1851730790..9764cb81ca 100644 --- a/fabric-key-binding-api-v1/src/client/resources/fabric-key-binding-api-v1.mixins.json +++ b/fabric-key-binding-api-v1/src/client/resources/fabric-key-binding-api-v1.mixins.json @@ -3,8 +3,9 @@ "package": "net.fabricmc.fabric.mixin.client.keybinding", "compatibilityLevel": "JAVA_16", "client": [ + "GameOptionsMixin", "KeyBindingAccessor", - "GameOptionsMixin" + "KeyBindingMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java index 896e91cf0d..62e4505aa9 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java @@ -25,6 +25,7 @@ import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingContext; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; public class KeyBindingsTest implements ClientModInitializer { @@ -35,6 +36,10 @@ public void onInitializeClient() { KeyBinding stickyBinding = KeyBindingHelper.registerKeyBinding(new StickyKeyBinding("key.fabric-key-binding-api-v1-testmod.test_keybinding_sticky", GLFW.GLFW_KEY_R, "key.category.first.test", () -> true)); KeyBinding duplicateBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.test_keybinding_duplicate", GLFW.GLFW_KEY_RIGHT_SHIFT, "key.category.first.test")); + KeyBinding inGameBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.in_game_keybinding", GLFW.GLFW_KEY_EQUAL, "key.category.context"), KeyBindingContext.IN_GAME); + KeyBinding screenBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.screen_keybinding", GLFW.GLFW_KEY_EQUAL, "key.category.context"), KeyBindingContext.IN_SCREEN); + KeyBinding allBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.all_keybinding", GLFW.GLFW_KEY_BACKSLASH, "key.category.context"), KeyBindingContext.ALL); + ClientTickEvents.END_CLIENT_TICK.register(client -> { while (binding1.wasPressed()) { client.player.sendMessage(Text.literal("Key 1 was pressed!"), false); @@ -51,6 +56,10 @@ public void onInitializeClient() { while (duplicateBinding.wasPressed()) { client.player.sendMessage(Text.literal("Duplicate Key was pressed!"), false); } + + while (inGameBinding.wasPressed()) { + client.player.sendMessage(Text.literal("In-game key was pressed")); + } }); } } diff --git a/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json b/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json index f678d522df..348055c770 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json +++ b/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json @@ -1,8 +1,12 @@ { "key.category.first.test": "First Test Category", "key.category.second.test": "Second Test Category", + "key.category.context": "Context Test Category", "key.fabric-key-binding-api-v1-testmod.test_keybinding_1": "Test 1", "key.fabric-key-binding-api-v1-testmod.test_keybinding_2": "Test 2", "key.fabric-key-binding-api-v1-testmod.test_keybinding_sticky": "Sticky Test", - "key.fabric-key-binding-api-v1-testmod.test_keybinding_duplicate": "Duplicate Test" + "key.fabric-key-binding-api-v1-testmod.test_keybinding_duplicate": "Duplicate Test", + "key.fabric-key-binding-api-v1-testmod.in_game_keybinding": "In Game", + "key.fabric-key-binding-api-v1-testmod.screen_keybinding": "In Screen", + "key.fabric-key-binding-api-v1-testmod.all_keybinding": "All" } From 1ec85888635c0c428d05d7c9a75feace07fdce57 Mon Sep 17 00:00:00 2001 From: deirn Date: Tue, 29 Aug 2023 14:09:20 +0700 Subject: [PATCH 02/15] only press one conflicting key bind --- .../keybinding/KeyBindingRegistryImpl.java | 2 - .../client/keybinding/KeyBindingMixin.java | 61 ++++++++++++++++--- .../keybinding/CustomKeyBindingContext.java | 28 +++++++++ .../client/keybinding/KeyBindingsTest.java | 30 +++++++++ .../lang/en_us.json | 6 +- 5 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java index ddd3d37b3a..007aae0b5c 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java @@ -16,9 +16,7 @@ package net.fabricmc.fabric.impl.client.keybinding; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java index 99a7ac96ad..989d97f85f 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java @@ -18,8 +18,12 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -52,16 +56,19 @@ public abstract class KeyBindingMixin implements KeyBindingExtensions { private InputUtil.Key boundKey; @Unique - private KeyBindingContext context = KeyBindingContext.IN_GAME; + private KeyBindingContext fabric_context; + + @Unique + private Set fabric_conflictingKeyBinds; @Override public KeyBindingContext fabric_getContext() { - return context; + return fabric_context; } @Override public void fabric_setContext(KeyBindingContext context) { - this.context = context; + this.fabric_context = context; } @Inject(method = "onKeyPressed", at = @At("HEAD")) @@ -69,10 +76,13 @@ private static void onKeyPressed(InputUtil.Key key, CallbackInfo ci) { List list = KeyBindingRegistryImpl.KEY_TO_BINDINGS.get(key); if (list == null) return; + Set uniqueKeyBinds = Collections.newSetFromMap(new IdentityHashMap<>()); + for (KeyBinding binding : list) { - if (KeyBindingContext.of(binding).isActive()) { + KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; + + if (mixed.fabric_context.isActive() && uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds)) { ((KeyBindingMixin) (Object) binding).timesPressed++; - break; } } } @@ -82,10 +92,13 @@ private static void setKeyPressed(InputUtil.Key key, boolean pressed, CallbackIn List list = KeyBindingRegistryImpl.KEY_TO_BINDINGS.get(key); if (list == null) return; + Set uniqueKeyBinds = Collections.newSetFromMap(new IdentityHashMap<>()); + for (KeyBinding binding : list) { - if (KeyBindingContext.of(binding).isActive()) { + KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; + + if (mixed.fabric_context.isActive() && uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds)) { binding.setPressed(pressed); - break; } } } @@ -97,27 +110,57 @@ private static void updateKeysByCode(CallbackInfo ci) { for (KeyBinding binding : KEYS_BY_ID.values()) { KeyBindingRegistryImpl.putToMap(KeyBindingHelper.getBoundKeyOf(binding), binding); } + + for (List bindings : KeyBindingRegistryImpl.KEY_TO_BINDINGS.values()) { + for (KeyBinding binding : bindings) { + ((KeyBindingMixin) (Object) binding).fabric_conflictingKeyBinds.clear(); + } + + for (KeyBinding binding : bindings) { + KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; + + for (KeyBinding otherBinding : bindings) { + if (binding == otherBinding) continue; + KeyBindingMixin otherMixed = (KeyBindingMixin) (Object) otherBinding; + + if (KeyBindingContext.conflicts(mixed.fabric_context, otherMixed.fabric_context)) { + otherMixed.fabric_conflictingKeyBinds.add(binding); + otherMixed.fabric_conflictingKeyBinds.addAll(mixed.fabric_conflictingKeyBinds); + mixed.fabric_conflictingKeyBinds.add(otherBinding); + mixed.fabric_conflictingKeyBinds.addAll(otherMixed.fabric_conflictingKeyBinds); + } + } + } + } } @Inject(method = "(Ljava/lang/String;Lnet/minecraft/client/util/InputUtil$Type;ILjava/lang/String;)V", at = @At("TAIL")) private void init(String translationKey, InputUtil.Type type, int code, String category, CallbackInfo ci) { + fabric_context = KeyBindingContext.IN_GAME; + fabric_conflictingKeyBinds = Collections.newSetFromMap(new IdentityHashMap<>()); KeyBindingRegistryImpl.putToMap(boundKey, (KeyBinding) (Object) this); } @Inject(method = "equals", at = @At("RETURN"), cancellable = true) private void equals(KeyBinding other, CallbackInfoReturnable cir) { - if (!KeyBindingContext.conflicts(context, KeyBindingContext.of(other))) { + if (!KeyBindingContext.conflicts(fabric_context, KeyBindingContext.of(other))) { cir.setReturnValue(false); } } + // Make KEYS_BY_ID deterministic + @Redirect(method = "", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;", ordinal = 0)) + private static HashMap makeMapOrdered() { + return new LinkedHashMap<>(); + } + // Return empty set, skipping the loop @Redirect(method = "updateKeysByCode", at = @At(value = "INVOKE", target = "Ljava/util/Map;values()Ljava/util/Collection;")) private static Collection skipVanillaLoop(Map instance) { return Collections.emptySet(); } - // Skip putting this to KEY_TO_BINDINGS + // Skip putting this to KEY_TO_BINDINGS, this also skips vanilla onKeyPressed and setKeyPressed loops @Redirect(method = "(Ljava/lang/String;Lnet/minecraft/client/util/InputUtil$Type;ILjava/lang/String;)V", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 1)) private Object skipVanillaMapping(Map instance, Object k, Object v) { return null; diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java new file mode 100644 index 0000000000..2d9765d79d --- /dev/null +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.client.keybinding; + +import net.minecraft.client.MinecraftClient; + +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingContext; + +public class CustomKeyBindingContext implements KeyBindingContext { + @Override + public boolean isActive() { + return MinecraftClient.getInstance().currentScreen == null; + } +} diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java index 62e4505aa9..4d56628949 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java @@ -40,6 +40,16 @@ public void onInitializeClient() { KeyBinding screenBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.screen_keybinding", GLFW.GLFW_KEY_EQUAL, "key.category.context"), KeyBindingContext.IN_SCREEN); KeyBinding allBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.all_keybinding", GLFW.GLFW_KEY_BACKSLASH, "key.category.context"), KeyBindingContext.ALL); + // context1 won't conflict with context2 + // therefore, one key from context1 and context2 will both be registered as pressed + CustomKeyBindingContext context1 = new CustomKeyBindingContext(); + KeyBinding customCtxBinding1 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.custom_context_1", GLFW.GLFW_KEY_SEMICOLON, "key.category.context"), context1); + KeyBinding customCtxBinding2 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.custom_context_2", GLFW.GLFW_KEY_SEMICOLON, "key.category.context"), context1); + + CustomKeyBindingContext context2 = new CustomKeyBindingContext(); + KeyBinding customCtxBinding3 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.custom_context_3", GLFW.GLFW_KEY_SEMICOLON, "key.category.context"), context2); + KeyBinding customCtxBinding4 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.custom_context_4", GLFW.GLFW_KEY_SEMICOLON, "key.category.context"), context2); + ClientTickEvents.END_CLIENT_TICK.register(client -> { while (binding1.wasPressed()) { client.player.sendMessage(Text.literal("Key 1 was pressed!"), false); @@ -60,6 +70,26 @@ public void onInitializeClient() { while (inGameBinding.wasPressed()) { client.player.sendMessage(Text.literal("In-game key was pressed")); } + + while (allBinding.wasPressed()) { + client.player.sendMessage(Text.literal("ALL context key was pressed!")); + } + + while (customCtxBinding1.wasPressed()) { + client.player.sendMessage(Text.literal("Custom Context Key 1 was pressed!")); + } + + while (customCtxBinding2.wasPressed()) { + client.player.sendMessage(Text.literal("Custom Context Key 2 was pressed!")); + } + + while (customCtxBinding3.wasPressed()) { + client.player.sendMessage(Text.literal("Custom Context Key 3 was pressed!")); + } + + while (customCtxBinding4.wasPressed()) { + client.player.sendMessage(Text.literal("Custom Context Key 4 was pressed!")); + } }); } } diff --git a/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json b/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json index 348055c770..e97d779052 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json +++ b/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json @@ -8,5 +8,9 @@ "key.fabric-key-binding-api-v1-testmod.test_keybinding_duplicate": "Duplicate Test", "key.fabric-key-binding-api-v1-testmod.in_game_keybinding": "In Game", "key.fabric-key-binding-api-v1-testmod.screen_keybinding": "In Screen", - "key.fabric-key-binding-api-v1-testmod.all_keybinding": "All" + "key.fabric-key-binding-api-v1-testmod.all_keybinding": "All", + "key.fabric-key-binding-api-v1-testmod.custom_context_1": "Custom Context 1", + "key.fabric-key-binding-api-v1-testmod.custom_context_2": "Custom Context 2", + "key.fabric-key-binding-api-v1-testmod.custom_context_3": "Custom Context 3", + "key.fabric-key-binding-api-v1-testmod.custom_context_4": "Custom Context 4" } From 98c85a4dfcf0445522267f667b07fc2b3cff4d99 Mon Sep 17 00:00:00 2001 From: deirn Date: Tue, 29 Aug 2023 15:30:09 +0700 Subject: [PATCH 03/15] replace the conflict text --- .../keybinding/KeyBindingEntryMixin.java | 20 +++++++++++++++++++ .../fabric-key-binding-api-v1/lang/en_us.json | 3 +++ .../fabric-key-binding-api-v1.mixins.json | 1 + 3 files changed, 24 insertions(+) create mode 100644 fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java create mode 100644 fabric-key-binding-api-v1/src/client/resources/assets/fabric-key-binding-api-v1/lang/en_us.json diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java new file mode 100644 index 0000000000..5c4c15d771 --- /dev/null +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java @@ -0,0 +1,20 @@ +package net.fabricmc.fabric.mixin.client.keybinding; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import net.minecraft.client.gui.screen.option.ControlsListWidget; + +@Mixin(ControlsListWidget.KeyBindingEntry.class) +public class KeyBindingEntryMixin { + @ModifyConstant(method = "update", constant = @Constant(stringValue = ", ")) + private String makeConflictTextMultiline(String constant) { + return "\n"; + } + + @ModifyConstant(method = "update", constant = @Constant(stringValue = "controls.keybinds.duplicateKeybinds")) + private String replaceConflictText(String constant) { + return "fabric.keybinding.conflicts"; + } +} diff --git a/fabric-key-binding-api-v1/src/client/resources/assets/fabric-key-binding-api-v1/lang/en_us.json b/fabric-key-binding-api-v1/src/client/resources/assets/fabric-key-binding-api-v1/lang/en_us.json new file mode 100644 index 0000000000..d1465fee34 --- /dev/null +++ b/fabric-key-binding-api-v1/src/client/resources/assets/fabric-key-binding-api-v1/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "fabric.keybinding.conflicts": "This key is conflicting with:\n%s" +} diff --git a/fabric-key-binding-api-v1/src/client/resources/fabric-key-binding-api-v1.mixins.json b/fabric-key-binding-api-v1/src/client/resources/fabric-key-binding-api-v1.mixins.json index 9764cb81ca..8e438fde3a 100644 --- a/fabric-key-binding-api-v1/src/client/resources/fabric-key-binding-api-v1.mixins.json +++ b/fabric-key-binding-api-v1/src/client/resources/fabric-key-binding-api-v1.mixins.json @@ -5,6 +5,7 @@ "client": [ "GameOptionsMixin", "KeyBindingAccessor", + "KeyBindingEntryMixin", "KeyBindingMixin" ], "injectors": { From 76f420724da9753f4a01359621ba5800fdb660ff Mon Sep 17 00:00:00 2001 From: deirn Date: Tue, 29 Aug 2023 15:31:06 +0700 Subject: [PATCH 04/15] fix checkstyle --- .../fabric/api/client/keybinding/v1/KeyBindingContext.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java index bb56fa8239..e928714f6d 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java @@ -25,8 +25,8 @@ /** * {@link KeyBindingContext} decides how {@link KeyBinding} with same bounded key behaves in regard to each other. - *

- * Bindings with different context will not conflict with each other even if they have the same bounded key. + * + *

Bindings with different context will not conflict with each other even if they have the same bounded key. */ public interface KeyBindingContext { /** From 02d30638c9a97caad118776342bbf481c3527cfc Mon Sep 17 00:00:00 2001 From: deirn Date: Tue, 29 Aug 2023 15:32:38 +0700 Subject: [PATCH 05/15] add license header --- .../client/keybinding/KeyBindingEntryMixin.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java index 5c4c15d771..4a119a08b8 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.fabricmc.fabric.mixin.client.keybinding; import org.spongepowered.asm.mixin.Mixin; From 4df4afe5e6041d97bea4aca02c9721bbe4be9daa Mon Sep 17 00:00:00 2001 From: deirn Date: Wed, 30 Aug 2023 16:44:26 +0700 Subject: [PATCH 06/15] update javadoc provide client instance on `isActive` require explicit impl for `conflictsWith` --- .../keybinding/v1/KeyBindingContext.java | 43 ++++++++------ .../keybinding/KeyBindingContextImpl.java | 59 +++++++++++++++++++ .../client/keybinding/KeyBindingMixin.java | 5 +- .../keybinding/CustomKeyBindingContext.java | 9 ++- 4 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingContextImpl.java diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java index e928714f6d..00848534c7 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java @@ -21,54 +21,61 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; +import net.fabricmc.fabric.impl.client.keybinding.KeyBindingContextImpl; import net.fabricmc.fabric.impl.client.keybinding.KeyBindingExtensions; /** * {@link KeyBindingContext} decides how {@link KeyBinding} with same bounded key behaves in regard to each other. * *

Bindings with different context will not conflict with each other even if they have the same bounded key. + * + *

Along with the provided generic contexts, mods can also create its own context by implementing this interface. */ public interface KeyBindingContext { /** * {@link KeyBinding} that used in-game. All vanilla key binds have this context. */ - KeyBindingContext IN_GAME = () -> MinecraftClient.getInstance().currentScreen == null; + KeyBindingContext IN_GAME = KeyBindingContextImpl.IN_GAME; /** * {@link KeyBinding} that used when a screen is open. */ - KeyBindingContext IN_SCREEN = () -> MinecraftClient.getInstance().currentScreen != null; + KeyBindingContext IN_SCREEN = KeyBindingContextImpl.IN_SCREEN; /** * {@link KeyBinding} that might be used in any context. This context conflicts with any other context. */ - KeyBindingContext ALL = new KeyBindingContext() { - @Override - public boolean isActive() { - return true; - } - - @Override - public boolean conflictsWith(KeyBindingContext other) { - return true; - } - }; + KeyBindingContext ALL = KeyBindingContextImpl.ALL; + /** + * Returns the context of the key binding. + */ static KeyBindingContext of(KeyBinding binding) { return ((KeyBindingExtensions) binding).fabric_getContext(); } + /** + * Returns whether one context conflicts with the other. + */ static boolean conflicts(KeyBindingContext left, KeyBindingContext right) { return left.conflictsWith(right) || right.conflictsWith(left); } - boolean isActive(); + /** + * Returns whether the key bind can be activated in the current state of the game. + * If not, {@link KeyBinding#isPressed()} and {@link KeyBinding#wasPressed()} will always return {@code false}. + */ + boolean isActive(MinecraftClient client); /** - * Use {@link #conflicts(KeyBindingContext, KeyBindingContext)} for checking if two context conflicts. + * Returns whether this context conflict with the other. + * + *

Do not call! Use {@link #conflicts(KeyBindingContext, KeyBindingContext)} instead. + * + *

Along with the same instance, most custom context implementation should probably conflict with either + * {@link #IN_GAME} or {@link #IN_SCREEN}, unless it needs to be called alongside the generic context. + *

return this == other || other == IN_GAME;
*/ @ApiStatus.OverrideOnly - default boolean conflictsWith(KeyBindingContext other) { - return this == other; - } + boolean conflictsWith(KeyBindingContext other); } diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingContextImpl.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingContextImpl.java new file mode 100644 index 0000000000..727f8ccf18 --- /dev/null +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingContextImpl.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.keybinding; + +import net.minecraft.client.MinecraftClient; + +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingContext; + +public class KeyBindingContextImpl { + public static final KeyBindingContext IN_GAME = new KeyBindingContext() { + @Override + public boolean isActive(MinecraftClient client) { + return client.currentScreen == null; + } + + @Override + public boolean conflictsWith(KeyBindingContext other) { + return this == other; + } + }; + + public static final KeyBindingContext IN_SCREEN = new KeyBindingContext() { + @Override + public boolean isActive(MinecraftClient client) { + return client.currentScreen != null; + } + + @Override + public boolean conflictsWith(KeyBindingContext other) { + return this == other; + } + }; + + public static final KeyBindingContext ALL = new KeyBindingContext() { + @Override + public boolean isActive(MinecraftClient client) { + return true; + } + + @Override + public boolean conflictsWith(KeyBindingContext other) { + return true; + } + }; +} diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java index 989d97f85f..ff42575e35 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java @@ -35,6 +35,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; @@ -81,7 +82,7 @@ private static void onKeyPressed(InputUtil.Key key, CallbackInfo ci) { for (KeyBinding binding : list) { KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; - if (mixed.fabric_context.isActive() && uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds)) { + if (mixed.fabric_context.isActive(MinecraftClient.getInstance()) && uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds)) { ((KeyBindingMixin) (Object) binding).timesPressed++; } } @@ -97,7 +98,7 @@ private static void setKeyPressed(InputUtil.Key key, boolean pressed, CallbackIn for (KeyBinding binding : list) { KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; - if (mixed.fabric_context.isActive() && uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds)) { + if (mixed.fabric_context.isActive(MinecraftClient.getInstance()) && uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds)) { binding.setPressed(pressed); } } diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java index 2d9765d79d..9766f1adf1 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java @@ -22,7 +22,12 @@ public class CustomKeyBindingContext implements KeyBindingContext { @Override - public boolean isActive() { - return MinecraftClient.getInstance().currentScreen == null; + public boolean isActive(MinecraftClient client) { + return client.currentScreen == null; + } + + @Override + public boolean conflictsWith(KeyBindingContext other) { + return this == other; } } From c20262cc6e8e09eff72b01f1ea764ed457501730 Mon Sep 17 00:00:00 2001 From: deirn Date: Wed, 30 Aug 2023 18:00:45 +0700 Subject: [PATCH 07/15] skip solving conflicting key if it's only one --- .../mixin/client/keybinding/KeyBindingMixin.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java index ff42575e35..706e78d245 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java @@ -77,12 +77,12 @@ private static void onKeyPressed(InputUtil.Key key, CallbackInfo ci) { List list = KeyBindingRegistryImpl.KEY_TO_BINDINGS.get(key); if (list == null) return; - Set uniqueKeyBinds = Collections.newSetFromMap(new IdentityHashMap<>()); + Set uniqueKeyBinds = list.size() <= 1 ? null : Collections.newSetFromMap(new IdentityHashMap<>()); for (KeyBinding binding : list) { KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; - if (mixed.fabric_context.isActive(MinecraftClient.getInstance()) && uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds)) { + if (mixed.fabric_context.isActive(MinecraftClient.getInstance()) && (uniqueKeyBinds == null || uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds))) { ((KeyBindingMixin) (Object) binding).timesPressed++; } } @@ -93,12 +93,12 @@ private static void setKeyPressed(InputUtil.Key key, boolean pressed, CallbackIn List list = KeyBindingRegistryImpl.KEY_TO_BINDINGS.get(key); if (list == null) return; - Set uniqueKeyBinds = Collections.newSetFromMap(new IdentityHashMap<>()); + Set uniqueKeyBinds = list.size() <= 1 ? null : Collections.newSetFromMap(new IdentityHashMap<>()); for (KeyBinding binding : list) { KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; - if (mixed.fabric_context.isActive(MinecraftClient.getInstance()) && uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds)) { + if (mixed.fabric_context.isActive(MinecraftClient.getInstance()) && (uniqueKeyBinds == null || uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds))) { binding.setPressed(pressed); } } @@ -114,7 +114,9 @@ private static void updateKeysByCode(CallbackInfo ci) { for (List bindings : KeyBindingRegistryImpl.KEY_TO_BINDINGS.values()) { for (KeyBinding binding : bindings) { - ((KeyBindingMixin) (Object) binding).fabric_conflictingKeyBinds.clear(); + KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; + mixed.fabric_conflictingKeyBinds.clear(); + mixed.fabric_conflictingKeyBinds.add(binding); } for (KeyBinding binding : bindings) { From e4daa5616a8c8358083ec6264e7951dcdb005cfb Mon Sep 17 00:00:00 2001 From: deirn Date: Wed, 30 Aug 2023 20:31:35 +0700 Subject: [PATCH 08/15] add item context test --- .../keybinding/ItemKeyBindingContext.java | 43 ++++++++++++++ .../client/keybinding/KeyBindingsTest.java | 58 ++++++++++--------- .../lang/en_us.json | 4 +- 3 files changed, 77 insertions(+), 28 deletions(-) create mode 100644 fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/ItemKeyBindingContext.java diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/ItemKeyBindingContext.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/ItemKeyBindingContext.java new file mode 100644 index 0000000000..61324a4cc2 --- /dev/null +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/ItemKeyBindingContext.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.client.keybinding; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.util.Hand; + +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingContext; + +public class ItemKeyBindingContext implements KeyBindingContext { + private final Item item; + + public ItemKeyBindingContext(Item item) { + this.item = item; + } + + @Override + public boolean isActive(MinecraftClient client) { + ClientPlayerEntity player = client.player; + return IN_GAME.isActive(client) && player != null && player.getStackInHand(Hand.MAIN_HAND).isOf(item); + } + + @Override + public boolean conflictsWith(KeyBindingContext other) { + return this == other || IN_GAME == other; + } +} diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java index 4d56628949..589ce48854 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java @@ -18,9 +18,11 @@ import org.lwjgl.glfw.GLFW; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.option.StickyKeyBinding; import net.minecraft.client.util.InputUtil; +import net.minecraft.item.Items; import net.minecraft.text.Text; import net.fabricmc.api.ClientModInitializer; @@ -36,19 +38,22 @@ public void onInitializeClient() { KeyBinding stickyBinding = KeyBindingHelper.registerKeyBinding(new StickyKeyBinding("key.fabric-key-binding-api-v1-testmod.test_keybinding_sticky", GLFW.GLFW_KEY_R, "key.category.first.test", () -> true)); KeyBinding duplicateBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.test_keybinding_duplicate", GLFW.GLFW_KEY_RIGHT_SHIFT, "key.category.first.test")); - KeyBinding inGameBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.in_game_keybinding", GLFW.GLFW_KEY_EQUAL, "key.category.context"), KeyBindingContext.IN_GAME); - KeyBinding screenBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.screen_keybinding", GLFW.GLFW_KEY_EQUAL, "key.category.context"), KeyBindingContext.IN_SCREEN); - KeyBinding allBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.all_keybinding", GLFW.GLFW_KEY_BACKSLASH, "key.category.context"), KeyBindingContext.ALL); + KeyBinding inGameBinding = register("in_game_keybinding", GLFW.GLFW_KEY_EQUAL, "context", KeyBindingContext.IN_GAME); + KeyBinding screenBinding = register("screen_keybinding", GLFW.GLFW_KEY_EQUAL, "context", KeyBindingContext.IN_SCREEN); + KeyBinding allBinding = register("all_keybinding", GLFW.GLFW_KEY_BACKSLASH, "context", KeyBindingContext.ALL); // context1 won't conflict with context2 // therefore, one key from context1 and context2 will both be registered as pressed CustomKeyBindingContext context1 = new CustomKeyBindingContext(); - KeyBinding customCtxBinding1 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.custom_context_1", GLFW.GLFW_KEY_SEMICOLON, "key.category.context"), context1); - KeyBinding customCtxBinding2 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.custom_context_2", GLFW.GLFW_KEY_SEMICOLON, "key.category.context"), context1); + KeyBinding customCtxBinding1 = register("custom_context_1", GLFW.GLFW_KEY_SEMICOLON, "context", context1); + KeyBinding customCtxBinding2 = register("custom_context_2", GLFW.GLFW_KEY_SEMICOLON, "context", context1); CustomKeyBindingContext context2 = new CustomKeyBindingContext(); - KeyBinding customCtxBinding3 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.custom_context_3", GLFW.GLFW_KEY_SEMICOLON, "key.category.context"), context2); - KeyBinding customCtxBinding4 = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.custom_context_4", GLFW.GLFW_KEY_SEMICOLON, "key.category.context"), context2); + KeyBinding customCtxBinding3 = register("custom_context_3", GLFW.GLFW_KEY_SEMICOLON, "context", context2); + KeyBinding customCtxBinding4 = register("custom_context_4", GLFW.GLFW_KEY_SEMICOLON, "context", context2); + + KeyBinding diamondSwordBinding = register("diamond_sword", GLFW.GLFW_KEY_I, "context", new ItemKeyBindingContext(Items.DIAMOND_SWORD)); + KeyBinding netheriteSwordBinding = register("netherite_sword", GLFW.GLFW_KEY_I, "context", new ItemKeyBindingContext(Items.NETHERITE_SWORD)); ClientTickEvents.END_CLIENT_TICK.register(client -> { while (binding1.wasPressed()) { @@ -67,29 +72,28 @@ public void onInitializeClient() { client.player.sendMessage(Text.literal("Duplicate Key was pressed!"), false); } - while (inGameBinding.wasPressed()) { - client.player.sendMessage(Text.literal("In-game key was pressed")); - } + sendMessageWhenPressed(client, inGameBinding, "In-game key was pressed"); + sendMessageWhenPressed(client, allBinding, "ALL context key was pressed!"); - while (allBinding.wasPressed()) { - client.player.sendMessage(Text.literal("ALL context key was pressed!")); - } + // 1 and 3 should be called at the same time + sendMessageWhenPressed(client, customCtxBinding1, "Custom Context Key 1 was pressed!"); + sendMessageWhenPressed(client, customCtxBinding2, "Custom Context Key 2 was pressed!"); + sendMessageWhenPressed(client, customCtxBinding3, "Custom Context Key 3 was pressed!"); + sendMessageWhenPressed(client, customCtxBinding4, "Custom Context Key 4 was pressed!"); - while (customCtxBinding1.wasPressed()) { - client.player.sendMessage(Text.literal("Custom Context Key 1 was pressed!")); - } - - while (customCtxBinding2.wasPressed()) { - client.player.sendMessage(Text.literal("Custom Context Key 2 was pressed!")); - } + sendMessageWhenPressed(client, diamondSwordBinding, "Diamond Sword Key was pressed!"); + sendMessageWhenPressed(client, netheriteSwordBinding, "Netherite Sword Key was pressed!"); + }); + } - while (customCtxBinding3.wasPressed()) { - client.player.sendMessage(Text.literal("Custom Context Key 3 was pressed!")); - } + private static KeyBinding register(String key, int code, String category, KeyBindingContext context) { + return KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod." + key, code, "key.category." + category), context); + } - while (customCtxBinding4.wasPressed()) { - client.player.sendMessage(Text.literal("Custom Context Key 4 was pressed!")); - } - }); + private static void sendMessageWhenPressed(MinecraftClient client, KeyBinding binding, String message) { + while (binding.wasPressed()) { + client.player.sendMessage(Text.literal(message)); + } } + } diff --git a/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json b/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json index e97d779052..9c7fbe0173 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json +++ b/fabric-key-binding-api-v1/src/testmodClient/resources/assets/fabric-keybindings-v1-testmod/lang/en_us.json @@ -12,5 +12,7 @@ "key.fabric-key-binding-api-v1-testmod.custom_context_1": "Custom Context 1", "key.fabric-key-binding-api-v1-testmod.custom_context_2": "Custom Context 2", "key.fabric-key-binding-api-v1-testmod.custom_context_3": "Custom Context 3", - "key.fabric-key-binding-api-v1-testmod.custom_context_4": "Custom Context 4" + "key.fabric-key-binding-api-v1-testmod.custom_context_4": "Custom Context 4", + "key.fabric-key-binding-api-v1-testmod.diamond_sword": "Diamond Sword", + "key.fabric-key-binding-api-v1-testmod.netherite_sword": "Netherite Sword" } From 901c458eaffcdce49e953db22e5cf70a542d1b8b Mon Sep 17 00:00:00 2001 From: deirn Date: Wed, 30 Aug 2023 20:32:20 +0700 Subject: [PATCH 09/15] conflictsWith -> conflicts --- .../fabric/api/client/keybinding/v1/KeyBindingContext.java | 4 ++-- .../impl/client/keybinding/KeyBindingContextImpl.java | 6 +++--- .../test/client/keybinding/CustomKeyBindingContext.java | 2 +- .../test/client/keybinding/ItemKeyBindingContext.java | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java index 00848534c7..fbebd0dfc7 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingContext.java @@ -58,7 +58,7 @@ static KeyBindingContext of(KeyBinding binding) { * Returns whether one context conflicts with the other. */ static boolean conflicts(KeyBindingContext left, KeyBindingContext right) { - return left.conflictsWith(right) || right.conflictsWith(left); + return left.conflicts(right) || right.conflicts(left); } /** @@ -77,5 +77,5 @@ static boolean conflicts(KeyBindingContext left, KeyBindingContext right) { *
return this == other || other == IN_GAME;
*/ @ApiStatus.OverrideOnly - boolean conflictsWith(KeyBindingContext other); + boolean conflicts(KeyBindingContext other); } diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingContextImpl.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingContextImpl.java index 727f8ccf18..1134ba6dfb 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingContextImpl.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingContextImpl.java @@ -28,7 +28,7 @@ public boolean isActive(MinecraftClient client) { } @Override - public boolean conflictsWith(KeyBindingContext other) { + public boolean conflicts(KeyBindingContext other) { return this == other; } }; @@ -40,7 +40,7 @@ public boolean isActive(MinecraftClient client) { } @Override - public boolean conflictsWith(KeyBindingContext other) { + public boolean conflicts(KeyBindingContext other) { return this == other; } }; @@ -52,7 +52,7 @@ public boolean isActive(MinecraftClient client) { } @Override - public boolean conflictsWith(KeyBindingContext other) { + public boolean conflicts(KeyBindingContext other) { return true; } }; diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java index 9766f1adf1..12b6888391 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/CustomKeyBindingContext.java @@ -27,7 +27,7 @@ public boolean isActive(MinecraftClient client) { } @Override - public boolean conflictsWith(KeyBindingContext other) { + public boolean conflicts(KeyBindingContext other) { return this == other; } } diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/ItemKeyBindingContext.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/ItemKeyBindingContext.java index 61324a4cc2..5b60688477 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/ItemKeyBindingContext.java +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/ItemKeyBindingContext.java @@ -37,7 +37,7 @@ public boolean isActive(MinecraftClient client) { } @Override - public boolean conflictsWith(KeyBindingContext other) { + public boolean conflicts(KeyBindingContext other) { return this == other || IN_GAME == other; } } From 38b79b5f976556099944062c42f483d9de2cfc69 Mon Sep 17 00:00:00 2001 From: deirn Date: Wed, 30 Aug 2023 21:42:22 +0700 Subject: [PATCH 10/15] make `KeyBinding#isPressed` and `wasPressed` work on screen --- .../client/keybinding/KeyBindingsTest.java | 1 - fabric-screen-api-v1/build.gradle | 2 +- .../fabric/mixin/screen/ScreenMixin.java | 18 +++++++++++++++++- .../src/client/resources/fabric.mod.json | 3 ++- .../fabric/test/screen/ScreenTests.java | 13 +++++++++++++ .../lang/en_us.json | 3 +++ 6 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 fabric-screen-api-v1/src/testmodClient/resources/assets/fabric-screen-api-v1-testmod/lang/en_us.json diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java index 589ce48854..1da1ba7bd6 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java @@ -39,7 +39,6 @@ public void onInitializeClient() { KeyBinding duplicateBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding("key.fabric-key-binding-api-v1-testmod.test_keybinding_duplicate", GLFW.GLFW_KEY_RIGHT_SHIFT, "key.category.first.test")); KeyBinding inGameBinding = register("in_game_keybinding", GLFW.GLFW_KEY_EQUAL, "context", KeyBindingContext.IN_GAME); - KeyBinding screenBinding = register("screen_keybinding", GLFW.GLFW_KEY_EQUAL, "context", KeyBindingContext.IN_SCREEN); KeyBinding allBinding = register("all_keybinding", GLFW.GLFW_KEY_BACKSLASH, "context", KeyBindingContext.ALL); // context1 won't conflict with context2 diff --git a/fabric-screen-api-v1/build.gradle b/fabric-screen-api-v1/build.gradle index b1e70a3f58..c5d8aeb4e6 100644 --- a/fabric-screen-api-v1/build.gradle +++ b/fabric-screen-api-v1/build.gradle @@ -1,4 +1,4 @@ archivesBaseName = "fabric-screen-api-v1" version = getSubprojectVersion(project) -moduleDependencies(project, ['fabric-api-base']) +moduleDependencies(project, ['fabric-api-base', 'fabric-key-binding-api-v1']) diff --git a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/ScreenMixin.java b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/ScreenMixin.java index 92483af80d..fb685192ba 100644 --- a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/ScreenMixin.java +++ b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/ScreenMixin.java @@ -26,12 +26,14 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import net.minecraft.client.gui.Selectable; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.Drawable; import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents; @@ -149,9 +151,23 @@ private void beforeInit(MinecraftClient client, int width, int height) { this.beforeMouseScrollEvent = ScreenEventFactory.createBeforeMouseScrollEvent(); this.afterMouseScrollEvent = ScreenEventFactory.createAfterMouseScrollEvent(); + // Make KeyBinding.isPressed and wasPressed work on screen + // Registered here to make sure it is called first + beforeKeyPressEvent.register((screen, keycode, scancode, modifiers) -> onKeyBind(InputUtil.fromKeyCode(keycode, scancode), true)); + beforeKeyReleaseEvent.register((screen, keycode, scancode, modifiers) -> onKeyBind(InputUtil.fromKeyCode(keycode, scancode), false)); + beforeMouseClickEvent.register((screen, mouseX, mouseY, button) -> onKeyBind(InputUtil.Type.MOUSE.createFromCode(button), true)); + beforeMouseReleaseEvent.register((screen, mouseX, mouseY, button) -> onKeyBind(InputUtil.Type.MOUSE.createFromCode(button), false)); + removeEvent.register(screen -> KeyBinding.unpressAll()); + ScreenEvents.BEFORE_INIT.invoker().beforeInit(client, (Screen) (Object) this, width, height); } + @Unique + private static void onKeyBind(InputUtil.Key key, boolean pressed) { + KeyBinding.setKeyPressed(key, pressed); + if (pressed) KeyBinding.onKeyPressed(key); + } + @Unique private void afterInit(MinecraftClient client, int width, int height) { ScreenEvents.AFTER_INIT.invoker().afterInit(client, (Screen) (Object) this, width, height); diff --git a/fabric-screen-api-v1/src/client/resources/fabric.mod.json b/fabric-screen-api-v1/src/client/resources/fabric.mod.json index c22ea4cfd2..99aca01cf8 100644 --- a/fabric-screen-api-v1/src/client/resources/fabric.mod.json +++ b/fabric-screen-api-v1/src/client/resources/fabric.mod.json @@ -17,7 +17,8 @@ ], "depends": { "fabricloader": ">=0.8.2", - "fabric-api-base": "*" + "fabric-api-base": "*", + "fabric-key-binding-api-v1": "*" }, "description": "Adds screen related hooks.", "mixins": [ diff --git a/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java b/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java index f3a867a156..9bc2e1bbb0 100644 --- a/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java +++ b/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java @@ -18,6 +18,7 @@ import java.util.List; +import org.lwjgl.glfw.GLFW; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,9 +26,12 @@ import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.TitleScreen; import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.option.KeyBinding; import net.minecraft.util.Identifier; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingContext; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents; import net.fabricmc.fabric.api.client.screen.v1.Screens; @@ -36,6 +40,8 @@ public final class ScreenTests implements ClientModInitializer { public static final Identifier GUI_ICONS_TEXTURE = new Identifier("textures/gui/icons.png"); private static final Logger LOGGER = LoggerFactory.getLogger("FabricScreenApiTests"); + private static final KeyBinding SCREEN_BINDING = new KeyBinding("key.fabric-screen-api-v1-testmod.screen_keybinding", GLFW.GLFW_KEY_EQUAL, "key.category.context"); + @Override public void onInitializeClient() { LOGGER.info("Started Screen Testmod"); @@ -44,6 +50,7 @@ public void onInitializeClient() { }); ScreenEvents.AFTER_INIT.register(this::afterInitScreen); + KeyBindingHelper.registerKeyBinding(SCREEN_BINDING, KeyBindingContext.IN_SCREEN); } private void afterInitScreen(MinecraftClient client, Screen screen, int windowWidth, int windowHeight) { @@ -88,5 +95,11 @@ private void afterInitScreen(MinecraftClient client, Screen screen, int windowWi LOGGER.warn("Pressed, Code: {}, Scancode: {}, Modifiers: {}", key, scancode, modifiers); }); } + + ScreenKeyboardEvents.beforeKeyPress(screen).register((screen1, key, scancode, modifiers) -> { + if (SCREEN_BINDING.isPressed()) { + LOGGER.info("Screen key binding was pressed on screen {}!", screen.getClass().getSimpleName()); + } + }); } } diff --git a/fabric-screen-api-v1/src/testmodClient/resources/assets/fabric-screen-api-v1-testmod/lang/en_us.json b/fabric-screen-api-v1/src/testmodClient/resources/assets/fabric-screen-api-v1-testmod/lang/en_us.json new file mode 100644 index 0000000000..94dfbe6916 --- /dev/null +++ b/fabric-screen-api-v1/src/testmodClient/resources/assets/fabric-screen-api-v1-testmod/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "key.fabric-screen-api-v1-testmod.screen_keybinding": "In Screen" +} From 625cf27de7321230faf9a29c09e47a05f9d5d036 Mon Sep 17 00:00:00 2001 From: deirn Date: Fri, 8 Sep 2023 23:32:17 +0700 Subject: [PATCH 11/15] only change conflict text if there's multiple conflicting context --- .../keybinding/v1/KeyBindingHelper.java | 1 + .../keybinding/KeyBindingEntryMixin.java | 23 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.java index ee5b9fc607..f02b880880 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/api/client/keybinding/v1/KeyBindingHelper.java @@ -60,6 +60,7 @@ public static KeyBinding registerKeyBinding(KeyBinding keyBinding) { */ public static KeyBinding registerKeyBinding(KeyBinding keyBinding, KeyBindingContext context) { Objects.requireNonNull(keyBinding, "key binding cannot be null"); + Objects.requireNonNull(context, "context cannot be null"); return KeyBindingRegistryImpl.registerKeyBinding(keyBinding, context); } diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java index 4a119a08b8..a69e3b4d47 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java @@ -16,14 +16,25 @@ package net.fabricmc.fabric.mixin.client.keybinding; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.ModifyConstant; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.option.ControlsListWidget; +import net.minecraft.client.option.KeyBinding; + +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingContext; @Mixin(ControlsListWidget.KeyBindingEntry.class) -public class KeyBindingEntryMixin { +public abstract class KeyBindingEntryMixin { + + @Shadow + @Final + private KeyBinding binding; + @ModifyConstant(method = "update", constant = @Constant(stringValue = ", ")) private String makeConflictTextMultiline(String constant) { return "\n"; @@ -31,6 +42,14 @@ private String makeConflictTextMultiline(String constant) { @ModifyConstant(method = "update", constant = @Constant(stringValue = "controls.keybinds.duplicateKeybinds")) private String replaceConflictText(String constant) { - return "fabric.keybinding.conflicts"; + for (KeyBinding otherBinding : MinecraftClient.getInstance().options.allKeys) { + if (otherBinding == binding || !binding.equals(otherBinding)) continue; + + if (KeyBindingContext.of(binding) != KeyBindingContext.of(otherBinding)) { + return "fabric.keybinding.conflicts"; + } + } + + return constant; } } From 31ada2deb0e9b2501ac9c1205c63ca01b11ec3b3 Mon Sep 17 00:00:00 2001 From: deirn Date: Fri, 8 Sep 2023 23:33:32 +0700 Subject: [PATCH 12/15] add null check --- .../fabric/mixin/client/keybinding/KeyBindingEntryMixin.java | 1 - 1 file changed, 1 deletion(-) diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java index a69e3b4d47..4ee3e17c6a 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingEntryMixin.java @@ -30,7 +30,6 @@ @Mixin(ControlsListWidget.KeyBindingEntry.class) public abstract class KeyBindingEntryMixin { - @Shadow @Final private KeyBinding binding; From e8b53f6b4732a8ea87b38914a40a4d4dbef82d65 Mon Sep 17 00:00:00 2001 From: deirn Date: Fri, 8 Sep 2023 23:34:54 +0700 Subject: [PATCH 13/15] fix style --- .../fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java index 1da1ba7bd6..328d23165f 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java @@ -94,5 +94,4 @@ private static void sendMessageWhenPressed(MinecraftClient client, KeyBinding bi client.player.sendMessage(Text.literal(message)); } } - } From 409f886706c37fe273e2df1d825c2bb39d09c739 Mon Sep 17 00:00:00 2001 From: deirn Date: Fri, 8 Sep 2023 23:46:03 +0700 Subject: [PATCH 14/15] explain the test more --- .../fabric/test/client/keybinding/KeyBindingsTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java index 328d23165f..d77ea35222 100644 --- a/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java +++ b/fabric-key-binding-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/client/keybinding/KeyBindingsTest.java @@ -71,15 +71,23 @@ public void onInitializeClient() { client.player.sendMessage(Text.literal("Duplicate Key was pressed!"), false); } + // When ALL binding is set to the same key, it then will conflict with all other bindings + // only one binding will be marked as pressed, based on what binding registered first. + // Vanilla bindings are registered last as it is made on GameOptions ctor instead of statically. + // - allBinding & inGameBinding conflicts -> inGameBinding will be called + // - allBinding & customCtxBinding conflicts -> allBinding will be called + // - allBinding & vanilla conflicts -> allBinding will be called sendMessageWhenPressed(client, inGameBinding, "In-game key was pressed"); sendMessageWhenPressed(client, allBinding, "ALL context key was pressed!"); - // 1 and 3 should be called at the same time + // 1 and 2 will conflict, 3 and 4 will conflict. + // 1 and 3 should be called at the same time. sendMessageWhenPressed(client, customCtxBinding1, "Custom Context Key 1 was pressed!"); sendMessageWhenPressed(client, customCtxBinding2, "Custom Context Key 2 was pressed!"); sendMessageWhenPressed(client, customCtxBinding3, "Custom Context Key 3 was pressed!"); sendMessageWhenPressed(client, customCtxBinding4, "Custom Context Key 4 was pressed!"); + // Won't conflict with each other, hold the respective item and press. sendMessageWhenPressed(client, diamondSwordBinding, "Diamond Sword Key was pressed!"); sendMessageWhenPressed(client, netheriteSwordBinding, "Netherite Sword Key was pressed!"); }); From 2b7bfba8e677025a2d980f64df8afb96e1e5b950 Mon Sep 17 00:00:00 2001 From: deirn Date: Sat, 9 Sep 2023 00:43:21 +0700 Subject: [PATCH 15/15] actually only call one conflicting key --- .../client/keybinding/KeyBindingMixin.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java index 706e78d245..a9b9b3f094 100644 --- a/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java +++ b/fabric-key-binding-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/keybinding/KeyBindingMixin.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -72,17 +73,34 @@ public void fabric_setContext(KeyBindingContext context) { this.fabric_context = context; } + @Unique + private static boolean fabric_isActiveAndHasNoConflict(@Nullable Set conflictingKeyBinds, KeyBinding binding) { + KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; + if (!mixed.fabric_context.isActive(MinecraftClient.getInstance())) return false; + + if (conflictingKeyBinds == null) { + return true; + } else { + boolean hasNoConflict = true; + + for (KeyBinding conflictingKeyBind : mixed.fabric_conflictingKeyBinds) { + // single ampersand so it always added to the set + hasNoConflict = hasNoConflict & conflictingKeyBinds.add(conflictingKeyBind); + } + + return hasNoConflict; + } + } + @Inject(method = "onKeyPressed", at = @At("HEAD")) private static void onKeyPressed(InputUtil.Key key, CallbackInfo ci) { List list = KeyBindingRegistryImpl.KEY_TO_BINDINGS.get(key); if (list == null) return; - Set uniqueKeyBinds = list.size() <= 1 ? null : Collections.newSetFromMap(new IdentityHashMap<>()); + Set conflictingKeyBinds = list.size() <= 1 ? null : Collections.newSetFromMap(new IdentityHashMap<>()); for (KeyBinding binding : list) { - KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; - - if (mixed.fabric_context.isActive(MinecraftClient.getInstance()) && (uniqueKeyBinds == null || uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds))) { + if (fabric_isActiveAndHasNoConflict(conflictingKeyBinds, binding)) { ((KeyBindingMixin) (Object) binding).timesPressed++; } } @@ -93,12 +111,10 @@ private static void setKeyPressed(InputUtil.Key key, boolean pressed, CallbackIn List list = KeyBindingRegistryImpl.KEY_TO_BINDINGS.get(key); if (list == null) return; - Set uniqueKeyBinds = list.size() <= 1 ? null : Collections.newSetFromMap(new IdentityHashMap<>()); + Set conflictingKeyBinds = list.size() <= 1 ? null : Collections.newSetFromMap(new IdentityHashMap<>()); for (KeyBinding binding : list) { - KeyBindingMixin mixed = (KeyBindingMixin) (Object) binding; - - if (mixed.fabric_context.isActive(MinecraftClient.getInstance()) && (uniqueKeyBinds == null || uniqueKeyBinds.addAll(mixed.fabric_conflictingKeyBinds))) { + if (fabric_isActiveAndHasNoConflict(conflictingKeyBinds, binding)) { binding.setPressed(pressed); } }