mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2026-08-20 01:08:54 +00:00
Rewrite computer input handling (again)
Hey, it lasted almost a year! Computer input is somewhat stateful, as we need to track things like which key(s) are currently held, and what the last mouse button/position was. This code is currently duplicated in several places (specifically TerminalWidget, ServerInputState and the standalone emulator). In order to implement lectern pocket computer mouse interactions, we'd have to duplicate this logic once again. Instead, we move this code into a common class. - Replace the InputHandler interface with a simpler ComputerInput one (this no longer has computer actions, like reboot or terminate). This interface never made much sense (aside from hiding implementation details), as code only ever consumed a single implementation of it. On the client, this requires a new "ClientComputerActions" class. This feels a bit clunky to me, but it's simple and it works. - Replace ComputerEvents with a EventComputerInput class (terrible name, I know!), which queues events on a computer. - Move common input state tracking and validation into a single UserComputerInput class, which wraps an existing ComputerInput. This is used by both the terminal widget, and the server-side input state.
This commit is contained in:
+6
-4
@@ -8,10 +8,10 @@ import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton;
|
||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||
import dan200.computercraft.client.network.ClientNetworking;
|
||||
import dan200.computercraft.core.input.UserComputerInput;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||
@@ -58,7 +58,8 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
protected @Nullable TerminalWidget terminal;
|
||||
protected Terminal terminalData;
|
||||
protected final ComputerFamily family;
|
||||
protected final InputHandler input;
|
||||
protected final UserComputerInput computerInput;
|
||||
protected final ClientComputerActions computerActions;
|
||||
|
||||
protected final int sidebarYOffset;
|
||||
|
||||
@@ -72,7 +73,8 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
family = container.getFamily();
|
||||
displayStack = container.getDisplayStack();
|
||||
uploadMaxSize = container.getUploadMaxSize();
|
||||
input = new ClientInputHandler(menu);
|
||||
computerInput = new UserComputerInput(new ClientComputerInput(menu), menu.getTerminal());
|
||||
computerActions = new ClientComputerActions(menu);
|
||||
this.sidebarYOffset = sidebarYOffset;
|
||||
}
|
||||
|
||||
@@ -88,7 +90,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
super.init();
|
||||
|
||||
terminal = addRenderableWidget(createTerminal());
|
||||
ComputerSidebar.addButtons(menu::isOn, input, this::addRenderableWidget, leftPos, topPos + sidebarYOffset);
|
||||
ComputerSidebar.addButtons(menu::isOn, computerActions, this::addRenderableWidget, leftPos, topPos + sidebarYOffset);
|
||||
setFocused(terminal);
|
||||
}
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.gui;
|
||||
|
||||
import dan200.computercraft.client.network.ClientNetworking;
|
||||
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
|
||||
/**
|
||||
* Actions that can be applied to a computer.
|
||||
*
|
||||
* @see ComputerActionServerMessage
|
||||
*/
|
||||
public final class ClientComputerActions {
|
||||
private final AbstractContainerMenu menu;
|
||||
|
||||
public ClientComputerActions(AbstractContainerMenu menu) {
|
||||
this.menu = menu;
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TERMINATE));
|
||||
}
|
||||
|
||||
public void turnOn() {
|
||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
|
||||
}
|
||||
|
||||
public void reboot() {
|
||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
|
||||
}
|
||||
}
|
||||
+5
-26
@@ -5,9 +5,8 @@
|
||||
package dan200.computercraft.client.gui;
|
||||
|
||||
import dan200.computercraft.client.network.ClientNetworking;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import dan200.computercraft.core.input.ComputerInput;
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
|
||||
import dan200.computercraft.shared.network.server.KeyEventServerMessage;
|
||||
import dan200.computercraft.shared.network.server.MouseEventServerMessage;
|
||||
import dan200.computercraft.shared.network.server.PasteEventComputerMessage;
|
||||
@@ -16,37 +15,17 @@ import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* An {@link InputHandler} for use on the client.
|
||||
* An {@link ComputerInput} for use on the client.
|
||||
* <p>
|
||||
* This queues events on the remote player's open {@link ComputerMenu}.
|
||||
* This queues events on the player's open {@link ComputerMenu}.
|
||||
*/
|
||||
public final class ClientInputHandler implements InputHandler {
|
||||
public final class ClientComputerInput implements ComputerInput {
|
||||
private final AbstractContainerMenu menu;
|
||||
|
||||
public ClientInputHandler(AbstractContainerMenu menu) {
|
||||
public ClientComputerInput(AbstractContainerMenu menu) {
|
||||
this.menu = menu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TERMINATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void turnOn() {
|
||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.SHUTDOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reboot() {
|
||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyDown(int key, boolean repeat) {
|
||||
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.Action.REPEAT : KeyEventServerMessage.Action.DOWN, key));
|
||||
@@ -33,7 +33,10 @@ public final class ComputerScreen<T extends AbstractComputerMenu> extends Abstra
|
||||
|
||||
@Override
|
||||
protected TerminalWidget createTerminal() {
|
||||
return new TerminalWidget(terminalData, input, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH + BORDER, topPos + BORDER);
|
||||
return new TerminalWidget(
|
||||
terminalData, computerInput, computerActions,
|
||||
leftPos + AbstractComputerMenu.SIDEBAR_WIDTH + BORDER, topPos + BORDER
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+6
-4
@@ -5,7 +5,7 @@
|
||||
package dan200.computercraft.client.gui;
|
||||
|
||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.input.UserComputerInput;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
@@ -29,13 +29,15 @@ import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
*/
|
||||
public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen implements MenuAccess<T> {
|
||||
private final T menu;
|
||||
private final Terminal terminalData;
|
||||
protected final UserComputerInput computerInput;
|
||||
protected final ClientComputerActions computerActions;
|
||||
private @Nullable TerminalWidget terminal;
|
||||
|
||||
public NoTermComputerScreen(T menu, Inventory player, Component title) {
|
||||
super(title);
|
||||
this.menu = menu;
|
||||
terminalData = menu.getTerminal();
|
||||
computerInput = new UserComputerInput(new ClientComputerInput(menu), menu.getTerminal());
|
||||
computerActions = new ClientComputerActions(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,7 +55,7 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
||||
|
||||
super.init();
|
||||
|
||||
terminal = addWidget(new TerminalWidget(terminalData, new ClientInputHandler(menu), 0, 0));
|
||||
terminal = addWidget(new TerminalWidget(menu.getTerminal(), computerInput, computerActions, 0, 0));
|
||||
terminal.visible = false;
|
||||
terminal.active = false;
|
||||
setFocused(terminal);
|
||||
|
||||
@@ -40,7 +40,10 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
||||
|
||||
@Override
|
||||
protected TerminalWidget createTerminal() {
|
||||
return new TerminalWidget(terminalData, input, leftPos + BORDER + AbstractComputerMenu.SIDEBAR_WIDTH, topPos + BORDER);
|
||||
return new TerminalWidget(
|
||||
terminalData, computerInput, computerActions,
|
||||
leftPos + BORDER + AbstractComputerMenu.SIDEBAR_WIDTH, topPos + BORDER
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+7
-7
@@ -4,10 +4,10 @@
|
||||
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import dan200.computercraft.client.gui.ClientComputerActions;
|
||||
import dan200.computercraft.client.gui.GuiSprites;
|
||||
import dan200.computercraft.client.gui.widgets.DynamicImageButton.HintedMessage;
|
||||
import dan200.computercraft.client.render.SpriteRenderer;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -34,7 +34,7 @@ public final class ComputerSidebar {
|
||||
private ComputerSidebar() {
|
||||
}
|
||||
|
||||
public static void addButtons(BooleanSupplier isOn, InputHandler input, Consumer<AbstractWidget> add, int x, int y) {
|
||||
public static void addButtons(BooleanSupplier isOn, ClientComputerActions actions, Consumer<AbstractWidget> add, int x, int y) {
|
||||
x += CORNERS_BORDER + 1;
|
||||
y += CORNERS_BORDER + ICON_MARGIN;
|
||||
|
||||
@@ -46,7 +46,7 @@ public final class ComputerSidebar {
|
||||
add.accept(new DynamicImageButton(
|
||||
x, y, ICON_WIDTH, ICON_HEIGHT,
|
||||
h -> isOn.getAsBoolean() ? GuiSprites.TURNED_ON.get(h) : GuiSprites.TURNED_OFF.get(h),
|
||||
b -> toggleComputer(isOn, input),
|
||||
b -> toggleComputer(isOn, actions),
|
||||
() -> isOn.getAsBoolean() ? turnOff : turnOn
|
||||
));
|
||||
|
||||
@@ -55,7 +55,7 @@ public final class ComputerSidebar {
|
||||
add.accept(new DynamicImageButton(
|
||||
x, y, ICON_WIDTH, ICON_HEIGHT,
|
||||
GuiSprites.TERMINATE::get,
|
||||
b -> input.terminate(),
|
||||
b -> actions.terminate(),
|
||||
new HintedMessage(
|
||||
Component.translatable("gui.computercraft.tooltip.terminate"),
|
||||
Component.translatable("gui.computercraft.tooltip.terminate.key")
|
||||
@@ -71,11 +71,11 @@ public final class ComputerSidebar {
|
||||
renderer.blitVerticalSliced(sprite, x, y, AbstractComputerMenu.SIDEBAR_WIDTH, HEIGHT, FULL_BORDER, FULL_BORDER, TEX_HEIGHT);
|
||||
}
|
||||
|
||||
private static void toggleComputer(BooleanSupplier isOn, InputHandler input) {
|
||||
private static void toggleComputer(BooleanSupplier isOn, ClientComputerActions actions) {
|
||||
if (isOn.getAsBoolean()) {
|
||||
input.shutdown();
|
||||
actions.shutdown();
|
||||
} else {
|
||||
input.turnOn();
|
||||
actions.turnOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-84
@@ -5,12 +5,13 @@
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import com.mojang.blaze3d.vertex.Tesselator;
|
||||
import dan200.computercraft.client.gui.ClientComputerActions;
|
||||
import dan200.computercraft.client.gui.ClientComputerInput;
|
||||
import dan200.computercraft.client.gui.KeyConverter;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.input.UserComputerInput;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.AbstractWidget;
|
||||
@@ -21,8 +22,6 @@ import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
|
||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
|
||||
@@ -31,7 +30,7 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FON
|
||||
* A widget which renders a computer terminal and handles input events (keyboard, mouse, clipboard) and computer
|
||||
* shortcuts (terminate/shutdown/reboot).
|
||||
*
|
||||
* @see dan200.computercraft.client.gui.ClientInputHandler The input handler typically used with this class.
|
||||
* @see ClientComputerInput The input handler typically used with this class.
|
||||
*/
|
||||
public class TerminalWidget extends AbstractWidget {
|
||||
private static final Component DESCRIPTION = Component.translatable("gui.computercraft.terminal");
|
||||
@@ -40,7 +39,8 @@ public class TerminalWidget extends AbstractWidget {
|
||||
private static final float KEY_SUPPRESS_DELAY = 0.2f;
|
||||
|
||||
private final Terminal terminal;
|
||||
private final InputHandler computer;
|
||||
private final UserComputerInput computerInput;
|
||||
private final ClientComputerActions computerActions;
|
||||
|
||||
// The positions of the actual terminal
|
||||
private final int innerX;
|
||||
@@ -52,17 +52,12 @@ public class TerminalWidget extends AbstractWidget {
|
||||
private float rebootTimer = -1;
|
||||
private float shutdownTimer = -1;
|
||||
|
||||
private int lastMouseButton = -1;
|
||||
private int lastMouseX = -1;
|
||||
private int lastMouseY = -1;
|
||||
|
||||
private final BitSet keysDown = new BitSet(256);
|
||||
|
||||
public TerminalWidget(Terminal terminal, InputHandler computer, int x, int y) {
|
||||
public TerminalWidget(Terminal terminal, UserComputerInput computerInput, ClientComputerActions computerActions, int x, int y) {
|
||||
super(x, y, terminal.getWidth() * FONT_WIDTH + MARGIN * 2, terminal.getHeight() * FONT_HEIGHT + MARGIN * 2, DESCRIPTION);
|
||||
|
||||
this.terminal = terminal;
|
||||
this.computer = computer;
|
||||
this.computerInput = computerInput;
|
||||
this.computerActions = computerActions;
|
||||
|
||||
innerX = x + MARGIN;
|
||||
innerY = y + MARGIN;
|
||||
@@ -72,8 +67,7 @@ public class TerminalWidget extends AbstractWidget {
|
||||
|
||||
@Override
|
||||
public boolean charTyped(char ch, int modifiers) {
|
||||
var terminalChar = StringUtil.unicodeToTerminal(ch);
|
||||
if (StringUtil.isTypableChar(terminalChar)) computer.charTyped((byte) terminalChar);
|
||||
computerInput.codepointTyped(ch);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -100,27 +94,19 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
if (key >= 0 && terminateTimer < KEY_SUPPRESS_DELAY && rebootTimer < KEY_SUPPRESS_DELAY && shutdownTimer < KEY_SUPPRESS_DELAY) {
|
||||
// Queue the "key" event and add to the down set
|
||||
var repeat = keysDown.get(key);
|
||||
keysDown.set(key);
|
||||
computer.keyDown(key, repeat);
|
||||
computerInput.keyDown(key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void paste() {
|
||||
var clipboard = StringUtil.getClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
|
||||
if (clipboard.remaining() > 0) computer.paste(clipboard);
|
||||
computerInput.paste(Minecraft.getInstance().keyboardHandler.getClipboard());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyReleased(int key, int scancode, int modifiers) {
|
||||
// Queue the "key_up" event and remove from the down set
|
||||
if (key >= 0 && keysDown.get(key)) {
|
||||
keysDown.set(key, false);
|
||||
computer.keyUp(key);
|
||||
}
|
||||
computerInput.keyUp(key);
|
||||
|
||||
switch (KeyConverter.physicalToActual(key, scancode)) {
|
||||
case GLFW.GLFW_KEY_T -> terminateTimer = -1;
|
||||
@@ -136,18 +122,10 @@ public class TerminalWidget extends AbstractWidget {
|
||||
@Override
|
||||
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
if (!inTermRegion(mouseX, mouseY)) return false;
|
||||
if (!hasMouseSupport() || button < 0 || button > 2) return false;
|
||||
|
||||
var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
|
||||
computer.mouseClick(button + 1, charX + 1, charY + 1);
|
||||
|
||||
lastMouseButton = button;
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
computerInput.mouseClick(button + 1, charX + 1, charY + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -155,20 +133,10 @@ public class TerminalWidget extends AbstractWidget {
|
||||
@Override
|
||||
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||
if (!inTermRegion(mouseX, mouseY)) return false;
|
||||
if (!hasMouseSupport() || button < 0 || button > 2) return false;
|
||||
|
||||
var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
|
||||
if (lastMouseButton == button) {
|
||||
computer.mouseUp(lastMouseButton + 1, charX + 1, charY + 1);
|
||||
lastMouseButton = -1;
|
||||
}
|
||||
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
computerInput.mouseUp(button + 1, charX + 1, charY + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -176,36 +144,21 @@ public class TerminalWidget extends AbstractWidget {
|
||||
@Override
|
||||
public boolean mouseDragged(double mouseX, double mouseY, int button, double v2, double v3) {
|
||||
if (!inTermRegion(mouseX, mouseY)) return false;
|
||||
if (!hasMouseSupport() || button < 0 || button > 2) return false;
|
||||
|
||||
var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
|
||||
if (button == lastMouseButton && (charX != lastMouseX || charY != lastMouseY)) {
|
||||
computer.mouseDrag(button + 1, charX + 1, charY + 1);
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
}
|
||||
|
||||
computerInput.mouseDrag(button + 1, charX + 1, charY + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
|
||||
if (!inTermRegion(mouseX, mouseY)) return false;
|
||||
if (!hasMouseSupport() || delta == 0) return false;
|
||||
if (delta == 0) return false;
|
||||
|
||||
var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
|
||||
computer.mouseScroll(delta < 0 ? 1 : -1, charX + 1, charY + 1);
|
||||
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
computerInput.mouseScroll(delta < 0 ? 1 : -1, charX + 1, charY + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -214,21 +167,17 @@ public class TerminalWidget extends AbstractWidget {
|
||||
return active && visible && mouseX >= innerX && mouseY >= innerY && mouseX < innerX + innerWidth && mouseY < innerY + innerHeight;
|
||||
}
|
||||
|
||||
private boolean hasMouseSupport() {
|
||||
return terminal.isColour();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (terminateTimer >= 0 && terminateTimer < TERMINATE_TIME && (terminateTimer += 0.05f) > TERMINATE_TIME) {
|
||||
computer.terminate();
|
||||
computerActions.terminate();
|
||||
}
|
||||
|
||||
if (shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME) {
|
||||
computer.shutdown();
|
||||
computerActions.shutdown();
|
||||
}
|
||||
|
||||
if (rebootTimer >= 0 && rebootTimer < TERMINATE_TIME && (rebootTimer += 0.05f) > TERMINATE_TIME) {
|
||||
computer.reboot();
|
||||
computerActions.reboot();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,18 +186,7 @@ public class TerminalWidget extends AbstractWidget {
|
||||
super.setFocused(focused);
|
||||
|
||||
if (!focused) {
|
||||
// When blurring, we should make all keys go up
|
||||
for (var key = 0; key < keysDown.size(); key++) {
|
||||
if (keysDown.get(key)) computer.keyUp(key);
|
||||
}
|
||||
keysDown.clear();
|
||||
|
||||
// When blurring, we should make the last mouse button go up
|
||||
if (lastMouseButton >= 0) {
|
||||
computer.mouseUp(lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1);
|
||||
lastMouseButton = -1;
|
||||
}
|
||||
|
||||
computerInput.releaseInputs();
|
||||
shutdownTimer = terminateTimer = rebootTimer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.computer.core;
|
||||
|
||||
import dan200.computercraft.shared.computer.menu.ServerInputHandler;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Handles user-provided input, forwarding it to a computer. This describes the "shape" of both the client-and
|
||||
* server-side input handlers.
|
||||
*
|
||||
* @see ServerInputHandler
|
||||
* @see ServerComputer
|
||||
*/
|
||||
public interface InputHandler {
|
||||
void keyDown(int key, boolean repeat);
|
||||
|
||||
void keyUp(int key);
|
||||
|
||||
void charTyped(byte chr);
|
||||
|
||||
void paste(ByteBuffer contents);
|
||||
|
||||
void mouseClick(int button, int x, int y);
|
||||
|
||||
void mouseUp(int button, int x, int y);
|
||||
|
||||
void mouseDrag(int button, int x, int y);
|
||||
|
||||
void mouseScroll(int direction, int x, int y);
|
||||
|
||||
void terminate();
|
||||
|
||||
void shutdown();
|
||||
|
||||
void turnOn();
|
||||
|
||||
void reboot();
|
||||
}
|
||||
+7
-3
@@ -13,8 +13,9 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.WorkMonitor;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.ComputerEnvironment;
|
||||
import dan200.computercraft.core.computer.ComputerEvents;
|
||||
import dan200.computercraft.core.computer.ComputerSide;
|
||||
import dan200.computercraft.core.input.EventComputerInput;
|
||||
import dan200.computercraft.core.input.UserComputerInput;
|
||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||
import dan200.computercraft.impl.ApiFactories;
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
@@ -37,7 +38,7 @@ import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ServerComputer implements ComputerEnvironment, ComputerEvents.Receiver {
|
||||
public class ServerComputer implements ComputerEnvironment {
|
||||
public static final ComputerComponent<MetricsObserver> METRICS = ComputerComponent.create("computercraft", "metrics");
|
||||
|
||||
private final int instanceID;
|
||||
@@ -213,7 +214,6 @@ public class ServerComputer implements ComputerEnvironment, ComputerEvents.Recei
|
||||
computer.reboot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void queueEvent(String event, @Nullable Object @Nullable [] arguments) {
|
||||
computer.queueEvent(event, arguments);
|
||||
}
|
||||
@@ -222,6 +222,10 @@ public class ServerComputer implements ComputerEnvironment, ComputerEvents.Recei
|
||||
queueEvent(event, null);
|
||||
}
|
||||
|
||||
public final UserComputerInput createComputerInput() {
|
||||
return new UserComputerInput(new EventComputerInput(computer), terminal);
|
||||
}
|
||||
|
||||
public final int getRedstoneOutput(ComputerSide side) {
|
||||
return computer.isOn() ? computer.getRedstone().getExternalOutput(side) : 0;
|
||||
}
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ public abstract class AbstractComputerMenu extends AbstractContainerMenu impleme
|
||||
private final ContainerData data;
|
||||
|
||||
private final @Nullable ServerComputer computer;
|
||||
private final @Nullable ServerInputState<AbstractComputerMenu> input;
|
||||
private final @Nullable ServerInputState input;
|
||||
|
||||
private final @Nullable NetworkedTerminal terminal;
|
||||
|
||||
@@ -51,7 +51,7 @@ public abstract class AbstractComputerMenu extends AbstractContainerMenu impleme
|
||||
addDataSlots(data);
|
||||
|
||||
this.computer = computer;
|
||||
input = computer == null ? null : new ServerInputState<>(this);
|
||||
input = computer == null ? null : new ServerInputState(this, computer);
|
||||
terminal = containerData == null ? null : containerData.terminal().create();
|
||||
displayStack = containerData == null ? ItemStack.EMPTY : containerData.displayStack();
|
||||
uploadMaxSize = containerData == null ? Config.uploadMaxSize : containerData.uploadMaxSize();
|
||||
|
||||
+10
-3
@@ -4,7 +4,7 @@
|
||||
|
||||
package dan200.computercraft.shared.computer.menu;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import dan200.computercraft.core.input.ComputerInput;
|
||||
import dan200.computercraft.shared.computer.upload.FileSlice;
|
||||
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||
import dan200.computercraft.shared.network.server.ComputerServerMessage;
|
||||
@@ -14,13 +14,20 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An {@link InputHandler} which operates on the server, receiving data from the client over the network.
|
||||
* An {@link ComputerInput} which operates on the server, receiving data from the client over the network.
|
||||
*
|
||||
* @see ServerInputState The default implementation of this interface.
|
||||
* @see ComputerServerMessage Packets which consume this interface.
|
||||
* @see ComputerMenu
|
||||
*/
|
||||
public interface ServerInputHandler extends InputHandler {
|
||||
public interface ServerInputHandler {
|
||||
/**
|
||||
* Get a {@link ComputerInput} that handles events for this computer.
|
||||
*
|
||||
* @return The computer input.
|
||||
*/
|
||||
ComputerInput getComputerInput();
|
||||
|
||||
/**
|
||||
* Start a file upload into this container.
|
||||
*
|
||||
|
||||
+15
-107
@@ -7,15 +7,14 @@ package dan200.computercraft.shared.computer.menu;
|
||||
import dan200.computercraft.core.apis.handles.ByteBufferChannel;
|
||||
import dan200.computercraft.core.apis.transfer.TransferredFile;
|
||||
import dan200.computercraft.core.apis.transfer.TransferredFiles;
|
||||
import dan200.computercraft.core.computer.ComputerEvents;
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
import dan200.computercraft.core.input.ComputerInput;
|
||||
import dan200.computercraft.core.input.UserComputerInput;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.upload.FileSlice;
|
||||
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||
import dan200.computercraft.shared.network.client.UploadResultMessage;
|
||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
@@ -23,7 +22,6 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -31,113 +29,31 @@ import java.util.UUID;
|
||||
* The default concrete implementation of {@link ServerInputHandler}.
|
||||
* <p>
|
||||
* This keeps track of the current key and mouse state, and releases them when the container is closed.
|
||||
*
|
||||
* @param <T> The type of container this server input belongs to.
|
||||
*/
|
||||
public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> implements ServerInputHandler {
|
||||
public class ServerInputState implements ServerInputHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ServerInputState.class);
|
||||
|
||||
private final T owner;
|
||||
private final IntSet keysDown = new IntOpenHashSet(4);
|
||||
|
||||
private int lastMouseX;
|
||||
private int lastMouseY;
|
||||
private int lastMouseDown = -1;
|
||||
private final AbstractContainerMenu owner;
|
||||
private final ServerComputer computer;
|
||||
private final UserComputerInput input;
|
||||
|
||||
private @Nullable UUID toUploadId;
|
||||
private @Nullable List<FileUpload> toUpload;
|
||||
|
||||
public ServerInputState(T owner) {
|
||||
public ServerInputState(AbstractContainerMenu owner, ServerComputer computer) {
|
||||
this.owner = owner;
|
||||
this.computer = computer;
|
||||
this.input = computer.createComputerInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyDown(int key, boolean repeat) {
|
||||
keysDown.add(key);
|
||||
ComputerEvents.keyDown(owner.getComputer(), key, repeat);
|
||||
public ComputerInput getComputerInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyUp(int key) {
|
||||
keysDown.remove(key);
|
||||
ComputerEvents.keyUp(owner.getComputer(), key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void charTyped(byte chr) {
|
||||
if (StringUtil.isTypableChar(chr)) ComputerEvents.charTyped(owner.getComputer(), chr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paste(ByteBuffer contents) {
|
||||
if (contents.remaining() > 0 && isValidClipboard(contents)) ComputerEvents.paste(owner.getComputer(), contents);
|
||||
}
|
||||
|
||||
private static boolean isValidClipboard(ByteBuffer buffer) {
|
||||
for (int i = buffer.position(), max = buffer.limit(); i < max; i++) {
|
||||
if (!StringUtil.isTypableChar(buffer.get(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClick(int button, int x, int y) {
|
||||
lastMouseX = x;
|
||||
lastMouseY = y;
|
||||
lastMouseDown = button;
|
||||
|
||||
ComputerEvents.mouseClick(owner.getComputer(), button, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseUp(int button, int x, int y) {
|
||||
lastMouseX = x;
|
||||
lastMouseY = y;
|
||||
lastMouseDown = -1;
|
||||
|
||||
ComputerEvents.mouseUp(owner.getComputer(), button, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDrag(int button, int x, int y) {
|
||||
lastMouseX = x;
|
||||
lastMouseY = y;
|
||||
lastMouseDown = button;
|
||||
|
||||
ComputerEvents.mouseDrag(owner.getComputer(), button, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseScroll(int direction, int x, int y) {
|
||||
lastMouseX = x;
|
||||
lastMouseY = y;
|
||||
|
||||
ComputerEvents.mouseScroll(owner.getComputer(), direction, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void terminate() {
|
||||
owner.getComputer().queueEvent("terminate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
owner.getComputer().shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void turnOn() {
|
||||
owner.getComputer().turnOn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reboot() {
|
||||
owner.getComputer().reboot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startUpload(UUID uuid, List<FileUpload> files) {
|
||||
toUploadId = uuid;
|
||||
public void startUpload(UUID uploadId, List<FileUpload> files) {
|
||||
toUploadId = uploadId;
|
||||
toUpload = files;
|
||||
}
|
||||
|
||||
@@ -162,7 +78,6 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
|
||||
}
|
||||
|
||||
private UploadResultMessage finishUpload(ServerPlayer player) {
|
||||
var computer = owner.getComputer();
|
||||
if (toUpload == null) {
|
||||
return UploadResultMessage.error(owner, UploadResult.COMPUTER_OFF_MSG);
|
||||
}
|
||||
@@ -187,13 +102,6 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
|
||||
}
|
||||
|
||||
public void close() {
|
||||
var computer = owner.getComputer();
|
||||
var keys = keysDown.iterator();
|
||||
while (keys.hasNext()) ComputerEvents.keyUp(computer, keys.nextInt());
|
||||
|
||||
if (lastMouseDown != -1) ComputerEvents.mouseUp(computer, lastMouseDown, lastMouseX, lastMouseY);
|
||||
|
||||
keysDown.clear();
|
||||
lastMouseDown = -1;
|
||||
input.releaseInputs();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -33,10 +33,10 @@ public class ComputerActionServerMessage extends ComputerServerMessage {
|
||||
@Override
|
||||
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
||||
switch (action) {
|
||||
case TERMINATE -> container.getInput().terminate();
|
||||
case TURN_ON -> container.getInput().turnOn();
|
||||
case REBOOT -> container.getInput().reboot();
|
||||
case SHUTDOWN -> container.getInput().shutdown();
|
||||
case TERMINATE -> container.getComputer().queueEvent("terminate");
|
||||
case TURN_ON -> container.getComputer().turnOn();
|
||||
case REBOOT -> container.getComputer().reboot();
|
||||
case SHUTDOWN -> container.getComputer().shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ public class KeyEventServerMessage extends ComputerServerMessage {
|
||||
|
||||
@Override
|
||||
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
||||
var input = container.getInput();
|
||||
var input = container.getInput().getComputerInput();
|
||||
switch (type) {
|
||||
case UP -> input.keyUp(key);
|
||||
case DOWN -> input.keyDown(key, false);
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ public class MouseEventServerMessage extends ComputerServerMessage {
|
||||
|
||||
@Override
|
||||
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
||||
var input = container.getInput();
|
||||
var input = container.getInput().getComputerInput();
|
||||
switch (type) {
|
||||
case CLICK -> input.mouseClick(arg, x, y);
|
||||
case DRAG -> input.mouseDrag(arg, x, y);
|
||||
|
||||
+3
-3
@@ -4,10 +4,10 @@
|
||||
|
||||
package dan200.computercraft.shared.network.server;
|
||||
|
||||
import dan200.computercraft.core.input.ComputerInput;
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||
import dan200.computercraft.shared.computer.menu.ServerInputHandler;
|
||||
import dan200.computercraft.shared.network.MessageType;
|
||||
import dan200.computercraft.shared.network.NetworkMessages;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
@@ -19,7 +19,7 @@ import java.nio.ByteBuffer;
|
||||
/**
|
||||
* Paste a string on a {@link ServerComputer}.
|
||||
*
|
||||
* @see ServerInputHandler#paste(ByteBuffer)
|
||||
* @see ComputerInput#paste(ByteBuffer)
|
||||
*/
|
||||
public class PasteEventComputerMessage extends ComputerServerMessage {
|
||||
private final ByteBuffer text;
|
||||
@@ -51,7 +51,7 @@ public class PasteEventComputerMessage extends ComputerServerMessage {
|
||||
|
||||
@Override
|
||||
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
||||
container.getInput().paste(text);
|
||||
container.getInput().getComputerInput().paste(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,7 +31,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
* <li>Passes main thread tasks to the {@link MainThreadScheduler.Executor}.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class Computer implements ComputerEvents.Receiver {
|
||||
public class Computer {
|
||||
private static final int START_DELAY = 50;
|
||||
|
||||
// Various properties of the computer
|
||||
@@ -114,7 +114,6 @@ public class Computer implements ComputerEvents.Receiver {
|
||||
executor.queueStop(false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueEvent(String event, @Nullable Object @Nullable [] args) {
|
||||
executor.queueEvent(event, args);
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Built-in events that can be queued on a computer.
|
||||
*/
|
||||
public final class ComputerEvents {
|
||||
private ComputerEvents() {
|
||||
}
|
||||
|
||||
public static void keyDown(Receiver receiver, int key, boolean repeat) {
|
||||
receiver.queueEvent("key", new Object[]{ key, repeat });
|
||||
}
|
||||
|
||||
public static void keyUp(Receiver receiver, int key) {
|
||||
receiver.queueEvent("key_up", new Object[]{ key });
|
||||
}
|
||||
|
||||
/**
|
||||
* Type a character on the computer.
|
||||
*
|
||||
* @param receiver The computer to queue the event on.
|
||||
* @param chr The character to type.
|
||||
* @see StringUtil#isTypableChar(byte)
|
||||
*/
|
||||
public static void charTyped(Receiver receiver, byte chr) {
|
||||
receiver.queueEvent("char", new Object[]{ new byte[]{ chr } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste a string.
|
||||
*
|
||||
* @param receiver The computer to queue the event on.
|
||||
* @param contents The string to paste.
|
||||
* @see StringUtil#getClipboardString(String)
|
||||
*/
|
||||
public static void paste(Receiver receiver, ByteBuffer contents) {
|
||||
receiver.queueEvent("paste", new Object[]{ contents });
|
||||
}
|
||||
|
||||
public static void mouseClick(Receiver receiver, int button, int x, int y) {
|
||||
receiver.queueEvent("mouse_click", new Object[]{ button, x, y });
|
||||
}
|
||||
|
||||
public static void mouseUp(Receiver receiver, int button, int x, int y) {
|
||||
receiver.queueEvent("mouse_up", new Object[]{ button, x, y });
|
||||
}
|
||||
|
||||
public static void mouseDrag(Receiver receiver, int button, int x, int y) {
|
||||
receiver.queueEvent("mouse_drag", new Object[]{ button, x, y });
|
||||
}
|
||||
|
||||
public static void mouseScroll(Receiver receiver, int direction, int x, int y) {
|
||||
receiver.queueEvent("mouse_scroll", new Object[]{ direction, x, y });
|
||||
}
|
||||
|
||||
/**
|
||||
* An object that can receive computer events.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Receiver {
|
||||
void queueEvent(String event, @Nullable Object @Nullable [] arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.input;
|
||||
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Input events that can be performed on a computer.
|
||||
*
|
||||
* @see EventComputerInput
|
||||
* @see UserComputerInput
|
||||
*/
|
||||
public interface ComputerInput {
|
||||
/**
|
||||
* Queue a {@code key} event.
|
||||
*
|
||||
* @param key The key that was pressed.
|
||||
* @param repeat Whether this is a repeat input.
|
||||
*/
|
||||
void keyDown(int key, boolean repeat);
|
||||
|
||||
/**
|
||||
* Queue a {@code key_up} event.
|
||||
*
|
||||
* @param key The key that was released.
|
||||
*/
|
||||
void keyUp(int key);
|
||||
|
||||
/**
|
||||
* Type a character on the computer.
|
||||
*
|
||||
* @param chr The character to type.
|
||||
* @see StringUtil#isTypableChar(byte)
|
||||
*/
|
||||
void charTyped(byte chr);
|
||||
|
||||
/**
|
||||
* Paste a string.
|
||||
*
|
||||
* @param contents The string to paste.
|
||||
* @see StringUtil#getClipboardString(String)
|
||||
*/
|
||||
void paste(ByteBuffer contents);
|
||||
|
||||
/**
|
||||
* Queue a {@code mouse_click} event.
|
||||
*
|
||||
* @param button The mouse button that was pressed, between 1 and 3 (inclusive).
|
||||
* @param x The x coordinate of the mouse, between 1 and the terminal width (inclusive).
|
||||
* @param y The y coordinate of the mouse, between 1 and the terminal height (inclusive).
|
||||
*/
|
||||
void mouseClick(int button, int x, int y);
|
||||
|
||||
/**
|
||||
* Queue a {@code mouse_up} event.
|
||||
*
|
||||
* @param button The mouse button that was released, between 1 and 3 (inclusive).
|
||||
* @param x The x coordinate of the mouse, between 1 and the terminal width (inclusive).
|
||||
* @param y The y coordinate of the mouse, between 1 and the terminal height (inclusive).
|
||||
*/
|
||||
void mouseUp(int button, int x, int y);
|
||||
|
||||
/**
|
||||
* Queue a {@code mouse_drag} event.
|
||||
*
|
||||
* @param button The mouse button that is being pressed, between 1 and 3 (inclusive).
|
||||
* @param x The x coordinate of the mouse, between 1 and the terminal width (inclusive).
|
||||
* @param y The y coordinate of the mouse, between 1 and the terminal height (inclusive).
|
||||
*/
|
||||
void mouseDrag(int button, int x, int y);
|
||||
|
||||
/**
|
||||
* Queue a {@code mouse_scroll} event.
|
||||
*
|
||||
* @param direction The direction of the scroll, where negative values are up and positive ones are down.
|
||||
* @param x The x coordinate of the mouse, between 1 and the terminal width (inclusive).
|
||||
* @param y The y coordinate of the mouse, between 1 and the terminal height (inclusive).
|
||||
*/
|
||||
void mouseScroll(int direction, int x, int y);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.input;
|
||||
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A {@link ComputerInput} that queues events on the computer.
|
||||
*/
|
||||
public final class EventComputerInput implements ComputerInput {
|
||||
private final QueueEvent receiver;
|
||||
|
||||
public EventComputerInput(QueueEvent receiver) {
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
public EventComputerInput(Computer computer) {
|
||||
this(computer::queueEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyDown(int key, boolean repeat) {
|
||||
receiver.queueEvent("key", new Object[]{ key, repeat });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyUp(int key) {
|
||||
receiver.queueEvent("key_up", new Object[]{ key });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void charTyped(byte chr) {
|
||||
receiver.queueEvent("char", new Object[]{ new byte[]{ chr } });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paste(ByteBuffer contents) {
|
||||
receiver.queueEvent("paste", new Object[]{ contents });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClick(int button, int x, int y) {
|
||||
receiver.queueEvent("mouse_click", new Object[]{ button, x, y });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseUp(int button, int x, int y) {
|
||||
receiver.queueEvent("mouse_up", new Object[]{ button, x, y });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDrag(int button, int x, int y) {
|
||||
receiver.queueEvent("mouse_drag", new Object[]{ button, x, y });
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseScroll(int direction, int x, int y) {
|
||||
receiver.queueEvent("mouse_scroll", new Object[]{ direction, x, y });
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to queue events.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QueueEvent {
|
||||
void queueEvent(String event, @Nullable Object @Nullable [] arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.core.input;
|
||||
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A {@link ComputerInput} that wraps an existing {@link ComputerInput}. This both validates any user inputs (e.g.
|
||||
* ensuring mouse presses only happen on an advanced terminal), and supports {@linkplain #releaseInputs()
|
||||
* releasing any held inputs} (e.g. for when the computer loses focus).
|
||||
*/
|
||||
public final class UserComputerInput implements ComputerInput {
|
||||
private final ComputerInput delegate;
|
||||
private final boolean mouseSupport;
|
||||
private final int termWidth;
|
||||
private final int termHeight;
|
||||
|
||||
private final IntSet keysDown = new IntOpenHashSet(4);
|
||||
|
||||
private int lastMouseX;
|
||||
private int lastMouseY;
|
||||
private int lastMouseDown = -1;
|
||||
|
||||
public UserComputerInput(ComputerInput delegate, boolean mouseSupport, int termWidth, int termHeight) {
|
||||
this.delegate = delegate;
|
||||
this.mouseSupport = mouseSupport;
|
||||
this.termWidth = termWidth;
|
||||
this.termHeight = termHeight;
|
||||
}
|
||||
|
||||
public UserComputerInput(ComputerInput delegate, Terminal terminal) {
|
||||
this(delegate, terminal.isColour(), terminal.getWidth(), terminal.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyDown(int key, boolean repeat) {
|
||||
if (key < 0) return;
|
||||
|
||||
keysDown.add(key);
|
||||
delegate.keyDown(key, repeat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a {@code key} event on the computer. This behaves the same as {@link #keyDown(int, boolean)}, but infers
|
||||
* the {@code "repeat} state from the currently held keys.
|
||||
*
|
||||
* @param key The key to press.
|
||||
*/
|
||||
public void keyDown(int key) {
|
||||
keyDown(key, keysDown.contains(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyUp(int key) {
|
||||
if (key < 0) return;
|
||||
|
||||
keysDown.remove(key);
|
||||
delegate.keyUp(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void charTyped(byte chr) {
|
||||
delegate.charTyped(chr);
|
||||
}
|
||||
|
||||
public void codepointTyped(int codepoint) {
|
||||
var terminalChar = StringUtil.unicodeToTerminal(codepoint);
|
||||
if (StringUtil.isTypableChar(terminalChar)) charTyped((byte) terminalChar);
|
||||
}
|
||||
|
||||
private static boolean isValidClipboard(ByteBuffer buffer) {
|
||||
for (int i = buffer.position(), max = buffer.limit(); i < max; i++) {
|
||||
if (!StringUtil.isTypableChar(buffer.get(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paste(ByteBuffer contents) {
|
||||
if (contents.remaining() > 0 && isValidClipboard(contents)) delegate.paste(contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste a string.
|
||||
*
|
||||
* @param contents The string to paste.
|
||||
*/
|
||||
public void paste(String contents) {
|
||||
paste(StringUtil.getClipboardString(contents));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClick(int button, int x, int y) {
|
||||
if (!mouseSupport || button < 1 || button > 3) return;
|
||||
var clampedX = lastMouseX = Math.min(Math.max(x, 1), termWidth);
|
||||
var clampedY = lastMouseY = Math.min(Math.max(y, 1), termHeight);
|
||||
|
||||
delegate.mouseClick(button, clampedX, clampedY);
|
||||
lastMouseDown = button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a {@code mouse_click} event on the computer. This behaves the same as {@link #mouseClick(int, int, int)},
|
||||
* but infers the mouse position from the last mouse position.
|
||||
*
|
||||
* @param button The mouse button pressed, between 1 and 3.
|
||||
*/
|
||||
public void mouseClick(int button) {
|
||||
mouseClick(button, lastMouseX, lastMouseY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseUp(int button, int x, int y) {
|
||||
if (!mouseSupport || button < 1 || button > 3) return;
|
||||
var clampedX = lastMouseX = Math.min(Math.max(x, 1), termWidth);
|
||||
var clampedY = lastMouseY = Math.min(Math.max(y, 1), termHeight);
|
||||
|
||||
if (lastMouseDown == button) {
|
||||
delegate.mouseUp(button, clampedX, clampedY);
|
||||
lastMouseDown = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a {@code mouse_scroll} event on the computer. This behaves the same as {@link #mouseUp(int, int, int)},
|
||||
* but infers the mouse position from the last mouse position.
|
||||
*
|
||||
* @param button The mouse button released, between 1 and 3.
|
||||
*/
|
||||
public void mouseUp(int button) {
|
||||
mouseUp(button, lastMouseX, lastMouseY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDrag(int button, int x, int y) {
|
||||
if (!mouseSupport || button < 1 || button > 3) return;
|
||||
var clampedX = Math.min(Math.max(x, 1), termWidth);
|
||||
var clampedY = Math.min(Math.max(y, 1), termHeight);
|
||||
|
||||
if (button == lastMouseDown && (clampedX != lastMouseX || clampedY != lastMouseY)) {
|
||||
delegate.mouseDrag(button, clampedX, clampedY);
|
||||
lastMouseX = clampedX;
|
||||
lastMouseY = clampedY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the mouse position, and optionally queue a {@code mouse_drag} event on the computer.
|
||||
* <p>
|
||||
* This is similar to {@link #mouseDrag(int, int, int)}, but when the currently clicked button is not available.
|
||||
*
|
||||
* @param x The X position of the mouse, between 1 and the terminal width.
|
||||
* @param y The Y position of the mouse, between 1 and the terminal width.
|
||||
*/
|
||||
public void mouseMove(int x, int y) {
|
||||
if (!mouseSupport) return;
|
||||
var clampedX = Math.min(Math.max(x, 1), termWidth);
|
||||
var clampedY = Math.min(Math.max(y, 1), termHeight);
|
||||
|
||||
if (lastMouseDown != -1 && (clampedX != lastMouseX || clampedY != lastMouseY)) {
|
||||
delegate.mouseDrag(lastMouseDown, clampedX, clampedY);
|
||||
}
|
||||
|
||||
lastMouseX = clampedX;
|
||||
lastMouseY = clampedY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseScroll(int direction, int x, int y) {
|
||||
if (!mouseSupport || direction == 0) return;
|
||||
var clampedX = lastMouseX = Math.min(Math.max(x, 1), termWidth);
|
||||
var clampedY = lastMouseY = Math.min(Math.max(y, 1), termHeight);
|
||||
|
||||
delegate.mouseScroll(direction, clampedX, clampedY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a {@code mouse_scroll} event on the computer. This behaves the same as {@link #mouseScroll(int, int, int)},
|
||||
* but infers the mouse position from the last mouse position.
|
||||
*
|
||||
* @param direction The direction of the scroll.
|
||||
*/
|
||||
public void mouseScroll(int direction) {
|
||||
mouseScroll(direction, lastMouseX, lastMouseY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all currently held inputs, such as held keys and pressed mouse buttons.
|
||||
*/
|
||||
public void releaseInputs() {
|
||||
// Release all keys
|
||||
var keys = keysDown.iterator();
|
||||
while (keys.hasNext()) delegate.keyUp(keys.nextInt());
|
||||
keysDown.clear();
|
||||
|
||||
// Release last held mouse button.
|
||||
if (lastMouseDown != -1) {
|
||||
delegate.mouseUp(lastMouseDown, lastMouseX, lastMouseY);
|
||||
lastMouseDown = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
package dan200.computercraft.core.util;
|
||||
|
||||
import dan200.computercraft.core.computer.ComputerEvents;
|
||||
import dan200.computercraft.core.input.ComputerInput;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@@ -71,8 +71,8 @@ public final class StringUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character is capable of being input and passed to a {@linkplain ComputerEvents#charTyped(ComputerEvents.Receiver, byte)
|
||||
* "char" event}.
|
||||
* Check if a character is capable of being input and passed to a {@linkplain ComputerInput#charTyped(byte) "char"
|
||||
* event}.
|
||||
*
|
||||
* @param chr The character to check.
|
||||
* @return Whether this character can be typed.
|
||||
@@ -82,8 +82,8 @@ public final class StringUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character is capable of being input and passed to a {@linkplain ComputerEvents#charTyped(ComputerEvents.Receiver, byte)
|
||||
* "char" event}.
|
||||
* Check if a character is capable of being input and passed to a {@linkplain ComputerInput#charTyped(byte) "char"
|
||||
* * event}.
|
||||
*
|
||||
* @param chr The character to check.
|
||||
* @return Whether this character can be typed.
|
||||
|
||||
@@ -8,8 +8,8 @@ import dan200.computercraft.core.apis.handles.ArrayByteChannel;
|
||||
import dan200.computercraft.core.apis.transfer.TransferredFile;
|
||||
import dan200.computercraft.core.apis.transfer.TransferredFiles;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.ComputerEvents;
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
import dan200.computercraft.core.input.EventComputerInput;
|
||||
import dan200.computercraft.core.input.UserComputerInput;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWDropCallback;
|
||||
import org.lwjgl.glfw.GLFWKeyCallbackI;
|
||||
@@ -21,7 +21,6 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -35,23 +34,19 @@ public class InputState {
|
||||
private static final float KEY_SUPPRESS_DELAY = 0.2f;
|
||||
|
||||
private final Computer computer;
|
||||
private final BitSet keysDown = new BitSet(256);
|
||||
private final UserComputerInput input;
|
||||
|
||||
private float terminateTimer = -1;
|
||||
private float rebootTimer = -1;
|
||||
private float shutdownTimer = -1;
|
||||
|
||||
private int lastMouseButton = -1;
|
||||
private int lastMouseX = -1;
|
||||
private int lastMouseY = -1;
|
||||
|
||||
public InputState(Computer computer) {
|
||||
this.computer = computer;
|
||||
this.input = new UserComputerInput(new EventComputerInput(computer), computer.getEnvironment().getTerminal());
|
||||
}
|
||||
|
||||
public void onCharEvent(int codepoint) {
|
||||
var terminalChar = StringUtil.unicodeToTerminal(codepoint);
|
||||
if (StringUtil.isTypableChar(terminalChar)) ComputerEvents.charTyped(computer, (byte) terminalChar);
|
||||
input.codepointTyped(codepoint);
|
||||
}
|
||||
|
||||
public void onKeyEvent(long window, int key, int action, int modifiers) {
|
||||
@@ -66,10 +61,7 @@ public class InputState {
|
||||
|
||||
if (key == GLFW.GLFW_KEY_V && modifiers == GLFW.GLFW_MOD_CONTROL) {
|
||||
var string = GLFW.glfwGetClipboardString(window);
|
||||
if (string != null) {
|
||||
var clipboard = StringUtil.getClipboardString(string);
|
||||
if (clipboard.remaining() > 0) ComputerEvents.paste(computer, clipboard);
|
||||
}
|
||||
if (string != null) input.paste(string);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,19 +80,12 @@ public class InputState {
|
||||
}
|
||||
|
||||
if (key >= 0 && terminateTimer < KEY_SUPPRESS_DELAY && rebootTimer < KEY_SUPPRESS_DELAY && shutdownTimer < KEY_SUPPRESS_DELAY) {
|
||||
// Queue the "key" event and add to the down set
|
||||
var repeat = keysDown.get(key);
|
||||
keysDown.set(key);
|
||||
ComputerEvents.keyDown(computer, key, repeat);
|
||||
input.keyDown(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void keyReleased(int key) {
|
||||
// Queue the "key_up" event and remove from the down set
|
||||
if (key >= 0 && keysDown.get(key)) {
|
||||
keysDown.set(key, false);
|
||||
ComputerEvents.keyUp(computer, key);
|
||||
}
|
||||
input.keyUp(key);
|
||||
|
||||
switch (key) {
|
||||
case GLFW.GLFW_KEY_T -> terminateTimer = -1;
|
||||
@@ -113,33 +98,17 @@ public class InputState {
|
||||
|
||||
public void onMouseClick(int button, int action) {
|
||||
switch (action) {
|
||||
case GLFW.GLFW_PRESS -> {
|
||||
ComputerEvents.mouseClick(computer, button + 1, lastMouseX + 1, lastMouseY + 1);
|
||||
lastMouseButton = button;
|
||||
}
|
||||
case GLFW.GLFW_RELEASE -> {
|
||||
if (button == lastMouseButton) {
|
||||
ComputerEvents.mouseUp(computer, button + 1, lastMouseX + 1, lastMouseY + 1);
|
||||
lastMouseButton = -1;
|
||||
}
|
||||
}
|
||||
case GLFW.GLFW_PRESS -> input.mouseClick(button + 1);
|
||||
case GLFW.GLFW_RELEASE -> input.mouseUp(button + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMouseMove(int mouseX, int mouseY) {
|
||||
if (mouseX == lastMouseX && mouseY == lastMouseY) return;
|
||||
|
||||
lastMouseX = mouseX;
|
||||
lastMouseY = mouseY;
|
||||
if (lastMouseButton != -1) {
|
||||
ComputerEvents.mouseDrag(computer, lastMouseButton + 1, mouseX + 1, mouseY + 1);
|
||||
}
|
||||
input.mouseMove(mouseX + 1, mouseY + 1);
|
||||
}
|
||||
|
||||
public void onMouseScroll(double yOffset) {
|
||||
if (yOffset != 0) {
|
||||
ComputerEvents.mouseScroll(computer, yOffset < 0 ? 1 : -1, lastMouseX + 1, lastMouseY + 1);
|
||||
}
|
||||
if (yOffset != 0) input.mouseScroll(yOffset < 0 ? 1 : -1);
|
||||
}
|
||||
|
||||
public void onFileDrop(int count, long names) {
|
||||
|
||||
@@ -313,8 +313,6 @@ public class Main {
|
||||
glfwSetCursorPosCallback(window, (w, x, y) -> {
|
||||
var charX = (int) (((x / SCALE) - MARGIN) / PIXEL_WIDTH);
|
||||
var charY = (int) (((y / SCALE) - MARGIN) / PIXEL_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
inputState.onMouseMove(charX, charY);
|
||||
});
|
||||
glfwSetScrollCallback(window, (w, xOffset, yOffset) -> inputState.onMouseScroll(yOffset));
|
||||
|
||||
Reference in New Issue
Block a user