diff --git a/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx b/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx index d1deb88..2dff789 100644 --- a/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx +++ b/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx @@ -8,7 +8,7 @@ import { CCConnectionStore } from "../../../../store/connection"; import { IntrinsicComponentDefinition } from "../../../../store/intrinsics/base"; import { CCNodePinStore } from "../../../../store/nodePin"; import { useStore } from "../../../../store/react"; -import getCCComponentEditorRendererNodeGeometry from "../renderer/Node/geometry"; +import { getCCComponentEditorRendererNodeGeometry } from "../renderer/Node/geometry"; import { useComponentEditorStore } from "../store"; export function CCComponentEditorNodePinPropertyEditor() { @@ -123,33 +123,22 @@ export function CCComponentEditorNodePinPropertyEditor() { nodePin.id, ); for (const connection of connections) { - const anotherNodePinId = - connection.from === nodePin.id - ? connection.to - : connection.from; - const fromNodePinId = - connection.from === nodePin.id - ? nodePin.id - : anotherNodePinId; - const toNodePinId = - connection.from === nodePin.id - ? anotherNodePinId - : nodePin.id; + const from = connection.from; + const to = connection.to; const parentComponentId = connection.parentComponentId; - store.connections.unregister([connection.id]); - if ( - store.nodePins.isConnectable(nodePin.id, anotherNodePinId) - ) { - // reconnect if still connectable after bit width change - store.connections.register( - CCConnectionStore.create({ - parentComponentId, - from: fromNodePinId, - to: toNodePinId, - bentPortion: 0.5, - }), - ); - } + store.connections.unregister([connection.id]).then(() => { + if (store.nodePins.isConnectable(from, to)) { + // reconnect if still connectable after bit width change + store.connections.register( + CCConnectionStore.create({ + parentComponentId, + from, + to, + bentPortion: 0.5, + }), + ); + } + }); } } continue; diff --git a/src/pages/edit/Editor/components/ViewModeSwitcher.tsx b/src/pages/edit/Editor/components/ViewModeSwitcher.tsx index dabfb5d..da79a9a 100644 --- a/src/pages/edit/Editor/components/ViewModeSwitcher.tsx +++ b/src/pages/edit/Editor/components/ViewModeSwitcher.tsx @@ -1,9 +1,12 @@ import { Edit, PlayArrow } from "@mui/icons-material"; import { Fab } from "@mui/material"; +import { isEachInputPinConnected } from "../../../../store/component"; +import { useStore } from "../../../../store/react"; import { useComponentEditorStore } from "../store"; export default function CCComponentEditorViewModeSwitcher() { const componentEditorState = useComponentEditorStore()(); + const { store } = useStore(); return ( {componentEditorState.editorMode === "edit" ? : } diff --git a/src/pages/edit/Editor/renderer/ComponentPin/index.tsx b/src/pages/edit/Editor/renderer/ComponentPin/index.tsx index 1fc775a..a0ce9a2 100644 --- a/src/pages/edit/Editor/renderer/ComponentPin/index.tsx +++ b/src/pages/edit/Editor/renderer/ComponentPin/index.tsx @@ -5,7 +5,7 @@ import { useStore } from "../../../../../store/react"; import { wrappingIncrementSimulationValue } from "../../../../../store/simulation"; import { useComponentEditorStore } from "../../store"; import { stringifySimulationValue } from "../../store/slices/core"; -import getCCComponentEditorRendererNodeGeometry from "./../Node/geometry"; +import { getCCComponentEditorRendererNodeGeometry } from "./../Node/geometry"; export type CCComponentEditorRendererComponentPinProps = { nodePinId: CCNodePinId; }; diff --git a/src/pages/edit/Editor/renderer/Connection/index.tsx b/src/pages/edit/Editor/renderer/Connection/index.tsx index f1748c3..8808bdd 100644 --- a/src/pages/edit/Editor/renderer/Connection/index.tsx +++ b/src/pages/edit/Editor/renderer/Connection/index.tsx @@ -9,7 +9,7 @@ import ensureStoreItem from "../../../../../store/react/error"; import { useNode } from "../../../../../store/react/selectors"; import { useComponentEditorStore } from "../../store"; import { stringifySimulationValue } from "../../store/slices/core/index"; -import getCCComponentEditorRendererNodeGeometry from "../Node/geometry"; +import { getCCComponentEditorRendererNodeGeometry } from "../Node/geometry"; export type CCComponentEditorRendererConnectionEndpoint = { direction: CCComponentPinType; diff --git a/src/pages/edit/Editor/renderer/Node/components/Default/geometry.ts b/src/pages/edit/Editor/renderer/Node/components/Default/geometry.ts index a6aedb5..4c40bed 100644 --- a/src/pages/edit/Editor/renderer/Node/components/Default/geometry.ts +++ b/src/pages/edit/Editor/renderer/Node/components/Default/geometry.ts @@ -1,50 +1,41 @@ -import { type Vector2, vector2 } from "../../../../../../../common/vector2"; +import type { Vector2 } from "../../../../../../../common/vector2"; import type { CCNodePinId } from "../../../../../../../store/nodePin"; import type { - CCComponentEditorRendererNodeGeometryCalculator, - CCComponentEditorRendererNodeGeometrySource, + CCComponentEditorRendererNodeLayout, + CCComponentEditorRendererNodeLayoutSource, } from "../../types"; const width = 100; const gapY = 20; const paddingY = 15; -export const ccComponentRendererNodeDefaultGeometryCalculator: CCComponentEditorRendererNodeGeometryCalculator = - (source: CCComponentEditorRendererNodeGeometrySource) => { - const size: Vector2 = { - x: width, - y: - gapY * - Math.max( - source.inputNodePinIds.length, - source.outputNodePinIds.length, - ) + - paddingY * 2, - }; +export function ccComponentRendererNodeDefaultLayoutCalculator( + source: CCComponentEditorRendererNodeLayoutSource, +): CCComponentEditorRendererNodeLayout { + const size: Vector2 = { + x: width, + y: + gapY * + Math.max( + source.inputNodePinIds.length, + source.outputNodePinIds.length, + ) + + paddingY * 2, + }; - const nodePinPositionById = new Map(); - for (const [index, nodePinId] of source.inputNodePinIds.entries()) { - nodePinPositionById.set(nodePinId, { - x: source.position.x - size.x / 2, - y: - source.position.y + - gapY * (index - source.inputNodePinIds.length / 2 + 0.5), - }); - } - for (const [index, nodePinId] of source.outputNodePinIds.entries()) { - nodePinPositionById.set(nodePinId, { - x: source.position.x + size.x / 2, - y: - source.position.y + - gapY * (index - source.outputNodePinIds.length / 2 + 0.5), - }); - } + const nodePinOffsetById = new Map(); - return { - rect: { - position: vector2.sub(source.position, vector2.div(size, 2)), - size, - }, - nodePinPositionById, - }; - }; + const startYIn = + size.y / 2 - (gapY * (source.inputNodePinIds.length - 1)) / 2; + for (const [index, pinId] of source.inputNodePinIds.entries()) { + nodePinOffsetById.set(pinId, { x: 0, y: startYIn + gapY * index }); + } + + const startYOut = + size.y / 2 - (gapY * (source.outputNodePinIds.length - 1)) / 2; + for (const [index, pinId] of source.outputNodePinIds.entries()) { + nodePinOffsetById.set(pinId, { x: size.x, y: startYOut + gapY * index }); + } + + return { size, nodePinOffsetById }; +} diff --git a/src/pages/edit/Editor/renderer/Node/components/Default/index.tsx b/src/pages/edit/Editor/renderer/Node/components/Default/index.tsx index 133721b..292cbe7 100644 --- a/src/pages/edit/Editor/renderer/Node/components/Default/index.tsx +++ b/src/pages/edit/Editor/renderer/Node/components/Default/index.tsx @@ -8,18 +8,18 @@ export function CCComponentEditorRendererNodeDefaultRenderer( <> {props.component.name} { + e.stopPropagation(); + }} + disableTouchRipple + disableFocusRipple + > + + + ); +} diff --git a/src/pages/edit/Editor/renderer/Node/components/Display/geometry.ts b/src/pages/edit/Editor/renderer/Node/components/Display/geometry.ts index 88b09be..9a94731 100644 --- a/src/pages/edit/Editor/renderer/Node/components/Display/geometry.ts +++ b/src/pages/edit/Editor/renderer/Node/components/Display/geometry.ts @@ -1,33 +1,34 @@ +import nullthrows from "nullthrows"; import { type Vector2, vector2 } from "../../../../../../../common/vector2"; +import type { CCIntrinsicComponentDisplaySpec } from "../../../../../../../store/intrinsics/types"; import type { CCNodePinId } from "../../../../../../../store/nodePin"; import type { - CCComponentEditorRendererNodeGeometryCalculator, - CCComponentEditorRendererNodeGeometrySource, + CCComponentEditorRendererNodeLayout, + CCComponentEditorRendererNodeLayoutSource, } from "../../types"; -const width = 320; -const height = 200; +const size = { x: 320, y: 200 }; -export const ccComponentRendererNodeDisplayGeometryCalculator: CCComponentEditorRendererNodeGeometryCalculator = - (source: CCComponentEditorRendererNodeGeometrySource) => { - const size: Vector2 = { - x: width, - y: height, - }; +export const ccComponentEditorRendererNodeDisplayLayoutConstants = { + padding: 8, + gridSize: 12, + gridSizeDisplayWidth: 60, +}; - return { - rect: { - position: vector2.sub(source.position, vector2.div(size, 2)), - size, - }, - nodePinPositionById: new Map( - source.inputNodePinIds.map( - (id) => - [ - id, - vector2.create(source.position.x - size.x / 2, source.position.y), - ] as const, - ), - ), - }; +export function ccComponentRendererNodeDisplayLayoutCalculator( + source: CCComponentEditorRendererNodeLayoutSource, +): CCComponentEditorRendererNodeLayout { + const { padding, gridSize, gridSizeDisplayWidth } = + ccComponentEditorRendererNodeDisplayLayoutConstants; + const config = source.config as CCIntrinsicComponentDisplaySpec["config"]; + + return { + size: { + x: gridSizeDisplayWidth + gridSize * config.resolution.x + padding * 2, + y: gridSize * config.resolution.y + padding * 2, + }, + nodePinOffsetById: new Map([ + [nullthrows(source.inputNodePinIds[0]), vector2.create(0, size.y / 2)], + ]), }; +} diff --git a/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx b/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx index a80634f..d63a48c 100644 --- a/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx +++ b/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx @@ -5,6 +5,9 @@ import type { CCIntrinsicComponentDisplaySpec } from "../../../../../../../store import { useStore } from "../../../../../../../store/react"; import { useComponentEditorStore } from "../../../../store"; import type { CCComponentEditorRendererNodeRendererProps } from "../../types"; +import { CCComponentEditorRendererNodeDefaultRenderer } from "../Default"; +import { CCComponentEditorRendererNodeDisplayRendererConfigSettingButton } from "./ConfigSettingButton"; +import { ccComponentEditorRendererNodeDisplayLayoutConstants } from "./geometry"; export function CCComponentEditorRendererNodeDisplayRenderer( props: CCComponentEditorRendererNodeRendererProps, @@ -23,38 +26,27 @@ export function CCComponentEditorRendererNodeDisplayRenderer( ? editorState.getNodePinValue(inputNodePin.id) : undefined; + const { padding, gridSize, gridSizeDisplayWidth } = + ccComponentEditorRendererNodeDisplayLayoutConstants; + return ( <> - - - Display - + {config.resolution.x}x{config.resolution.y} + + + {Array(config.resolution.y) .keys() .map((y) => @@ -63,10 +55,10 @@ export function CCComponentEditorRendererNodeDisplayRenderer( .map((x) => ( -> = { +const specialLayoutCalculators: { + [key in CCIntrinsicComponentType]?: ( + source: CCComponentEditorRendererNodeLayoutSource, + ) => CCComponentEditorRendererNodeLayout; +} = { [ccIntrinsicComponentTypes.DISPLAY]: - ccComponentRendererNodeDisplayGeometryCalculator, + ccComponentRendererNodeDisplayLayoutCalculator, }; -export default function getCCComponentEditorRendererNodeGeometry( +export function getCCComponentEditorRendererNodeLayout( store: CCStore, nodeId: CCNodeId, -) { +): CCComponentEditorRendererNodeLayout { const node = nullthrows(store.nodes.get(nodeId)); const component = nullthrows(store.components.get(node.componentId)); const nodePins = store.nodePins.getManyByNodeId(nodeId); - const source: CCComponentEditorRendererNodeGeometrySource = { - position: node.position, + const layoutCalculator = + (component.intrinsicType && + specialLayoutCalculators[component.intrinsicType]) ?? + ccComponentRendererNodeDefaultLayoutCalculator; + + return layoutCalculator({ + config: node.config, inputNodePinIds: nodePins .filter((np) => { const cp = nullthrows(store.componentPins.get(np.componentPinId)); @@ -44,11 +50,32 @@ export default function getCCComponentEditorRendererNodeGeometry( return cp.type === "output"; }) .map((np) => np.id), + }); +} + +export function ccComponentEditorRendererLayoutToGeometry( + layout: CCComponentEditorRendererNodeLayout, + nodePosition: Vector2, +): CCComponentEditorRendererNodeGeometry { + const position = vector2.sub(nodePosition, vector2.div(layout.size, 2)); + return { + rect: { position: position, size: layout.size }, + nodePinPositionById: new Map( + layout.nodePinOffsetById + .entries() + .map(([nodePinId, offset]) => [ + nodePinId, + vector2.add(position, offset), + ]), + ), }; +} - const calculator = - (component.intrinsicType && - specialGeometryCalculators[component.intrinsicType]) ?? - ccComponentRendererNodeDefaultGeometryCalculator; - return calculator(source); +export function getCCComponentEditorRendererNodeGeometry( + store: CCStore, + nodeId: CCNodeId, +): CCComponentEditorRendererNodeGeometry { + const node = nullthrows(store.nodes.get(nodeId)); + const layout = getCCComponentEditorRendererNodeLayout(store, nodeId); + return ccComponentEditorRendererLayoutToGeometry(layout, node.position); } diff --git a/src/pages/edit/Editor/renderer/Node/index.tsx b/src/pages/edit/Editor/renderer/Node/index.tsx index 58dfe46..add0190 100644 --- a/src/pages/edit/Editor/renderer/Node/index.tsx +++ b/src/pages/edit/Editor/renderer/Node/index.tsx @@ -12,7 +12,10 @@ import { useComponentEditorStore } from "../../store"; import CCComponentEditorRendererNodePin from "../NodePin"; import { CCComponentEditorRendererNodeDefaultRenderer } from "./components/Default"; import { CCComponentEditorRendererNodeDisplayRenderer } from "./components/Display"; -import getCCComponentEditorRendererNodeGeometry from "./geometry"; +import { + ccComponentEditorRendererLayoutToGeometry, + getCCComponentEditorRendererNodeLayout, +} from "./geometry"; import type { CCComponentEditorRendererNodeRendererNodeState, CCComponentEditorRendererNodeRendererProps, @@ -44,7 +47,11 @@ const CCComponentEditorRendererNode = ensureStoreItem( vector2.zero, ); - const geometry = getCCComponentEditorRendererNodeGeometry(store, nodeId); + const layout = getCCComponentEditorRendererNodeLayout(store, nodeId); + const geometry = ccComponentEditorRendererLayoutToGeometry( + layout, + node.position, + ); const Renderer = (component.intrinsicType && specialRenderers[component.intrinsicType]) || CCComponentEditorRendererNodeDefaultRenderer; @@ -86,8 +93,13 @@ const CCComponentEditorRendererNode = ensureStoreItem( return ( <> - {/** biome-ignore lint/a11y/noStaticElementInteractions: SVG */} - - + {store.nodePins.getManyByNodeId(nodeId).map((nodePin) => ( ; }; -export type CCComponentEditorRendererNodeGeometrySource = { - position: Vector2; +export type CCComponentEditorRendererNodeLayoutSource = { + config: CCNode["config"]; inputNodePinIds: CCNodePinId[]; outputNodePinIds: CCNodePinId[]; }; @@ -26,6 +28,6 @@ export type CCComponentEditorRendererNodeGeometry = { nodePinPositionById: Map; }; -export type CCComponentEditorRendererNodeGeometryCalculator = ( - source: CCComponentEditorRendererNodeGeometrySource, -) => CCComponentEditorRendererNodeGeometry; +export type CCComponentEditorRendererNodeRendererNodeState = { + isSelected: boolean; +}; diff --git a/src/pages/edit/Editor/renderer/NodePin/index.tsx b/src/pages/edit/Editor/renderer/NodePin/index.tsx index 1c25056..c1a5c98 100644 --- a/src/pages/edit/Editor/renderer/NodePin/index.tsx +++ b/src/pages/edit/Editor/renderer/NodePin/index.tsx @@ -14,7 +14,7 @@ import { CCComponentEditorRendererConnectionCore, type CCComponentEditorRendererConnectionEndpoint, } from "./../Connection"; -import getCCComponentEditorRendererNodeGeometry from "./../Node/geometry"; +import { getCCComponentEditorRendererNodeGeometry } from "./../Node/geometry"; const NODE_PIN_POSITION_SENSITIVITY = 10; diff --git a/src/pages/edit/Editor/store/slices/core/index.ts b/src/pages/edit/Editor/store/slices/core/index.ts index ada2720..4240d94 100644 --- a/src/pages/edit/Editor/store/slices/core/index.ts +++ b/src/pages/edit/Editor/store/slices/core/index.ts @@ -191,11 +191,6 @@ export const createComponentEditorStoreCoreSlice: ComponentEditorSliceCreator< } if (isUpdated) editorStore.setState((s) => ({ ...s })); }; - store.nodes.on("didRegister", executeSimulation); - store.nodes.on("didUpdate", executeSimulation); - store.nodes.on("didUnregister", executeSimulation); - store.connections.on("didRegister", executeSimulation); - store.connections.on("didUnregister", executeSimulation); editorStore.subscribe(executeSimulation); }, }; diff --git a/src/store/component.ts b/src/store/component.ts index b2835e5..fc1bf11 100644 --- a/src/store/component.ts +++ b/src/store/component.ts @@ -219,3 +219,28 @@ export function validateAllComponents(store: CCStore) { validateComponent(store, component.id); } } + +export function isEachInputPinConnected( + store: CCStore, + componentId: CCComponentId, +) { + const component = nullthrows(store.components.get(componentId)); + if (component.intrinsicType) return true; + const nodes = store.nodes.getManyByParentComponentId(componentId); + for (const node of nodes) { + const nodePins = store.nodePins.getManyByNodeId(node.id); + for (const nodePin of nodePins) { + const componentPin = nullthrows( + store.componentPins.get(nodePin.componentPinId), + ); + if (componentPin.type === "input") { + const connectionsAssociatedWithNodePin = + store.connections.getConnectionsByNodePinId(nodePin.id); + if (connectionsAssociatedWithNodePin.length === 0) { + return false; + } + } + } + } + return true; +} diff --git a/src/store/connection.ts b/src/store/connection.ts index 938e59a..4d0d332 100644 --- a/src/store/connection.ts +++ b/src/store/connection.ts @@ -61,6 +61,35 @@ export class CCConnectionStore extends EventEmitter { this.unregister(connections.map((connection) => connection.id)); } }); + this.#store.connections.on("didRegister", (connection) => { + const component = nullthrows( + this.#store.components.get(connection.parentComponentId), + ); + const nodes = this.#store.nodes.getManyByComponentId(component.id); + for (const node of nodes) { + const nodePins = this.#store.nodePins.getManyByNodeId(node.id); + for (const nodePin of nodePins) { + const connections = this.getConnectionsByNodePinId(nodePin.id); + for (const connection of connections) { + const parentComponentId = connection.parentComponentId; + const from = connection.from; + const to = connection.to; + this.unregister([connection.id]).then(() => { + if (this.#store.nodePins.isConnectable(from, to)) { + this.register( + CCConnectionStore.create({ + from, + to, + parentComponentId, + bentPortion: 0.5, + }), + ); + } + }); + } + } + } + }); } /**