mirror of
https://github.com/TeamMidnightDust/MidnightControls.git
synced 2025-12-17 08:55:10 +01:00
Merge 0ef59057af into 114a72cdde
This commit is contained in:
@@ -204,6 +204,7 @@ public class MidnightControlsClient extends MidnightControls {
|
|||||||
RainbowColor.tick();
|
RainbowColor.tick();
|
||||||
TouchInput.tick();
|
TouchInput.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when opening a screen.
|
* Called when opening a screen.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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<List<String>> letters;
|
||||||
|
private final List<List<String>> symbols;
|
||||||
|
|
||||||
|
private KeyboardLayout(List<List<String>> letters, List<List<String>> symbols) {
|
||||||
|
this.letters = letters;
|
||||||
|
this.symbols = symbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,299 @@
|
|||||||
|
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;
|
||||||
|
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.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);
|
||||||
|
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().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<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.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 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,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);
|
||||||
|
}
|
||||||
@@ -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")
|
@Invoker("hasScrollbar")
|
||||||
boolean midnightcontrols$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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"compatibilityLevel": "JAVA_21",
|
"compatibilityLevel": "JAVA_21",
|
||||||
"client": [
|
"client": [
|
||||||
"AdvancementsScreenAccessor",
|
"AdvancementsScreenAccessor",
|
||||||
|
"BookEditScreenAccessor",
|
||||||
"ChatScreenMixin",
|
"ChatScreenMixin",
|
||||||
"ClickableWidgetAccessor",
|
"ClickableWidgetAccessor",
|
||||||
"ClientPlayerEntityMixin",
|
"ClientPlayerEntityMixin",
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
"RecipeBookScreenAccessor",
|
"RecipeBookScreenAccessor",
|
||||||
"RecipeBookWidgetAccessor",
|
"RecipeBookWidgetAccessor",
|
||||||
"ScreenMixin",
|
"ScreenMixin",
|
||||||
|
"AbstractSignEditScreenAccessor",
|
||||||
"TabNavigationWidgetAccessor",
|
"TabNavigationWidgetAccessor",
|
||||||
"WorldRendererMixin"
|
"WorldRendererMixin"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package eu.midnightdust.midnightcontrols.fabric;
|
|||||||
import eu.midnightdust.midnightcontrols.MidnightControlsConstants;
|
import eu.midnightdust.midnightcontrols.MidnightControlsConstants;
|
||||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
import eu.midnightdust.midnightcontrols.client.MidnightControlsClient;
|
||||||
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig;
|
||||||
|
import eu.midnightdust.midnightcontrols.fabric.event.MouseClickListener;
|
||||||
import eu.midnightdust.midnightcontrols.packet.ControlsModePayload;
|
import eu.midnightdust.midnightcontrols.packet.ControlsModePayload;
|
||||||
import eu.midnightdust.midnightcontrols.packet.FeaturePayload;
|
import eu.midnightdust.midnightcontrols.packet.FeaturePayload;
|
||||||
import eu.midnightdust.midnightcontrols.packet.HelloPayload;
|
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.keybinding.v1.KeyBindingHelper;
|
||||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
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.ResourceManagerHelper;
|
||||||
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
|
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
import net.fabricmc.loader.api.ModContainer;
|
import net.fabricmc.loader.api.ModContainer;
|
||||||
import org.thinkingstudio.obsidianui.fabric.event.OpenScreenCallback;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -51,6 +53,9 @@ public class MidnightControlsClientFabric implements ClientModInitializer {
|
|||||||
ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> MidnightControlsClient.onLeave());
|
ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> MidnightControlsClient.onLeave());
|
||||||
|
|
||||||
ClientTickEvents.START_CLIENT_TICK.register(MidnightControlsClient::onTick);
|
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 -> {
|
FabricLoader.getInstance().getModContainer(MidnightControlsConstants.NAMESPACE).ifPresent(modContainer -> {
|
||||||
ResourceManagerHelper.registerBuiltinResourcePack(id("bedrock"), modContainer, ResourcePackActivationType.NORMAL);
|
ResourceManagerHelper.registerBuiltinResourcePack(id("bedrock"), modContainer, ResourcePackActivationType.NORMAL);
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
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<Integer> 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);
|
||||||
|
}, 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<Integer> calculatePathToElement(Element parent, Element target) {
|
||||||
|
if (parent instanceof ParentElement parentElement) {
|
||||||
|
List<? extends Element> 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<Integer> subPath = calculatePathToElement(childParent, target);
|
||||||
|
if (subPath != null) {
|
||||||
|
// found in this subtree, prepend current index
|
||||||
|
List<Integer> fullPath = new ArrayList<>();
|
||||||
|
fullPath.add(i);
|
||||||
|
fullPath.addAll(subPath);
|
||||||
|
return fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextFieldWidget findTextFieldByPath(Element parent, List<Integer> path) {
|
||||||
|
if (path == null || path.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent instanceof ParentElement parentElement) {
|
||||||
|
List<? extends Element> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user