Skip to content

Commit 4e37d91

Browse files
committed
Refactor node layout to use relative offsets and add play-mode guards
Separate node layout (relative pin offsets) from geometry (absolute positions), replacing the single geometry calculator with a layout → geometry two-step. Switch node SVG wrapper from <g> to <svg> so child elements can use percentage dimensions. Update Display node to compute its size from config resolution and add a config settings button stub. Disable the ViewModeSwitcher play button when any input pin is unconnected. Remove redundant node/connection event listeners from the simulation trigger. Key changes covered: - Layout / LayoutSource types split out from Geometry, with nodePinOffsetById replacing absolute positions - <g> → <svg> in Node/index.tsx so Default can use width="100%" - Display/geometry.ts now scales with config.resolution and exports layout constants - New ConfigSettingButton component for Display nodes - isEachInputPinConnected utility in component.ts gating the play button - executeSimulation decoupled from raw store events in the core slice
1 parent 31cb374 commit 4e37d91

16 files changed

Lines changed: 262 additions & 166 deletions

File tree

‎src/pages/edit/Editor/components/NodePinPropertyEditor.tsx‎

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { CCConnectionStore } from "../../../../store/connection";
88
import { IntrinsicComponentDefinition } from "../../../../store/intrinsics/base";
99
import { CCNodePinStore } from "../../../../store/nodePin";
1010
import { useStore } from "../../../../store/react";
11-
import getCCComponentEditorRendererNodeGeometry from "../renderer/Node/geometry";
11+
import { getCCComponentEditorRendererNodeGeometry } from "../renderer/Node/geometry";
1212
import { useComponentEditorStore } from "../store";
1313

1414
export function CCComponentEditorNodePinPropertyEditor() {
@@ -123,33 +123,22 @@ export function CCComponentEditorNodePinPropertyEditor() {
123123
nodePin.id,
124124
);
125125
for (const connection of connections) {
126-
const anotherNodePinId =
127-
connection.from === nodePin.id
128-
? connection.to
129-
: connection.from;
130-
const fromNodePinId =
131-
connection.from === nodePin.id
132-
? nodePin.id
133-
: anotherNodePinId;
134-
const toNodePinId =
135-
connection.from === nodePin.id
136-
? anotherNodePinId
137-
: nodePin.id;
126+
const from = connection.from;
127+
const to = connection.to;
138128
const parentComponentId = connection.parentComponentId;
139-
store.connections.unregister([connection.id]);
140-
if (
141-
store.nodePins.isConnectable(nodePin.id, anotherNodePinId)
142-
) {
143-
// reconnect if still connectable after bit width change
144-
store.connections.register(
145-
CCConnectionStore.create({
146-
parentComponentId,
147-
from: fromNodePinId,
148-
to: toNodePinId,
149-
bentPortion: 0.5,
150-
}),
151-
);
152-
}
129+
store.connections.unregister([connection.id]).then(() => {
130+
if (store.nodePins.isConnectable(from, to)) {
131+
// reconnect if still connectable after bit width change
132+
store.connections.register(
133+
CCConnectionStore.create({
134+
parentComponentId,
135+
from,
136+
to,
137+
bentPortion: 0.5,
138+
}),
139+
);
140+
}
141+
});
153142
}
154143
}
155144
continue;

‎src/pages/edit/Editor/components/ViewModeSwitcher.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { Edit, PlayArrow } from "@mui/icons-material";
22
import { Fab } from "@mui/material";
3+
import { isEachInputPinConnected } from "../../../../store/component";
4+
import { useStore } from "../../../../store/react";
35
import { useComponentEditorStore } from "../store";
46

57
export default function CCComponentEditorViewModeSwitcher() {
68
const componentEditorState = useComponentEditorStore()();
9+
const { store } = useStore();
710

811
return (
912
<Fab
@@ -15,6 +18,9 @@ export default function CCComponentEditorViewModeSwitcher() {
1518
);
1619
componentEditorState.setTimeStep(0);
1720
}}
21+
disabled={
22+
!isEachInputPinConnected(store, componentEditorState.componentId)
23+
}
1824
>
1925
{componentEditorState.editorMode === "edit" ? <PlayArrow /> : <Edit />}
2026
</Fab>

‎src/pages/edit/Editor/renderer/ComponentPin/index.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useStore } from "../../../../../store/react";
55
import { wrappingIncrementSimulationValue } from "../../../../../store/simulation";
66
import { useComponentEditorStore } from "../../store";
77
import { stringifySimulationValue } from "../../store/slices/core";
8-
import getCCComponentEditorRendererNodeGeometry from "./../Node/geometry";
8+
import { getCCComponentEditorRendererNodeGeometry } from "./../Node/geometry";
99
export type CCComponentEditorRendererComponentPinProps = {
1010
nodePinId: CCNodePinId;
1111
};

‎src/pages/edit/Editor/renderer/Connection/index.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import ensureStoreItem from "../../../../../store/react/error";
99
import { useNode } from "../../../../../store/react/selectors";
1010
import { useComponentEditorStore } from "../../store";
1111
import { stringifySimulationValue } from "../../store/slices/core/index";
12-
import getCCComponentEditorRendererNodeGeometry from "../Node/geometry";
12+
import { getCCComponentEditorRendererNodeGeometry } from "../Node/geometry";
1313

1414
export type CCComponentEditorRendererConnectionEndpoint = {
1515
direction: CCComponentPinType;
Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,41 @@
1-
import { type Vector2, vector2 } from "../../../../../../../common/vector2";
1+
import type { Vector2 } from "../../../../../../../common/vector2";
22
import type { CCNodePinId } from "../../../../../../../store/nodePin";
33
import type {
4-
CCComponentEditorRendererNodeGeometryCalculator,
5-
CCComponentEditorRendererNodeGeometrySource,
4+
CCComponentEditorRendererNodeLayout,
5+
CCComponentEditorRendererNodeLayoutSource,
66
} from "../../types";
77

88
const width = 100;
99
const gapY = 20;
1010
const paddingY = 15;
1111

12-
export const ccComponentRendererNodeDefaultGeometryCalculator: CCComponentEditorRendererNodeGeometryCalculator =
13-
(source: CCComponentEditorRendererNodeGeometrySource) => {
14-
const size: Vector2 = {
15-
x: width,
16-
y:
17-
gapY *
18-
Math.max(
19-
source.inputNodePinIds.length,
20-
source.outputNodePinIds.length,
21-
) +
22-
paddingY * 2,
23-
};
12+
export function ccComponentRendererNodeDefaultLayoutCalculator(
13+
source: CCComponentEditorRendererNodeLayoutSource,
14+
): CCComponentEditorRendererNodeLayout {
15+
const size: Vector2 = {
16+
x: width,
17+
y:
18+
gapY *
19+
Math.max(
20+
source.inputNodePinIds.length,
21+
source.outputNodePinIds.length,
22+
) +
23+
paddingY * 2,
24+
};
2425

25-
const nodePinPositionById = new Map<CCNodePinId, Vector2>();
26-
for (const [index, nodePinId] of source.inputNodePinIds.entries()) {
27-
nodePinPositionById.set(nodePinId, {
28-
x: source.position.x - size.x / 2,
29-
y:
30-
source.position.y +
31-
gapY * (index - source.inputNodePinIds.length / 2 + 0.5),
32-
});
33-
}
34-
for (const [index, nodePinId] of source.outputNodePinIds.entries()) {
35-
nodePinPositionById.set(nodePinId, {
36-
x: source.position.x + size.x / 2,
37-
y:
38-
source.position.y +
39-
gapY * (index - source.outputNodePinIds.length / 2 + 0.5),
40-
});
41-
}
26+
const nodePinOffsetById = new Map<CCNodePinId, Vector2>();
4227

43-
return {
44-
rect: {
45-
position: vector2.sub(source.position, vector2.div(size, 2)),
46-
size,
47-
},
48-
nodePinPositionById,
49-
};
50-
};
28+
const startYIn =
29+
size.y / 2 - (gapY * (source.inputNodePinIds.length - 1)) / 2;
30+
for (const [index, pinId] of source.inputNodePinIds.entries()) {
31+
nodePinOffsetById.set(pinId, { x: 0, y: startYIn + gapY * index });
32+
}
33+
34+
const startYOut =
35+
size.y / 2 - (gapY * (source.outputNodePinIds.length - 1)) / 2;
36+
for (const [index, pinId] of source.outputNodePinIds.entries()) {
37+
nodePinOffsetById.set(pinId, { x: size.x, y: startYOut + gapY * index });
38+
}
39+
40+
return { size, nodePinOffsetById };
41+
}

‎src/pages/edit/Editor/renderer/Node/components/Default/index.tsx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@ export function CCComponentEditorRendererNodeDefaultRenderer(
88
<>
99
<text
1010
fill={theme.palette.textPrimary}
11-
x={props.geometry.rect.position.x}
12-
y={props.geometry.rect.position.y - 5}
11+
x={0}
12+
y={-5}
1313
textAnchor="start"
1414
fontSize={12}
1515
>
1616
{props.component.name}
1717
</text>
1818
<rect
19-
x={props.geometry.rect.position.x}
20-
y={props.geometry.rect.position.y}
21-
width={props.geometry.rect.size.x}
22-
height={props.geometry.rect.size.y}
19+
x={0}
20+
y={0}
21+
width="100%"
22+
height="100%"
2323
fill={theme.palette.white}
2424
stroke={
2525
props.nodeState.isSelected
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { SettingsOutlined } from "@mui/icons-material";
2+
import { IconButton } from "@mui/material";
3+
import type { CCNodeId } from "../../../../../../../store/node";
4+
import type { CCComponentEditorRendererNodeGeometry } from "../../types";
5+
6+
type Props = {
7+
nodeId: CCNodeId;
8+
geometry: CCComponentEditorRendererNodeGeometry;
9+
};
10+
11+
export function CCComponentEditorRendererNodeDisplayRendererConfigSettingButton(
12+
_props: Props,
13+
) {
14+
return (
15+
<IconButton
16+
sx={{ width: "20px", height: "20px" }}
17+
onPointerDown={(e) => {
18+
e.stopPropagation();
19+
}}
20+
disableTouchRipple
21+
disableFocusRipple
22+
>
23+
<SettingsOutlined sx={{ width: "12px", height: "12px" }} />
24+
</IconButton>
25+
);
26+
}
Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
1+
import nullthrows from "nullthrows";
12
import { type Vector2, vector2 } from "../../../../../../../common/vector2";
3+
import type { CCIntrinsicComponentDisplaySpec } from "../../../../../../../store/intrinsics/types";
24
import type { CCNodePinId } from "../../../../../../../store/nodePin";
35
import type {
4-
CCComponentEditorRendererNodeGeometryCalculator,
5-
CCComponentEditorRendererNodeGeometrySource,
6+
CCComponentEditorRendererNodeLayout,
7+
CCComponentEditorRendererNodeLayoutSource,
68
} from "../../types";
79

8-
const width = 320;
9-
const height = 200;
10+
const size = { x: 320, y: 200 };
1011

11-
export const ccComponentRendererNodeDisplayGeometryCalculator: CCComponentEditorRendererNodeGeometryCalculator =
12-
(source: CCComponentEditorRendererNodeGeometrySource) => {
13-
const size: Vector2 = {
14-
x: width,
15-
y: height,
16-
};
12+
export const ccComponentEditorRendererNodeDisplayLayoutConstants = {
13+
padding: 8,
14+
gridSize: 12,
15+
gridSizeDisplayWidth: 60,
16+
};
1717

18-
return {
19-
rect: {
20-
position: vector2.sub(source.position, vector2.div(size, 2)),
21-
size,
22-
},
23-
nodePinPositionById: new Map<CCNodePinId, Vector2>(
24-
source.inputNodePinIds.map(
25-
(id) =>
26-
[
27-
id,
28-
vector2.create(source.position.x - size.x / 2, source.position.y),
29-
] as const,
30-
),
31-
),
32-
};
18+
export function ccComponentRendererNodeDisplayLayoutCalculator(
19+
source: CCComponentEditorRendererNodeLayoutSource,
20+
): CCComponentEditorRendererNodeLayout {
21+
const { padding, gridSize, gridSizeDisplayWidth } =
22+
ccComponentEditorRendererNodeDisplayLayoutConstants;
23+
const config = source.config as CCIntrinsicComponentDisplaySpec["config"];
24+
25+
return {
26+
size: {
27+
x: gridSizeDisplayWidth + gridSize * config.resolution.x + padding * 2,
28+
y: gridSize * config.resolution.y + padding * 2,
29+
},
30+
nodePinOffsetById: new Map<CCNodePinId, Vector2>([
31+
[nullthrows(source.inputNodePinIds[0]), vector2.create(0, size.y / 2)],
32+
]),
3333
};
34+
}

‎src/pages/edit/Editor/renderer/Node/components/Display/index.tsx‎

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import type { CCIntrinsicComponentDisplaySpec } from "../../../../../../../store
55
import { useStore } from "../../../../../../../store/react";
66
import { useComponentEditorStore } from "../../../../store";
77
import type { CCComponentEditorRendererNodeRendererProps } from "../../types";
8+
import { CCComponentEditorRendererNodeDefaultRenderer } from "../Default";
9+
import { CCComponentEditorRendererNodeDisplayRendererConfigSettingButton } from "./ConfigSettingButton";
10+
import { ccComponentEditorRendererNodeDisplayLayoutConstants } from "./geometry";
811

912
export function CCComponentEditorRendererNodeDisplayRenderer(
1013
props: CCComponentEditorRendererNodeRendererProps,
@@ -23,38 +26,27 @@ export function CCComponentEditorRendererNodeDisplayRenderer(
2326
? editorState.getNodePinValue(inputNodePin.id)
2427
: undefined;
2528

29+
const { padding, gridSize, gridSizeDisplayWidth } =
30+
ccComponentEditorRendererNodeDisplayLayoutConstants;
31+
2632
return (
2733
<>
28-
<rect
29-
x={props.geometry.rect.position.x}
30-
y={props.geometry.rect.position.y}
31-
width={props.geometry.rect.size.x}
32-
height={props.geometry.rect.size.y}
33-
fill={theme.palette.white}
34-
stroke={
35-
props.nodeState.isSelected
36-
? theme.palette.primary
37-
: theme.palette.textPrimary
38-
}
39-
strokeWidth={2}
40-
rx={2}
41-
/>
42-
<text
43-
x={props.geometry.rect.position.x + 8}
44-
y={props.geometry.rect.position.y + 20}
45-
fontSize={12}
46-
fill={theme.palette.textPrimary}
47-
>
48-
Display
49-
</text>
34+
<CCComponentEditorRendererNodeDefaultRenderer {...props} />
5035
<text
51-
x={props.geometry.rect.position.x + 8}
52-
y={props.geometry.rect.position.y + 40}
36+
x={padding}
37+
y={padding}
5338
fontSize={16}
5439
fill={theme.palette.textPrimary}
40+
dominantBaseline="hanging"
5541
>
5642
{config.resolution.x}x{config.resolution.y}
5743
</text>
44+
<foreignObject x={padding / 2} y={padding + 16} width={32} height={32}>
45+
<CCComponentEditorRendererNodeDisplayRendererConfigSettingButton
46+
nodeId={props.node.id}
47+
geometry={props.geometry}
48+
/>
49+
</foreignObject>
5850
{Array(config.resolution.y)
5951
.keys()
6052
.map((y) =>
@@ -63,10 +55,10 @@ export function CCComponentEditorRendererNodeDisplayRenderer(
6355
.map((x) => (
6456
<rect
6557
key={`${x}-${y}`}
66-
x={props.geometry.rect.position.x + 64 + x * 12}
67-
y={props.geometry.rect.position.y + 8 + y * 12}
68-
width={12}
69-
height={12}
58+
x={padding + gridSizeDisplayWidth + x * gridSize}
59+
y={padding + y * gridSize}
60+
width={gridSize}
61+
height={gridSize}
7062
fill={
7163
inputValue?.[
7264
config.resolution.x * config.resolution.y -

0 commit comments

Comments
 (0)