From a4be13ac625f8bf669e2c7b4cd83fffab4759b07 Mon Sep 17 00:00:00 2001
From: Watermelon914 <37270891+Watermelon914@users.noreply.github.com>
Date: Sun, 26 Sep 2021 00:40:24 +0100
Subject: [PATCH] Adds a lot of QoL to the integrated circuit UI. (#61677)
Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com>
---
.../wiremod/core/integrated_circuit.dm | 9 +
code/modules/wiremod/core/port.dm | 9 +-
.../IntegratedCircuit/VariableMenu.js | 281 +++++++++++-------
.../interfaces/IntegratedCircuit/constants.js | 2 +
.../interfaces/IntegratedCircuit/index.js | 139 +++++++--
5 files changed, 317 insertions(+), 123 deletions(-)
diff --git a/code/modules/wiremod/core/integrated_circuit.dm b/code/modules/wiremod/core/integrated_circuit.dm
index 9315db39c51..ffc74054dfc 100644
--- a/code/modules/wiremod/core/integrated_circuit.dm
+++ b/code/modules/wiremod/core/integrated_circuit.dm
@@ -570,8 +570,17 @@ GLOBAL_LIST_EMPTY_TYPED(integrated_circuits, /obj/item/integrated_circuit)
if(!add_component(component, usr))
qdel(component)
return
+ if(params["is_setter"])
+ var/obj/item/circuit_component/setter/setter = component
+ setter.variable_name.set_input(params["variable"])
+ else
+ var/obj/item/circuit_component/getter/getter = component
+ getter.variable_name.set_input(params["variable"])
+ component.rel_x = text2num(params["rel_x"])
+ component.rel_y = text2num(params["rel_y"])
RegisterSignal(component, COMSIG_CIRCUIT_COMPONENT_REMOVED, .proc/clear_setter_or_getter)
setter_and_getter_count++
+ return TRUE
if("move_screen")
screen_x = text2num(params["screen_x"])
screen_y = text2num(params["screen_y"])
diff --git a/code/modules/wiremod/core/port.dm b/code/modules/wiremod/core/port.dm
index 78b5ad846fb..61058bd5dab 100644
--- a/code/modules/wiremod/core/port.dm
+++ b/code/modules/wiremod/core/port.dm
@@ -168,10 +168,15 @@
src.connected_ports = list()
/**
- * Introduces two ports to one another.
+ * Connects an input port to an output port.
+ *
+ * Arguments:
+ * * output - The output port to connect to.
*/
/datum/port/input/proc/connect(datum/port/output/output)
- connected_ports |= output
+ if(output in connected_ports)
+ return
+ connected_ports += output
RegisterSignal(output, COMSIG_PORT_SET_VALUE, .proc/receive_value)
RegisterSignal(output, COMSIG_PORT_SET_TYPE, .proc/check_type)
RegisterSignal(output, COMSIG_PORT_DISCONNECT, .proc/disconnect)
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/VariableMenu.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/VariableMenu.js
index 03951705de5..f76efc68292 100644
--- a/tgui/packages/tgui/interfaces/IntegratedCircuit/VariableMenu.js
+++ b/tgui/packages/tgui/interfaces/IntegratedCircuit/VariableMenu.js
@@ -1,4 +1,3 @@
-import { useLocalState } from '../../backend';
import {
Box,
Stack,
@@ -6,109 +5,191 @@ import {
Button,
Input,
Dropdown,
+ Icon,
} from '../../components';
+import { Component } from 'inferno';
+import { shallowDiffers } from 'common/react';
-export const VariableMenu = (props, context) => {
- const {
- variables,
- onAddVariable,
- onRemoveVariable,
- handleAddSetter,
- handleAddGetter,
- types,
- ...rest
- } = props;
+export class VariableMenu extends Component {
+ constructor() {
+ super();
+ this.state = {
+ variable_name: "",
+ variable_type: "any",
+ };
+ }
- const [name, setName] = useLocalState(context, "variable_name", null);
- const [type, setType] = useLocalState(context, "variable_type", types[1]);
+ shouldComponentUpdate(nextProps, nextState) {
+ if (shallowDiffers(this.state, nextState)) {
+ return true;
+ }
- return (
-
-
-
-
-
- {variables.map(val => (
-
-
-
-
-
- {val.name}
-
-
-
-
-
-
-
-
-
+ const { variables } = this.props;
+ if (variables.length !== nextProps.variables.length) {
+ return true;
+ }
+ for (let i = 0; i < variables.length; i++) {
+ if (shallowDiffers(variables[i], nextProps.variables[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ render() {
+ const {
+ variables,
+ onAddVariable,
+ onRemoveVariable,
+ onClose,
+ handleMouseDownSetter,
+ handleMouseDownGetter,
+ types,
+ ...rest
+ } = this.props;
+ const {
+ variable_name,
+ variable_type,
+ } = this.state;
+
+ return (
+
+ }
+ >
+
+
+
+
+ {variables.map(val => (
+
+
+
+
+
+ {val.name}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ this.setState({
+ variable_name: nameVal,
+ })}
+ />
- ))}
-
-
-
-
-
-
-
-
+
+
+
+ this.setState({
+ variable_type: selectedVal,
+ })}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+const PlusIconButton = (props, context) => {
+ return (
+
+
+
+
+
);
};
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/constants.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/constants.js
index 6f6a7c72932..0c13b0fa660 100644
--- a/tgui/packages/tgui/interfaces/IntegratedCircuit/constants.js
+++ b/tgui/packages/tgui/interfaces/IntegratedCircuit/constants.js
@@ -4,3 +4,5 @@ export const SVG_CURVE_INTENSITY = 64;
export const MOUSE_BUTTON_LEFT = 0;
export const OPTION_DROPDOWN_LARGE_CHAR_AMOUNT = 12;
+// in milliseconds
+export const TIME_UNTIL_PORT_RELEASE_WORKS = 100;
diff --git a/tgui/packages/tgui/interfaces/IntegratedCircuit/index.js b/tgui/packages/tgui/interfaces/IntegratedCircuit/index.js
index c24c4a15b20..89d86e2d6d6 100644
--- a/tgui/packages/tgui/interfaces/IntegratedCircuit/index.js
+++ b/tgui/packages/tgui/interfaces/IntegratedCircuit/index.js
@@ -10,7 +10,7 @@ import { Component } from 'inferno';
import { Window } from '../../layouts';
import { resolveAsset } from '../../assets';
import { CircuitInfo } from './CircuitInfo';
-import { ABSOLUTE_Y_OFFSET, MOUSE_BUTTON_LEFT } from './constants';
+import { ABSOLUTE_Y_OFFSET, MOUSE_BUTTON_LEFT, TIME_UNTIL_PORT_RELEASE_WORKS } from './constants';
import { Connections } from './Connections';
import { ObjectComponent } from './ObjectComponent';
import { VariableMenu } from './VariableMenu';
@@ -39,6 +39,10 @@ export class IntegratedCircuit extends Component {
this.handlePortRelease = this.handlePortRelease.bind(this);
this.handleZoomChange = this.handleZoomChange.bind(this);
this.handleBackgroundMoved = this.handleBackgroundMoved.bind(this);
+
+ this.onVarClickedSetter = this.onVarClickedSetter.bind(this);
+ this.onVarClickedGetter = this.onVarClickedGetter.bind(this);
+ this.handleVarDropped = this.handleVarDropped.bind(this);
}
// Helper function to get an element's exact position
@@ -82,6 +86,11 @@ export class IntegratedCircuit extends Component {
}
handlePortClick(portIndex, componentId, port, isOutput, event) {
+ if (this.state.selectedPort) {
+ this.handlePortUp(portIndex, componentId, port, isOutput, event);
+ return;
+ }
+
if (event.button !== MOUSE_BUTTON_LEFT) {
return;
}
@@ -98,6 +107,9 @@ export class IntegratedCircuit extends Component {
this.handlePortDrag(event);
+ this.timeUntilPortReleaseTimesOut
+ = Date.now() + TIME_UNTIL_PORT_RELEASE_WORKS;
+
window.addEventListener('mousemove', this.handlePortDrag);
window.addEventListener('mouseup', this.handlePortRelease);
}
@@ -105,7 +117,7 @@ export class IntegratedCircuit extends Component {
// mouse up called whilst over a port. This means we can check if selectedPort
// exists and do perform some actions if it does.
handlePortUp(portIndex, componentId, port, isOutput, event) {
- const { act } = useBackend(this.context);
+ const { act, data: uiData } = useBackend(this.context);
const {
selectedPort,
} = this.state;
@@ -115,6 +127,9 @@ export class IntegratedCircuit extends Component {
if (selectedPort.is_output === isOutput) {
return;
}
+ this.setState({
+ selectedPort: null,
+ });
let data;
if (isOutput) {
data = {
@@ -132,6 +147,26 @@ export class IntegratedCircuit extends Component {
};
}
act("add_connection", data);
+
+ const { components } = uiData;
+ const {
+ input_component_id,
+ input_port_id,
+ output_component_id,
+ output_port_id,
+ } = data;
+
+ const input_comp = components[input_component_id-1];
+ const input_port = input_comp.input_ports[input_port_id-1];
+ const output_comp = components[output_component_id-1];
+ const output_port = output_comp.output_ports[output_port_id-1];
+ // Do not predict ports that do not match because there is no guarantee
+ // that they will properly match.
+ // TODO: Implement proper prediction for this
+ if (!input_port || input_port.type !== output_port.type) {
+ return;
+ }
+ input_port.connected_to.push(isOutput? port.ref : selectedPort.ref);
}
handlePortDrag(event) {
@@ -144,12 +179,20 @@ export class IntegratedCircuit extends Component {
}
handlePortRelease(event) {
+ window.removeEventListener('mouseup', this.handlePortRelease);
+
+ // This will let players release their mouse when dragging
+ // to stop connecting the port, whilst letting players
+ // click on the port to click and connect.
+ if (this.timeUntilPortReleaseTimesOut > Date.now()) {
+ return;
+ }
+
this.setState({
selectedPort: null,
});
window.removeEventListener('mousemove', this.handlePortDrag);
- window.removeEventListener('mouseup', this.handlePortRelease);
}
handlePortRightClick(portIndex, componentId, port, isOutput, event) {
@@ -174,11 +217,6 @@ export class IntegratedCircuit extends Component {
backgroundX: newX,
backgroundY: newY,
});
- if (this.state.menuOpen) {
- this.setState({
- menuOpen: false,
- });
- }
}
componentDidMount() {
@@ -197,6 +235,10 @@ export class IntegratedCircuit extends Component {
if (examined_name) {
act('remove_examined_component');
}
+
+ if (this.state.selectedPort) {
+ this.handlePortRelease(event);
+ }
}
handleMouseUp(event) {
@@ -210,6 +252,55 @@ export class IntegratedCircuit extends Component {
}
}
+ onVarClickedSetter(event, variable) {
+ this.handleVarClicked(event, variable, true);
+ }
+
+ onVarClickedGetter(event, variable) {
+ this.handleVarClicked(event, variable, false);
+ }
+
+ handleVarClicked(event, variable, is_setter) {
+ this.setState({
+ draggingVariable: variable,
+ variableIsSetter: is_setter,
+ });
+
+ window.addEventListener('mouseup', this.handleVarDropped);
+ }
+
+ handleVarDropped(event) {
+ const { data, act } = useBackend(this.context);
+ const {
+ draggingVariable,
+ variableIsSetter,
+ backgroundX,
+ backgroundY,
+ zoom,
+ } = this.state;
+ const {
+ screen_x,
+ screen_y,
+ } = data;
+
+ const xPos = (event.clientX - (backgroundX || screen_x));
+ const yPos = (event.clientY - (backgroundY || screen_y));
+
+ act("add_setter_or_getter", {
+ variable: draggingVariable,
+ is_setter: variableIsSetter,
+ rel_x: xPos*Math.pow(zoom, -1),
+ rel_y: (yPos + ABSOLUTE_Y_OFFSET)*Math.pow(zoom, -1),
+ });
+
+ this.setState({
+ draggingVariable: null,
+ variableIsSetter: null,
+ });
+
+ window.removeEventListener('mouseup', this.handleVarDropped);
+ }
+
render() {
const { act, data } = useBackend(this.context);
const {
@@ -263,8 +354,8 @@ export class IntegratedCircuit extends Component {
return (
this.setState({ menuOpen: false })}
onAddVariable={(name, type, event) => act("add_variable", {
variable_name: name,
variable_datatype: type,
@@ -370,12 +468,11 @@ export class IntegratedCircuit extends Component {
onRemoveVariable={(name, event) => act("remove_variable", {
variable_name: name,
})}
- handleAddSetter={(e) => act("add_setter_or_getter", {
- is_setter: true,
- })}
- handleAddGetter={(e) => act("add_setter_or_getter", {
- is_setter: false,
- })}
+ handleMouseDownSetter={this.onVarClickedSetter}
+ handleMouseDownGetter={this.onVarClickedGetter}
+ style={{
+ "border-radius": "0px 32px 0px 0px",
+ }}
/>
)}