Skip to content

Commit 0b33be3

Browse files
committed
add editor background grid
1 parent 11aefd6 commit 0b33be3

File tree

14 files changed

+196
-134
lines changed

14 files changed

+196
-134
lines changed

src/common/theme.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,10 @@ import { createTheme } from "@mui/material";
33
// See https://www.figma.com/file/M3dC0Gk98IGSGlxY901rBh/
44
export const blackColor = "#000000";
55
export const whiteColor = "#ffffff";
6-
export const grayColor = {
7-
main: "#9e9e9e",
8-
darken2: "#616161",
9-
};
106
export const primaryColor = "#00d372";
117
export const activeColor = "#00aaff";
128
export const errorColor = "#ff0000";
13-
export const editorBackgroundColor = "#f3f3f3";
9+
export const editorBackgroundColor = "#fafafa";
1410
export const editorGridColor = "#dddddd";
1511

1612
export const theme = createTheme({

src/common/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export type Point = { x: number; y: number };
1+
import type { Vector2 } from "./vector2";
2+
3+
export type Perspective = { center: Vector2; scale: number };

src/common/vector2.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export type Vector2 = { x: number; y: number };
2+
3+
export const vector2 = {
4+
zero: { x: 0, y: 0 },
5+
add: (a: Vector2, b: Vector2): Vector2 => ({
6+
x: a.x + b.x,
7+
y: a.y + b.y,
8+
}),
9+
sub: (a: Vector2, b: Vector2): Vector2 => ({
10+
x: a.x - b.x,
11+
y: a.y - b.y,
12+
}),
13+
mul: (a: Vector2, b: number): Vector2 => ({
14+
x: a.x * b,
15+
y: a.y * b,
16+
}),
17+
div: (a: Vector2, b: number): Vector2 => ({
18+
x: a.x / b,
19+
y: a.y / b,
20+
}),
21+
fromDomEvent: (e: { offsetX: number; offsetY: number }): Vector2 => ({
22+
x: e.offsetX,
23+
y: e.offsetY,
24+
}),
25+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { ReactElement } from "react";
2+
import { useComponentEditorStore } from "../store";
3+
import { vector2 } from "../../../../common/vector2";
4+
5+
export default function CCComponentEditorGrid() {
6+
const componentEditorState = useComponentEditorStore()();
7+
const logScale = Math.log2(componentEditorState.perspective.scale);
8+
const canvasOriginPosition = componentEditorState.fromStageToCanvas(
9+
vector2.zero
10+
);
11+
const canvasGridSize = 100 * 2 ** (Math.ceil(logScale) - logScale);
12+
13+
const elements: ReactElement[] = [];
14+
let i = 0;
15+
for (
16+
let x = (canvasOriginPosition.x % canvasGridSize) - canvasGridSize;
17+
x <= componentEditorState.rendererSize.x;
18+
x += canvasGridSize
19+
) {
20+
elements.push(
21+
<div
22+
key={i}
23+
style={{
24+
position: "absolute",
25+
top: 0,
26+
left: 0,
27+
width: "1px",
28+
height: "100%",
29+
backgroundColor: "rgba(0, 0, 0, 0.1)",
30+
transform: `translateX(${x}px)`,
31+
}}
32+
/>
33+
);
34+
i += 1;
35+
}
36+
for (
37+
let y = (canvasOriginPosition.y % canvasGridSize) - canvasGridSize;
38+
y <= componentEditorState.rendererSize.y;
39+
y += canvasGridSize
40+
) {
41+
elements.push(
42+
<div
43+
key={i}
44+
style={{
45+
position: "absolute",
46+
top: 0,
47+
left: 0,
48+
width: "100%",
49+
height: "1px",
50+
backgroundColor: "rgba(0, 0, 0, 0.1)",
51+
transform: `translateY(${y}px)`,
52+
}}
53+
/>
54+
);
55+
i += 1;
56+
}
57+
58+
return <div aria-hidden>{elements}</div>;
59+
}

src/pages/edit/Editor/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import CCComponentEditorViewModeSwitcher from "./components/ViewModeSwitcher";
99
import CCComponentEditorContextMenu from "./components/ContextMenu";
1010
import type { CCComponentId } from "../../../store/component";
1111
import CCComponentEditorRenderer from "./renderer";
12+
import CCComponentEditorGrid from "./components/Grid";
13+
import { editorBackgroundColor } from "../../../common/theme";
1214

1315
export type CCComponentEditorProps = {
1416
componentId: CCComponentId;
@@ -27,7 +29,14 @@ function CCComponentEditorContent({
2729
useState(false);
2830

2931
return (
30-
<Box sx={{ position: "relative", overflow: "hidden" }}>
32+
<Box
33+
sx={{
34+
position: "relative",
35+
overflow: "hidden",
36+
backgroundColor: editorBackgroundColor,
37+
}}
38+
>
39+
<CCComponentEditorGrid />
3140
<CCComponentEditorRenderer />
3241
<CCComponentEditorTitleBar
3342
onComponentPropertyDialogOpen={() =>
Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import * as matrix from "transformation-matrix";
21
import { useComponentEditorStore } from "../store";
3-
import { whiteColor } from "../../../../common/theme";
2+
import { vector2 } from "../../../../common/vector2";
43

54
export default function CCComponentEditorRendererBackground() {
65
const componentEditorState = useComponentEditorStore()();
@@ -14,28 +13,20 @@ export default function CCComponentEditorRendererBackground() {
1413
}}
1514
onPointerDown={(pointerDownEvent) => {
1615
const { currentTarget } = pointerDownEvent;
17-
const startUserTransformation =
18-
componentEditorState.userPerspectiveTransformation;
19-
const startInverseViewTransformation =
20-
componentEditorState.getInverseViewTransformation();
21-
const startPoint = matrix.applyToPoint(startInverseViewTransformation, {
22-
x: pointerDownEvent.nativeEvent.offsetX,
23-
y: pointerDownEvent.nativeEvent.offsetY,
24-
});
16+
const startPerspective = componentEditorState.perspective;
17+
const startPoint = vector2.fromDomEvent(pointerDownEvent.nativeEvent);
2518
const onPointerMove = (pointerMoveEvent: PointerEvent) => {
26-
const endPoint = matrix.applyToPoint(startInverseViewTransformation, {
27-
x: pointerMoveEvent.offsetX,
28-
y: pointerMoveEvent.offsetY,
29-
});
30-
componentEditorState.setUserPerspectiveTransformation(
31-
matrix.compose(
32-
startUserTransformation,
33-
matrix.translate(
34-
endPoint.x - startPoint.x,
35-
endPoint.y - startPoint.y
19+
const endPoint = vector2.fromDomEvent(pointerMoveEvent);
20+
componentEditorState.setPerspective({
21+
...startPerspective,
22+
center: vector2.sub(
23+
startPerspective.center,
24+
vector2.mul(
25+
vector2.sub(endPoint, startPoint),
26+
startPerspective.scale
3627
)
37-
)
38-
);
28+
),
29+
});
3930
};
4031
currentTarget.addEventListener("pointermove", onPointerMove);
4132
const onPointerUp = () => {
@@ -45,22 +36,22 @@ export default function CCComponentEditorRendererBackground() {
4536
currentTarget.addEventListener("pointerup", onPointerUp);
4637
}}
4738
onWheel={(wheelEvent) => {
48-
const scale = Math.exp(-wheelEvent.deltaY / 256);
49-
const center = matrix.applyToPoint(
50-
componentEditorState.getInverseViewTransformation(),
51-
{
52-
x: wheelEvent.nativeEvent.offsetX,
53-
y: wheelEvent.nativeEvent.offsetY,
54-
}
55-
);
56-
componentEditorState.setUserPerspectiveTransformation(
57-
matrix.compose(
58-
componentEditorState.userPerspectiveTransformation,
59-
matrix.scale(scale, scale, center.x, center.y)
60-
)
39+
const scaleDelta = Math.exp(wheelEvent.deltaY / 256);
40+
const scaleCenter = componentEditorState.fromCanvasToStage(
41+
vector2.fromDomEvent(wheelEvent.nativeEvent)
6142
);
43+
componentEditorState.setPerspective({
44+
scale: componentEditorState.perspective.scale * scaleDelta,
45+
center: vector2.add(
46+
scaleCenter,
47+
vector2.mul(
48+
vector2.sub(componentEditorState.perspective.center, scaleCenter),
49+
scaleDelta
50+
)
51+
),
52+
});
6253
}}
63-
fill={whiteColor}
54+
fill="transparent"
6455
/>
6556
);
6657
}

src/pages/edit/Editor/renderer/Node.tsx

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import nullthrows from "nullthrows";
22
import { useState } from "react";
3-
import * as matrix from "transformation-matrix";
43
import type { CCNodeId } from "../../../../store/node";
54
import { useNode } from "../../../../store/react/selectors";
65
import { useStore } from "../../../../store/react";
@@ -9,6 +8,7 @@ import CCComponentEditorRendererNodePin from "./NodePin";
98
import getCCComponentEditorRendererNodeGeometry from "./Node.geometry";
109
import ensureStoreItem from "../../../../store/react/error";
1110
import { blackColor, primaryColor, whiteColor } from "../../../../common/theme";
11+
import { vector2 } from "../../../../common/vector2";
1212

1313
export type CCComponentEditorRendererNodeProps = {
1414
nodeId: CCNodeId;
@@ -22,40 +22,31 @@ const CCComponentEditorRendererNode = ensureStoreItem(
2222
const geometry = getCCComponentEditorRendererNodeGeometry(store, nodeId);
2323
const componentEditorState = useComponentEditorStore()();
2424
const [dragging, setDragging] = useState(false);
25-
const [dragStartPosition, setDragStartPosition] = useState({ x: 0, y: 0 });
26-
const [previousNodePosition, setPreviousNodePosition] = useState({
27-
x: 0,
28-
y: 0,
29-
});
25+
const [dragStartPosition, setDragStartPosition] = useState(vector2.zero);
26+
const [previousNodePosition, setPreviousNodePosition] = useState(
27+
vector2.zero
28+
);
3029

3130
const handleDragStart = (e: React.PointerEvent) => {
32-
setDragStartPosition({
33-
x: e.nativeEvent.offsetX,
34-
y: e.nativeEvent.offsetY,
35-
});
36-
setPreviousNodePosition({
37-
x: node.position.x,
38-
y: node.position.y,
39-
});
31+
setDragStartPosition(vector2.fromDomEvent(e.nativeEvent));
32+
setPreviousNodePosition(node.position);
4033
setDragging(true);
4134
e.currentTarget.setPointerCapture(e.pointerId);
4235
};
4336

4437
const handleDragging = (e: React.PointerEvent) => {
4538
if (dragging) {
46-
const { sx, sy } = matrix.decomposeTSR(
47-
componentEditorState.getInverseViewTransformation()
48-
).scale;
49-
const transformation = matrix.scale(sx, sy);
50-
const diff = matrix.applyToPoint(transformation, {
51-
x: e.nativeEvent.offsetX - dragStartPosition.x,
52-
y: e.nativeEvent.offsetY - dragStartPosition.y,
53-
});
5439
store.nodes.update(nodeId, {
55-
position: {
56-
x: previousNodePosition.x + diff.x,
57-
y: previousNodePosition.y + diff.y,
58-
},
40+
position: vector2.add(
41+
previousNodePosition,
42+
vector2.mul(
43+
vector2.sub(
44+
vector2.fromDomEvent(e.nativeEvent),
45+
dragStartPosition
46+
),
47+
componentEditorState.perspective.scale
48+
)
49+
),
5950
});
6051
}
6152
};

src/pages/edit/Editor/renderer/NodePin.tsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,35 @@
11
import { useState, type PointerEvent, type ReactNode } from "react";
2-
import * as matrix from "transformation-matrix";
32
import { KDTree } from "mnemonist";
43
import nullthrows from "nullthrows";
5-
import type { Point } from "../../../../common/types";
64
import type { CCNodePinId } from "../../../../store/nodePin";
75
import { CCComponentEditorRendererConnectionCore } from "./Connection";
86
import { useComponentEditorStore } from "../store";
97
import { useStore } from "../../../../store/react";
108
import getCCComponentEditorRendererNodeGeometry from "./Node.geometry";
119
import { CCConnectionStore } from "../../../../store/connection";
1210
import type { SimulationValue } from "../store/slices/core";
11+
import { vector2, type Vector2 } from "../../../../common/vector2";
1312

1413
const NODE_PIN_POSITION_SENSITIVITY = 10;
1514

1615
export type CCComponentEditorRendererNodeProps = {
1716
nodePinId: CCNodePinId;
18-
position: Point;
17+
position: Vector2;
1918
};
2019
export default function CCComponentEditorRendererNodePin({
2120
nodePinId,
2221
position,
2322
}: CCComponentEditorRendererNodeProps) {
2423
const { store } = useStore();
24+
const componentEditorState = useComponentEditorStore()();
2525
const nodePin = nullthrows(store.nodePins.get(nodePinId));
2626
const node = nullthrows(store.nodes.get(nodePin.nodeId));
2727
const componentPin = nullthrows(
2828
store.componentPins.get(nodePin.componentPinId)
2929
);
3030

31-
const inverseViewTransformation = useComponentEditorStore()((s) =>
32-
s.getInverseViewTransformation()
33-
);
3431
const [draggingState, setDraggingState] = useState<{
35-
cursorPosition: Point;
32+
cursorPosition: Vector2;
3633
nodePinPositionKDTree: KDTree<CCNodePinId>;
3734
} | null>(null);
3835
const onDrag = (e: PointerEvent) => {
@@ -62,10 +59,9 @@ export default function CCComponentEditorRendererNodePin({
6259
);
6360
}
6461
setDraggingState({
65-
cursorPosition: matrix.applyToPoint(inverseViewTransformation, {
66-
x: e.nativeEvent.offsetX,
67-
y: e.nativeEvent.offsetY,
68-
}),
62+
cursorPosition: componentEditorState.fromCanvasToStage(
63+
vector2.fromDomEvent(e.nativeEvent)
64+
),
6965
nodePinPositionKDTree,
7066
});
7167
};
@@ -110,7 +106,6 @@ export default function CCComponentEditorRendererNodePin({
110106
const hasNoConnection =
111107
store.connections.getConnectionsByNodePinId(nodePinId).length === 0;
112108

113-
const componentEditorStore = useComponentEditorStore()();
114109
const pinType = componentPin.type;
115110
const simulationValueToString = (simulationValue: SimulationValue) => {
116111
return simulationValue.reduce(
@@ -123,18 +118,18 @@ export default function CCComponentEditorRendererNodePin({
123118
let nodePinValueInit = null;
124119
if (isSimulationMode && hasNoConnection) {
125120
if (pinType === "input") {
126-
nodePinValueInit = componentEditorStore.getInputValue(
121+
nodePinValueInit = componentEditorState.getInputValue(
127122
implementedComponentPin!.id
128123
)!;
129124
} else {
130-
nodePinValueInit = componentEditorStore.getNodePinValue(nodePinId)!;
125+
nodePinValueInit = componentEditorState.getNodePinValue(nodePinId)!;
131126
}
132127
}
133128
const nodePinValue = nodePinValueInit;
134129
const updateInputValue = () => {
135130
const updatedPinValue = [...nodePinValue!];
136131
updatedPinValue[0] = !updatedPinValue[0];
137-
componentEditorStore.setInputValue(
132+
componentEditorState.setInputValue(
138133
implementedComponentPin!.id,
139134
updatedPinValue
140135
);

0 commit comments

Comments
 (0)