@yuzhouu/canvas-kit 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -0
- package/_vendor/canvas-core/bindings/BindingUtil.ts +92 -0
- package/_vendor/canvas-core/bindings/BindingUtilRegistry.ts +22 -0
- package/_vendor/canvas-core/bindings/arrow/arrowBindingHelpers.ts +52 -0
- package/_vendor/canvas-core/contextMenu.ts +85 -0
- package/_vendor/canvas-core/editor/Editor.ts +2614 -0
- package/_vendor/canvas-core/editor/HistoryManager.ts +83 -0
- package/_vendor/canvas-core/editor/StateNode.ts +162 -0
- package/_vendor/canvas-core/editor/camera.ts +6 -0
- package/_vendor/canvas-core/editor/clipboard.ts +478 -0
- package/_vendor/canvas-core/editor/commands.ts +833 -0
- package/_vendor/canvas-core/editor/edgeScroll.ts +37 -0
- package/_vendor/canvas-core/editor/editorComputedHelpers.ts +257 -0
- package/_vendor/canvas-core/editor/editorSelectionTransforms.ts +116 -0
- package/_vendor/canvas-core/editor/events.ts +93 -0
- package/_vendor/canvas-core/editor/index.ts +12 -0
- package/_vendor/canvas-core/editor/interactions.ts +101 -0
- package/_vendor/canvas-core/editor/internal.ts +22 -0
- package/_vendor/canvas-core/editor/managers/BindingManager.ts +262 -0
- package/_vendor/canvas-core/editor/managers/CameraManager.ts +147 -0
- package/_vendor/canvas-core/editor/managers/ClipboardManager.ts +32 -0
- package/_vendor/canvas-core/editor/managers/DocumentSnapshotManager.ts +256 -0
- package/_vendor/canvas-core/editor/managers/InputsManager.ts +319 -0
- package/_vendor/canvas-core/editor/managers/InteractionTransientManager.ts +218 -0
- package/_vendor/canvas-core/editor/managers/QueryManager.ts +837 -0
- package/_vendor/canvas-core/editor/managers/RenderWindowManager.ts +347 -0
- package/_vendor/canvas-core/editor/managers/SelectionLayoutManager.ts +196 -0
- package/_vendor/canvas-core/editor/managers/SelectionManager.ts +98 -0
- package/_vendor/canvas-core/editor/managers/SelectionOverlayManager.ts +308 -0
- package/_vendor/canvas-core/editor/managers/ShapeEditingManager.ts +226 -0
- package/_vendor/canvas-core/editor/managers/ShapeGeometryManager.ts +162 -0
- package/_vendor/canvas-core/editor/managers/ShapeHierarchyManager.ts +797 -0
- package/_vendor/canvas-core/editor/managers/SideEffectManager.ts +83 -0
- package/_vendor/canvas-core/editor/managers/SnapManager.ts +433 -0
- package/_vendor/canvas-core/editor/managers/SnapSnapshotBuilder.ts +430 -0
- package/_vendor/canvas-core/editor/managers/SpatialIndexManager.ts +354 -0
- package/_vendor/canvas-core/editor/managers/ToolManager.ts +81 -0
- package/_vendor/canvas-core/editor/managers/queryState.ts +583 -0
- package/_vendor/canvas-core/editor/managers/selectionGeometry.ts +287 -0
- package/_vendor/canvas-core/editor/managers/shapeOrder.ts +350 -0
- package/_vendor/canvas-core/editor/managers/shapeReparenting.ts +160 -0
- package/_vendor/canvas-core/editor/managers/snapCalculations.ts +412 -0
- package/_vendor/canvas-core/editor/managers/snapGeometry.ts +202 -0
- package/_vendor/canvas-core/editor/managers/snapIndicators.ts +403 -0
- package/_vendor/canvas-core/editor/managers/snapTypes.ts +122 -0
- package/_vendor/canvas-core/editor/queries/shapeTransforms.ts +171 -0
- package/_vendor/canvas-core/editor/renderWindow.ts +439 -0
- package/_vendor/canvas-core/editor/resizeGeometry.ts +654 -0
- package/_vendor/canvas-core/editor/selection.ts +489 -0
- package/_vendor/canvas-core/editor/selectionHandleInteractions.ts +43 -0
- package/_vendor/canvas-core/editor/selectionOverlay.ts +567 -0
- package/_vendor/canvas-core/exports/integrity.ts +174 -0
- package/_vendor/canvas-core/exports/svg.ts +494 -0
- package/_vendor/canvas-core/geometry/Box.ts +80 -0
- package/_vendor/canvas-core/geometry/Ellipse2d.ts +67 -0
- package/_vendor/canvas-core/geometry/Geometry2d.ts +52 -0
- package/_vendor/canvas-core/geometry/Mat.ts +92 -0
- package/_vendor/canvas-core/geometry/Polyline2d.ts +63 -0
- package/_vendor/canvas-core/geometry/Rectangle2d.ts +64 -0
- package/_vendor/canvas-core/geometry/Vec.ts +123 -0
- package/_vendor/canvas-core/geometry/index.ts +13 -0
- package/_vendor/canvas-core/geometry/segments.ts +65 -0
- package/_vendor/canvas-core/index.ts +391 -0
- package/_vendor/canvas-core/plugins/index.ts +5 -0
- package/_vendor/canvas-core/plugins/types.ts +53 -0
- package/_vendor/canvas-core/reactivity.ts +16 -0
- package/_vendor/canvas-core/schema/records.ts +221 -0
- package/_vendor/canvas-core/schema/snapshots.ts +527 -0
- package/_vendor/canvas-core/shapes/ShapeUtil.ts +516 -0
- package/_vendor/canvas-core/shapes/ShapeUtilRegistry.ts +26 -0
- package/_vendor/canvas-core/shapes/capabilities.ts +67 -0
- package/_vendor/canvas-core/shapes/types.ts +20 -0
- package/_vendor/canvas-core/toolbar.ts +148 -0
- package/_vendor/canvas-core/tools/hand/HandTool.ts +75 -0
- package/_vendor/canvas-core/tools/index.ts +2 -0
- package/_vendor/canvas-core/tools/select/SelectTool.ts +33 -0
- package/_vendor/canvas-core/tools/select/selectContainment.ts +104 -0
- package/_vendor/canvas-core/tools/select/selectToolHelpers.ts +801 -0
- package/_vendor/canvas-core/tools/select/states/BrushingState.ts +145 -0
- package/_vendor/canvas-core/tools/select/states/DraggingSelectionHandleInteractionState.ts +116 -0
- package/_vendor/canvas-core/tools/select/states/IdleState.ts +159 -0
- package/_vendor/canvas-core/tools/select/states/PointingCanvasState.ts +41 -0
- package/_vendor/canvas-core/tools/select/states/PointingResizeHandleState.ts +78 -0
- package/_vendor/canvas-core/tools/select/states/PointingRotateHandleState.ts +64 -0
- package/_vendor/canvas-core/tools/select/states/PointingSelectionHandleInteractionState.ts +34 -0
- package/_vendor/canvas-core/tools/select/states/PointingShapeState.ts +140 -0
- package/_vendor/canvas-core/tools/select/states/ResizingState.ts +171 -0
- package/_vendor/canvas-core/tools/select/states/RotatingState.ts +124 -0
- package/_vendor/canvas-core/tools/select/states/SelectStateNode.ts +128 -0
- package/_vendor/canvas-core/tools/select/states/TranslatingState.ts +139 -0
- package/_vendor/canvas-core/tools/select/states/index.ts +11 -0
- package/_vendor/canvas-plugin-api/feature.ts +204 -0
- package/_vendor/canvas-plugin-api/index.ts +142 -0
- package/_vendor/canvas-product-react/api/apiRegistry.ts +35 -0
- package/_vendor/canvas-product-react/api/canvasProductApi.ts +422 -0
- package/_vendor/canvas-product-react/api/index.ts +31 -0
- package/_vendor/canvas-product-react/capabilities/clipboard/clipboardService.ts +36 -0
- package/_vendor/canvas-product-react/capabilities/clipboard/systemClipboard.ts +245 -0
- package/_vendor/canvas-product-react/capabilities/context-menu/systemContextMenu.ts +396 -0
- package/_vendor/canvas-product-react/capabilities/context-menu/useContextMenuSurface.ts +59 -0
- package/_vendor/canvas-product-react/capabilities/export/useSvgExport.ts +156 -0
- package/_vendor/canvas-product-react/capabilities/history/useHistoryControls.ts +23 -0
- package/_vendor/canvas-product-react/capabilities/pages/usePageControls.ts +41 -0
- package/_vendor/canvas-product-react/capabilities/toolbar/systemToolbar.ts +490 -0
- package/_vendor/canvas-product-react/capabilities/toolbar/useToolbarSurfaces.ts +92 -0
- package/_vendor/canvas-product-react/capabilities/zoom/useZoomControls.ts +27 -0
- package/_vendor/canvas-product-react/index.ts +84 -0
- package/_vendor/canvas-product-react/persistence/index.ts +18 -0
- package/_vendor/canvas-product-react/persistence/localSnapshotPersistence.ts +294 -0
- package/_vendor/canvas-product-react/runtime/CanvasProductRoot.tsx +250 -0
- package/_vendor/canvas-product-react/runtime/productDefinition.ts +72 -0
- package/_vendor/canvas-product-react/runtime/productServices.ts +96 -0
- package/_vendor/canvas-react/components/CanvasView.tsx +796 -0
- package/_vendor/canvas-react/components/OverlayLayer.tsx +879 -0
- package/_vendor/canvas-react/components/PageSpace.tsx +34 -0
- package/_vendor/canvas-react/components/ShapeLayer.tsx +428 -0
- package/_vendor/canvas-react/components/index.ts +4 -0
- package/_vendor/canvas-react/hooks/useEditor.ts +11 -0
- package/_vendor/canvas-react/hooks/useValue.ts +14 -0
- package/_vendor/canvas-react/host/CanvasRendererRegistryContext.tsx +14 -0
- package/_vendor/canvas-react/host/contextMenuRegistry.ts +61 -0
- package/_vendor/canvas-react/host/domEventGuards.ts +61 -0
- package/_vendor/canvas-react/host/index.ts +38 -0
- package/_vendor/canvas-react/host/pluginHost.ts +635 -0
- package/_vendor/canvas-react/host/rendererRegistry.tsx +58 -0
- package/_vendor/canvas-react/host/toolbarRegistry.ts +100 -0
- package/_vendor/canvas-react/index.ts +3 -0
- package/_vendor/plugin-arrow/ArrowBindingUtil.ts +239 -0
- package/_vendor/plugin-arrow/ArrowShapeUtil.ts +311 -0
- package/_vendor/plugin-arrow/ArrowTool.ts +208 -0
- package/_vendor/plugin-arrow/arrowGeometry.ts +441 -0
- package/_vendor/plugin-arrow/arrowTerminalBindingResolver.ts +253 -0
- package/_vendor/plugin-arrow/arrowTerminalCommands.ts +230 -0
- package/_vendor/plugin-arrow/canvas.tsx +92 -0
- package/_vendor/plugin-arrow/core.ts +409 -0
- package/_vendor/plugin-arrow/feature.ts +20 -0
- package/_vendor/plugin-arrow/index.ts +28 -0
- package/_vendor/plugin-arrow/selectionHandleInteractions.ts +154 -0
- package/_vendor/plugin-arrow/types.ts +63 -0
- package/_vendor/plugin-draw/DrawShapeRenderer.tsx +60 -0
- package/_vendor/plugin-draw/DrawShapeUtil.ts +372 -0
- package/_vendor/plugin-draw/DrawTool.ts +444 -0
- package/_vendor/plugin-draw/canvas.tsx +24 -0
- package/_vendor/plugin-draw/core.ts +515 -0
- package/_vendor/plugin-draw/drawGeometry.ts +491 -0
- package/_vendor/plugin-draw/drawHitTest.ts +271 -0
- package/_vendor/plugin-draw/drawInterpolation.ts +250 -0
- package/_vendor/plugin-draw/drawPath.ts +550 -0
- package/_vendor/plugin-draw/drawSegments.ts +106 -0
- package/_vendor/plugin-draw/drawSelectionTransform.ts +67 -0
- package/_vendor/plugin-draw/drawShapeGeometry.ts +307 -0
- package/_vendor/plugin-draw/drawStrokeGeometry.ts +454 -0
- package/_vendor/plugin-draw/feature.ts +20 -0
- package/_vendor/plugin-draw/index.ts +31 -0
- package/_vendor/plugin-draw/types.ts +36 -0
- package/_vendor/plugin-eraser/EraserTool.ts +297 -0
- package/_vendor/plugin-eraser/canvas.tsx +616 -0
- package/_vendor/plugin-eraser/core.ts +49 -0
- package/_vendor/plugin-eraser/feature.ts +20 -0
- package/_vendor/plugin-eraser/index.ts +4 -0
- package/_vendor/plugin-frame/FrameShapeUtil.ts +173 -0
- package/_vendor/plugin-frame/FrameTool.ts +225 -0
- package/_vendor/plugin-frame/canvas.tsx +75 -0
- package/_vendor/plugin-frame/core.ts +534 -0
- package/_vendor/plugin-frame/feature.ts +20 -0
- package/_vendor/plugin-frame/index.ts +6 -0
- package/_vendor/plugin-geo/GeoShapeUtil.ts +463 -0
- package/_vendor/plugin-geo/GeoTools.ts +247 -0
- package/_vendor/plugin-geo/canvas.tsx +90 -0
- package/_vendor/plugin-geo/core.ts +495 -0
- package/_vendor/plugin-geo/feature.ts +20 -0
- package/_vendor/plugin-geo/index.ts +7 -0
- package/_vendor/plugin-geo/types.ts +27 -0
- package/_vendor/plugin-group/GroupShapeUtil.ts +59 -0
- package/_vendor/plugin-group/canvas.tsx +59 -0
- package/_vendor/plugin-group/core.ts +40 -0
- package/_vendor/plugin-group/feature.ts +20 -0
- package/_vendor/plugin-group/index.ts +5 -0
- package/_vendor/plugin-image/ImageShapeUtil.ts +95 -0
- package/_vendor/plugin-image/canvas.tsx +212 -0
- package/_vendor/plugin-image/core.ts +542 -0
- package/_vendor/plugin-image/crop.ts +770 -0
- package/_vendor/plugin-image/cropInteractions.ts +350 -0
- package/_vendor/plugin-image/cropSession.ts +38 -0
- package/_vendor/plugin-image/feature.ts +20 -0
- package/_vendor/plugin-image/index.ts +29 -0
- package/_vendor/plugin-image/types.ts +55 -0
- package/_vendor/plugin-image-product-react/ImageImportController.tsx +143 -0
- package/_vendor/plugin-image-product-react/imageAssetService.ts +52 -0
- package/_vendor/plugin-image-product-react/imageBlobStore.ts +315 -0
- package/_vendor/plugin-image-product-react/imageHostActions.ts +438 -0
- package/_vendor/plugin-image-product-react/imageProductApi.ts +88 -0
- package/_vendor/plugin-image-product-react/imageToolbarHostBridge.ts +48 -0
- package/_vendor/plugin-image-product-react/imageToolbarRuntime.ts +21 -0
- package/_vendor/plugin-image-product-react/index.ts +60 -0
- package/_vendor/plugin-image-product-react/snapshotGuards.ts +33 -0
- package/_vendor/plugin-image-product-react/svgImageAssetInliner.ts +54 -0
- package/_vendor/plugin-image-product-react/useImageImport.ts +64 -0
- package/_vendor/plugin-laser/LaserTool.ts +154 -0
- package/_vendor/plugin-laser/canvas.tsx +645 -0
- package/_vendor/plugin-laser/core.ts +49 -0
- package/_vendor/plugin-laser/feature.ts +20 -0
- package/_vendor/plugin-laser/index.ts +4 -0
- package/_vendor/plugin-text/ArrowLabelBindingUtil.ts +336 -0
- package/_vendor/plugin-text/GeoLabelBindingUtil.ts +391 -0
- package/_vendor/plugin-text/TextShapeSvgExporter.ts +101 -0
- package/_vendor/plugin-text/TextShapeUtil.ts +284 -0
- package/_vendor/plugin-text/TextTool.ts +228 -0
- package/_vendor/plugin-text/canvas.tsx +115 -0
- package/_vendor/plugin-text/core.ts +422 -0
- package/_vendor/plugin-text/feature.ts +20 -0
- package/_vendor/plugin-text/host/RichTextEditingBox.tsx +144 -0
- package/_vendor/plugin-text/host/RichTextToolbar.tsx +266 -0
- package/_vendor/plugin-text/host/TiptapTextEditor.tsx +191 -0
- package/_vendor/plugin-text/host/richTextRendering.tsx +49 -0
- package/_vendor/plugin-text/host/textDomSelection.ts +93 -0
- package/_vendor/plugin-text/host/textEditingBoxStyle.ts +36 -0
- package/_vendor/plugin-text/host/textEntryFocus.ts +307 -0
- package/_vendor/plugin-text/host/textFontLoader.ts +149 -0
- package/_vendor/plugin-text/host/textMeasurement.ts +258 -0
- package/_vendor/plugin-text/host/textMeasurementSync.ts +77 -0
- package/_vendor/plugin-text/host/textRangeSelection.ts +134 -0
- package/_vendor/plugin-text/host/textShapeRendererRuntime.tsx +89 -0
- package/_vendor/plugin-text/host/textShapeSync.ts +173 -0
- package/_vendor/plugin-text/host/tiptapTextEditorController.ts +148 -0
- package/_vendor/plugin-text/index.ts +18 -0
- package/_vendor/plugin-text/richTextUtils.ts +259 -0
- package/_vendor/plugin-text/textInteractions.ts +366 -0
- package/_vendor/plugin-text/textLabelHelpers.ts +235 -0
- package/_vendor/plugin-text/textShapeHelpers.ts +123 -0
- package/_vendor/plugin-text/textStyle.ts +96 -0
- package/_vendor/plugin-text/textTypes.ts +48 -0
- package/_vendor/signals/atom.ts +74 -0
- package/_vendor/signals/computed.ts +132 -0
- package/_vendor/signals/core.ts +130 -0
- package/_vendor/signals/effect.ts +117 -0
- package/_vendor/signals/index.ts +9 -0
- package/_vendor/signals/types.ts +17 -0
- package/_vendor/store/Store.ts +171 -0
- package/_vendor/store/diff.ts +229 -0
- package/_vendor/store/history.ts +177 -0
- package/_vendor/store/index.ts +32 -0
- package/_vendor/store/types.ts +37 -0
- package/api/canvasKitApi.d.ts +29 -0
- package/index.css +3 -0
- package/index.d.ts +16 -0
- package/index.mjs +1 -0
- package/internal-modules.d.ts +187 -0
- package/package.json +48 -0
- package/product/Shell.d.ts +19 -0
- package/product/chromeContext.d.ts +70 -0
- package/product/createCanvasKitEditor.d.ts +6 -0
- package/product/index.d.ts +7 -0
- package/product/persistence.d.ts +3 -0
- package/product/product.d.ts +22 -0
- package/product/useCanvasClipboardShortcuts.d.ts +6 -0
- package/product-iyBbEPIs.mjs +4 -0
- package/product.mjs +1 -0
- package/standard/extensions.d.ts +1 -0
- package/standard/index.d.ts +3 -0
- package/standard/standardApi.d.ts +91 -0
- package/standard-BmrxwqpJ.mjs +44 -0
- package/standard.mjs +1 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import { memo, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { type LandCanvasPlugin } from "@land/canvas-plugin-api";
|
|
3
|
+
import { type Vec } from "@land/canvas-core";
|
|
4
|
+
import { PageSpace, useEditor, useValue } from "@land/canvas-react";
|
|
5
|
+
|
|
6
|
+
export function createEraserCanvasPlugin(): LandCanvasPlugin {
|
|
7
|
+
return {
|
|
8
|
+
id: "land.eraser.react",
|
|
9
|
+
canvasComponents: [
|
|
10
|
+
{
|
|
11
|
+
id: "land.eraser.trail-overlay",
|
|
12
|
+
component: EraserTrailOverlay,
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
setup() {
|
|
16
|
+
// Eraser does not render shape records.
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const EraserTrailOverlay = memo(function EraserTrailOverlay() {
|
|
22
|
+
const editor = useEditor();
|
|
23
|
+
const isTrailActive = useValue(editor.eraserTrailActiveSignal);
|
|
24
|
+
|
|
25
|
+
if (!isTrailActive) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<PageSpace
|
|
31
|
+
dataUi="eraser-trail-page-space"
|
|
32
|
+
className="pointer-events-none absolute inset-0"
|
|
33
|
+
>
|
|
34
|
+
<svg
|
|
35
|
+
width="100%"
|
|
36
|
+
height="100%"
|
|
37
|
+
style={{
|
|
38
|
+
display: "block",
|
|
39
|
+
width: "100%",
|
|
40
|
+
height: "100%",
|
|
41
|
+
overflow: "visible",
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<EraserTrailContent />
|
|
45
|
+
</svg>
|
|
46
|
+
</PageSpace>
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const EraserTrailContent = memo(function EraserTrailContent() {
|
|
51
|
+
const editor = useEditor();
|
|
52
|
+
const trail = useValue(editor.eraserTrailSignal);
|
|
53
|
+
const zoom = useValue(editor.cameraZoomSignal);
|
|
54
|
+
const safeZoom = Math.max(zoom, 0.0001);
|
|
55
|
+
const clockTime = useAnimationClock();
|
|
56
|
+
const sampleState = useRef<EraserTrailSampleState>({
|
|
57
|
+
samples: [],
|
|
58
|
+
sourceLength: 0,
|
|
59
|
+
});
|
|
60
|
+
const renderTime = Math.max(clockTime, getNowMs());
|
|
61
|
+
syncEraserTrailSampleState(
|
|
62
|
+
sampleState.current,
|
|
63
|
+
trail,
|
|
64
|
+
safeZoom,
|
|
65
|
+
renderTime,
|
|
66
|
+
);
|
|
67
|
+
const trailPoints = getRenderableEraserTrailPoints(
|
|
68
|
+
sampleState.current.samples,
|
|
69
|
+
renderTime,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (trailPoints.length === 0) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const path = getEraserScribblePath(
|
|
77
|
+
trailPoints,
|
|
78
|
+
ERASER_SCRIBBLE_SIZE / safeZoom,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!path) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<g data-ui="eraser-trail-overlay">
|
|
87
|
+
<path
|
|
88
|
+
data-ui="eraser-trail-scribble"
|
|
89
|
+
d={path}
|
|
90
|
+
fill={ERASER_SCRIBBLE_FILL}
|
|
91
|
+
/>
|
|
92
|
+
</g>
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
interface TrailRenderPoint {
|
|
97
|
+
x: number;
|
|
98
|
+
y: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface EraserTrailTimedPoint extends TrailRenderPoint {
|
|
102
|
+
time: number;
|
|
103
|
+
velocity: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface EraserTrailSampleState {
|
|
107
|
+
samples: EraserTrailTimedPoint[];
|
|
108
|
+
sourceLength: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface EraserScribbleSample {
|
|
112
|
+
point: TrailRenderPoint;
|
|
113
|
+
direction: TrailRenderPoint;
|
|
114
|
+
radius: number;
|
|
115
|
+
runningLength: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const ERASER_SCRIBBLE_FILL = "rgba(142, 142, 142, 0.16)";
|
|
119
|
+
const ERASER_SCRIBBLE_MAX_POINTS = 48;
|
|
120
|
+
const ERASER_SCRIBBLE_MIN_POINT_DISTANCE = 1;
|
|
121
|
+
const ERASER_SCRIBBLE_SIZE = 12;
|
|
122
|
+
const ERASER_SCRIBBLE_STREAMLINE = 0.32;
|
|
123
|
+
const ERASER_SCRIBBLE_CAP_STEPS = 8;
|
|
124
|
+
const ERASER_SCRIBBLE_DOT_STEPS = 13;
|
|
125
|
+
const ERASER_SCRIBBLE_INITIAL_SAMPLE_INTERVAL = 16;
|
|
126
|
+
const ERASER_SCRIBBLE_MIN_SAMPLE_INTERVAL = 8;
|
|
127
|
+
const ERASER_SCRIBBLE_BASE_LIFETIME = 180;
|
|
128
|
+
const ERASER_SCRIBBLE_SPEED_LIFETIME = 180;
|
|
129
|
+
const ERASER_SCRIBBLE_MAX_LIFETIME = 680;
|
|
130
|
+
|
|
131
|
+
function useAnimationClock(): number {
|
|
132
|
+
const [time, setTime] = useState(() => getNowMs());
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (typeof window === "undefined") {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let frame = 0;
|
|
140
|
+
const tick = (nextTime: number) => {
|
|
141
|
+
setTime(nextTime);
|
|
142
|
+
frame = window.requestAnimationFrame(tick);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
frame = window.requestAnimationFrame(tick);
|
|
146
|
+
return () => {
|
|
147
|
+
window.cancelAnimationFrame(frame);
|
|
148
|
+
};
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
return time;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getNowMs(): number {
|
|
155
|
+
return typeof performance === "undefined" ? Date.now() : performance.now();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function syncEraserTrailSampleState(
|
|
159
|
+
state: EraserTrailSampleState,
|
|
160
|
+
points: readonly Vec[],
|
|
161
|
+
zoom: number,
|
|
162
|
+
now: number,
|
|
163
|
+
): void {
|
|
164
|
+
pruneEraserTrailSamples(state, now);
|
|
165
|
+
if (points.length < state.sourceLength) {
|
|
166
|
+
state.samples = [];
|
|
167
|
+
state.sourceLength = 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (points.length === state.sourceLength) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const firstNewIndex = state.sourceLength;
|
|
175
|
+
const newPointCount = points.length - firstNewIndex;
|
|
176
|
+
const previousTime =
|
|
177
|
+
state.samples[state.samples.length - 1]?.time ??
|
|
178
|
+
now - ERASER_SCRIBBLE_INITIAL_SAMPLE_INTERVAL * newPointCount;
|
|
179
|
+
for (let i = firstNewIndex; i < points.length; i++) {
|
|
180
|
+
const point = points[i];
|
|
181
|
+
const progress = (i - firstNewIndex + 1) / newPointCount;
|
|
182
|
+
appendEraserTrailSample(
|
|
183
|
+
state,
|
|
184
|
+
{ x: point.x, y: point.y },
|
|
185
|
+
zoom,
|
|
186
|
+
lerpNumber(previousTime, now, progress),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
state.sourceLength = points.length;
|
|
191
|
+
pruneEraserTrailSamples(state, now);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function appendEraserTrailSample(
|
|
195
|
+
state: EraserTrailSampleState,
|
|
196
|
+
point: TrailRenderPoint,
|
|
197
|
+
zoom: number,
|
|
198
|
+
time: number,
|
|
199
|
+
): void {
|
|
200
|
+
const previous = state.samples[state.samples.length - 1];
|
|
201
|
+
if (!previous) {
|
|
202
|
+
state.samples.push({ ...point, time, velocity: 0 });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const minDistance = ERASER_SCRIBBLE_MIN_POINT_DISTANCE / zoom;
|
|
207
|
+
const distance = getTrailPointDistance(previous, point);
|
|
208
|
+
if (distance < minDistance) {
|
|
209
|
+
state.samples[state.samples.length - 1] = {
|
|
210
|
+
...previous,
|
|
211
|
+
...point,
|
|
212
|
+
time,
|
|
213
|
+
};
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const elapsed = Math.max(
|
|
218
|
+
ERASER_SCRIBBLE_MIN_SAMPLE_INTERVAL,
|
|
219
|
+
time - previous.time,
|
|
220
|
+
);
|
|
221
|
+
const screenVelocity = (distance * zoom) / elapsed;
|
|
222
|
+
state.samples.push({
|
|
223
|
+
...point,
|
|
224
|
+
time,
|
|
225
|
+
velocity: lerpNumber(previous.velocity, screenVelocity, 0.68),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function pruneEraserTrailSamples(
|
|
230
|
+
state: EraserTrailSampleState,
|
|
231
|
+
now: number,
|
|
232
|
+
): void {
|
|
233
|
+
state.samples = getActiveEraserTrailSamples(state.samples, now);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function getRenderableEraserTrailPoints(
|
|
237
|
+
samples: readonly EraserTrailTimedPoint[],
|
|
238
|
+
now: number,
|
|
239
|
+
): TrailRenderPoint[] {
|
|
240
|
+
const activeSamples = getActiveEraserTrailSamples(samples, now);
|
|
241
|
+
const activePoints =
|
|
242
|
+
activeSamples.length > ERASER_SCRIBBLE_MAX_POINTS
|
|
243
|
+
? activeSamples.slice(-ERASER_SCRIBBLE_MAX_POINTS)
|
|
244
|
+
: activeSamples;
|
|
245
|
+
return getStreamlinedTrailPoints(activePoints, ERASER_SCRIBBLE_STREAMLINE);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function getActiveEraserTrailSamples(
|
|
249
|
+
samples: readonly EraserTrailTimedPoint[],
|
|
250
|
+
now: number,
|
|
251
|
+
): EraserTrailTimedPoint[] {
|
|
252
|
+
const head = samples[samples.length - 1];
|
|
253
|
+
if (!head) {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const cutoffTime = now - getEraserTrailSampleLifetime(head);
|
|
258
|
+
if (head.time < cutoffTime) {
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const firstActiveIndex = samples.findIndex(
|
|
263
|
+
(sample) => sample.time >= cutoffTime,
|
|
264
|
+
);
|
|
265
|
+
if (firstActiveIndex === -1) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const activeSamples = samples.slice(firstActiveIndex);
|
|
270
|
+
const previous = samples[firstActiveIndex - 1];
|
|
271
|
+
const first = activeSamples[0];
|
|
272
|
+
if (previous && first.time > cutoffTime) {
|
|
273
|
+
const t = clamp(
|
|
274
|
+
(cutoffTime - previous.time) / (first.time - previous.time),
|
|
275
|
+
0,
|
|
276
|
+
1,
|
|
277
|
+
);
|
|
278
|
+
return [lerpEraserTrailSample(previous, first, t), ...activeSamples];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return activeSamples;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function lerpEraserTrailSample(
|
|
285
|
+
a: EraserTrailTimedPoint,
|
|
286
|
+
b: EraserTrailTimedPoint,
|
|
287
|
+
t: number,
|
|
288
|
+
): EraserTrailTimedPoint {
|
|
289
|
+
return {
|
|
290
|
+
...lerpTrailPoint(a, b, t),
|
|
291
|
+
time: lerpNumber(a.time, b.time, t),
|
|
292
|
+
velocity: lerpNumber(a.velocity, b.velocity, t),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function getEraserTrailSampleLifetime(sample: EraserTrailTimedPoint): number {
|
|
297
|
+
return clamp(
|
|
298
|
+
ERASER_SCRIBBLE_BASE_LIFETIME +
|
|
299
|
+
sample.velocity * ERASER_SCRIBBLE_SPEED_LIFETIME,
|
|
300
|
+
ERASER_SCRIBBLE_BASE_LIFETIME,
|
|
301
|
+
ERASER_SCRIBBLE_MAX_LIFETIME,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function getStreamlinedTrailPoints(
|
|
306
|
+
points: readonly TrailRenderPoint[],
|
|
307
|
+
streamline: number,
|
|
308
|
+
): TrailRenderPoint[] {
|
|
309
|
+
if (points.length <= 1) {
|
|
310
|
+
return [...points];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const t = 0.15 + (1 - streamline) * 0.85;
|
|
314
|
+
const result: TrailRenderPoint[] = [points[0]];
|
|
315
|
+
for (let i = 1; i < points.length; i++) {
|
|
316
|
+
result.push(lerpTrailPoint(result[result.length - 1], points[i], t));
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function getEraserScribblePath(
|
|
322
|
+
points: readonly TrailRenderPoint[],
|
|
323
|
+
size: number,
|
|
324
|
+
): string | null {
|
|
325
|
+
if (points.length === 0 || size <= 0) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (points.length === 1) {
|
|
330
|
+
return getCircleTrailPath(points[0], size / 2);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const samples = getEraserScribbleSamples(points, size);
|
|
334
|
+
const outline = getEraserScribbleOutline(samples);
|
|
335
|
+
return getSmoothClosedTrailPathFromPoints(outline);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function getEraserScribbleSamples(
|
|
339
|
+
points: readonly TrailRenderPoint[],
|
|
340
|
+
size: number,
|
|
341
|
+
): EraserScribbleSample[] {
|
|
342
|
+
const runningLengths = getTrailRunningLengths(points);
|
|
343
|
+
const totalLength = runningLengths[runningLengths.length - 1] ?? 0;
|
|
344
|
+
const taperLength = Math.max(size, totalLength);
|
|
345
|
+
|
|
346
|
+
return points.map((point, index) => {
|
|
347
|
+
const runningLength = runningLengths[index] ?? 0;
|
|
348
|
+
const taper = clamp(runningLength / taperLength, 0, 1);
|
|
349
|
+
return {
|
|
350
|
+
point,
|
|
351
|
+
direction: getTrailPointDirection(points, index),
|
|
352
|
+
radius: (size / 2) * taper,
|
|
353
|
+
runningLength,
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function getEraserScribbleOutline(
|
|
359
|
+
samples: readonly EraserScribbleSample[],
|
|
360
|
+
): TrailRenderPoint[] {
|
|
361
|
+
if (samples.length === 0) {
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (samples.length === 1) {
|
|
366
|
+
return getCircleTrailPoints(
|
|
367
|
+
samples[0].point,
|
|
368
|
+
Math.max(samples[0].radius, 0.01),
|
|
369
|
+
ERASER_SCRIBBLE_DOT_STEPS,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const leftPoints: TrailRenderPoint[] = [];
|
|
374
|
+
const rightPoints: TrailRenderPoint[] = [];
|
|
375
|
+
|
|
376
|
+
for (const sample of samples) {
|
|
377
|
+
const normal = getTrailPointPerpendicular(sample.direction);
|
|
378
|
+
appendDistinctTrailPoint(
|
|
379
|
+
leftPoints,
|
|
380
|
+
subtractTrailPoints(sample.point, multiplyTrailPoint(normal, sample.radius)),
|
|
381
|
+
);
|
|
382
|
+
appendDistinctTrailPoint(
|
|
383
|
+
rightPoints,
|
|
384
|
+
addTrailPoints(sample.point, multiplyTrailPoint(normal, sample.radius)),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const outline: TrailRenderPoint[] = [];
|
|
389
|
+
for (const point of leftPoints) {
|
|
390
|
+
appendDistinctTrailPoint(outline, point);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const lastSample = samples[samples.length - 1];
|
|
394
|
+
const lastNormal = getTrailPointPerpendicular(lastSample.direction);
|
|
395
|
+
const endCap = getRoundEndCapTrailPoints({
|
|
396
|
+
center: lastSample.point,
|
|
397
|
+
direction: lastSample.direction,
|
|
398
|
+
normal: lastNormal,
|
|
399
|
+
radius: lastSample.radius,
|
|
400
|
+
});
|
|
401
|
+
for (const point of endCap) {
|
|
402
|
+
appendDistinctTrailPoint(outline, point);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
for (let i = rightPoints.length - 1; i >= 0; i--) {
|
|
406
|
+
appendDistinctTrailPoint(outline, rightPoints[i]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return outline;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function getTrailRunningLengths(
|
|
413
|
+
points: readonly TrailRenderPoint[],
|
|
414
|
+
): number[] {
|
|
415
|
+
const runningLengths = [0];
|
|
416
|
+
for (let i = 1; i < points.length; i++) {
|
|
417
|
+
runningLengths.push(
|
|
418
|
+
runningLengths[i - 1] + getTrailPointDistance(points[i - 1], points[i]),
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return runningLengths;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function getTrailPointDirection(
|
|
425
|
+
points: readonly TrailRenderPoint[],
|
|
426
|
+
index: number,
|
|
427
|
+
): TrailRenderPoint {
|
|
428
|
+
if (points.length <= 1) {
|
|
429
|
+
return { x: 1, y: 0 };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const previous = points[index - 1] ?? points[index];
|
|
433
|
+
const next = points[index + 1] ?? points[index];
|
|
434
|
+
const direction =
|
|
435
|
+
index === 0
|
|
436
|
+
? subtractTrailPoints(next, points[index])
|
|
437
|
+
: index === points.length - 1
|
|
438
|
+
? subtractTrailPoints(points[index], previous)
|
|
439
|
+
: subtractTrailPoints(next, previous);
|
|
440
|
+
|
|
441
|
+
return normalizeTrailPoint(direction);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function getRoundEndCapTrailPoints({
|
|
445
|
+
center,
|
|
446
|
+
direction,
|
|
447
|
+
normal,
|
|
448
|
+
radius,
|
|
449
|
+
}: {
|
|
450
|
+
center: TrailRenderPoint;
|
|
451
|
+
direction: TrailRenderPoint;
|
|
452
|
+
normal: TrailRenderPoint;
|
|
453
|
+
radius: number;
|
|
454
|
+
}): TrailRenderPoint[] {
|
|
455
|
+
if (radius <= 0) {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const points: TrailRenderPoint[] = [];
|
|
460
|
+
for (let step = 1; step < ERASER_SCRIBBLE_CAP_STEPS; step++) {
|
|
461
|
+
const t = step / ERASER_SCRIBBLE_CAP_STEPS;
|
|
462
|
+
points.push(
|
|
463
|
+
addTrailPoints(
|
|
464
|
+
center,
|
|
465
|
+
addTrailPoints(
|
|
466
|
+
multiplyTrailPoint(direction, Math.sin(Math.PI * t) * radius),
|
|
467
|
+
multiplyTrailPoint(normal, -Math.cos(Math.PI * t) * radius),
|
|
468
|
+
),
|
|
469
|
+
),
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
return points;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function getCircleTrailPath(center: TrailRenderPoint, radius: number): string {
|
|
476
|
+
const points = getCircleTrailPoints(
|
|
477
|
+
center,
|
|
478
|
+
radius,
|
|
479
|
+
ERASER_SCRIBBLE_DOT_STEPS,
|
|
480
|
+
);
|
|
481
|
+
return getSmoothClosedTrailPathFromPoints(points) ?? "";
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function getCircleTrailPoints(
|
|
485
|
+
center: TrailRenderPoint,
|
|
486
|
+
radius: number,
|
|
487
|
+
steps: number,
|
|
488
|
+
): TrailRenderPoint[] {
|
|
489
|
+
const points: TrailRenderPoint[] = [];
|
|
490
|
+
for (let step = 0; step < steps; step++) {
|
|
491
|
+
const angle = (Math.PI * 2 * step) / steps;
|
|
492
|
+
points.push({
|
|
493
|
+
x: center.x + Math.cos(angle) * radius,
|
|
494
|
+
y: center.y + Math.sin(angle) * radius,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
return points;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function getTrailPointDistance(a: TrailRenderPoint, b: TrailRenderPoint): number {
|
|
501
|
+
return Math.sqrt(getTrailPointDistanceSquared(a, b));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function getTrailPointDistanceSquared(
|
|
505
|
+
a: TrailRenderPoint,
|
|
506
|
+
b: TrailRenderPoint,
|
|
507
|
+
): number {
|
|
508
|
+
const dx = a.x - b.x;
|
|
509
|
+
const dy = a.y - b.y;
|
|
510
|
+
return dx * dx + dy * dy;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function getTrailPointPerpendicular(point: TrailRenderPoint): TrailRenderPoint {
|
|
514
|
+
return { x: -point.y, y: point.x };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function normalizeTrailPoint(point: TrailRenderPoint): TrailRenderPoint {
|
|
518
|
+
const length = Math.sqrt(point.x * point.x + point.y * point.y);
|
|
519
|
+
if (length <= 0) {
|
|
520
|
+
return { x: 1, y: 0 };
|
|
521
|
+
}
|
|
522
|
+
return { x: point.x / length, y: point.y / length };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function addTrailPoints(
|
|
526
|
+
a: TrailRenderPoint,
|
|
527
|
+
b: TrailRenderPoint,
|
|
528
|
+
): TrailRenderPoint {
|
|
529
|
+
return { x: a.x + b.x, y: a.y + b.y };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function subtractTrailPoints(
|
|
533
|
+
a: TrailRenderPoint,
|
|
534
|
+
b: TrailRenderPoint,
|
|
535
|
+
): TrailRenderPoint {
|
|
536
|
+
return { x: a.x - b.x, y: a.y - b.y };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function multiplyTrailPoint(
|
|
540
|
+
point: TrailRenderPoint,
|
|
541
|
+
scale: number,
|
|
542
|
+
): TrailRenderPoint {
|
|
543
|
+
return { x: point.x * scale, y: point.y * scale };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function lerpTrailPoint(
|
|
547
|
+
a: TrailRenderPoint,
|
|
548
|
+
b: TrailRenderPoint,
|
|
549
|
+
t: number,
|
|
550
|
+
): TrailRenderPoint {
|
|
551
|
+
return {
|
|
552
|
+
x: lerpNumber(a.x, b.x, t),
|
|
553
|
+
y: lerpNumber(a.y, b.y, t),
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function lerpNumber(a: number, b: number, t: number): number {
|
|
558
|
+
return a + (b - a) * t;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function clamp(value: number, min: number, max: number): number {
|
|
562
|
+
return Math.max(min, Math.min(max, value));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function appendDistinctTrailPoint(
|
|
566
|
+
points: TrailRenderPoint[],
|
|
567
|
+
point: TrailRenderPoint,
|
|
568
|
+
): void {
|
|
569
|
+
const previous = points[points.length - 1];
|
|
570
|
+
if (!previous || getTrailPointDistanceSquared(previous, point) > 0.04) {
|
|
571
|
+
points.push(point);
|
|
572
|
+
} else {
|
|
573
|
+
points[points.length - 1] = point;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function getSmoothClosedTrailPathFromPoints(
|
|
578
|
+
points: readonly TrailRenderPoint[],
|
|
579
|
+
): string | null {
|
|
580
|
+
if (points.length < 2) {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (points.length === 2) {
|
|
585
|
+
return `M ${formatTrailSvgPoint(points[0])} L ${formatTrailSvgPoint(points[1])} Z`;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const commands = [
|
|
589
|
+
`M ${formatTrailSvgPoint(getTrailMidpoint(points[0], points[1]))}`,
|
|
590
|
+
];
|
|
591
|
+
for (let i = 1; i < points.length; i++) {
|
|
592
|
+
const current = points[i];
|
|
593
|
+
const next = points[(i + 1) % points.length];
|
|
594
|
+
commands.push(
|
|
595
|
+
`Q ${formatTrailSvgPoint(current)} ${formatTrailSvgPoint(getTrailMidpoint(current, next))}`,
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
commands.push("Z");
|
|
599
|
+
|
|
600
|
+
return commands.join(" ");
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function getTrailMidpoint(
|
|
604
|
+
a: TrailRenderPoint,
|
|
605
|
+
b: TrailRenderPoint,
|
|
606
|
+
): TrailRenderPoint {
|
|
607
|
+
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function formatTrailSvgPoint(point: TrailRenderPoint): string {
|
|
611
|
+
return `${formatTrailSvgNumber(point.x)},${formatTrailSvgNumber(point.y)}`;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function formatTrailSvgNumber(value: number): string {
|
|
615
|
+
return Number.isInteger(value) ? `${value}` : value.toFixed(3);
|
|
616
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SET_ACTIVE_TOOL_TOOLBAR_ACTION_ID,
|
|
3
|
+
type LandCorePlugin,
|
|
4
|
+
type ToolbarContribution,
|
|
5
|
+
} from "@land/canvas-core";
|
|
6
|
+
import { EraserTool } from "./EraserTool";
|
|
7
|
+
|
|
8
|
+
export function createEraserCorePlugin(): LandCorePlugin {
|
|
9
|
+
return {
|
|
10
|
+
id: "land.eraser",
|
|
11
|
+
toolbarContributions: [createEraserToolToolbarContribution()],
|
|
12
|
+
setup(editor) {
|
|
13
|
+
editor.tools.register(
|
|
14
|
+
"eraser",
|
|
15
|
+
(currentEditor) => new EraserTool(currentEditor),
|
|
16
|
+
{ shortcut: "e" },
|
|
17
|
+
);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createEraserToolToolbarContribution(): ToolbarContribution {
|
|
23
|
+
return {
|
|
24
|
+
id: "eraser.tool-toolbar",
|
|
25
|
+
surface: "tool",
|
|
26
|
+
placement: "chrome",
|
|
27
|
+
order: 50,
|
|
28
|
+
actionIds: [SET_ACTIVE_TOOL_TOOLBAR_ACTION_ID],
|
|
29
|
+
getGroups() {
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
id: "eraser.tools",
|
|
33
|
+
items: [
|
|
34
|
+
{
|
|
35
|
+
kind: "button",
|
|
36
|
+
id: "eraser.tool.eraser",
|
|
37
|
+
actionId: SET_ACTIVE_TOOL_TOOLBAR_ACTION_ID,
|
|
38
|
+
value: "eraser",
|
|
39
|
+
label: "Eraser",
|
|
40
|
+
shortcut: "E",
|
|
41
|
+
icon: "eraser",
|
|
42
|
+
dataUi: "eraser-tool-button",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineLandExtension,
|
|
3
|
+
type LandFeatureBundle,
|
|
4
|
+
} from "@land/canvas-plugin-api";
|
|
5
|
+
import { createEraserCanvasPlugin } from "./canvas";
|
|
6
|
+
import { createEraserCorePlugin } from "./core";
|
|
7
|
+
|
|
8
|
+
export function createEraserFeatureBundle(): LandFeatureBundle {
|
|
9
|
+
return {
|
|
10
|
+
id: "land.eraser.bundle",
|
|
11
|
+
corePlugins: [createEraserCorePlugin()],
|
|
12
|
+
canvasPlugins: [createEraserCanvasPlugin()],
|
|
13
|
+
shapeTypes: [],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Eraser = defineLandExtension({
|
|
18
|
+
name: "eraser",
|
|
19
|
+
createFeature: createEraserFeatureBundle,
|
|
20
|
+
});
|