UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

142 lines (141 loc) 4.7 kB
import { computed } from "@tldraw/state"; import { createComputedCache } from "@tldraw/store"; import { OverlayUtil } from "./OverlayUtil.mjs"; const indicatorPathCache = createComputedCache( "shapeIndicatorPath", (editor, shape) => { const util = editor.getShapeUtil(shape); return util.getIndicatorPath(shape); }, { areRecordsEqual(a, b) { return a.props === b.props; } } ); function strokeShapeIndicators(editor, ctx, shapeIds) { if (shapeIds.length === 0) return; const batched = new Path2D(); for (const shapeId of shapeIds) { const shape = editor.getShape(shapeId); if (!shape || shape.isLocked) continue; const pageTransform = editor.getShapePageTransform(shape); if (!pageTransform) continue; const indicatorPath = indicatorPathCache.get(editor, shape.id); if (!indicatorPath) continue; if (indicatorPath instanceof Path2D) { batched.addPath(indicatorPath, pageTransform); continue; } const { path, clipPath, additionalPaths } = indicatorPath; if (!clipPath) { batched.addPath(path, pageTransform); if (additionalPaths) { for (const p of additionalPaths) batched.addPath(p, pageTransform); } continue; } ctx.save(); ctx.transform( pageTransform.a, pageTransform.b, pageTransform.c, pageTransform.d, pageTransform.e, pageTransform.f ); ctx.save(); ctx.clip(clipPath, "evenodd"); ctx.stroke(path); ctx.restore(); if (additionalPaths) { for (const p of additionalPaths) ctx.stroke(p); } ctx.restore(); } ctx.stroke(batched); } class ShapeIndicatorOverlayUtil extends OverlayUtil { static type = "shape_indicator"; options = { zIndex: 50, lineWidth: 1.5, hintedLineWidth: 2.5 }; // Narrow projection of instance state. Reading the full record would // re-fire getOverlays on every cursor move / brush update; gating on these // three booleans means we only re-fire when one of them actually flips. _instanceFlags$ = computed( "shape indicator instance flags", () => { const i = this.editor.getInstanceState(); return { isChangingStyle: i.isChangingStyle, isHoveringCanvas: i.isHoveringCanvas, isCoarsePointer: i.isCoarsePointer }; }, { isEqual: (a, b) => a.isChangingStyle === b.isChangingStyle && a.isHoveringCanvas === b.isHoveringCanvas && a.isCoarsePointer === b.isCoarsePointer } ); isActive() { return true; } getOverlays() { const editor = this.editor; const renderingShapeIds = new Set(editor.getRenderingShapes().map((s) => s.id)); const idsToDisplay = []; const { isChangingStyle, isHoveringCanvas, isCoarsePointer } = this._instanceFlags$.get(); const isIdleOrEditing = editor.isInAny("select.idle", "select.editing_shape"); const isInSelectState = editor.isInAny( "select.brushing", "select.scribble_brushing", "select.pointing_shape", "select.pointing_selection", "select.pointing_handle" ); if (!isChangingStyle && (isIdleOrEditing || isInSelectState)) { for (const id of editor.getSelectedShapeIds()) { if (renderingShapeIds.has(id)) idsToDisplay.push(id); } if (isIdleOrEditing && isHoveringCanvas && !isCoarsePointer) { const hovered = editor.getHoveredShapeId(); if (hovered && renderingShapeIds.has(hovered) && !idsToDisplay.includes(hovered)) { idsToDisplay.push(hovered); } } } const hintingShapeIds = []; for (const id of editor.getHintingShapeIds()) { if (renderingShapeIds.has(id)) hintingShapeIds.push(id); } if (idsToDisplay.length === 0 && hintingShapeIds.length === 0) { return []; } return [ { id: "shape_indicator", type: "shape_indicator", props: { idsToDisplay, hintingShapeIds } } ]; } render(ctx, overlays) { const overlay = overlays[0]; if (!overlay) return; const editor = this.editor; const zoom = editor.getZoomLevel(); const { idsToDisplay, hintingShapeIds } = overlay.props; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.strokeStyle = editor.getCurrentTheme().colors[editor.getColorMode()].selectionStroke; ctx.lineWidth = this.options.lineWidth / zoom; strokeShapeIndicators(editor, ctx, idsToDisplay); if (hintingShapeIds.length > 0) { ctx.lineWidth = this.options.hintedLineWidth / zoom; strokeShapeIndicators(editor, ctx, hintingShapeIds); } } } export { ShapeIndicatorOverlayUtil, strokeShapeIndicators }; //# sourceMappingURL=ShapeIndicatorOverlayUtil.mjs.map