import { findAsset } from '../../core/model/Project'; import { allShapes, treeRows } from '../../core/model/vectorTree'; import { currentGradientBar, currentLayerList, currentShapeList } from '../inspector/shapeSection'; import { activeLayerOf } from '../vectorart/activeLayer'; import { centreOf, snapButton, type HookContext } from './hookHelpers'; /** The Vector art tab (#129). */ export function vectorArtHooks({ state, editor }: HookContext) { const { vectorArtEditor, inspector } = editor; /** The two lists, scrolled into view first: the layers stand over the shapes. */ const shapeList = () => { const list = currentShapeList(); if (list) inspector.reveal(list); return list; }; const layerList = () => { const list = currentLayerList(); if (list) inspector.reveal(list); return list; }; return { /** The Vector art tab (#129, stage 3): where a canvas pixel is, and which tool a press uses. */ vectorArt: { current: () => vectorArtEditor.currentAssetId, info: () => vectorArtEditor.shownInfo, pointAt: (x: number, y: number) => vectorArtEditor.canvasToScreen(x, y), rect: () => vectorArtEditor.canvasRect(), tool: () => vectorArtEditor.canvas.currentTool, snapping: () => vectorArtEditor.canvas.snapping, cursor: () => vectorArtEditor.canvas.cursor, snapButton: () => snapButton(vectorArtEditor.snap), /** The picked point of the selected shape (#288), `{ ring, index }` or null. */ picked: () => vectorArtEditor.canvas.pickedAt, /** Whether the pen has a line open that the next click would add a point to. */ drawing: () => vectorArtEditor.canvas.drawing, /** The header's point type (#195): where to click it, what it says, and the type it reads. */ pointButton: () => ({ ...centreOf(vectorArtEditor.pointButton), text: vectorArtEditor.pointButton.text, enabled: vectorArtEditor.pointButton.enabled, kind: vectorArtEditor.canvas.pickedKind, }), /** The shapes as the model holds them, in drawing order, so a check can read points and handles exactly. */ shapes: (assetId: string) => { const asset = findAsset(state.project, assetId); if (!asset || asset.type !== 'vectorart') return null; return allShapes(asset).map((shape) => ({ id: shape.id, name: shape.name, fill: shape.fill, opacity: shape.opacity, stroke: shape.stroke ? (JSON.parse(JSON.stringify(shape.stroke)) as unknown) : null, paint: shape.paint ? (JSON.parse(JSON.stringify(shape.paint)) as unknown) : null, rings: shape.rings.map((ring) => ({ closed: ring.closed, points: ring.points.map((point) => ({ x: point.x, y: point.y, in: point.in ? { ...point.in } : null, out: point.out ? { ...point.out } : null, mode: point.mode ?? null, })), })), })); }, /** * The layers (#190), lowest first: which one is worked on, and the tree of each as the list * shows it – top row first, a group's rows one step deeper. */ layers: (assetId: string) => { const asset = findAsset(state.project, assetId); if (!asset || asset.type !== 'vectorart') return null; const active = activeLayerOf(state, asset)?.id ?? ''; return asset.layers.map((layer) => ({ id: layer.id, name: layer.name, hidden: layer.hidden === true, active: layer.id === active, rows: treeRows(layer.items), })); }, /** The layer list in the inspector: the order it shows, top first, and where its parts sit. */ layerRows: () => currentLayerList()?.shownIds ?? null, layerRowAt: (layerId: string) => layerList()?.rowGlobal(layerId) ?? null, layerEyeAt: (layerId: string) => layerList()?.eyeGlobal(layerId) ?? null, layerRemoveAt: (layerId: string) => layerList()?.removeGlobal(layerId) ?? null, layerAddAt: () => layerList()?.addGlobal() ?? null, /** * The frame round the selection (#291) where it is drawn: its corners (top left first, * clockwise), the middle of each edge (top first) and the knob; null while there is none. */ frame: () => vectorArtEditor.canvas.frameInfo, selected: () => (state.selection?.kind === 'shape' ? state.selection.shapeId : ''), selectedIds: () => state.selectedShapeIds, /** * The shape list in the inspector (#129, stage 4): the order it shows, top first, and where a * row sits. A row carries a name that also stands in the tab, so a check cannot click it by text. */ shapeRows: () => currentShapeList()?.shownIds ?? null, shapeRowAt: (shapeId: string) => shapeList()?.rowGlobal(shapeId) ?? null, /** The cross on a shape's row. */ shapeRemoveAt: (shapeId: string) => shapeList()?.removeGlobal(shapeId) ?? null, /** The duplicate mark on a shape's row, and where a double-click edits its name. */ shapeDuplicateAt: (shapeId: string) => shapeList()?.duplicateGlobal(shapeId) ?? null, shapeNameAt: (shapeId: string) => shapeList()?.nameGlobal(shapeId) ?? null, /** The field a shape's name is being edited in, with what it holds; null while none is. */ shapeRenaming: () => { const field = currentShapeList()?.renameField; return field ? { ...centreOf(field), value: field.value } : null; }, /** The Paint tool (#186): its button, the "Handles" menu, and which part it shows. */ paintButton: () => centreOf(vectorArtEditor.paintButton), paintTarget: () => ({ ...centreOf(vectorArtEditor.paintTargetButton), text: vectorArtEditor.paintTargetButton.text, target: vectorArtEditor.canvas.paintTarget, }), /** A colour stop's marker, or the bar at an offset, in the inspector's fill or outline bar. */ stopAt: (part: 'fill' | 'stroke', index: number) => currentGradientBar(part)?.stopGlobal(index) ?? null, barAt: (part: 'fill' | 'stroke', offset: number) => currentGradientBar(part)?.barGlobal(offset) ?? null, }, }; }