import { commandIds } from "@commands/ids"; import type { Dispatch } from "@commands/dispatcher"; import type { InputDocument, InputLayer } from "./read-model"; import { resolveTransformTargetBounds, viewportPointToDocumentPoint, type InputViewportState } from "./document-geometry"; import type { PointerInputEvent } from "./pointer"; export function handleArtboardSelection(options: { event: PointerInputEvent; document: InputDocument; viewport: InputViewportState; dispatch: Dispatch; }): boolean { if (options.event.pointerType !== "mouse" || (options.event.buttons & 1) !== 1) return false; const point = viewportPointToDocumentPoint(options.event.position, options.viewport); const layerHit = findTopmostLayerAtPoint(options.document, point); if (layerHit) { options.dispatch(commandIds.selectionSet, { artboardId: layerHit.artboardId, layerIds: [layerHit.layerId] }); return true; } const artboard = [...options.document.artboards].reverse().find((candidate) => { if (!candidate.visible || candidate.locked) return false; const bounds = candidate.bounds; return point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h; }); if (!artboard) { options.dispatch(commandIds.selectionClear, undefined); return true; } options.dispatch(commandIds.selectionSet, { artboardId: artboard.id, layerIds: [] }); return true; } function findTopmostLayerAtPoint(document: InputDocument, point: { x: number; y: number }) { const maskLayerIds = collectMaskLayerIds(document.artboards.flatMap((artboard) => artboard.layers)); for (const artboard of [...document.artboards].reverse()) { if (!artboard.visible || artboard.locked) continue; const layerId = findTopmostLayerInTreeAtPoint(document, [...artboard.layers].reverse(), point, maskLayerIds); if (layerId) return { artboardId: artboard.id, layerId }; } return undefined; } function findTopmostLayerInTreeAtPoint(document: InputDocument, layers: InputLayer[], point: { x: number; y: number }, maskLayerIds: ReadonlySet): string | undefined { for (const layer of layers) { if (!layer.visible || layer.locked || maskLayerIds.has(layer.id)) continue; if (layer.type === "group") { const childId = findTopmostLayerInTreeAtPoint(document, [...layer.children].reverse(), point, maskLayerIds); if (childId) return childId; } const bounds = resolveTransformTargetBounds(document, { type: "layer", id: layer.id }); if (bounds && point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h) { return layer.id; } } return undefined; } function collectMaskLayerIds(layers: readonly InputLayer[], ids = new Set()): Set { for (const layer of layers) { const layerMask = layer.layerMask ?? layer.clippingMask; if (layerMask) ids.add(layerMask.maskLayerId); if (layer.type === "group") collectMaskLayerIds(layer.children, ids); } return ids; }