@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
package/src/render/snap.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { settings } from '../model/settings';
|
|
|
4
4
|
import { SK } from '../model/store';
|
|
5
5
|
import { gridSteps } from '../model/planGeom';
|
|
6
6
|
import { mainT, S, s2w } from './view';
|
|
7
|
+
import { intoWall } from '../editor/elevation';
|
|
7
8
|
|
|
8
9
|
export interface SnapOpts {
|
|
9
10
|
touch?: boolean;
|
|
@@ -15,11 +16,61 @@ export interface SnapOpts {
|
|
|
15
16
|
prevLine?: string | null;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
/** Snap a screen point: endpoints > midpoints/intersections > H/V/perp guides > on
|
|
19
|
+
/** Snap a screen point: endpoints > midpoints/intersections > on line > H/V/perp guides > on circle > grid. In the wall view, never past the wall. */
|
|
20
|
+
/**
|
|
21
|
+
* Shift held: from `a`, the direction to `p` locked to the nearest 45° — level, plumb or a diagonal — keeping how
|
|
22
|
+
* far along it `p` is. `dir`: which (H level, V plumb, D diagonal).
|
|
23
|
+
*/
|
|
24
|
+
export function snap45(a: { x: number; y: number }, p: { x: number; y: number }): { x: number; y: number; dir: 'H' | 'V' | 'D' } {
|
|
25
|
+
const dx = p.x - a.x, dy = p.y - a.y, L = hyp(dx, dy);
|
|
26
|
+
if (L < 1e-9) return { x: a.x, y: a.y, dir: 'H' };
|
|
27
|
+
const k = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)), th = k * (Math.PI / 4);
|
|
28
|
+
const ux = Math.round(Math.cos(th) * 1e12) / 1e12, uy = Math.round(Math.sin(th) * 1e12) / 1e12, t = dx * ux + dy * uy;
|
|
29
|
+
return { x: a.x + ux * t, y: a.y + uy * t, dir: k % 4 === 0 ? 'H' : Math.abs(k % 4) === 2 ? 'V' : 'D' };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A drawing snap with Shift held, from point `from`: locked to the nearest 45°; level or plumb comes with its guide (auto-constrain adds it). */
|
|
33
|
+
export function shiftSnap(from: string, s: Snap): Snap {
|
|
34
|
+
const a = SK().points[from];
|
|
35
|
+
if (!a) return s;
|
|
36
|
+
const q = snap45(a, s), [x, y] = intoWall(q.x, q.y);
|
|
37
|
+
const guides: Guide[] = q.dir === 'D' ? [] : [{ kind: q.dir, from, x1: a.x, y1: a.y, x2: x, y2: y }];
|
|
38
|
+
return { x, y, kind: 'free', guides };
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
export function snapAt(sx: number, sy: number, o: SnapOpts = {}): Snap {
|
|
42
|
+
const s = snapRaw(sx, sy, o), [x, y] = intoWall(s.x, s.y);
|
|
43
|
+
return x === s.x && y === s.y ? s : { ...s, x, y, kind: s.kind === 'grid' ? 'grid' : 'free', ref: null, refs: undefined };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
The grid: with Snap to grid on, whatever a snap leaves free goes on the grid that's drawn (gridSteps: what's seen is
|
|
48
|
+
what's snapped to) — the whole point in open space; the other direction when a guide holds one (drawn level from a
|
|
49
|
+
point on the grid: a whole number of steps long); along a line, where it crosses a grid line; along a perpendicular,
|
|
50
|
+
whole steps from where it starts. Endpoints, midpoints and intersections still come first. And it's steady: as the
|
|
51
|
+
pointer moves, the snap holds the grid line it's on until the pointer is well past halfway to the next, so a
|
|
52
|
+
finger's wobble at the halfway point doesn't flick it back and forth.
|
|
53
|
+
*/
|
|
54
|
+
const HOLD = 0.15;
|
|
55
|
+
/** The last snap's grid lines, while the pointer moves on from it (each axis, if it went on the grid). */
|
|
56
|
+
let held: { st: number; x?: number; y?: number; sx: number; sy: number; t: number } | null = null;
|
|
57
|
+
|
|
58
|
+
/** The grid line nearest v — or, `last` (the one it was on) while v isn't yet well past halfway to the next. */
|
|
59
|
+
export function onGrid(v: number, st: number, last?: number): number {
|
|
60
|
+
const r = Math.round(v / st) * st;
|
|
61
|
+
return last !== undefined && Math.abs(v - last) <= st * (0.5 + HOLD) ? last : r;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function snapRaw(sx: number, sy: number, o: SnapOpts): Snap {
|
|
20
65
|
const sk = SK(), T = mainT(), tol = o.touch ? 22 : 12;
|
|
21
66
|
const [wx, wy] = s2w(sx, sy);
|
|
22
67
|
const ex = o.exclude || new Set<string>();
|
|
68
|
+
const st = settings.snapGrid ? gridSteps(T.s).minor : 0, now = performance.now();
|
|
69
|
+
// (the same pointer moving on: not a new tap somewhere else, nor one after a pause)
|
|
70
|
+
const was = held && held.st === st && now - held.t < 250 && hyp(sx - held.sx, sy - held.sy) < 48 ? held : null;
|
|
71
|
+
let usedX: number | undefined, usedY: number | undefined;
|
|
72
|
+
const gridX = (v: number) => (usedX = onGrid(v, st, was?.x)), gridY = (v: number) => (usedY = onGrid(v, st, was?.y));
|
|
73
|
+
const finish = (snap: Snap): Snap => { held = st ? { st, x: usedX, y: usedY, sx, sy, t: now } : null; return snap; };
|
|
23
74
|
let best: (Snap & { sc: number }) | null = null;
|
|
24
75
|
const cand = (x: number, y: number, kind: Snap['kind'], ref: string | [string, string], prio: number, label: string) => {
|
|
25
76
|
const [px, py] = S(T, x, y);
|
|
@@ -62,7 +113,19 @@ export function snapAt(sx: number, sy: number, o: SnapOpts = {}): Snap {
|
|
|
62
113
|
}
|
|
63
114
|
if (best) {
|
|
64
115
|
const { sc: _sc, ...snap } = best as Snap & { sc: number };
|
|
65
|
-
return snap;
|
|
116
|
+
return finish(snap);
|
|
117
|
+
}
|
|
118
|
+
// on a line, near the pointer (checked before the alignment guides: a line under the pointer wins over lining up with
|
|
119
|
+
// a point elsewhere — where both apply, the snap is where the guide crosses the line)
|
|
120
|
+
let bestL: { d: number; x: number; y: number; id: string } | null = null;
|
|
121
|
+
for (const id of near) {
|
|
122
|
+
const l = sk.lines[id], A = sk.points[l.a], B = sk.points[l.b];
|
|
123
|
+
const t = projT(A, B, { x: wx, y: wy });
|
|
124
|
+
if (t < 0 || t > 1) continue;
|
|
125
|
+
const fx = A.x + (B.x - A.x) * t, fy = A.y + (B.y - A.y) * t;
|
|
126
|
+
const [qx, qy] = S(T, fx, fy);
|
|
127
|
+
const d = hyp(qx - sx, qy - sy);
|
|
128
|
+
if (d < tol && (!bestL || d < bestL.d)) bestL = { d, x: fx, y: fy, id };
|
|
66
129
|
}
|
|
67
130
|
let x = wx, y = wy, kind: Snap['kind'] = 'free';
|
|
68
131
|
const guides: Guide[] = [];
|
|
@@ -77,42 +140,62 @@ export function snapAt(sx: number, sy: number, o: SnapOpts = {}): Snap {
|
|
|
77
140
|
if (gy == null && Math.abs(sy - py) < tol) gy = { y: r.p.y, from: r.id, own: r.own };
|
|
78
141
|
if (gx == null && Math.abs(sx - px) < tol) gx = { x: r.p.x, from: r.id, own: r.own };
|
|
79
142
|
}
|
|
143
|
+
if (bestL) {
|
|
144
|
+
// a line under the pointer: on it — where a guide crosses it, if one does (level with the start, and on the line)
|
|
145
|
+
const l = sk.lines[bestL.id], A = sk.points[l.a], B = sk.points[l.b], dx = B.x - A.x, dy = B.y - A.y;
|
|
146
|
+
const g = gy && Math.abs(dy) > 1e-9 ? { t: (gy.y - A.y) / dy, h: gy } : gx && Math.abs(dx) > 1e-9 ? { t: (gx.x - A.x) / dx, v: gx } : null;
|
|
147
|
+
if (g && g.t >= 0 && g.t <= 1) {
|
|
148
|
+
const ix = A.x + dx * g.t, iy = A.y + dy * g.t, [qx, qy] = S(T, ix, iy);
|
|
149
|
+
if (hyp(qx - sx, qy - sy) < tol * 1.5) {
|
|
150
|
+
const gd = 'h' in g ? g.h! : g.v!, H = 'h' in g;
|
|
151
|
+
return finish({ x: ix, y: iy, kind: 'online', ref: bestL.id, label: 'On line', guides: [H ? { x1: ix - 1, y1: iy, x2: ix + 1, y2: iy, kind: 'H', from: gd.from, label: gd.own ? 'Horizontal' : 'Aligned' } : { x1: ix, y1: iy - 1, x2: ix, y2: iy + 1, kind: 'V', from: gd.from, label: gd.own ? 'Vertical' : 'Aligned' }] });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// on the grid, as far as the line allows: where it crosses the grid line nearest (along a level or plumb line,
|
|
155
|
+
// the grid point on it)
|
|
156
|
+
if (st) {
|
|
157
|
+
const on = bestL, near2 = (c: { x: number; y: number }) => hyp(c.x - on.x, c.y - on.y);
|
|
158
|
+
const opts: { x: number; y: number; t: number; axis: 'x' | 'y' }[] = [];
|
|
159
|
+
if (Math.abs(dx) > 1e-9) { const gv = onGrid(on.x, st, was?.x), t = (gv - A.x) / dx; opts.push({ x: gv, y: A.y + dy * t, t, axis: 'x' }); }
|
|
160
|
+
if (Math.abs(dy) > 1e-9) { const gv = onGrid(on.y, st, was?.y), t = (gv - A.y) / dy; opts.push({ x: A.x + dx * t, y: gv, t, axis: 'y' }); }
|
|
161
|
+
const pick = opts
|
|
162
|
+
.filter(c => { const [qx, qy] = S(T, c.x, c.y); return c.t >= 0 && c.t <= 1 && hyp(qx - sx, qy - sy) < tol * 1.5; })
|
|
163
|
+
.sort((a, b) => near2(a) - near2(b))[0];
|
|
164
|
+
if (pick) {
|
|
165
|
+
if (pick.axis === 'x') usedX = pick.x; else usedY = pick.y;
|
|
166
|
+
return finish({ x: pick.x, y: pick.y, kind: 'online', ref: on.id, label: 'On line', guides: [] });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return finish({ x: bestL.x, y: bestL.y, kind: 'online', ref: bestL.id, label: 'On line', guides: [] });
|
|
170
|
+
}
|
|
80
171
|
if (gy) { y = gy.y; guides.push({ x1: wx - 1, y1: gy.y, x2: wx + 1, y2: gy.y, kind: 'H', from: gy.from, label: gy.own ? 'Horizontal' : 'Aligned' }); }
|
|
81
172
|
if (gx) { x = gx.x; guides.push({ x1: gx.x, y1: wy - 1, x2: gx.x, y2: wy + 1, kind: 'V', from: gx.from, label: gx.own ? 'Vertical' : 'Aligned' }); }
|
|
82
173
|
if (!guides.length && from && o.prevLine && sk.lines[o.prevLine]) {
|
|
83
174
|
const l = sk.lines[o.prevLine], A = sk.points[l.a], B = sk.points[l.b];
|
|
84
175
|
const d = norm2(B.x - A.x, B.y - A.y), n = [-d[1], d[0]];
|
|
85
|
-
const
|
|
176
|
+
const t0 = (wx - from.x) * n[0] + (wy - from.y) * n[1];
|
|
177
|
+
// (with the grid: whole steps of it from where it starts)
|
|
178
|
+
const t = st ? onGrid(t0, st) : t0;
|
|
86
179
|
const px = from.x + n[0] * t, py = from.y + n[1] * t;
|
|
87
|
-
const [qx, qy] = S(T,
|
|
88
|
-
if (hyp(qx - sx, qy - sy) < tol && Math.abs(
|
|
180
|
+
const [qx, qy] = S(T, from.x + n[0] * t0, from.y + n[1] * t0);
|
|
181
|
+
if (hyp(qx - sx, qy - sy) < tol && Math.abs(t0) * T.s > tol) {
|
|
89
182
|
x = px; y = py;
|
|
90
183
|
guides.push({ x1: from.x, y1: from.y, x2: px, y2: py, kind: 'perp', label: 'Perpendicular' });
|
|
91
184
|
}
|
|
92
185
|
}
|
|
93
186
|
if (!guides.length) {
|
|
94
|
-
let bestL: { d: number; x: number; y: number; id: string } | null = null;
|
|
95
|
-
for (const id of near) {
|
|
96
|
-
const l = sk.lines[id], A = sk.points[l.a], B = sk.points[l.b];
|
|
97
|
-
const t = projT(A, B, { x: wx, y: wy });
|
|
98
|
-
if (t < 0 || t > 1) continue;
|
|
99
|
-
const fx = A.x + (B.x - A.x) * t, fy = A.y + (B.y - A.y) * t;
|
|
100
|
-
const [qx, qy] = S(T, fx, fy);
|
|
101
|
-
const d = hyp(qx - sx, qy - sy);
|
|
102
|
-
if (d < tol && (!bestL || d < bestL.d)) bestL = { d, x: fx, y: fy, id };
|
|
103
|
-
}
|
|
104
|
-
if (bestL) return { x: bestL.x, y: bestL.y, kind: 'online', ref: bestL.id, label: 'On line', guides: [] };
|
|
105
187
|
for (const id in sk.circles) {
|
|
106
188
|
const ci = sk.circles[id], o2 = sk.points[ci.c];
|
|
107
189
|
const d = hyp(wx - o2.x, wy - o2.y);
|
|
108
190
|
if (Math.abs(d - ci.r) * T.s < tol && d > 1e-6) {
|
|
109
|
-
return { x: o2.x + ((wx - o2.x) / d) * ci.r, y: o2.y + ((wy - o2.y) / d) * ci.r, kind: 'oncircle', ref: id, label: 'On circle', guides: [] };
|
|
191
|
+
return finish({ x: o2.x + ((wx - o2.x) / d) * ci.r, y: o2.y + ((wy - o2.y) / d) * ci.r, kind: 'oncircle', ref: id, label: 'On circle', guides: [] });
|
|
110
192
|
}
|
|
111
193
|
}
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
194
|
+
if (st) { x = gridX(wx); y = gridY(wy); kind = 'grid'; }
|
|
195
|
+
} else if (st && !guides.some(g => g.kind === 'perp')) {
|
|
196
|
+
// a guide holds one direction: the other goes on the grid
|
|
197
|
+
if (!gx) x = gridX(wx);
|
|
198
|
+
if (!gy) y = gridY(wy);
|
|
116
199
|
}
|
|
117
|
-
return { x, y, kind, ref: null, label: '', guides };
|
|
200
|
+
return finish({ x, y, kind, ref: null, label: '', guides });
|
|
118
201
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { DrawCtx } from './ctx';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Figma's corner smoothing, at the 60% it calls "iOS" (Apple's continuous corners): a corner keeps its circular arc of
|
|
5
|
+
radius r in the middle, but shorter, and eases into each straight side over a longer run — (1 + smoothing) · r of
|
|
6
|
+
the side instead of r — so the curvature has no sudden step where the side ends. The construction is Figma's own
|
|
7
|
+
(as published in its "Desperately seeking squircles", and the MIT figma-squircle package).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** How much every rounded corner in the editor is smoothed: Figma's 60%, "iOS". */
|
|
11
|
+
export const CORNER_SMOOTHING = 0.6;
|
|
12
|
+
|
|
13
|
+
const rad = (deg: number): number => (deg * Math.PI) / 180;
|
|
14
|
+
|
|
15
|
+
/** One corner's measures: the run along each side (p), the two Béziers' (a, b, c, d), the arc's half-sweep (alpha). */
|
|
16
|
+
function cornerParams(r: number, smoothing: number, budget: number) {
|
|
17
|
+
// a corner can't run further along its sides than half the shorter side: less smoothing where it can't fit
|
|
18
|
+
const s = Math.max(0, Math.min(smoothing, budget / r - 1));
|
|
19
|
+
const p = Math.min((1 + s) * r, budget);
|
|
20
|
+
const arcMeasure = 90 * (1 - s);
|
|
21
|
+
const arcSection = Math.sin(rad(arcMeasure / 2)) * r * Math.SQRT2;
|
|
22
|
+
const alpha = (90 - arcMeasure) / 2;
|
|
23
|
+
const p3ToP4 = r * Math.tan(rad(alpha / 2));
|
|
24
|
+
const beta = 45 * s;
|
|
25
|
+
const c = p3ToP4 * Math.cos(rad(beta));
|
|
26
|
+
const d = c * Math.tan(rad(beta));
|
|
27
|
+
const b = (p - arcSection - c - d) / 3;
|
|
28
|
+
return { p, a: 2 * b, b, c, d, alpha: rad(alpha) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A rounded rectangle's path with smoothed corners (x, y its top left; r each corner's radius). A pill or a circle —
|
|
33
|
+
* r at least half the shorter side — is left round: smoothing has nothing to ease into there, as in Figma.
|
|
34
|
+
*/
|
|
35
|
+
export function smoothRectPath(c: DrawCtx, x: number, y: number, w: number, h: number, r: number, smoothing = CORNER_SMOOTHING): void {
|
|
36
|
+
const budget = Math.min(w, h) / 2;
|
|
37
|
+
r = Math.max(0, Math.min(r, budget));
|
|
38
|
+
c.beginPath();
|
|
39
|
+
if (r <= 0 || smoothing <= 0 || r >= budget - 1e-6) {
|
|
40
|
+
c.moveTo(x + r, y);
|
|
41
|
+
c.arcTo(x + w, y, x + w, y + h, r);
|
|
42
|
+
c.arcTo(x + w, y + h, x, y + h, r);
|
|
43
|
+
c.arcTo(x, y + h, x, y, r);
|
|
44
|
+
c.arcTo(x, y, x + w, y, r);
|
|
45
|
+
c.closePath();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const k = cornerParams(r, smoothing, budget);
|
|
49
|
+
// each corner drawn in its own frame: u along the side coming into it, v along the side leaving it, the corner's
|
|
50
|
+
// vertex at the origin — then turned to its place, clockwise from the top right
|
|
51
|
+
const corners: { ox: number; oy: number; ux: [number, number]; vx: [number, number]; turn: number }[] = [
|
|
52
|
+
{ ox: x + w, oy: y, ux: [1, 0], vx: [0, 1], turn: 0 },
|
|
53
|
+
{ ox: x + w, oy: y + h, ux: [0, 1], vx: [-1, 0], turn: Math.PI / 2 },
|
|
54
|
+
{ ox: x, oy: y + h, ux: [-1, 0], vx: [0, -1], turn: Math.PI },
|
|
55
|
+
{ ox: x, oy: y, ux: [0, -1], vx: [1, 0], turn: (3 * Math.PI) / 2 },
|
|
56
|
+
];
|
|
57
|
+
const { p, a, b, c: cc, d, alpha } = k;
|
|
58
|
+
corners.forEach((q, i) => {
|
|
59
|
+
const at = (u: number, v: number): [number, number] => [q.ox + q.ux[0] * u + q.vx[0] * v, q.oy + q.ux[1] * u + q.vx[1] * v];
|
|
60
|
+
const [sx, sy] = at(-p, 0);
|
|
61
|
+
if (i === 0) c.moveTo(sx, sy); else c.lineTo(sx, sy);
|
|
62
|
+
// into the arc, along the side
|
|
63
|
+
const [c1x, c1y] = at(-p + a, 0), [c2x, c2y] = at(-p + a + b, 0), [e1x, e1y] = at(-p + a + b + cc, d);
|
|
64
|
+
c.bezierCurveTo(c1x, c1y, c2x, c2y, e1x, e1y);
|
|
65
|
+
// the arc: radius r about (−r, r), its middle (90° less twice alpha) — angles turned with the corner
|
|
66
|
+
const [cx, cy] = at(-r, r);
|
|
67
|
+
c.arc(cx, cy, r, -Math.PI / 2 + alpha + q.turn, -alpha + q.turn, false);
|
|
68
|
+
// out of the arc onto the next side (the first Bézier mirrored across the corner's diagonal)
|
|
69
|
+
const [c3x, c3y] = at(0, p - a - b), [c4x, c4y] = at(0, p - a), [ex, ey] = at(0, p);
|
|
70
|
+
c.bezierCurveTo(c3x, c3y, c4x, c4y, ex, ey);
|
|
71
|
+
});
|
|
72
|
+
c.closePath();
|
|
73
|
+
}
|
package/src/render/view.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { clamp } from '../model/geom';
|
|
2
|
+
import { faceEdit } from '../model/faces';
|
|
2
3
|
import { page, SK } from '../model/store';
|
|
3
4
|
import type { Note, Sketch, Underlay, View } from '../model/types';
|
|
5
|
+
import { CLOSED_SHAPES, isSegment, NOTE_TEXT_SIZE, noteTextIn } from '../model/markup';
|
|
6
|
+
import { LINK_FULL_PX, LINK_PIN_PX, LINK_TILE_PX, linkSide } from '../model/links';
|
|
4
7
|
import { draw } from './draw';
|
|
5
8
|
import { onSessionReset, sessionState } from '../model/session';
|
|
6
9
|
import { refreshDrawerSoon } from '../editor/ports';
|
|
10
|
+
import { FONT } from './theme';
|
|
11
|
+
import { ZOOM_MAX, ZOOM_MIN } from './navigate';
|
|
7
12
|
|
|
8
13
|
/** World (inches, y up) → screen transform. */
|
|
9
14
|
export interface Xform { s: number; ox: number; oy: number }
|
|
@@ -21,8 +26,13 @@ export function initCanvas(el: HTMLCanvasElement): void {
|
|
|
21
26
|
cvs.ctx = el.getContext('2d')!;
|
|
22
27
|
}
|
|
23
28
|
|
|
29
|
+
/** The pan/zoom of each wall face that's been open, by page and face. */
|
|
30
|
+
const faceViews = sessionState(() => ({} as Record<string, View>));
|
|
31
|
+
|
|
32
|
+
/** The view of what's being edited: the page's plan, or the wall face open. */
|
|
24
33
|
export function V(): View {
|
|
25
34
|
const pg = page();
|
|
35
|
+
if (faceEdit.key) return (faceViews[pg.id + '|' + faceEdit.key] ??= { cx: 0, cy: 48, s: 2 });
|
|
26
36
|
if (!pg.view) pg.view = { cx: 84, cy: 120, s: 2 };
|
|
27
37
|
return pg.view;
|
|
28
38
|
}
|
|
@@ -42,17 +52,17 @@ export function s2w(sx: number, sy: number): [number, number] {
|
|
|
42
52
|
export function zoomAt(sx: number, sy: number, f: number): void {
|
|
43
53
|
const v = V();
|
|
44
54
|
const [wx, wy] = s2w(sx, sy);
|
|
45
|
-
v.s = clamp(v.s * f,
|
|
55
|
+
v.s = clamp(v.s * f, ZOOM_MIN, ZOOM_MAX);
|
|
46
56
|
v.cx = wx - (sx - cvs.W / 2) / v.s;
|
|
47
57
|
v.cy = wy + (sy - cvs.H / 2) / v.s;
|
|
48
|
-
|
|
58
|
+
requestViewDraw();
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
export function panBy(dx: number, dy: number): void {
|
|
52
62
|
const v = V();
|
|
53
63
|
v.cx -= dx / v.s;
|
|
54
64
|
v.cy += dy / v.s;
|
|
55
|
-
|
|
65
|
+
requestViewDraw();
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
export function sketchBounds(sk: Sketch, inclOrigin = true): { x0: number; y0: number; x1: number; y1: number } | null {
|
|
@@ -60,24 +70,42 @@ export function sketchBounds(sk: Sketch, inclOrigin = true): { x0: number; y0: n
|
|
|
60
70
|
const add = (x: number, y: number) => { x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x); y1 = Math.max(y1, y); };
|
|
61
71
|
for (const id in sk.points) { const p = sk.points[id]; if (p.origin && !inclOrigin) continue; add(p.x, p.y); }
|
|
62
72
|
for (const id in sk.circles) { const c = sk.circles[id], o = sk.points[c.c]; if (o) { add(o.x - c.r, o.y - c.r); add(o.x + c.r, o.y + c.r); } }
|
|
63
|
-
for (const n of sk.notes) {
|
|
73
|
+
for (const n of sk.notes) {
|
|
74
|
+
if (n.pts) n.pts.forEach(p => add(p[0], p[1]));
|
|
75
|
+
else if (n.type === 'link') {
|
|
76
|
+
// its tile, and what its leader points at
|
|
77
|
+
const h = linkSide(n) / 2, x = n.x ?? 0, y = n.y ?? 0;
|
|
78
|
+
add(x - h, y - h); add(x + h, y + h);
|
|
79
|
+
if (n.leader) add(n.leader[0], n.leader[1]);
|
|
80
|
+
} else add(n.x!, n.y!);
|
|
81
|
+
}
|
|
64
82
|
if (x0 === Infinity) return null;
|
|
65
83
|
return { x0, y0, x1, y1 };
|
|
66
84
|
}
|
|
67
85
|
|
|
86
|
+
/** The open wall's face, for the wall view to fit (registered by editor/elevation.ts; null on the plan). */
|
|
87
|
+
let wallBox: () => { x0: number; y0: number; x1: number; y1: number } | null = () => null;
|
|
88
|
+
export function setWallBox(fn: typeof wallBox): void { wallBox = fn; }
|
|
89
|
+
|
|
68
90
|
export function fitView(): void {
|
|
69
|
-
const sk = SK(), pg = page();
|
|
91
|
+
const sk = SK(), pg = page(), wallFace = wallBox();
|
|
70
92
|
let b = sketchBounds(sk);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
// the wall view fits the wall itself (and what's drawn on it); the page's photos and plans are under the plan, not
|
|
94
|
+
// on the wall — counting them zoomed a wall out to take in a plan nobody could see there
|
|
95
|
+
if (faceEdit.key && wallFace) b = b ? { x0: Math.min(b.x0, wallFace.x0), y0: Math.min(b.y0, wallFace.y0), x1: Math.max(b.x1, wallFace.x1), y1: Math.max(b.y1, wallFace.y1) } : wallFace;
|
|
96
|
+
if (!faceEdit.key) for (const u of pg.underlays) {
|
|
97
|
+
// (as it sits, turned)
|
|
98
|
+
const cs = underlayCorners(u), xs = cs.map(q => q.x), ys = cs.map(q => q.y);
|
|
99
|
+
const bb = { x0: Math.min(...xs), y0: Math.min(...ys), x1: Math.max(...xs), y1: Math.max(...ys) };
|
|
74
100
|
b = b ? { x0: Math.min(b.x0, bb.x0), y0: Math.min(b.y0, bb.y0), x1: Math.max(b.x1, bb.x1), y1: Math.max(b.y1, bb.y1) } : bb;
|
|
75
101
|
}
|
|
76
102
|
const v = V();
|
|
77
103
|
if (!b || (b.x1 - b.x0 < 1 && b.y1 - b.y0 < 1)) { v.cx = 0; v.cy = 0; v.s = 3; requestDraw(); return; }
|
|
78
104
|
const { W, H } = cvs;
|
|
79
|
-
const phone = W < 760;
|
|
80
|
-
|
|
105
|
+
const phone = W < 760, wall = faceEdit.key != null;
|
|
106
|
+
// a wall face: room for the dimensions along the floor and beside it, and for the key plan (top right)
|
|
107
|
+
const L = phone ? 78 : 110, R = wall ? (phone ? 64 : Math.max(110, keyPlanSize(W) + 40)) : phone ? 28 : 110;
|
|
108
|
+
const Tp = phone ? 200 : wall ? 110 : 150, Bt = wall ? (phone ? 210 : 190) : 120;
|
|
81
109
|
const bw = Math.max(b.x1 - b.x0, 24), bh = Math.max(b.y1 - b.y0, 24);
|
|
82
110
|
v.s = clamp(Math.min((W - L - R) / bw, (H - Tp - Bt) / bh), 0.08, 60);
|
|
83
111
|
const scx = (L + W - R) / 2, scy = (Tp + H - Bt) / 2;
|
|
@@ -86,6 +114,9 @@ export function fitView(): void {
|
|
|
86
114
|
requestDraw();
|
|
87
115
|
}
|
|
88
116
|
|
|
117
|
+
/** Size of the wall view's key plan (top right), pixels. */
|
|
118
|
+
export const keyPlanSize = (W: number): number => Math.min(150, Math.max(96, W * 0.16));
|
|
119
|
+
|
|
89
120
|
/** Height of an image underlay in inches: its own, or from the picture's aspect ratio. */
|
|
90
121
|
export function underlayH(u: Underlay): number {
|
|
91
122
|
if (u.h) return u.h;
|
|
@@ -95,9 +126,40 @@ export function underlayH(u: Underlay): number {
|
|
|
95
126
|
|
|
96
127
|
export type HandlePart = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w';
|
|
97
128
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
129
|
+
/*
|
|
130
|
+
An image turned (its `rot`) turns about its middle: x, y, w and h are its box before it's turned. These give its
|
|
131
|
+
points as they sit turned. The world's y runs up, so a turn clockwise as it's seen is the other way round in it.
|
|
132
|
+
*/
|
|
133
|
+
const turnOf = (u: Underlay): number => (-(u.rot ?? 0) * Math.PI) / 180;
|
|
134
|
+
/** An image's middle, inches. */
|
|
135
|
+
export function underlayMid(u: Underlay): { x: number; y: number } {
|
|
136
|
+
return { x: u.x + u.w / 2, y: u.y - underlayH(u) / 2 };
|
|
137
|
+
}
|
|
138
|
+
/** The point `lx` across and `ly` up from an image's middle in its own (unturned) frame, as it sits turned (inches). */
|
|
139
|
+
export function underlayAt(u: Underlay, lx: number, ly: number): { x: number; y: number } {
|
|
140
|
+
const m = underlayMid(u), a = turnOf(u), c = Math.cos(a), s = Math.sin(a);
|
|
141
|
+
return { x: m.x + lx * c - ly * s, y: m.y + lx * s + ly * c };
|
|
142
|
+
}
|
|
143
|
+
/** A point (inches) as across and up from an image's middle, in its own unturned frame. */
|
|
144
|
+
export function underlayLocal(u: Underlay, x: number, y: number): { x: number; y: number } {
|
|
145
|
+
const m = underlayMid(u), a = turnOf(u), c = Math.cos(a), s = Math.sin(a), dx = x - m.x, dy = y - m.y;
|
|
146
|
+
return { x: dx * c + dy * s, y: -dx * s + dy * c };
|
|
147
|
+
}
|
|
148
|
+
/** Where an image's handles sit in its own frame (as fractions of its half width and half height), in boxHandles' order. */
|
|
149
|
+
const HANDLES: [HandlePart, number, number][] = [['nw', -1, 1], ['n', 0, 1], ['ne', 1, 1], ['e', 1, 0], ['se', 1, -1], ['s', 0, -1], ['sw', -1, -1], ['w', -1, 0]];
|
|
150
|
+
/** An image's corners as it sits turned: top left, top right, bottom right, bottom left (inches). */
|
|
151
|
+
export function underlayCorners(u: Underlay): { x: number; y: number }[] {
|
|
152
|
+
const hw = u.w / 2, hh = underlayH(u) / 2;
|
|
153
|
+
return [[-1, 1], [1, 1], [1, -1], [-1, -1]].map(([i, j]) => underlayAt(u, i * hw, j * hh));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Screen positions of an underlay's resize handles: corners scale, side handles stretch. `ang`: how far the image is
|
|
158
|
+
* turned on screen (radians clockwise), for the side handles' bars to lie along its sides.
|
|
159
|
+
*/
|
|
160
|
+
export function underlayHandles(T: Xform, u: Underlay): { part: HandlePart; x: number; y: number; ang: number }[] {
|
|
161
|
+
const hw = u.w / 2, hh = underlayH(u) / 2, ang = ((u.rot ?? 0) * Math.PI) / 180;
|
|
162
|
+
return HANDLES.map(([part, i, j]) => { const p = underlayAt(u, i * hw, j * hh), [x, y] = S(T, p.x, p.y); return { part, x, y, ang }; });
|
|
101
163
|
}
|
|
102
164
|
|
|
103
165
|
/** A world-space box (y up: top > bottom). */
|
|
@@ -120,13 +182,100 @@ export function noteBox(n: Note): Box | null {
|
|
|
120
182
|
return { left: Math.min(...xs), right: Math.max(...xs), top: Math.max(...ys), bottom: Math.min(...ys) };
|
|
121
183
|
}
|
|
122
184
|
|
|
123
|
-
/**
|
|
185
|
+
/**
|
|
186
|
+
* A note's text is set at one size (NOTE_TEXT_SIZE px) and the whole note scaled to its size on the drawing: its
|
|
187
|
+
* letters are `ts` inches tall (NOTE_TEXT_IN unless set, from NOTE_TEXT_MIN_IN to NOTE_TEXT_MAX_IN), so it zooms with
|
|
188
|
+
* the drawing rather than staying one size on screen. (Set at one size, the text is measured — and on a device its
|
|
189
|
+
* font made — once, not at every step of a zoom.)
|
|
190
|
+
*/
|
|
191
|
+
export { NOTE_TEXT_SIZE, NOTE_TEXT_IN, NOTE_TEXT_MIN_IN, NOTE_TEXT_MAX_IN, noteTextIn } from '../model/markup';
|
|
192
|
+
export const noteFont = (size: number): string => '600 ' + size + 'px ' + FONT;
|
|
193
|
+
|
|
194
|
+
/** What measures text: a drawing context, or anything that sets a font and measures with it. */
|
|
195
|
+
export interface TextMeasure { font: string; measureText(s: string): { width: number } }
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* What measures text outside drawing a frame (picking a note, its handles, a box select): the browser's canvas, or on
|
|
199
|
+
* a device — which has no browser canvas — the host's (Skia, with the drawing's own fonts: skia/host.ts), so a note
|
|
200
|
+
* is picked by the same box it's drawn in. Failing both, an estimate rather than a crash.
|
|
201
|
+
*/
|
|
202
|
+
let hostMeasure: TextMeasure | null = null;
|
|
203
|
+
export function setTextMeasure(m: TextMeasure | null): void { hostMeasure = m; }
|
|
204
|
+
const estimate: TextMeasure = {
|
|
205
|
+
font: '',
|
|
206
|
+
measureText(s: string) { const size = Number(/([\d.]+)px/.exec(this.font)?.[1] ?? 14); return { width: s.length * size * 0.6 }; },
|
|
207
|
+
};
|
|
208
|
+
export const textMeasure = (): TextMeasure => cvs.ctx ?? hostMeasure ?? estimate;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* A note's box on screen at its size: left / top corner, width, height; `mid` is the text's middle line, `k` its scale
|
|
212
|
+
* from the unscaled size (padding and corners grow with it). Measured with `c` (the drawing's context when drawing).
|
|
213
|
+
*/
|
|
214
|
+
export function noteTextBox(T: Xform, n: Note, c: TextMeasure = textMeasure()) {
|
|
215
|
+
const size = noteTextIn(n) * T.s, k = size / NOTE_TEXT_SIZE, [x, mid] = S(T, n.x!, n.y!);
|
|
216
|
+
c.font = noteFont(NOTE_TEXT_SIZE);
|
|
217
|
+
const w = (c.measureText(n.text!).width + 16) * k;
|
|
218
|
+
return { x, y: mid - 14 * k, w, h: 28 * k, mid, k, size };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** How much a link's tile shows on the screen: all of it, its picture or icon alone, or a pin. */
|
|
222
|
+
export type LinkLod = 'full' | 'compact' | 'pin';
|
|
223
|
+
|
|
224
|
+
/** A tile on the screen: its square (top left, side), its middle, how much it shows, and its scale from the layout. */
|
|
225
|
+
export interface LinkBox { x: number; y: number; s: number; cx: number; cy: number; lod: LinkLod; k: number }
|
|
226
|
+
|
|
227
|
+
export function linkBox(T: Xform, n: Note): LinkBox {
|
|
228
|
+
const [cx, cy] = S(T, n.x ?? 0, n.y ?? 0), side = linkSide(n) * T.s;
|
|
229
|
+
const lod: LinkLod = side >= LINK_FULL_PX ? 'full' : side >= LINK_PIN_PX ? 'compact' : 'pin';
|
|
230
|
+
const s = Math.max(LINK_PIN_PX, side);
|
|
231
|
+
return { x: cx - s / 2, y: cy - s / 2, s, cx, cy, lod, k: s / LINK_TILE_PX };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Where a leader leaves the tile toward screen point tx, ty: the edge of its square (null: the point's under it). */
|
|
235
|
+
export function leaderStart(b: LinkBox, tx: number, ty: number): [number, number] | null {
|
|
236
|
+
const dx = tx - b.cx, dy = ty - b.cy, h = b.s / 2, m = Math.max(Math.abs(dx), Math.abs(dy));
|
|
237
|
+
if (m <= h) return null;
|
|
238
|
+
return [b.cx + (dx * h) / m, b.cy + (dy * h) / m];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** A tile's leader on the screen, from the tile's edge to its tip (null: none, or its tip is under the tile). */
|
|
242
|
+
export function leaderPath(T: Xform, n: Note, b: LinkBox = linkBox(T, n)): [[number, number], [number, number]] | null {
|
|
243
|
+
if (!n.leader) return null;
|
|
244
|
+
const tip = S(T, n.leader[0], n.leader[1]), start = leaderStart(b, tip[0], tip[1]);
|
|
245
|
+
return start ? [start, tip] : null;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** A tile's handle: its leader's tip (tiles are sized together, by the Tile size slider, not one by one). */
|
|
249
|
+
export function linkHandles(T: Xform, n: Note): { part: string; x: number; y: number }[] {
|
|
250
|
+
if (!n.leader) return [];
|
|
251
|
+
const [x, y] = S(T, n.leader[0], n.leader[1]);
|
|
252
|
+
return [{ part: 'tip', x, y }];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** A tile's box and leader tip on the screen, for fitting and box selection: left, top, right, bottom. */
|
|
256
|
+
export function linkExtent(T: Xform, n: Note): [number, number, number, number] {
|
|
257
|
+
const b = linkBox(T, n);
|
|
258
|
+
let l = b.x, t = b.y, r = b.x + b.s, bt = b.y + b.s;
|
|
259
|
+
if (n.leader) { const [x, y] = S(T, n.leader[0], n.leader[1]); l = Math.min(l, x); r = Math.max(r, x); t = Math.min(t, y); bt = Math.max(bt, y); }
|
|
260
|
+
return [l, t, r, bt];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Handles for adjusting markup: a line's or arrow's ends ('p0', 'p1'), the box around a cloud, shape or pen stroke, a note's corners. */
|
|
124
264
|
export function noteHandles(T: Xform, n: Note): { part: string; x: number; y: number }[] {
|
|
125
|
-
if (n.type === '
|
|
265
|
+
if (n.type === 'link') return linkHandles(T, n);
|
|
266
|
+
if (isSegment(n) && n.pts && n.pts.length >= 2) {
|
|
126
267
|
const last = n.pts.length - 1;
|
|
127
268
|
return [{ part: 'p0', p: n.pts[0] }, { part: 'p' + last, p: n.pts[last] }].map(({ part, p }) => { const [x, y] = S(T, p[0], p[1]); return { part, x, y }; });
|
|
128
269
|
}
|
|
129
|
-
if (n.type === 'cloud' || n.type === 'pen') { const b = noteBox(n); return b ? boxHandles(T, b) : []; }
|
|
270
|
+
if (n.type === 'cloud' || n.type === 'pen' || CLOSED_SHAPES.has(n.type)) { const b = noteBox(n); return b ? boxHandles(T, b) : []; }
|
|
271
|
+
if (n.type === 'text' && n.text) {
|
|
272
|
+
// a note scales as a whole (its text can't stretch): corners only
|
|
273
|
+
const b = noteTextBox(T, n);
|
|
274
|
+
return [
|
|
275
|
+
{ part: 'nw', x: b.x, y: b.y }, { part: 'ne', x: b.x + b.w, y: b.y },
|
|
276
|
+
{ part: 'se', x: b.x + b.w, y: b.y + b.h }, { part: 'sw', x: b.x, y: b.y + b.h },
|
|
277
|
+
];
|
|
278
|
+
}
|
|
130
279
|
return [];
|
|
131
280
|
}
|
|
132
281
|
|
|
@@ -142,7 +291,7 @@ const browserImage: ImageLoader = (src, onLoad) => {
|
|
|
142
291
|
};
|
|
143
292
|
let loadImage: ImageLoader = browserImage;
|
|
144
293
|
/** Load pictures some other way from now on (null: the browser's). */
|
|
145
|
-
export function setImageLoader(fn: ImageLoader | null): void { loadImage = fn || browserImage; imgs.clear(); }
|
|
294
|
+
export function setImageLoader(fn: ImageLoader | null): void { loadImage = fn || browserImage; imgs.clear(); thumbs.clear(); }
|
|
146
295
|
|
|
147
296
|
/** Turns a stored reference (`stored:…`, kept by a drawing in place of a URL) into a URL to load. */
|
|
148
297
|
let resolveStored: ((ref: string) => Promise<string>) | null = null;
|
|
@@ -155,6 +304,31 @@ export function aliasImage(from: string, to: string): void {
|
|
|
155
304
|
const im = imgs.get(from);
|
|
156
305
|
if (im && !imgs.has(to)) imgs.set(to, im);
|
|
157
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Small pictures of linked files (a link tile's), by address: kept apart from the page's photos and plans, and only the
|
|
309
|
+
* last few dozen used — a drawing can link many — each drawn again as it arrives. What's let go goes between frames,
|
|
310
|
+
* and never what the last frame drew (so many on the screen at once don't load each other out, again and again).
|
|
311
|
+
*/
|
|
312
|
+
const thumbs = new Map<string, { im: ImageLike; used: number }>(), THUMBS_KEPT = 64;
|
|
313
|
+
let thumbFrame = 0;
|
|
314
|
+
/** A new frame is being drawn: let go of the least recently used past those kept, if the last frame didn't draw them. */
|
|
315
|
+
export function nextThumbFrame(): void {
|
|
316
|
+
for (const [k, o] of thumbs) {
|
|
317
|
+
if (thumbs.size <= THUMBS_KEPT || o.used === thumbFrame) break;
|
|
318
|
+
thumbs.delete(k);
|
|
319
|
+
}
|
|
320
|
+
thumbFrame++;
|
|
321
|
+
}
|
|
322
|
+
export function linkThumb(src: string): ImageLike {
|
|
323
|
+
let t = thumbs.get(src);
|
|
324
|
+
// (the most recently used last: the first is the one to let go)
|
|
325
|
+
if (t) thumbs.delete(src);
|
|
326
|
+
else t = { im: loadImage(src, () => requestDraw()), used: 0 };
|
|
327
|
+
t.used = thumbFrame;
|
|
328
|
+
thumbs.set(src, t);
|
|
329
|
+
return t.im;
|
|
330
|
+
}
|
|
331
|
+
|
|
158
332
|
export function imgCache(src: string | undefined): ImageLike | null {
|
|
159
333
|
if (!src) return null;
|
|
160
334
|
let im = imgs.get(src);
|
|
@@ -178,15 +352,77 @@ let drawFrame = 0;
|
|
|
178
352
|
let paint: () => void = () => draw();
|
|
179
353
|
export function setFrameRenderer(fn: (() => void) | null): void { paint = fn || (() => draw()); }
|
|
180
354
|
|
|
181
|
-
|
|
182
|
-
|
|
355
|
+
/*
|
|
356
|
+
What the next frame has to redraw, for a host that keeps the drawing as a picture (see PlanCanvas.native): the
|
|
357
|
+
drawing itself only when something in it changed (requestDraw), or when the view moved (the host compares the view
|
|
358
|
+
it last drew at); otherwise just the moving parts, as a tool follows the pointer (requestOverlayDraw). The page's
|
|
359
|
+
photos and plans are kept apart, under it: turning, fading, moving or sizing one redraws only them
|
|
360
|
+
(requestImageDraw), not every wall and dimension over them. A host that redraws everything each frame (the
|
|
361
|
+
browser) ignores this.
|
|
362
|
+
*/
|
|
363
|
+
const frame = { drawingChanged: true, imagesChanged: true };
|
|
364
|
+
|
|
365
|
+
/** Whether the drawing (not only its moving parts) has changed since the host last kept it; the host then marks it kept. */
|
|
366
|
+
export const drawingChanged = (): boolean => frame.drawingChanged;
|
|
367
|
+
export function markDrawingKept(): void { frame.drawingChanged = false; }
|
|
368
|
+
/** Whether the photos and plans under the drawing have changed since the host last kept them; it then marks them kept. */
|
|
369
|
+
export const imagesChanged = (): boolean => frame.imagesChanged;
|
|
370
|
+
export function markImagesKept(): void { frame.imagesChanged = false; }
|
|
371
|
+
|
|
372
|
+
function schedule(): void {
|
|
183
373
|
if (drawFrame) return;
|
|
184
374
|
drawFrame = requestAnimationFrame(() => { drawFrame = 0; paint(); });
|
|
185
375
|
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Paint a frame that's waiting, now. A press is tested against the tap targets as last painted (the chips), so one that
|
|
379
|
+
* comes between an edit and the next frame — a quick tap, a slow frame — would land on where a chip was, not where it is.
|
|
380
|
+
*/
|
|
381
|
+
export function paintPending(): void {
|
|
382
|
+
if (!drawFrame) return;
|
|
383
|
+
cancelAnimationFrame(drawFrame);
|
|
384
|
+
drawFrame = 0;
|
|
385
|
+
paint();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* The view as the host moved it itself — a device pans and zooms on its UI thread, at the screen's rate, and tells
|
|
390
|
+
* this thread where it got to: take it, and draw the plan there on the next frame.
|
|
391
|
+
*/
|
|
392
|
+
export function setViewFromHost(s: number, cx: number, cy: number): void {
|
|
393
|
+
const v = V();
|
|
394
|
+
v.s = s; v.cx = cx; v.cy = cy;
|
|
395
|
+
schedule();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Repaint on the next animation frame (several requests in one frame paint once). */
|
|
399
|
+
export function requestDraw(): void {
|
|
400
|
+
frame.drawingChanged = true;
|
|
401
|
+
frame.imagesChanged = true;
|
|
402
|
+
schedule();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Only the page's photos and plans changed (one turned, faded, moved or sized): the drawing over them can be reused. */
|
|
406
|
+
export function requestImageDraw(): void {
|
|
407
|
+
frame.imagesChanged = true;
|
|
408
|
+
schedule();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Only the moving parts changed (a tool's preview, the snap marker, the selection box): the rest can be reused. */
|
|
412
|
+
export function requestOverlayDraw(): void { schedule(); }
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* The view panned or zoomed: draw the next frame there. The drawing itself didn't change — a host that keeps it sees
|
|
416
|
+
* the view moved, and draws it again at the new one, with nothing waiting for the view to settle first.
|
|
417
|
+
*/
|
|
418
|
+
function requestViewDraw(): void { schedule(); }
|
|
186
419
|
onSessionReset(() => {
|
|
187
420
|
if (drawFrame) cancelAnimationFrame(drawFrame);
|
|
188
421
|
drawFrame = 0;
|
|
422
|
+
frame.drawingChanged = true;
|
|
423
|
+
frame.imagesChanged = true;
|
|
189
424
|
imgs.clear();
|
|
425
|
+
thumbs.clear();
|
|
190
426
|
});
|
|
191
427
|
|
|
192
428
|
/** The plan's size in display points, where no browser canvas measures it (a device). */
|