mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-19 03:55:11 +01:00
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:
@@ -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"])
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user