From 9e12381471680f1456d1ed396f582d9f3f9ed8a6 Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Fri, 18 Apr 2025 19:43:01 +0200 Subject: [PATCH 01/18] Implement basic virtual keyboard support - Listener for clicks inside of text fields and other text-based screens - Virtual keyboard screen in a QWERTY layout --- .../client/MidnightControlsClient.java | 1 + .../client/gui/VirtualKeyboardScreen.java | 108 +++++++++ .../mixin/AbstractSignEditScreenAccessor.java | 15 ++ .../client/mixin/BookEditScreenAccessor.java | 24 ++ .../CreativeInventoryScreenAccessor.java | 6 + .../resources/midnightcontrols.mixins.json | 2 + .../fabric/MidnightControlsClientFabric.java | 7 +- .../fabric/event/MouseClickListener.java | 217 ++++++++++++++++++ 8 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/VirtualKeyboardScreen.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenAccessor.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/BookEditScreenAccessor.java create mode 100644 fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsClient.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsClient.java index 21df7d7..074250f 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsClient.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsClient.java @@ -204,6 +204,7 @@ public class MidnightControlsClient extends MidnightControls { RainbowColor.tick(); TouchInput.tick(); } + /** * Called when opening a screen. */ diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/VirtualKeyboardScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/VirtualKeyboardScreen.java new file mode 100644 index 0000000..55ca8c4 --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/VirtualKeyboardScreen.java @@ -0,0 +1,108 @@ +package eu.midnightdust.midnightcontrols.client.gui; + +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.text.Text; +import org.thinkingstudio.obsidianui.Position; +import org.thinkingstudio.obsidianui.SpruceTexts; +import org.thinkingstudio.obsidianui.screen.SpruceScreen; +import org.thinkingstudio.obsidianui.widget.SpruceButtonWidget; +import org.thinkingstudio.obsidianui.widget.container.SpruceContainerWidget; + +public class VirtualKeyboardScreen extends SpruceScreen { + private SpruceContainerWidget container; + private TextFieldWidget bufferDisplay; + private final StringBuilder buffer; + private final CloseCallback closeCallback; + + @FunctionalInterface + public interface CloseCallback { + void onClose(String text); + } + + public VirtualKeyboardScreen(String initialText, CloseCallback closeCallback) { + super(Text.literal("Virtual Keyboard")); + + this.buffer = new StringBuilder(initialText); + this.closeCallback = closeCallback; + } + + @Override + protected void init() { + super.init(); + + this.bufferDisplay = new TextFieldWidget(this.textRenderer, this.width / 2 - 100, this.height / 4 - 40, 200, 20, Text.literal("")); + this.bufferDisplay.setEditable(false); + this.bufferDisplay.setMaxLength(1024); + this.bufferDisplay.setText(buffer.toString()); + this.addDrawableChild(this.bufferDisplay); + + rebuildKeyboard(); + + this.addDrawableChild(container); + this.addDrawableChild(new SpruceButtonWidget(Position.of(this, this.width / 2 - 50, this.height - 30), 100, 20, SpruceTexts.GUI_DONE, btn -> this.close())); + } + + @Override + public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { + super.render(drawContext, mouseX, mouseY, delta); + drawContext.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 10, 0xFFFFFF); + } + + + private void rebuildKeyboard() { + this.container = new SpruceContainerWidget(Position.of(0, this.height / 4 - 10), this.width, this.height / 2); + + + var row1 = new String[]{"q", "w", "e", "r", "t", "y", "u", "i", "o", "p"}; + var row2 = new String[]{"a", "s", "d", "f", "g", "h", "j", "k", "l"}; + var row3 = new String[]{"z", "x", "c", "v", "b", "n", "m"}; + + addKeyRow(0, row1); + addKeyRow(1, row2); + addKeyRow(2, row3); + } + + private void addKeyRow(int rowOffset, String... keys) { + int keyWidth = 20; + int spacing = 2; + int totalWidth = (keyWidth + spacing) * keys.length - spacing; + int startX = (this.width - totalWidth) / 2; + int y = this.height / 4 + rowOffset * 24; + + for (int i = 0; i < keys.length; i++) { + String key = keys[i]; + this.container.addChild(new SpruceButtonWidget(Position.of(startX + i * (keyWidth + spacing), y), keyWidth, 20, Text.literal(key), btn -> handleKeyPress(key))); + } + } + + private void handleKeyPress(String key) { + if (this.client == null) return; + + // TODO + if (key.equals("\b")) { + if (!buffer.isEmpty()) { + buffer.deleteCharAt(buffer.length() - 1); + } + } else { + buffer.append(key); + } + + if (this.bufferDisplay != null) { + this.bufferDisplay.setText(buffer.toString()); + } + } + + @Override + public boolean shouldPause() { + return false; + } + + @Override + public void close() { + super.close(); + if (closeCallback != null) { + closeCallback.onClose(buffer.toString()); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenAccessor.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenAccessor.java new file mode 100644 index 0000000..fa5036c --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenAccessor.java @@ -0,0 +1,15 @@ +package eu.midnightdust.midnightcontrols.client.mixin; + +import net.minecraft.block.entity.SignText; +import net.minecraft.client.gui.screen.ingame.AbstractSignEditScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(AbstractSignEditScreen.class) +public interface AbstractSignEditScreenAccessor { + @Accessor("text") + SignText midnightcontrols$getText(); + + @Accessor("text") + void midnightcontrols$setText(SignText text); +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/BookEditScreenAccessor.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/BookEditScreenAccessor.java new file mode 100644 index 0000000..e26f51b --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/BookEditScreenAccessor.java @@ -0,0 +1,24 @@ +package eu.midnightdust.midnightcontrols.client.mixin; + +import net.minecraft.client.gui.screen.ingame.BookEditScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(BookEditScreen.class) +public interface BookEditScreenAccessor { + @Accessor("signing") + boolean midnightcontrols$isSigning(); + + @Accessor("title") + String midnightcontrols$getTitle(); + + @Accessor("title") + void midnightcontrols$setTitle(String title); + + @Invoker("getCurrentPageContent") + String midnightcontrols$getCurrentPageContent(); + + @Invoker("setPageContent") + void midnightcontrols$setPageContent(String newContent); +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/CreativeInventoryScreenAccessor.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/CreativeInventoryScreenAccessor.java index 94961cf..2ab6c1d 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/CreativeInventoryScreenAccessor.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/CreativeInventoryScreenAccessor.java @@ -58,4 +58,10 @@ public interface CreativeInventoryScreenAccessor { */ @Invoker("hasScrollbar") boolean midnightcontrols$hasScrollbar(); + + /** + * Triggers searching the creative inventory from the current value of the internal {@link net.minecraft.client.gui.widget.TextFieldWidget} + */ + @Invoker("search") + void midnightcontrols$search(); } diff --git a/common/src/main/resources/midnightcontrols.mixins.json b/common/src/main/resources/midnightcontrols.mixins.json index b00371b..4a01ff8 100644 --- a/common/src/main/resources/midnightcontrols.mixins.json +++ b/common/src/main/resources/midnightcontrols.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_21", "client": [ "AdvancementsScreenAccessor", + "BookEditScreenAccessor", "ChatScreenMixin", "ClickableWidgetAccessor", "ClientPlayerEntityMixin", @@ -22,6 +23,7 @@ "RecipeBookScreenAccessor", "RecipeBookWidgetAccessor", "ScreenMixin", + "AbstractSignEditScreenAccessor", "TabNavigationWidgetAccessor", "WorldRendererMixin" ], 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 60d43ac..7c9f04c 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.fabric.event.MouseClickListener; import eu.midnightdust.midnightcontrols.packet.ControlsModePayload; import eu.midnightdust.midnightcontrols.packet.FeaturePayload; import eu.midnightdust.midnightcontrols.packet.HelloPayload; @@ -11,11 +12,12 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +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.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; -import org.thinkingstudio.obsidianui.fabric.event.OpenScreenCallback; import java.util.Optional; @@ -51,6 +53,9 @@ public class MidnightControlsClientFabric implements ClientModInitializer { ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> MidnightControlsClient.onLeave()); ClientTickEvents.START_CLIENT_TICK.register(MidnightControlsClient::onTick); + ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> { + ScreenMouseEvents.allowMouseClick(screen).register(new MouseClickListener(screen)); + }); FabricLoader.getInstance().getModContainer(MidnightControlsConstants.NAMESPACE).ifPresent(modContainer -> { ResourceManagerHelper.registerBuiltinResourcePack(id("bedrock"), modContainer, ResourcePackActivationType.NORMAL); diff --git a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java new file mode 100644 index 0000000..6eefac9 --- /dev/null +++ b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java @@ -0,0 +1,217 @@ +package eu.midnightdust.midnightcontrols.fabric.event; +import eu.midnightdust.midnightcontrols.client.gui.VirtualKeyboardScreen; +import eu.midnightdust.midnightcontrols.client.mixin.AbstractSignEditScreenAccessor; +import eu.midnightdust.midnightcontrols.client.mixin.BookEditScreenAccessor; +import eu.midnightdust.midnightcontrols.client.mixin.CreativeInventoryScreenAccessor; +import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents; +import net.minecraft.block.entity.SignText; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.ParentElement; +import net.minecraft.client.gui.screen.ChatScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.BookEditScreen; +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.client.gui.screen.ingame.SignEditScreen; +import net.minecraft.client.gui.widget.TextFieldWidget; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static eu.midnightdust.midnightcontrols.MidnightControls.logger; +import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client; + +record ScreenLink(Screen screen, List elementPath) {} + +public class MouseClickListener implements ScreenMouseEvents.AllowMouseClick { + private final Screen screen; + + private ScreenLink link; + + public MouseClickListener(Screen screen) { + this.screen = screen; + } + + @Override + public boolean allowMouseClick(Screen screen, double mouseX, double mouseY, int button) { + interceptMouseClick(screen, mouseX, mouseY); + return true; + } + + private void interceptMouseClick(Screen screen, double mouseX, double mouseY) { + logger.info("In scr: {}", screen.getClass()); + switch(screen) { + case BookEditScreen bookEditScreen -> { + if(screen.hoveredElement(mouseX, mouseY).isPresent()) { + return; + } + handleBookEditScreenClick(bookEditScreen); + } + case SignEditScreen signEditScreen -> { + if(screen.hoveredElement(mouseX, mouseY).isPresent()) { + return; + } + handleSignEditScreenClick(signEditScreen); + } + default -> { + var textField = findClickedTextField(screen, mouseX, mouseY); + if (textField == null) { + return; + } + handleTextFieldClick(textField); + } + } + } + + // Add equals and hashCode to prevent duplicate registrations + @Override + public boolean equals(Object obj) { + if (obj instanceof MouseClickListener) { + return ((MouseClickListener) obj).screen == this.screen; + } + return false; + } + + @Override + public int hashCode() { + return screen.hashCode(); + } + + // handlers + + private void handleBookEditScreenClick(BookEditScreen bookEditScreen) { + var accessor = (BookEditScreenAccessor) screen; + + VirtualKeyboardScreen virtualKeyboardScreen; + if(accessor.midnightcontrols$isSigning()) { + virtualKeyboardScreen = new VirtualKeyboardScreen(accessor.midnightcontrols$getTitle(), (text) -> { + client.setScreen(bookEditScreen); + accessor.midnightcontrols$setTitle(text); + }); + } + else { + virtualKeyboardScreen = new VirtualKeyboardScreen(accessor.midnightcontrols$getCurrentPageContent(), (text) -> { + client.setScreen(bookEditScreen); + accessor.midnightcontrols$setPageContent(text); + }); + } + + client.setScreen(virtualKeyboardScreen); + } + + private void handleSignEditScreenClick(SignEditScreen signEditScreen) { + var accessor = (AbstractSignEditScreenAccessor) signEditScreen; + // TODO + } + + private void handleTextFieldClick(TextFieldWidget textField) { + this.link = new ScreenLink(screen, calculatePathToElement(screen, textField)); + var virtualKeyboardScreen = new VirtualKeyboardScreen(textField.getText(), this::handleKeyboardClose); + client.setScreen(virtualKeyboardScreen); + } + + private void handleKeyboardClose(String newText) { + if(this.link == null) { + return; + } + + client.setScreen(this.link.screen()); + var txtField = findTextFieldByPath(screen, this.link.elementPath()); + if (txtField == null) { + return; + } + + txtField.setText(newText); + + + switch (this.link.screen()) { + case CreativeInventoryScreen creativeInventoryScreen -> { + var accessor = (CreativeInventoryScreenAccessor) creativeInventoryScreen; + accessor.midnightcontrols$search(); + } + case ChatScreen chatScreen -> { + // send the chat message + chatScreen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0); + } + default -> {} + } + } + + // utility + + private TextFieldWidget findClickedTextField(Screen screen, double mouseX, double mouseY) { + for (Element element : screen.children()) { + if (element instanceof TextFieldWidget textField) { + if (textField.isMouseOver(mouseX, mouseY) && textField.isFocused()) { + return textField; + } + } + } + + // not hovering over a text field + return null; + } + + /** + * Calculates the path between a parent and a target in the UI hierarchy + */ + private List calculatePathToElement(Element parent, Element target) { + if (parent instanceof ParentElement parentElement) { + List children = parentElement.children(); + + // check direct children first + for (int i = 0; i < children.size(); i++) { + if (children.get(i) == target) { + // found it, return the path to this element + return Collections.singletonList(i); + } + } + + // check each child's children + for (int i = 0; i < children.size(); i++) { + if (children.get(i) instanceof ParentElement childParent) { + List subPath = calculatePathToElement(childParent, target); + if (subPath != null) { + // found in this subtree, prepend current index + List fullPath = new ArrayList<>(); + fullPath.add(i); + fullPath.addAll(subPath); + return fullPath; + } + } + } + } + + // Not found + return null; + } + + private TextFieldWidget findTextFieldByPath(Element parent, List path) { + if (path == null || path.isEmpty()) { + return null; + } + + if (parent instanceof ParentElement parentElement) { + List children = parentElement.children(); + int index = path.getFirst(); + + if (index >= 0 && index < children.size()) { + Element child = children.get(index); + + if (path.size() == 1) { + // This should be our target + return (child instanceof TextFieldWidget) ? (TextFieldWidget) child : null; + } else { + // Continue traversing + if (child instanceof ParentElement) { + return findTextFieldByPath(child, path.subList(1, path.size())); + } + } + } + } + + return null; + } +} From 50103ce4cf411ca92efd30ee38ca9854b91f2878 Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Sun, 20 Apr 2025 19:53:26 +0200 Subject: [PATCH 02/18] Move cursor to end of text in book --- .../midnightcontrols/client/mixin/BookEditScreenAccessor.java | 4 ++++ .../midnightcontrols/fabric/event/MouseClickListener.java | 1 + 2 files changed, 5 insertions(+) diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/BookEditScreenAccessor.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/BookEditScreenAccessor.java index e26f51b..e44a57c 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/BookEditScreenAccessor.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/BookEditScreenAccessor.java @@ -1,6 +1,7 @@ package eu.midnightdust.midnightcontrols.client.mixin; import net.minecraft.client.gui.screen.ingame.BookEditScreen; +import net.minecraft.client.util.SelectionManager; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; @@ -16,6 +17,9 @@ public interface BookEditScreenAccessor { @Accessor("title") void midnightcontrols$setTitle(String title); + @Accessor("currentPageSelectionManager") + SelectionManager midnightcontrols$getCurrentPageSelectionManager(); + @Invoker("getCurrentPageContent") String midnightcontrols$getCurrentPageContent(); diff --git a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java index 6eefac9..d8b3610 100644 --- a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java +++ b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java @@ -95,6 +95,7 @@ public class MouseClickListener implements ScreenMouseEvents.AllowMouseClick { virtualKeyboardScreen = new VirtualKeyboardScreen(accessor.midnightcontrols$getCurrentPageContent(), (text) -> { client.setScreen(bookEditScreen); accessor.midnightcontrols$setPageContent(text); + accessor.midnightcontrols$getCurrentPageSelectionManager().putCursorAtEnd(); }); } From 662bac30533252146ecf12b92a701068bedf86be Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:36:26 +0200 Subject: [PATCH 03/18] Finalize virtual keyboard screen - Added backspace support - Added space support - Added new line support - Added caps support - Added symbol support --- .../client/gui/VirtualKeyboardScreen.java | 108 ------ .../client/gui/virtualkeyboard/KeyInfo.java | 16 + .../gui/virtualkeyboard/KeyboardLayout.java | 60 ++++ .../VirtualKeyboardScreen.java | 308 ++++++++++++++++++ .../fabric/event/MouseClickListener.java | 11 +- 5 files changed, 388 insertions(+), 115 deletions(-) delete mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/VirtualKeyboardScreen.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyInfo.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/VirtualKeyboardScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/VirtualKeyboardScreen.java deleted file mode 100644 index 55ca8c4..0000000 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/VirtualKeyboardScreen.java +++ /dev/null @@ -1,108 +0,0 @@ -package eu.midnightdust.midnightcontrols.client.gui; - -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.text.Text; -import org.thinkingstudio.obsidianui.Position; -import org.thinkingstudio.obsidianui.SpruceTexts; -import org.thinkingstudio.obsidianui.screen.SpruceScreen; -import org.thinkingstudio.obsidianui.widget.SpruceButtonWidget; -import org.thinkingstudio.obsidianui.widget.container.SpruceContainerWidget; - -public class VirtualKeyboardScreen extends SpruceScreen { - private SpruceContainerWidget container; - private TextFieldWidget bufferDisplay; - private final StringBuilder buffer; - private final CloseCallback closeCallback; - - @FunctionalInterface - public interface CloseCallback { - void onClose(String text); - } - - public VirtualKeyboardScreen(String initialText, CloseCallback closeCallback) { - super(Text.literal("Virtual Keyboard")); - - this.buffer = new StringBuilder(initialText); - this.closeCallback = closeCallback; - } - - @Override - protected void init() { - super.init(); - - this.bufferDisplay = new TextFieldWidget(this.textRenderer, this.width / 2 - 100, this.height / 4 - 40, 200, 20, Text.literal("")); - this.bufferDisplay.setEditable(false); - this.bufferDisplay.setMaxLength(1024); - this.bufferDisplay.setText(buffer.toString()); - this.addDrawableChild(this.bufferDisplay); - - rebuildKeyboard(); - - this.addDrawableChild(container); - this.addDrawableChild(new SpruceButtonWidget(Position.of(this, this.width / 2 - 50, this.height - 30), 100, 20, SpruceTexts.GUI_DONE, btn -> this.close())); - } - - @Override - public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { - super.render(drawContext, mouseX, mouseY, delta); - drawContext.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 10, 0xFFFFFF); - } - - - private void rebuildKeyboard() { - this.container = new SpruceContainerWidget(Position.of(0, this.height / 4 - 10), this.width, this.height / 2); - - - var row1 = new String[]{"q", "w", "e", "r", "t", "y", "u", "i", "o", "p"}; - var row2 = new String[]{"a", "s", "d", "f", "g", "h", "j", "k", "l"}; - var row3 = new String[]{"z", "x", "c", "v", "b", "n", "m"}; - - addKeyRow(0, row1); - addKeyRow(1, row2); - addKeyRow(2, row3); - } - - private void addKeyRow(int rowOffset, String... keys) { - int keyWidth = 20; - int spacing = 2; - int totalWidth = (keyWidth + spacing) * keys.length - spacing; - int startX = (this.width - totalWidth) / 2; - int y = this.height / 4 + rowOffset * 24; - - for (int i = 0; i < keys.length; i++) { - String key = keys[i]; - this.container.addChild(new SpruceButtonWidget(Position.of(startX + i * (keyWidth + spacing), y), keyWidth, 20, Text.literal(key), btn -> handleKeyPress(key))); - } - } - - private void handleKeyPress(String key) { - if (this.client == null) return; - - // TODO - if (key.equals("\b")) { - if (!buffer.isEmpty()) { - buffer.deleteCharAt(buffer.length() - 1); - } - } else { - buffer.append(key); - } - - if (this.bufferDisplay != null) { - this.bufferDisplay.setText(buffer.toString()); - } - } - - @Override - public boolean shouldPause() { - return false; - } - - @Override - public void close() { - super.close(); - if (closeCallback != null) { - closeCallback.onClose(buffer.toString()); - } - } -} \ No newline at end of file diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyInfo.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyInfo.java new file mode 100644 index 0000000..513a5e4 --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyInfo.java @@ -0,0 +1,16 @@ +package eu.midnightdust.midnightcontrols.client.gui.virtualkeyboard; + +public record KeyInfo(String keySymbol, String displayText, double widthFactor) { + // Convenience constructor for standard width keys + KeyInfo(String keySymbol, double widthFactor) { + this(keySymbol, keySymbol, widthFactor); + } + + KeyInfo(String keySymbol) { + this(keySymbol, 1.0); + } + + KeyInfo(String keySymbol, String displayText) { + this(keySymbol, displayText, 1.0); + } +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java new file mode 100644 index 0000000..ccaa4d3 --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java @@ -0,0 +1,60 @@ +package eu.midnightdust.midnightcontrols.client.gui.virtualkeyboard; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class KeyboardLayout { + + public static KeyboardLayout QWERTY = new KeyboardLayout(createQwertyLetterLayout(), createSymbolLayout()); + + private final List> letters; + private final List> symbols; + + private KeyboardLayout(List> letters, List> symbols) { + this.letters = letters; + this.symbols = symbols; + } + + public List> getLetters() { + return letters; + } + + public List> getSymbols() { + return symbols; + } + + private static List> createQwertyLetterLayout() { + List> letters = new ArrayList<>(); + letters.add(Arrays.asList( + new KeyInfo("q"), new KeyInfo("w"), new KeyInfo("e"), new KeyInfo("r"), new KeyInfo("t"), + new KeyInfo("y"), new KeyInfo("u"), new KeyInfo("i"), new KeyInfo("o"), new KeyInfo("p") + )); + letters.add(Arrays.asList( + new KeyInfo("a"), new KeyInfo("s"), new KeyInfo("d"), new KeyInfo("f"), new KeyInfo("g"), + new KeyInfo("h"), new KeyInfo("j"), new KeyInfo("k"), new KeyInfo("l") + )); + letters.add(Arrays.asList( + new KeyInfo("z"), new KeyInfo("x"), new KeyInfo("c"), new KeyInfo("v"), + new KeyInfo("b"), new KeyInfo("n"), new KeyInfo("m") + )); + return letters; + } + + private static List> createSymbolLayout() { + List> symbols = new ArrayList<>(); + symbols.add(Arrays.asList( + new KeyInfo("1"), new KeyInfo("2"), new KeyInfo("3"), new KeyInfo("4"), new KeyInfo("5"), + new KeyInfo("6"), new KeyInfo("7"), new KeyInfo("8"), new KeyInfo("9"), new KeyInfo("0") + )); + symbols.add(Arrays.asList( + new KeyInfo("@"), new KeyInfo("#"), new KeyInfo("$"), new KeyInfo("%"), new KeyInfo("&"), + new KeyInfo("*"), new KeyInfo("-"), new KeyInfo("+"), new KeyInfo("("), new KeyInfo(")") + )); + symbols.add(Arrays.asList( + new KeyInfo("!"), new KeyInfo("\""), new KeyInfo("'"), new KeyInfo(":"), new KeyInfo(";"), + new KeyInfo(","), new KeyInfo("."), new KeyInfo("?"), new KeyInfo("/") + )); + return symbols; + } +} \ No newline at end of file diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java new file mode 100644 index 0000000..758089b --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java @@ -0,0 +1,308 @@ +package eu.midnightdust.midnightcontrols.client.gui.virtualkeyboard; + +import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.Text; +import org.thinkingstudio.obsidianui.Position; +import org.thinkingstudio.obsidianui.SpruceTexts; +import org.thinkingstudio.obsidianui.screen.SpruceScreen; +import org.thinkingstudio.obsidianui.widget.SpruceButtonWidget; +import org.thinkingstudio.obsidianui.widget.container.SpruceContainerWidget; +import org.thinkingstudio.obsidianui.widget.text.SpruceTextAreaWidget; + +import java.util.List; + +public class VirtualKeyboardScreen extends SpruceScreen { + + @FunctionalInterface + public interface CloseCallback { + void onClose(String text); + } + + private static final int STANDARD_KEY_WIDTH = 20; + public static final int SPECIAL_KEY_WIDTH = (int) (STANDARD_KEY_WIDTH * 1.5); + private static final int KEY_HEIGHT = 20; + private static final int HORIZONTAL_SPACING = 2; + private static final int VERTICAL_SPACING = 4; + private static final int CONTAINER_PADDING = 10; + + // Key symbols + private static final String BACKSPACE_SYMBOL = "\b"; + private static final String NEWLINE_SYMBOL = "\n"; + private static final String SPACE_SYMBOL = " "; + + private final StringBuilder buffer; + private final CloseCallback closeCallback; + private final KeyboardLayout layout; + private final boolean newLineSupport; + + private boolean capsMode; + private boolean symbolMode; + private SpruceTextAreaWidget bufferDisplayArea; + private SpruceContainerWidget keyboardContainer; + + public VirtualKeyboardScreen(String initialText, CloseCallback closeCallback, boolean newLineSupport) { + super(Text.literal("Virtual Keyboard")); + this.buffer = new StringBuilder(initialText); + this.closeCallback = closeCallback; + this.layout = KeyboardLayout.QWERTY; + this.capsMode = false; + this.symbolMode = false; + this.newLineSupport = newLineSupport; + } + + @Override + protected void init() { + super.init(); + + this.bufferDisplayArea = createBufferDisplayArea(); + this.addDrawableChild(this.bufferDisplayArea); + + rebuildKeyboard(); + + int doneButtonY = this.keyboardContainer.getY() + this.keyboardContainer.getHeight() + VERTICAL_SPACING * 2; + this.addDrawableChild( + new SpruceButtonWidget( + Position.of(this, this.width / 2 - 50, doneButtonY), + 100, + 20, + SpruceTexts.GUI_DONE, + btn -> this.close() + ) + ); + } + + @Override + public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { + this.renderBackground(drawContext, mouseX, mouseY, delta); + drawContext.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 15, 0xFFFFFF); + super.render(drawContext, mouseX, mouseY, delta); + } + + @Override + public boolean shouldPause() { + return false; + } + + @Override + public void close() { + super.close(); + if (this.closeCallback != null) { + this.closeCallback.onClose(this.buffer.toString()); + } + } + + private void rebuildKeyboard() { + if (this.keyboardContainer != null) { + this.remove(this.keyboardContainer); + } + + var keys = getActiveKeyLayout(); + var keyboardContainer = createKeyboardContainer(keys); + + addLetterRows(keyboardContainer, keys); + addFunctionKeys(keyboardContainer); + addBottomRow(keyboardContainer); + + this.keyboardContainer = keyboardContainer; + this.addDrawableChild(this.keyboardContainer); + } + + private SpruceContainerWidget createKeyboardContainer(List> layoutKeys) { + int containerWidth = this.width; + int totalKeyboardHeight = calculateKeyboardHeight(layoutKeys); + int keyboardY = this.bufferDisplayArea.getY() + this.bufferDisplayArea.getHeight() + VERTICAL_SPACING * 2; + + return new SpruceContainerWidget( + Position.of(0, keyboardY), + containerWidth, + totalKeyboardHeight + ); + } + + private SpruceTextAreaWidget createBufferDisplayArea() { + int lineCount = this.newLineSupport ? 3 : 1; + int bufferX = this.width / 2 - 100; + int bufferY = this.height / 4 - VERTICAL_SPACING * 5 - 5; + int bufferWidth = 200; + int desiredHeight = (this.textRenderer.fontHeight + 2) * lineCount + 6; + + var bufferDisplay = new SpruceTextAreaWidget( + Position.of(bufferX, bufferY), + bufferWidth, + desiredHeight, + Text.literal("Buffer Display") + ); + bufferDisplay.setText(this.buffer.toString()); + bufferDisplay.setEditable(false); + bufferDisplay.setUneditableColor(0xFFFFFFFF); + bufferDisplay.setDisplayedLines(lineCount); + bufferDisplay.setCursorToEnd(); + + return bufferDisplay; + } + + private int calculateKeyboardHeight(List> keyRows) { + return keyRows.size() * (KEY_HEIGHT + VERTICAL_SPACING) + + (KEY_HEIGHT + VERTICAL_SPACING) + // space for bottom row + CONTAINER_PADDING * 2; // top and bottom padding + } + + private void addLetterRows(SpruceContainerWidget container, List> keyRows) { + int currentY = CONTAINER_PADDING; + + for (List row : keyRows) { + int rowWidth = calculateRowWidth(row); + // center row + int currentX = (container.getWidth() - rowWidth) / 2; + + for (KeyInfo keyInfo : row) { + int keyWidth = (int) (STANDARD_KEY_WIDTH * keyInfo.widthFactor()); + String displayText = getKeyDisplayText(keyInfo); + container.addChild( + new SpruceButtonWidget( + Position.of(currentX, currentY), + keyWidth, + KEY_HEIGHT, + Text.literal(displayText), + btn -> handleKeyPress(displayText) + ) + ); + + currentX += keyWidth + HORIZONTAL_SPACING; + } + + currentY += KEY_HEIGHT + VERTICAL_SPACING; + } + } + + private int calculateRowWidth(List row) { + int rowWidth = 0; + for (int i = 0; i < row.size(); i++) { + rowWidth += (int) (STANDARD_KEY_WIDTH * row.get(i).widthFactor()); + if (i < row.size() - 1) { + rowWidth += HORIZONTAL_SPACING; + } + } + return rowWidth; + } + + private void addFunctionKeys(SpruceContainerWidget container) { + List firstRow = getActiveKeyLayout().get(0); + int firstRowWidth = calculateRowWidth(firstRow); + + // position backspace at the right of the first row + int backspaceWidth = (int) (STANDARD_KEY_WIDTH * 1.5); + int backspaceX = (container.getWidth() + firstRowWidth) / 2 + HORIZONTAL_SPACING; + + container.addChild( + new SpruceButtonWidget( + Position.of(backspaceX, CONTAINER_PADDING), + backspaceWidth, + KEY_HEIGHT, + Text.literal("←"), + btn -> handleKeyPress(BACKSPACE_SYMBOL) + ) + ); + + + if (this.newLineSupport) { + // position newline at the right of the second row + List secondRow = getActiveKeyLayout().get(1); + int newlineWidth = (int) (STANDARD_KEY_WIDTH * 1.5); + int secondRowWidth = calculateRowWidth(secondRow); + int newlineX = (container.getWidth() + secondRowWidth) / 2 + HORIZONTAL_SPACING; + int newlineY = CONTAINER_PADDING + (KEY_HEIGHT + VERTICAL_SPACING); + + container.addChild( + new SpruceButtonWidget( + Position.of(newlineX, newlineY), + newlineWidth, + KEY_HEIGHT, + Text.literal("⏎"), + btn -> handleKeyPress(NEWLINE_SYMBOL) + ) + ); + } + } + + private void addBottomRow(SpruceContainerWidget container) { + // calculate positions for bottom row + int rowY = CONTAINER_PADDING + getActiveKeyLayout().size() * (KEY_HEIGHT + VERTICAL_SPACING); + + // space bar - wide key in the middle + double spaceWidthFactor = 5.0; + int spaceKeyWidth = (int) (STANDARD_KEY_WIDTH * spaceWidthFactor); + int spaceX = (container.getWidth() - spaceKeyWidth) / 2; + + container.addChild( + new SpruceButtonWidget( + Position.of(spaceX, rowY), + spaceKeyWidth, + KEY_HEIGHT, + Text.literal("Space"), + btn -> handleKeyPress(SPACE_SYMBOL) + ) + ); + + // caps key - left of space + if (!this.symbolMode) { + int capsX = spaceX - SPECIAL_KEY_WIDTH - HORIZONTAL_SPACING * 2; + var capsModeButton = new SpruceButtonWidget( + Position.of(capsX, rowY), + SPECIAL_KEY_WIDTH, + KEY_HEIGHT, + Text.literal(this.capsMode ? "caps" : "CAPS"), + btn -> toggleCapsMode()); + + container.addChild(capsModeButton); + } + + // symbols key - right of space + int symbolsX = spaceX + spaceKeyWidth + HORIZONTAL_SPACING * 2; + var symbolModeButton = new SpruceButtonWidget( + Position.of(symbolsX, rowY), + SPECIAL_KEY_WIDTH, + KEY_HEIGHT, + Text.literal(this.symbolMode ? "ABC" : "123?!"), + btn -> toggleSymbolMode() + ); + container.addChild(symbolModeButton); + } + + private void handleKeyPress(String key) { + if (key.equals(BACKSPACE_SYMBOL)) { + if (!this.buffer.isEmpty()) { + this.buffer.deleteCharAt(buffer.length() - 1); + } + } else { + this.buffer.append(key); + } + + if (this.bufferDisplayArea != null) { + this.bufferDisplayArea.setText(this.buffer.toString()); + this.bufferDisplayArea.setCursorToEnd(); + } + } + + private String getKeyDisplayText(KeyInfo keyInfo) { + if(this.capsMode && !this.symbolMode) { + return keyInfo.displayText().toUpperCase(); + } + + return keyInfo.displayText(); + } + + private List> getActiveKeyLayout() { + return this.symbolMode ? this.layout.getSymbols() : this.layout.getLetters(); + } + + private void toggleCapsMode() { + this.capsMode = !this.capsMode; + rebuildKeyboard(); + } + + private void toggleSymbolMode() { + this.symbolMode = !this.symbolMode; + rebuildKeyboard(); + } +} \ No newline at end of file diff --git a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java index d8b3610..5980a85 100644 --- a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java +++ b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java @@ -1,10 +1,9 @@ package eu.midnightdust.midnightcontrols.fabric.event; -import eu.midnightdust.midnightcontrols.client.gui.VirtualKeyboardScreen; +import eu.midnightdust.midnightcontrols.client.gui.virtualkeyboard.VirtualKeyboardScreen; import eu.midnightdust.midnightcontrols.client.mixin.AbstractSignEditScreenAccessor; import eu.midnightdust.midnightcontrols.client.mixin.BookEditScreenAccessor; import eu.midnightdust.midnightcontrols.client.mixin.CreativeInventoryScreenAccessor; import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents; -import net.minecraft.block.entity.SignText; import net.minecraft.client.gui.Element; import net.minecraft.client.gui.ParentElement; import net.minecraft.client.gui.screen.ChatScreen; @@ -18,7 +17,6 @@ import org.lwjgl.glfw.GLFW; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import static eu.midnightdust.midnightcontrols.MidnightControls.logger; import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client; @@ -89,14 +87,14 @@ public class MouseClickListener implements ScreenMouseEvents.AllowMouseClick { virtualKeyboardScreen = new VirtualKeyboardScreen(accessor.midnightcontrols$getTitle(), (text) -> { client.setScreen(bookEditScreen); accessor.midnightcontrols$setTitle(text); - }); + }, true); } else { virtualKeyboardScreen = new VirtualKeyboardScreen(accessor.midnightcontrols$getCurrentPageContent(), (text) -> { client.setScreen(bookEditScreen); accessor.midnightcontrols$setPageContent(text); accessor.midnightcontrols$getCurrentPageSelectionManager().putCursorAtEnd(); - }); + }, true); } client.setScreen(virtualKeyboardScreen); @@ -109,7 +107,7 @@ public class MouseClickListener implements ScreenMouseEvents.AllowMouseClick { private void handleTextFieldClick(TextFieldWidget textField) { this.link = new ScreenLink(screen, calculatePathToElement(screen, textField)); - var virtualKeyboardScreen = new VirtualKeyboardScreen(textField.getText(), this::handleKeyboardClose); + var virtualKeyboardScreen = new VirtualKeyboardScreen(textField.getText(), this::handleKeyboardClose, false); client.setScreen(virtualKeyboardScreen); } @@ -126,7 +124,6 @@ public class MouseClickListener implements ScreenMouseEvents.AllowMouseClick { txtField.setText(newText); - switch (this.link.screen()) { case CreativeInventoryScreen creativeInventoryScreen -> { var accessor = (CreativeInventoryScreenAccessor) creativeInventoryScreen; From 041eeb29aaf801ed09590460f0ce1e54e3d041f6 Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:39:56 +0200 Subject: [PATCH 04/18] Make special key constant private --- .../client/gui/virtualkeyboard/VirtualKeyboardScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java index 758089b..00d5557 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java @@ -19,7 +19,7 @@ public class VirtualKeyboardScreen extends SpruceScreen { } private static final int STANDARD_KEY_WIDTH = 20; - public static final int SPECIAL_KEY_WIDTH = (int) (STANDARD_KEY_WIDTH * 1.5); + private static final int SPECIAL_KEY_WIDTH = (int) (STANDARD_KEY_WIDTH * 1.5); private static final int KEY_HEIGHT = 20; private static final int HORIZONTAL_SPACING = 2; private static final int VERTICAL_SPACING = 4; From bb5c6976c0d5cf4fd0cb5519731f3de8aabf2c93 Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:16:08 +0200 Subject: [PATCH 05/18] Replace KeyInfo with String - KeyInfo was leftover code from toying around with various implementations. String is sufficient in current implementation. --- .../client/gui/virtualkeyboard/KeyInfo.java | 16 ------- .../gui/virtualkeyboard/KeyboardLayout.java | 42 +++++++++---------- .../VirtualKeyboardScreen.java | 41 +++++++----------- 3 files changed, 36 insertions(+), 63 deletions(-) delete mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyInfo.java diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyInfo.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyInfo.java deleted file mode 100644 index 513a5e4..0000000 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyInfo.java +++ /dev/null @@ -1,16 +0,0 @@ -package eu.midnightdust.midnightcontrols.client.gui.virtualkeyboard; - -public record KeyInfo(String keySymbol, String displayText, double widthFactor) { - // Convenience constructor for standard width keys - KeyInfo(String keySymbol, double widthFactor) { - this(keySymbol, keySymbol, widthFactor); - } - - KeyInfo(String keySymbol) { - this(keySymbol, 1.0); - } - - KeyInfo(String keySymbol, String displayText) { - this(keySymbol, displayText, 1.0); - } -} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java index ccaa4d3..69732a6 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java @@ -8,52 +8,52 @@ public class KeyboardLayout { public static KeyboardLayout QWERTY = new KeyboardLayout(createQwertyLetterLayout(), createSymbolLayout()); - private final List> letters; - private final List> symbols; + private final List> letters; + private final List> symbols; - private KeyboardLayout(List> letters, List> symbols) { + private KeyboardLayout(List> letters, List> symbols) { this.letters = letters; this.symbols = symbols; } - public List> getLetters() { + public List> getLetters() { return letters; } - public List> getSymbols() { + public List> getSymbols() { return symbols; } - private static List> createQwertyLetterLayout() { - List> letters = new ArrayList<>(); + private static List> createQwertyLetterLayout() { + List> letters = new ArrayList<>(); letters.add(Arrays.asList( - new KeyInfo("q"), new KeyInfo("w"), new KeyInfo("e"), new KeyInfo("r"), new KeyInfo("t"), - new KeyInfo("y"), new KeyInfo("u"), new KeyInfo("i"), new KeyInfo("o"), new KeyInfo("p") + "q", "w", "e", "r", "t", + "y", "u", "i", "o", "p" )); letters.add(Arrays.asList( - new KeyInfo("a"), new KeyInfo("s"), new KeyInfo("d"), new KeyInfo("f"), new KeyInfo("g"), - new KeyInfo("h"), new KeyInfo("j"), new KeyInfo("k"), new KeyInfo("l") + "a", "s", "d", "f", "g", + "h", "j", "k", "l" )); letters.add(Arrays.asList( - new KeyInfo("z"), new KeyInfo("x"), new KeyInfo("c"), new KeyInfo("v"), - new KeyInfo("b"), new KeyInfo("n"), new KeyInfo("m") + "z", "x", "c", "v", + "b", "n", "m" )); return letters; } - private static List> createSymbolLayout() { - List> symbols = new ArrayList<>(); + private static List> createSymbolLayout() { + List> symbols = new ArrayList<>(); symbols.add(Arrays.asList( - new KeyInfo("1"), new KeyInfo("2"), new KeyInfo("3"), new KeyInfo("4"), new KeyInfo("5"), - new KeyInfo("6"), new KeyInfo("7"), new KeyInfo("8"), new KeyInfo("9"), new KeyInfo("0") + "1", "2", "3", "4", "5", + "6", "7", "8", "9", "0" )); symbols.add(Arrays.asList( - new KeyInfo("@"), new KeyInfo("#"), new KeyInfo("$"), new KeyInfo("%"), new KeyInfo("&"), - new KeyInfo("*"), new KeyInfo("-"), new KeyInfo("+"), new KeyInfo("("), new KeyInfo(")") + "@", "#", "$", "%", "&", + "*", "-", "+", "(", ")" )); symbols.add(Arrays.asList( - new KeyInfo("!"), new KeyInfo("\""), new KeyInfo("'"), new KeyInfo(":"), new KeyInfo(";"), - new KeyInfo(","), new KeyInfo("."), new KeyInfo("?"), new KeyInfo("/") + "!", "\"", "'", ":", ";", + ",", ".", "?", "/" )); return symbols; } diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java index 00d5557..9b1d036 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java @@ -74,7 +74,6 @@ public class VirtualKeyboardScreen extends SpruceScreen { @Override public void render(DrawContext drawContext, int mouseX, int mouseY, float delta) { this.renderBackground(drawContext, mouseX, mouseY, delta); - drawContext.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 15, 0xFFFFFF); super.render(drawContext, mouseX, mouseY, delta); } @@ -96,10 +95,10 @@ public class VirtualKeyboardScreen extends SpruceScreen { this.remove(this.keyboardContainer); } - var keys = getActiveKeyLayout(); - var keyboardContainer = createKeyboardContainer(keys); + var layoutKeys = getActiveKeyLayout(); + var keyboardContainer = createKeyboardContainer(layoutKeys); - addLetterRows(keyboardContainer, keys); + addLayoutRows(keyboardContainer, layoutKeys); addFunctionKeys(keyboardContainer); addBottomRow(keyboardContainer); @@ -107,7 +106,7 @@ public class VirtualKeyboardScreen extends SpruceScreen { this.addDrawableChild(this.keyboardContainer); } - private SpruceContainerWidget createKeyboardContainer(List> layoutKeys) { + private SpruceContainerWidget createKeyboardContainer(List> layoutKeys) { int containerWidth = this.width; int totalKeyboardHeight = calculateKeyboardHeight(layoutKeys); int keyboardY = this.bufferDisplayArea.getY() + this.bufferDisplayArea.getHeight() + VERTICAL_SPACING * 2; @@ -141,44 +140,42 @@ public class VirtualKeyboardScreen extends SpruceScreen { return bufferDisplay; } - private int calculateKeyboardHeight(List> keyRows) { + private int calculateKeyboardHeight(List> keyRows) { return keyRows.size() * (KEY_HEIGHT + VERTICAL_SPACING) + (KEY_HEIGHT + VERTICAL_SPACING) + // space for bottom row CONTAINER_PADDING * 2; // top and bottom padding } - private void addLetterRows(SpruceContainerWidget container, List> keyRows) { + private void addLayoutRows(SpruceContainerWidget container, List> keyLayoutRows) { int currentY = CONTAINER_PADDING; - for (List row : keyRows) { + for (List row : keyLayoutRows) { int rowWidth = calculateRowWidth(row); // center row int currentX = (container.getWidth() - rowWidth) / 2; - for (KeyInfo keyInfo : row) { - int keyWidth = (int) (STANDARD_KEY_WIDTH * keyInfo.widthFactor()); - String displayText = getKeyDisplayText(keyInfo); + for (String key : row) { + String displayText = (this.capsMode && !this.symbolMode) ? key.toUpperCase() : key; container.addChild( new SpruceButtonWidget( Position.of(currentX, currentY), - keyWidth, + STANDARD_KEY_WIDTH, KEY_HEIGHT, Text.literal(displayText), btn -> handleKeyPress(displayText) ) ); - currentX += keyWidth + HORIZONTAL_SPACING; + currentX += STANDARD_KEY_WIDTH + HORIZONTAL_SPACING; } currentY += KEY_HEIGHT + VERTICAL_SPACING; } } - private int calculateRowWidth(List row) { + private int calculateRowWidth(List row) { int rowWidth = 0; for (int i = 0; i < row.size(); i++) { - rowWidth += (int) (STANDARD_KEY_WIDTH * row.get(i).widthFactor()); if (i < row.size() - 1) { rowWidth += HORIZONTAL_SPACING; } @@ -187,7 +184,7 @@ public class VirtualKeyboardScreen extends SpruceScreen { } private void addFunctionKeys(SpruceContainerWidget container) { - List firstRow = getActiveKeyLayout().get(0); + List firstRow = getActiveKeyLayout().get(0); int firstRowWidth = calculateRowWidth(firstRow); // position backspace at the right of the first row @@ -207,7 +204,7 @@ public class VirtualKeyboardScreen extends SpruceScreen { if (this.newLineSupport) { // position newline at the right of the second row - List secondRow = getActiveKeyLayout().get(1); + List secondRow = getActiveKeyLayout().get(1); int newlineWidth = (int) (STANDARD_KEY_WIDTH * 1.5); int secondRowWidth = calculateRowWidth(secondRow); int newlineX = (container.getWidth() + secondRowWidth) / 2 + HORIZONTAL_SPACING; @@ -284,15 +281,7 @@ public class VirtualKeyboardScreen extends SpruceScreen { } } - private String getKeyDisplayText(KeyInfo keyInfo) { - if(this.capsMode && !this.symbolMode) { - return keyInfo.displayText().toUpperCase(); - } - - return keyInfo.displayText(); - } - - private List> getActiveKeyLayout() { + private List> getActiveKeyLayout() { return this.symbolMode ? this.layout.getSymbols() : this.layout.getLetters(); } From 9ebd1a9ceafa20284b3f77b273d7f8250535887e Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:17:25 +0200 Subject: [PATCH 06/18] Increase displayed lines for newline support --- .../client/gui/virtualkeyboard/VirtualKeyboardScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java index 9b1d036..0ed71ae 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java @@ -119,7 +119,7 @@ public class VirtualKeyboardScreen extends SpruceScreen { } private SpruceTextAreaWidget createBufferDisplayArea() { - int lineCount = this.newLineSupport ? 3 : 1; + int lineCount = this.newLineSupport ? 4 : 1; int bufferX = this.width / 2 - 100; int bufferY = this.height / 4 - VERTICAL_SPACING * 5 - 5; int bufferWidth = 200; From 0ef59057afc78204c1cf61c7de71c0f495ddf619 Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:21:02 +0200 Subject: [PATCH 07/18] Add missing row width calculation --- .../client/gui/virtualkeyboard/VirtualKeyboardScreen.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java index 0ed71ae..2f876a3 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java @@ -176,6 +176,8 @@ public class VirtualKeyboardScreen extends SpruceScreen { private int calculateRowWidth(List row) { int rowWidth = 0; for (int i = 0; i < row.size(); i++) { + rowWidth += STANDARD_KEY_WIDTH; + // padding if (i < row.size() - 1) { rowWidth += HORIZONTAL_SPACING; } From 70d923f959b04bde82e5b0581a5cb8e276b844ab Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 22:52:57 +0200 Subject: [PATCH 08/18] Implement NeoForge support --- .../client/MidnightControlsClient.java | 2 + .../virtualkeyboard/KeyboardLayout.java | 2 +- .../MouseClickInterceptor.java | 37 ++++ .../AbstractScreenClickHandler.java | 7 + .../BookEditScreenClickHandler.java | 36 ++++ .../DefaultScreenClickHandler.java | 137 +++++++++++++ .../clickhandler/SignEditScreenHandler.java | 16 ++ .../gui}/VirtualKeyboardScreen.java | 3 +- .../fabric/event/MouseClickListener.java | 182 +----------------- .../MidnightControlsClientNeoforge.java | 14 ++ 10 files changed, 254 insertions(+), 182 deletions(-) rename common/src/main/java/eu/midnightdust/midnightcontrols/client/{gui => }/virtualkeyboard/KeyboardLayout.java (95%) create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/MouseClickInterceptor.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/AbstractScreenClickHandler.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/BookEditScreenClickHandler.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/DefaultScreenClickHandler.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenHandler.java rename common/src/main/java/eu/midnightdust/midnightcontrols/client/{gui/virtualkeyboard => virtualkeyboard/gui}/VirtualKeyboardScreen.java (98%) diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsClient.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsClient.java index 074250f..0e36650 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsClient.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsClient.java @@ -26,6 +26,7 @@ import eu.midnightdust.midnightcontrols.client.mixin.KeyBindingIDAccessor; import eu.midnightdust.midnightcontrols.client.ring.ButtonBindingRingAction; import eu.midnightdust.midnightcontrols.client.ring.MidnightRing; import eu.midnightdust.midnightcontrols.client.util.platform.NetworkUtil; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.MouseClickInterceptor; import net.minecraft.client.gui.screen.Screen; import org.thinkingstudio.obsidianui.hud.HudManager; import eu.midnightdust.midnightcontrols.client.touch.TouchInput; @@ -75,6 +76,7 @@ public class MidnightControlsClient extends MidnightControls { public static final MidnightInput input = new MidnightInput(); public static final MidnightRing ring = new MidnightRing(); public static final MidnightReacharound reacharound = new MidnightReacharound(); + public static final MouseClickInterceptor clickInterceptor = new MouseClickInterceptor(); public static boolean isWayland; private static MidnightControlsHud hud; private static ControlsMode previousControlsMode; diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayout.java similarity index 95% rename from common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java rename to common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayout.java index 69732a6..82d9b0b 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/KeyboardLayout.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayout.java @@ -1,4 +1,4 @@ -package eu.midnightdust.midnightcontrols.client.gui.virtualkeyboard; +package eu.midnightdust.midnightcontrols.client.virtualkeyboard; import java.util.ArrayList; import java.util.Arrays; diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/MouseClickInterceptor.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/MouseClickInterceptor.java new file mode 100644 index 0000000..b4eb81f --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/MouseClickInterceptor.java @@ -0,0 +1,37 @@ +package eu.midnightdust.midnightcontrols.client.virtualkeyboard; + +import com.google.common.collect.ImmutableMap; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler.AbstractScreenClickHandler; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler.BookEditScreenClickHandler; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler.DefaultScreenClickHandler; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler.SignEditScreenHandler; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.BookEditScreen; +import net.minecraft.client.gui.screen.ingame.SignEditScreen; + +import java.util.HashMap; +import java.util.Map; + + +public class MouseClickInterceptor { + + private final Map, AbstractScreenClickHandler> clickHandlers; + + public MouseClickInterceptor() { + this.clickHandlers = new HashMap<>(); + this.clickHandlers.put(BookEditScreen.class, new BookEditScreenClickHandler()); + this.clickHandlers.put(SignEditScreen.class, new SignEditScreenHandler()); + this.clickHandlers.put(Screen.class, new DefaultScreenClickHandler()); + } + + @SuppressWarnings("unchecked") + public void intercept(T screen, double mouseX, double mouseY) { + AbstractScreenClickHandler handler = (AbstractScreenClickHandler) clickHandlers.get(screen.getClass()); + + if (handler == null) { + handler = (AbstractScreenClickHandler) clickHandlers.get(Screen.class); + } + + handler.handle(screen, mouseX, mouseY); + } +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/AbstractScreenClickHandler.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/AbstractScreenClickHandler.java new file mode 100644 index 0000000..f347f05 --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/AbstractScreenClickHandler.java @@ -0,0 +1,7 @@ +package eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler; + +import net.minecraft.client.gui.screen.Screen; + +public abstract class AbstractScreenClickHandler { + public abstract void handle(T screen, double mouseX, double mouseY); +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/BookEditScreenClickHandler.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/BookEditScreenClickHandler.java new file mode 100644 index 0000000..dbb1ebf --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/BookEditScreenClickHandler.java @@ -0,0 +1,36 @@ +package eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler; + +import eu.midnightdust.midnightcontrols.client.mixin.BookEditScreenAccessor; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.gui.VirtualKeyboardScreen; +import net.minecraft.client.gui.screen.ingame.BookEditScreen; + +import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client; + +public class BookEditScreenClickHandler extends AbstractScreenClickHandler { + @Override + public void handle(BookEditScreen screen, double mouseX, double mouseY) { + // don't open the keyboard if a UI element was clicked + if(screen.hoveredElement(mouseX, mouseY).isPresent()) { + return; + } + + var accessor = (BookEditScreenAccessor) screen; + + VirtualKeyboardScreen virtualKeyboardScreen; + if(accessor.midnightcontrols$isSigning()) { + virtualKeyboardScreen = new VirtualKeyboardScreen(accessor.midnightcontrols$getTitle(), (text) -> { + client.setScreen(screen); + accessor.midnightcontrols$setTitle(text); + }, true); + } + else { + virtualKeyboardScreen = new VirtualKeyboardScreen(accessor.midnightcontrols$getCurrentPageContent(), (text) -> { + client.setScreen(screen); + accessor.midnightcontrols$setPageContent(text); + accessor.midnightcontrols$getCurrentPageSelectionManager().putCursorAtEnd(); + }, true); + } + + client.setScreen(virtualKeyboardScreen); + } +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/DefaultScreenClickHandler.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/DefaultScreenClickHandler.java new file mode 100644 index 0000000..647fbb7 --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/DefaultScreenClickHandler.java @@ -0,0 +1,137 @@ +package eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler; + +import eu.midnightdust.midnightcontrols.client.mixin.CreativeInventoryScreenAccessor; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.gui.VirtualKeyboardScreen; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.ParentElement; +import net.minecraft.client.gui.screen.ChatScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.client.gui.widget.TextFieldWidget; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client; + +public class DefaultScreenClickHandler extends AbstractScreenClickHandler { + + private Screen parentScreen; + private List textFieldElementPath; + + @Override + public void handle(Screen screen, double mouseX, double mouseY) { + var textField = findClickedTextField(screen, mouseX, mouseY); + if (textField == null) { + return; + } + + this.parentScreen = screen; + this.textFieldElementPath = calculatePathToElement(screen, textField); + + var virtualKeyboardScreen = new VirtualKeyboardScreen(textField.getText(), this::handleKeyboardClose, false); + client.setScreen(virtualKeyboardScreen); + } + + private void handleKeyboardClose(String newText) { + if(this.parentScreen == null || this.textFieldElementPath == null) { + return; + } + + client.setScreen(this.parentScreen); + TextFieldWidget textField = findTextFieldByPath(this.parentScreen, this.textFieldElementPath); + if (textField == null) { + return; + } + + textField.setText(newText); + + switch (this.parentScreen) { + case CreativeInventoryScreen creativeInventoryScreen -> { + var accessor = (CreativeInventoryScreenAccessor) creativeInventoryScreen; + accessor.midnightcontrols$search(); + } + case ChatScreen chatScreen -> { + // send the chat message + chatScreen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0); + } + default -> {} + } + } + + private TextFieldWidget findClickedTextField(Screen screen, double mouseX, double mouseY) { + for (Element element : screen.children()) { + if (element instanceof TextFieldWidget textField) { + if (textField.isMouseOver(mouseX, mouseY) && textField.isFocused()) { + return textField; + } + } + } + + // not hovering over a text field + return null; + } + + /** + * Calculates the path between a parent and a target in the UI hierarchy + */ + protected List calculatePathToElement(Element parent, Element target) { + if (parent instanceof ParentElement parentElement) { + List children = parentElement.children(); + + // check direct children first + for (int i = 0; i < children.size(); i++) { + if (children.get(i) == target) { + // found it, return the path to this element + return Collections.singletonList(i); + } + } + + // check each child's children + for (int i = 0; i < children.size(); i++) { + if (children.get(i) instanceof ParentElement childParent) { + List subPath = calculatePathToElement(childParent, target); + if (subPath != null) { + // found in this subtree, prepend current index + List fullPath = new ArrayList<>(); + fullPath.add(i); + fullPath.addAll(subPath); + return fullPath; + } + } + } + } + + // Not found + return null; + } + + protected TextFieldWidget findTextFieldByPath(Element parent, List path) { + if (path == null || path.isEmpty()) { + return null; + } + + if (parent instanceof ParentElement parentElement) { + List children = parentElement.children(); + int index = path.getFirst(); + + if (index >= 0 && index < children.size()) { + Element child = children.get(index); + + if (path.size() == 1) { + // This should be our target + return (child instanceof TextFieldWidget) ? (TextFieldWidget) child : null; + } else { + // Continue traversing + if (child instanceof ParentElement) { + return findTextFieldByPath(child, path.subList(1, path.size())); + } + } + } + } + + return null; + } +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenHandler.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenHandler.java new file mode 100644 index 0000000..405272f --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenHandler.java @@ -0,0 +1,16 @@ +package eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler; + +import eu.midnightdust.midnightcontrols.client.mixin.AbstractSignEditScreenAccessor; +import net.minecraft.client.gui.screen.ingame.SignEditScreen; + +public class SignEditScreenHandler extends AbstractScreenClickHandler { + @Override + public void handle(SignEditScreen screen, double mouseX, double mouseY) { + // don't open the keyboard if a UI element was clicked + if(screen.hoveredElement(mouseX, mouseY).isPresent()) { + return; + } + + var accessor = (AbstractSignEditScreenAccessor) screen; + } +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/gui/VirtualKeyboardScreen.java similarity index 98% rename from common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java rename to common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/gui/VirtualKeyboardScreen.java index 2f876a3..bb26353 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/virtualkeyboard/VirtualKeyboardScreen.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/gui/VirtualKeyboardScreen.java @@ -1,5 +1,6 @@ -package eu.midnightdust.midnightcontrols.client.gui.virtualkeyboard; +package eu.midnightdust.midnightcontrols.client.virtualkeyboard.gui; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.KeyboardLayout; import net.minecraft.client.gui.DrawContext; import net.minecraft.text.Text; import org.thinkingstudio.obsidianui.Position; diff --git a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java index 5980a85..53b03a9 100644 --- a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java +++ b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java @@ -1,68 +1,22 @@ package eu.midnightdust.midnightcontrols.fabric.event; -import eu.midnightdust.midnightcontrols.client.gui.virtualkeyboard.VirtualKeyboardScreen; -import eu.midnightdust.midnightcontrols.client.mixin.AbstractSignEditScreenAccessor; -import eu.midnightdust.midnightcontrols.client.mixin.BookEditScreenAccessor; -import eu.midnightdust.midnightcontrols.client.mixin.CreativeInventoryScreenAccessor; import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents; -import net.minecraft.client.gui.Element; -import net.minecraft.client.gui.ParentElement; -import net.minecraft.client.gui.screen.ChatScreen; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.ingame.BookEditScreen; -import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; -import net.minecraft.client.gui.screen.ingame.SignEditScreen; -import net.minecraft.client.gui.widget.TextFieldWidget; -import org.lwjgl.glfw.GLFW; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static eu.midnightdust.midnightcontrols.MidnightControls.logger; -import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client; - -record ScreenLink(Screen screen, List elementPath) {} +import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.clickInterceptor; public class MouseClickListener implements ScreenMouseEvents.AllowMouseClick { private final Screen screen; - private ScreenLink link; - public MouseClickListener(Screen screen) { this.screen = screen; } @Override public boolean allowMouseClick(Screen screen, double mouseX, double mouseY, int button) { - interceptMouseClick(screen, mouseX, mouseY); + clickInterceptor.intercept(screen, mouseX, mouseY); return true; } - private void interceptMouseClick(Screen screen, double mouseX, double mouseY) { - logger.info("In scr: {}", screen.getClass()); - switch(screen) { - case BookEditScreen bookEditScreen -> { - if(screen.hoveredElement(mouseX, mouseY).isPresent()) { - return; - } - handleBookEditScreenClick(bookEditScreen); - } - case SignEditScreen signEditScreen -> { - if(screen.hoveredElement(mouseX, mouseY).isPresent()) { - return; - } - handleSignEditScreenClick(signEditScreen); - } - default -> { - var textField = findClickedTextField(screen, mouseX, mouseY); - if (textField == null) { - return; - } - handleTextFieldClick(textField); - } - } - } - // Add equals and hashCode to prevent duplicate registrations @Override public boolean equals(Object obj) { @@ -77,139 +31,7 @@ public class MouseClickListener implements ScreenMouseEvents.AllowMouseClick { return screen.hashCode(); } - // handlers - private void handleBookEditScreenClick(BookEditScreen bookEditScreen) { - var accessor = (BookEditScreenAccessor) screen; - VirtualKeyboardScreen virtualKeyboardScreen; - if(accessor.midnightcontrols$isSigning()) { - virtualKeyboardScreen = new VirtualKeyboardScreen(accessor.midnightcontrols$getTitle(), (text) -> { - client.setScreen(bookEditScreen); - accessor.midnightcontrols$setTitle(text); - }, true); - } - else { - virtualKeyboardScreen = new VirtualKeyboardScreen(accessor.midnightcontrols$getCurrentPageContent(), (text) -> { - client.setScreen(bookEditScreen); - accessor.midnightcontrols$setPageContent(text); - accessor.midnightcontrols$getCurrentPageSelectionManager().putCursorAtEnd(); - }, true); - } - client.setScreen(virtualKeyboardScreen); - } - - private void handleSignEditScreenClick(SignEditScreen signEditScreen) { - var accessor = (AbstractSignEditScreenAccessor) signEditScreen; - // TODO - } - - private void handleTextFieldClick(TextFieldWidget textField) { - this.link = new ScreenLink(screen, calculatePathToElement(screen, textField)); - var virtualKeyboardScreen = new VirtualKeyboardScreen(textField.getText(), this::handleKeyboardClose, false); - client.setScreen(virtualKeyboardScreen); - } - - private void handleKeyboardClose(String newText) { - if(this.link == null) { - return; - } - - client.setScreen(this.link.screen()); - var txtField = findTextFieldByPath(screen, this.link.elementPath()); - if (txtField == null) { - return; - } - - txtField.setText(newText); - - switch (this.link.screen()) { - case CreativeInventoryScreen creativeInventoryScreen -> { - var accessor = (CreativeInventoryScreenAccessor) creativeInventoryScreen; - accessor.midnightcontrols$search(); - } - case ChatScreen chatScreen -> { - // send the chat message - chatScreen.keyPressed(GLFW.GLFW_KEY_ENTER, 0, 0); - } - default -> {} - } - } - - // utility - - private TextFieldWidget findClickedTextField(Screen screen, double mouseX, double mouseY) { - for (Element element : screen.children()) { - if (element instanceof TextFieldWidget textField) { - if (textField.isMouseOver(mouseX, mouseY) && textField.isFocused()) { - return textField; - } - } - } - - // not hovering over a text field - return null; - } - - /** - * Calculates the path between a parent and a target in the UI hierarchy - */ - private List calculatePathToElement(Element parent, Element target) { - if (parent instanceof ParentElement parentElement) { - List children = parentElement.children(); - - // check direct children first - for (int i = 0; i < children.size(); i++) { - if (children.get(i) == target) { - // found it, return the path to this element - return Collections.singletonList(i); - } - } - - // check each child's children - for (int i = 0; i < children.size(); i++) { - if (children.get(i) instanceof ParentElement childParent) { - List subPath = calculatePathToElement(childParent, target); - if (subPath != null) { - // found in this subtree, prepend current index - List fullPath = new ArrayList<>(); - fullPath.add(i); - fullPath.addAll(subPath); - return fullPath; - } - } - } - } - - // Not found - return null; - } - - private TextFieldWidget findTextFieldByPath(Element parent, List path) { - if (path == null || path.isEmpty()) { - return null; - } - - if (parent instanceof ParentElement parentElement) { - List children = parentElement.children(); - int index = path.getFirst(); - - if (index >= 0 && index < children.size()) { - Element child = children.get(index); - - if (path.size() == 1) { - // This should be our target - return (child instanceof TextFieldWidget) ? (TextFieldWidget) child : null; - } else { - // Continue traversing - if (child instanceof ParentElement) { - return findTextFieldByPath(child, path.subList(1, path.size())); - } - } - } - } - - return null; - } } 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 9bd1e20..581e2a6 100644 --- a/neoforge/src/main/java/eu/midnightdust/midnightcontrols/neoforge/MidnightControlsClientNeoforge.java +++ b/neoforge/src/main/java/eu/midnightdust/midnightcontrols/neoforge/MidnightControlsClientNeoforge.java @@ -5,6 +5,7 @@ import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig; import eu.midnightdust.midnightcontrols.client.util.platform.NetworkUtil; import eu.midnightdust.midnightcontrols.packet.ControlsModePayload; import eu.midnightdust.midnightcontrols.packet.HelloPayload; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.resource.DirectoryResourcePack; import net.minecraft.resource.ResourcePackInfo; import net.minecraft.resource.ResourcePackPosition; @@ -21,6 +22,7 @@ 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.event.AddPackFindersEvent; import net.neoforged.neoforgespi.locating.IModFile; @@ -34,6 +36,8 @@ import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.BIN import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.BINDING_LOOK_UP; import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.BINDING_RING; import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client; +import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.clickInterceptor; + @Mod(value = NAMESPACE, dist = Dist.CLIENT) public class MidnightControlsClientNeoforge { @@ -90,5 +94,15 @@ public class MidnightControlsClientNeoforge { public static void startClientTick(ClientTickEvent.Pre event) { MidnightControlsClient.onTick(client); } + @SubscribeEvent + public static void onMouseButtonPressed(ScreenEvent.MouseButtonPressed.Pre event) { + if (!event.isCanceled()) { + Screen screen = event.getScreen(); + double mouseX = event.getMouseX(); + double mouseY = event.getMouseY(); + + clickInterceptor.intercept(screen, mouseX, mouseY); + } + } } } From 73c5fe1a82ecc48c147dbc85bed49b9317751929 Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:03:07 +0200 Subject: [PATCH 09/18] Add virtual keyboard config option --- .../midnightcontrols/client/MidnightControlsConfig.java | 2 ++ .../client/gui/MidnightControlsSettingsScreen.java | 4 ++++ .../main/resources/assets/midnightcontrols/lang/en_us.json | 2 ++ .../midnightcontrols/fabric/event/MouseClickListener.java | 5 ++++- .../neoforge/MidnightControlsClientNeoforge.java | 2 +- 5 files changed, 13 insertions(+), 2 deletions(-) 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 894cff2..b693621 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java @@ -88,6 +88,7 @@ public class MidnightControlsConfig extends MidnightConfig { @Entry(category = SCREENS, name = "midnightcontrols.menu.virtual_mouse") public static boolean virtualMouse = false; @Entry(category = SCREENS, name = "midnightcontrols.menu.virtual_mouse.skin") public static VirtualMouseSkin virtualMouseSkin = VirtualMouseSkin.DEFAULT_LIGHT; @Entry(category = SCREENS, name = "midnightcontrols.menu.hide_cursor") public static boolean hideNormalMouse = false; + @Entry(category = SCREENS, name = "midnightcontrols.menu.virtual_keyboard") public static boolean virtualKeyboard = false; @Entry(category = CONTROLLER, name = "Controller ID") @Hidden public static Object controllerID = 0; @Entry(category = CONTROLLER, name = "2nd Controller ID") @Hidden public static Object secondControllerID = -1; @Entry(category = VISUAL, name = "midnightcontrols.menu.controller_type") public static ControllerType controllerType = ControllerType.DEFAULT; @@ -374,6 +375,7 @@ public class MidnightControlsConfig extends MidnightConfig { unfocusedInput = false; virtualMouse = false; virtualMouseSkin = VirtualMouseSkin.DEFAULT_LIGHT; + virtualKeyboard = false; controllerID = 0; secondControllerID = -1; controllerType = ControllerType.DEFAULT; diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java index 3df0cff..0701104 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java @@ -64,6 +64,7 @@ public class MidnightControlsSettingsScreen extends SpruceScreen { private final SpruceOption eyeTrackingAsMouseOption; private final SpruceOption eyeTrackingDeadzone; private final SpruceOption virtualMouseOption; + private final SpruceOption virtualKeyboardOption; private final SpruceOption hideCursorOption; private final SpruceOption resetOption; private final SpruceOption advancedConfigOption; @@ -299,6 +300,8 @@ public class MidnightControlsSettingsScreen extends SpruceScreen { value -> MidnightControlsConfig.unfocusedInput = value, Text.translatable("midnightcontrols.menu.unfocused_input.tooltip")); this.virtualMouseOption = new SpruceToggleBooleanOption("midnightcontrols.menu.virtual_mouse", () -> MidnightControlsConfig.virtualMouse, value -> MidnightControlsConfig.virtualMouse = value, Text.translatable("midnightcontrols.menu.virtual_mouse.tooltip")); + this.virtualKeyboardOption = new SpruceToggleBooleanOption("midnightcontrols.menu.virtual_keyboard", () -> MidnightControlsConfig.virtualMouse, + value -> MidnightControlsConfig.virtualMouse = value, Text.translatable("midnightcontrols.menu.virtual_keyboard.tooltip")); this.hideCursorOption = new SpruceToggleBooleanOption("midnightcontrols.menu.hide_cursor", () -> MidnightControlsConfig.hideNormalMouse, value -> MidnightControlsConfig.hideNormalMouse = value, Text.translatable("midnightcontrols.menu.hide_cursor.tooltip")); // Touch options @@ -390,6 +393,7 @@ public class MidnightControlsSettingsScreen extends SpruceScreen { list.addSingleOptionEntry(this.yAxisRotationSpeedOption); list.addSingleOptionEntry(this.mouseSpeedOption); list.addSingleOptionEntry(this.virtualMouseOption); + list.addSingleOptionEntry(this.virtualKeyboardOption); list.addSingleOptionEntry(this.hideCursorOption); list.addSingleOptionEntry(this.joystickAsMouseOption); list.addSingleOptionEntry(this.eyeTrackingAsMouseOption); 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 99ff087..e1b4f00 100644 --- a/common/src/main/resources/assets/midnightcontrols/lang/en_us.json +++ b/common/src/main/resources/assets/midnightcontrols/lang/en_us.json @@ -220,6 +220,8 @@ "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", diff --git a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java index 53b03a9..d1e22e8 100644 --- a/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java +++ b/fabric/src/main/java/eu/midnightdust/midnightcontrols/fabric/event/MouseClickListener.java @@ -1,4 +1,5 @@ package eu.midnightdust.midnightcontrols.fabric.event; +import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig; import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents; import net.minecraft.client.gui.screen.Screen; @@ -13,7 +14,9 @@ public class MouseClickListener implements ScreenMouseEvents.AllowMouseClick { @Override public boolean allowMouseClick(Screen screen, double mouseX, double mouseY, int button) { - clickInterceptor.intercept(screen, mouseX, mouseY); + if(MidnightControlsConfig.virtualKeyboard) { + clickInterceptor.intercept(screen, mouseX, mouseY); + } return true; } 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 581e2a6..b98e286 100644 --- a/neoforge/src/main/java/eu/midnightdust/midnightcontrols/neoforge/MidnightControlsClientNeoforge.java +++ b/neoforge/src/main/java/eu/midnightdust/midnightcontrols/neoforge/MidnightControlsClientNeoforge.java @@ -96,7 +96,7 @@ public class MidnightControlsClientNeoforge { } @SubscribeEvent public static void onMouseButtonPressed(ScreenEvent.MouseButtonPressed.Pre event) { - if (!event.isCanceled()) { + if (MidnightControlsConfig.virtualKeyboard && !event.isCanceled()) { Screen screen = event.getScreen(); double mouseX = event.getMouseX(); double mouseY = event.getMouseY(); From 86622962f8114e512c588d6b1fc55004b9515b70 Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:05:02 +0200 Subject: [PATCH 10/18] Fix tooltip entry in en_us locale --- .../src/main/resources/assets/midnightcontrols/lang/en_us.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e1b4f00..dfe2b56 100644 --- a/common/src/main/resources/assets/midnightcontrols/lang/en_us.json +++ b/common/src/main/resources/assets/midnightcontrols/lang/en_us.json @@ -221,7 +221,7 @@ "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.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", From 33845e111bf7d416cfa7373a5f4310fead8976c7 Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:08:48 +0200 Subject: [PATCH 11/18] Fix handler naming --- .../client/virtualkeyboard/MouseClickInterceptor.java | 5 ++--- ...ditScreenHandler.java => SignEditScreenClickHandler.java} | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) rename common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/{SignEditScreenHandler.java => SignEditScreenClickHandler.java} (85%) diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/MouseClickInterceptor.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/MouseClickInterceptor.java index b4eb81f..85441c2 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/MouseClickInterceptor.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/MouseClickInterceptor.java @@ -1,10 +1,9 @@ package eu.midnightdust.midnightcontrols.client.virtualkeyboard; -import com.google.common.collect.ImmutableMap; import eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler.AbstractScreenClickHandler; import eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler.BookEditScreenClickHandler; import eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler.DefaultScreenClickHandler; -import eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler.SignEditScreenHandler; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler.SignEditScreenClickHandler; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.BookEditScreen; import net.minecraft.client.gui.screen.ingame.SignEditScreen; @@ -20,7 +19,7 @@ public class MouseClickInterceptor { public MouseClickInterceptor() { this.clickHandlers = new HashMap<>(); this.clickHandlers.put(BookEditScreen.class, new BookEditScreenClickHandler()); - this.clickHandlers.put(SignEditScreen.class, new SignEditScreenHandler()); + this.clickHandlers.put(SignEditScreen.class, new SignEditScreenClickHandler()); this.clickHandlers.put(Screen.class, new DefaultScreenClickHandler()); } diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenHandler.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenClickHandler.java similarity index 85% rename from common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenHandler.java rename to common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenClickHandler.java index 405272f..ced8546 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenHandler.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenClickHandler.java @@ -3,7 +3,7 @@ package eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler; import eu.midnightdust.midnightcontrols.client.mixin.AbstractSignEditScreenAccessor; import net.minecraft.client.gui.screen.ingame.SignEditScreen; -public class SignEditScreenHandler extends AbstractScreenClickHandler { +public class SignEditScreenClickHandler extends AbstractScreenClickHandler { @Override public void handle(SignEditScreen screen, double mouseX, double mouseY) { // don't open the keyboard if a UI element was clicked From 24e58027b22e2fd88c1fc8539df8bafbac522a1d Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:57:31 +0200 Subject: [PATCH 12/18] Add obisidianUI text field interop --- .../DefaultScreenClickHandler.java | 99 ++++++++++--------- .../clickhandler/TextFieldWrapper.java | 77 +++++++++++++++ 2 files changed, 130 insertions(+), 46 deletions(-) create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/TextFieldWrapper.java diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/DefaultScreenClickHandler.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/DefaultScreenClickHandler.java index 647fbb7..78842e2 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/DefaultScreenClickHandler.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/DefaultScreenClickHandler.java @@ -7,7 +7,6 @@ import net.minecraft.client.gui.ParentElement; import net.minecraft.client.gui.screen.ChatScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; -import net.minecraft.client.gui.widget.TextFieldWidget; import org.lwjgl.glfw.GLFW; import java.util.ArrayList; @@ -23,25 +22,25 @@ public class DefaultScreenClickHandler extends AbstractScreenClickHandler {} + default -> { + } } } - private TextFieldWidget findClickedTextField(Screen screen, double mouseX, double mouseY) { - for (Element element : screen.children()) { - if (element instanceof TextFieldWidget textField) { + + private TextFieldWrapper findClickedTextField(List elements, double mouseX, double mouseY) { + for (Element element : elements) { + if (TextFieldWrapper.isValidTextField(element)) { + TextFieldWrapper textField = new TextFieldWrapper(element); if (textField.isMouseOver(mouseX, mouseY) && textField.isFocused()) { return textField; } } + + if (element instanceof ParentElement parentElement) { + TextFieldWrapper found = findClickedTextField(parentElement.children(), mouseX, mouseY); + if (found != null) { + return found; + } + } } - // not hovering over a text field return null; } @@ -78,58 +86,57 @@ public class DefaultScreenClickHandler extends AbstractScreenClickHandler calculatePathToElement(Element parent, Element target) { - if (parent instanceof ParentElement parentElement) { - List children = parentElement.children(); + if (!(parent instanceof ParentElement parentElement)) { + return null; + } - // check direct children first - for (int i = 0; i < children.size(); i++) { - if (children.get(i) == target) { - // found it, return the path to this element - return Collections.singletonList(i); - } + List children = parentElement.children(); + + for (int i = 0; i < children.size(); i++) { + Element child = children.get(i); + + if (child == target) { + return Collections.singletonList(i); } - // check each child's children - for (int i = 0; i < children.size(); i++) { - if (children.get(i) instanceof ParentElement childParent) { - List subPath = calculatePathToElement(childParent, target); - if (subPath != null) { - // found in this subtree, prepend current index - List fullPath = new ArrayList<>(); - fullPath.add(i); - fullPath.addAll(subPath); - return fullPath; - } + if (child instanceof ParentElement) { + List subPath = calculatePathToElement(child, target); + if (subPath != null) { + List fullPath = new ArrayList<>(subPath.size() + 1); + fullPath.add(i); + fullPath.addAll(subPath); + return fullPath; } } } - // Not found return null; } - protected TextFieldWidget findTextFieldByPath(Element parent, List path) { + protected TextFieldWrapper findTextFieldByPath(Element parent, List path) { if (path == null || path.isEmpty()) { return null; } - if (parent instanceof ParentElement parentElement) { - List children = parentElement.children(); - int index = path.getFirst(); + if (!(parent instanceof ParentElement parentElement)) { + return null; + } - if (index >= 0 && index < children.size()) { - Element child = children.get(index); + List children = parentElement.children(); + int index = path.get(0); - if (path.size() == 1) { - // This should be our target - return (child instanceof TextFieldWidget) ? (TextFieldWidget) child : null; - } else { - // Continue traversing - if (child instanceof ParentElement) { - return findTextFieldByPath(child, path.subList(1, path.size())); - } - } - } + if (index < 0 || index >= children.size()) { + return null; + } + + Element child = children.get(index); + + if (path.size() == 1) { + return TextFieldWrapper.isValidTextField(child) ? new TextFieldWrapper(child) : null; + } + + if (child instanceof ParentElement) { + return findTextFieldByPath(child, path.subList(1, path.size())); } return null; diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/TextFieldWrapper.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/TextFieldWrapper.java new file mode 100644 index 0000000..f1190cb --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/TextFieldWrapper.java @@ -0,0 +1,77 @@ +package eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler; + +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.widget.TextFieldWidget; +import org.thinkingstudio.obsidianui.widget.text.SpruceTextFieldWidget; + +public record TextFieldWrapper(Object textField) { + + public TextFieldWrapper { + if (!isValidTextField(textField)) { + throw new IllegalArgumentException("Type " + textField.getClass() + " is not marked as a valid text field"); + } + } + + Element asElement() { + return (Element) textField; + } + + String getText() { + switch (textField) { + case SpruceTextFieldWidget spruceTextField -> { + return spruceTextField.getText(); + } + case TextFieldWidget vanillaTextField -> { + return vanillaTextField.getText(); + } + default -> { + return null; + } + } + } + + void setText(String text) { + switch (textField) { + case SpruceTextFieldWidget spruceTextField -> { + spruceTextField.setText(text); + } + case TextFieldWidget vanillaTextField -> { + vanillaTextField.setText(text); + } + default -> { + } + } + } + + boolean isMouseOver(double mouseX, double mouseY) { + switch (textField) { + case SpruceTextFieldWidget spruceTextField -> { + return spruceTextField.isMouseOver(mouseX, mouseY); + } + case TextFieldWidget vanillaTextField -> { + return vanillaTextField.isMouseOver(mouseX, mouseY); + } + default -> { + return false; + } + } + } + + boolean isFocused() { + switch (textField) { + case SpruceTextFieldWidget spruceTextField -> { + return spruceTextField.isFocused(); + } + case TextFieldWidget vanillaTextField -> { + return vanillaTextField.isFocused(); + } + default -> { + return false; + } + } + } + + static boolean isValidTextField(Object textField) { + return textField instanceof TextFieldWidget || textField instanceof SpruceTextFieldWidget; + } +} From 1231c231a9ffe10e6d0041edc566962933ac816c Mon Sep 17 00:00:00 2001 From: cryy <19242445+cryy@users.noreply.github.com> Date: Tue, 22 Apr 2025 23:57:53 +0200 Subject: [PATCH 13/18] Fix virtual keyboard config --- .../client/gui/MidnightControlsSettingsScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java index 0701104..40c7928 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java @@ -301,7 +301,7 @@ public class MidnightControlsSettingsScreen extends SpruceScreen { this.virtualMouseOption = new SpruceToggleBooleanOption("midnightcontrols.menu.virtual_mouse", () -> MidnightControlsConfig.virtualMouse, value -> MidnightControlsConfig.virtualMouse = value, Text.translatable("midnightcontrols.menu.virtual_mouse.tooltip")); this.virtualKeyboardOption = new SpruceToggleBooleanOption("midnightcontrols.menu.virtual_keyboard", () -> MidnightControlsConfig.virtualMouse, - value -> MidnightControlsConfig.virtualMouse = value, Text.translatable("midnightcontrols.menu.virtual_keyboard.tooltip")); + value -> MidnightControlsConfig.virtualKeyboard = value, Text.translatable("midnightcontrols.menu.virtual_keyboard.tooltip")); this.hideCursorOption = new SpruceToggleBooleanOption("midnightcontrols.menu.hide_cursor", () -> MidnightControlsConfig.hideNormalMouse, value -> MidnightControlsConfig.hideNormalMouse = value, Text.translatable("midnightcontrols.menu.hide_cursor.tooltip")); // Touch options From ecb7cfd8885dc293aceeefc25ca944e179200cae Mon Sep 17 00:00:00 2001 From: Martin Prokoph Date: Sat, 10 May 2025 10:38:54 +0200 Subject: [PATCH 14/18] feat: load virtual keyboard layouts from JSON --- .../virtualkeyboard/KeyboardLayout.java | 47 ++++++++++++++++++- .../keyboard-layouts/de_quertz.json | 16 +++++++ .../keyboard-layouts/en_querty.json | 16 +++++++ 3 files changed, 77 insertions(+), 2 deletions(-) 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/virtualkeyboard/KeyboardLayout.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayout.java index 82d9b0b..1c07fe7 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 @@ -1,21 +1,64 @@ package eu.midnightdust.midnightcontrols.client.virtualkeyboard; +import com.google.gson.JsonObject; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class KeyboardLayout { - public static KeyboardLayout QWERTY = new KeyboardLayout(createQwertyLetterLayout(), createSymbolLayout()); + public static KeyboardLayout QWERTY = new KeyboardLayout("US (Qwerty)", "en-US", createQwertyLetterLayout(), createSymbolLayout()); + public static final List KEYBOARD_LAYOUTS = new ArrayList<>(); + private final String name; + private final String locale; private final List> letters; private final List> symbols; - private KeyboardLayout(List> letters, List> symbols) { + private KeyboardLayout(String name, String locale, List> letters, List> symbols) { + this.name = name; + this.locale = locale; this.letters = letters; this.symbols = symbols; } + public 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)); + } catch (Exception e) { + throw new RuntimeException("Error loading keyboard definition: %s".formatted(e)); + } + } + public 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; + var rowJson = lettersJson.get("row%s".formatted(i)).getAsJsonArray(); + List row = new ArrayList<>(); + for (int j = 0; j < rowJson.size(); j++) { + row.add(rowJson.get(j).getAsString()); + } + arr.add(row); + } + return arr; + } + else { + return letters ? createQwertyLetterLayout() : createSymbolLayout(); + } + } + + public String getName() { + return name; + } + + public String getLocale() { + return locale; + } + public List> getLetters() { return letters; } 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..4a58ed1 --- /dev/null +++ b/common/src/main/resources/assets/midnightcontrols/keyboard-layouts/de_quertz.json @@ -0,0 +1,16 @@ +{ + "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 new file mode 100644 index 0000000..e7c5a2f --- /dev/null +++ b/common/src/main/resources/assets/midnightcontrols/keyboard-layouts/en_querty.json @@ -0,0 +1,16 @@ +{ + "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": ["!", "\"", "'", ":", ";", ",", ".", "?", "/"] + } +} From 0dfd1994dcaa17a187660b257628851f88ec1c00 Mon Sep 17 00:00:00 2001 From: Martin Prokoph Date: Mon, 19 May 2025 16:20:46 +0200 Subject: [PATCH 15/18] 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) From 7b723513ae200cf9906512eb8882c6b1ba68df98 Mon Sep 17 00:00:00 2001 From: Martin Prokoph Date: Mon, 19 May 2025 19:18:18 +0200 Subject: [PATCH 16/18] feat: easy switching between virtual keyboard layouts --- .../client/gui/MidnightControlsSettingsScreen.java | 11 +++++++++++ .../client/virtualkeyboard/KeyboardLayout.java | 6 ++++++ .../client/virtualkeyboard/KeyboardLayoutManager.java | 9 +++++++++ gradle.properties | 2 +- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java index 9e06f94..cccff9e 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/MidnightControlsSettingsScreen.java @@ -14,6 +14,7 @@ import com.mojang.blaze3d.systems.RenderSystem; import eu.midnightdust.midnightcontrols.MidnightControlsConstants; import eu.midnightdust.midnightcontrols.client.MidnightControlsClient; import eu.midnightdust.midnightcontrols.client.util.platform.NetworkUtil; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.KeyboardLayoutManager; import org.thinkingstudio.obsidianui.background.Background; import org.thinkingstudio.obsidianui.mixin.DrawContextAccessor; import org.thinkingstudio.obsidianui.widget.SpruceWidget; @@ -140,6 +141,15 @@ public class MidnightControlsSettingsScreen extends SpruceScreen { maxAnalogValueOption("midnightcontrols.menu.max_right_x_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X), maxAnalogValueOption("midnightcontrols.menu.max_right_y_value", GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y) }; + // Controller options + public final static SpruceOption virtualKeyboardLayoutOption = + new SpruceCyclingOption("midnightcontrols.menu.virtual_keyboard_layout", + amount -> { + MidnightControlsConfig.keyboardLayout = KeyboardLayoutManager.getNext(KeyboardLayoutManager.getById(MidnightControlsConfig.keyboardLayout)).getId(); + }, + option -> { + return option.getDisplayText(Text.translatable(KeyboardLayoutManager.getById(MidnightControlsConfig.keyboardLayout).getTranslationKey())); + }, null); private static SpruceOption maxAnalogValueOption(String key, int axis) { return new SpruceDoubleOption(key, .25f, 1.f, 0.05f, @@ -395,6 +405,7 @@ public class MidnightControlsSettingsScreen extends SpruceScreen { list.addSingleOptionEntry(this.mouseSpeedOption); list.addSingleOptionEntry(this.virtualMouseOption); list.addSingleOptionEntry(this.virtualKeyboardOption); + list.addSingleOptionEntry(this.virtualKeyboardLayoutOption); list.addSingleOptionEntry(this.hideCursorOption); list.addSingleOptionEntry(this.joystickAsMouseOption); list.addSingleOptionEntry(this.eyeTrackingAsMouseOption); 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 a88c9ad..23d6d83 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 @@ -52,6 +52,12 @@ public class KeyboardLayout { return id; } + public String getTranslationKey() { + String[] identifier = id.split(":"); + if (identifier.length != 2) return "Invalid Keyboard ID: %s".formatted(id); + return "midnightcontrols.virtual_keyboard.layout.%s.%s".formatted(identifier[0], identifier[1]); + } + public List> getLetters() { return letters; } 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 index ba2cfae..6a762af 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayoutManager.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/KeyboardLayoutManager.java @@ -24,4 +24,13 @@ public class KeyboardLayoutManager { public static KeyboardLayout getById(String id) { return KEYBOARD_LAYOUTS.get(id) == null ? KeyboardLayout.QWERTY : KEYBOARD_LAYOUTS.get(id); } + public static KeyboardLayout getNext(KeyboardLayout current) { + KeyboardLayout[] layouts = KEYBOARD_LAYOUTS.values().toArray(KeyboardLayout[]::new); + int currentIndex = -1; + for (int i = 0; i < layouts.length; i++) { + if (layouts[i] == current) currentIndex = i; + } + currentIndex = currentIndex+1 >= layouts.length ? 0 : currentIndex + 1; + return layouts[currentIndex]; + } } diff --git a/gradle.properties b/gradle.properties index d3b3219..fdc3da1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,7 @@ modrinth_id = bXX9h73M curseforge_id = 621768 # Configure the IDs here after creating the projects on the websites -midnightlib_version=1.7.0+1.21.4 +midnightlib_version=1.7.3+1.21.4 fabric_loader_version=0.16.10 fabric_api_version=0.119.5+1.21.5 From 7375e5ad207aa1b43c982276516157faa7897a44 Mon Sep 17 00:00:00 2001 From: Martin Prokoph Date: Mon, 19 May 2025 20:28:06 +0200 Subject: [PATCH 17/18] feat: add reset button icon --- .../client/gui/widget/ControlsListWidget.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/widget/ControlsListWidget.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/widget/ControlsListWidget.java index d8d5b35..519e0fd 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/widget/ControlsListWidget.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/gui/widget/ControlsListWidget.java @@ -14,11 +14,14 @@ import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig; import eu.midnightdust.midnightcontrols.client.controller.ButtonBinding; import eu.midnightdust.midnightcontrols.client.controller.ButtonCategory; import eu.midnightdust.midnightcontrols.client.controller.InputManager; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.util.Identifier; import org.thinkingstudio.obsidianui.Position; import org.thinkingstudio.obsidianui.SpruceTexts; import org.thinkingstudio.obsidianui.navigation.NavigationDirection; import org.thinkingstudio.obsidianui.navigation.NavigationUtils; import org.thinkingstudio.obsidianui.widget.SpruceButtonWidget; +import org.thinkingstudio.obsidianui.widget.SpruceIconButtonWidget; import org.thinkingstudio.obsidianui.widget.SpruceSeparatorWidget; import org.thinkingstudio.obsidianui.widget.SpruceWidget; import org.thinkingstudio.obsidianui.widget.container.SpruceEntryListWidget; @@ -86,7 +89,7 @@ public class ControlsListWidget extends SpruceEntryListWidget { + this.editButton = new ControllerButtonWidget(Position.of(this, parent.getWidth() / 2 - 8, 0), 120, this.binding, btn -> { gui.focusedBinding = binding; MidnightControlsClient.input.beginControlsInput(gui); }) { @@ -96,13 +99,24 @@ public class ControlsListWidget extends SpruceEntryListWidget MidnightControlsConfig.setButtonBinding(binding, binding.getDefaultButton())) { protected Text getNarrationMessage() { return Text.translatable("narrator.controls.reset", bindingName); } + + private final Identifier resetTexture = Identifier.of("midnightlib","icon/reset"); + + @Override + protected int renderIcon(DrawContext drawContext, int mouseX, int mouseY, float delta) { + int size = 12; + int x = this.getX() + this.getWidth() / 2 - size / 2; + int y = this.getY() + this.getHeight() / 2 - size / 2; + drawContext.drawGuiTexture(RenderLayer::getGuiTextured, resetTexture, x, y, size, size); + return 1; + } }; this.children.add(this.resetButton); this.unbindButton = new SpruceButtonWidget(Position.of(this, From 0f407ac2451a5460412a1ddeaf5015a2a365e40b Mon Sep 17 00:00:00 2001 From: Martin Prokoph Date: Mon, 19 May 2025 22:49:26 +0200 Subject: [PATCH 18/18] feat: edit signs using the virtual keyboard! --- .../mixin/AbstractSignEditScreenAccessor.java | 15 -------- .../mixin/AbstractSignEditScreenMixin.java | 34 +++++++++++++++++++ .../util/AbstractSignEditScreenAccessor.java | 14 ++++++++ .../SignEditScreenClickHandler.java | 22 +++++++++++- .../resources/midnightcontrols.mixins.json | 6 ++-- 5 files changed, 72 insertions(+), 19 deletions(-) delete mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenAccessor.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenMixin.java create mode 100644 common/src/main/java/eu/midnightdust/midnightcontrols/client/util/AbstractSignEditScreenAccessor.java diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenAccessor.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenAccessor.java deleted file mode 100644 index fa5036c..0000000 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenAccessor.java +++ /dev/null @@ -1,15 +0,0 @@ -package eu.midnightdust.midnightcontrols.client.mixin; - -import net.minecraft.block.entity.SignText; -import net.minecraft.client.gui.screen.ingame.AbstractSignEditScreen; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(AbstractSignEditScreen.class) -public interface AbstractSignEditScreenAccessor { - @Accessor("text") - SignText midnightcontrols$getText(); - - @Accessor("text") - void midnightcontrols$setText(SignText text); -} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenMixin.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenMixin.java new file mode 100644 index 0000000..90084ed --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/mixin/AbstractSignEditScreenMixin.java @@ -0,0 +1,34 @@ +package eu.midnightdust.midnightcontrols.client.mixin; + +import eu.midnightdust.midnightcontrols.client.util.AbstractSignEditScreenAccessor; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.block.entity.SignText; +import net.minecraft.client.gui.screen.ingame.AbstractSignEditScreen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(AbstractSignEditScreen.class) +public class AbstractSignEditScreenMixin implements AbstractSignEditScreenAccessor { + @Shadow @Final private String[] messages; + @Shadow private SignText text; + @Shadow @Final protected SignBlockEntity blockEntity; + @Shadow @Final private boolean front; + + @Override + public String[] midnightcontrols$getMessages() { + return messages; + } + + @Override + public void midnightcontrols$setMessage(int line, String text) { + this.messages[line] = text; + this.text = this.text.withMessage(line, Text.literal(text)); + } + + @Override + public void midnightcontrols$writeToBlockEntity() { + this.blockEntity.setText(this.text, this.front); + } +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/util/AbstractSignEditScreenAccessor.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/util/AbstractSignEditScreenAccessor.java new file mode 100644 index 0000000..3828077 --- /dev/null +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/util/AbstractSignEditScreenAccessor.java @@ -0,0 +1,14 @@ +package eu.midnightdust.midnightcontrols.client.util; + +import org.spongepowered.asm.mixin.Unique; + +public interface AbstractSignEditScreenAccessor { + @Unique + String[] midnightcontrols$getMessages(); + + @Unique + void midnightcontrols$setMessage(int line, String text); + + @Unique + void midnightcontrols$writeToBlockEntity(); +} diff --git a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenClickHandler.java b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenClickHandler.java index ced8546..f052193 100644 --- a/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenClickHandler.java +++ b/common/src/main/java/eu/midnightdust/midnightcontrols/client/virtualkeyboard/clickhandler/SignEditScreenClickHandler.java @@ -1,8 +1,11 @@ package eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler; -import eu.midnightdust.midnightcontrols.client.mixin.AbstractSignEditScreenAccessor; +import eu.midnightdust.midnightcontrols.client.util.AbstractSignEditScreenAccessor; +import eu.midnightdust.midnightcontrols.client.virtualkeyboard.gui.VirtualKeyboardScreen; import net.minecraft.client.gui.screen.ingame.SignEditScreen; +import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.client; + public class SignEditScreenClickHandler extends AbstractScreenClickHandler { @Override public void handle(SignEditScreen screen, double mouseX, double mouseY) { @@ -12,5 +15,22 @@ public class SignEditScreenClickHandler extends AbstractScreenClickHandler { + client.setScreen(screen); + String[] lines = text.split("\n"); + for (int i = 0; i < 4; i++) accessor.midnightcontrols$setMessage(i, lines.length > i ? lines[i] : ""); + accessor.midnightcontrols$writeToBlockEntity(); + }, true); + + client.setScreen(virtualKeyboardScreen); } } diff --git a/common/src/main/resources/midnightcontrols.mixins.json b/common/src/main/resources/midnightcontrols.mixins.json index c745ebd..5e0673e 100644 --- a/common/src/main/resources/midnightcontrols.mixins.json +++ b/common/src/main/resources/midnightcontrols.mixins.json @@ -3,6 +3,8 @@ "package": "eu.midnightdust.midnightcontrols.client.mixin", "compatibilityLevel": "JAVA_21", "client": [ + "AbstractBlockAccessor", + "AbstractSignEditScreenMixin", "AdvancementsScreenAccessor", "BookEditScreenAccessor", "ChatScreenMixin", @@ -24,10 +26,8 @@ "RecipeBookScreenAccessor", "RecipeBookWidgetAccessor", "ScreenMixin", - "AbstractSignEditScreenAccessor", "TabNavigationWidgetAccessor", - "WorldRendererMixin", - "AbstractBlockAccessor" + "WorldRendererMixin" ], "injectors": { "defaultRequire": 1