mirror of
https://github.com/TeamMidnightDust/MidnightControls.git
synced 2025-12-13 15:25:08 +01:00
Merge pull request #350 from TeamMidnightDust/feat/virtual-keyboard
feat: virtual keyboard support!
This commit is contained in:
@@ -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;
|
||||
@@ -204,6 +206,7 @@ public class MidnightControlsClient extends MidnightControls {
|
||||
RainbowColor.tick();
|
||||
TouchInput.tick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when opening a screen.
|
||||
*/
|
||||
|
||||
@@ -112,6 +112,7 @@ public class MidnightControlsConfig extends MidnightConfig {
|
||||
@Condition(requiredOption = "virtualMouse", visibleButLocked = true)
|
||||
@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;
|
||||
@Comment(category = TOUCH, centered = true, name="\uD83E\uDE84 Behaviour") public static Comment _touchBehaviour;
|
||||
@@ -147,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<String> 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",
|
||||
@@ -402,6 +404,7 @@ public class MidnightControlsConfig extends MidnightConfig {
|
||||
unfocusedInput = false;
|
||||
virtualMouse = false;
|
||||
virtualMouseSkin = VirtualMouseSkin.DEFAULT_LIGHT;
|
||||
virtualKeyboard = false;
|
||||
controllerID = 0;
|
||||
secondControllerID = -1;
|
||||
controllerType = ControllerType.DEFAULT;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -65,6 +66,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;
|
||||
@@ -139,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,
|
||||
@@ -300,6 +311,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.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
|
||||
@@ -391,6 +404,8 @@ public class MidnightControlsSettingsScreen extends SpruceScreen {
|
||||
list.addSingleOptionEntry(this.yAxisRotationSpeedOption);
|
||||
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);
|
||||
|
||||
@@ -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<ControlsListWidget
|
||||
super(parent);
|
||||
this.binding = binding;
|
||||
this.bindingName = I18n.translate(this.binding.getTranslationKey());
|
||||
this.editButton = new ControllerButtonWidget(Position.of(this, parent.getWidth() / 2 - 8, 0), 110, this.binding, btn -> {
|
||||
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<ControlsListWidget
|
||||
}
|
||||
};
|
||||
this.children.add(editButton);
|
||||
this.resetButton = new SpruceButtonWidget(Position.of(this,
|
||||
this.resetButton = new SpruceIconButtonWidget(Position.of(this,
|
||||
this.editButton.getPosition().getRelativeX() + this.editButton.getWidth() + 2, 0),
|
||||
44, 20, Text.translatable("controls.reset"),
|
||||
37, 20, Text.empty(),
|
||||
btn -> 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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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;
|
||||
|
||||
@Mixin(BookEditScreen.class)
|
||||
public interface BookEditScreenAccessor {
|
||||
@Accessor("signing")
|
||||
boolean midnightcontrols$isSigning();
|
||||
|
||||
@Accessor("title")
|
||||
String midnightcontrols$getTitle();
|
||||
|
||||
@Accessor("title")
|
||||
void midnightcontrols$setTitle(String title);
|
||||
|
||||
@Accessor("currentPageSelectionManager")
|
||||
SelectionManager midnightcontrols$getCurrentPageSelectionManager();
|
||||
|
||||
@Invoker("getCurrentPageContent")
|
||||
String midnightcontrols$getCurrentPageContent();
|
||||
|
||||
@Invoker("setPageContent")
|
||||
void midnightcontrols$setPageContent(String newContent);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
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("en_US:qwerty", createQwertyLetterLayout(), createSymbolLayout());
|
||||
|
||||
private final String id;
|
||||
private final List<List<String>> letters;
|
||||
private final List<List<String>> symbols;
|
||||
|
||||
private KeyboardLayout(String id, List<List<String>> letters, List<List<String>> symbols) {
|
||||
this.id = id;
|
||||
this.letters = letters;
|
||||
this.symbols = symbols;
|
||||
}
|
||||
|
||||
public static KeyboardLayout fromJson(JsonObject json) {
|
||||
try {
|
||||
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));
|
||||
}
|
||||
}
|
||||
private static List<List<String>> getFromJson(JsonObject json, boolean letters) {
|
||||
String type = letters ? "letters" : "symbols";
|
||||
List<List<String>> arr = new ArrayList<>();
|
||||
if (json.has(type)) {
|
||||
JsonObject lettersJson = json.get(type).getAsJsonObject();
|
||||
for (int i = 0; ; i++) {
|
||||
if (!lettersJson.has("row"+i)) break;
|
||||
var rowJson = lettersJson.get("row%s".formatted(i)).getAsJsonArray();
|
||||
List<String> 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 getId() {
|
||||
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<List<String>> getLetters() {
|
||||
return letters;
|
||||
}
|
||||
|
||||
public List<List<String>> getSymbols() {
|
||||
return symbols;
|
||||
}
|
||||
|
||||
private static List<List<String>> createQwertyLetterLayout() {
|
||||
List<List<String>> letters = new ArrayList<>();
|
||||
letters.add(Arrays.asList(
|
||||
"q", "w", "e", "r", "t",
|
||||
"y", "u", "i", "o", "p"
|
||||
));
|
||||
letters.add(Arrays.asList(
|
||||
"a", "s", "d", "f", "g",
|
||||
"h", "j", "k", "l"
|
||||
));
|
||||
letters.add(Arrays.asList(
|
||||
"z", "x", "c", "v",
|
||||
"b", "n", "m"
|
||||
));
|
||||
return letters;
|
||||
}
|
||||
|
||||
private static List<List<String>> createSymbolLayout() {
|
||||
List<List<String>> symbols = new ArrayList<>();
|
||||
symbols.add(Arrays.asList(
|
||||
"1", "2", "3", "4", "5",
|
||||
"6", "7", "8", "9", "0"
|
||||
));
|
||||
symbols.add(Arrays.asList(
|
||||
"@", "#", "$", "%", "&",
|
||||
"*", "-", "+", "(", ")"
|
||||
));
|
||||
symbols.add(Arrays.asList(
|
||||
"!", "\"", "'", ":", ";",
|
||||
",", ".", "?", "/"
|
||||
));
|
||||
return symbols;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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<String, KeyboardLayout> 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);
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package eu.midnightdust.midnightcontrols.client.virtualkeyboard;
|
||||
|
||||
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.SignEditScreenClickHandler;
|
||||
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<Class<?>, AbstractScreenClickHandler<?>> clickHandlers;
|
||||
|
||||
public MouseClickInterceptor() {
|
||||
this.clickHandlers = new HashMap<>();
|
||||
this.clickHandlers.put(BookEditScreen.class, new BookEditScreenClickHandler());
|
||||
this.clickHandlers.put(SignEditScreen.class, new SignEditScreenClickHandler());
|
||||
this.clickHandlers.put(Screen.class, new DefaultScreenClickHandler());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Screen> void intercept(T screen, double mouseX, double mouseY) {
|
||||
AbstractScreenClickHandler<T> handler = (AbstractScreenClickHandler<T>) clickHandlers.get(screen.getClass());
|
||||
|
||||
if (handler == null) {
|
||||
handler = (AbstractScreenClickHandler<T>) clickHandlers.get(Screen.class);
|
||||
}
|
||||
|
||||
handler.handle(screen, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler;
|
||||
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
|
||||
public abstract class AbstractScreenClickHandler<T extends Screen> {
|
||||
public abstract void handle(T screen, double mouseX, double mouseY);
|
||||
}
|
||||
@@ -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<BookEditScreen> {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
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 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<Screen> {
|
||||
|
||||
private Screen parentScreen;
|
||||
private List<Integer> textFieldElementPath;
|
||||
|
||||
@Override
|
||||
public void handle(Screen screen, double mouseX, double mouseY) {
|
||||
var textField = findClickedTextField(screen.children(), mouseX, mouseY);
|
||||
if (textField == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.parentScreen = screen;
|
||||
this.textFieldElementPath = calculatePathToElement(screen, textField.asElement());
|
||||
|
||||
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);
|
||||
TextFieldWrapper 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 TextFieldWrapper findClickedTextField(List<? extends Element> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the path between a parent and a target in the UI hierarchy
|
||||
*/
|
||||
protected List<Integer> calculatePathToElement(Element parent, Element target) {
|
||||
if (!(parent instanceof ParentElement parentElement)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<? extends Element> children = parentElement.children();
|
||||
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
Element child = children.get(i);
|
||||
|
||||
if (child == target) {
|
||||
return Collections.singletonList(i);
|
||||
}
|
||||
|
||||
if (child instanceof ParentElement) {
|
||||
List<Integer> subPath = calculatePathToElement(child, target);
|
||||
if (subPath != null) {
|
||||
List<Integer> fullPath = new ArrayList<>(subPath.size() + 1);
|
||||
fullPath.add(i);
|
||||
fullPath.addAll(subPath);
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected TextFieldWrapper findTextFieldByPath(Element parent, List<Integer> path) {
|
||||
if (path == null || path.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(parent instanceof ParentElement parentElement)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<? extends Element> children = parentElement.children();
|
||||
int index = path.get(0);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package eu.midnightdust.midnightcontrols.client.virtualkeyboard.clickhandler;
|
||||
|
||||
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<SignEditScreen> {
|
||||
@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;
|
||||
|
||||
StringBuilder linesToString = new StringBuilder();
|
||||
String[] messages = accessor.midnightcontrols$getMessages();
|
||||
for (int i = 0; i < Math.min(4, messages.length); i++) {
|
||||
String line = messages[i];
|
||||
linesToString.append(line);
|
||||
if (!line.isEmpty() && i < 3) linesToString.append("\n");
|
||||
}
|
||||
|
||||
VirtualKeyboardScreen virtualKeyboardScreen = new VirtualKeyboardScreen(linesToString.toString(), (text) -> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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.translatable("midnightcontrols.virtual_keyboard.screen"));
|
||||
this.buffer = new StringBuilder(initialText);
|
||||
this.closeCallback = closeCallback;
|
||||
this.layout = KeyboardLayoutManager.getById(MidnightControlsConfig.keyboardLayout);
|
||||
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);
|
||||
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 layoutKeys = getActiveKeyLayout();
|
||||
var keyboardContainer = createKeyboardContainer(layoutKeys);
|
||||
|
||||
addLayoutRows(keyboardContainer, layoutKeys);
|
||||
addFunctionKeys(keyboardContainer);
|
||||
addBottomRow(keyboardContainer);
|
||||
|
||||
this.keyboardContainer = keyboardContainer;
|
||||
this.addDrawableChild(this.keyboardContainer);
|
||||
}
|
||||
|
||||
private SpruceContainerWidget createKeyboardContainer(List<List<String>> 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 ? 4 : 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<List<String>> 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 addLayoutRows(SpruceContainerWidget container, List<List<String>> keyLayoutRows) {
|
||||
int currentY = CONTAINER_PADDING;
|
||||
|
||||
for (List<String> row : keyLayoutRows) {
|
||||
int rowWidth = calculateRowWidth(row);
|
||||
// center row
|
||||
int currentX = (container.getWidth() - rowWidth) / 2;
|
||||
|
||||
for (String key : row) {
|
||||
String displayText = (this.capsMode && !this.symbolMode) ? key.toUpperCase() : key;
|
||||
container.addChild(
|
||||
new SpruceButtonWidget(
|
||||
Position.of(currentX, currentY),
|
||||
STANDARD_KEY_WIDTH,
|
||||
KEY_HEIGHT,
|
||||
Text.literal(displayText),
|
||||
btn -> handleKeyPress(displayText)
|
||||
)
|
||||
);
|
||||
|
||||
currentX += STANDARD_KEY_WIDTH + HORIZONTAL_SPACING;
|
||||
}
|
||||
|
||||
currentY += KEY_HEIGHT + VERTICAL_SPACING;
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateRowWidth(List<String> 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;
|
||||
}
|
||||
}
|
||||
return rowWidth;
|
||||
}
|
||||
|
||||
private void addFunctionKeys(SpruceContainerWidget container) {
|
||||
List<String> firstRow = getActiveKeyLayout().getFirst();
|
||||
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<String> 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.translatable("midnightcontrols.virtual_keyboard.keyboard.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 List<List<String>> 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();
|
||||
}
|
||||
}
|
||||
@@ -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": ["!", "\"", "'", ":", ";", ",", ".", "?", "/"]
|
||||
}
|
||||
}
|
||||
@@ -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": ["!", "\"", "'", ":", ";", ",", ".", "?", "/"]
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
"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",
|
||||
@@ -224,6 +228,10 @@
|
||||
"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",
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
"package": "eu.midnightdust.midnightcontrols.client.mixin",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"client": [
|
||||
"AbstractBlockAccessor",
|
||||
"AbstractSignEditScreenMixin",
|
||||
"AdvancementsScreenAccessor",
|
||||
"BookEditScreenAccessor",
|
||||
"ChatScreenMixin",
|
||||
"ClickableWidgetAccessor",
|
||||
"ClientPlayerEntityMixin",
|
||||
@@ -24,8 +27,7 @@
|
||||
"RecipeBookWidgetAccessor",
|
||||
"ScreenMixin",
|
||||
"TabNavigationWidgetAccessor",
|
||||
"WorldRendererMixin",
|
||||
"AbstractBlockAccessor"
|
||||
"WorldRendererMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
@@ -3,6 +3,8 @@ 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;
|
||||
import eu.midnightdust.midnightcontrols.packet.HelloPayload;
|
||||
@@ -11,11 +13,16 @@ 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.fabric.api.resource.SimpleSynchronousResourceReloadListener;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import org.thinkingstudio.obsidianui.fabric.event.OpenScreenCallback;
|
||||
import net.minecraft.resource.ResourceManager;
|
||||
import net.minecraft.resource.ResourceType;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -51,11 +58,25 @@ 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);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
|
||||
import static eu.midnightdust.midnightcontrols.client.MidnightControlsClient.clickInterceptor;
|
||||
|
||||
public class MouseClickListener implements ScreenMouseEvents.AllowMouseClick {
|
||||
private final Screen screen;
|
||||
|
||||
public MouseClickListener(Screen screen) {
|
||||
this.screen = screen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowMouseClick(Screen screen, double mouseX, double mouseY, int button) {
|
||||
if(MidnightControlsConfig.virtualKeyboard) {
|
||||
clickInterceptor.intercept(screen, mouseX, mouseY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -2,9 +2,11 @@ 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;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.resource.DirectoryResourcePack;
|
||||
import net.minecraft.resource.ResourcePackInfo;
|
||||
import net.minecraft.resource.ResourcePackPosition;
|
||||
@@ -18,9 +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.*;
|
||||
import net.neoforged.neoforge.event.AddPackFindersEvent;
|
||||
import net.neoforged.neoforgespi.locating.IModFile;
|
||||
|
||||
@@ -34,6 +34,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 {
|
||||
@@ -71,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)
|
||||
@@ -90,5 +96,15 @@ public class MidnightControlsClientNeoforge {
|
||||
public static void startClientTick(ClientTickEvent.Pre event) {
|
||||
MidnightControlsClient.onTick(client);
|
||||
}
|
||||
@SubscribeEvent
|
||||
public static void onMouseButtonPressed(ScreenEvent.MouseButtonPressed.Pre event) {
|
||||
if (MidnightControlsConfig.virtualKeyboard && !event.isCanceled()) {
|
||||
Screen screen = event.getScreen();
|
||||
double mouseX = event.getMouseX();
|
||||
double mouseY = event.getMouseY();
|
||||
|
||||
clickInterceptor.intercept(screen, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user