Adds a lot of QoL to the integrated circuit UI. (#61677)

Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com>
This commit is contained in:
Watermelon914
2021-09-26 00:40:24 +01:00
committed by GitHub
parent 07d8766fe7
commit a4be13ac62
5 changed files with 317 additions and 123 deletions
@@ -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"])
+7 -2
View File
@@ -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)
@@ -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 (
<Section
title="Variable Options"
{...rest}
fill
height="100%"
>
<Stack height="100%">
<Stack.Item grow={1} mr={2}>
<Section fill scrollable>
<Stack vertical>
{variables.map(val => (
<Stack.Item key={val.name}>
<Box backgroundColor="transparent" px="1px" py="1px" height="100%">
<Stack align="center">
<Stack.Item grow={1}>
<Box textAlign="center">
{val.name}
</Box>
</Stack.Item>
<Stack.Item minWidth="80px">
<Button
textAlign="center"
fluid
color={val.color}
>
{val.datatype}
</Button>
</Stack.Item>
<Stack.Item>
<Button
icon="times"
color="bad"
onClick={(e) => onRemoveVariable(val.name, e)}
/>
</Stack.Item>
</Stack>
</Box>
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 (
<Section
title="Variable Options"
{...rest}
fill
buttons={
<Button
icon="times"
color="transparent"
mr={2}
onClick={onClose}
/>
}
>
<Stack height="100%">
<Stack.Item grow={1} mr={2}>
<Section fill scrollable>
<Stack vertical fill>
{variables.map(val => (
<Stack.Item key={val.name}>
<Box backgroundColor="transparent" px="1px" py="1px" height="100%">
<Stack>
<Stack.Item basis="50%" grow>
<Box width="100%" overflow="hidden">
{val.name}
</Box>
</Stack.Item>
<Stack.Item minWidth="80px">
<Button
textAlign="center"
fluid
color={val.color}
>
{val.datatype}
</Button>
</Stack.Item>
<Stack.Item>
<Button
fluid
onMouseDown={(e) =>
handleMouseDownSetter(e, val.name)}
color={val.color}
tooltip={
multiline`
Drag me onto the circuit's grid
to make a setter for this variable`
}
icon="hammer"
/>
</Stack.Item>
<Stack.Item>
<Button
fluid
tooltip={
multiline`
Drag me onto the circuit's grid
to make a getter for this variable`
}
color={val.color}
onMouseDown={(e) =>
handleMouseDownGetter(e, val.name)}
icon="eye"
/>
</Stack.Item>
<Stack.Item>
<Button
icon="times"
color="bad"
onClick={(e) => onRemoveVariable(val.name, e)}
/>
</Stack.Item>
</Stack>
</Box>
</Stack.Item>
))}
</Stack>
</Section>
</Stack.Item>
<Stack.Item height="100%" width="25%">
<Box height="100%">
<Stack vertical fill>
<Stack.Item>
<Input
placeholder="Name"
fluid
onChange={(e, nameVal) => this.setState({
variable_name: nameVal,
})}
/>
</Stack.Item>
))}
</Stack>
</Section>
</Stack.Item>
<Stack.Item height="100%" width="25%">
<Section fill height="100%">
<Stack vertical fill>
<Stack.Item>
<Input
placeholder="Name"
fluid
onInput={(e, nameVal) => setName(nameVal)}
/>
</Stack.Item>
<Stack.Item>
<Dropdown
options={types}
width="100%"
onSelected={(selectedVal) => setType(selectedVal)}
/>
</Stack.Item>
<Stack.Item grow={1}>
<Button
content="Add Variable"
onClick={(e) => onAddVariable(name, type, e)}
fluid
/>
</Stack.Item>
<Stack.Item>
<Button
content="Add Setter"
fluid
icon="plus"
onClick={handleAddSetter}
/>
</Stack.Item>
<Stack.Item>
<Button
content="Add Getter"
fluid
icon="plus"
onClick={handleAddGetter}
/>
</Stack.Item>
</Stack>
</Section>
</Stack.Item>
</Stack>
</Section>
<Stack.Item>
<Stack fill>
<Stack.Item grow>
<Dropdown
options={types}
displayText={variable_type}
className="Datatype__Option"
color="black"
width="100%"
over
onSelected={(selectedVal) => this.setState({
variable_type: selectedVal,
})}
/>
</Stack.Item>
<Stack.Item>
<Button
height="100%"
color="green"
onClick={(e) =>
onAddVariable(variable_name, variable_type, e)}
fluid
>
<PlusIconButton />
</Button>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Box>
</Stack.Item>
</Stack>
</Section>
);
}
}
const PlusIconButton = (props, context) => {
return (
<Stack fill align="center">
<Stack.Item grow>
<Icon
name="plus"
size={1}
width="100%"
m="0em"
/>
</Stack.Item>
</Stack>
);
};
@@ -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;
@@ -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 (
<Window
width={600}
height={600}
width={1200}
height={800}
buttons={(
<Box
width="160px"
@@ -353,16 +444,23 @@ export class IntegratedCircuit extends Component {
{!!menuOpen && (
<Box
position="absolute"
bottom={0}
left={0}
height="50%"
minHeight="300px"
width="100%"
backgroundColor="#202020"
bottom={0}
height="20%"
minHeight="175px"
minWidth="600px"
width="50%"
style={{
"border-radius": "0px 32px 0px 0px",
"background-color": "rgba(0, 0, 0, 0.3)",
"-ms-user-select": "none",
}}
unselectable="on"
>
<VariableMenu
variables={variables}
types={global_basic_types}
onClose={(event) => 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",
}}
/>
</Box>
)}