From 0dfd1994dcaa17a187660b257628851f88ec1c00 Mon Sep 17 00:00:00 2001 From: Martin Prokoph Date: Mon, 19 May 2025 16:20:46 +0200 Subject: [PATCH] feat: data-driven virtual keyboard layouts --- .../client/MidnightControlsConfig.java | 1 + .../MidnightControlsReloadListener.java | 16 +++++++++++ .../virtualkeyboard/KeyboardLayout.java | 27 +++++++------------ .../KeyboardLayoutManager.java | 27 +++++++++++++++++++ .../gui/VirtualKeyboardScreen.java | 10 ++++--- .../keyboard-layouts/de_quertz.json | 16 ----------- .../keyboard-layouts/en_querty.json | 16 ----------- .../keyboard_layouts/de_quertz.json | 14 ++++++++++ .../keyboard_layouts/en_querty.json | 14 ++++++++++ .../assets/midnightcontrols/lang/de_de.json | 4 +++ .../assets/midnightcontrols/lang/en_us.json | 10 +++++-- .../fabric/MidnightControlsClientFabric.java | 16 +++++++++++ .../MidnightControlsClientNeoforge.java | 10 ++++--- 13 files changed, 122 insertions(+), 59 deletions(-) create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsReloadListener.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayoutManager.java delete mode 100644 common/src/main/resources/assets/midnightcontrols/keyboard-layouts/de_quertz.json delete mode 100644 common/src/main/resources/assets/midnightcontrols/keyboard-layouts/en_querty.json create mode 100644 common/src/main/resources/assets/midnightcontrols/keyboard_layouts/de_quertz.json create mode 100644 common/src/main/resources/assets/midnightcontrols/keyboard_layouts/en_querty.json diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java index 85d4b9c..ddb8df3 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java @@ -148,6 +148,7 @@ public class MidnightControlsConfig extends MidnightConfig { @Comment(category = SCREENS, centered = true, name="\uD83D\uDD27 UI Modifications") public static Comment _uiMods; @Entry(category = SCREENS, name = "midnightcontrols.menu.move_chat") public static boolean moveChat = false; @Entry(category = SCREENS, name = "Enable Shortcut in Controls Options") public static boolean shortcutInControls = true; + @Entry(category = MISC, name = "midnightcontrols.menu.virtual_keyboard_layout") public static String keyboardLayout = "en_US:qwerty"; @Entry(category = MISC, name = "Debug") public static boolean debug = false; @Entry(category = MISC, name = "Excluded Keybindings") public static List excludedKeybindings = Lists.newArrayList("key.forward", "key.left", "key.back", "key.right", "key.jump", "key.sneak", "key.sprint", "key.inventory", "key.swapOffhand", "key.drop", "key.use", "key.attack", "key.chat", "key.playerlist", "key.screenshot", "key.togglePerspective", "key.smoothCamera", "key.fullscreen", "key.saveToolbarActivator", "key.loadToolbarActivator", diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsReloadListener.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsReloadListener.java new file mode 100644 index 0000000..a52f01a --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsReloadListener.java @@ -0,0 +1,16 @@ +package eu.midnightdust.midnightcontrols.client; + +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.KeyboardLayoutManager; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.SynchronousResourceReloader; + +public class MidnightControlsReloadListener implements SynchronousResourceReloader { + public static final MidnightControlsReloadListener INSTANCE = new MidnightControlsReloadListener(); + + private MidnightControlsReloadListener() {} + + @Override + public void reload(ResourceManager manager) { + manager.findResources("keyboard_layouts", path -> path.toString().startsWith("midnightcontrols") && path.toString().endsWith(".json")).forEach(KeyboardLayoutManager::loadLayout); + } +} \ No newline at end of file diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayout.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayout.java index 1c07fe7..a88c9ad 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayout.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayout.java @@ -8,35 +8,32 @@ import java.util.List; public class KeyboardLayout { - public static KeyboardLayout QWERTY = new KeyboardLayout("US (Qwerty)", "en-US", createQwertyLetterLayout(), createSymbolLayout()); - public static final List KEYBOARD_LAYOUTS = new ArrayList<>(); + public static KeyboardLayout QWERTY = new KeyboardLayout("en_US:qwerty", createQwertyLetterLayout(), createSymbolLayout()); - private final String name; - private final String locale; + private final String id; private final List> letters; private final List> symbols; - private KeyboardLayout(String name, String locale, List> letters, List> symbols) { - this.name = name; - this.locale = locale; + private KeyboardLayout(String id, List> letters, List> symbols) { + this.id = id; this.letters = letters; this.symbols = symbols; } - public KeyboardLayout fromJson(JsonObject json) { + public static KeyboardLayout fromJson(JsonObject json) { try { - return new KeyboardLayout(json.get("metadata").getAsJsonObject().get("name").getAsString(), json.get("metadata").getAsJsonObject().get("locale").getAsString(), getFromJson(json, true), getFromJson(json, false)); + return new KeyboardLayout(json.get("id").getAsString(), getFromJson(json, true), getFromJson(json, false)); } catch (Exception e) { throw new RuntimeException("Error loading keyboard definition: %s".formatted(e)); } } - public List> getFromJson(JsonObject json, boolean letters) { + private static List> getFromJson(JsonObject json, boolean letters) { String type = letters ? "letters" : "symbols"; List> arr = new ArrayList<>(); if (json.has(type)) { JsonObject lettersJson = json.get(type).getAsJsonObject(); for (int i = 0; ; i++) { - if (!lettersJson.has("row%s".formatted(i))) break; + if (!lettersJson.has("row"+i)) break; var rowJson = lettersJson.get("row%s".formatted(i)).getAsJsonArray(); List row = new ArrayList<>(); for (int j = 0; j < rowJson.size(); j++) { @@ -51,12 +48,8 @@ public class KeyboardLayout { } } - public String getName() { - return name; - } - - public String getLocale() { - return locale; + public String getId() { + return id; } public List> getLetters() { diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayoutManager.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayoutManager.java new file mode 100644 index 0000000..ba2cfae --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayoutManager.java @@ -0,0 +1,27 @@ +package eu.midnightdust.midnightcontrols.client.virtualkeyboard; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig; +import net.minecraft.resource.Resource; +import net.minecraft.util.Identifier; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class KeyboardLayoutManager { + private static final Map KEYBOARD_LAYOUTS = new HashMap<>(); + + public static void loadLayout(Identifier id, Resource resource) { + try { + JsonObject json = JsonParser.parseReader(resource.getReader()).getAsJsonObject(); + KeyboardLayout layout = KeyboardLayout.fromJson(json); + KEYBOARD_LAYOUTS.put(layout.getId(), layout); + if (MidnightControlsConfig.debug) System.out.printf("Loaded keyboard layout: %s\n", layout.getId()); + } catch (IOException e) { throw new RuntimeException(e); } + } + public static KeyboardLayout getById(String id) { + return KEYBOARD_LAYOUTS.get(id) == null ? KeyboardLayout.QWERTY : KEYBOARD_LAYOUTS.get(id); + } +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/gui/VirtualKeyboardScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/gui/VirtualKeyboardScreen.java index bb26353..2711b0f 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/gui/VirtualKeyboardScreen.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/gui/VirtualKeyboardScreen.java @@ -1,6 +1,8 @@ package eu.midnightdust.midnightcontrols.client.virtualkeyboard.gui; +import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig; import eu.midnightdust.midnightcontrols.client.virtualkeyboard.KeyboardLayout; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.KeyboardLayoutManager; import net.minecraft.client.gui.DrawContext; import net.minecraft.text.Text; import org.thinkingstudio.obsidianui.Position; @@ -42,10 +44,10 @@ public class VirtualKeyboardScreen extends SpruceScreen { private SpruceContainerWidget keyboardContainer; public VirtualKeyboardScreen(String initialText, CloseCallback closeCallback, boolean newLineSupport) { - super(Text.literal("Virtual Keyboard")); + super(Text.translatable("midnightcontrols.virtual_keyboard.screen")); this.buffer = new StringBuilder(initialText); this.closeCallback = closeCallback; - this.layout = KeyboardLayout.QWERTY; + this.layout = KeyboardLayoutManager.getById(MidnightControlsConfig.keyboardLayout); this.capsMode = false; this.symbolMode = false; this.newLineSupport = newLineSupport; @@ -187,7 +189,7 @@ public class VirtualKeyboardScreen extends SpruceScreen { } private void addFunctionKeys(SpruceContainerWidget container) { - List firstRow = getActiveKeyLayout().get(0); + List firstRow = getActiveKeyLayout().getFirst(); int firstRowWidth = calculateRowWidth(firstRow); // position backspace at the right of the first row @@ -239,7 +241,7 @@ public class VirtualKeyboardScreen extends SpruceScreen { Position.of(spaceX, rowY), spaceKeyWidth, KEY_HEIGHT, - Text.literal("Space"), + Text.translatable("midnightcontrols.virtual_keyboard.keyboard.space"), btn -> handleKeyPress(SPACE_SYMBOL) ) ); diff --git a/common/src/main/resources/assets/midnightcontrols/keyboard-layouts/de_quertz.json b/common/src/main/resources/assets/midnightcontrols/keyboard-layouts/de_quertz.json deleted file mode 100644 index 4a58ed1..0000000 --- a/common/src/main/resources/assets/midnightcontrols/keyboard-layouts/de_quertz.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "metadata": { - "name": "German (Quertz)", - "locale": "de-DE" - }, - "letters": { - "row1": ["q", "w", "e", "r", "t", "z", "u", "i", "o", "p", "ü"], - "row2": ["a", "s", "d", "f", "g", "h", "j", "k", "l", "ö", "ä"], - "row3": ["y", "x", "c", "v", "b", "n", "m", "ß"] - }, - "symbols": { - "row1": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], - "row2": ["@", "#", "$", "%", "&", "*", "-", "+", "(", ")"], - "row3": ["!", "\"", "'", ":", ";", ",", ".", "?", "/"] - } -} diff --git a/common/src/main/resources/assets/midnightcontrols/keyboard-layouts/en_querty.json b/common/src/main/resources/assets/midnightcontrols/keyboard-layouts/en_querty.json deleted file mode 100644 index e7c5a2f..0000000 --- a/common/src/main/resources/assets/midnightcontrols/keyboard-layouts/en_querty.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "metadata": { - "name": "US (Querty)", - "locale": "en-US" - }, - "letters": { - "row1": ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"], - "row2": ["a", "s", "d", "f", "g", "h", "j", "k", "l"], - "row3": ["z", "x", "c", "v", "b", "n", "m"] - }, - "symbols": { - "row1": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], - "row2": ["@", "#", "$", "%", "&", "*", "-", "+", "(", ")"], - "row3": ["!", "\"", "'", ":", ";", ",", ".", "?", "/"] - } -} diff --git a/common/src/main/resources/assets/midnightcontrols/keyboard_layouts/de_quertz.json b/common/src/main/resources/assets/midnightcontrols/keyboard_layouts/de_quertz.json new file mode 100644 index 0000000..25620f6 --- /dev/null +++ b/common/src/main/resources/assets/midnightcontrols/keyboard_layouts/de_quertz.json @@ -0,0 +1,14 @@ +{ + "id": "de_DE:qwertz", + + "letters": { + "row0": ["q", "w", "e", "r", "t", "z", "u", "i", "o", "p", "ü"], + "row1": ["a", "s", "d", "f", "g", "h", "j", "k", "l", "ö", "ä"], + "row2": ["y", "x", "c", "v", "b", "n", "m", "ß"] + }, + "symbols": { + "row0": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], + "row1": ["@", "#", "$", "%", "&", "*", "-", "+", "(", ")"], + "row2": ["!", "\"", "'", ":", ";", ",", ".", "?", "/"] + } +} diff --git a/common/src/main/resources/assets/midnightcontrols/keyboard_layouts/en_querty.json b/common/src/main/resources/assets/midnightcontrols/keyboard_layouts/en_querty.json new file mode 100644 index 0000000..1f4b5d5 --- /dev/null +++ b/common/src/main/resources/assets/midnightcontrols/keyboard_layouts/en_querty.json @@ -0,0 +1,14 @@ +{ + "id": "en_US:qwerty", + + "letters": { + "row0": ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"], + "row1": ["a", "s", "d", "f", "g", "h", "j", "k", "l"], + "row2": ["z", "x", "c", "v", "b", "n", "m"] + }, + "symbols": { + "row0": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], + "row1": ["@", "#", "$", "%", "&", "*", "-", "+", "(", ")"], + "row2": ["!", "\"", "'", ":", ";", ",", ".", "?", "/"] + } +} diff --git a/common/src/main/resources/assets/midnightcontrols/lang/de_de.json b/common/src/main/resources/assets/midnightcontrols/lang/de_de.json index ac41b9d..7e0fb97 100644 --- a/common/src/main/resources/assets/midnightcontrols/lang/de_de.json +++ b/common/src/main/resources/assets/midnightcontrols/lang/de_de.json @@ -146,6 +146,10 @@ "midnightcontrols.menu.title.visual": "Visuelle Optionen", "midnightcontrols.menu.unfocused_input": "Unfokussierte Eingabe", "midnightcontrols.menu.unfocused_input.tooltip": "Erlaube Controllereingabe auch wenn das Fenster nicht fokussiert ist.", + "midnightcontrols.virtual_keyboard.screen": "Virtuelle Tastatur", + "midnightcontrols.virtual_keyboard.keyboard.space": "Leertaste", + "midnightcontrols.virtual_keyboard.layout.en_US.qwerty": "Englisch (Qwerty)", + "midnightcontrols.virtual_keyboard.layout.de_DE.qwertz": "Deutsch (Qwertz)", "midnightcontrols.menu.virtual_mouse": "Virtuelle Maus", "midnightcontrols.menu.virtual_mouse.tooltip": "Aktiviere die virtuelle Maus.", "midnightcontrols.menu.virtual_mouse.skin": "Aussehen der Virtuellen Maus", diff --git a/common/src/main/resources/assets/midnightcontrols/lang/en_us.json b/common/src/main/resources/assets/midnightcontrols/lang/en_us.json index dfe2b56..6b1b3c7 100644 --- a/common/src/main/resources/assets/midnightcontrols/lang/en_us.json +++ b/common/src/main/resources/assets/midnightcontrols/lang/en_us.json @@ -217,15 +217,21 @@ "midnightcontrols.menu.touch_with_controller": "Touch in Controller mode", "midnightcontrols.menu.unfocused_input": "Unfocused Input", "midnightcontrols.menu.unfocused_input.tooltip": "Allows controller input when the window is not focused.", + "midnightcontrols.menu.virtual_keyboard": "Virtual Keyboard", + "midnightcontrols.menu.virtual_keyboard.tooltip": "Enables a virtual on-screen keyboard", + "midnightcontrols.menu.virtual_keyboard_layout": "Virtual Keyboard Layout", + "midnightcontrols.menu.virtual_keyboard_layout.tooltip": "Defines which layout the on-screen keyboard will follow.", "midnightcontrols.menu.virtual_mouse": "Virtual Mouse", "midnightcontrols.menu.virtual_mouse.tooltip": "Enables the virtual mouse, which is useful during splitscreen.", "midnightcontrols.menu.virtual_mouse.skin": "Virtual Mouse Skin", - "midnightcontrols.menu.virtual_keyboard": "Virtual Keyboard", - "midnightcontrols.menu.virtual_keyboard.tooltip": "Enables a virtual on-screen keyboard", "midnightcontrols.menu.hide_cursor": "Hide Normal Mouse Cursor", "midnightcontrols.menu.hide_cursor.tooltip": "Hides the normal mouse cursor, leaving only the virtual mouse visible.", "midnightcontrols.narrator.unbound": "Unbound %s", "midnightcontrols.not_bound": "Not bound", + "midnightcontrols.virtual_keyboard.screen": "Virtual Keyboard", + "midnightcontrols.virtual_keyboard.keyboard.space": "Space", + "midnightcontrols.virtual_keyboard.layout.en_US.qwerty": "English (Qwerty)", + "midnightcontrols.virtual_keyboard.layout.de_DE.qwertz": "German (Qwertz)", "midnightcontrols.virtual_mouse.skin.default_light": "Default Light", "midnightcontrols.virtual_mouse.skin.default_dark": "Default Dark", "midnightcontrols.virtual_mouse.skin.second_light": "Second Light", diff --git a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/MidnightControlsClientFabric.java b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/MidnightControlsClientFabric.java index 7c9f04c..2743690 100644 --- a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/MidnightControlsClientFabric.java +++ b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/MidnightControlsClientFabric.java @@ -3,6 +3,7 @@ package eu.midnightdust.midnightcontrols.fabric; import eu.midnightdust.midnightcontrols.MidnightControlsConstants; import eu.midnightdust.midnightcontrols.client.MidnightControlsClient; import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig; +import eu.midnightdust.midnightcontrols.client.MidnightControlsReloadListener; import eu.midnightdust.midnightcontrols.fabric.event.MouseClickListener; import eu.midnightdust.midnightcontrols.packet.ControlsModePayload; import eu.midnightdust.midnightcontrols.packet.FeaturePayload; @@ -16,8 +17,12 @@ import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents; import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.fabricmc.fabric.api.resource.ResourcePackActivationType; +import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.ResourceType; +import net.minecraft.util.Identifier; import java.util.Optional; @@ -62,5 +67,16 @@ public class MidnightControlsClientFabric implements ClientModInitializer { ResourceManagerHelper.registerBuiltinResourcePack(id("legacy"), modContainer, ResourcePackActivationType.NORMAL); }); MidnightControlsClient.initClient(); + + ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener() { + @Override + public Identifier getFabricId() { + return id("keyboard_layouts"); + } + @Override + public void reload(ResourceManager manager) { + MidnightControlsReloadListener.INSTANCE.reload(manager); + } + }); } } diff --git a/neoforge/src/main/java/eu/midnightdust/midnightcontrols/neoforge/MidnightControlsClientNeoforge.java b/neoforge/src/main/java/eu/midnightdust/midnightcontrols/neoforge/MidnightControlsClientNeoforge.java index b98e286..f6023f9 100644 --- a/neoforge/src/main/java/eu/midnightdust/midnightcontrols/neoforge/MidnightControlsClientNeoforge.java +++ b/neoforge/src/main/java/eu/midnightdust/midnightcontrols/neoforge/MidnightControlsClientNeoforge.java @@ -2,6 +2,7 @@ package eu.midnightdust.midnightcontrols.neoforge; import eu.midnightdust.midnightcontrols.client.MidnightControlsClient; import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig; +import eu.midnightdust.midnightcontrols.client.MidnightControlsReloadListener; import eu.midnightdust.midnightcontrols.client.util.platform.NetworkUtil; import eu.midnightdust.midnightcontrols.packet.ControlsModePayload; import eu.midnightdust.midnightcontrols.packet.HelloPayload; @@ -19,10 +20,7 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.ModList; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.fml.common.Mod; -import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; -import net.neoforged.neoforge.client.event.ClientTickEvent; -import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; -import net.neoforged.neoforge.client.event.ScreenEvent; +import net.neoforged.neoforge.client.event.*; import net.neoforged.neoforge.event.AddPackFindersEvent; import net.neoforged.neoforgespi.locating.IModFile; @@ -75,6 +73,10 @@ public class MidnightControlsClientNeoforge { } catch (NullPointerException e) {e.fillInStackTrace();} })); } + @SubscribeEvent + public static void onResourceReload(AddClientReloadListenersEvent event) { + event.addListener(id("keyboard-layouts"), MidnightControlsReloadListener.INSTANCE); + } } @EventBusSubscriber(modid = NAMESPACE, bus = EventBusSubscriber.Bus.GAME, value = Dist.CLIENT)