@reekon-tools/boldr-cad 1.3.0 → 1.5.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 +21 -4
- package/dist/app/edit.d.ts +3 -3
- package/dist/app/edit.js +26 -1
- package/dist/app/edit.js.map +1 -1
- package/dist/app/state.d.ts +3 -5
- package/dist/app/state.js +0 -1
- package/dist/app/state.js.map +1 -1
- package/dist/data/sync.d.ts +1 -1
- package/dist/data/sync.js +7 -2
- package/dist/data/sync.js.map +1 -1
- package/dist/editor/actions.d.ts +8 -0
- package/dist/editor/actions.js +149 -169
- package/dist/editor/actions.js.map +1 -1
- package/dist/editor/elevation.d.ts +4 -1
- package/dist/editor/elevation.js +16 -17
- package/dist/editor/elevation.js.map +1 -1
- package/dist/editor/gestures.d.ts +21 -0
- package/dist/editor/gestures.js +92 -14
- package/dist/editor/gestures.js.map +1 -1
- package/dist/editor/keys.js +2 -2
- package/dist/editor/keys.js.map +1 -1
- package/dist/editor/linkActions.d.ts +22 -3
- package/dist/editor/linkActions.js +95 -13
- package/dist/editor/linkActions.js.map +1 -1
- package/dist/editor/mode.js +4 -3
- package/dist/editor/mode.js.map +1 -1
- package/dist/editor/project.js +1 -2
- package/dist/editor/project.js.map +1 -1
- package/dist/editor/scaleGate.d.ts +13 -0
- package/dist/editor/scaleGate.js +27 -0
- package/dist/editor/scaleGate.js.map +1 -0
- package/dist/editor/selection.js +13 -9
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/tooltips.js +9 -0
- package/dist/editor/tooltips.js.map +1 -1
- package/dist/editor/view3d.d.ts +4 -1
- package/dist/editor/view3d.js +6 -2
- package/dist/editor/view3d.js.map +1 -1
- package/dist/export/pdf.d.ts +5 -2
- package/dist/export/pdf.js +12 -8
- package/dist/export/pdf.js.map +1 -1
- package/dist/export/plan.d.ts +1 -1
- package/dist/export/plan.js +14 -9
- package/dist/export/plan.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/model/demo.d.ts +1 -0
- package/dist/model/demo.js +2 -1
- package/dist/model/demo.js.map +1 -1
- package/dist/model/elevation.d.ts +1 -1
- package/dist/model/elevation.js +3 -3
- package/dist/model/elevation.js.map +1 -1
- package/dist/model/history.js +4 -1
- package/dist/model/history.js.map +1 -1
- package/dist/model/links.d.ts +4 -1
- package/dist/model/links.js +9 -2
- package/dist/model/links.js.map +1 -1
- package/dist/model/measurement.d.ts +2 -2
- package/dist/model/measurement.js +3 -3
- package/dist/model/measurement.js.map +1 -1
- package/dist/model/openings.d.ts +10 -0
- package/dist/model/openings.js +18 -0
- package/dist/model/openings.js.map +1 -1
- package/dist/model/planGeom.d.ts +37 -2
- package/dist/model/planGeom.js +72 -9
- package/dist/model/planGeom.js.map +1 -1
- package/dist/model/project.js +5 -2
- package/dist/model/project.js.map +1 -1
- package/dist/model/rooms.d.ts +18 -5
- package/dist/model/rooms.js +178 -73
- package/dist/model/rooms.js.map +1 -1
- package/dist/model/scale.d.ts +30 -0
- package/dist/model/scale.js +120 -0
- package/dist/model/scale.js.map +1 -0
- package/dist/model/settings.d.ts +0 -1
- package/dist/model/settings.js +1 -1
- package/dist/model/settings.js.map +1 -1
- package/dist/model/sketch.d.ts +21 -1
- package/dist/model/sketch.js +58 -4
- package/dist/model/sketch.js.map +1 -1
- package/dist/model/stroke.js +2 -2
- package/dist/model/types.d.ts +38 -7
- package/dist/react/BottomBar.d.ts +2 -1
- package/dist/react/BottomBar.js +94 -25
- package/dist/react/BottomBar.js.map +1 -1
- package/dist/react/Drawer.js +3 -2
- package/dist/react/Drawer.js.map +1 -1
- package/dist/react/Duck.js.map +1 -1
- package/dist/react/KeypadSheet.js.map +1 -1
- package/dist/react/MarkupPanel.js +1 -1
- package/dist/react/PlanCanvas.native.js +54 -7
- package/dist/react/PlanCanvas.native.js.map +1 -1
- package/dist/react/Popovers.js +14 -3
- package/dist/react/Popovers.js.map +1 -1
- package/dist/react/Rail.js +1 -1
- package/dist/react/Sheet.d.ts +2 -2
- package/dist/react/Sheet.js.map +1 -1
- package/dist/react/barScroll.d.ts +23 -0
- package/dist/react/barScroll.js +47 -0
- package/dist/react/barScroll.js.map +1 -0
- package/dist/react/corners.d.ts +6 -2
- package/dist/react/corners.js.map +1 -1
- package/dist/react/host.d.ts +7 -0
- package/dist/react/host.js +14 -1
- package/dist/react/host.js.map +1 -1
- package/dist/react/motion.d.ts +1 -1
- package/dist/react/motion.js +1 -1
- package/dist/react/motion.js.map +1 -1
- package/dist/render/draw.d.ts +21 -4
- package/dist/render/draw.js +184 -149
- package/dist/render/draw.js.map +1 -1
- package/dist/render/elevation.js +3 -3
- package/dist/render/elevation.js.map +1 -1
- package/dist/render/figmaIcons.d.ts +6 -0
- package/dist/render/figmaIcons.js +16 -1
- package/dist/render/figmaIcons.js.map +1 -1
- package/dist/render/hit.d.ts +8 -1
- package/dist/render/hit.js +23 -4
- package/dist/render/hit.js.map +1 -1
- package/dist/render/icons.js +27 -20
- package/dist/render/icons.js.map +1 -1
- package/dist/render/lens.d.ts +27 -0
- package/dist/render/lens.js +41 -0
- package/dist/render/lens.js.map +1 -0
- package/dist/render/linkTile.d.ts +20 -2
- package/dist/render/linkTile.js +476 -246
- package/dist/render/linkTile.js.map +1 -1
- package/dist/render/snap.d.ts +5 -1
- package/dist/render/snap.js +3 -1
- package/dist/render/snap.js.map +1 -1
- package/dist/render/svgPath.d.ts +29 -0
- package/dist/render/svgPath.js +114 -0
- package/dist/render/svgPath.js.map +1 -0
- package/dist/render/tileIcons.d.ts +18 -0
- package/dist/render/tileIcons.js +67 -0
- package/dist/render/tileIcons.js.map +1 -0
- package/dist/render/view.d.ts +18 -1
- package/dist/render/view.js +31 -4
- package/dist/render/view.js.map +1 -1
- package/dist/skia/SkiaDrawCtx.d.ts +8 -0
- package/dist/skia/SkiaDrawCtx.js +27 -5
- package/dist/skia/SkiaDrawCtx.js.map +1 -1
- package/dist/solver/equations.d.ts +6 -0
- package/dist/solver/equations.js +23 -6
- package/dist/solver/equations.js.map +1 -1
- package/dist/solver/gauss-newton.js +6 -1
- package/dist/solver/gauss-newton.js.map +1 -1
- package/dist/tools/annotate.d.ts +0 -2
- package/dist/tools/annotate.js +1 -22
- package/dist/tools/annotate.js.map +1 -1
- package/dist/tools/chamfer.js +4 -0
- package/dist/tools/chamfer.js.map +1 -1
- package/dist/tools/dim.d.ts +3 -3
- package/dist/tools/dim.js +26 -65
- package/dist/tools/dim.js.map +1 -1
- package/dist/tools/fillet.js +4 -0
- package/dist/tools/fillet.js.map +1 -1
- package/dist/tools/freehand.js +1 -4
- package/dist/tools/freehand.js.map +1 -1
- package/dist/tools/line.js +15 -11
- package/dist/tools/line.js.map +1 -1
- package/dist/tools/modify.js +6 -1
- package/dist/tools/modify.js.map +1 -1
- package/dist/tools/registry.d.ts +5 -0
- package/dist/tools/registry.js +6 -1
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/select.d.ts +19 -0
- package/dist/tools/select.js +116 -17
- package/dist/tools/select.js.map +1 -1
- package/dist/tools/shapes.d.ts +1 -1
- package/dist/tools/shapes.js +12 -5
- package/dist/tools/shapes.js.map +1 -1
- package/dist/tools/survey.d.ts +6 -0
- package/dist/tools/survey.js +28 -0
- package/dist/tools/survey.js.map +1 -1
- package/dist/view3d/build.js +9 -8
- package/dist/view3d/build.js.map +1 -1
- package/package.json +9 -9
- package/src/app/edit.ts +25 -1
- package/src/app/state.ts +3 -5
- package/src/data/sync.ts +7 -2
- package/src/editor/actions.ts +126 -112
- package/src/editor/elevation.ts +13 -16
- package/src/editor/gestures.ts +83 -13
- package/src/editor/keys.ts +2 -2
- package/src/editor/linkActions.ts +90 -13
- package/src/editor/mode.ts +4 -3
- package/src/editor/project.ts +1 -2
- package/src/editor/scaleGate.ts +32 -0
- package/src/editor/selection.ts +13 -7
- package/src/editor/tooltips.ts +8 -0
- package/src/editor/view3d.ts +6 -2
- package/src/export/pdf.ts +7 -4
- package/src/export/plan.ts +14 -10
- package/src/index.ts +1 -1
- package/src/model/demo.ts +2 -1
- package/src/model/elevation.ts +4 -4
- package/src/model/history.ts +4 -1
- package/src/model/links.ts +8 -2
- package/src/model/measurement.ts +3 -3
- package/src/model/openings.ts +18 -0
- package/src/model/planGeom.ts +70 -10
- package/src/model/project.ts +6 -2
- package/src/model/rooms.ts +151 -63
- package/src/model/scale.ts +105 -0
- package/src/model/settings.ts +1 -2
- package/src/model/sketch.ts +48 -4
- package/src/model/stroke.ts +2 -2
- package/src/model/types.ts +38 -7
- package/src/react/BottomBar.tsx +114 -29
- package/src/react/Drawer.tsx +14 -10
- package/src/react/Duck.tsx +3 -3
- package/src/react/KeypadSheet.tsx +2 -2
- package/src/react/MarkupPanel.tsx +1 -1
- package/src/react/PlanCanvas.native.tsx +60 -6
- package/src/react/Popovers.tsx +24 -9
- package/src/react/Rail.tsx +1 -1
- package/src/react/Sheet.tsx +3 -3
- package/src/react/barScroll.ts +57 -0
- package/src/react/corners.ts +6 -3
- package/src/react/host.ts +14 -1
- package/src/react/motion.tsx +6 -6
- package/src/render/draw.ts +156 -116
- package/src/render/elevation.ts +5 -4
- package/src/render/figmaIcons.ts +17 -1
- package/src/render/hit.ts +21 -4
- package/src/render/icons.ts +27 -20
- package/src/render/lens.ts +52 -0
- package/src/render/linkTile.ts +436 -190
- package/src/render/snap.ts +7 -2
- package/src/render/svgPath.ts +95 -0
- package/src/render/tileIcons.ts +75 -0
- package/src/render/view.ts +37 -5
- package/src/skia/SkiaDrawCtx.ts +26 -4
- package/src/solver/equations.ts +22 -6
- package/src/solver/gauss-newton.ts +4 -1
- package/src/styles.css +3 -2
- package/src/tools/annotate.ts +1 -18
- package/src/tools/chamfer.ts +3 -0
- package/src/tools/dim.ts +26 -52
- package/src/tools/fillet.ts +3 -0
- package/src/tools/freehand.ts +1 -3
- package/src/tools/line.ts +12 -11
- package/src/tools/modify.ts +5 -1
- package/src/tools/registry.ts +10 -1
- package/src/tools/select.ts +99 -18
- package/src/tools/shapes.ts +9 -4
- package/src/tools/survey.ts +21 -0
- package/src/ui/ctx.ts +4 -3
- package/src/ui/drawer.ts +8 -2
- package/src/view3d/build.ts +11 -10
package/src/render/elevation.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { cabPoints, cornerAnchor, emptyFace, openingAnchor } from '../model/face
|
|
|
5
5
|
import { edgeS, openingSpan, pickBetween, refS, sameRef, widthEntered } from '../model/openings';
|
|
6
6
|
import type { Constraint, OpeningDim, OpeningEdge, OpeningRef, Sketch, WallFace } from '../model/types';
|
|
7
7
|
import { fmtLen } from '../units';
|
|
8
|
-
import { chip, linkBlue,
|
|
8
|
+
import { chip, linkBlue, linkIcon } from './draw';
|
|
9
9
|
import { FONT, TH } from './theme';
|
|
10
10
|
import { S, type Xform } from './view';
|
|
11
11
|
import type { DrawCtx } from './ctx';
|
|
@@ -35,7 +35,8 @@ export interface ElevHit { x: number; y: number; w: number; h: number; act: Elev
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Chip look: locked blue, reference grey in parentheses, unlocked (just measured) neutral grey, red when it can't be met,
|
|
38
|
-
* and 'linked': locked, but set in another view (the floor plan) — an outlined blue chip with
|
|
38
|
+
* and 'linked': locked, but set in another view (the floor plan) — an outlined blue chip with the link icon, read-only
|
|
39
|
+
* here. (Not a linked measurement's chip: that's one of the job's, set from outside the drawing — chip()'s `mark`.)
|
|
39
40
|
*/
|
|
40
41
|
type Look = 'drive' | 'ref' | 'free' | 'bad' | 'linked';
|
|
41
42
|
const LOOK = (look: Look) => look === 'drive' ? { bg: TH.blue, fg: '#fff', line: TH.blue } : look === 'bad' ? { bg: TH.red, fg: '#fff', line: TH.red }
|
|
@@ -75,7 +76,7 @@ function dim(c: C2D, p: [number, number], q: [number, number], nx: number, ny: n
|
|
|
75
76
|
// a number that holds nothing (dashed) reads in parentheses, as in the plan
|
|
76
77
|
const linked = look === 'linked', text = dashed ? '(' + label + ')' : linked ? ' ' + label : label; // (figure spaces: room for the link mark)
|
|
77
78
|
const w = chip(c, text, cx, cy, { bg: st.bg, fg: st.fg, font: '600 11.5px', h, pad: 6, border: selected ? [TH.yellow, 2] : linked ? [st.line, 1.5] : null });
|
|
78
|
-
if (linked)
|
|
79
|
+
if (linked) linkIcon(c, cx - w / 2 + 11, cy, 10, st.fg);
|
|
79
80
|
return { x: cx - w / 2 - 4, y: cy - h / 2 - 4, w: w + 8, h: h + 8 };
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -433,7 +434,7 @@ export function drawWallChips(c: C2D, T: Xform, sk: Sketch, f: ElevFrame, face:
|
|
|
433
434
|
}
|
|
434
435
|
next += lostRows.length;
|
|
435
436
|
// the wall's length comes from the floor plan: linked (outlined blue) when a dimension locks it there, grey when free
|
|
436
|
-
const planLocked = Object.values(sk.cons).some(cn => cn.type === 'dist' && cn.line === f.lid && cn.driving !== false
|
|
437
|
+
const planLocked = Object.values(sk.cons).some(cn => cn.type === 'dist' && cn.line === f.lid && cn.driving !== false);
|
|
437
438
|
const anyRow = rows.some(r => r.length) || next > 1;
|
|
438
439
|
tag(S(T, f.face[0], 0), S(T, f.face[1], 0), 0, anyRow ? ROW0 + ROW * next : ROW0, fmtLen(f.face[1] - f.face[0]), planLocked ? 'linked' : 'free', { k: 'len' });
|
|
439
440
|
// the wall's height: blue once entered, grey while it's the default
|
package/src/render/figmaIcons.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
Icons drawn in a Figma frame, kept exactly as Figma exports them (their own size, viewBox and colours) rather than
|
|
3
|
-
redrawn on the rail's 24 grid: the Pages panel's Delete and + Page (Figma 2548:101989)
|
|
3
|
+
redrawn on the rail's 24 grid: the Pages panel's Delete and + Page (Figma 2548:101989), and the link icon on a
|
|
4
|
+
linked measurement's chip (Figma 2795:77149), which the canvas draws from its paths (render/svgPath.ts).
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
/** Delete page: a trash can, white, for the red pill. */
|
|
@@ -20,3 +21,18 @@ export const PLUS_SVG = `<svg preserveAspectRatio="none" overflow="visible" styl
|
|
|
20
21
|
<path id="Vector" d="M9.25 0.125C9.58152 0.125 9.89937 0.256791 10.1338 0.491211C10.3682 0.725631 10.5 1.04348 10.5 1.375V8H17.125C17.4565 8 17.7744 8.13179 18.0088 8.36621C18.2432 8.60063 18.375 8.91848 18.375 9.25C18.375 9.58152 18.2432 9.89937 18.0088 10.1338C17.7744 10.3682 17.4565 10.5 17.125 10.5H10.5V17.125C10.5 17.4565 10.3682 17.7744 10.1338 18.0088C9.89937 18.2432 9.58152 18.375 9.25 18.375C8.91848 18.375 8.60063 18.2432 8.36621 18.0088C8.13179 17.7744 8 17.4565 8 17.125V10.5H1.375C1.04348 10.5 0.725631 10.3682 0.491211 10.1338C0.256791 9.89937 0.125 9.58152 0.125 9.25C0.125 8.91848 0.256791 8.60063 0.491211 8.36621C0.725631 8.13179 1.04348 8 1.375 8H8V1.375C8 1.04348 8.13179 0.725631 8.36621 0.491211C8.60063 0.256791 8.91848 0.125 9.25 0.125Z" fill="#181A1C" stroke="#181A1C" stroke-width="0.25"/>
|
|
21
22
|
</svg>`;
|
|
22
23
|
export const PLUS_SIZE = { width: 18.5, height: 18.5 };
|
|
24
|
+
|
|
25
|
+
/** Link: a chain link, dark grey, for the white badge on a linked measurement's chip. */
|
|
26
|
+
export const LINK_SVG = `<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
27
|
+
<g id="link-svgrepo-com (2) 1" clip-path="url(#clip0_0_4)">
|
|
28
|
+
<path id="Vector" d="M5.28769 1.15165C6.02508 0.414261 7.02519 0 8.06805 0C10.2396 0 12 1.76041 12 3.93198C12 4.97481 11.5858 5.97492 10.8484 6.71231L9.31065 8.25L8.25 7.18934L9.78772 5.65165C10.2438 5.19557 10.5 4.57698 10.5 3.93198C10.5 2.58884 9.41115 1.5 8.06805 1.5C7.42302 1.5 6.80444 1.75622 6.34835 2.21231L4.81066 3.75L3.75 2.68934L5.28769 1.15165Z" fill="#181A1C"/>
|
|
29
|
+
<path id="Vector_2" d="M5.65165 9.78772L7.18934 8.25L8.25 9.31065L6.71231 10.8484C5.97492 11.5858 4.97481 12 3.93198 12C1.76041 12 0 10.2396 0 8.06805C0 7.0252 0.41426 6.02508 1.15165 5.28769L2.68934 3.75L3.75 4.81066L2.21231 6.34835C1.75622 6.80444 1.5 7.42302 1.5 8.06805C1.5 9.41115 2.58884 10.5 3.93198 10.5C4.57698 10.5 5.19557 10.2438 5.65165 9.78772Z" fill="#181A1C"/>
|
|
30
|
+
<path id="Vector_3" d="M4.28039 8.78136L8.78038 4.28137L7.71973 3.2207L3.21973 7.72071L4.28039 8.78136Z" fill="#181A1C"/>
|
|
31
|
+
</g>
|
|
32
|
+
<defs>
|
|
33
|
+
<clipPath id="clip0_0_4">
|
|
34
|
+
<rect width="12" height="12" fill="white"/>
|
|
35
|
+
</clipPath>
|
|
36
|
+
</defs>
|
|
37
|
+
</svg>`;
|
|
38
|
+
export const LINK_SIZE = { width: 12, height: 12 };
|
package/src/render/hit.ts
CHANGED
|
@@ -6,12 +6,12 @@ import { roomValid } from '../model/sketch';
|
|
|
6
6
|
import { page, SK } from '../model/store';
|
|
7
7
|
import { edgePoint } from '../model/openings';
|
|
8
8
|
import { elevOpen, wallBodyAt } from '../editor/elevation';
|
|
9
|
-
import type { Note, OpeningEdge } from '../model/types';
|
|
9
|
+
import type { LinkTarget, Note, OpeningEdge, Underlay } from '../model/types';
|
|
10
10
|
import { cabinetFootprints } from '../model/facesolve';
|
|
11
11
|
import { arcAngles, curveFrame, edgeId, OPENING_TICK_EXT, openingFrame, wallThick } from '../model/planGeom';
|
|
12
12
|
import { noteLook, shapeOutline } from '../model/markup';
|
|
13
13
|
import { smoothPolyline } from '../model/smooth';
|
|
14
|
-
import { leaderPath, linkBox, mainT, noteHandles, noteTextBox, S, s2w, underlayH, underlayHandles, underlayLocal, type Xform } from './view';
|
|
14
|
+
import { imageLinkBadge, leaderPath, linkBox, mainT, noteHandles, noteTextBox, S, s2w, underlayH, underlayHandles, underlayLocal, type Xform } from './view';
|
|
15
15
|
|
|
16
16
|
/** The handle closest to (sx, sy) within `tol` px (small boxes put handles close together; take the nearest, not the first). */
|
|
17
17
|
function nearestHandle<H extends { x: number; y: number }>(hs: H[], sx: number, sy: number, tol: number): H | null {
|
|
@@ -42,6 +42,20 @@ export function linkAt(sx: number, sy: number, touch = false): (Note & { link: N
|
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* The expanded photo whose badge is under a screen point (its link icon, in its corner: a tap opens what it's for —
|
|
47
|
+
* a view-only one too), the top one. The badges are drawn over every image, so they're picked before any.
|
|
48
|
+
*/
|
|
49
|
+
export function imageLinkAt(sx: number, sy: number, touch = false): (Underlay & { link: LinkTarget }) | null {
|
|
50
|
+
if (!settings.layers.underlay || elevOpen()) return null;
|
|
51
|
+
const T = mainT(), ims = page().underlays;
|
|
52
|
+
for (let i = ims.length - 1; i >= 0; i--) {
|
|
53
|
+
const b = imageLinkBadge(T, ims[i]);
|
|
54
|
+
if (b && hyp(sx - b.x, sy - b.y) <= b.r + (touch ? 6 : 2)) return ims[i] as Underlay & { link: LinkTarget };
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
function notePointHit(n: Note, sx: number, sy: number, T: Xform, tol: number): boolean {
|
|
46
60
|
if (n.type === 'link') return !!linkHit(T, n, sx, sy, tol);
|
|
47
61
|
if (n.type === 'text') {
|
|
@@ -129,6 +143,9 @@ export function hitAny(sx: number, sy: number, { touch = false, only = null as s
|
|
|
129
143
|
if (notePointHit(n, sx, sy, T, tol)) return { type: 'note', id: n.id };
|
|
130
144
|
}
|
|
131
145
|
if (!only) {
|
|
146
|
+
// an expanded photo's badge: a tap opens what it's for, a drag moves the photo
|
|
147
|
+
const lu = imageLinkAt(sx, sy, touch);
|
|
148
|
+
if (lu) return { type: 'underlay', id: lu.id, part: 'link' };
|
|
132
149
|
// the topmost image under the point
|
|
133
150
|
const [wx, wy] = s2w(sx, sy);
|
|
134
151
|
for (let i = ims.length - 1; i >= 0; i--) {
|
|
@@ -145,7 +162,7 @@ export function hitAny(sx: number, sy: number, { touch = false, only = null as s
|
|
|
145
162
|
const h = chips[i];
|
|
146
163
|
if (h.weak) continue;
|
|
147
164
|
if (sx >= h.x && sx <= h.x + h.w && sy >= h.y && sy <= h.y + h.h) {
|
|
148
|
-
if (h.kind === 'dim'
|
|
165
|
+
if (h.kind === 'dim') return { type: 'dim', id: h.id };
|
|
149
166
|
if (h.kind === 'cons') return { type: 'cons', id: h.id };
|
|
150
167
|
if (h.kind === 'odim') return { type: 'odim', id: h.id, part: h.part };
|
|
151
168
|
if (h.kind === 'room') return { type: 'room', id: h.id };
|
|
@@ -219,7 +236,7 @@ export function hitAny(sx: number, sy: number, { touch = false, only = null as s
|
|
|
219
236
|
const [x, y] = S(T, g.o.x, g.o.y);
|
|
220
237
|
const [wx, wy] = s2w(sx, sy);
|
|
221
238
|
const th = Math.atan2(wy - g.o.y, wx - g.o.x);
|
|
222
|
-
const sweep = normAng(g.t1 - g.t0), at = normAng(th - g.t0);
|
|
239
|
+
const sweep = g.full ? 2 * Math.PI : normAng(g.t1 - g.t0), at = normAng(th - g.t0);
|
|
223
240
|
const tw = sk.arcs[id].kind === 'wall' ? Math.max(0, ((sk.arcs[id].thick || 4.5) * T.s) / 2 - tol / 2) : 0;
|
|
224
241
|
if (at <= sweep) consider('arc', id, Math.abs(hyp(x - sx, y - sy) - g.r * T.s) - tw);
|
|
225
242
|
}
|
package/src/render/icons.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
/*
|
|
1
|
+
/*
|
|
2
|
+
24px stroke icons (currentColor). The bottom bar's are Figma's bar icon library ("BOTTOM TOOL BAR - CONSTRAINTS",
|
|
3
|
+
2528:101128: the user's call, 2026-10-27, "update icons"), drawn in one ink so they read in each of the bar's states
|
|
4
|
+
(white; dark on a toggle that's on; red when locked). Where Figma draws a part in its lighter grey (#E8EAED), it's
|
|
5
|
+
that ink at 0.88 here, and its grey fill (#626364) the ink at 0.17.
|
|
6
|
+
*/
|
|
2
7
|
export const IC: Record<string, string> = {
|
|
3
8
|
select: '<path d="M6 3.5l12.5 7.2-5.6 1.4-2.6 5.4z"/>',
|
|
4
9
|
line: '<path d="M6.6 17.4L17.4 6.6"/><circle cx="5" cy="19" r="2"/><circle cx="19" cy="5" r="2"/>',
|
|
@@ -9,17 +14,17 @@ export const IC: Record<string, string> = {
|
|
|
9
14
|
// a corner cut off straight, its corners sharp (Figma 2523:101121)
|
|
10
15
|
chamfer: '<path d="M2 22V11.79L9.45 2H22" stroke-linejoin="miter"/>',
|
|
11
16
|
// a corner of wall as drawn in plan: an L-shaped band, hatched (not a box, so it isn't taken for the Rectangle)
|
|
12
|
-
wall: '<path d="M4
|
|
17
|
+
wall: '<path d="M4 20V4H20V9H9V20H4Z"/><path d="M4 16.5L9 11.5M4 10.5L10.5 4M12.5 9L17.5 4" stroke-width="1"/>',
|
|
13
18
|
rect: '<rect x="4.5" y="6" width="15" height="12" rx="1"/>',
|
|
14
19
|
circle: '<circle cx="12" cy="12" r="7.5"/><circle cx="12" cy="12" r="1" fill="currentColor"/>',
|
|
15
20
|
arc: '<path d="M4.5 13.5a8.5 8.5 0 0 1 15 0"/><circle cx="4.5" cy="13.5" r="1.6"/><circle cx="19.5" cy="13.5" r="1.6"/>', // (centred in the box: y 9–15)
|
|
16
|
-
dim: '<path d="
|
|
21
|
+
dim: '<path d="M3.5 7V17M20.5 7V17M4.5 12H19.5M7.5 14.5L4.5 12L7.5 9.5M16.5 14.5L19.5 12L16.5 9.5"/>',
|
|
17
22
|
trim: '<circle cx="6.5" cy="7" r="2.6"/><circle cx="6.5" cy="17" r="2.6"/><path d="M8.6 8.6L20 18M8.6 15.4L20 6"/>',
|
|
18
|
-
offset: '<path d="M4 15L15 4M9 20L20 9"/><path d="M11
|
|
19
|
-
split: '<path d="M3 12H9.5M14.5 12H21"/><path d="
|
|
23
|
+
offset: '<path d="M4 15L15 4M9 20L20 9"/><path d="M11 9L13.5 11.5" stroke-width="1.5"/>',
|
|
24
|
+
split: '<path d="M3 12H9.5M14.5 12H21"/><path d="M11.9998 14.1969C13.2148 14.1969 14.1998 13.2119 14.1998 11.9969C14.1998 10.7818 13.2148 9.79688 11.9998 9.79688C10.7848 9.79688 9.7998 10.7818 9.7998 11.9969C9.7998 13.2119 10.7848 14.1969 11.9998 14.1969Z" fill="currentColor"/><path d="M12 4V7M12 17V20" stroke-width="1.5"/>',
|
|
20
25
|
opening: '<path d="M5 20V6"/><path d="M5 6a14 14 0 0 1 14 14"/><path d="M3 20h18"/>',
|
|
21
26
|
room: '<path d="M4 5h9v6h7v8H4z"/>',
|
|
22
|
-
constr: '<path d="M5 19L19 5" stroke-dasharray="2
|
|
27
|
+
constr: '<path d="M5 19L19 5" stroke-dasharray="2 4"/>',
|
|
23
28
|
undo: '<path d="M9 14L4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>',
|
|
24
29
|
redo: '<path d="M15 14l5-5-5-5"/><path d="M20 9H9.5a5.5 5.5 0 0 0 0 11H13"/>',
|
|
25
30
|
pen: '<path d="M4 20l1-4.5L16 4.5l3.5 3.5L8.5 19z"/><path d="M13.5 7l3.5 3.5"/>',
|
|
@@ -33,10 +38,12 @@ export const IC: Record<string, string> = {
|
|
|
33
38
|
image: '<path d="M16 5h6"/><path d="M19 2v6"/><path d="M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/><circle cx="9" cy="9" r="2"/>',
|
|
34
39
|
text: '<path d="M5 6h14M12 6v13M9 19h6"/>',
|
|
35
40
|
cloud: '<path d="M7 18a3 3 0 0 1-.4-6A4.5 4.5 0 0 1 15 9.6 3.5 3.5 0 1 1 17.5 18z"/>',
|
|
36
|
-
calib: '<path d="M3
|
|
41
|
+
calib: '<path d="M3 15L15 3L18 6L21 9L9 21L3 15Z"/><path d="M7 12L9 14M10 9L12.5 11.5M13 6L15 8" stroke-width="1"/>',
|
|
37
42
|
aselect: '<path d="M6 3.5l12.5 7.2-5.6 1.4-2.6 5.4z"/>',
|
|
38
43
|
trash: '<path d="M5 7h14M10 7V4.5h4V7M7 7l1 13h8l1-13"/>',
|
|
39
44
|
plus: '<path d="M12 5v14M5 12h14"/>',
|
|
45
|
+
// Lucide's expand: a photo's tile to the photo whole, and back
|
|
46
|
+
expand: '<path d="M21 21l-6-6M21 21v-4.8M21 21h-4.8"/><path d="M3 16.2V21M3 21h4.8M3 21l6-6"/><path d="M21 7.8V3M21 3h-4.8M21 3l-6 6"/><path d="M3 7.8V3M3 3h4.8M3 3l6 6"/>',
|
|
40
47
|
fit: '<path d="M14 4h6v6M10 20H4v-6M20 4l-6.5 6.5M4 20l6.5-6.5"/>',
|
|
41
48
|
magnet: '<path d="M6 4v8a6 6 0 0 0 12 0V4h-4v8a2 2 0 0 1-4 0V4z"/><path d="M6 8h4M14 8h4"/>',
|
|
42
49
|
eye: '<path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12z"/><circle cx="12" cy="12" r="2.8"/>',
|
|
@@ -51,8 +58,8 @@ export const IC: Record<string, string> = {
|
|
|
51
58
|
// a circle half filled: how see-through
|
|
52
59
|
opacity: '<circle cx="12" cy="12" r="8"/><path d="M12 4a8 8 0 0 1 0 16z" fill="currentColor" stroke="none"/>',
|
|
53
60
|
// two overlapping sheets: the front one solid (bring to front), or the back one (send to back)
|
|
54
|
-
tofront: '<path d="M13 3.5H5C4.17157 3.5 3.5 4.17157 3.5 5V13C3.5 13.8284 4.17157 14.5 5 14.5H13C13.8284 14.5 14.5 13.8284 14.5 13V5C14.5 4.17157 13.8284 3.5 13 3.5Z" stroke-width="1
|
|
55
|
-
toback: '<path d="
|
|
61
|
+
tofront: '<path d="M13 3.5H5C4.17157 3.5 3.5 4.17157 3.5 5V13C3.5 13.8284 4.17157 14.5 5 14.5H13C13.8284 14.5 14.5 13.8284 14.5 13V5C14.5 4.17157 13.8284 3.5 13 3.5Z" stroke-width="1" stroke-dasharray="2.75 2.5"/><path d="M19 9.5H11C10.1716 9.5 9.5 10.1716 9.5 11V19C9.5 19.8284 10.1716 20.5 11 20.5H19C19.8284 20.5 20.5 19.8284 20.5 19V11C20.5 10.1716 19.8284 9.5 19 9.5Z" stroke-opacity="0.88" fill="currentColor" fill-opacity="0.17"/>',
|
|
62
|
+
toback: '<path d="M13 3.5H5C4.17157 3.5 3.5 4.17157 3.5 5V13C3.5 13.8284 4.17157 14.5 5 14.5H13C13.8284 14.5 14.5 13.8284 14.5 13V5C14.5 4.17157 13.8284 3.5 13 3.5Z" stroke-opacity="0.88" fill="currentColor" fill-opacity="0.17"/><path d="M19 9.5H11C10.1716 9.5 9.5 10.1716 9.5 11V19C9.5 19.8284 10.1716 20.5 11 20.5H19C19.8284 20.5 20.5 19.8284 20.5 19V11C20.5 10.1716 19.8284 9.5 19 9.5Z" stroke-width="1" stroke-dasharray="2.75 2.5"/>',
|
|
56
63
|
share: '<circle cx="6" cy="12" r="2.4"/><circle cx="18" cy="6" r="2.4"/><circle cx="18" cy="18" r="2.4"/><path d="M8.2 10.9l7.6-3.8M8.2 13.1l7.6 3.8"/>',
|
|
57
64
|
dots: '<circle cx="5" cy="12" r="1.6" fill="currentColor"/><circle cx="12" cy="12" r="1.6" fill="currentColor"/><circle cx="19" cy="12" r="1.6" fill="currentColor"/>',
|
|
58
65
|
// BOLDR's save (Figma 2247:138414): a filled disk, the full 24 box
|
|
@@ -62,19 +69,19 @@ export const IC: Record<string, string> = {
|
|
|
62
69
|
v: '<path d="M12 4v16"/><circle cx="12" cy="4" r="1.8" fill="currentColor"/><circle cx="12" cy="20" r="1.8" fill="currentColor"/>',
|
|
63
70
|
par: '<path d="M5 18L13 6M11 18L19 6"/>',
|
|
64
71
|
perp: '<path d="M4 19H20M10 19V5"/><path d="M10 14H15V19"/>',
|
|
65
|
-
tan: '<
|
|
72
|
+
tan: '<path d="M2 8H22" stroke-opacity="0.88"/><path d="M12 20C15.3137 20 18 17.3137 18 14C18 10.6863 15.3137 8 12 8C8.68629 8 6 10.6863 6 14C6 17.3137 8.68629 20 12 20Z"/>',
|
|
66
73
|
col: '<path d="M3 20L8.5 14.5M15.5 7.5L21 2"/><path d="M10.5 12.5L13.5 9.5" stroke-dasharray="1.5 2"/>',
|
|
67
74
|
eq: '<path d="M5 9h14M5 15h14"/>',
|
|
68
|
-
mid: '<path d="M3.5 17L20.5 7"
|
|
69
|
-
online: '<path d="M3.5 17L20.5 7"
|
|
70
|
-
oncircle: '<
|
|
75
|
+
mid: '<path d="M3.5 17L20.5 7" stroke-opacity="0.88"/><path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="currentColor"/>',
|
|
76
|
+
online: '<path d="M3.5 17L20.5 7" stroke-opacity="0.88"/><path d="M15 13C16.6569 13 18 11.6569 18 10C18 8.34315 16.6569 7 15 7C13.3431 7 12 8.34315 12 10C12 11.6569 13.3431 13 15 13Z"/>',
|
|
77
|
+
oncircle: '<path d="M11 20C14.866 20 18 16.866 18 13C18 9.13401 14.866 6 11 6C7.13401 6 4 9.13401 4 13C4 16.866 7.13401 20 11 20Z" stroke-opacity="0.88"/><path d="M16 11C17.6569 11 19 9.65685 19 8C19 6.34315 17.6569 5 16 5C14.3431 5 13 6.34315 13 8C13 9.65685 14.3431 11 16 11Z" fill="currentColor"/>',
|
|
71
78
|
lock: '<rect x="5.5" y="10.5" width="13" height="9.5" rx="2"/><path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5"/>',
|
|
72
79
|
unlock: '<rect x="5.5" y="10.5" width="13" height="9.5" rx="2"/><path d="M8.5 10.5V8a3.5 3.5 0 0 1 6.8-1.2"/>',
|
|
73
80
|
angle: '<path d="M4 19h16M4 19L15 5"/><path d="M10.5 19a6.5 6.5 0 0 0-2.6-5.2"/>',
|
|
74
81
|
diam: '<circle cx="12" cy="12" r="7.5"/><path d="M6.7 17.3L17.3 6.7"/>',
|
|
75
|
-
merge: '<path d="M4.2 14.1969C5.41503 14.1969 6.4 13.2119 6.4 11.9969C6.4 10.7818 5.41503 9.79688 4.2 9.79688C2.98497 9.79688 2 10.7818 2 11.9969C2 13.2119 2.98497 14.1969 4.2 14.1969Z"/><path d="
|
|
82
|
+
merge: '<path d="M4.2 14.1969C5.41503 14.1969 6.4 13.2119 6.4 11.9969C6.4 10.7818 5.41503 9.79688 4.2 9.79688C2.98497 9.79688 2 10.7818 2 11.9969C2 13.2119 2.98497 14.1969 4.2 14.1969Z"/><path d="M19.2 14.1969C20.415 14.1969 21.4 13.2119 21.4 11.9969C21.4 10.7818 20.415 9.79688 19.2 9.79688C17.985 9.79688 17 10.7818 17 11.9969C17 13.2119 17.985 14.1969 19.2 14.1969Z" stroke-opacity="0.88"/><path d="M6.5 12H14.5M12 14.5L14.5 12L12 9.5" stroke-width="1.5"/>',
|
|
76
83
|
chain: '<path d="M9 7H6a5 5 0 0 0 0 10h3M15 7h3a5 5 0 0 1 0 10h-3M8 12h8"/>',
|
|
77
|
-
thick: '<path d="M3
|
|
84
|
+
thick: '<path d="M3 4H21M3 17H21"/><path d="M11.9998 6.29688C11.9998 8.2495 11.9998 12.5474 11.9998 14.5M13.6998 7.99687L11.9998 6.29688L10.2998 7.99687M10.2998 13L11.9998 14.7L13.6998 13" stroke-width="1.5"/>',
|
|
78
85
|
// a wall's length measured on its face on top, at the bottom, left or right (as the page shows the wall), or along its
|
|
79
86
|
// centerline (a level wall's, a plumb one's): a wall dimension's choices
|
|
80
87
|
faceT: '<path d="M3 16h18" stroke-width="1.3"/><path d="M3 8h18M6 5.5L3 8l3 2.5M18 5.5L21 8l-3 2.5"/>',
|
|
@@ -85,9 +92,9 @@ export const IC: Record<string, string> = {
|
|
|
85
92
|
faceMidV: '<path d="M6 3v18M18 3v18" stroke-width="1.3"/><path d="M12 3v18M9.5 6L12 3l2.5 3M9.5 18L12 21l2.5-3"/>',
|
|
86
93
|
// the distance between two walls (plumb ones; H: level): between their faces toward each other, their centerlines,
|
|
87
94
|
// their far faces
|
|
88
|
-
spanIn: '<path d="M2.5
|
|
89
|
-
spanMid: '<path d="M2.5
|
|
90
|
-
spanOut: '<path d="M2.5
|
|
95
|
+
spanIn: '<path d="M2.5 4V20M7 4V20M17 4V20M21.5 4V20" stroke-opacity="0.88" stroke-width="1.3"/><path d="M8 12.5H16M10 15L8 12.5L10 10M14 15L16 12.5L14 10" stroke-width="1.5"/>',
|
|
96
|
+
spanMid: '<path d="M2.5 4V20M7.23684 4V20M17.7632 4V20M22.5 4V20" stroke-opacity="0.88" stroke-width="1.3"/><path d="M4.5 4V20M20.5 4V20" stroke-opacity="0.88" stroke-width="1" stroke-dasharray="1.5 2"/><path d="M4.75 12H19.25M7.25 14.5L4.75 12L7.25 9.5M16.75 14.5L19.25 12L16.75 9.5" stroke-width="1"/>',
|
|
97
|
+
spanOut: '<path d="M2.5 3V15M7 3V15M17 3V15M21.5 3V15" stroke-opacity="0.88" stroke-width="1.3"/><path d="M2.5 19.5H21.5M5 22L2.5 19.5L5 17M19 22L21.5 19.5L19 17" stroke-width="1.5"/>',
|
|
91
98
|
spanInH: '<path d="M4 2.5h16M4 7h16M4 17h16M4 21.5h16" stroke-width="1.3"/><path d="M12 7v10M9.5 9.5L12 7l2.5 2.5M9.5 14.5L12 17l2.5-2.5"/>',
|
|
92
99
|
spanMidH: '<path d="M4 2.5h16M4 7h16M4 17h16M4 21.5h16" stroke-width="1.3"/><path d="M4 4.75h16M4 19.25h16" stroke-width="1" stroke-dasharray="1.5 2"/><path d="M12 4.75v14.5M9.5 7.25L12 4.75l2.5 2.5M9.5 16.75l2.5 2.5 2.5-2.5"/>',
|
|
93
100
|
spanOutH: '<path d="M3 2.5h12M3 7h12M3 17h12M3 21.5h12" stroke-width="1.3"/><path d="M19.5 2.5v19M17 5L19.5 2.5 22 5M17 19l2.5 2.5L22 19"/>',
|
|
@@ -101,7 +108,7 @@ export const IC: Record<string, string> = {
|
|
|
101
108
|
cabinet: '<rect x="4" y="6" width="16" height="14" rx="1"/><path d="M12 6v14M10 11v3M14 11v3M3 20h18"/>',
|
|
102
109
|
window: '<path d="M2.5 8.5v7M21.5 8.5v7M2.5 12h3M18.5 12h3"/><path d="M5.5 10h13M5.5 14h13M5.5 8.5v7M18.5 8.5v7"/>',
|
|
103
110
|
door: '<path d="M5 20V6"/><path d="M5 6a14 14 0 0 1 14 14"/><path d="M3 20h18"/>',
|
|
104
|
-
convert: '<path d="M4
|
|
111
|
+
convert: '<path d="M4 17C7 11 8.5 18 11.5 12.5C14.5 7 16 9 20 6"/><path d="M4 21H20" stroke-width="1.5" stroke-dasharray="3 3"/>',
|
|
105
112
|
rename: '<path d="M4 20h4l11-11-4-4L4 16z"/>',
|
|
106
113
|
typelen: '<rect x="3" y="7" width="18" height="10" rx="2"/><path d="M7 12h.01M11 12h.01M15 12h2"/>',
|
|
107
114
|
finish: '<path d="M5 12.5l4.5 4.5L19 7.5"/>',
|
|
@@ -115,7 +122,7 @@ export const IC: Record<string, string> = {
|
|
|
115
122
|
// a measurement linked to a dimension, and let go of (Lucide's link and unlink); saved to the drawing's group
|
|
116
123
|
// (Lucide's folder-plus)
|
|
117
124
|
open: '<path d="M15 3h6v6"/><path d="M10 14L21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
|
|
118
|
-
leader: '<
|
|
125
|
+
leader: '<path d="M11.5 11.5L20 20" stroke-opacity="0.88"/><path d="M14.5 20H20V14.5" stroke-opacity="0.88"/><path d="M9.5 3H5C3.89543 3 3 3.89543 3 5V9.5C3 10.6046 3.89543 11.5 5 11.5H9.5C10.6046 11.5 11.5 10.6046 11.5 9.5V5C11.5 3.89543 10.6046 3 9.5 3Z"/>',
|
|
119
126
|
link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
|
|
120
127
|
unlink: '<path d="M18.84 12.25l1.72-1.71a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M5.17 11.75l-1.71 1.71a5 5 0 0 0 7.07 7.07l1.71-1.71"/><path d="M8 2v3M2 8h3M16 19v3M19 16h3"/>',
|
|
121
128
|
// the link tiles' size (Lucide's scaling)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ed } from '../app/state';
|
|
2
|
+
import { lensPointer } from '../editor/gestures';
|
|
3
|
+
import { clamp } from '../model/geom';
|
|
4
|
+
import { TH } from './theme';
|
|
5
|
+
import { mainT, S } from './view';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
A device's magnifier (the user's call, 2026-10-27: "a zoom in box in the corner while dragging and creating lines or
|
|
9
|
+
shapes like skia annotation have", for "pencil / finger interactions only - do not include in web"). While a finger
|
|
10
|
+
or Apple Pencil is placing or dragging something, in Draft, Annotate or the wall view, a square in a top corner shows
|
|
11
|
+
the drawing around it twice as close, with a cross on the point it'll land on: the snap, when there is one, so what
|
|
12
|
+
it snaps to shows before it's let go. As Skia annotation's lens: a third of the screen's shorter side, its corners
|
|
13
|
+
rounded, a line round it. react/PlanCanvas.native.tsx draws it from the pictures the frame is already made of; the
|
|
14
|
+
browser's canvas has none.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** How much closer than the drawing; the share of the screen's shorter side it's across, and its bounds; its corners. */
|
|
18
|
+
export const LENS_ZOOM = 2, LENS_SHARE = 0.3, LENS_MIN = 120, LENS_MAX = 240, LENS_R = 16;
|
|
19
|
+
/** From the room's edges to it; how near a finger may come before it moves to the other top corner; its cross's arms. */
|
|
20
|
+
export const LENS_GAP = 12, LENS_CLEAR = 40, LENS_ARM = 9;
|
|
21
|
+
|
|
22
|
+
export interface Lens {
|
|
23
|
+
/** its top left and its side, on the canvas */
|
|
24
|
+
x: number; y: number; size: number;
|
|
25
|
+
/** the finger or pencil, which its middle shows */
|
|
26
|
+
fx: number; fy: number;
|
|
27
|
+
/** its cross, on the canvas: where what's placed lands (kept inside it) */
|
|
28
|
+
cx: number; cy: number;
|
|
29
|
+
/** its line and its cross, in the drawing's ink */
|
|
30
|
+
ink: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The lens on a W × H canvas, while a finger or pencil places or drags something (else null): in the top-left corner
|
|
35
|
+
* of the room the chrome leaves it (`room`: from the canvas's left and top) or, with the finger near there, the top
|
|
36
|
+
* right.
|
|
37
|
+
*/
|
|
38
|
+
export function lensNow(W: number, H: number, room: { left: number; top: number }): Lens | null {
|
|
39
|
+
const f = lensPointer();
|
|
40
|
+
if (!f) return null;
|
|
41
|
+
const size = Math.round(clamp(Math.min(W, H) * LENS_SHARE, LENS_MIN, LENS_MAX));
|
|
42
|
+
let x = room.left + LENS_GAP;
|
|
43
|
+
const y = room.top + LENS_GAP;
|
|
44
|
+
if (f.x < x + size + LENS_CLEAR && f.y < y + size + LENS_CLEAR) x = W - LENS_GAP - size;
|
|
45
|
+
// (the cross on the snap, as close again as the drawing is in it, and never past its edge)
|
|
46
|
+
const s = ed.snapState, [px, py] = s ? S(mainT(), s.x, s.y) : [f.x, f.y], half = size / 2 - LENS_ARM;
|
|
47
|
+
return {
|
|
48
|
+
x, y, size, fx: f.x, fy: f.y,
|
|
49
|
+
cx: x + size / 2 + clamp(LENS_ZOOM * (px - f.x), -half, half), cy: y + size / 2 + clamp(LENS_ZOOM * (py - f.y), -half, half),
|
|
50
|
+
ink: TH.ink,
|
|
51
|
+
};
|
|
52
|
+
}
|