{ "version": 3, "sources": ["../../../../../src/lib/tools/SelectTool/children/Brushing.ts"], "sourcesContent": ["import {\n\tBox2d,\n\tHIT_TEST_MARGIN,\n\tMatrix2d,\n\tStateNode,\n\tTLCancelEvent,\n\tTLEventHandlers,\n\tTLFrameShape,\n\tTLGroupShape,\n\tTLInterruptEvent,\n\tTLKeyboardEvent,\n\tTLPageId,\n\tTLPointerEventInfo,\n\tTLShape,\n\tTLShapeId,\n\tVec2d,\n\tpointInPolygon,\n\tpolygonsIntersect,\n} from '@tldraw/editor'\n\nexport class Brushing extends StateNode {\n\tstatic override id = 'brushing'\n\n\tinfo = {} as TLPointerEventInfo & { target: 'canvas' }\n\n\tbrush = new Box2d()\n\tinitialSelectedShapeIds: TLShapeId[] = []\n\texcludedShapeIds = new Set()\n\n\t// The shape that the brush started on\n\tinitialStartShape: TLShape | null = null\n\n\toverride onEnter = (info: TLPointerEventInfo & { target: 'canvas' }) => {\n\t\tconst { altKey, currentPagePoint } = this.editor.inputs\n\n\t\tif (altKey) {\n\t\t\tthis.parent.transition('scribble_brushing', info)\n\t\t\treturn\n\t\t}\n\n\t\tthis.excludedShapeIds = new Set(\n\t\t\tthis.editor.currentPageShapes\n\t\t\t\t.filter(\n\t\t\t\t\t(shape) =>\n\t\t\t\t\t\tthis.editor.isShapeOfType(shape, 'group') ||\n\t\t\t\t\t\tthis.editor.isShapeOrAncestorLocked(shape)\n\t\t\t\t)\n\t\t\t\t.map((shape) => shape.id)\n\t\t)\n\n\t\tthis.info = info\n\t\tthis.initialSelectedShapeIds = this.editor.selectedShapeIds.slice()\n\t\tthis.initialStartShape = this.editor.getShapesAtPoint(currentPagePoint)[0]\n\t\tthis.onPointerMove()\n\t}\n\n\toverride onExit = () => {\n\t\tthis.initialSelectedShapeIds = []\n\t\tthis.editor.updateInstanceState({ brush: null })\n\t}\n\n\toverride onPointerMove = () => {\n\t\tthis.hitTestShapes()\n\t}\n\n\toverride onPointerUp: TLEventHandlers['onPointerUp'] = () => {\n\t\tthis.complete()\n\t}\n\n\toverride onComplete: TLEventHandlers['onComplete'] = () => {\n\t\tthis.complete()\n\t}\n\n\toverride onCancel?: TLCancelEvent | undefined = (info) => {\n\t\tthis.editor.setSelectedShapes(this.initialSelectedShapeIds, { squashing: true })\n\t\tthis.parent.transition('idle', info)\n\t}\n\n\toverride onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {\n\t\tif (this.editor.inputs.altKey) {\n\t\t\tthis.parent.transition('scribble_brushing', info)\n\t\t} else {\n\t\t\tthis.hitTestShapes()\n\t\t}\n\t}\n\n\toverride onKeyUp?: TLKeyboardEvent | undefined = () => {\n\t\tthis.hitTestShapes()\n\t}\n\n\tprivate complete() {\n\t\tthis.parent.transition('idle', {})\n\t}\n\n\tprivate hitTestShapes() {\n\t\tconst {\n\t\t\tzoomLevel,\n\t\t\tcurrentPageId,\n\t\t\tcurrentPageShapes: currentPageShapes,\n\t\t\tinputs: { originPagePoint, currentPagePoint, shiftKey, ctrlKey },\n\t\t} = this.editor\n\n\t\t// Set the brush to contain the current and origin points\n\t\tthis.brush.setTo(Box2d.FromPoints([originPagePoint, currentPagePoint]))\n\n\t\t// We'll be collecting shape ids\n\t\tconst results = new Set(shiftKey ? this.initialSelectedShapeIds : [])\n\n\t\tlet A: Vec2d,\n\t\t\tB: Vec2d,\n\t\t\tshape: TLShape,\n\t\t\tpageBounds: Box2d | undefined,\n\t\t\tpageTransform: Matrix2d | undefined,\n\t\t\tlocalCorners: Vec2d[]\n\n\t\t// We'll be testing the corners of the brush against the shapes\n\t\tconst { corners } = this.brush\n\n\t\tconst { excludedShapeIds } = this\n\n\t\ttestAllShapes: for (let i = 0, n = currentPageShapes.length; i < n; i++) {\n\t\t\tshape = currentPageShapes[i]\n\t\t\tif (excludedShapeIds.has(shape.id)) continue testAllShapes\n\t\t\tif (results.has(shape.id)) continue testAllShapes\n\n\t\t\tpageBounds = this.editor.getShapePageBounds(shape)\n\t\t\tif (!pageBounds) continue testAllShapes\n\n\t\t\t// If the brush fully wraps a shape, it's almost certainly a hit\n\t\t\tif (this.brush.contains(pageBounds)) {\n\t\t\t\tthis.handleHit(shape, currentPagePoint, currentPageId, results, corners)\n\t\t\t\tcontinue testAllShapes\n\t\t\t}\n\n\t\t\t// Should we even test for a single segment intersections? Only if\n\t\t\t// we're not holding the ctrl key for alternate selection mode\n\t\t\t// (only wraps count!), or if the shape is a frame.\n\t\t\tif (ctrlKey || this.editor.isShapeOfType(shape, 'frame')) {\n\t\t\t\tcontinue testAllShapes\n\t\t\t}\n\n\t\t\t// If the brush collides the page bounds, then do hit tests against\n\t\t\t// each of the brush's four sides.\n\t\t\tif (this.brush.collides(pageBounds)) {\n\t\t\t\t// Shapes expect to hit test line segments in their own coordinate system,\n\t\t\t\t// so we first need to get the brush corners in the shape's local space.\n\t\t\t\tconst geometry = this.editor.getShapeGeometry(shape)\n\n\t\t\t\tpageTransform = this.editor.getShapePageTransform(shape)\n\n\t\t\t\tif (!pageTransform) {\n\t\t\t\t\tcontinue testAllShapes\n\t\t\t\t}\n\n\t\t\t\t// Check whether any of the the brush edges intersect the shape\n\t\t\t\tlocalCorners = pageTransform.clone().invert().applyToPoints(corners)\n\n\t\t\t\thitTestBrushEdges: for (let i = 0; i < localCorners.length; i++) {\n\t\t\t\t\tA = localCorners[i]\n\t\t\t\t\tB = localCorners[(i + 1) % localCorners.length]\n\n\t\t\t\t\tif (geometry.hitTestLineSegment(A, B, HIT_TEST_MARGIN / zoomLevel)) {\n\t\t\t\t\t\tthis.handleHit(shape, currentPagePoint, currentPageId, results, corners)\n\t\t\t\t\t\tbreak hitTestBrushEdges\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.editor.updateInstanceState({ brush: { ...this.brush.toJson() } })\n\t\tthis.editor.setSelectedShapes(Array.from(results), { squashing: true })\n\t}\n\n\toverride onInterrupt: TLInterruptEvent = () => {\n\t\tthis.editor.updateInstanceState({ brush: null })\n\t}\n\n\tprivate handleHit(\n\t\tshape: TLShape,\n\t\tcurrentPagePoint: Vec2d,\n\t\tcurrentPageId: TLPageId,\n\t\tresults: Set,\n\t\tcorners: Vec2d[]\n\t) {\n\t\tif (shape.parentId === currentPageId) {\n\t\t\tresults.add(shape.id)\n\t\t\treturn\n\t\t}\n\n\t\t// Find the outermost selectable shape, check to see if it has a\n\t\t// page mask; and if so, check to see if the brush intersects it\n\t\tconst selectedShape = this.editor.getOutermostSelectableShape(shape)\n\t\tconst pageMask = this.editor.getShapeMask(selectedShape.id)\n\n\t\tif (\n\t\t\tpageMask &&\n\t\t\t!polygonsIntersect(pageMask, corners) &&\n\t\t\t!pointInPolygon(currentPagePoint, pageMask)\n\t\t) {\n\t\t\treturn\n\t\t}\n\n\t\tresults.add(selectedShape.id)\n\t}\n}\n"], "mappings": "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", "names": ["i"] }