@reekon-tools/boldr-cad 0.1.0 → 1.0.0
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 +81 -9
- package/dist/app/edit.d.ts +12 -1
- package/dist/app/edit.js +96 -11
- package/dist/app/edit.js.map +1 -1
- package/dist/app/state.d.ts +31 -4
- package/dist/app/state.js +31 -2
- package/dist/app/state.js.map +1 -1
- package/dist/data/memory.d.ts +5 -0
- package/dist/data/memory.js +2 -2
- package/dist/data/memory.js.map +1 -1
- package/dist/data/provider.d.ts +19 -7
- package/dist/data/sync.d.ts +7 -2
- package/dist/data/sync.js +115 -64
- package/dist/data/sync.js.map +1 -1
- package/dist/editor/actions.d.ts +1 -2
- package/dist/editor/actions.js +391 -113
- package/dist/editor/actions.js.map +1 -1
- package/dist/editor/confirm.d.ts +23 -0
- package/dist/editor/confirm.js +35 -0
- package/dist/editor/confirm.js.map +1 -0
- package/dist/editor/elevation.d.ts +191 -90
- package/dist/editor/elevation.js +812 -616
- package/dist/editor/elevation.js.map +1 -1
- package/dist/editor/face.d.ts +73 -0
- package/dist/editor/face.js +269 -0
- package/dist/editor/face.js.map +1 -0
- package/dist/editor/files.d.ts +6 -0
- package/dist/editor/files.js +50 -4
- package/dist/editor/files.js.map +1 -1
- package/dist/editor/gestures.d.ts +18 -3
- package/dist/editor/gestures.js +145 -30
- package/dist/editor/gestures.js.map +1 -1
- package/dist/editor/keypad.d.ts +19 -4
- package/dist/editor/keypad.js +39 -9
- package/dist/editor/keypad.js.map +1 -1
- package/dist/editor/keys.js +36 -4
- package/dist/editor/keys.js.map +1 -1
- package/dist/editor/laser.d.ts +20 -4
- package/dist/editor/laser.js +104 -21
- package/dist/editor/laser.js.map +1 -1
- package/dist/editor/linkActions.d.ts +49 -0
- package/dist/editor/linkActions.js +210 -0
- package/dist/editor/linkActions.js.map +1 -0
- package/dist/editor/linkFollow.d.ts +2 -0
- package/dist/editor/linkFollow.js +53 -0
- package/dist/editor/linkFollow.js.map +1 -0
- package/dist/editor/linkWrite.d.ts +13 -0
- package/dist/editor/linkWrite.js +84 -0
- package/dist/editor/linkWrite.js.map +1 -0
- package/dist/editor/links.d.ts +84 -0
- package/dist/editor/links.js +130 -0
- package/dist/editor/links.js.map +1 -0
- package/dist/editor/markup.d.ts +29 -3
- package/dist/editor/markup.js +95 -10
- package/dist/editor/markup.js.map +1 -1
- package/dist/editor/mode.d.ts +40 -2
- package/dist/editor/mode.js +71 -4
- package/dist/editor/mode.js.map +1 -1
- package/dist/editor/ports.d.ts +36 -6
- package/dist/editor/ports.js +10 -1
- package/dist/editor/ports.js.map +1 -1
- package/dist/editor/project.d.ts +38 -6
- package/dist/editor/project.js +167 -27
- package/dist/editor/project.js.map +1 -1
- package/dist/editor/selection.d.ts +10 -0
- package/dist/editor/selection.js +62 -8
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/status.js +3 -0
- package/dist/editor/status.js.map +1 -1
- package/dist/editor/tooltips.d.ts +5 -0
- package/dist/editor/tooltips.js +48 -0
- package/dist/editor/tooltips.js.map +1 -0
- package/dist/editor/view3d.d.ts +17 -0
- package/dist/editor/view3d.js +135 -0
- package/dist/editor/view3d.js.map +1 -0
- package/dist/editor/view3dState.d.ts +39 -0
- package/dist/editor/view3dState.js +44 -0
- package/dist/editor/view3dState.js.map +1 -0
- package/dist/export/dxf.js +1 -1
- package/dist/export/dxf.js.map +1 -1
- package/dist/export/pdf.js +17 -12
- package/dist/export/pdf.js.map +1 -1
- package/dist/export/plan.d.ts +23 -6
- package/dist/export/plan.js +127 -44
- package/dist/export/plan.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/model/constraints.js +15 -4
- package/dist/model/constraints.js.map +1 -1
- package/dist/model/demo.js +3 -3
- package/dist/model/demo.js.map +1 -1
- package/dist/model/elevation.d.ts +6 -9
- package/dist/model/elevation.js +5 -13
- package/dist/model/elevation.js.map +1 -1
- package/dist/model/faces.d.ts +58 -0
- package/dist/model/faces.js +197 -0
- package/dist/model/faces.js.map +1 -0
- package/dist/model/facesolve.d.ts +72 -0
- package/dist/model/facesolve.js +369 -0
- package/dist/model/facesolve.js.map +1 -0
- package/dist/model/history.d.ts +3 -2
- package/dist/model/history.js +17 -11
- package/dist/model/history.js.map +1 -1
- package/dist/model/images.d.ts +18 -0
- package/dist/model/images.js +26 -0
- package/dist/model/images.js.map +1 -0
- package/dist/model/links.d.ts +46 -0
- package/dist/model/links.js +73 -0
- package/dist/model/links.js.map +1 -0
- package/dist/model/markup.d.ts +46 -0
- package/dist/model/markup.js +71 -0
- package/dist/model/markup.js.map +1 -0
- package/dist/model/measurement.d.ts +11 -0
- package/dist/model/measurement.js +24 -0
- package/dist/model/measurement.js.map +1 -0
- package/dist/model/openings.d.ts +4 -0
- package/dist/model/openings.js +4 -0
- package/dist/model/openings.js.map +1 -1
- package/dist/model/planGeom.d.ts +72 -2
- package/dist/model/planGeom.js +184 -10
- package/dist/model/planGeom.js.map +1 -1
- package/dist/model/project.js +3 -2
- package/dist/model/project.js.map +1 -1
- package/dist/model/projectfile.js +2 -1
- package/dist/model/projectfile.js.map +1 -1
- package/dist/model/rooms.js +2 -2
- package/dist/model/rooms.js.map +1 -1
- package/dist/model/settings.d.ts +6 -0
- package/dist/model/settings.js +2 -2
- package/dist/model/settings.js.map +1 -1
- package/dist/model/sketch.d.ts +14 -0
- package/dist/model/sketch.js +143 -24
- package/dist/model/sketch.js.map +1 -1
- package/dist/model/smooth.d.ts +6 -0
- package/dist/model/smooth.js +52 -0
- package/dist/model/smooth.js.map +1 -0
- package/dist/model/store.d.ts +12 -0
- package/dist/model/store.js +35 -3
- package/dist/model/store.js.map +1 -1
- package/dist/model/types.d.ts +173 -8
- package/dist/model/wallfaces.d.ts +32 -0
- package/dist/model/wallfaces.js +133 -20
- package/dist/model/wallfaces.js.map +1 -1
- package/dist/model/wallshape.d.ts +15 -0
- package/dist/model/wallshape.js +89 -0
- package/dist/model/wallshape.js.map +1 -0
- package/dist/react/BottomBar.d.ts +9 -5
- package/dist/react/BottomBar.js +213 -24
- package/dist/react/BottomBar.js.map +1 -1
- package/dist/react/Buttons.d.ts +13 -2
- package/dist/react/Buttons.js +37 -5
- package/dist/react/Buttons.js.map +1 -1
- package/dist/react/CadEditor.d.ts +46 -3
- package/dist/react/CadEditor.js +77 -20
- package/dist/react/CadEditor.js.map +1 -1
- package/dist/react/ConfirmSheet.d.ts +4 -0
- package/dist/react/ConfirmSheet.js +31 -0
- package/dist/react/ConfirmSheet.js.map +1 -0
- package/dist/react/ConflictSheet.d.ts +2 -1
- package/dist/react/ConflictSheet.js +16 -8
- package/dist/react/ConflictSheet.js.map +1 -1
- package/dist/react/Drawer.d.ts +8 -2
- package/dist/react/Drawer.js +167 -38
- package/dist/react/Drawer.js.map +1 -1
- package/dist/react/Duck.d.ts +13 -0
- package/dist/react/Duck.js +45 -0
- package/dist/react/Duck.js.map +1 -0
- package/dist/react/Empty3D.d.ts +2 -0
- package/dist/react/Empty3D.js +39 -0
- package/dist/react/Empty3D.js.map +1 -0
- package/dist/react/FitScroll.d.ts +6 -0
- package/dist/react/FitScroll.js +14 -0
- package/dist/react/FitScroll.js.map +1 -0
- package/dist/react/Icon.native.d.ts +6 -0
- package/dist/react/Icon.native.js +4 -0
- package/dist/react/Icon.native.js.map +1 -1
- package/dist/react/Icon.web.d.ts +6 -0
- package/dist/react/Icon.web.js +4 -0
- package/dist/react/Icon.web.js.map +1 -1
- package/dist/react/KeypadSheet.d.ts +9 -2
- package/dist/react/KeypadSheet.js +174 -42
- package/dist/react/KeypadSheet.js.map +1 -1
- package/dist/react/LaserTray.js +8 -5
- package/dist/react/LaserTray.js.map +1 -1
- package/dist/react/MarkupPanel.d.ts +8 -0
- package/dist/react/MarkupPanel.js +67 -0
- package/dist/react/MarkupPanel.js.map +1 -0
- package/dist/react/PageThumb.native.d.ts +7 -2
- package/dist/react/PageThumb.native.js +19 -7
- package/dist/react/PageThumb.native.js.map +1 -1
- package/dist/react/PageThumb.web.d.ts +3 -2
- package/dist/react/PageThumb.web.js +4 -4
- package/dist/react/PageThumb.web.js.map +1 -1
- package/dist/react/PlanCanvas.native.d.ts +1 -1
- package/dist/react/PlanCanvas.native.js +134 -36
- package/dist/react/PlanCanvas.native.js.map +1 -1
- package/dist/react/PlanCanvas.web.js +16 -5
- package/dist/react/PlanCanvas.web.js.map +1 -1
- package/dist/react/Popovers.d.ts +12 -6
- package/dist/react/Popovers.js +53 -26
- package/dist/react/Popovers.js.map +1 -1
- package/dist/react/Rail.d.ts +24 -0
- package/dist/react/Rail.js +324 -34
- package/dist/react/Rail.js.map +1 -1
- package/dist/react/ShapeMenu.d.ts +7 -0
- package/dist/react/ShapeMenu.js +58 -0
- package/dist/react/ShapeMenu.js.map +1 -0
- package/dist/react/Sheet.d.ts +14 -4
- package/dist/react/Sheet.js +54 -9
- package/dist/react/Sheet.js.map +1 -1
- package/dist/react/Slider.d.ts +18 -0
- package/dist/react/Slider.js +27 -0
- package/dist/react/Slider.js.map +1 -0
- package/dist/react/Toast.js +14 -6
- package/dist/react/Toast.js.map +1 -1
- package/dist/react/ToolTip.d.ts +7 -0
- package/dist/react/ToolTip.js +43 -0
- package/dist/react/ToolTip.js.map +1 -0
- package/dist/react/TopBar.d.ts +6 -3
- package/dist/react/TopBar.js +48 -17
- package/dist/react/TopBar.js.map +1 -1
- package/dist/react/Tween.d.ts +7 -0
- package/dist/react/Tween.js +26 -0
- package/dist/react/Tween.js.map +1 -0
- package/dist/react/Tween.native.d.ts +6 -0
- package/dist/react/Tween.native.js +43 -0
- package/dist/react/Tween.native.js.map +1 -0
- package/dist/react/View3D.native.d.ts +4 -0
- package/dist/react/View3D.native.js +103 -0
- package/dist/react/View3D.native.js.map +1 -0
- package/dist/react/View3D.web.d.ts +4 -0
- package/dist/react/View3D.web.js +151 -0
- package/dist/react/View3D.web.js.map +1 -0
- package/dist/react/View3DHost.d.ts +143 -0
- package/dist/react/View3DHost.js +377 -0
- package/dist/react/View3DHost.js.map +1 -0
- package/dist/react/corners.d.ts +7 -0
- package/dist/react/corners.js +51 -0
- package/dist/react/corners.js.map +1 -0
- package/dist/react/easing.d.ts +15 -0
- package/dist/react/easing.js +10 -0
- package/dist/react/easing.js.map +1 -0
- package/dist/react/host.d.ts +7 -2
- package/dist/react/host.js +8 -3
- package/dist/react/host.js.map +1 -1
- package/dist/react/hostKeypad.d.ts +56 -0
- package/dist/react/hostKeypad.js +5 -0
- package/dist/react/hostKeypad.js.map +1 -0
- package/dist/react/motion.d.ts +77 -0
- package/dist/react/motion.js +173 -0
- package/dist/react/motion.js.map +1 -0
- package/dist/react/ports.js +90 -8
- package/dist/react/ports.js.map +1 -1
- package/dist/react/saving.d.ts +15 -0
- package/dist/react/saving.js +76 -0
- package/dist/react/saving.js.map +1 -0
- package/dist/react/store.d.ts +38 -1
- package/dist/react/store.js +31 -1
- package/dist/react/store.js.map +1 -1
- package/dist/react/theme.d.ts +10 -0
- package/dist/react/theme.js +8 -0
- package/dist/react/theme.js.map +1 -1
- package/dist/react/topRow.d.ts +22 -0
- package/dist/react/topRow.js +61 -0
- package/dist/react/topRow.js.map +1 -0
- package/dist/react/touch.d.ts +6 -0
- package/dist/react/touch.js +9 -0
- package/dist/react/touch.js.map +1 -0
- package/dist/render/ctx.d.ts +8 -0
- package/dist/render/draw.d.ts +33 -1
- package/dist/render/draw.js +593 -215
- package/dist/render/draw.js.map +1 -1
- package/dist/render/elevation.d.ts +65 -43
- package/dist/render/elevation.js +414 -140
- package/dist/render/elevation.js.map +1 -1
- package/dist/render/figmaIcons.d.ts +12 -0
- package/dist/render/figmaIcons.js +21 -0
- package/dist/render/figmaIcons.js.map +1 -0
- package/dist/render/hit.d.ts +22 -2
- package/dist/render/hit.js +168 -25
- package/dist/render/hit.js.map +1 -1
- package/dist/render/icons.js +69 -10
- package/dist/render/icons.js.map +1 -1
- package/dist/render/linkTile.d.ts +15 -0
- package/dist/render/linkTile.js +369 -0
- package/dist/render/linkTile.js.map +1 -0
- package/dist/render/markup.d.ts +11 -0
- package/dist/render/markup.js +112 -0
- package/dist/render/markup.js.map +1 -0
- package/dist/render/navigate.d.ts +88 -0
- package/dist/render/navigate.js +100 -0
- package/dist/render/navigate.js.map +1 -0
- package/dist/render/snap.d.ts +20 -1
- package/dist/render/snap.js +118 -25
- package/dist/render/snap.js.map +1 -1
- package/dist/render/squircle.d.ts +8 -0
- package/dist/render/squircle.js +71 -0
- package/dist/render/squircle.js.map +1 -0
- package/dist/render/view.d.ts +115 -2
- package/dist/render/view.js +244 -19
- package/dist/render/view.js.map +1 -1
- package/dist/skia/SkiaDrawCtx.d.ts +30 -2
- package/dist/skia/SkiaDrawCtx.js +198 -43
- package/dist/skia/SkiaDrawCtx.js.map +1 -1
- package/dist/skia/host.d.ts +18 -2
- package/dist/skia/host.js +46 -1
- package/dist/skia/host.js.map +1 -1
- package/dist/solver/equations.js +37 -2
- package/dist/solver/equations.js.map +1 -1
- package/dist/solver/gauss-newton.d.ts +17 -0
- package/dist/solver/gauss-newton.js +144 -36
- package/dist/solver/gauss-newton.js.map +1 -1
- package/dist/solver/linalg.d.ts +45 -2
- package/dist/solver/linalg.js +164 -10
- package/dist/solver/linalg.js.map +1 -1
- package/dist/solver/types.d.ts +5 -0
- package/dist/tools/annotate.d.ts +57 -1
- package/dist/tools/annotate.js +64 -43
- package/dist/tools/annotate.js.map +1 -1
- package/dist/tools/chamfer.d.ts +12 -0
- package/dist/tools/chamfer.js +114 -0
- package/dist/tools/chamfer.js.map +1 -0
- package/dist/tools/corner.d.ts +38 -0
- package/dist/tools/corner.js +99 -0
- package/dist/tools/corner.js.map +1 -0
- package/dist/tools/dim.d.ts +86 -9
- package/dist/tools/dim.js +581 -96
- package/dist/tools/dim.js.map +1 -1
- package/dist/tools/elevation.js +1 -1
- package/dist/tools/elevation.js.map +1 -1
- package/dist/tools/fillet.d.ts +9 -0
- package/dist/tools/fillet.js +86 -0
- package/dist/tools/fillet.js.map +1 -0
- package/dist/tools/freehand.d.ts +3 -2
- package/dist/tools/freehand.js +19 -12
- package/dist/tools/freehand.js.map +1 -1
- package/dist/tools/index.js +14 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/line.d.ts +8 -3
- package/dist/tools/line.js +66 -30
- package/dist/tools/line.js.map +1 -1
- package/dist/tools/modify.d.ts +13 -0
- package/dist/tools/modify.js +105 -25
- package/dist/tools/modify.js.map +1 -1
- package/dist/tools/openings.js +22 -15
- package/dist/tools/openings.js.map +1 -1
- package/dist/tools/registry.d.ts +8 -0
- package/dist/tools/registry.js +42 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/select.d.ts +77 -3
- package/dist/tools/select.js +560 -74
- package/dist/tools/select.js.map +1 -1
- package/dist/tools/shapes.d.ts +14 -0
- package/dist/tools/shapes.js +154 -75
- package/dist/tools/shapes.js.map +1 -1
- package/dist/units/format.d.ts +10 -2
- package/dist/units/format.js +18 -3
- package/dist/units/format.js.map +1 -1
- package/dist/view3d/build.d.ts +35 -0
- package/dist/view3d/build.js +630 -0
- package/dist/view3d/build.js.map +1 -0
- package/dist/view3d/camera.d.ts +60 -0
- package/dist/view3d/camera.js +84 -0
- package/dist/view3d/camera.js.map +1 -0
- package/dist/view3d/fatLines.d.ts +11 -0
- package/dist/view3d/fatLines.js +83 -0
- package/dist/view3d/fatLines.js.map +1 -0
- package/dist/view3d/lines.d.ts +17 -0
- package/dist/view3d/lines.js +271 -0
- package/dist/view3d/lines.js.map +1 -0
- package/dist/view3d/mesh.d.ts +51 -0
- package/dist/view3d/mesh.js +101 -0
- package/dist/view3d/mesh.js.map +1 -0
- package/dist/view3d/poly.d.ts +16 -0
- package/dist/view3d/poly.js +109 -0
- package/dist/view3d/poly.js.map +1 -0
- package/dist/view3d/viewer.d.ts +86 -0
- package/dist/view3d/viewer.js +350 -0
- package/dist/view3d/viewer.js.map +1 -0
- package/package.json +15 -2
- package/src/app/edit.ts +83 -12
- package/src/app/main.ts +11 -5
- package/src/app/state.ts +38 -5
- package/src/data/memory.ts +2 -2
- package/src/data/provider.ts +15 -7
- package/src/data/sync.ts +87 -50
- package/src/editor/actions.ts +292 -83
- package/src/editor/confirm.ts +58 -0
- package/src/editor/elevation.ts +698 -478
- package/src/editor/face.ts +227 -0
- package/src/editor/files.ts +40 -4
- package/src/editor/gestures.ts +147 -31
- package/src/editor/keypad.ts +43 -11
- package/src/editor/keys.ts +25 -5
- package/src/editor/laser.ts +98 -20
- package/src/editor/linkActions.ts +194 -0
- package/src/editor/linkFollow.ts +43 -0
- package/src/editor/linkWrite.ts +77 -0
- package/src/editor/links.ts +181 -0
- package/src/editor/markup.ts +92 -8
- package/src/editor/mode.ts +83 -7
- package/src/editor/ports.ts +37 -6
- package/src/editor/project.ts +155 -26
- package/src/editor/selection.ts +54 -8
- package/src/editor/status.ts +2 -0
- package/src/editor/tooltips.ts +47 -0
- package/src/editor/view3d.ts +119 -0
- package/src/editor/view3dState.ts +61 -0
- package/src/export/dxf.ts +1 -1
- package/src/export/pdf.ts +15 -7
- package/src/export/plan.ts +119 -35
- package/src/index.ts +5 -0
- package/src/input/keyboard.ts +2 -4
- package/src/model/constraints.ts +13 -4
- package/src/model/demo.ts +3 -3
- package/src/model/elevation.ts +6 -16
- package/src/model/faces.ts +153 -0
- package/src/model/facesolve.ts +322 -0
- package/src/model/history.ts +19 -11
- package/src/model/images.ts +35 -0
- package/src/model/links.ts +77 -0
- package/src/model/markup.ts +80 -0
- package/src/model/measurement.ts +26 -0
- package/src/model/openings.ts +6 -0
- package/src/model/planGeom.ts +174 -8
- package/src/model/project.ts +3 -2
- package/src/model/projectfile.ts +2 -1
- package/src/model/rooms.ts +1 -1
- package/src/model/settings.ts +8 -2
- package/src/model/sketch.ts +101 -14
- package/src/model/smooth.ts +51 -0
- package/src/model/store.ts +35 -4
- package/src/model/types.ts +170 -8
- package/src/model/wallfaces.ts +126 -17
- package/src/model/wallshape.ts +97 -0
- package/src/react/BottomBar.tsx +263 -46
- package/src/react/Buttons.tsx +50 -9
- package/src/react/CadEditor.tsx +133 -30
- package/src/react/ConfirmSheet.tsx +38 -0
- package/src/react/ConflictSheet.tsx +19 -12
- package/src/react/Drawer.tsx +213 -110
- package/src/react/Duck.tsx +50 -0
- package/src/react/Empty3D.tsx +32 -0
- package/src/react/FitScroll.tsx +19 -0
- package/src/react/Icon.native.tsx +5 -0
- package/src/react/Icon.web.tsx +5 -0
- package/src/react/KeypadSheet.tsx +253 -60
- package/src/react/LaserTray.tsx +12 -8
- package/src/react/MarkupPanel.tsx +150 -0
- package/src/react/PageThumb.native.tsx +18 -7
- package/src/react/PageThumb.web.tsx +4 -4
- package/src/react/PlanCanvas.native.tsx +151 -34
- package/src/react/PlanCanvas.web.tsx +15 -5
- package/src/react/Popovers.tsx +116 -51
- package/src/react/Rail.tsx +360 -27
- package/src/react/ShapeMenu.tsx +66 -0
- package/src/react/Sheet.tsx +56 -11
- package/src/react/Slider.tsx +48 -0
- package/src/react/Toast.tsx +16 -9
- package/src/react/ToolTip.tsx +49 -0
- package/src/react/TopBar.tsx +97 -39
- package/src/react/Tween.native.tsx +36 -0
- package/src/react/Tween.tsx +24 -0
- package/src/react/View3D.native.tsx +131 -0
- package/src/react/View3D.web.tsx +131 -0
- package/src/react/View3DHost.ts +367 -0
- package/src/react/corners.ts +52 -0
- package/src/react/easing.ts +14 -0
- package/src/react/host.ts +8 -3
- package/src/react/hostKeypad.ts +66 -0
- package/src/react/motion.tsx +239 -0
- package/src/react/ports.ts +78 -9
- package/src/react/saving.ts +80 -0
- package/src/react/store.ts +35 -3
- package/src/react/theme.ts +8 -0
- package/src/react/topRow.ts +71 -0
- package/src/react/touch.ts +10 -0
- package/src/render/ctx.ts +8 -0
- package/src/render/draw.ts +437 -175
- package/src/render/elevation.ts +349 -143
- package/src/render/figmaIcons.ts +22 -0
- package/src/render/hit.ts +140 -21
- package/src/render/icons.ts +69 -10
- package/src/render/linkTile.ts +297 -0
- package/src/render/markup.ts +91 -0
- package/src/render/navigate.ts +114 -0
- package/src/render/snap.ts +105 -22
- package/src/render/squircle.ts +73 -0
- package/src/render/view.ts +255 -19
- package/src/skia/SkiaDrawCtx.ts +163 -41
- package/src/skia/host.ts +46 -1
- package/src/solver/equations.ts +37 -2
- package/src/solver/gauss-newton.ts +125 -28
- package/src/solver/linalg.ts +141 -5
- package/src/solver/types.ts +5 -0
- package/src/styles.css +91 -42
- package/src/tools/annotate.ts +58 -29
- package/src/tools/chamfer.ts +103 -0
- package/src/tools/corner.ts +99 -0
- package/src/tools/dim.ts +524 -92
- package/src/tools/elevation.ts +1 -1
- package/src/tools/fillet.ts +82 -0
- package/src/tools/freehand.ts +20 -16
- package/src/tools/index.ts +17 -4
- package/src/tools/line.ts +59 -23
- package/src/tools/modify.ts +86 -24
- package/src/tools/openings.ts +8 -13
- package/src/tools/registry.ts +27 -2
- package/src/tools/select.ts +424 -75
- package/src/tools/shapes.ts +119 -58
- package/src/ui/ctx.ts +2 -1
- package/src/ui/domPorts.ts +23 -4
- package/src/ui/drawer.ts +6 -26
- package/src/ui/elevation.ts +10 -101
- package/src/ui/keypad.ts +8 -14
- package/src/ui/laser.ts +2 -2
- package/src/ui/menu.ts +63 -17
- package/src/ui/noteinput.ts +3 -3
- package/src/ui/opacity.ts +108 -0
- package/src/ui/palette.ts +67 -12
- package/src/ui/rail.ts +21 -7
- package/src/ui/topbar.ts +4 -14
- package/src/units/format.ts +18 -3
- package/src/view3d/build.ts +607 -0
- package/src/view3d/camera.ts +113 -0
- package/src/view3d/fatLines.ts +83 -0
- package/src/view3d/lines.ts +258 -0
- package/src/view3d/mesh.ts +111 -0
- package/src/view3d/poly.ts +104 -0
- package/src/view3d/viewer.ts +348 -0
- package/dist/react/ElevCanvas.native.d.ts +0 -3
- package/dist/react/ElevCanvas.native.js +0 -58
- package/dist/react/ElevCanvas.native.js.map +0 -1
- package/dist/react/ElevCanvas.web.d.ts +0 -4
- package/dist/react/ElevCanvas.web.js +0 -44
- package/dist/react/ElevCanvas.web.js.map +0 -1
- package/dist/react/ElevationSheet.d.ts +0 -2
- package/dist/react/ElevationSheet.js +0 -38
- package/dist/react/ElevationSheet.js.map +0 -1
- package/dist/render/elevationView.d.ts +0 -3
- package/dist/render/elevationView.js +0 -100
- package/dist/render/elevationView.js.map +0 -1
- package/src/react/ElevCanvas.native.tsx +0 -68
- package/src/react/ElevCanvas.web.tsx +0 -42
- package/src/react/ElevationSheet.tsx +0 -88
- package/src/render/elevationView.ts +0 -73
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { LINK_PIN_PX, LINK_TILE_PX } from '../model/links';
|
|
2
|
+
import { noteLook } from '../model/markup';
|
|
3
|
+
import type { LinkInfo, Note } from '../model/types';
|
|
4
|
+
import type { DrawCtx } from './ctx';
|
|
5
|
+
import { strokeMarkup } from './markup';
|
|
6
|
+
import { smoothRectPath } from './squircle';
|
|
7
|
+
import { FONT, TH } from './theme';
|
|
8
|
+
import { cvs, leaderPath, linkBox, linkThumb, type ImageLike, type LinkBox, type Xform } from './view';
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
A link's tile on the plan: a square card for one of the job's files (a photo's picture edge to edge; anything else's
|
|
12
|
+
picture, or an icon for its kind, above its name) or measurements (its value and name), and its leader — a line in the markup's look from the card's edge to
|
|
13
|
+
what it points at. The card is laid out once at LINK_TILE_PX and scaled to its size on the drawing, so its shapes and
|
|
14
|
+
text are the same at every step of a pinch (kept, on a device). Small on the screen it shows less: its picture or
|
|
15
|
+
icon alone, then a pin of a fixed size. Where it is on the screen (and its handles) is view.ts's, and picking it
|
|
16
|
+
hit.ts's.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface LinkDrawOpts {
|
|
20
|
+
selected: boolean;
|
|
21
|
+
alpha: number;
|
|
22
|
+
/** what the host says it is now (none yet: what it was when linked) */
|
|
23
|
+
info?: LinkInfo;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Draw tile `n` and its leader. */
|
|
27
|
+
export function drawLink(c: DrawCtx, T: Xform, n: Note, o: LinkDrawOpts): void {
|
|
28
|
+
const b = linkBox(T, n), lp = leaderPath(T, n, b);
|
|
29
|
+
c.save();
|
|
30
|
+
c.globalAlpha = o.alpha;
|
|
31
|
+
// (under the card, which covers where it starts)
|
|
32
|
+
if (lp) strokeMarkup(c, lp, noteLook(n), o.selected ? 'rgba(255,173,0,.45)' : undefined);
|
|
33
|
+
if (b.lod === 'pin') drawPin(c, b, n, o);
|
|
34
|
+
else drawCard(c, b, n, o);
|
|
35
|
+
c.restore();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A line `px` wide on the screen, in the layout of a card at scale `k` — in steps, four to each doubling of the zoom
|
|
40
|
+
* (within a fifth of `px`): a pinch draws with the same few paints, on a device, not a new one at every step.
|
|
41
|
+
*/
|
|
42
|
+
const screenW = (px: number, k: number): number => Math.pow(2, Math.round(Math.log2(px / k) * 4) / 4);
|
|
43
|
+
|
|
44
|
+
/** The layout's side, and its rounding; how wide a selected tile's ring is outside its edge (pt). */
|
|
45
|
+
const L = LINK_TILE_PX, R = 12, SELECT_W = 3;
|
|
46
|
+
|
|
47
|
+
/** A rounded rectangle's path, one shape for each size (as draw.ts's rr: kept by an engine that can). */
|
|
48
|
+
function rr(c: DrawCtx, x: number, y: number, w: number, h: number, r: number): void {
|
|
49
|
+
if (c.shape) c.shape('rr ' + w + ' ' + h + ' ' + r, x, y, (cc, X, Y) => smoothRectPath(cc, X, Y, w, h, r));
|
|
50
|
+
else smoothRectPath(c, x, y, w, h, r);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The kind of thing a tile is for, as its icon shows it. */
|
|
54
|
+
type Glyph = 'photo' | 'pdf' | 'document' | 'note' | 'calculator' | 'label' | 'drawing' | 'tape' | 'removed';
|
|
55
|
+
const glyphOf = (n: Note, info?: LinkInfo): Glyph => {
|
|
56
|
+
if (info?.exists === false) return 'removed';
|
|
57
|
+
if (n.link!.kind === 'measurement') return 'tape';
|
|
58
|
+
const t = info?.fileType ?? n.link!.fileType;
|
|
59
|
+
return t === 'photo' || t === 'pdf' || t === 'note' || t === 'calculator' || t === 'label' || t === 'drawing' ? t : 'document';
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function drawCard(c: DrawCtx, b: LinkBox, n: Note, o: LinkDrawOpts): void {
|
|
63
|
+
const k = b.k, t = n.link!, info = o.info, gone = info?.exists === false, glyph = glyphOf(n, info);
|
|
64
|
+
c.save();
|
|
65
|
+
c.translate(b.x, b.y);
|
|
66
|
+
c.scale(k, k);
|
|
67
|
+
rr(c, 0, 0, L, L, R);
|
|
68
|
+
// selected: a yellow ring round the card's edge, the same 3 pt at any size — stroked twice as wide on the card's own
|
|
69
|
+
// outline before it's filled, so the card covers the inside half (and no new shape is made as it zooms)
|
|
70
|
+
if (o.selected) { c.lineWidth = screenW(2 * SELECT_W, k); c.strokeStyle = TH.yellow; c.stroke(); }
|
|
71
|
+
c.fillStyle = gone ? TH.chip : TH.badge; c.fill();
|
|
72
|
+
if (!o.selected) { c.lineWidth = screenW(1, k); c.strokeStyle = TH.badgeline; c.stroke(); }
|
|
73
|
+
// (its picture asked for only near the screen: a drawing can link more than are kept)
|
|
74
|
+
const near = b.x < cvs.W * 2 && b.y < cvs.H * 2 && b.x + b.s > -cvs.W && b.y + b.s > -cvs.H;
|
|
75
|
+
const pic = near && !gone && t.kind === 'file' && info?.thumbUrl ? linkThumb(info.thumbUrl) : null, shown = pic && pic.complete && pic.naturalWidth > 0;
|
|
76
|
+
// a photo is its picture, edge to edge — it says what it is (until it's loaded: its icon and name, as any file)
|
|
77
|
+
const photo = shown && glyph === 'photo';
|
|
78
|
+
if (photo) cover(c, pic, 0, 0, L, L, R);
|
|
79
|
+
else if (b.lod === 'compact') {
|
|
80
|
+
// the picture or the icon alone
|
|
81
|
+
if (shown) cover(c, pic, 6, 6, L - 12, L - 12, R - 4);
|
|
82
|
+
else drawGlyph(c, glyph, L / 2, L / 2, 46, TH.ink2);
|
|
83
|
+
} else if (t.kind === 'measurement' && !gone) {
|
|
84
|
+
// its value, and its name
|
|
85
|
+
drawGlyph(c, 'tape', 18, 17, 18, TH.ink2);
|
|
86
|
+
const value = info?.text ?? t.text ?? '—';
|
|
87
|
+
c.font = '700 18px ' + FONT; c.fillStyle = TH.blue; c.textAlign = 'center'; c.textBaseline = 'middle';
|
|
88
|
+
c.fillText(fit(c, value, L - 12), L / 2, 46);
|
|
89
|
+
drawName(c, k, info?.name ?? t.name ?? 'Measurement', 75, L - 14, TH.ink2, false);
|
|
90
|
+
} else if (gone) {
|
|
91
|
+
// the removed mark, and what it was
|
|
92
|
+
drawGlyph(c, glyph, L / 2, 30, 28, TH.ink2);
|
|
93
|
+
c.font = '700 11px ' + FONT; c.fillStyle = TH.ink2; c.textAlign = 'center'; c.textBaseline = 'middle';
|
|
94
|
+
c.fillText('Removed', L / 2, 58);
|
|
95
|
+
drawName(c, k, t.name ?? (t.kind === 'file' ? 'File' : 'Measurement'), 79, L - 12, TH.ink2, false);
|
|
96
|
+
} else {
|
|
97
|
+
// its picture or icon above, its name below
|
|
98
|
+
if (shown) cover(c, pic, 6, 6, L - 12, 62, R - 4);
|
|
99
|
+
else {
|
|
100
|
+
const a = c.globalAlpha;
|
|
101
|
+
rr(c, 6, 6, L - 12, 62, R - 4);
|
|
102
|
+
c.fillStyle = TH.chip; c.globalAlpha = a * 0.5; c.fill(); c.globalAlpha = a;
|
|
103
|
+
drawGlyph(c, glyph, L / 2, 37, 34, TH.ink2);
|
|
104
|
+
}
|
|
105
|
+
drawName(c, k, info?.name ?? t.name ?? 'File', 81, L - 12, TH.ink, true);
|
|
106
|
+
}
|
|
107
|
+
c.restore();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Far out, a tile is a pin: a small fixed-size card with its icon. */
|
|
111
|
+
function drawPin(c: DrawCtx, b: LinkBox, n: Note, o: LinkDrawOpts): void {
|
|
112
|
+
const s = LINK_PIN_PX;
|
|
113
|
+
rr(c, b.cx - s / 2, b.cy - s / 2, s, s, 6);
|
|
114
|
+
// (selected: ringed round its edge, as a card is)
|
|
115
|
+
if (o.selected) { c.lineWidth = 2 * SELECT_W; c.strokeStyle = TH.yellow; c.stroke(); }
|
|
116
|
+
c.fillStyle = o.info?.exists === false ? TH.chip : TH.badge; c.fill();
|
|
117
|
+
if (!o.selected) { c.lineWidth = 1; c.strokeStyle = TH.badgeline; c.stroke(); }
|
|
118
|
+
drawGlyph(c, glyphOf(n, o.info), b.cx, b.cy, 14, TH.ink2);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** A picture filling the rounded rectangle x, y, w, h (its middle shown, its sides cut off). */
|
|
122
|
+
function cover(c: DrawCtx, im: ImageLike, x: number, y: number, w: number, h: number, r: number): void {
|
|
123
|
+
const iw = im.naturalWidth, ih = im.naturalHeight || 1, k = Math.max(w / iw, h / ih), dw = iw * k, dh = ih * k;
|
|
124
|
+
c.save();
|
|
125
|
+
rr(c, x, y, w, h, r);
|
|
126
|
+
c.clip();
|
|
127
|
+
c.drawImage(im, x + (w - dw) / 2, y + (h - dh) / 2, dw, dh);
|
|
128
|
+
c.restore();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/*
|
|
132
|
+
A name grows with its card until it's NAME_MAX_PX on the screen; past that it stays that size, and the card, wider on
|
|
133
|
+
the screen as it's zoomed in, shows more of it — in the end all of it: on two even lines once two show the whole of it
|
|
134
|
+
(the strip under a picture always has the room by then), and on one once one does. Each name is measured once, at
|
|
135
|
+
each of its two sizes: laying it out again at another width, every step of a pinch, measures nothing.
|
|
136
|
+
*/
|
|
137
|
+
const NAME_PX = 11, NAME_MAX_PX = 14, NAME_LINE = 1.25;
|
|
138
|
+
|
|
139
|
+
/** A name's prefixes' widths at a font (w[i]: its first i characters), and an ellipsis's. */
|
|
140
|
+
const prefixes = new Map<string, { w: number[]; ell: number }>();
|
|
141
|
+
function widthsOf(c: DrawCtx, text: string): { w: number[]; ell: number } {
|
|
142
|
+
const key = c.font + '|' + text;
|
|
143
|
+
let p = prefixes.get(key);
|
|
144
|
+
if (p) return p;
|
|
145
|
+
const w = [0];
|
|
146
|
+
for (let i = 1; i <= text.length; i++) w.push(c.measureText(text.slice(0, i)).width);
|
|
147
|
+
p = { w, ell: c.measureText('…').width };
|
|
148
|
+
if (prefixes.size > 1000) prefixes.clear();
|
|
149
|
+
prefixes.set(key, p);
|
|
150
|
+
return p;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The most of `text` that fits `maxW` at the current font: all of it, or its start and an ellipsis. */
|
|
154
|
+
export function fitName(c: DrawCtx, text: string, maxW: number): string {
|
|
155
|
+
const { w, ell } = widthsOf(c, text), n = text.length;
|
|
156
|
+
if (w[n] <= maxW) return text;
|
|
157
|
+
let lo = 0, hi = n;
|
|
158
|
+
while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (w[mid] + ell <= maxW) lo = mid; else hi = mid - 1; }
|
|
159
|
+
return text.slice(0, lo).trimEnd() + '…';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Where a name breaks onto two lines: at the space nearest half its width (with none, after the _ - . or / nearest it;
|
|
164
|
+
* with none of those, halfway across), so the two are even — and the same however wide its tile, so zooming never
|
|
165
|
+
* moves the break. `width`: the wider of the two lines.
|
|
166
|
+
*/
|
|
167
|
+
const splits = new Map<string, { cut: number; next: number; width: number }>();
|
|
168
|
+
function splitOf(c: DrawCtx, text: string): { cut: number; next: number; width: number } {
|
|
169
|
+
const key = c.font + '|' + text;
|
|
170
|
+
let sp = splits.get(key);
|
|
171
|
+
if (sp) return sp;
|
|
172
|
+
const { w } = widthsOf(c, text), n = text.length, total = w[n];
|
|
173
|
+
sp = { cut: 0, next: 0, width: Infinity };
|
|
174
|
+
for (let i = 1; i < n - 1; i++) {
|
|
175
|
+
if (text[i] !== ' ') continue;
|
|
176
|
+
const width = Math.max(w[i], total - w[i + 1]);
|
|
177
|
+
if (width < sp.width) sp = { cut: i, next: i + 1, width };
|
|
178
|
+
}
|
|
179
|
+
// (no space, as a file's name often has none: after a _ - . or /, which stays on the first line)
|
|
180
|
+
if (!sp.cut) for (let i = 2; i < n - 1; i++) {
|
|
181
|
+
if (!'_-./'.includes(text[i - 1])) continue;
|
|
182
|
+
const width = Math.max(w[i], total - w[i]);
|
|
183
|
+
if (width < sp.width) sp = { cut: i, next: i, width };
|
|
184
|
+
}
|
|
185
|
+
if (!sp.cut) {
|
|
186
|
+
let i = 1;
|
|
187
|
+
while (i < n - 1 && w[i] < total / 2) i++;
|
|
188
|
+
sp = { cut: i, next: i, width: Math.max(w[i], total - w[i]) };
|
|
189
|
+
}
|
|
190
|
+
if (splits.size > 1000) splits.clear();
|
|
191
|
+
splits.set(key, sp);
|
|
192
|
+
return sp;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** All of `text` on two lines `maxW` wide, evenly (see splitOf); null if one line takes it, or two can't. */
|
|
196
|
+
export function twoLines(c: DrawCtx, text: string, maxW: number): [string, string] | null {
|
|
197
|
+
const n = text.length;
|
|
198
|
+
if (n < 2 || widthsOf(c, text).w[n] <= maxW) return null;
|
|
199
|
+
const sp = splitOf(c, text);
|
|
200
|
+
return sp.width <= maxW ? [text.slice(0, sp.cut).trimEnd(), text.slice(sp.next).trimStart()] : null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* A name on card `k` (its scale from the layout), centred on `y` and at most `maxW` wide in the layout: the layout's
|
|
205
|
+
* size until that's NAME_MAX_PX on the screen; past it, that size on the screen (drawn at the screen's own scale, so one
|
|
206
|
+
* font does), showing more of the name as the card grows — and with `wrap`, on two lines once two show all of it.
|
|
207
|
+
*/
|
|
208
|
+
function drawName(c: DrawCtx, k: number, text: string, y: number, maxW: number, color: string, wrap: boolean): void {
|
|
209
|
+
c.fillStyle = color; c.textAlign = 'center'; c.textBaseline = 'middle';
|
|
210
|
+
if (NAME_PX * k <= NAME_MAX_PX) {
|
|
211
|
+
c.font = '600 ' + NAME_PX + 'px ' + FONT;
|
|
212
|
+
c.fillText(fitName(c, text, maxW), L / 2, y);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
c.save();
|
|
216
|
+
c.scale(1 / k, 1 / k);
|
|
217
|
+
c.font = '600 ' + NAME_MAX_PX + 'px ' + FONT;
|
|
218
|
+
const W = maxW * k, x = (L / 2) * k, one = fitName(c, text, W);
|
|
219
|
+
const two = wrap && one !== text ? twoLines(c, text, W) : null;
|
|
220
|
+
if (two) {
|
|
221
|
+
// (centred in the strip under the picture: from 68 to the card's foot)
|
|
222
|
+
const h = NAME_MAX_PX * NAME_LINE, mid = 82 * k;
|
|
223
|
+
c.fillText(two[0], x, mid - h / 2);
|
|
224
|
+
c.fillText(two[1], x, mid + h / 2);
|
|
225
|
+
} else c.fillText(one, x, y * k);
|
|
226
|
+
c.restore();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Names cut to fit, by font, width and text: measured once, not every frame. */
|
|
230
|
+
const fitted = new Map<string, string>();
|
|
231
|
+
function fit(c: DrawCtx, text: string, maxW: number): string {
|
|
232
|
+
const key = c.font + '|' + maxW + '|' + text;
|
|
233
|
+
let out = fitted.get(key);
|
|
234
|
+
if (out != null) return out;
|
|
235
|
+
out = text;
|
|
236
|
+
if (c.measureText(text).width > maxW) {
|
|
237
|
+
let lo = 0, hi = text.length;
|
|
238
|
+
while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (c.measureText(text.slice(0, mid) + '…').width <= maxW) lo = mid; else hi = mid - 1; }
|
|
239
|
+
out = text.slice(0, lo).trimEnd() + '…';
|
|
240
|
+
}
|
|
241
|
+
if (fitted.size > 1000) fitted.clear();
|
|
242
|
+
fitted.set(key, out);
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** A glyph's parts on a 24-unit square: what's stroked (dashed, then solid), what's filled, and a word. */
|
|
247
|
+
interface GlyphDraw { dashed?: (c: DrawCtx) => void; stroke?: (c: DrawCtx) => void; fill?: (c: DrawCtx) => void; text?: string }
|
|
248
|
+
|
|
249
|
+
const page = (c: DrawCtx, fold: boolean) => {
|
|
250
|
+
c.moveTo(6, 3); c.lineTo(fold ? 14 : 18, 3);
|
|
251
|
+
if (fold) c.lineTo(18, 7);
|
|
252
|
+
c.lineTo(18, 21); c.lineTo(6, 21); c.closePath();
|
|
253
|
+
if (fold) { c.moveTo(14, 3); c.lineTo(14, 7); c.lineTo(18, 7); }
|
|
254
|
+
};
|
|
255
|
+
const rules = (c: DrawCtx, ys: number[]) => { for (const y of ys) { c.moveTo(9, y); c.lineTo(15, y); } };
|
|
256
|
+
const dots = (c: DrawCtx, at: [number, number][], r: number) => { for (const [x, y] of at) { c.moveTo(x + r, y); c.arc(x, y, r, 0, Math.PI * 2); } };
|
|
257
|
+
|
|
258
|
+
const GLYPHS: Record<Glyph, GlyphDraw> = {
|
|
259
|
+
photo: {
|
|
260
|
+
stroke: c => { c.roundRect(3, 5, 18, 14, 2); c.moveTo(5, 17); c.lineTo(10, 11); c.lineTo(13, 14); c.lineTo(15, 12); c.lineTo(19, 17); },
|
|
261
|
+
fill: c => dots(c, [[16, 9]], 1.6),
|
|
262
|
+
},
|
|
263
|
+
pdf: { stroke: c => page(c, true), text: 'PDF' },
|
|
264
|
+
note: { stroke: c => { page(c, false); rules(c, [8, 12, 16]); } },
|
|
265
|
+
calculator: {
|
|
266
|
+
stroke: c => { c.roundRect(5, 3, 14, 18, 2); c.moveTo(8, 7); c.lineTo(16, 7); },
|
|
267
|
+
fill: c => dots(c, [11.5, 15, 18.5].flatMap(y => [8.5, 12, 15.5].map(x => [x, y] as [number, number])), 0.9),
|
|
268
|
+
},
|
|
269
|
+
label: {
|
|
270
|
+
stroke: c => { c.moveTo(4, 8); c.lineTo(14, 8); c.lineTo(20, 12); c.lineTo(14, 16); c.lineTo(4, 16); c.closePath(); },
|
|
271
|
+
fill: c => dots(c, [[14, 12]], 1.2),
|
|
272
|
+
},
|
|
273
|
+
drawing: { stroke: c => { page(c, false); c.moveTo(9, 17); c.lineTo(15.5, 8.5); } },
|
|
274
|
+
tape: { stroke: c => { c.roundRect(2.5, 8, 19, 8, 1.5); for (const x of [6, 9, 12, 15, 18]) { c.moveTo(x, 8); c.lineTo(x, x === 12 ? 12.5 : 11); } } },
|
|
275
|
+
removed: { dashed: c => page(c, false), stroke: c => { c.moveTo(4, 20); c.lineTo(20, 4); } },
|
|
276
|
+
document: { stroke: c => { page(c, true); rules(c, [12, 16]); } },
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/** A glyph part's path: kept as a shape where the engine can (a tile's icon is drawn every frame, the same each time). */
|
|
280
|
+
function part(c: DrawCtx, key: string, draw: (c: DrawCtx) => void): void {
|
|
281
|
+
if (c.shape) c.shape(key, 0, 0, cc => draw(cc));
|
|
282
|
+
else { c.beginPath(); draw(c); }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** An icon for kind `g`, `size` across, centred on cx, cy (paths: the canvas can't draw the chrome's SVG icons). */
|
|
286
|
+
function drawGlyph(c: DrawCtx, g: Glyph, cx: number, cy: number, size: number, color: string): void {
|
|
287
|
+
const u = size / 24, d = GLYPHS[g];
|
|
288
|
+
c.save();
|
|
289
|
+
c.translate(cx - size / 2, cy - size / 2);
|
|
290
|
+
c.scale(u, u);
|
|
291
|
+
c.strokeStyle = color; c.fillStyle = color; c.lineWidth = 1.8; c.lineCap = 'round'; c.lineJoin = 'round';
|
|
292
|
+
if (d.dashed) { c.setLineDash([2.2, 2]); part(c, 'glyph- ' + g, d.dashed); c.stroke(); c.setLineDash([]); }
|
|
293
|
+
if (d.stroke) { part(c, 'glyph| ' + g, d.stroke); c.stroke(); }
|
|
294
|
+
if (d.fill) { part(c, 'glyph. ' + g, d.fill); c.fill(); }
|
|
295
|
+
if (d.text) { c.font = '700 6px ' + FONT; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(d.text, 12, 15); }
|
|
296
|
+
c.restore();
|
|
297
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { dashFor, type NoteLook } from '../model/markup';
|
|
2
|
+
import { smoothSpans } from '../model/smooth';
|
|
3
|
+
import type { DrawCtx } from './ctx';
|
|
4
|
+
|
|
5
|
+
type P = [number, number];
|
|
6
|
+
|
|
7
|
+
/** An arrowhead's length for a stroke width (screen pixels). */
|
|
8
|
+
const headLen = (w: number): number => Math.max(12, w * 3.2);
|
|
9
|
+
|
|
10
|
+
/** The unit direction an end points along, taken from far enough back that a pen stroke's jitter doesn't swing it. */
|
|
11
|
+
function endDir(pts: P[], h: number): P {
|
|
12
|
+
const tip = pts[pts.length - 1];
|
|
13
|
+
for (let i = pts.length - 2; i >= 0; i--) {
|
|
14
|
+
const dx = tip[0] - pts[i][0], dy = tip[1] - pts[i][1], d = Math.hypot(dx, dy);
|
|
15
|
+
if (d >= h || i === 0) return d > 1e-9 ? [dx / d, dy / d] : [1, 0];
|
|
16
|
+
}
|
|
17
|
+
return [1, 0];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The path stopped short of its last point by `back` (the arrowhead covers the rest). */
|
|
21
|
+
function trimEnd(pts: P[], dir: P, back: number): P[] {
|
|
22
|
+
const tip = pts[pts.length - 1], stop: P = [tip[0] - dir[0] * back, tip[1] - dir[1] * back];
|
|
23
|
+
const kept = pts.slice(0, -1).filter((p, i) => i === 0 || Math.hypot(p[0] - tip[0], p[1] - tip[1]) > back);
|
|
24
|
+
return [...kept, stop];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A stroke's path: a curve through its points (model/smooth.ts), not straight from one to the next — or a line, from end to end. */
|
|
28
|
+
function trace(c: DrawCtx, pts: P[]): void {
|
|
29
|
+
c.beginPath(); c.moveTo(pts[0][0], pts[0][1]);
|
|
30
|
+
if (pts.length < 3) { c.lineTo(pts[1][0], pts[1][1]); return; }
|
|
31
|
+
for (const [a, b, e] of smoothSpans(pts)) c.bezierCurveTo(a[0], a[1], b[0], b[1], e[0], e[1]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stroke an open piece of markup (screen points) in its look: width, dashes, and each end's finish (round, flat or an
|
|
36
|
+
* arrowhead). `halo`: a wider stroke under it in that color (the selection glow).
|
|
37
|
+
*/
|
|
38
|
+
export function strokeMarkup(c: DrawCtx, pts: P[], look: NoteLook, halo?: string): void {
|
|
39
|
+
if (pts.length < 2) return;
|
|
40
|
+
const w = look.width, h = headLen(w);
|
|
41
|
+
let path = pts;
|
|
42
|
+
const ends: { tip: P; dir: P; cap: NoteLook['endCap'] }[] = [];
|
|
43
|
+
for (const which of ['end', 'start'] as const) {
|
|
44
|
+
const seq = which === 'end' ? path : path.slice().reverse();
|
|
45
|
+
const cap = which === 'end' ? look.endCap : look.startCap, dir = endDir(seq, h), tip = seq[seq.length - 1];
|
|
46
|
+
ends.push({ tip, dir, cap });
|
|
47
|
+
if (cap === 'arrow') {
|
|
48
|
+
const t = trimEnd(seq, dir, h * 0.6);
|
|
49
|
+
path = which === 'end' ? t : t.reverse();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
c.save();
|
|
53
|
+
c.lineJoin = 'round';
|
|
54
|
+
if (halo) {
|
|
55
|
+
c.save(); c.lineWidth = w + 6; c.lineCap = 'round'; c.strokeStyle = halo;
|
|
56
|
+
trace(c, pts); c.stroke();
|
|
57
|
+
c.restore();
|
|
58
|
+
}
|
|
59
|
+
c.strokeStyle = look.color; c.fillStyle = look.color; c.lineWidth = w;
|
|
60
|
+
// dots are zero-length dashes, so they need round caps; otherwise the ends are drawn on their own below
|
|
61
|
+
c.lineCap = look.style === 'dot' ? 'round' : 'butt';
|
|
62
|
+
c.setLineDash(dashFor(look.style, w));
|
|
63
|
+
trace(c, path); c.stroke();
|
|
64
|
+
c.setLineDash([]);
|
|
65
|
+
for (const { tip, dir, cap } of ends) {
|
|
66
|
+
if (cap === 'arrow') {
|
|
67
|
+
const bx = tip[0] - dir[0] * h, by = tip[1] - dir[1] * h, half = h * 0.48;
|
|
68
|
+
c.beginPath(); c.moveTo(tip[0], tip[1]);
|
|
69
|
+
c.lineTo(bx - dir[1] * half, by + dir[0] * half);
|
|
70
|
+
c.lineTo(bx + dir[1] * half, by - dir[0] * half);
|
|
71
|
+
c.closePath(); c.fill();
|
|
72
|
+
} else if (cap === 'round' && look.style !== 'dot') {
|
|
73
|
+
c.beginPath(); c.arc(tip[0], tip[1], w / 2, 0, Math.PI * 2); c.fill();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
c.restore();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Stroke a closed shape's outline (screen points) in its look: width and dashes, its corners rounded. `halo` as above. */
|
|
80
|
+
export function strokeOutline(c: DrawCtx, pts: P[], look: NoteLook, halo?: string): void {
|
|
81
|
+
if (pts.length < 2) return;
|
|
82
|
+
const path = () => { c.beginPath(); pts.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.closePath(); };
|
|
83
|
+
c.save();
|
|
84
|
+
c.lineJoin = 'round';
|
|
85
|
+
if (halo) { c.save(); c.lineWidth = look.width + 6; c.strokeStyle = halo; path(); c.stroke(); c.restore(); }
|
|
86
|
+
c.strokeStyle = look.color; c.lineWidth = look.width;
|
|
87
|
+
c.lineCap = look.style === 'dot' ? 'round' : 'butt';
|
|
88
|
+
c.setLineDash(dashFor(look.style, look.width));
|
|
89
|
+
path(); c.stroke();
|
|
90
|
+
c.restore();
|
|
91
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Pan and zoom by fingers, as plain functions with no imports: on a device they run on the UI thread (each is a
|
|
3
|
+
worklet — the 'worklet' line is how Reanimated's Babel plugin knows to copy it there; anywhere else it's an unused
|
|
4
|
+
string), so a pinch moves the drawing at the screen's own rate, whatever the JavaScript thread is doing. view.ts
|
|
5
|
+
pans and zooms the same way for the browser's wheel and drags.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** The view: zoom (screen px per inch) and the world point at the middle of the screen. */
|
|
9
|
+
export interface NavView { s: number; cx: number; cy: number }
|
|
10
|
+
|
|
11
|
+
/** The fingers: their middle, how far apart the first two are (0 for one finger), and how many there are. */
|
|
12
|
+
export interface Pinch { x: number; y: number; d: number; n: number }
|
|
13
|
+
|
|
14
|
+
/** The furthest out and in the view zooms. */
|
|
15
|
+
export const ZOOM_MIN = 0.04, ZOOM_MAX = 400;
|
|
16
|
+
|
|
17
|
+
/** Where the fingers are as a pinch: the first two's middle and spread, or one finger's position. */
|
|
18
|
+
export function pinchOf(pts: { x: number; y: number }[]): Pinch | null {
|
|
19
|
+
'worklet';
|
|
20
|
+
if (pts.length >= 2) {
|
|
21
|
+
const a = pts[0], b = pts[1];
|
|
22
|
+
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, d: Math.hypot(a.x - b.x, a.y - b.y), n: pts.length };
|
|
23
|
+
}
|
|
24
|
+
if (pts.length === 1) return { x: pts[0].x, y: pts[0].y, d: 0, n: 1 };
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The view after the fingers went from `last` to `now` on a W × H screen: moved with the fingers' middle, then zoomed
|
|
30
|
+
* about it by how much further apart they are (two or more fingers) — so what's under the fingers stays under them.
|
|
31
|
+
*/
|
|
32
|
+
export function navStep(v: NavView, last: Pinch, now: Pinch, W: number, H: number): NavView {
|
|
33
|
+
'worklet';
|
|
34
|
+
const s0 = v.s;
|
|
35
|
+
let cx = v.cx - (now.x - last.x) / s0, cy = v.cy + (now.y - last.y) / s0, s = s0;
|
|
36
|
+
if (now.n >= 2 && last.d > 0 && now.d > 0) {
|
|
37
|
+
// the world point under the fingers' middle, which the zoom keeps there
|
|
38
|
+
const wx = (now.x - (W / 2 - cx * s0)) / s0, wy = (H / 2 + cy * s0 - now.y) / s0;
|
|
39
|
+
s = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, (s0 * now.d) / last.d));
|
|
40
|
+
cx = wx - (now.x - W / 2) / s;
|
|
41
|
+
cy = wy + (now.y - H / 2) / s;
|
|
42
|
+
}
|
|
43
|
+
return { s, cx, cy };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* How to move and scale a picture drawn at one view (its zoom `s` and screen offset `ox`, `oy` — see view.ts Xform)
|
|
48
|
+
* so it sits where view `to` puts it on a W × H screen: scale by k about the screen's corner, then move by tx, ty.
|
|
49
|
+
*/
|
|
50
|
+
export function shiftTo(s: number, ox: number, oy: number, to: NavView, W: number, H: number): { k: number; tx: number; ty: number } {
|
|
51
|
+
'worklet';
|
|
52
|
+
const k = to.s / s;
|
|
53
|
+
return { k, tx: W / 2 - to.cx * to.s - ox * k, ty: H / 2 + to.cy * to.s - oy * k };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The view as the fingers left it (on a device's UI thread); `seq` counts their moves. */
|
|
57
|
+
export interface UiView extends NavView { seq: number }
|
|
58
|
+
|
|
59
|
+
/** What the drawing on screen was drawn at: its view, the latest of the fingers' views it includes, the screen size. */
|
|
60
|
+
export interface DrawnAt { view: NavView; seq: number; W: number; H: number }
|
|
61
|
+
|
|
62
|
+
/** The fingers on the screen and what they're doing: navigating or not, where they were last, whether a pen has been seen. */
|
|
63
|
+
export interface NavState { fingers: Record<number, { x: number; y: number }>; navigating: boolean; last: Pinch | null; pen: boolean }
|
|
64
|
+
|
|
65
|
+
export const NAV_DOWN = 0, NAV_MOVE = 1, NAV_UP = 2, NAV_CANCEL = 3;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* One touch event, as the UI thread sees it: the fingers after it, and the view (moved if `moved`). Fingers navigate —
|
|
69
|
+
* pan, and zoom about their middle — once two are down, or any one once a pen has been seen or when the drawing is
|
|
70
|
+
* view only (editor/gestures.ts decides the same on its side), until the last one lifts. A pen or a mouse never
|
|
71
|
+
* navigates. `type` is NAV_DOWN… ; `allIds`, every pointer still down (on a down event: a finger that went without
|
|
72
|
+
* a word, a lost touch-up, is dropped so it can't hold a gesture open).
|
|
73
|
+
*/
|
|
74
|
+
export function navTouch(
|
|
75
|
+
st: NavState, type: number, kind: string, changed: { id: number; x: number; y: number }[], allIds: number[] | null,
|
|
76
|
+
readOnly: boolean, view: UiView, drawn: DrawnAt | null,
|
|
77
|
+
): { st: NavState; view: UiView; moved: boolean } {
|
|
78
|
+
'worklet';
|
|
79
|
+
const pen = st.pen || kind === 'pen';
|
|
80
|
+
if (kind !== 'touch') return { st: { ...st, pen }, view, moved: false };
|
|
81
|
+
const fingers: Record<number, { x: number; y: number }> = {};
|
|
82
|
+
for (const k in st.fingers) {
|
|
83
|
+
const id = Number(k);
|
|
84
|
+
if (type !== NAV_DOWN || !allIds || allIds.includes(id)) fingers[id] = st.fingers[id];
|
|
85
|
+
}
|
|
86
|
+
for (const t of changed) {
|
|
87
|
+
if (type === NAV_UP || type === NAV_CANCEL) delete fingers[t.id];
|
|
88
|
+
else fingers[t.id] = { x: t.x, y: t.y };
|
|
89
|
+
}
|
|
90
|
+
const pts = Object.keys(fingers).map(k => fingers[Number(k)]);
|
|
91
|
+
if (!pts.length) return { st: { fingers, navigating: false, last: null, pen }, view, moved: false };
|
|
92
|
+
let navigating = st.navigating, v = view;
|
|
93
|
+
if (!navigating) {
|
|
94
|
+
if (pts.length < 2 && !(type === NAV_DOWN && (pen || readOnly))) return { st: { fingers, navigating: false, last: null, pen }, view, moved: false };
|
|
95
|
+
navigating = true;
|
|
96
|
+
// start from the view the drawing on screen was drawn at, unless the fingers' own is newer
|
|
97
|
+
if (drawn && v.seq <= drawn.seq) v = { s: drawn.view.s, cx: drawn.view.cx, cy: drawn.view.cy, seq: drawn.seq };
|
|
98
|
+
}
|
|
99
|
+
const now = pinchOf(pts), was = st.last, next = { fingers, navigating, last: now, pen };
|
|
100
|
+
// a finger came or went: carry on from here, no jump
|
|
101
|
+
if (type !== NAV_MOVE || !now || !was || was.n !== now.n || !drawn) return { st: next, view: v, moved: false };
|
|
102
|
+
const to = navStep(v, was, now, drawn.W, drawn.H);
|
|
103
|
+
return { st: next, view: { s: to.s, cx: to.cx, cy: to.cy, seq: v.seq + 1 }, moved: true };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* How to carry the drawing on screen — drawn at transform (s, ox, oy) — to the fingers' view: null when it already
|
|
108
|
+
* shows it (the fingers haven't moved the view since it was drawn).
|
|
109
|
+
*/
|
|
110
|
+
export function carryTo(drawn: { s: number; ox: number; oy: number; W: number; H: number; seq: number }, view: UiView): { k: number; tx: number; ty: number } | null {
|
|
111
|
+
'worklet';
|
|
112
|
+
if (view.seq <= drawn.seq) return null;
|
|
113
|
+
return shiftTo(drawn.s, drawn.ox, drawn.oy, view, drawn.W, drawn.H);
|
|
114
|
+
}
|