import { NameTransitionCommand } from '../../core/commands/graphCommands'; import { currentNodePalette } from '../graph/NodePalette'; import { rectOf } from '../textProbe'; import { centreOf, snapButton, type HookContext } from './hookHelpers'; /** The node graphs: behaviors, state machines, particles and shaders. */ export function graphHooks({ state, editor }: HookContext) { const { graphEditor } = editor; return { graph: { current: () => graphEditor.currentAssetId, portAt: (nodeId: string, port: string, isOutput: boolean) => graphEditor.canvas.portGlobal(nodeId, port, isOutput), nodeAt: (nodeId: string) => graphEditor.canvas.nodeGlobal(nodeId), chevronAt: (nodeId: string) => graphEditor.canvas.chevronGlobal(nodeId), nodeLabel: (nodeId: string) => graphEditor.canvas.nodeLabel(nodeId), nodeProblem: (nodeId: string) => graphEditor.canvas.nodeProblem(nodeId), nodeSettings: (nodeId: string) => graphEditor.canvas.nodeSettings(nodeId), /** Each input row as drawn: its name, and the inline value beside it while unconnected. */ nodeInputs: (nodeId: string) => graphEditor.canvas.nodeInputs(nodeId), /** Each output row as drawn: while playing, the value it last carried in front of its name. */ nodeOutputs: (nodeId: string) => graphEditor.canvas.nodeOutputs(nodeId), /** Beads in flight on exec edges as drawn, in graph coordinates; `progress` 0 = source, 1 = target. */ beads: () => graphEditor.canvas.beadsDrawn(), /** Drawn rectangles on screen: what "fit" has to keep inside `canvasRect`. */ nodeRect: (nodeId: string) => graphEditor.canvas.nodeRect(nodeId), canvasRect: () => graphEditor.canvas.canvasRect(), /** Bare canvas below every node – where a step right-clicks to open the palette. */ emptySpot: () => graphEditor.canvas.emptySpotGlobal(), /** * What the open palette lists, in order. No colour check can see a ranking: the rows all * look alike, only their order carries the answer (#43). */ paletteRows: () => currentNodePalette()?.rowsDrawn() ?? null, selected: () => state.selectedNodeIds, /** Groups and comments as drawn – text and both colours included, since no pixel says them. */ annotations: () => graphEditor.canvas.annotationsDrawn(), /** Where one is grabbed: its title bar, or with `corner` the handle that resizes it. */ annotationAt: (id: string, corner = false) => graphEditor.canvas.annotationGlobal(id, corner), /** The box as drawn on screen – how a check sees whether a fit brought it into view. */ annotationRect: (id: string) => graphEditor.canvas.annotationRect(id), dropTarget: () => graphEditor.canvas.pendingDropTarget(), danglingEdge: () => graphEditor.canvas.danglingEdgeVisible(), edgeDrawn: (edgeId: string) => graphEditor.canvas.edgeDrawn(edgeId), /** The colour it was stroked in: `danger` is an edge whose two ends stopped fitting (#207). */ edgeColor: (edgeId: string) => graphEditor.canvas.edgeColor(edgeId), /** Halfway along a link on screen, and the links drawn as picked (#289). */ edgeAt: (edgeId: string) => graphEditor.canvas.edgeGlobal(edgeId), pickedEdges: () => graphEditor.canvas.pickedEdgesDrawn(), /** The types the node's ports are drawn with, after a shared group had its say (#207). */ portTypes: (nodeId: string) => graphEditor.canvas.portTypes(nodeId), blockFrames: () => graphEditor.canvas.blockFrames(), foldMark: (edgeId: string, isOutput: boolean) => graphEditor.canvas.foldMarkGlobal(edgeId, isOutput), foldMarks: () => graphEditor.canvas.foldMarksDrawn(), // The canvas, not the whole tab: a centre measured through the header sits above the // drawing area. center: () => centreOf(graphEditor.canvas), snapping: () => graphEditor.canvas.snapEnabled, snapButton: () => snapButton(graphEditor.snap), zoom: () => graphEditor.canvas.zoomLevel, crumbs: () => graphEditor.breadcrumb.crumbsDrawn(), }, stateMachine: { current: () => editor.stateMachineEditor.currentAssetId, nodeAt: (nodeId: string) => editor.stateMachineEditor.canvas.nodeGlobal(nodeId), nodeLabel: (nodeId: string) => editor.stateMachineEditor.canvas.nodeLabel(nodeId), nodeRect: (nodeId: string) => editor.stateMachineEditor.canvas.nodeRect(nodeId), canvasRect: () => editor.stateMachineEditor.canvas.canvasRect(), center: () => centreOf(editor.stateMachineEditor.canvas), snapping: () => editor.stateMachineEditor.canvas.snapEnabled, snapButton: () => snapButton(editor.stateMachineEditor.snap), /** Where a state's hidden pin sits; it cannot be grabbed – `portGrabbable` is the check. */ portAt: (nodeId: string, port: string, isOutput: boolean) => editor.stateMachineEditor.canvas.portGlobal(nodeId, port, isOutput), portGrabbable: (nodeId: string, port: string, isOutput: boolean) => editor.stateMachineEditor.canvas.portGrabbable(nodeId, port, isOutput), edgeDrawn: (edgeId: string) => editor.stateMachineEditor.canvas.edgeDrawn(edgeId), edgeAt: (edgeId: string) => editor.stateMachineEditor.canvas.edgeGlobal(edgeId), pickedEdges: () => editor.stateMachineEditor.canvas.pickedEdgesDrawn(), transitionLabel: (edgeId: string) => editor.stateMachineEditor.canvas.transitionLabelDrawn(edgeId), transitionLabelAt: (edgeId: string) => editor.stateMachineEditor.canvas.transitionLabelAt(edgeId), hint: () => editor.stateMachineEditor.canvas.shownHint(), /** Names a transition the way the inspector row does, for steps that need one already named. */ nameTransition: (edgeId: string, name: string) => state.execute(new NameTransitionCommand(editor.stateMachineEditor.currentAssetId, edgeId, name)), dropTarget: () => editor.stateMachineEditor.canvas.pendingDropTarget(), danglingEdge: () => editor.stateMachineEditor.canvas.danglingEdgeVisible(), emptySpot: () => editor.stateMachineEditor.canvas.emptySpotGlobal(), selected: () => state.selectedNodeIds, /** The start ring and the live marks as drawn – what no colour check could tell apart. */ startRing: (nodeId: string) => editor.stateMachineEditor.canvas.startRingDrawn(nodeId), stateMarks: () => editor.stateMachineEditor.canvas.stateMarksDrawn(), }, particles: { current: () => editor.particlesEditor.currentAssetId, info: () => editor.particlesEditor.shownInfo, snapButton: () => snapButton(editor.particlesEditor.snap), nodeAt: (nodeId: string) => editor.particlesEditor.canvas.nodeGlobal(nodeId), nodeLabel: (nodeId: string) => editor.particlesEditor.canvas.nodeLabel(nodeId), nodeRect: (nodeId: string) => editor.particlesEditor.canvas.nodeRect(nodeId), nodeProblem: (nodeId: string) => editor.particlesEditor.canvas.nodeProblem(nodeId), canvasRect: () => editor.particlesEditor.canvas.canvasRect(), emptySpot: () => editor.particlesEditor.canvas.emptySpotGlobal(), center: () => centreOf(editor.particlesEditor.canvas), /** The panel beside the graph: how many particles it draws and which second it stands at. */ preview: () => ({ count: editor.particlesEditor.preview.count, time: Math.round(editor.particlesEditor.preview.time * 100) / 100, holding: editor.particlesEditor.preview.holding, }), previewRect: () => rectOf(editor.particlesEditor.preview), /** Hold button, time readout and slider as they are shown, not as the preview holds them. */ controls: () => editor.particlesEditor.controls, splitterAt: () => centreOf(editor.particlesEditor.splitter), sliderAt: () => centreOf(editor.particlesEditor.slider), /** Jumps the preview to a second – exact, because a particle is a function of its age. */ hold: (at: number | null) => editor.particlesEditor.preview.hold(at), }, shader: { current: () => editor.shaderEditor.currentAssetId, info: () => editor.shaderEditor.shownInfo, nodeAt: (nodeId: string) => editor.shaderEditor.canvas.nodeGlobal(nodeId), nodeLabel: (nodeId: string) => editor.shaderEditor.canvas.nodeLabel(nodeId), nodeRect: (nodeId: string) => editor.shaderEditor.canvas.nodeRect(nodeId), nodeProblem: (nodeId: string) => editor.shaderEditor.canvas.nodeProblem(nodeId), nodeSettings: (nodeId: string) => editor.shaderEditor.canvas.nodeSettings(nodeId), canvasRect: () => editor.shaderEditor.canvas.canvasRect(), emptySpot: () => editor.shaderEditor.canvas.emptySpotGlobal(), center: () => centreOf(editor.shaderEditor.canvas), snapping: () => editor.shaderEditor.canvas.snapEnabled, snapButton: () => snapButton(editor.shaderEditor.snap), /** The panel beside the graph (#168): what it shows, its clock and the sliders' values. */ preview: () => ({ ...editor.shaderEditor.controls, holding: editor.shaderEditor.preview.holding, seconds: Math.round(editor.shaderEditor.preview.time * 100) / 100, }), previewRect: () => rectOf(editor.shaderEditor.preview), /** Where the picture sits inside the panel, in screen pixels. */ pictureRect: () => editor.shaderEditor.preview.pictureRect, sliderAt: (key: string) => { const slider = editor.shaderEditor.sliderOf(key); return slider ? rectOf(slider) : null; }, splitterAt: () => centreOf(editor.shaderEditor.splitter), hold: (at: number | null) => editor.shaderEditor.hold(at), }, }; }