From c42667b035dd36161e885e6b7f81c7fe543bfcac Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Sun, 6 Jan 2019 13:28:09 +0100 Subject: [PATCH] Keybinding API (#57) --- .../client/keybinding/FabricKeyBinding.java | 49 ++++++++ .../client/keybinding/KeyBindingRegistry.java | 43 +++++++ .../keybinding/KeyBindingRegistryImpl.java | 111 ++++++++++++++++++ .../client/keybinding/MixinGameOptions.java | 37 ++++++ .../client/keybinding/MixinKeyBinding.java | 33 ++++++ .../net.fabricmc.fabric.mixins.client.json | 2 + .../keybinding/KeyBindingModClient.java | 38 ++++++ 7 files changed, 313 insertions(+) create mode 100644 src/main/java/net/fabricmc/fabric/api/client/keybinding/FabricKeyBinding.java create mode 100644 src/main/java/net/fabricmc/fabric/api/client/keybinding/KeyBindingRegistry.java create mode 100644 src/main/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java create mode 100644 src/main/java/net/fabricmc/fabric/mixin/client/keybinding/MixinGameOptions.java create mode 100644 src/main/java/net/fabricmc/fabric/mixin/client/keybinding/MixinKeyBinding.java create mode 100644 src/test/java/net/fabricmc/fabric/keybinding/KeyBindingModClient.java diff --git a/src/main/java/net/fabricmc/fabric/api/client/keybinding/FabricKeyBinding.java b/src/main/java/net/fabricmc/fabric/api/client/keybinding/FabricKeyBinding.java new file mode 100644 index 000000000..1a85ff37c --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/api/client/keybinding/FabricKeyBinding.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016, 2017, 2018 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; + +import net.minecraft.client.settings.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.util.Identifier; + +/** + * Expanded version of {@link KeyBinding} for use by Fabric mods. + * + * *ALL* instantiated FabricKeyBindings should be registered in + * {@link KeyBindingRegistry#register(FabricKeyBinding)}! + */ +public class FabricKeyBinding extends KeyBinding { + protected FabricKeyBinding(Identifier id, InputUtil.Type type, int code, String category) { + super("key." + id.toString().replace(':', '.'), type, code, category); + } + + public static class Builder { + protected final FabricKeyBinding binding; + + protected Builder(FabricKeyBinding binding) { + this.binding = binding; + } + + public FabricKeyBinding build() { + return binding; + } + + public static Builder create(Identifier id, InputUtil.Type type, int code, String category) { + return new Builder(new FabricKeyBinding(id, type, code, category)); + } + } +} diff --git a/src/main/java/net/fabricmc/fabric/api/client/keybinding/KeyBindingRegistry.java b/src/main/java/net/fabricmc/fabric/api/client/keybinding/KeyBindingRegistry.java new file mode 100644 index 000000000..4e27afff8 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/api/client/keybinding/KeyBindingRegistry.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016, 2017, 2018 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; + +import net.fabricmc.fabric.impl.client.keybinding.KeyBindingRegistryImpl; +import net.minecraft.client.settings.KeyBinding; + +/** + * Interface for registering key bindings. + * + * @see KeyBinding + */ +public interface KeyBindingRegistry { + static KeyBindingRegistry INSTANCE = KeyBindingRegistryImpl.INSTANCE; + + /** + * Add a new key binding category. + * @param categoryName The key binding category name. + * @return True if a new category was added. + */ + boolean addCategory(String categoryName); + + /** + * Register a new key binding. + * @param binding The key binding. + * @return True if a new key binding was registered. + */ + boolean register(FabricKeyBinding binding); +} diff --git a/src/main/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java b/src/main/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java new file mode 100644 index 000000000..29d7f29a2 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/impl/client/keybinding/KeyBindingRegistryImpl.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2016, 2017, 2018 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.FabricKeyBinding; +import net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.settings.KeyBinding; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class KeyBindingRegistryImpl implements KeyBindingRegistry { + public static final KeyBindingRegistryImpl INSTANCE = new KeyBindingRegistryImpl(); + private static final Logger LOGGER = LogManager.getLogger(); + + private Map cachedCategoryMap; + private List fabricKeyBindingList; + + private KeyBindingRegistryImpl() { + fabricKeyBindingList = new ArrayList<>(); + } + + private Map getCategoryMap() { + if (cachedCategoryMap == null) { + try { + //noinspection JavaReflectionMemberAccess + Method m = KeyBinding.class.getDeclaredMethod("fabric_getCategoryMap"); + m.setAccessible(true); + + //noinspection unchecked + cachedCategoryMap = (Map) m.invoke(null); + } catch (Exception e) { + throw new RuntimeException(e); + } + + if (cachedCategoryMap == null) { + throw new RuntimeException("Cached key binding category map missing!"); + } + } + + return cachedCategoryMap; + } + + private boolean hasCategory(String categoryName) { + return getCategoryMap().containsKey(categoryName); + } + + @Override + public boolean addCategory(String categoryName) { + Map map = getCategoryMap(); + if (map.containsKey(categoryName)) { + return false; + } + + Optional largest = map.values().stream().max(Integer::compareTo); + int largestInt = largest.orElse(0); + map.put(categoryName, largestInt + 1); + return true; + } + + @Override + public boolean register(FabricKeyBinding binding) { + for (KeyBinding exBinding : fabricKeyBindingList) { + if (exBinding == binding) { + return false; + } else if (exBinding.method_1431().equals(binding.method_1431())) { + throw new RuntimeException("Attempted to register two key bindings with equal ID: " + binding.method_1431() + "!"); + } + } + + if (!hasCategory(binding.method_1423())) { + LOGGER.warn("Tried to register key binding with unregistered category '" + binding.method_1423() + "' - please use addCategory to ensure intended category ordering!"); + addCategory(binding.method_1423()); + } + + fabricKeyBindingList.add(binding); + return true; + } + + public KeyBinding[] process(KeyBinding[] keysAll) { + List newKeysAll = new ArrayList<>(); + for (KeyBinding binding : keysAll) { + if (!(binding instanceof FabricKeyBinding)) { + newKeysAll.add(binding); + } + } + + newKeysAll.addAll(fabricKeyBindingList); + return newKeysAll.toArray(new KeyBinding[0]); + } +} diff --git a/src/main/java/net/fabricmc/fabric/mixin/client/keybinding/MixinGameOptions.java b/src/main/java/net/fabricmc/fabric/mixin/client/keybinding/MixinGameOptions.java new file mode 100644 index 000000000..1842b7349 --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/mixin/client/keybinding/MixinGameOptions.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016, 2017, 2018 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 net.fabricmc.fabric.impl.client.keybinding.KeyBindingRegistryImpl; +import net.minecraft.client.settings.GameOptions; +import net.minecraft.client.settings.KeyBinding; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameOptions.class) +public class MixinGameOptions { + @Shadow + public KeyBinding[] keysAll; + + @Inject(at = @At("HEAD"), method = "load()V") + public void loadHook(CallbackInfo info) { + keysAll = KeyBindingRegistryImpl.INSTANCE.process(keysAll); + } +} \ No newline at end of file diff --git a/src/main/java/net/fabricmc/fabric/mixin/client/keybinding/MixinKeyBinding.java b/src/main/java/net/fabricmc/fabric/mixin/client/keybinding/MixinKeyBinding.java new file mode 100644 index 000000000..722e102ee --- /dev/null +++ b/src/main/java/net/fabricmc/fabric/mixin/client/keybinding/MixinKeyBinding.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016, 2017, 2018 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 net.minecraft.client.settings.KeyBinding; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Map; + +@Mixin(KeyBinding.class) +public class MixinKeyBinding { + @Shadow + private static Map field_1656; + + private static Map fabric_getCategoryMap() { + return field_1656; + } +} diff --git a/src/main/resources/net.fabricmc.fabric.mixins.client.json b/src/main/resources/net.fabricmc.fabric.mixins.client.json index b95567397..9e3227ff0 100644 --- a/src/main/resources/net.fabricmc.fabric.mixins.client.json +++ b/src/main/resources/net.fabricmc.fabric.mixins.client.json @@ -7,6 +7,8 @@ "bugfix.MixinBiomeColors", "client.itemgroup.MixinItemGroup", "client.itemgroup.MixinCreativePlayerInventoryGui", + "client.keybinding.MixinGameOptions", + "client.keybinding.MixinKeyBinding", "client.model.MixinModelLoader", "client.render.MixinBlockColorMap", "client.render.MixinBlockEntityRenderManager", diff --git a/src/test/java/net/fabricmc/fabric/keybinding/KeyBindingModClient.java b/src/test/java/net/fabricmc/fabric/keybinding/KeyBindingModClient.java new file mode 100644 index 000000000..fea6e38f8 --- /dev/null +++ b/src/test/java/net/fabricmc/fabric/keybinding/KeyBindingModClient.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016, 2017, 2018 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.keybinding; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.keybinding.FabricKeyBinding; +import net.fabricmc.fabric.api.client.keybinding.KeyBindingRegistry; +import net.minecraft.client.util.InputUtil; +import net.minecraft.util.Identifier; + +public class KeyBindingModClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + KeyBindingRegistry.INSTANCE.addCategory("fabric.test"); + KeyBindingRegistry.INSTANCE.register( + FabricKeyBinding.Builder.create( + new Identifier("fabric:test"), + InputUtil.Type.KEY_KEYBOARD, + 37, + "fabric.test" + ).build() + ); + } +}