@grida/hud 0.2.0 → 0.2.1
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 +499 -36
- package/dist/core/index.d.mts +181 -0
- package/dist/core/index.d.ts +181 -0
- package/dist/core/index.js +301 -0
- package/dist/core/index.mjs +291 -0
- package/dist/cursors/index.d.mts +1 -1
- package/dist/cursors/index.d.ts +1 -1
- package/dist/cursors/index.js +1 -1
- package/dist/cursors/index.mjs +1 -1
- package/dist/index-BrfEdWbQ.d.ts +3140 -0
- package/dist/index-Cmbe2X5b.d.mts +3140 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +55 -2
- package/dist/index.mjs +3 -3
- package/dist/overlay-CVV4s3IL.d.ts +241 -0
- package/dist/overlay-dsG32baA.d.mts +241 -0
- package/dist/primitives/bedrock.d.mts +47 -0
- package/dist/primitives/bedrock.d.ts +47 -0
- package/dist/primitives/bedrock.js +71 -0
- package/dist/primitives/bedrock.mjs +65 -0
- package/dist/react.d.mts +3 -2
- package/dist/react.d.ts +2 -1
- package/dist/react.js +1 -1
- package/dist/react.mjs +1 -1
- package/dist/surface-BHQVvRFC.js +7356 -0
- package/dist/surface-NHSzUR8r.mjs +6902 -0
- package/dist/types-3wwFisZs.d.mts +296 -0
- package/dist/types-3wwFisZs.d.ts +296 -0
- package/package.json +12 -2
- package/dist/index-Cp0X4SV7.d.ts +0 -947
- package/dist/index-DhGdcuQz.d.mts +0 -947
- package/dist/surface-BvMmXoEl.mjs +0 -2471
- package/dist/surface-ofSNTJ8H.js +0 -2607
- /package/dist/{cursor-BFGUuD2M.d.mts → cursor-CxS8EMvm.d.mts} +0 -0
- /package/dist/{cursor-CIYvFshz.d.ts → cursor-CxS8EMvm.d.ts} +0 -0
- /package/dist/{cursor-BieMVb71.mjs → cursor-DW-uAPVE.mjs} +0 -0
- /package/dist/{cursor-DsP9qtN2.js → cursor-FGiJBdU-.js} +0 -0
|
@@ -0,0 +1,3140 @@
|
|
|
1
|
+
import { a as HUDPaintStripes, f as HUDSemanticGroup, r as HUDPaint, t as HUDDraw } from "./types-3wwFisZs.mjs";
|
|
2
|
+
import { i as RotationCorner, n as CursorRenderer, r as ResizeDirection, t as CursorIcon } from "./cursor-CxS8EMvm.mjs";
|
|
3
|
+
import cmath from "@grida/cmath";
|
|
4
|
+
import { Measurement } from "@grida/cmath/_measurement";
|
|
5
|
+
import { guide } from "@grida/cmath/_snap";
|
|
6
|
+
|
|
7
|
+
//#region primitives/pixel-grid.d.ts
|
|
8
|
+
declare const DEFAULT_PIXEL_GRID_COLOR = "rgba(150, 150, 150, 0.15)";
|
|
9
|
+
declare const DEFAULT_PIXEL_GRID_STEPS: [number, number];
|
|
10
|
+
interface PixelGridConfig {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
/** Minimum `transform[0][0]` (uniform scale) at which the grid renders. */
|
|
13
|
+
zoomThreshold: number;
|
|
14
|
+
/**
|
|
15
|
+
* Optional camera transform used to space the grid. Hosts that drive the
|
|
16
|
+
* HUD canvas's own `setTransform` can omit this — the pixel grid falls
|
|
17
|
+
* back to the canvas's chrome transform. Hosts that keep the HUD at
|
|
18
|
+
* identity (applying the camera elsewhere — e.g. as a CSS transform on
|
|
19
|
+
* an outer element) must supply this explicitly and update it on every
|
|
20
|
+
* camera change via `setPixelGridTransform`.
|
|
21
|
+
*/
|
|
22
|
+
transform?: cmath.Transform;
|
|
23
|
+
color?: string;
|
|
24
|
+
steps?: [number, number];
|
|
25
|
+
}
|
|
26
|
+
interface DrawPixelGridParams {
|
|
27
|
+
ctx: CanvasRenderingContext2D;
|
|
28
|
+
transform: cmath.Transform;
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
dpr: number;
|
|
32
|
+
color?: string;
|
|
33
|
+
steps?: [number, number];
|
|
34
|
+
}
|
|
35
|
+
declare function drawPixelGrid(p: DrawPixelGridParams): void;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region primitives/ruler.d.ts
|
|
38
|
+
type RulerAxis = "x" | "y";
|
|
39
|
+
/**
|
|
40
|
+
* Width (in CSS pixels) of the strip the top ruler occupies along the
|
|
41
|
+
* top edge of the viewport, and equivalently the width of the strip the
|
|
42
|
+
* left ruler occupies along the left edge. Same value for both so the
|
|
43
|
+
* corner clip is square — change one and the L-shape stops aligning.
|
|
44
|
+
*/
|
|
45
|
+
declare const DEFAULT_RULER_STRIP = 20;
|
|
46
|
+
declare const DEFAULT_RULER_TICK_HEIGHT = 6;
|
|
47
|
+
declare const DEFAULT_RULER_OVERLAP_THRESHOLD = 80;
|
|
48
|
+
declare const DEFAULT_RULER_TEXT_SIDE_OFFSET = 12;
|
|
49
|
+
declare const DEFAULT_RULER_FONT = "10px sans-serif";
|
|
50
|
+
declare const DEFAULT_RULER_COLOR = "rgba(128, 128, 128, 0.5)";
|
|
51
|
+
declare const DEFAULT_RULER_ACCENT_BACKGROUND = "rgba(80, 200, 255, 0.25)";
|
|
52
|
+
declare const DEFAULT_RULER_ACCENT_COLOR = "rgba(80, 200, 255, 1)";
|
|
53
|
+
declare const DEFAULT_RULER_BACKGROUND = "transparent";
|
|
54
|
+
declare const DEFAULT_RULER_STEPS: readonly number[];
|
|
55
|
+
/**
|
|
56
|
+
* Recommended drag distance threshold (in CSS pixels) for the ruler's
|
|
57
|
+
* create-guide gesture. Hosts implementing drag-from-strip should not
|
|
58
|
+
* commit a new guide until the pointer has moved this far from
|
|
59
|
+
* pointer-down — without it, a stray click on the strip creates an
|
|
60
|
+
* unwanted guide.
|
|
61
|
+
*
|
|
62
|
+
* Threshold-only — hud does not own the gesture. It owns the value
|
|
63
|
+
* because the value is a property of the ruler chrome's UX, not of
|
|
64
|
+
* any particular host's gesture pipeline.
|
|
65
|
+
*/
|
|
66
|
+
declare const DEFAULT_RULER_DRAG_THRESHOLD = 4;
|
|
67
|
+
type RulerRange = [a: number, b: number];
|
|
68
|
+
/**
|
|
69
|
+
* A priority mark on a ruler strip — typically a guide position the host
|
|
70
|
+
* wants the user to see at all zooms, regardless of where the regular
|
|
71
|
+
* step ticks land.
|
|
72
|
+
*
|
|
73
|
+
* With only `pos` set, the mark renders identically to a regular step
|
|
74
|
+
* tick — short stroke at `tickHeight`, label color = `color`. The extra
|
|
75
|
+
* fields below let a consumer render a mark as a full-strip line with
|
|
76
|
+
* an accent stroke + label color — the standard guide-position
|
|
77
|
+
* affordance every editor ships:
|
|
78
|
+
*
|
|
79
|
+
* ```ts
|
|
80
|
+
* { pos: 120, strokeHeight: strip, strokeColor: red, color: red, text: "120" }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* Defaults are chosen so an existing minimal `{ pos }` mark keeps
|
|
84
|
+
* rendering exactly as before. All extra fields are optional.
|
|
85
|
+
*/
|
|
86
|
+
interface RulerMark {
|
|
87
|
+
pos: number;
|
|
88
|
+
/** Tick stroke + (default) label color. */
|
|
89
|
+
color?: string;
|
|
90
|
+
/** Label text. */
|
|
91
|
+
text?: string;
|
|
92
|
+
/** Override the stroke color independently of the label color. */
|
|
93
|
+
strokeColor?: string;
|
|
94
|
+
/** Stroke width in CSS pixels. Default 1. */
|
|
95
|
+
strokeWidth?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Stroke height in CSS pixels. Default `tickHeight`. Pass `strip`
|
|
98
|
+
* (the strip width) for a full-strip mark — the standard
|
|
99
|
+
* guide-position affordance.
|
|
100
|
+
*/
|
|
101
|
+
strokeHeight?: number;
|
|
102
|
+
/** Label color. Defaults to `color` if omitted. */
|
|
103
|
+
textColor?: string;
|
|
104
|
+
/** Label alignment. Default "center". */
|
|
105
|
+
textAlign?: CanvasTextAlign;
|
|
106
|
+
/** Label position offset from `pos`. Default 0. */
|
|
107
|
+
textAlignOffset?: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Public config for the back-most ruler chrome.
|
|
111
|
+
*
|
|
112
|
+
* The HUD draws both axes in one pass: a horizontal strip across the top
|
|
113
|
+
* edge and a vertical strip down the left edge, with the corner square
|
|
114
|
+
* left blank. This is the L-shape every editor ruler ships.
|
|
115
|
+
*
|
|
116
|
+
* Coordinate model: identical to `PixelGridConfig` — `transform` is the
|
|
117
|
+
* camera (screen ↔ doc). If omitted, the HUD's chrome transform is used.
|
|
118
|
+
* Hosts that drive the HUD canvas at identity (e.g. the camera is on the
|
|
119
|
+
* underlying SVG/canvas) MUST supply this and update it on camera change
|
|
120
|
+
* via `setRulerTransform`.
|
|
121
|
+
*/
|
|
122
|
+
interface RulerConfig {
|
|
123
|
+
enabled: boolean;
|
|
124
|
+
/** Optional camera transform. See `PixelGridConfig.transform` for the
|
|
125
|
+
* two-transform contract. */
|
|
126
|
+
transform?: cmath.Transform;
|
|
127
|
+
/** Which axes to render. Default both. */
|
|
128
|
+
axes?: readonly RulerAxis[];
|
|
129
|
+
/** Strip width in CSS pixels. Default {@link DEFAULT_RULER_STRIP}. */
|
|
130
|
+
strip?: number;
|
|
131
|
+
/** Tick line height in CSS pixels. */
|
|
132
|
+
tickHeight?: number;
|
|
133
|
+
/** Ranges to highlight (in doc-space units), per axis. */
|
|
134
|
+
ranges?: {
|
|
135
|
+
x?: readonly RulerRange[];
|
|
136
|
+
y?: readonly RulerRange[];
|
|
137
|
+
};
|
|
138
|
+
/** Priority marks (in doc-space units), per axis. */
|
|
139
|
+
marks?: {
|
|
140
|
+
x?: readonly RulerMark[];
|
|
141
|
+
y?: readonly RulerMark[];
|
|
142
|
+
};
|
|
143
|
+
/** Minimum tick spacing in screen px before ticks fade near priority points. */
|
|
144
|
+
overlapThreshold?: number;
|
|
145
|
+
/** Offset from the strip edge for the label text. */
|
|
146
|
+
textSideOffset?: number;
|
|
147
|
+
/** Label font. */
|
|
148
|
+
font?: string;
|
|
149
|
+
/** Background fill for the ruler strip. */
|
|
150
|
+
backgroundColor?: string;
|
|
151
|
+
/** Tick + label color. */
|
|
152
|
+
color?: string;
|
|
153
|
+
/**
|
|
154
|
+
* Color of the 1-px inner-edge separator (the line where the strip
|
|
155
|
+
* meets the editing area). Defaults to `color` — the tick color —
|
|
156
|
+
* so existing consumers don't regress.
|
|
157
|
+
*
|
|
158
|
+
* Every production editor (Figma, Sketch, XD, Illustrator, Affinity)
|
|
159
|
+
* paints this line distinctly LIGHTER than the ticks. With shared
|
|
160
|
+
* color the host has to choose: ticks readable but separator looks
|
|
161
|
+
* like a heavy underline, OR separator correct but ticks too faint.
|
|
162
|
+
* Pass a lighter value here to match the universal convention; e.g.
|
|
163
|
+
* the OKLCH `border` token most design systems already define.
|
|
164
|
+
*
|
|
165
|
+
* The separator field is decoupled from `color` precisely because
|
|
166
|
+
* the two responsibilities (read-the-number vs. mark-the-edge) want
|
|
167
|
+
* different weights, and no single token gets both right.
|
|
168
|
+
*/
|
|
169
|
+
borderColor?: string;
|
|
170
|
+
/** Range fill color. */
|
|
171
|
+
accentBackgroundColor?: string;
|
|
172
|
+
/** Range label / boundary color. */
|
|
173
|
+
accentColor?: string;
|
|
174
|
+
/** Custom step series, e.g. for non-decimal units. */
|
|
175
|
+
steps?: readonly number[];
|
|
176
|
+
/**
|
|
177
|
+
* Subdivisions between major ticks. See `@grida/ruler` for the heuristic.
|
|
178
|
+
* - `false` / `0`: no subticks (default)
|
|
179
|
+
* - `true` / `"auto"`: 1-2-5 heuristic
|
|
180
|
+
* - `number`: fixed subdivision count
|
|
181
|
+
*/
|
|
182
|
+
subticks?: false | true | "auto" | number;
|
|
183
|
+
/** Subtick line height. Defaults to `round(tickHeight * 0.4)`. */
|
|
184
|
+
subtickHeight?: number;
|
|
185
|
+
/** Subtick color. Defaults to `color`. */
|
|
186
|
+
subtickColor?: string;
|
|
187
|
+
}
|
|
188
|
+
interface DrawRulerParams {
|
|
189
|
+
ctx: CanvasRenderingContext2D;
|
|
190
|
+
transform: cmath.Transform;
|
|
191
|
+
/** Viewport width in CSS pixels. */
|
|
192
|
+
width: number;
|
|
193
|
+
/** Viewport height in CSS pixels. */
|
|
194
|
+
height: number;
|
|
195
|
+
/** Device pixel ratio of the canvas. */
|
|
196
|
+
dpr: number;
|
|
197
|
+
config: RulerConfig;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Paint the L-shape ruler chrome (top + left strips) into the canvas
|
|
201
|
+
* context. Stateless: every call clears and redraws the strips in
|
|
202
|
+
* screen-space.
|
|
203
|
+
*
|
|
204
|
+
* The function assumes the caller has reset the ctx transform to identity
|
|
205
|
+
* for the device pixel scale; it applies `dpr` itself via `setTransform`.
|
|
206
|
+
*
|
|
207
|
+
* The corner square (`strip × strip` at origin) is deliberately left
|
|
208
|
+
* blank — neither axis is in charge of it. Hosts that want a corner fill
|
|
209
|
+
* draw it as a host-fed extra above the HUD.
|
|
210
|
+
*/
|
|
211
|
+
declare function drawRuler(p: DrawRulerParams): void;
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region primitives/transform-box.d.ts
|
|
214
|
+
/**
|
|
215
|
+
* 2×3 affine transform. Structurally compatible with `AffineTransform`
|
|
216
|
+
* from `@grida/cg` — the HUD package keeps a local alias to avoid
|
|
217
|
+
* acquiring a `@grida/cg` workspace dep just for the type.
|
|
218
|
+
*/
|
|
219
|
+
type AffineTransform = [[number, number, number], [number, number, number]];
|
|
220
|
+
type TransformBoxAction = {
|
|
221
|
+
type: "translate";
|
|
222
|
+
delta: cmath.Vector2;
|
|
223
|
+
} | {
|
|
224
|
+
type: "scale-side";
|
|
225
|
+
side: cmath.RectangleSide;
|
|
226
|
+
delta: cmath.Vector2;
|
|
227
|
+
} | {
|
|
228
|
+
type: "rotate";
|
|
229
|
+
corner: cmath.IntercardinalDirection;
|
|
230
|
+
delta: cmath.Vector2;
|
|
231
|
+
};
|
|
232
|
+
interface TransformBoxOptions {
|
|
233
|
+
size: cmath.Vector2;
|
|
234
|
+
}
|
|
235
|
+
type TransformBoxCorners = {
|
|
236
|
+
nw: cmath.Vector2;
|
|
237
|
+
ne: cmath.Vector2;
|
|
238
|
+
se: cmath.Vector2;
|
|
239
|
+
sw: cmath.Vector2;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Reduces a transform-box affine in response to a UI action.
|
|
243
|
+
*/
|
|
244
|
+
declare function reduceTransformBox(base: AffineTransform, action: TransformBoxAction, options: TransformBoxOptions): AffineTransform;
|
|
245
|
+
/**
|
|
246
|
+
* Given a transform-box matrix and the box size, returns the four
|
|
247
|
+
* transformed corners in pixel space.
|
|
248
|
+
*
|
|
249
|
+
* Each corner = transform * box_corner
|
|
250
|
+
*/
|
|
251
|
+
declare function getTransformBoxCorners(transform: AffineTransform, size: cmath.Vector2): TransformBoxCorners;
|
|
252
|
+
/**
|
|
253
|
+
* Decomposes an affine transform into rotation (deg), scale [sx, sy],
|
|
254
|
+
* and translation [tx, ty] components.
|
|
255
|
+
*/
|
|
256
|
+
declare function decompose(transform: AffineTransform): {
|
|
257
|
+
rotation: number;
|
|
258
|
+
scale: cmath.Vector2;
|
|
259
|
+
translation: cmath.Vector2;
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Composes rotation (deg), scale, and translation into an affine
|
|
263
|
+
* transform, anchored so the supplied `center` is preserved.
|
|
264
|
+
*/
|
|
265
|
+
declare function compose(rotation: number, scale: cmath.Vector2, translation: cmath.Vector2, center: cmath.Vector2): AffineTransform;
|
|
266
|
+
/**
|
|
267
|
+
* Convert pixel-space corners back to a box-relative transform.
|
|
268
|
+
*/
|
|
269
|
+
declare function cornersToBoxTransform(corners: TransformBoxCorners, size: cmath.Vector2): AffineTransform;
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region event/shape.d.ts
|
|
272
|
+
/**
|
|
273
|
+
* Selection shape — what the chrome wraps.
|
|
274
|
+
*
|
|
275
|
+
* Hosts return a `SelectionShape` from `shapeOf(id)` so the HUD can lay out
|
|
276
|
+
* the right kind of chrome:
|
|
277
|
+
*
|
|
278
|
+
* - **`rect`** — standard bounding box. Renders selection outline + 4 corner
|
|
279
|
+
* knobs, with virtual edge / rotation hit regions.
|
|
280
|
+
* - **`transformed`** — an axis-aligned local bbox PLUS a 2×3 affine matrix
|
|
281
|
+
* mapping local-frame points into doc-space. Covers rotation, skew,
|
|
282
|
+
* non-uniform scale, and mirror with one code path. `local.width × local.height`
|
|
283
|
+
* are the artwork's true dims (read by the size badge and by `applyResize`
|
|
284
|
+
* in local space). Identity matrix here is byte-equivalent to
|
|
285
|
+
* `{ kind: "rect", rect: local }`.
|
|
286
|
+
* - **`line`** — two-endpoint primitive (e.g. SVG `<line>`). Renders the
|
|
287
|
+
* line segment + 2 endpoint knobs. No edge / rotation regions.
|
|
288
|
+
* - **`unresolved`** — internal-only. Used inside `SelectionGroup` when the
|
|
289
|
+
* host gave a flat `NodeId[]` to `setSelection` — the chrome builder
|
|
290
|
+
* resolves the real shape by calling `shapeOf(id)` at frame build time.
|
|
291
|
+
* Hosts never emit this; treating it as host-facing would be a bug.
|
|
292
|
+
*
|
|
293
|
+
* Future kinds (e.g. `polyline`, `ellipse_oriented`) can be added without
|
|
294
|
+
* breaking the existing kinds — the chrome builder branches on `kind`.
|
|
295
|
+
*/
|
|
296
|
+
type SelectionShape = {
|
|
297
|
+
kind: "rect";
|
|
298
|
+
rect: Rect;
|
|
299
|
+
} | {
|
|
300
|
+
kind: "transformed"; /** Local-frame AABB; width/height are artwork's true dims. */
|
|
301
|
+
local: Rect; /** 2×3 affine mapping local-frame points → doc-space. */
|
|
302
|
+
matrix: cmath.Transform;
|
|
303
|
+
} | {
|
|
304
|
+
kind: "line";
|
|
305
|
+
p1: cmath.Vector2;
|
|
306
|
+
p2: cmath.Vector2;
|
|
307
|
+
} | {
|
|
308
|
+
kind: "unresolved";
|
|
309
|
+
id: string;
|
|
310
|
+
};
|
|
311
|
+
/**
|
|
312
|
+
* A logical selection group. The HUD renders one chrome instance per group.
|
|
313
|
+
*
|
|
314
|
+
* The host pre-computes `shape` (typically the union of member bounds for
|
|
315
|
+
* multi-node groups). Member `ids` are the gesture target — they all
|
|
316
|
+
* translate/resize together as a unit.
|
|
317
|
+
*/
|
|
318
|
+
interface SelectionGroup {
|
|
319
|
+
ids: readonly NodeId[];
|
|
320
|
+
shape: SelectionShape;
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region event/gesture.d.ts
|
|
324
|
+
/**
|
|
325
|
+
* Rect type local to the event/ layer.
|
|
326
|
+
*
|
|
327
|
+
* Mirrors `cmath.Rectangle` shape; declared here to keep `event/` free of
|
|
328
|
+
* imports beyond `cmath` types.
|
|
329
|
+
*/
|
|
330
|
+
interface Rect {
|
|
331
|
+
x: number;
|
|
332
|
+
y: number;
|
|
333
|
+
width: number;
|
|
334
|
+
height: number;
|
|
335
|
+
}
|
|
336
|
+
type NodeId = string;
|
|
337
|
+
/**
|
|
338
|
+
* Active interaction state for the surface.
|
|
339
|
+
*
|
|
340
|
+
* Coordinates inside each variant are documented per-field. The surface
|
|
341
|
+
* stores anchor points in document-space (so they survive camera pans during
|
|
342
|
+
* a gesture); incremental deltas are computed against the anchor each move.
|
|
343
|
+
*/
|
|
344
|
+
type SurfaceGesture = {
|
|
345
|
+
kind: "idle";
|
|
346
|
+
} | {
|
|
347
|
+
kind: "pan"; /** Last screen-space pointer position. */
|
|
348
|
+
prev_screen: cmath.Vector2;
|
|
349
|
+
} | {
|
|
350
|
+
kind: "marquee"; /** Anchor (pointer-down) in document-space. */
|
|
351
|
+
anchor_doc: cmath.Vector2; /** Current pointer in document-space. */
|
|
352
|
+
current_doc: cmath.Vector2;
|
|
353
|
+
} | {
|
|
354
|
+
/**
|
|
355
|
+
* Freeform polygon selection. Sibling to `marquee` — empty-space drag
|
|
356
|
+
* promotes here when the host has set the surface's
|
|
357
|
+
* `vectorSelectionMode` to `"lasso"`. The HUD doesn't decide which
|
|
358
|
+
* gesture to use; the host pushes the mode in alongside its tool
|
|
359
|
+
* toggle. See `Surface.setVectorSelectionMode`.
|
|
360
|
+
*/
|
|
361
|
+
kind: "lasso"; /** First sample (pointer-down position in document-space). */
|
|
362
|
+
anchor_doc: cmath.Vector2;
|
|
363
|
+
/**
|
|
364
|
+
* Polyline samples in document-space, oldest-first.
|
|
365
|
+
* `points[0] === anchor_doc`. Per pointer_move samples are appended
|
|
366
|
+
* only when the rounded screen-pixel differs from the last sample,
|
|
367
|
+
* keeping growth bounded on slow drags. The host's hit-test closes
|
|
368
|
+
* the polygon implicitly (last → first) — matches
|
|
369
|
+
* `cmath.polygon.pointInPolygon`'s ray-cast.
|
|
370
|
+
*/
|
|
371
|
+
points: cmath.Vector2[];
|
|
372
|
+
} | {
|
|
373
|
+
kind: "translate"; /** Selected ids at the start of the gesture. */
|
|
374
|
+
ids: NodeId[]; /** Anchor (pointer-down) in document-space. */
|
|
375
|
+
anchor_doc: cmath.Vector2; /** Last reported pointer in document-space. */
|
|
376
|
+
last_doc: cmath.Vector2;
|
|
377
|
+
} | {
|
|
378
|
+
kind: "resize"; /** Member ids of the group being resized (1 or more). */
|
|
379
|
+
ids: NodeId[]; /** Which handle the user grabbed. */
|
|
380
|
+
direction: ResizeDirection;
|
|
381
|
+
/**
|
|
382
|
+
* Selection shape at gesture start. For `kind: "rect"` this is the
|
|
383
|
+
* doc-space bbox; for `kind: "transformed"` it carries the local-frame
|
|
384
|
+
* AABB + matrix so resize math runs in the rotated/skewed local frame.
|
|
385
|
+
*/
|
|
386
|
+
initial_shape: SelectionShape; /** Anchor (pointer-down) in document-space. */
|
|
387
|
+
anchor_doc: cmath.Vector2; /** Current shape during the gesture. Same kind as `initial_shape`. */
|
|
388
|
+
current_shape: SelectionShape;
|
|
389
|
+
} | {
|
|
390
|
+
kind: "rotate";
|
|
391
|
+
ids: NodeId[];
|
|
392
|
+
corner: RotationCorner; /** Subject center in document-space. */
|
|
393
|
+
center_doc: cmath.Vector2; /** Angle at gesture start (radians). */
|
|
394
|
+
anchor_angle: number; /** Current angle (radians). */
|
|
395
|
+
current_angle: number;
|
|
396
|
+
/**
|
|
397
|
+
* Selection's screen-space rotation at gesture start (radians).
|
|
398
|
+
* Composed with `current_angle - anchor_angle` each frame to set
|
|
399
|
+
* the rotate-cursor's `baseAngle` — so cursors on already-rotated
|
|
400
|
+
* selections continue tilting correctly mid-gesture instead of
|
|
401
|
+
* snapping back to 0 + delta.
|
|
402
|
+
*/
|
|
403
|
+
initial_cursor_angle: number;
|
|
404
|
+
} | {
|
|
405
|
+
kind: "endpoint";
|
|
406
|
+
id: NodeId;
|
|
407
|
+
endpoint: "p1" | "p2"; /** Current endpoint position in document-space. */
|
|
408
|
+
pos_doc: cmath.Vector2;
|
|
409
|
+
} | {
|
|
410
|
+
/**
|
|
411
|
+
* Dragging one or more vertices of a path under content-edit. The
|
|
412
|
+
* `indices` are captured at gesture start; intra-gesture selection
|
|
413
|
+
* mirror changes do not affect what the gesture moves.
|
|
414
|
+
*/
|
|
415
|
+
kind: "translate_vertex"; /** Path node id under content-edit. */
|
|
416
|
+
node_id: NodeId; /** Vertex indices being moved. */
|
|
417
|
+
indices: number[]; /** Anchor (pointer-down) in document-space. */
|
|
418
|
+
anchor_doc: cmath.Vector2; /** Last reported pointer in document-space. */
|
|
419
|
+
last_doc: cmath.Vector2;
|
|
420
|
+
} | {
|
|
421
|
+
/**
|
|
422
|
+
* Dragging the path-edit sub-selection. Triggered by segment-body
|
|
423
|
+
* drag (the default — Meta switches to `bend_segment` instead) and
|
|
424
|
+
* any future drag origin that targets the whole sub-selection
|
|
425
|
+
* (multi-vertex drag is the planned follow-up).
|
|
426
|
+
*
|
|
427
|
+
* The HUD doesn't know which vertices are in the sub-selection
|
|
428
|
+
* (host owns it). It DOES know "this gesture additionally targets
|
|
429
|
+
* these vertex indices" — e.g. the endpoints of the segment that
|
|
430
|
+
* initiated the drag, when that segment isn't yet in the
|
|
431
|
+
* sub-selection. The host UNIONs `additional_vertex_indices` with
|
|
432
|
+
* its authoritative sub-selection on each preview frame.
|
|
433
|
+
*/
|
|
434
|
+
kind: "translate_vector_selection";
|
|
435
|
+
node_id: NodeId;
|
|
436
|
+
additional_vertex_indices: readonly number[];
|
|
437
|
+
anchor_doc: cmath.Vector2;
|
|
438
|
+
last_doc: cmath.Vector2;
|
|
439
|
+
} | {
|
|
440
|
+
/**
|
|
441
|
+
* Dragging a tangent control point. The host applies the mirror
|
|
442
|
+
* policy (`auto` by default — infer smooth-vs-broken). The chrome
|
|
443
|
+
* builder owned the anchor; this gesture only tracks the moving
|
|
444
|
+
* end of the handle line.
|
|
445
|
+
*/
|
|
446
|
+
kind: "translate_tangent";
|
|
447
|
+
node_id: NodeId;
|
|
448
|
+
tangent: readonly [number, 0 | 1];
|
|
449
|
+
/** Tangent control point's doc-space position at gesture start
|
|
450
|
+
* (carried from chrome via `decision.pos`). */
|
|
451
|
+
anchor_doc: cmath.Vector2;
|
|
452
|
+
last_doc: cmath.Vector2;
|
|
453
|
+
/** Pointer's doc-space position at gesture start. Distinct from
|
|
454
|
+
* `anchor_doc` because the cursor lands within the knob's Fitts'-
|
|
455
|
+
* tolerant hit area, not pixel-perfect on the control point.
|
|
456
|
+
* Used to detect "click-no-drag": commit is skipped when
|
|
457
|
+
* `last_doc === down_doc`, otherwise the absolute set_tangent
|
|
458
|
+
* would snap the control point to the cursor's down position. */
|
|
459
|
+
down_doc: cmath.Vector2;
|
|
460
|
+
} | {
|
|
461
|
+
/**
|
|
462
|
+
* Bending a segment. Press-down sampled a point on the curve at
|
|
463
|
+
* parameter `ca`; drag moves that point toward `last_doc`. The
|
|
464
|
+
* host re-solves tangents on every frame. Endpoints (a, b) stay
|
|
465
|
+
* fixed for the duration of the gesture.
|
|
466
|
+
*/
|
|
467
|
+
kind: "bend_segment";
|
|
468
|
+
node_id: NodeId;
|
|
469
|
+
segment: number; /** Frozen parametric position of the sampled point. */
|
|
470
|
+
ca: number;
|
|
471
|
+
anchor_doc: cmath.Vector2;
|
|
472
|
+
last_doc: cmath.Vector2;
|
|
473
|
+
} | {
|
|
474
|
+
/**
|
|
475
|
+
* Dragging a corner-radius handle.
|
|
476
|
+
*
|
|
477
|
+
* For RECT geometry the gesture carries the rect AABB and the
|
|
478
|
+
* candidate-anchor set captured at pointer_down. When the user
|
|
479
|
+
* grabs a single-corner knob (sub-max radii), `candidates` has
|
|
480
|
+
* length 1 and `anchor` is set. When the user grabs a
|
|
481
|
+
* coincidence group (oblong-max pair, square-max quadruple),
|
|
482
|
+
* `candidates` has length 2+ and `anchor` is `null` until the
|
|
483
|
+
* drag crosses the threshold; the state machine resolves the
|
|
484
|
+
* anchor from drag direction AMONG `candidates` only.
|
|
485
|
+
*
|
|
486
|
+
* For LINE geometry the gesture carries `a` and `b`; the
|
|
487
|
+
* projection axis is `a → b`. `anchor` is always `null` and
|
|
488
|
+
* `candidates` is empty.
|
|
489
|
+
*
|
|
490
|
+
* The new radius is derived each frame by projecting the
|
|
491
|
+
* cursor onto the relevant axis (rect: corner →
|
|
492
|
+
* `corner + (sign_x, sign_y)`; line: `a → b`). `explicit`
|
|
493
|
+
* latches the alt modifier at gesture start — the intent kind
|
|
494
|
+
* is decided once.
|
|
495
|
+
*/
|
|
496
|
+
kind: "corner_radius";
|
|
497
|
+
node_id: NodeId;
|
|
498
|
+
geometry: "rect" | "line";
|
|
499
|
+
/** RECT only — the rect AABB in LOCAL space. Used to derive
|
|
500
|
+
* per-anchor corner positions for projection. Undefined for
|
|
501
|
+
* line. */
|
|
502
|
+
rect?: {
|
|
503
|
+
x: number;
|
|
504
|
+
y: number;
|
|
505
|
+
width: number;
|
|
506
|
+
height: number;
|
|
507
|
+
};
|
|
508
|
+
/** RECT only — optional local → doc transform. Threaded
|
|
509
|
+
* through from the input so the gesture projects the cursor
|
|
510
|
+
* along the ROTATED axis on a rotated rect. */
|
|
511
|
+
transform?: cmath.Transform;
|
|
512
|
+
/** RECT only — the candidate anchors this gesture was opened
|
|
513
|
+
* for. Length 1 means anchor is locked at start; length 2 or
|
|
514
|
+
* 4 means anchor is `null` until threshold + direction
|
|
515
|
+
* resolution picks one. Empty for line. */
|
|
516
|
+
candidates: readonly ("nw" | "ne" | "se" | "sw")[];
|
|
517
|
+
/** Resolved corner anchor for rect (one of `candidates`).
|
|
518
|
+
* `null` while pre-resolution on a multi-candidate group, OR
|
|
519
|
+
* always for line geometry. */
|
|
520
|
+
anchor: "nw" | "ne" | "se" | "sw" | null; /** LINE only — the line endpoints. Undefined for rect. */
|
|
521
|
+
a?: cmath.Vector2;
|
|
522
|
+
b?: cmath.Vector2;
|
|
523
|
+
/** Screen-space pointer-down anchor — used by the multi-
|
|
524
|
+
* candidate threshold + direction resolution. */
|
|
525
|
+
anchor_screen: cmath.Vector2;
|
|
526
|
+
/** Doc-space pointer-down anchor — paired with `transform` to
|
|
527
|
+
* resolve the right corner on a rotated rect. The threshold
|
|
528
|
+
* check stays in screen-space (pixels), but the
|
|
529
|
+
* direction-resolution dot-product compares the doc-space drag
|
|
530
|
+
* delta (`point_doc - anchor_doc`) against the rect's
|
|
531
|
+
* rotated-into-doc sign vectors (`T.linear · sign_local`). */
|
|
532
|
+
anchor_doc: cmath.Vector2;
|
|
533
|
+
/** Whether alt was held at pointer_down. Latches at gesture
|
|
534
|
+
* start; intent kind is decided once. */
|
|
535
|
+
explicit: boolean; /** Most-recent doc-space pointer. */
|
|
536
|
+
last_doc: cmath.Vector2; /** Most-recent computed radius value (doc-space units). */
|
|
537
|
+
value: number;
|
|
538
|
+
/** Flips to `true` the first time `pointer_move` advances the
|
|
539
|
+
* gesture state (resolves the anchor or updates `value`).
|
|
540
|
+
* Pointer-up consults this to skip the commit on click-only
|
|
541
|
+
* interactions — `value` is seeded from the inset-padded knob
|
|
542
|
+
* position at pointer-down, so a pure click would otherwise
|
|
543
|
+
* commit a non-zero radius the user never intended. */
|
|
544
|
+
dragged: boolean;
|
|
545
|
+
} | {
|
|
546
|
+
/**
|
|
547
|
+
* Drag of a `parametric_handle` knob — opened from a
|
|
548
|
+
* `parametric_knob` overlay action and emits `parametric_handle`
|
|
549
|
+
* intents on every move + commit.
|
|
550
|
+
*
|
|
551
|
+
* When the action carries multiple `candidates` (a coincident
|
|
552
|
+
* group), `handle_id` is `null` until the drag crosses the
|
|
553
|
+
* threshold and direction-resolution picks one. `candidates`
|
|
554
|
+
* is the ordered list of (handle_id, curve, domain) tuples
|
|
555
|
+
* that hit-region stood in for.
|
|
556
|
+
*
|
|
557
|
+
* `modifiers` is the latched alt/shift state at pointer_down —
|
|
558
|
+
* intent payload reports it unchanged; host interprets.
|
|
559
|
+
*/
|
|
560
|
+
kind: "parametric_handle";
|
|
561
|
+
node_id: NodeId;
|
|
562
|
+
candidates: readonly {
|
|
563
|
+
handle_id: string;
|
|
564
|
+
track_doc: cmath.ui.Curve | cmath.ui.PointSet;
|
|
565
|
+
domain: {
|
|
566
|
+
min: number;
|
|
567
|
+
max: number;
|
|
568
|
+
step?: number;
|
|
569
|
+
};
|
|
570
|
+
}[];
|
|
571
|
+
handle_id: string | null;
|
|
572
|
+
anchor_screen: cmath.Vector2;
|
|
573
|
+
modifiers: {
|
|
574
|
+
alt: boolean;
|
|
575
|
+
shift: boolean;
|
|
576
|
+
};
|
|
577
|
+
last_doc: cmath.Vector2; /** Last computed value in host units (post-step-quantization). */
|
|
578
|
+
value: number;
|
|
579
|
+
/** Flips to `true` the first time `pointer_move` advances the
|
|
580
|
+
* gesture state (resolves the handle or updates `value`).
|
|
581
|
+
* Pointer-up skips commit on click-only interactions because
|
|
582
|
+
* `value` is seeded from the inset-padded knob position at
|
|
583
|
+
* pointer-down. */
|
|
584
|
+
dragged: boolean;
|
|
585
|
+
} | {
|
|
586
|
+
/**
|
|
587
|
+
* Drag of a `padding_handle` knob (padding named class). Opened
|
|
588
|
+
* eagerly from a `padding_handle` overlay action; emits
|
|
589
|
+
* `padding_handle` intents on every move + commit.
|
|
590
|
+
*
|
|
591
|
+
* `mirror` is NOT latched at gesture start — read live on each
|
|
592
|
+
* frame from `state.modifiers.alt`. Per the doctrine: "modifier
|
|
593
|
+
* change mid-gesture updates `mirror` on subsequent previews."
|
|
594
|
+
*
|
|
595
|
+
* The container `rect` and starting value are captured at
|
|
596
|
+
* pointer_down so subsequent value projection (`projectPaddingValue`)
|
|
597
|
+
* stays exact through camera moves — value math runs in doc-space
|
|
598
|
+
* against the rect snapshot, not the live geometry.
|
|
599
|
+
*/
|
|
600
|
+
kind: "padding_handle";
|
|
601
|
+
node_id: NodeId;
|
|
602
|
+
side: cmath.RectangleSide; /** Container rect snapshot at gesture start (doc-space). */
|
|
603
|
+
rect: Rect; /** Initial padding value at gesture start (doc-space units). */
|
|
604
|
+
initial_value: number; /** Most-recent doc-space pointer. */
|
|
605
|
+
last_doc: cmath.Vector2; /** Most-recent computed value (doc-space units, clamped to [0, max]). */
|
|
606
|
+
value: number;
|
|
607
|
+
/** Flips to `true` the first time `pointer_move` advances the
|
|
608
|
+
* gesture state. Pointer-up skips commit on click-only
|
|
609
|
+
* interactions — `value === initial_value` on first frame. */
|
|
610
|
+
dragged: boolean;
|
|
611
|
+
} | {
|
|
612
|
+
/**
|
|
613
|
+
* Drag of a transform-box handle (transform-box named class).
|
|
614
|
+
* Opened eagerly from a `transform_box_{body,side,corner}` overlay
|
|
615
|
+
* action; emits `transform_box` intents on every move + commit.
|
|
616
|
+
*
|
|
617
|
+
* `base_transform` is the transform at gesture start (frozen);
|
|
618
|
+
* each preview reduces from `base_transform` against the
|
|
619
|
+
* cumulative doc-space delta. `size` and `rotation` are the
|
|
620
|
+
* container parameters at gesture start (frozen — container
|
|
621
|
+
* doesn't change shape mid-gesture).
|
|
622
|
+
*/
|
|
623
|
+
kind: "transform_box";
|
|
624
|
+
id: string;
|
|
625
|
+
op: {
|
|
626
|
+
type: "translate";
|
|
627
|
+
} | {
|
|
628
|
+
type: "scale_side";
|
|
629
|
+
side: cmath.RectangleSide;
|
|
630
|
+
} | {
|
|
631
|
+
type: "rotate";
|
|
632
|
+
corner: cmath.IntercardinalDirection;
|
|
633
|
+
}; /** Box size in doc-space units (frozen at gesture start). */
|
|
634
|
+
size: cmath.Vector2;
|
|
635
|
+
/** Container rotation (degrees) at gesture start. Used to de-rotate
|
|
636
|
+
* doc-space cursor delta into box-local frame before reducing. */
|
|
637
|
+
rotation: number; /** Transform at gesture start (frozen). */
|
|
638
|
+
base_transform: AffineTransform; /** Pointer-down doc-space position. */
|
|
639
|
+
start_doc: cmath.Vector2; /** Most-recent doc-space pointer. */
|
|
640
|
+
last_doc: cmath.Vector2; /** Most-recent reduced transform (used for the commit emit). */
|
|
641
|
+
transform: AffineTransform; /** Flips to `true` once `pointer_move` advances. Click-no-drag → no commit. */
|
|
642
|
+
dragged: boolean;
|
|
643
|
+
};
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region primitives/corner-radius.d.ts
|
|
646
|
+
/**
|
|
647
|
+
* Screen-px inset of the handle's RESTING position from its anchoring
|
|
648
|
+
* corner (rect) or endpoint (line).
|
|
649
|
+
*
|
|
650
|
+
* The handle is floored to this distance whenever no gesture is in
|
|
651
|
+
* flight, so a radius=0 knob still sits inside the rect — visible and
|
|
652
|
+
* grabbable, not occluded by the resize-corner knob. During a gesture
|
|
653
|
+
* the floor is lifted: position follows the cursor.
|
|
654
|
+
*
|
|
655
|
+
* Locked, not configurable. Per `/sdk-design`: "core, not customizable."
|
|
656
|
+
*/
|
|
657
|
+
declare const DEFAULT_CORNER_RADIUS_HANDLE_INSET = 16;
|
|
658
|
+
/** Default screen-px size of the knob. Matches the visual size of
|
|
659
|
+
* resize-corner knobs by convention. */
|
|
660
|
+
declare const DEFAULT_CORNER_RADIUS_HANDLE_SIZE = 8;
|
|
661
|
+
/** Default screen-px hit-rect size. Padded above the visual knob per
|
|
662
|
+
* the package's render/hit asymmetry rule. */
|
|
663
|
+
declare const DEFAULT_CORNER_RADIUS_HIT_SIZE = 16;
|
|
664
|
+
/**
|
|
665
|
+
* One of the four named corners of a rect. Matches the package's
|
|
666
|
+
* existing `IntercardinalDirection` convention used by resize handles.
|
|
667
|
+
*/
|
|
668
|
+
type CornerRadiusAnchor = "nw" | "ne" | "se" | "sw";
|
|
669
|
+
/**
|
|
670
|
+
* Per-corner rectangular radius. Hosts populate all four — even
|
|
671
|
+
* "all-uniform" rounded rects pass `{ tl, tr, br, bl }` with the same
|
|
672
|
+
* value. The HUD never assumes uniformity; the surface dedup-collapses
|
|
673
|
+
* geometrically (when handles coincide) rather than value-checking.
|
|
674
|
+
*/
|
|
675
|
+
interface CornerRadiusRectangular {
|
|
676
|
+
tl: number;
|
|
677
|
+
tr: number;
|
|
678
|
+
br: number;
|
|
679
|
+
bl: number;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Public input handed to `surface.setCornerRadius(...)`.
|
|
683
|
+
*
|
|
684
|
+
* - `rect` ↔ `CornerRadiusRectangular` (four per-corner values, each
|
|
685
|
+
* knob travels at 45° from its corner; max radius `min(w,h)/2`).
|
|
686
|
+
* - `line` ↔ `{ value }` (one radius, knob travels along a→b).
|
|
687
|
+
*
|
|
688
|
+
* Illegal combinations (uniform-on-rect, rectangular-on-line) are
|
|
689
|
+
* unreachable by type.
|
|
690
|
+
*/
|
|
691
|
+
type CornerRadiusInput = {
|
|
692
|
+
node_id: NodeId;
|
|
693
|
+
geometry: {
|
|
694
|
+
kind: "rect";
|
|
695
|
+
/** Rect in LOCAL space (axis-aligned). When `transform` is
|
|
696
|
+
* omitted, "local" and "doc" coincide and the rect is the
|
|
697
|
+
* doc-space AABB. */
|
|
698
|
+
rect?: Rect;
|
|
699
|
+
/**
|
|
700
|
+
* Optional local → doc transform (2×3 affine). When set,
|
|
701
|
+
* handle positions are computed in local space (the four
|
|
702
|
+
* intercardinal diagonals from each corner) and then
|
|
703
|
+
* transformed into doc space — so rotated rects show their
|
|
704
|
+
* knobs along the ROTATED diagonals, not the doc-axis ones.
|
|
705
|
+
* Pure rotation (orthonormal) is the supported common case;
|
|
706
|
+
* non-orthonormal transforms work for rendering but are
|
|
707
|
+
* untested for the gesture's projection math.
|
|
708
|
+
*/
|
|
709
|
+
transform?: cmath.Transform;
|
|
710
|
+
};
|
|
711
|
+
radius: CornerRadiusRectangular;
|
|
712
|
+
} | {
|
|
713
|
+
node_id: NodeId;
|
|
714
|
+
geometry: {
|
|
715
|
+
kind: "line";
|
|
716
|
+
a: cmath.Vector2;
|
|
717
|
+
b: cmath.Vector2;
|
|
718
|
+
};
|
|
719
|
+
radius: {
|
|
720
|
+
value: number;
|
|
721
|
+
};
|
|
722
|
+
};
|
|
723
|
+
/**
|
|
724
|
+
* One handle the producer wants on screen: the doc-space anchor it
|
|
725
|
+
* sits at (so the camera moves it with the document), the screen-px
|
|
726
|
+
* size of the knob and its hit AABB, and a stable label identifying
|
|
727
|
+
* which interaction it triggers.
|
|
728
|
+
*
|
|
729
|
+
* `anchor` is `"nw" | "ne" | "se" | "sw"` for per-corner rect handles,
|
|
730
|
+
* `"line"` for line geometry. The surface owns the coincidence
|
|
731
|
+
* collapsing (and `cornerRadiusLayoutGroups` exposes the grouping
|
|
732
|
+
* predicate); the pure layout always returns one entry per corner.
|
|
733
|
+
*/
|
|
734
|
+
interface CornerRadiusHandleLayout {
|
|
735
|
+
/** Doc-space anchor — projected through the camera each frame. */
|
|
736
|
+
pos: cmath.Vector2;
|
|
737
|
+
/** Screen-px visual knob size. */
|
|
738
|
+
size: number;
|
|
739
|
+
/** Screen-px hit AABB size (>= size, padded per Fitts'). */
|
|
740
|
+
hit_size: number;
|
|
741
|
+
/** `nw/ne/se/sw` for per-corner rect handles, `"line"` for line. */
|
|
742
|
+
anchor: CornerRadiusAnchor | "line";
|
|
743
|
+
/** Stable identifier — `"corner_radius:<anchor>"` or
|
|
744
|
+
* `"corner_radius:line"`. */
|
|
745
|
+
label: string;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Sign of the intercardinal direction from a corner toward the rect's
|
|
749
|
+
* interior. NW corner → (+1, +1); NE → (-1, +1); SE → (-1, -1);
|
|
750
|
+
* SW → (+1, -1). The handle travels in this direction from the corner
|
|
751
|
+
* by `r` in BOTH x and y.
|
|
752
|
+
*/
|
|
753
|
+
declare function cornerRadiusAnchorSign(anchor: CornerRadiusAnchor): readonly [-1 | 1, -1 | 1];
|
|
754
|
+
/**
|
|
755
|
+
* Compute the handle position for one corner of a rect.
|
|
756
|
+
*
|
|
757
|
+
* The math — the handle IS the arc center of the rounded corner:
|
|
758
|
+
*
|
|
759
|
+
* reach = radius (during gesture)
|
|
760
|
+
* reach = max(radius, pad_screen / zoom) (at rest / commit)
|
|
761
|
+
* reach_cap = min(w, h) / 2 (closest-edge half)
|
|
762
|
+
* reach = clamp(reach, 0, reach_cap)
|
|
763
|
+
* handle = corner + (sign_x · reach, sign_y · reach)
|
|
764
|
+
*
|
|
765
|
+
* `reach` is the doc-space distance the arc center sits from the
|
|
766
|
+
* corner along EACH axis (x AND y). For a square at max, all four
|
|
767
|
+
* arc centers land at the rect's center. For an oblong at max
|
|
768
|
+
* (reach = min(w,h)/2), pairs along the SHORTER axis coincide:
|
|
769
|
+
*
|
|
770
|
+
* - w > h: TL/BL share `(r, h/2)`; TR/BR share `(w-r, h/2)`
|
|
771
|
+
* - h > w: TL/TR share `(w/2, r)`; BL/BR share `(w/2, h-r)`
|
|
772
|
+
*
|
|
773
|
+
* `during_gesture` lifts the padded floor: the handle can sit AT the
|
|
774
|
+
* corner at radius=0. After release the floor is re-applied and the
|
|
775
|
+
* handle snaps back to a grabbable position.
|
|
776
|
+
*
|
|
777
|
+
* `zoom` is the camera's uniform scale (`transform[0][0]`); pass `1`
|
|
778
|
+
* for unit tests against doc-space math.
|
|
779
|
+
*/
|
|
780
|
+
declare function cornerRadiusHandlePosRect(rect: Rect, anchor: CornerRadiusAnchor, radius: number, zoom: number, opts?: {
|
|
781
|
+
pad?: number;
|
|
782
|
+
during_gesture?: boolean;
|
|
783
|
+
transform?: cmath.Transform;
|
|
784
|
+
}): cmath.Vector2;
|
|
785
|
+
/**
|
|
786
|
+
* Compute the handle position along a line's `a → b` axis. Single
|
|
787
|
+
* handle for `line` geometry; same gesture-aware padding rule as the
|
|
788
|
+
* rect case. The saturation cap is `dist(a, b)`.
|
|
789
|
+
*
|
|
790
|
+
* The track here is the a→b segment of arbitrary direction, so the
|
|
791
|
+
* per-axis convention used by the rect case doesn't apply — `pad`
|
|
792
|
+
* converts to track-units as `pad / zoom`.
|
|
793
|
+
*/
|
|
794
|
+
declare function cornerRadiusHandlePosLine(a: cmath.Vector2, b: cmath.Vector2, radius: number, zoom: number, opts?: {
|
|
795
|
+
pad?: number;
|
|
796
|
+
during_gesture?: boolean;
|
|
797
|
+
}): cmath.Vector2;
|
|
798
|
+
/**
|
|
799
|
+
* Compute the layout for the corner-radius handles of one input.
|
|
800
|
+
*
|
|
801
|
+
* Pure: same inputs → same handles. No camera reads, no DOM.
|
|
802
|
+
*
|
|
803
|
+
* - `rect` geometry → 4 handles in `nw/ne/se/sw` declaration order.
|
|
804
|
+
* The center coincidence collapse is a downstream concern of the
|
|
805
|
+
* surface (which registers one hit region per coincidence group)
|
|
806
|
+
* and the renderer (which dedup-paints overlapping circles). The
|
|
807
|
+
* pure layout stays four-entry so consumers can inspect per-corner
|
|
808
|
+
* positions.
|
|
809
|
+
* - `line` geometry → 1 handle labelled `corner_radius:line`.
|
|
810
|
+
*
|
|
811
|
+
* `during_gesture` flips the handle position from "floored to padded
|
|
812
|
+
* inset" (at rest / on commit) to "raw radius value" (during drag).
|
|
813
|
+
* The surface passes `true` when its gesture machine is in a
|
|
814
|
+
* `corner_radius` state, `false` otherwise.
|
|
815
|
+
*/
|
|
816
|
+
declare function computeCornerRadiusLayout(input: CornerRadiusInput, fallback_rect: Rect | null, zoom: number, opts?: {
|
|
817
|
+
size?: number;
|
|
818
|
+
hit_size?: number;
|
|
819
|
+
pad?: number;
|
|
820
|
+
during_gesture?: boolean;
|
|
821
|
+
}): CornerRadiusHandleLayout[];
|
|
822
|
+
/**
|
|
823
|
+
* Group the four rect handle entries by doc-space coincidence (within
|
|
824
|
+
* `eps_doc`). Returns one group per distinct position, in nw/ne/se/sw
|
|
825
|
+
* iteration order. Each inner array is the list of anchors sharing
|
|
826
|
+
* that position.
|
|
827
|
+
*
|
|
828
|
+
* Geometric truth:
|
|
829
|
+
* - sub-max (each radius < min(w,h)/2) → 4 groups, one per corner
|
|
830
|
+
* - oblong max (w > h, each radius = h/2) → 2 groups:
|
|
831
|
+
* [['nw','sw'], ['ne','se']] (left pair, right pair)
|
|
832
|
+
* - oblong max (h > w, each radius = w/2) → 2 groups:
|
|
833
|
+
* [['nw','ne'], ['sw','se']] (top pair, bottom pair)
|
|
834
|
+
* - square max (each radius = w/2 = h/2) → 1 group:
|
|
835
|
+
* [['nw','ne','se','sw']] (all at the center)
|
|
836
|
+
*
|
|
837
|
+
* Returns `[]` for non-rect layouts (line geometry — there's no
|
|
838
|
+
* notion of multi-corner coincidence on a single-handle layout).
|
|
839
|
+
*/
|
|
840
|
+
declare function cornerRadiusLayoutGroups(layout: readonly CornerRadiusHandleLayout[], eps_doc?: number): CornerRadiusAnchor[][];
|
|
841
|
+
/**
|
|
842
|
+
* Resolve a drag's direction to the anchor whose intercardinal
|
|
843
|
+
* direction `(sign_x, sign_y)` best matches the drag delta.
|
|
844
|
+
*
|
|
845
|
+
* Used when a coincidence group has more than one candidate
|
|
846
|
+
* (oblong max — 2 candidates; square max — 4 candidates). Picks
|
|
847
|
+
* AMONG `candidates` only — never invents a corner outside the
|
|
848
|
+
* group. Tie-break: first listed candidate wins.
|
|
849
|
+
*
|
|
850
|
+
* `dx, dy` is the drag delta (cursor moved by). The user pulls the
|
|
851
|
+
* handle TOWARD the corner the radius will shrink at — so the drag
|
|
852
|
+
* direction matches `(sign_x, sign_y)` directly (TL knob exists at
|
|
853
|
+
* (r, r); pulling toward TL means cursor moves in (-x, -y); but
|
|
854
|
+
* that's the SAME as cursor moves "back along the (sign_x, sign_y)
|
|
855
|
+
* direction" which means delta · sign is negative). To keep the
|
|
856
|
+
* intent intuitive — "pulling toward NW selects NW" — we pick the
|
|
857
|
+
* candidate whose `(sign_x, sign_y)` minimizes the dot product
|
|
858
|
+
* (i.e. delta most opposite the corner→interior vector).
|
|
859
|
+
*
|
|
860
|
+
* Equivalent (and what the code does): maximize the dot product
|
|
861
|
+
* with the NEGATED delta.
|
|
862
|
+
*
|
|
863
|
+
* For a rotated rect, callers pass the rect's local→doc `transform`
|
|
864
|
+
* along with a DOC-space drag delta; the function rotates the local
|
|
865
|
+
* sign vectors through `T.linear` before comparing so the resolved
|
|
866
|
+
* corner is the one the user actually dragged toward — not the one
|
|
867
|
+
* that happens to line up with world axes. With `transform`
|
|
868
|
+
* omitted, the local frame coincides with the doc frame and the
|
|
869
|
+
* rotation is a no-op (back-compatible with axis-aligned callers).
|
|
870
|
+
*/
|
|
871
|
+
declare function resolveCornerDragAnchor(dx: number, dy: number, candidates: readonly CornerRadiusAnchor[], transform?: cmath.Transform): CornerRadiusAnchor;
|
|
872
|
+
/** @deprecated use `resolveCornerDragAnchor`. */
|
|
873
|
+
declare function resolveCenterDragAnchor(dx: number, dy: number): CornerRadiusAnchor;
|
|
874
|
+
interface DrawCornerRadiusParams {
|
|
875
|
+
ctx: CanvasRenderingContext2D;
|
|
876
|
+
transform: cmath.Transform;
|
|
877
|
+
/** Viewport width in CSS pixels. */
|
|
878
|
+
width: number;
|
|
879
|
+
/** Viewport height in CSS pixels. */
|
|
880
|
+
height: number;
|
|
881
|
+
/** Device pixel ratio of the canvas. */
|
|
882
|
+
dpr: number;
|
|
883
|
+
/** Pre-computed handle layout. Always one entry per corner (rect)
|
|
884
|
+
* or one entry (line); the painter dedups by screen-pixel so
|
|
885
|
+
* coincident handles paint as one circle. */
|
|
886
|
+
handles: readonly CornerRadiusHandleLayout[];
|
|
887
|
+
/** Stroke + fill color for the knob. */
|
|
888
|
+
color: string;
|
|
889
|
+
/** Fill color for the interior. Falls back to white for the standard
|
|
890
|
+
* hollow-ring look. */
|
|
891
|
+
fillColor?: string;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Paint corner-radius handles into the canvas context. Stateless.
|
|
895
|
+
*
|
|
896
|
+
* Coincident handles are dedup-painted at integer-pixel granularity:
|
|
897
|
+
* when multiple entries project to the same pixel (oblong-max pairs,
|
|
898
|
+
* square-max quadruple) only one circle is drawn. The deduplication
|
|
899
|
+
* mirrors `cornerRadiusLayoutGroups` — same geometric truth, two
|
|
900
|
+
* different consumers.
|
|
901
|
+
*/
|
|
902
|
+
declare function drawCornerRadius(p: DrawCornerRadiusParams): void;
|
|
903
|
+
//#endregion
|
|
904
|
+
//#region primitives/parametric-handle.d.ts
|
|
905
|
+
type ParametricHandle = cmath.parametric.ParametricHandle;
|
|
906
|
+
type ParametricHandleGroup = cmath.parametric.ParametricHandleGroup;
|
|
907
|
+
/**
|
|
908
|
+
* Hud-local protocol record: one node's `ParametricHandleSet` plus
|
|
909
|
+
* the `node_id` the surface uses to route per-handle intents back to
|
|
910
|
+
* the host. The math content (handles / groups / transform) carries
|
|
911
|
+
* the same semantics as `cmath.parametric.ParametricHandleSet`; the
|
|
912
|
+
* `node_id` is the hud-side routing tag.
|
|
913
|
+
*
|
|
914
|
+
* `inset` on each handle is in **track-units** (cmath contract).
|
|
915
|
+
* Hud's convention for translating its 16-screen-px snap-back to
|
|
916
|
+
* track-units lives in the corner-radius primitive (see
|
|
917
|
+
* `primitives/corner-radius.ts`); other producers that want the same
|
|
918
|
+
* convention compute `inset = screen_px / zoom · k` for whatever
|
|
919
|
+
* geometric factor `k` their track demands.
|
|
920
|
+
*
|
|
921
|
+
* `groups`, if provided, MUST be disjoint — each handle id may appear
|
|
922
|
+
* in at most one group. Overlapping declarations would register the
|
|
923
|
+
* same handle in multiple hit regions and make routing ambiguous.
|
|
924
|
+
* The layout-grouper enforces this at runtime by first-declared-wins:
|
|
925
|
+
* later groups that mention an already-claimed id are dropped (with a
|
|
926
|
+
* dev-mode warning).
|
|
927
|
+
*/
|
|
928
|
+
interface ParametricHandleInput {
|
|
929
|
+
node_id: NodeId;
|
|
930
|
+
handles: readonly ParametricHandle[];
|
|
931
|
+
groups?: readonly ParametricHandleGroup[];
|
|
932
|
+
transform?: cmath.Transform;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Hud's screen-px convention for the knob's resting floor from the
|
|
936
|
+
* curve's `t = 0` endpoint, used so a knob at value=0 doesn't collide
|
|
937
|
+
* with the resize-corner knob beneath it.
|
|
938
|
+
*
|
|
939
|
+
* This is hud's UX choice, NOT the unit the math layer reads.
|
|
940
|
+
* `ParametricHandle.inset` (in cmath) is documented in track-units;
|
|
941
|
+
* each producer translates this screen-px value through `zoom` (and
|
|
942
|
+
* any track-geometry factor) before populating the field.
|
|
943
|
+
*/
|
|
944
|
+
declare const DEFAULT_PARAMETRIC_HANDLE_INSET = 16;
|
|
945
|
+
/** Default screen-px size of the knob. Matches resize-corner knobs. */
|
|
946
|
+
declare const DEFAULT_PARAMETRIC_HANDLE_SIZE = 8;
|
|
947
|
+
/**
|
|
948
|
+
* Default screen-px hit AABB size. Padded above the visual knob per
|
|
949
|
+
* the package's render/hit asymmetry rule.
|
|
950
|
+
*/
|
|
951
|
+
declare const DEFAULT_PARAMETRIC_HIT_SIZE = 16;
|
|
952
|
+
/**
|
|
953
|
+
* One handle's render-time layout. `pos` is doc-space (after the
|
|
954
|
+
* input's `transform` has been applied); `track_doc` is the doc-space
|
|
955
|
+
* track (curve or point set) used by the gesture's projection.
|
|
956
|
+
* `domain` is the effective domain with defaults filled in
|
|
957
|
+
* (`min = 0`, `max = 1`).
|
|
958
|
+
*/
|
|
959
|
+
interface ParametricHandleLayout {
|
|
960
|
+
/** The node id of the owning input — routes intents back. */
|
|
961
|
+
node_id: NodeId;
|
|
962
|
+
/** The handle id within the input — names the manipulated parameter. */
|
|
963
|
+
handle_id: string;
|
|
964
|
+
/** Doc-space position of the knob center. */
|
|
965
|
+
pos: cmath.Vector2;
|
|
966
|
+
/** Screen-px visual knob size. */
|
|
967
|
+
size: number;
|
|
968
|
+
/** Screen-px hit AABB size (>= size, padded). */
|
|
969
|
+
hit_size: number;
|
|
970
|
+
/** Stable label — `parametric:<node_id>:<handle_id>`. */
|
|
971
|
+
label: string;
|
|
972
|
+
/** Doc-space track for the gesture's projection — continuous curve
|
|
973
|
+
* OR discrete point set. */
|
|
974
|
+
track_doc: cmath.ui.Curve | cmath.ui.PointSet;
|
|
975
|
+
/** Effective domain (`min`/`max` defaulted; `step` preserved). */
|
|
976
|
+
domain: {
|
|
977
|
+
min: number;
|
|
978
|
+
max: number;
|
|
979
|
+
step?: number;
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Compute the per-frame layout for every handle in an input. One
|
|
984
|
+
* layout entry per declared handle, in declaration order. Coincidence
|
|
985
|
+
* detection (the "4-corner collapse" semantic of corner-radius) is a
|
|
986
|
+
* separate concern — call {@link parametricHandleLayoutGroups} on the
|
|
987
|
+
* result.
|
|
988
|
+
*
|
|
989
|
+
* `during_gesture` lifts the snap-back floor: at rest, a handle whose
|
|
990
|
+
* `value` lands closer to `t = 0` than the input's `inset` (in
|
|
991
|
+
* track-units) is floored to `t_inset`; during a drag the knob
|
|
992
|
+
* follows the cursor down to `t = 0` so the gesture feels honest.
|
|
993
|
+
*
|
|
994
|
+
* Pure geometry; no zoom dependence — callers express `inset` in
|
|
995
|
+
* track-units, so screen-px → track-units conversion (if any) has
|
|
996
|
+
* already happened before this call.
|
|
997
|
+
*/
|
|
998
|
+
declare function computeParametricHandleLayout(input: ParametricHandleInput, opts?: {
|
|
999
|
+
size?: number;
|
|
1000
|
+
hit_size?: number;
|
|
1001
|
+
during_gesture?: boolean;
|
|
1002
|
+
}): ParametricHandleLayout[];
|
|
1003
|
+
/**
|
|
1004
|
+
* Partition a layout into hit-region groups using the input's
|
|
1005
|
+
* declared coincidence groups.
|
|
1006
|
+
*
|
|
1007
|
+
* A declared group "fires" only when ALL its members are within
|
|
1008
|
+
* `eps_doc` of each other in doc-space — i.e. the handles have
|
|
1009
|
+
* geometrically collapsed onto one point. When a group fires, its
|
|
1010
|
+
* members are returned as one sublist and the producer registers a
|
|
1011
|
+
* single hit region with `candidates = members`; the gesture resolves
|
|
1012
|
+
* which member the pointer meant from drag direction.
|
|
1013
|
+
*
|
|
1014
|
+
* When a declared group doesn't fire (members are spread out), its
|
|
1015
|
+
* members are returned as singletons — one hit region per handle.
|
|
1016
|
+
*
|
|
1017
|
+
* Handles not mentioned in any declared group are always singletons.
|
|
1018
|
+
*
|
|
1019
|
+
* The function never invents groupings the input didn't declare —
|
|
1020
|
+
* that's an `/sdk-design` invariant ("coincidence is opt-in").
|
|
1021
|
+
*/
|
|
1022
|
+
declare function parametricHandleLayoutGroups(input: ParametricHandleInput, layout: readonly ParametricHandleLayout[], eps_doc?: number): ParametricHandleLayout[][];
|
|
1023
|
+
/**
|
|
1024
|
+
* Resolve which handle in a coincident group the user is dragging.
|
|
1025
|
+
*
|
|
1026
|
+
* Each candidate has a curve whose tangent at the coincident position
|
|
1027
|
+
* points toward `t = 1`. The user "pulls the knob back along that
|
|
1028
|
+
* direction" to decrease value, so the drag delta most OPPOSITE the
|
|
1029
|
+
* tangent identifies the intended handle. Equivalent: maximize
|
|
1030
|
+
* `tangent · (-delta)`.
|
|
1031
|
+
*
|
|
1032
|
+
* Ties (rare — would require two curves with identical tangents
|
|
1033
|
+
* passing through the same point) break by first-listed candidate.
|
|
1034
|
+
*/
|
|
1035
|
+
declare function resolveParametricHandleByDirection(group: readonly ParametricHandleLayout[], dx: number, dy: number): ParametricHandleLayout;
|
|
1036
|
+
/**
|
|
1037
|
+
* Project a doc-space point onto a handle's track and return both
|
|
1038
|
+
* the parameter `t` and the host-units `value` (denormalized through
|
|
1039
|
+
* `domain`, then snapped to `step` if set).
|
|
1040
|
+
*
|
|
1041
|
+
* The producer calls this once per pointer_move during a drag. The
|
|
1042
|
+
* `value` is what flows back to the host on the intent's `value`
|
|
1043
|
+
* field; `t` is internal (useful for tests and debug overlays).
|
|
1044
|
+
*
|
|
1045
|
+
* Dispatches on `track.kind`: continuous curves use
|
|
1046
|
+
* {@link cmath.ui.projectPointOnCurve}; point sets use
|
|
1047
|
+
* {@link cmath.ui.projectPointOnSet}, which snaps to the nearest
|
|
1048
|
+
* point. `step` quantization (if `domain.step > 0`) is applied on
|
|
1049
|
+
* top of either, then clamped to `[min, max]`.
|
|
1050
|
+
*/
|
|
1051
|
+
declare function projectParametricHandleValue(layout: ParametricHandleLayout, point: cmath.Vector2): {
|
|
1052
|
+
t: number;
|
|
1053
|
+
value: number;
|
|
1054
|
+
};
|
|
1055
|
+
interface DrawParametricHandlesParams {
|
|
1056
|
+
ctx: CanvasRenderingContext2D;
|
|
1057
|
+
transform: cmath.Transform;
|
|
1058
|
+
/** Viewport width in CSS pixels. */
|
|
1059
|
+
width: number;
|
|
1060
|
+
/** Viewport height in CSS pixels. */
|
|
1061
|
+
height: number;
|
|
1062
|
+
/** Device pixel ratio of the canvas. */
|
|
1063
|
+
dpr: number;
|
|
1064
|
+
/** Pre-computed handle layouts. Coincident handles are dedup-painted
|
|
1065
|
+
* by screen pixel — overlapping knobs paint as one circle. */
|
|
1066
|
+
handles: readonly ParametricHandleLayout[];
|
|
1067
|
+
/** Stroke + fill color for the knob. */
|
|
1068
|
+
color: string;
|
|
1069
|
+
/** Fill color for the interior. Defaults to white for the standard
|
|
1070
|
+
* hollow-ring look (matches `drawCornerRadius`). */
|
|
1071
|
+
fillColor?: string;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Paint parametric handles into the canvas context. Stateless,
|
|
1075
|
+
* pixel-dedup'd. Same visual shape as `drawCornerRadius` — the two
|
|
1076
|
+
* collapse into one painter in Phase 2 when corner-radius migrates.
|
|
1077
|
+
*/
|
|
1078
|
+
declare function drawParametricHandles(p: DrawParametricHandlesParams): void;
|
|
1079
|
+
//#endregion
|
|
1080
|
+
//#region primitives/canvas.d.ts
|
|
1081
|
+
interface HUDCanvasOptions {
|
|
1082
|
+
color?: string;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Imperative Canvas 2D renderer for the HUD overlay.
|
|
1086
|
+
*
|
|
1087
|
+
* Owns a single `<canvas>` element and draws {@link HUDDraw} command lists
|
|
1088
|
+
* each frame. All drawing is immediate-mode: the canvas is cleared and
|
|
1089
|
+
* fully redrawn on every `draw()` call.
|
|
1090
|
+
*
|
|
1091
|
+
* The viewport transform is assumed to be axis-aligned (scale + translate only,
|
|
1092
|
+
* no rotation/shear). The off-diagonal components of the transform matrix are
|
|
1093
|
+
* ignored.
|
|
1094
|
+
*/
|
|
1095
|
+
declare class HUDCanvas {
|
|
1096
|
+
private canvas;
|
|
1097
|
+
private ctx;
|
|
1098
|
+
private dpr;
|
|
1099
|
+
private transform;
|
|
1100
|
+
private color;
|
|
1101
|
+
private width;
|
|
1102
|
+
private height;
|
|
1103
|
+
private pixelGrid;
|
|
1104
|
+
private ruler;
|
|
1105
|
+
private cornerRadiusHandles;
|
|
1106
|
+
private parametricHandles;
|
|
1107
|
+
constructor(canvas: HTMLCanvasElement, options?: HUDCanvasOptions);
|
|
1108
|
+
setColor(color?: string): void;
|
|
1109
|
+
setSize(w: number, h: number): void;
|
|
1110
|
+
setTransform(transform: cmath.Transform): void;
|
|
1111
|
+
/**
|
|
1112
|
+
* Configure the back-most pixel-grid layer. Pass `null` to disable.
|
|
1113
|
+
* Drawn before any HUD primitive, gated by `zoomThreshold`. See
|
|
1114
|
+
* `PixelGridConfig.transform` for the two-transform contract.
|
|
1115
|
+
*/
|
|
1116
|
+
setPixelGrid(config: PixelGridConfig | null): void;
|
|
1117
|
+
/**
|
|
1118
|
+
* Update only the pixel grid's transform, without replacing the rest of
|
|
1119
|
+
* the config. Cheap to call per camera tick.
|
|
1120
|
+
*/
|
|
1121
|
+
setPixelGridTransform(transform: cmath.Transform): void;
|
|
1122
|
+
/**
|
|
1123
|
+
* Configure the top-most ruler chrome (top + left strips). Pass `null`
|
|
1124
|
+
* to disable. Painted LAST in the frame — after the pixel grid,
|
|
1125
|
+
* selection chrome, marquee, handles, size meter, and host extras —
|
|
1126
|
+
* so the strips visually frame the viewport instead of being clipped
|
|
1127
|
+
* by anything drawn at the edges. See `RulerConfig.transform` for
|
|
1128
|
+
* the two-transform contract — same shape as the pixel grid.
|
|
1129
|
+
*
|
|
1130
|
+
* Paint-order rationale: pixel grid is a substrate (content-space,
|
|
1131
|
+
* the user reads it "under" the document), ruler is a frame
|
|
1132
|
+
* (viewport-space, the user reads it "around" the document). Frames
|
|
1133
|
+
* sit on top of everything they frame; substrates sit beneath. See
|
|
1134
|
+
* the README "Render path" section.
|
|
1135
|
+
*/
|
|
1136
|
+
setRuler(config: RulerConfig | null): void;
|
|
1137
|
+
/**
|
|
1138
|
+
* Update only the ruler's transform. Cheap to call per camera tick.
|
|
1139
|
+
* No-op when no ruler config is set.
|
|
1140
|
+
*/
|
|
1141
|
+
setRulerTransform(transform: cmath.Transform): void;
|
|
1142
|
+
/**
|
|
1143
|
+
* Configure the corner-radius handle overlay. Pass `null` to clear.
|
|
1144
|
+
*
|
|
1145
|
+
* Painted in the chrome band — ABOVE the surface's selection
|
|
1146
|
+
* outlines / resize knobs / host extras (which is the "handles, not
|
|
1147
|
+
* frame" band) and BELOW the ruler (the frame strip). The position
|
|
1148
|
+
* inside the chrome band — strictly above `screenRects` — matches
|
|
1149
|
+
* the layered-handle convention: a feature-specific knob always
|
|
1150
|
+
* sits over the generic resize chrome that draws under it.
|
|
1151
|
+
*
|
|
1152
|
+
* Hit-test entries are NOT registered from here; the Surface owns
|
|
1153
|
+
* the registry and pushes corner-radius regions alongside its
|
|
1154
|
+
* regular chrome regions. Render and hit live on independent
|
|
1155
|
+
* shapes per the package's render/hit asymmetry rule.
|
|
1156
|
+
*/
|
|
1157
|
+
setCornerRadiusHandles(handles: readonly CornerRadiusHandleLayout[] | null): void;
|
|
1158
|
+
/**
|
|
1159
|
+
* Push the per-frame parametric-handle layouts. Painted in the same
|
|
1160
|
+
* z-band as corner-radius handles (knob, not frame) — feature-
|
|
1161
|
+
* specific knobs above generic resize chrome, below marquee/lasso
|
|
1162
|
+
* and the ruler.
|
|
1163
|
+
*
|
|
1164
|
+
* Hit-test entries are NOT registered here; the Surface owns the
|
|
1165
|
+
* registry and pushes parametric regions alongside its other
|
|
1166
|
+
* chrome regions. Render and hit live on independent shapes per the
|
|
1167
|
+
* package's render/hit asymmetry rule.
|
|
1168
|
+
*/
|
|
1169
|
+
setParametricHandles(handles: readonly ParametricHandleLayout[] | null): void;
|
|
1170
|
+
/**
|
|
1171
|
+
* Clear the canvas and draw all primitives in `commands`.
|
|
1172
|
+
* Pass `undefined` to clear without drawing (e.g. when no overlay is active).
|
|
1173
|
+
*/
|
|
1174
|
+
draw(commands: HUDDraw | undefined): void;
|
|
1175
|
+
private applyViewTransform;
|
|
1176
|
+
private applyScreenTransform;
|
|
1177
|
+
/** Project a scalar offset on `axis` to screen-space. */
|
|
1178
|
+
private deltaToScreen;
|
|
1179
|
+
private drawRules;
|
|
1180
|
+
/**
|
|
1181
|
+
* Resolve an optional `HUDPaint` to a Canvas 2D fill/stroke value,
|
|
1182
|
+
* falling back to the legacy `color` + `opacity` fields when absent.
|
|
1183
|
+
*
|
|
1184
|
+
* Used by the primitive renderers to switch through `HUDPaint` when
|
|
1185
|
+
* present; the legacy color path is preserved for callers that haven't
|
|
1186
|
+
* adopted paint yet. Pattern resolution happens here — including the
|
|
1187
|
+
* counter-CTM transform that keeps stripes screen-aligned.
|
|
1188
|
+
*/
|
|
1189
|
+
private resolvePaintOrFallback;
|
|
1190
|
+
private drawLines;
|
|
1191
|
+
private drawRects;
|
|
1192
|
+
private drawPolylines;
|
|
1193
|
+
private drawPoints;
|
|
1194
|
+
/**
|
|
1195
|
+
* Draw rects whose **size is in screen-space** but whose **anchor is in
|
|
1196
|
+
* document-space**. The doc-space point is projected via the current
|
|
1197
|
+
* transform; the rect is then drawn at fixed CSS-pixel dimensions.
|
|
1198
|
+
*
|
|
1199
|
+
* This is the primitive used to draw resize / rotate handles — they must
|
|
1200
|
+
* remain a constant visual size regardless of viewport zoom.
|
|
1201
|
+
*/
|
|
1202
|
+
private drawScreenRects;
|
|
1203
|
+
}
|
|
1204
|
+
//#endregion
|
|
1205
|
+
//#region primitives/paint.d.ts
|
|
1206
|
+
declare const DEFAULT_STRIPES_ANGLE_DEG = 45;
|
|
1207
|
+
declare const DEFAULT_STRIPES_SPACING_PX = 8;
|
|
1208
|
+
declare const DEFAULT_STRIPES_THICKNESS_PX = 1.5;
|
|
1209
|
+
/**
|
|
1210
|
+
* Result of `resolvePaint`. The caller assigns `style` to
|
|
1211
|
+
* `ctx.fillStyle` / `ctx.strokeStyle` and uses `opacity` as `globalAlpha`
|
|
1212
|
+
* for the operation (caller is responsible for save/restore).
|
|
1213
|
+
*/
|
|
1214
|
+
interface ResolvedPaint {
|
|
1215
|
+
style: string | CanvasPattern;
|
|
1216
|
+
opacity: number;
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Pure geometry for a stripes tile. Computes the cross-stripe period
|
|
1220
|
+
* (tile height) and the per-stripe parameters in device pixels.
|
|
1221
|
+
* Separated from the rasterizer so the math is testable in Node.
|
|
1222
|
+
*
|
|
1223
|
+
* The tile is `1 × size`: one horizontal stripe period, axis-aligned.
|
|
1224
|
+
* Rotation is applied at draw time via `CanvasPattern.setTransform`,
|
|
1225
|
+
* NOT baked into the rasterized tile — baking rotation would force the
|
|
1226
|
+
* tile's axis-aligned period to `spacing / sin(angle)`, irrational for
|
|
1227
|
+
* the canonical 45° case, and produce visible breaks within each
|
|
1228
|
+
* rendered stripe (the previous behavior).
|
|
1229
|
+
*
|
|
1230
|
+
* To keep stripes screen-aligned regardless of viewport zoom, the tile
|
|
1231
|
+
* is built in *device pixels* and the consumer composes a counter-CTM
|
|
1232
|
+
* with the rotation in `setTransform`.
|
|
1233
|
+
*/
|
|
1234
|
+
declare function computeStripesTileGeometry(paint: HUDPaintStripes, dpr: number): {
|
|
1235
|
+
/**
|
|
1236
|
+
* Tile height in device pixels — one cross-stripe period. The tile's
|
|
1237
|
+
* width is fixed at 1 (the stripe is constant along the stripe
|
|
1238
|
+
* direction; horizontal width doesn't carry information).
|
|
1239
|
+
*/
|
|
1240
|
+
size: number;
|
|
1241
|
+
spacingPx: number;
|
|
1242
|
+
thicknessPx: number;
|
|
1243
|
+
angleRad: number;
|
|
1244
|
+
};
|
|
1245
|
+
/**
|
|
1246
|
+
* Rasterize an unrotated, axis-aligned stripes tile (device pixels).
|
|
1247
|
+
* The tile is `1 × size` — one horizontal stripe band wrapping the
|
|
1248
|
+
* `y=0` / `y=size` seam, so it tiles cleanly under `repeat`.
|
|
1249
|
+
*
|
|
1250
|
+
* Rotation is applied at draw time via the pattern transform in
|
|
1251
|
+
* `resolvePaint`. Baking rotation here would force the axis-aligned
|
|
1252
|
+
* tile dimensions to align with the rotated stripe lattice — irrational
|
|
1253
|
+
* for the canonical 45° case — and produce visible breaks within each
|
|
1254
|
+
* rendered stripe.
|
|
1255
|
+
*/
|
|
1256
|
+
declare function buildStripesTile(paint: HUDPaintStripes, dpr: number): OffscreenCanvas;
|
|
1257
|
+
/**
|
|
1258
|
+
* Resolve an `HUDPaint` to a Canvas 2D paint value.
|
|
1259
|
+
*
|
|
1260
|
+
* Solid → CSS color string. Stripes → `CanvasPattern` (with the pattern
|
|
1261
|
+
* transform pre-applied to keep tiles aligned to device pixels).
|
|
1262
|
+
*
|
|
1263
|
+
* Throws on unknown `kind` — closed-taxonomy enforcement; HUD does not
|
|
1264
|
+
* silently passthrough unknown paint kinds.
|
|
1265
|
+
*/
|
|
1266
|
+
declare function resolvePaint(ctx: CanvasRenderingContext2D, paint: HUDPaint, dpr: number): ResolvedPaint;
|
|
1267
|
+
//#endregion
|
|
1268
|
+
//#region primitives/draw.d.ts
|
|
1269
|
+
interface HUDGroupFilter {
|
|
1270
|
+
hidden?: Iterable<HUDSemanticGroup>;
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Filter a draw command list by semantic group.
|
|
1274
|
+
*
|
|
1275
|
+
* Ungrouped primitives are always kept. The function is intentionally shallow:
|
|
1276
|
+
* primitives are immutable command objects on the hot draw path, so preserving
|
|
1277
|
+
* object identity keeps this as a visibility pass rather than a rewrite.
|
|
1278
|
+
*/
|
|
1279
|
+
declare function filterHUDDrawByGroup(draw: HUDDraw | undefined, filter: HUDGroupFilter): HUDDraw | undefined;
|
|
1280
|
+
//#endregion
|
|
1281
|
+
//#region primitives/snap-guide.d.ts
|
|
1282
|
+
/**
|
|
1283
|
+
* Convert a `guide.SnapGuide` (the output of `guide.plot()`) into a
|
|
1284
|
+
* generic {@link HUDDraw} command list.
|
|
1285
|
+
*
|
|
1286
|
+
* `color`, when supplied, is applied as the per-item stroke override
|
|
1287
|
+
* for every emitted line, rule, and point. When absent, the HUD
|
|
1288
|
+
* canvas's current color is used.
|
|
1289
|
+
*/
|
|
1290
|
+
declare function snapGuideToHUDDraw(sg: guide.SnapGuide | undefined, color?: string): HUDDraw | undefined;
|
|
1291
|
+
//#endregion
|
|
1292
|
+
//#region primitives/measurement-guide.d.ts
|
|
1293
|
+
/**
|
|
1294
|
+
* Convert a {@link Measurement} (the output of `measure()`) into a
|
|
1295
|
+
* generic {@link HUDDraw} command list.
|
|
1296
|
+
*
|
|
1297
|
+
* All coordinates are in **document space** — the HUD canvas applies
|
|
1298
|
+
* the viewport transform.
|
|
1299
|
+
*
|
|
1300
|
+
* Produces:
|
|
1301
|
+
* - Two stroke-only rects for the A and B bounding boxes
|
|
1302
|
+
* - One labelled guide line per non-zero distance (solid)
|
|
1303
|
+
* - One auxiliary line per non-zero side connecting the guide to B (dashed)
|
|
1304
|
+
*
|
|
1305
|
+
* If `color` is provided, every emitted line and rect carries that color so
|
|
1306
|
+
* the guides render distinctly from the canvas's chrome color. When used via
|
|
1307
|
+
* `surface.draw(extra)` (the host-fed-extras channel) this is required to
|
|
1308
|
+
* separate measurement from selection chrome on a shared canvas.
|
|
1309
|
+
*/
|
|
1310
|
+
declare function measurementToHUDDraw(m: Measurement, color?: string): HUDDraw;
|
|
1311
|
+
//#endregion
|
|
1312
|
+
//#region primitives/marquee.d.ts
|
|
1313
|
+
/**
|
|
1314
|
+
* Convert two marquee corner points into a {@link HUDDraw} command list.
|
|
1315
|
+
*
|
|
1316
|
+
* All coordinates are in **document space**.
|
|
1317
|
+
*
|
|
1318
|
+
* Produces a single rectangle with a stroke outline and a semi-transparent fill.
|
|
1319
|
+
*/
|
|
1320
|
+
declare function marqueeToHUDDraw(a: cmath.Vector2, b: cmath.Vector2): HUDDraw;
|
|
1321
|
+
//#endregion
|
|
1322
|
+
//#region primitives/lasso.d.ts
|
|
1323
|
+
/**
|
|
1324
|
+
* Convert a lasso point sequence into a {@link HUDDraw} command list.
|
|
1325
|
+
*
|
|
1326
|
+
* All coordinates are in **document space**.
|
|
1327
|
+
*
|
|
1328
|
+
* Produces a single polyline with a dashed stroke and a semi-transparent fill.
|
|
1329
|
+
* The polyline lives on the {@link HUDDraw} TOP layer (`topPolylines`) so it
|
|
1330
|
+
* always paints above knobs, handles, outlines, and any other surface chrome
|
|
1331
|
+
* — the user must always see the live lasso region they are drawing,
|
|
1332
|
+
* regardless of what it overlaps. Standalone `<Lasso/>` overlays render on
|
|
1333
|
+
* their own canvas and don't need this guarantee, but routing through the
|
|
1334
|
+
* top slot makes hosts that inline the draw into the surface canvas behave
|
|
1335
|
+
* identically.
|
|
1336
|
+
*/
|
|
1337
|
+
declare function lassoToHUDDraw(points: cmath.Vector2[]): HUDDraw | undefined;
|
|
1338
|
+
//#endregion
|
|
1339
|
+
//#region event/event.d.ts
|
|
1340
|
+
/** Modifier-key snapshot at the moment an event was produced. */
|
|
1341
|
+
interface Modifiers {
|
|
1342
|
+
shift: boolean;
|
|
1343
|
+
alt: boolean;
|
|
1344
|
+
meta: boolean;
|
|
1345
|
+
ctrl: boolean;
|
|
1346
|
+
}
|
|
1347
|
+
declare const NO_MODS: Modifiers;
|
|
1348
|
+
type PointerButton = "primary" | "secondary" | "middle";
|
|
1349
|
+
/**
|
|
1350
|
+
* Input event consumed by `Surface.dispatch`.
|
|
1351
|
+
*
|
|
1352
|
+
* All coordinates are **screen-space CSS pixels relative to the canvas**.
|
|
1353
|
+
* The surface owns the camera and converts to document-space internally.
|
|
1354
|
+
*/
|
|
1355
|
+
type SurfaceEvent = {
|
|
1356
|
+
kind: "pointer_move";
|
|
1357
|
+
x: number;
|
|
1358
|
+
y: number;
|
|
1359
|
+
mods: Modifiers;
|
|
1360
|
+
} | {
|
|
1361
|
+
kind: "pointer_down";
|
|
1362
|
+
x: number;
|
|
1363
|
+
y: number;
|
|
1364
|
+
button: PointerButton;
|
|
1365
|
+
mods: Modifiers;
|
|
1366
|
+
} | {
|
|
1367
|
+
kind: "pointer_up";
|
|
1368
|
+
x: number;
|
|
1369
|
+
y: number;
|
|
1370
|
+
button: PointerButton;
|
|
1371
|
+
mods: Modifiers;
|
|
1372
|
+
} | {
|
|
1373
|
+
kind: "modifiers";
|
|
1374
|
+
mods: Modifiers;
|
|
1375
|
+
} | {
|
|
1376
|
+
kind: "wheel";
|
|
1377
|
+
x: number;
|
|
1378
|
+
y: number;
|
|
1379
|
+
dx: number;
|
|
1380
|
+
dy: number;
|
|
1381
|
+
mods: Modifiers;
|
|
1382
|
+
} | {
|
|
1383
|
+
kind: "key";
|
|
1384
|
+
phase: "down" | "up";
|
|
1385
|
+
code: string;
|
|
1386
|
+
mods: Modifiers;
|
|
1387
|
+
} | {
|
|
1388
|
+
kind: "blur";
|
|
1389
|
+
};
|
|
1390
|
+
/** Result of a `Surface.dispatch` call. */
|
|
1391
|
+
interface SurfaceResponse {
|
|
1392
|
+
needsRedraw: boolean;
|
|
1393
|
+
cursorChanged: boolean;
|
|
1394
|
+
hoverChanged: boolean;
|
|
1395
|
+
}
|
|
1396
|
+
//#endregion
|
|
1397
|
+
//#region classes/padding/intent.d.ts
|
|
1398
|
+
/**
|
|
1399
|
+
* Drag of a padding handle on a flex-parent container's side. The host
|
|
1400
|
+
* applies the new `value` to the node's `layout_padding_{side}` field.
|
|
1401
|
+
* When `mirror` is true (alt-held), host also applies the same value to
|
|
1402
|
+
* the opposite side; HUD paints both sides "selected" for the duration of
|
|
1403
|
+
* the gesture.
|
|
1404
|
+
*
|
|
1405
|
+
* The intent carries the resolved next padding value — not a pointer
|
|
1406
|
+
* delta. HUD owns the math; the host commits the outcome (D1).
|
|
1407
|
+
*
|
|
1408
|
+
* `value` is in doc-space units. The HUD clamps to 0 (no negative padding);
|
|
1409
|
+
* hosts wanting different clamps post-process.
|
|
1410
|
+
*/
|
|
1411
|
+
type PaddingIntent = {
|
|
1412
|
+
kind: "padding_handle";
|
|
1413
|
+
node_id: NodeId;
|
|
1414
|
+
side: cmath.RectangleSide;
|
|
1415
|
+
value: number;
|
|
1416
|
+
mirror: boolean;
|
|
1417
|
+
phase: IntentPhase;
|
|
1418
|
+
};
|
|
1419
|
+
//#endregion
|
|
1420
|
+
//#region classes/transform-box/intent.d.ts
|
|
1421
|
+
/**
|
|
1422
|
+
* Drag of a transform-box handle. HUD has already reduced the gesture;
|
|
1423
|
+
* the host commits `transform` to whatever field it bound the input to
|
|
1424
|
+
* (image-fit paint transform, free-transform node local transform, …).
|
|
1425
|
+
*
|
|
1426
|
+
* `op` carries the gesture's TYPE and TARGET so the host can
|
|
1427
|
+
* route / log / debug, but never the pointer delta — the host doesn't
|
|
1428
|
+
* need to re-reduce. HUD's reduction is the public outcome (D1 —
|
|
1429
|
+
* subscribe to outcomes, not events).
|
|
1430
|
+
*
|
|
1431
|
+
* `transform` is box-relative (translation normalized [0..1] against
|
|
1432
|
+
* `TransformBoxInput.size`).
|
|
1433
|
+
*/
|
|
1434
|
+
type TransformBoxIntent = {
|
|
1435
|
+
kind: "transform_box";
|
|
1436
|
+
id: string;
|
|
1437
|
+
op: {
|
|
1438
|
+
type: "translate";
|
|
1439
|
+
} | {
|
|
1440
|
+
type: "scale_side";
|
|
1441
|
+
side: cmath.RectangleSide;
|
|
1442
|
+
} | {
|
|
1443
|
+
type: "rotate";
|
|
1444
|
+
corner: cmath.IntercardinalDirection;
|
|
1445
|
+
};
|
|
1446
|
+
transform: AffineTransform;
|
|
1447
|
+
phase: IntentPhase;
|
|
1448
|
+
};
|
|
1449
|
+
//#endregion
|
|
1450
|
+
//#region classes/vector-path/intent.d.ts
|
|
1451
|
+
type VectorPathIntent = {
|
|
1452
|
+
/**
|
|
1453
|
+
* Select a single vertex within a path under content-edit. Mode
|
|
1454
|
+
* mirrors the node-level `select` intent. Hosts dispatch to their
|
|
1455
|
+
* path-edit session's sub-selection state.
|
|
1456
|
+
*/
|
|
1457
|
+
kind: "select_vertex";
|
|
1458
|
+
node_id: NodeId;
|
|
1459
|
+
index: number;
|
|
1460
|
+
mode: SelectMode;
|
|
1461
|
+
} | {
|
|
1462
|
+
/**
|
|
1463
|
+
* Translate one or more vertices of a path under content-edit. The
|
|
1464
|
+
* delta is in document-space, measured from gesture start to the
|
|
1465
|
+
* current frame. Hosts apply the delta to each indexed vertex.
|
|
1466
|
+
*/
|
|
1467
|
+
kind: "translate_vertices";
|
|
1468
|
+
node_id: NodeId;
|
|
1469
|
+
indices: number[];
|
|
1470
|
+
dx: number;
|
|
1471
|
+
dy: number;
|
|
1472
|
+
phase: IntentPhase;
|
|
1473
|
+
} | {
|
|
1474
|
+
/**
|
|
1475
|
+
* Translate the path-edit sub-selection. The host expands its
|
|
1476
|
+
* authoritative sub-selection (selected vertices ∪ endpoints of
|
|
1477
|
+
* selected segments) and UNIONs with `additional_vertex_indices`
|
|
1478
|
+
* before translating. Used by segment-body drag (default — Meta
|
|
1479
|
+
* switches to bend) so a drag of an unselected segment can still
|
|
1480
|
+
* translate its endpoints, and a drag of any item within a multi-
|
|
1481
|
+
* selection translates the whole selection coherently.
|
|
1482
|
+
*/
|
|
1483
|
+
kind: "translate_vector_selection";
|
|
1484
|
+
node_id: NodeId;
|
|
1485
|
+
additional_vertex_indices: readonly number[];
|
|
1486
|
+
dx: number;
|
|
1487
|
+
dy: number;
|
|
1488
|
+
phase: IntentPhase;
|
|
1489
|
+
} | {
|
|
1490
|
+
/**
|
|
1491
|
+
* Clear the path-edit sub-selection (vertices / segments / tangents)
|
|
1492
|
+
* WITHOUT exiting content-edit mode and WITHOUT touching the
|
|
1493
|
+
* host's node-level selection.
|
|
1494
|
+
*
|
|
1495
|
+
* Fires when the user single-clicks empty space while in content-
|
|
1496
|
+
* edit. Mirrors the dblclick `exit_content_edit` pattern — same
|
|
1497
|
+
* "click outside to back off" gesture, one fewer step. Without this
|
|
1498
|
+
* the user has no mouse way to drop a vertex sub-selection short of
|
|
1499
|
+
* leaving content-edit entirely.
|
|
1500
|
+
*/
|
|
1501
|
+
kind: "clear_vector_selection";
|
|
1502
|
+
} | {
|
|
1503
|
+
/**
|
|
1504
|
+
* Select a single segment within a path under content-edit. Mode
|
|
1505
|
+
* mirrors the node-level `select` intent. Fires when the user clicks
|
|
1506
|
+
* a segment OFF the ghost insertion knob — clicking the ghost itself
|
|
1507
|
+
* fires `split_segment` instead.
|
|
1508
|
+
*/
|
|
1509
|
+
kind: "select_segment";
|
|
1510
|
+
node_id: NodeId;
|
|
1511
|
+
segment: number;
|
|
1512
|
+
mode: SelectMode;
|
|
1513
|
+
} | {
|
|
1514
|
+
/**
|
|
1515
|
+
* Select a single closed-loop "region" within a path under
|
|
1516
|
+
* content-edit. Fires when the user clicks the interior body of
|
|
1517
|
+
* a closed loop (no vertex / tangent / segment-strip control
|
|
1518
|
+
* intercepted the click). Mode mirrors the node-level `select`
|
|
1519
|
+
* intent.
|
|
1520
|
+
*
|
|
1521
|
+
* Subsequent drag (if any) promotes to `translate_vector_selection`
|
|
1522
|
+
* — the host applies the delta to the loop's segments and their
|
|
1523
|
+
* endpoint vertices, the same way segment-body drag works today.
|
|
1524
|
+
* No new translate intent kind.
|
|
1525
|
+
*
|
|
1526
|
+
* The host's region commit policy is its own choice: typical
|
|
1527
|
+
* hosts also push the loop's segment indices into
|
|
1528
|
+
* `VectorSubSelection.segments` (so segment chrome highlights too)
|
|
1529
|
+
* and the picked region's id into `VectorSubSelection.regions`
|
|
1530
|
+
* (so the region's `selected` paint shows). The HUD doesn't
|
|
1531
|
+
* presume — it only reports "the user clicked region N."
|
|
1532
|
+
*/
|
|
1533
|
+
kind: "select_region";
|
|
1534
|
+
node_id: NodeId;
|
|
1535
|
+
region: number;
|
|
1536
|
+
mode: SelectMode;
|
|
1537
|
+
} | {
|
|
1538
|
+
/**
|
|
1539
|
+
* Select a single tangent within a path under content-edit. Mode
|
|
1540
|
+
* mirrors the node-level `select` intent.
|
|
1541
|
+
*/
|
|
1542
|
+
kind: "select_tangent";
|
|
1543
|
+
node_id: NodeId; /** `[vertex_idx, 0]` = ta on segment whose a===v; `[v, 1]` = tb where b===v. */
|
|
1544
|
+
tangent: readonly [number, 0 | 1];
|
|
1545
|
+
mode: SelectMode;
|
|
1546
|
+
} | {
|
|
1547
|
+
/**
|
|
1548
|
+
* Move a single tangent control point to a new doc-space position.
|
|
1549
|
+
* The host applies the mirror policy (`auto` infers from current
|
|
1550
|
+
* smooth-join state, `none` only moves the one tangent, `angle`
|
|
1551
|
+
* mirrors the opposite tangent's angle while preserving its length,
|
|
1552
|
+
* `all` mirrors both angle and length).
|
|
1553
|
+
*/
|
|
1554
|
+
kind: "set_tangent";
|
|
1555
|
+
node_id: NodeId;
|
|
1556
|
+
tangent: readonly [number, 0 | 1];
|
|
1557
|
+
pos: cmath.Vector2;
|
|
1558
|
+
mirror: "auto" | "none" | "angle" | "all";
|
|
1559
|
+
phase: IntentPhase;
|
|
1560
|
+
} | {
|
|
1561
|
+
/**
|
|
1562
|
+
* Insert a new vertex on segment `segment` at parametric position
|
|
1563
|
+
* `t ∈ [0,1]`. The split halves inherit the original's verb if
|
|
1564
|
+
* possible; arc groups are broken. Fires once per click — no
|
|
1565
|
+
* preview phase (split is atomic).
|
|
1566
|
+
*/
|
|
1567
|
+
kind: "split_segment";
|
|
1568
|
+
node_id: NodeId;
|
|
1569
|
+
segment: number;
|
|
1570
|
+
t: number;
|
|
1571
|
+
} | {
|
|
1572
|
+
/**
|
|
1573
|
+
* Bend segment `segment` by dragging a point originally at parameter
|
|
1574
|
+
* `ca` toward a doc-space target `cb`. The host re-solves the
|
|
1575
|
+
* segment's tangents to put `B(ca) === cb`, holding the endpoints
|
|
1576
|
+
* fixed. `phase` lets the host bracket a history preview the same
|
|
1577
|
+
* way translate does.
|
|
1578
|
+
*/
|
|
1579
|
+
kind: "bend_segment";
|
|
1580
|
+
node_id: NodeId;
|
|
1581
|
+
segment: number;
|
|
1582
|
+
ca: number;
|
|
1583
|
+
cb: cmath.Vector2;
|
|
1584
|
+
phase: IntentPhase;
|
|
1585
|
+
};
|
|
1586
|
+
//#endregion
|
|
1587
|
+
//#region event/intent.d.ts
|
|
1588
|
+
/** "preview" is emitted on every gesture move; "commit" once on release. */
|
|
1589
|
+
type IntentPhase = "preview" | "commit";
|
|
1590
|
+
/**
|
|
1591
|
+
* Selection mode for `select` intents.
|
|
1592
|
+
*
|
|
1593
|
+
* - `replace` — clear selection, then select the given ids
|
|
1594
|
+
* - `add` — union into the current selection
|
|
1595
|
+
* - `toggle` — flip each given id's membership
|
|
1596
|
+
*/
|
|
1597
|
+
type SelectMode = "replace" | "add" | "toggle";
|
|
1598
|
+
/**
|
|
1599
|
+
* Actionable change emitted by the surface. The host commits the intent
|
|
1600
|
+
* (wrapping in `history.preview` for `phase: "preview"`, finalizing for
|
|
1601
|
+
* `phase: "commit"`).
|
|
1602
|
+
*
|
|
1603
|
+
* The surface itself never mutates the document.
|
|
1604
|
+
*/
|
|
1605
|
+
type Intent = {
|
|
1606
|
+
kind: "select";
|
|
1607
|
+
ids: NodeId[];
|
|
1608
|
+
mode: SelectMode;
|
|
1609
|
+
} | {
|
|
1610
|
+
kind: "deselect_all";
|
|
1611
|
+
} | {
|
|
1612
|
+
kind: "translate";
|
|
1613
|
+
ids: NodeId[]; /** Total delta in document-space, from gesture start. */
|
|
1614
|
+
dx: number;
|
|
1615
|
+
dy: number;
|
|
1616
|
+
phase: IntentPhase;
|
|
1617
|
+
} | {
|
|
1618
|
+
kind: "resize"; /** Member ids of the group being resized (1 or more). */
|
|
1619
|
+
ids: NodeId[];
|
|
1620
|
+
anchor: ResizeDirection;
|
|
1621
|
+
/**
|
|
1622
|
+
* Target rect in document-space. For `transformed` selections this
|
|
1623
|
+
* is the AABB of the new shape — preserved so axis-aligned hosts
|
|
1624
|
+
* that ignore `shape` keep working unchanged.
|
|
1625
|
+
*/
|
|
1626
|
+
rect: Rect;
|
|
1627
|
+
/**
|
|
1628
|
+
* Full target shape. Present whenever the gesture produced one
|
|
1629
|
+
* (which is always, post-Commit 2 of the affine-first plan).
|
|
1630
|
+
* Hosts that handle rotated/sheared selections consume this
|
|
1631
|
+
* directly; legacy hosts can read `rect` and ignore `shape`.
|
|
1632
|
+
*/
|
|
1633
|
+
shape?: SelectionShape;
|
|
1634
|
+
phase: IntentPhase;
|
|
1635
|
+
} | {
|
|
1636
|
+
kind: "rotate"; /** Member ids of the group being rotated (typically 1). */
|
|
1637
|
+
ids: NodeId[]; /** Target angle delta in radians (relative to gesture start). */
|
|
1638
|
+
angle: number;
|
|
1639
|
+
phase: IntentPhase;
|
|
1640
|
+
} | {
|
|
1641
|
+
kind: "marquee_select"; /** Marquee rect in document-space (normalized). */
|
|
1642
|
+
rect: Rect;
|
|
1643
|
+
additive: boolean;
|
|
1644
|
+
phase: IntentPhase;
|
|
1645
|
+
} | {
|
|
1646
|
+
/**
|
|
1647
|
+
* Lasso (freeform polygon) selection. Symmetric to `marquee_select`
|
|
1648
|
+
* — emitted every pointer_move with `phase: "preview"` and on
|
|
1649
|
+
* pointer_up with `phase: "commit"`. The host runs its own hit-test;
|
|
1650
|
+
* for vector content-edit the predicate is
|
|
1651
|
+
* `cmath.polygon.pointInPolygon` against `polygon`.
|
|
1652
|
+
*
|
|
1653
|
+
* Lasso targets vertices and tangents only — segments are NOT tested
|
|
1654
|
+
* against the polygon. The host enforces that constraint; the HUD
|
|
1655
|
+
* just delivers the polygon.
|
|
1656
|
+
*/
|
|
1657
|
+
kind: "lasso_select";
|
|
1658
|
+
/**
|
|
1659
|
+
* Doc-space polygon, oldest-first. Treated as closed
|
|
1660
|
+
* (`polygon[last] → polygon[0]` implicit).
|
|
1661
|
+
*/
|
|
1662
|
+
polygon: cmath.Vector2[];
|
|
1663
|
+
additive: boolean;
|
|
1664
|
+
phase: IntentPhase;
|
|
1665
|
+
} | {
|
|
1666
|
+
kind: "set_endpoint"; /** Subject node id (line-shape selection). */
|
|
1667
|
+
id: NodeId; /** Which endpoint is being moved. */
|
|
1668
|
+
endpoint: "p1" | "p2"; /** Target position in document-space. */
|
|
1669
|
+
pos: cmath.Vector2;
|
|
1670
|
+
phase: IntentPhase;
|
|
1671
|
+
} | {
|
|
1672
|
+
kind: "enter_content_edit";
|
|
1673
|
+
id: NodeId;
|
|
1674
|
+
} | {
|
|
1675
|
+
/**
|
|
1676
|
+
* Exit content-edit mode. Fired by the HUD when the user dblclicks
|
|
1677
|
+
* "away" from the active edit (anywhere not on a vertex / tangent /
|
|
1678
|
+
* segment-strip overlay). No payload — the host knows which node
|
|
1679
|
+
* is under edit (it's the one it most recently pushed a vector
|
|
1680
|
+
* mirror for via `setVectorSelection`).
|
|
1681
|
+
*
|
|
1682
|
+
* Host policy: discard any in-flight preview, clear the vector
|
|
1683
|
+
* mirror (`setVectorSelection(null)`), return to whatever mode the
|
|
1684
|
+
* host considers "outside content-edit." The HUD doesn't presume
|
|
1685
|
+
* what that next mode is.
|
|
1686
|
+
*/
|
|
1687
|
+
kind: "exit_content_edit";
|
|
1688
|
+
} | VectorPathIntent | {
|
|
1689
|
+
/**
|
|
1690
|
+
* Drag of a corner-radius handle on `rect` geometry, default
|
|
1691
|
+
* branch (no alt). For the per-corner case (radii NOT all
|
|
1692
|
+
* equal) `anchor` is the corner the user grabbed; for the
|
|
1693
|
+
* center-handle case (radii all equal) `anchor` is RESOLVED
|
|
1694
|
+
* after the drag-threshold from the pull direction
|
|
1695
|
+
* (`corner → center` vector best matching the negated drag
|
|
1696
|
+
* delta).
|
|
1697
|
+
*
|
|
1698
|
+
* The host decides whether to apply the new `value` to all
|
|
1699
|
+
* four radii or only to the named `anchor`. The HUD doesn't
|
|
1700
|
+
* presume — it tells the host which corner the gesture
|
|
1701
|
+
* names, and the host's interaction model (e.g. "all-equal
|
|
1702
|
+
* stays all-equal during a center drag") picks the policy.
|
|
1703
|
+
* Hosts that want unambiguous single-corner semantics gate
|
|
1704
|
+
* on alt and read `corner_radius_explicit` instead.
|
|
1705
|
+
*/
|
|
1706
|
+
kind: "corner_radius";
|
|
1707
|
+
node_id: NodeId;
|
|
1708
|
+
anchor: "nw" | "ne" | "se" | "sw"; /** Target radius in doc-space units. */
|
|
1709
|
+
value: number;
|
|
1710
|
+
phase: IntentPhase;
|
|
1711
|
+
} | {
|
|
1712
|
+
/**
|
|
1713
|
+
* Same payload as `corner_radius`, but the user held alt
|
|
1714
|
+
* during the drag. Host MUST apply to the named `anchor`
|
|
1715
|
+
* only — never broadcast to other corners regardless of the
|
|
1716
|
+
* surrounding interaction model. Lets the user override an
|
|
1717
|
+
* all-equal default ("alt + drag a single corner makes it
|
|
1718
|
+
* different").
|
|
1719
|
+
*
|
|
1720
|
+
* Distinct kind, not a flag on `corner_radius`, so the host's
|
|
1721
|
+
* commit pipe doesn't have to branch on a hidden modifier
|
|
1722
|
+
* inside the payload — the modifier IS the intent.
|
|
1723
|
+
*/
|
|
1724
|
+
kind: "corner_radius_explicit";
|
|
1725
|
+
node_id: NodeId;
|
|
1726
|
+
anchor: "nw" | "ne" | "se" | "sw";
|
|
1727
|
+
value: number;
|
|
1728
|
+
phase: IntentPhase;
|
|
1729
|
+
} | {
|
|
1730
|
+
/**
|
|
1731
|
+
* Drag of a corner-radius handle on `line` geometry. Lines
|
|
1732
|
+
* have a single `corner_radius` field on the node (not four
|
|
1733
|
+
* per-corner radii), so there is no anchor to resolve and no
|
|
1734
|
+
* alt branch — there's only one knob.
|
|
1735
|
+
*
|
|
1736
|
+
* Host applies the new `value` to the node's singular
|
|
1737
|
+
* `corner_radius` field.
|
|
1738
|
+
*/
|
|
1739
|
+
kind: "corner_radius_uniform";
|
|
1740
|
+
node_id: NodeId;
|
|
1741
|
+
value: number;
|
|
1742
|
+
phase: IntentPhase;
|
|
1743
|
+
} | {
|
|
1744
|
+
/**
|
|
1745
|
+
* Drag of a parametric handle (the universal value-on-curve
|
|
1746
|
+
* affordance introduced by `surface.setParametricHandles`).
|
|
1747
|
+
* Hosts route to their reducer by `(node_id, handle_id)` and
|
|
1748
|
+
* decide policy from `modifiers` themselves — the producer
|
|
1749
|
+
* doesn't interpret `alt` / `shift` semantically.
|
|
1750
|
+
*
|
|
1751
|
+
* `value` is in host-units (whatever the host set on the
|
|
1752
|
+
* handle's `domain`), already snapped to `domain.step` if
|
|
1753
|
+
* configured.
|
|
1754
|
+
*
|
|
1755
|
+
* One intent kind for all consumers — corner-radius's
|
|
1756
|
+
* `corner_radius` / `corner_radius_explicit` / `corner_radius_uniform`
|
|
1757
|
+
* trio is a special case still emitted by `setCornerRadius`
|
|
1758
|
+
* for backward compatibility; new hosts standardize on this.
|
|
1759
|
+
*/
|
|
1760
|
+
kind: "parametric_handle";
|
|
1761
|
+
node_id: NodeId;
|
|
1762
|
+
handle_id: string;
|
|
1763
|
+
value: number;
|
|
1764
|
+
modifiers: {
|
|
1765
|
+
alt: boolean;
|
|
1766
|
+
shift: boolean;
|
|
1767
|
+
};
|
|
1768
|
+
phase: IntentPhase;
|
|
1769
|
+
} | PaddingIntent | TransformBoxIntent | {
|
|
1770
|
+
kind: "cancel_gesture";
|
|
1771
|
+
};
|
|
1772
|
+
/** Callback the host implements to receive intents. */
|
|
1773
|
+
type IntentHandler = (intent: Intent) => void;
|
|
1774
|
+
//#endregion
|
|
1775
|
+
//#region event/transform.d.ts
|
|
1776
|
+
/**
|
|
1777
|
+
* Surface camera transform: axis-aligned (scale + translate only).
|
|
1778
|
+
*
|
|
1779
|
+
* Stored as a `cmath.Transform`:
|
|
1780
|
+
* `[[sx, 0, tx], [0, sy, ty]]`
|
|
1781
|
+
*
|
|
1782
|
+
* Off-diagonal components are ignored; the surface does not support rotation
|
|
1783
|
+
* or shear at the camera level.
|
|
1784
|
+
*/
|
|
1785
|
+
type Transform = cmath.Transform;
|
|
1786
|
+
//#endregion
|
|
1787
|
+
//#region surface/style.d.ts
|
|
1788
|
+
/**
|
|
1789
|
+
* HUD style — colors, sizes, and offsets for the surface-owned chrome.
|
|
1790
|
+
*
|
|
1791
|
+
* All fields are optional; defaults follow.
|
|
1792
|
+
*/
|
|
1793
|
+
interface HUDStyle {
|
|
1794
|
+
/** Primary chrome color (selection outline, handle border). */
|
|
1795
|
+
chromeColor: string;
|
|
1796
|
+
/** Secondary color used for hover outline (lighter than chrome). */
|
|
1797
|
+
hoverColor: string;
|
|
1798
|
+
/** Handle visual size in screen-px. */
|
|
1799
|
+
handleSize: number;
|
|
1800
|
+
/** Handle fill color. */
|
|
1801
|
+
handleFill: string;
|
|
1802
|
+
/** Handle stroke color. */
|
|
1803
|
+
handleStroke: string;
|
|
1804
|
+
/** Selection outline stroke width (in screen-px). */
|
|
1805
|
+
selectionOutlineWidth: number;
|
|
1806
|
+
/** Hover outline stroke width (in screen-px). Typically thicker than selection. */
|
|
1807
|
+
hoverOutlineWidth: number;
|
|
1808
|
+
/** Whether to render rotation handles in addition to resize handles. */
|
|
1809
|
+
showRotationHandles: boolean;
|
|
1810
|
+
/**
|
|
1811
|
+
* Color used for IDLE vector segment outlines. Painted as a thin overlay
|
|
1812
|
+
* stroke on top of each segment to indicate "you are in path edit mode
|
|
1813
|
+
* and these segments are interactive" — independent of the document's
|
|
1814
|
+
* own stroke style. Default is a neutral gray affordance stroke.
|
|
1815
|
+
*/
|
|
1816
|
+
segmentIdleColor: string;
|
|
1817
|
+
/** Stroke width (screen-px) for IDLE segment outlines. */
|
|
1818
|
+
segmentIdleWidth: number;
|
|
1819
|
+
/**
|
|
1820
|
+
* Color used for HOVERED or SELECTED vector segment outlines. Same color
|
|
1821
|
+
* for both — hover differentiates via lower opacity (see `segmentHoverOpacity`).
|
|
1822
|
+
*/
|
|
1823
|
+
segmentActiveColor: string;
|
|
1824
|
+
/** Stroke width (screen-px) for hovered/selected segment outlines. */
|
|
1825
|
+
segmentActiveWidth: number;
|
|
1826
|
+
/** Opacity (0..1) applied to the hovered (not selected) segment outline. */
|
|
1827
|
+
segmentHoverOpacity: number;
|
|
1828
|
+
/** Color for tangent handle lines (vertex → control point). */
|
|
1829
|
+
tangentLineColor: string;
|
|
1830
|
+
/** Stroke width (screen-px) for tangent lines when their tangent is NOT
|
|
1831
|
+
* selected. */
|
|
1832
|
+
tangentLineIdleWidth: number;
|
|
1833
|
+
/** Stroke width (screen-px) for tangent lines when their tangent IS
|
|
1834
|
+
* selected. */
|
|
1835
|
+
tangentLineActiveWidth: number;
|
|
1836
|
+
/**
|
|
1837
|
+
* Paint applied to a region's interior when the cursor is hovering
|
|
1838
|
+
* the loop body (no other vector control claimed the pixel). Hosts
|
|
1839
|
+
* typically use `HUDPaintStripes` here — the canonical
|
|
1840
|
+
* "highlighted region, not committed selection" affordance.
|
|
1841
|
+
*
|
|
1842
|
+
* @see {@link HUDPaintStripes}
|
|
1843
|
+
*/
|
|
1844
|
+
vectorRegionHoverPaint: HUDPaint;
|
|
1845
|
+
/**
|
|
1846
|
+
* Paint applied to a region's interior when the loop is in the
|
|
1847
|
+
* host-pushed `VectorSubSelection.regions` array. Same shape vocabulary
|
|
1848
|
+
* as `vectorRegionHoverPaint`; conventionally a brighter / higher-
|
|
1849
|
+
* opacity variant so selected reads stronger than hover. Hover wins
|
|
1850
|
+
* over selected when both apply (existing precedence in vector chrome).
|
|
1851
|
+
*/
|
|
1852
|
+
vectorRegionSelectedPaint: HUDPaint;
|
|
1853
|
+
/**
|
|
1854
|
+
* Paint applied to a padding-overlay side rect when the cursor is
|
|
1855
|
+
* hovering it (HUD-owned; reads modifier state for axis-mirror).
|
|
1856
|
+
*
|
|
1857
|
+
* @see {@link HUDPaintStripes}
|
|
1858
|
+
*/
|
|
1859
|
+
paddingHoverPaint: HUDPaint;
|
|
1860
|
+
/**
|
|
1861
|
+
* Stroke color for the padding-overlay side rect when a `padding_handle`
|
|
1862
|
+
* gesture is in flight (HUD-derived from `state.gesture`). Painted as an
|
|
1863
|
+
* outline of the side rect — communicating "this is the padding box at
|
|
1864
|
+
* the current value" — instead of the hover stripe pattern. Cleaner read
|
|
1865
|
+
* during the drag than stripes, which clutter the geometry.
|
|
1866
|
+
*
|
|
1867
|
+
* Stroke width: `selectionOutlineWidth`. Fill: none.
|
|
1868
|
+
*
|
|
1869
|
+
* With axis-mirror on (alt-held), the opposite side also outlines.
|
|
1870
|
+
*/
|
|
1871
|
+
paddingSelectedStroke: string;
|
|
1872
|
+
/** Fill color for the padding mid-edge drag handle (visual knob). */
|
|
1873
|
+
paddingHandleFill: string;
|
|
1874
|
+
/** Stroke color (ring) for the padding mid-edge drag handle. */
|
|
1875
|
+
paddingHandleStroke: string;
|
|
1876
|
+
/**
|
|
1877
|
+
* Stroke color for the transform-box quad outline. The quad is the
|
|
1878
|
+
* only visible chrome of the transform-box model in idle — handles
|
|
1879
|
+
* are invisible hit-only by default.
|
|
1880
|
+
*
|
|
1881
|
+
* Default `#a3a3a3` (neutral-400) — a muted stroke that reads as
|
|
1882
|
+
* "transform target boundary" without competing with selection chrome.
|
|
1883
|
+
*/
|
|
1884
|
+
transformBoxStroke: string;
|
|
1885
|
+
/** Fill color for the (invisible by default) transform-box handles. */
|
|
1886
|
+
transformBoxHandleFill: string;
|
|
1887
|
+
/** Stroke color for transform-box handles (visible only in debug). */
|
|
1888
|
+
transformBoxHandleStroke: string;
|
|
1889
|
+
}
|
|
1890
|
+
//#endregion
|
|
1891
|
+
//#region classes/padding/input.d.ts
|
|
1892
|
+
/**
|
|
1893
|
+
* Input pushed by the host to enable padding chrome on a flex-parent
|
|
1894
|
+
* container. Schema-level feature flag — pass `null` (or never call
|
|
1895
|
+
* `setPaddingOverlay`) to disable. Hosts re-push on every padding /
|
|
1896
|
+
* container-rect change.
|
|
1897
|
+
*
|
|
1898
|
+
* @unstable Public shape may change until a second independent integration
|
|
1899
|
+
* ratifies it.
|
|
1900
|
+
*/
|
|
1901
|
+
interface PaddingOverlayInput {
|
|
1902
|
+
node_id: NodeId;
|
|
1903
|
+
/** Container rect in doc-space (the node's bounding rect). */
|
|
1904
|
+
rect: Rect;
|
|
1905
|
+
/**
|
|
1906
|
+
* Per-side padding in doc-space units. Absent or `<= 0` = side not
|
|
1907
|
+
* drawn. Matches the 4-value model used by `layout_padding_{side}`.
|
|
1908
|
+
*/
|
|
1909
|
+
padding: {
|
|
1910
|
+
top?: number;
|
|
1911
|
+
right?: number;
|
|
1912
|
+
bottom?: number;
|
|
1913
|
+
left?: number;
|
|
1914
|
+
};
|
|
1915
|
+
/**
|
|
1916
|
+
* Optional semantic group for visibility policy. The full overlay
|
|
1917
|
+
* fans out per-side rects + per-side handles; all carry the same
|
|
1918
|
+
* group so a single `SurfaceVisibilityPolicy` toggle suppresses the
|
|
1919
|
+
* whole model atomically.
|
|
1920
|
+
*/
|
|
1921
|
+
group?: HUDSemanticGroup;
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Hover state for the padding model — HUD-owned. The padding region's
|
|
1925
|
+
* hover is set on every idle `pointer_move` from the hit-regions
|
|
1926
|
+
* registry; the chrome reads it each frame to render the stripe fill.
|
|
1927
|
+
*
|
|
1928
|
+
* `mirror_side` is non-null when alt is held AND the pointer is on a
|
|
1929
|
+
* `padding_region` — the renderer paints both the hovered side and its
|
|
1930
|
+
* opposite. Cleared on alt release on the next pointer-move (modifier
|
|
1931
|
+
* events alone don't re-derive hover; that's an acceptable v1 cost —
|
|
1932
|
+
* the user will be moving the mouse over the region to see the effect
|
|
1933
|
+
* anyway).
|
|
1934
|
+
*/
|
|
1935
|
+
type PaddingHover = {
|
|
1936
|
+
kind: "padding_region";
|
|
1937
|
+
node_id: NodeId;
|
|
1938
|
+
side: cmath.RectangleSide;
|
|
1939
|
+
mirror_side?: cmath.RectangleSide;
|
|
1940
|
+
} | {
|
|
1941
|
+
kind: "padding_handle";
|
|
1942
|
+
node_id: NodeId;
|
|
1943
|
+
side: cmath.RectangleSide;
|
|
1944
|
+
};
|
|
1945
|
+
//#endregion
|
|
1946
|
+
//#region classes/transform-box/input.d.ts
|
|
1947
|
+
/**
|
|
1948
|
+
* Input pushed by the host to enable transform-box chrome.
|
|
1949
|
+
* Schema-level feature flag — pass `null` (or never call
|
|
1950
|
+
* `setTransformBox`) to disable.
|
|
1951
|
+
*
|
|
1952
|
+
* @unstable Public shape may change as the transform-box model
|
|
1953
|
+
* stabilises. The contract is shaped by a single integration today;
|
|
1954
|
+
* a second independent consumer will ratify it.
|
|
1955
|
+
*/
|
|
1956
|
+
interface TransformBoxInput {
|
|
1957
|
+
/**
|
|
1958
|
+
* Host-defined identity, echoed back on every intent so the host
|
|
1959
|
+
* can correlate (e.g. `"node-1:fill[2]"` for an image-paint
|
|
1960
|
+
* transform, `"node-1:local"` for a free-transform). NOT a NodeId
|
|
1961
|
+
* — the granularity is "one transform" per setter call.
|
|
1962
|
+
*/
|
|
1963
|
+
id: string;
|
|
1964
|
+
/**
|
|
1965
|
+
* Box-relative affine — same shape as AffineTransform (2×3).
|
|
1966
|
+
* Translation components ([0][2], [1][2]) are normalized [0..1]
|
|
1967
|
+
* against `size`.
|
|
1968
|
+
*/
|
|
1969
|
+
transform: AffineTransform;
|
|
1970
|
+
/** Box size in doc-space units. */
|
|
1971
|
+
size: cmath.Vector2;
|
|
1972
|
+
/**
|
|
1973
|
+
* Doc-space anchor for the box's [0,0]. Combined with `rotation`
|
|
1974
|
+
* to project box-local → doc-space. The box's local frame is
|
|
1975
|
+
* rotated by `+rotation` around `origin` to land in doc-space.
|
|
1976
|
+
*/
|
|
1977
|
+
origin: cmath.Vector2;
|
|
1978
|
+
/**
|
|
1979
|
+
* Container rotation in degrees (CCW). When non-zero, the
|
|
1980
|
+
* gesture's doc-space cursor delta is de-rotated by `-rotation`
|
|
1981
|
+
* before being fed to the math reducer.
|
|
1982
|
+
*/
|
|
1983
|
+
rotation?: number;
|
|
1984
|
+
/**
|
|
1985
|
+
* Optional semantic group for visibility policy. All emitted
|
|
1986
|
+
* overlays carry the same group so a single `SurfaceVisibilityPolicy`
|
|
1987
|
+
* toggle suppresses the whole model atomically.
|
|
1988
|
+
*/
|
|
1989
|
+
group?: HUDSemanticGroup;
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Hover state for the transform-box model — HUD-owned. The chrome
|
|
1993
|
+
* reads this each frame; in the current model nothing differs
|
|
1994
|
+
* visually per hover (handles are transparent by default), but the
|
|
1995
|
+
* cursor mapping reads it (`grab` for corner, `ns/ew-resize` for
|
|
1996
|
+
* sides, `move` for body). Cleared on pointer-out / blur.
|
|
1997
|
+
*/
|
|
1998
|
+
type TransformBoxHover = {
|
|
1999
|
+
kind: "transform_box_body";
|
|
2000
|
+
id: string;
|
|
2001
|
+
} | {
|
|
2002
|
+
kind: "transform_box_side";
|
|
2003
|
+
id: string;
|
|
2004
|
+
side: cmath.RectangleSide;
|
|
2005
|
+
} | {
|
|
2006
|
+
kind: "transform_box_corner";
|
|
2007
|
+
id: string;
|
|
2008
|
+
corner: cmath.IntercardinalDirection;
|
|
2009
|
+
};
|
|
2010
|
+
/**
|
|
2011
|
+
* Active gesture descriptor that the host's chrome rebuilder uses to
|
|
2012
|
+
* suppress handles during a drag (mirrors padding-overlay). The
|
|
2013
|
+
* Surface derives this from `state.gesture` when the gesture matches
|
|
2014
|
+
* this input's `id`.
|
|
2015
|
+
*/
|
|
2016
|
+
type TransformBoxActiveOp = {
|
|
2017
|
+
type: "translate";
|
|
2018
|
+
} | {
|
|
2019
|
+
type: "scale_side";
|
|
2020
|
+
side: cmath.RectangleSide;
|
|
2021
|
+
} | {
|
|
2022
|
+
type: "rotate";
|
|
2023
|
+
corner: cmath.IntercardinalDirection;
|
|
2024
|
+
};
|
|
2025
|
+
//#endregion
|
|
2026
|
+
//#region event/hit-regions.d.ts
|
|
2027
|
+
/**
|
|
2028
|
+
* Action a UI hit-region triggers when clicked.
|
|
2029
|
+
*
|
|
2030
|
+
* Each variant carries a snapshot of the relevant shape state at the time
|
|
2031
|
+
* the chrome was built — so the surface can start a gesture without an
|
|
2032
|
+
* extra round-trip to host providers. The chrome builder is the single
|
|
2033
|
+
* source of truth for "what does this hit region act on?"
|
|
2034
|
+
*
|
|
2035
|
+
* - `select_node` — user clicked on a node-representative UI region.
|
|
2036
|
+
* - `resize_handle` — one of 8 resize regions (4 corner knobs + 4 virtual
|
|
2037
|
+
* edges). Carries the group's member ids and the group's initial
|
|
2038
|
+
* `SelectionShape` — `rect` for axis-aligned groups, `transformed` for
|
|
2039
|
+
* rotated/sheared groups (so resize math runs in the local frame).
|
|
2040
|
+
* - `rotate_handle` — one of 4 virtual rotation regions outside the group's
|
|
2041
|
+
* corners. Carries the group's initial `SelectionShape` for center math
|
|
2042
|
+
* (pivot = center of `shapeBounds(initial_shape)`).
|
|
2043
|
+
* - `endpoint_handle` — endpoint of a line-shape selection. Carries the
|
|
2044
|
+
* line's current p1/p2 so dragging is relative to a stable snapshot.
|
|
2045
|
+
* - `translate_handle` — body region covering a selection group's bbox.
|
|
2046
|
+
* Pushed under the corner / edge / rotation regions so resize wins on
|
|
2047
|
+
* overlap. Lets the user grab any part of the selection chrome — including
|
|
2048
|
+
* transparent corners of a circle's bbox — to start a translate. Hud is
|
|
2049
|
+
* the event source once present.
|
|
2050
|
+
*/
|
|
2051
|
+
type OverlayAction = {
|
|
2052
|
+
kind: "select_node";
|
|
2053
|
+
id: NodeId;
|
|
2054
|
+
} | {
|
|
2055
|
+
kind: "resize_handle";
|
|
2056
|
+
direction: ResizeDirection;
|
|
2057
|
+
ids: readonly NodeId[];
|
|
2058
|
+
initial_shape: SelectionShape;
|
|
2059
|
+
} | {
|
|
2060
|
+
kind: "rotate_handle";
|
|
2061
|
+
corner: RotationCorner;
|
|
2062
|
+
ids: readonly NodeId[];
|
|
2063
|
+
initial_shape: SelectionShape;
|
|
2064
|
+
} | {
|
|
2065
|
+
kind: "endpoint_handle";
|
|
2066
|
+
endpoint: "p1" | "p2";
|
|
2067
|
+
id: NodeId; /** Snapshot of the line endpoints in doc-space at chrome build time. */
|
|
2068
|
+
p1: [number, number];
|
|
2069
|
+
p2: [number, number];
|
|
2070
|
+
} | {
|
|
2071
|
+
kind: "translate_handle";
|
|
2072
|
+
ids: readonly NodeId[];
|
|
2073
|
+
} | {
|
|
2074
|
+
/**
|
|
2075
|
+
* A vertex knob on a path being content-edited. Drag → translate the
|
|
2076
|
+
* vertex (and any other vertices co-selected at down-time) in the
|
|
2077
|
+
* path's local frame; click → replace-select the vertex.
|
|
2078
|
+
*/
|
|
2079
|
+
kind: "vertex_handle"; /** Path node id under content-edit. */
|
|
2080
|
+
node_id: NodeId; /** Index of the vertex within the path's vector network. */
|
|
2081
|
+
index: number; /** Doc-space position of the vertex at chrome build time. */
|
|
2082
|
+
pos: [number, number];
|
|
2083
|
+
} | {
|
|
2084
|
+
/**
|
|
2085
|
+
* A tangent control-point knob on a path under content-edit. Drag →
|
|
2086
|
+
* translate the tangent (the host applies mirror policy); click →
|
|
2087
|
+
* replace-select the tangent.
|
|
2088
|
+
*/
|
|
2089
|
+
kind: "tangent_handle";
|
|
2090
|
+
node_id: NodeId; /** `[vertex_idx, 0]` for ta on segment with a===v; `[v, 1]` for tb where b===v. */
|
|
2091
|
+
tangent: readonly [number, 0 | 1]; /** Doc-space position of the tangent control point at chrome build time. */
|
|
2092
|
+
pos: [number, number];
|
|
2093
|
+
} | {
|
|
2094
|
+
/**
|
|
2095
|
+
* A path segment under content-edit, claimed by the chrome's single
|
|
2096
|
+
* per-segment region. Click → split the segment at the **projected**
|
|
2097
|
+
* t (cursor → nearest point on curve). Drag → start a bend gesture
|
|
2098
|
+
* with `ca` frozen to that same projected t.
|
|
2099
|
+
*
|
|
2100
|
+
* Single-candidate insertion model: at any cursor position there is
|
|
2101
|
+
* EXACTLY ONE candidate insertion point per segment — the point on
|
|
2102
|
+
* the cubic closest to the cursor. The action carries the segment's
|
|
2103
|
+
* four doc-space control points; `event/state.ts` computes `t` on
|
|
2104
|
+
* demand via `cmath.bezier.project` at the cursor's current doc-space
|
|
2105
|
+
* position. No pre-sampled grid.
|
|
2106
|
+
*/
|
|
2107
|
+
kind: "segment_strip";
|
|
2108
|
+
node_id: NodeId;
|
|
2109
|
+
segment: number;
|
|
2110
|
+
/** Vertex index of endpoint `a` (used by the segment-drag → translate
|
|
2111
|
+
* path so the host can route the gesture to a vertex translation
|
|
2112
|
+
* without consulting its own segment table). */
|
|
2113
|
+
a_idx: number; /** Vertex index of endpoint `b`. */
|
|
2114
|
+
b_idx: number;
|
|
2115
|
+
/** Doc-space cubic control points. The consumer projects the cursor
|
|
2116
|
+
* against these on demand to get the live `t`. */
|
|
2117
|
+
a: readonly [number, number];
|
|
2118
|
+
b: readonly [number, number];
|
|
2119
|
+
a_control: readonly [number, number];
|
|
2120
|
+
b_control: readonly [number, number];
|
|
2121
|
+
} | {
|
|
2122
|
+
/**
|
|
2123
|
+
* Ghost insertion knob — the visible half-point preview on a hovered
|
|
2124
|
+
* segment. Sits in the priority ladder ABOVE `segment_strip` (so it
|
|
2125
|
+
* wins clicks at the marker) and BELOW `vertex_handle` (so a real
|
|
2126
|
+
* vertex collapsed onto the ghost still wins). Carries the segment's
|
|
2127
|
+
* cubic control points so the consumer can recompute `t` live —
|
|
2128
|
+
* mirrors `segment_strip`, but with explicitly "this is the half-
|
|
2129
|
+
* point control" semantics. Pointer-down dispatches to `split_segment`;
|
|
2130
|
+
* drag promotes to `bend_segment` with `ca` = the live `t`.
|
|
2131
|
+
*/
|
|
2132
|
+
kind: "ghost_handle";
|
|
2133
|
+
node_id: NodeId;
|
|
2134
|
+
segment: number; /** Vertex indices of the segment's endpoints, mirrors `segment_strip`. */
|
|
2135
|
+
a_idx: number;
|
|
2136
|
+
b_idx: number;
|
|
2137
|
+
a: readonly [number, number];
|
|
2138
|
+
b: readonly [number, number];
|
|
2139
|
+
a_control: readonly [number, number];
|
|
2140
|
+
b_control: readonly [number, number];
|
|
2141
|
+
} | {
|
|
2142
|
+
/**
|
|
2143
|
+
* Corner-radius handle — the built-in chrome promoted from labs to
|
|
2144
|
+
* `surface.setCornerRadius(input)`. One action variant covers all
|
|
2145
|
+
* three intent flavors the gesture emits (`corner_radius`,
|
|
2146
|
+
* `corner_radius_explicit`, `corner_radius_uniform`); the surface
|
|
2147
|
+
* branches on `geometry` + modifiers to pick the intent kind at
|
|
2148
|
+
* preview/commit time.
|
|
2149
|
+
*
|
|
2150
|
+
* - `geometry: "rect"`, `anchor: "nw"|"ne"|"se"|"sw"` — one of four
|
|
2151
|
+
* per-corner knobs. Carries the corner being acted on directly;
|
|
2152
|
+
* alt-held drag emits `corner_radius_explicit`, otherwise
|
|
2153
|
+
* `corner_radius`.
|
|
2154
|
+
* - `geometry: "rect"`, `anchor: null` — the singleton center
|
|
2155
|
+
* handle on an all-equal-radii rect. The surface resolves the
|
|
2156
|
+
* pulled-toward corner after the drag-threshold and emits
|
|
2157
|
+
* `corner_radius` / `corner_radius_explicit` from there. Alt
|
|
2158
|
+
* semantics still apply (after the resolution).
|
|
2159
|
+
* - `geometry: "line"`, `anchor: null` — single uniform handle on
|
|
2160
|
+
* the line's `a → b` axis. Emits `corner_radius_uniform`. Alt
|
|
2161
|
+
* has no effect (no anchor to pin).
|
|
2162
|
+
*
|
|
2163
|
+
* The action stores the original `pos` (doc-space anchor of the
|
|
2164
|
+
* handle at chrome build time). The gesture computes the new
|
|
2165
|
+
* radius from the cursor's projection onto the corresponding
|
|
2166
|
+
* geometry axis each frame.
|
|
2167
|
+
*/
|
|
2168
|
+
kind: "corner_radius_handle";
|
|
2169
|
+
node_id: NodeId;
|
|
2170
|
+
geometry: "rect" | "line"; /** Doc-space position of the handle at chrome build time. */
|
|
2171
|
+
pos: readonly [number, number];
|
|
2172
|
+
/**
|
|
2173
|
+
* RECT geometry only — the rect in LOCAL space (axis-aligned).
|
|
2174
|
+
* The gesture re-derives per-anchor corner positions from
|
|
2175
|
+
* this on every projection. When the input carries a
|
|
2176
|
+
* `transform`, this rect is the local AABB and `transform`
|
|
2177
|
+
* maps it to doc space; otherwise the rect IS in doc space.
|
|
2178
|
+
*/
|
|
2179
|
+
rect?: {
|
|
2180
|
+
x: number;
|
|
2181
|
+
y: number;
|
|
2182
|
+
width: number;
|
|
2183
|
+
height: number;
|
|
2184
|
+
};
|
|
2185
|
+
/**
|
|
2186
|
+
* RECT geometry only — optional local → doc affine transform.
|
|
2187
|
+
* Present when the host's selection is a rotated / sheared
|
|
2188
|
+
* rect; absent when axis-aligned. The state machine applies
|
|
2189
|
+
* this when projecting the cursor onto each anchor's
|
|
2190
|
+
* diagonal so a knob on a rotated rect tracks the rotated
|
|
2191
|
+
* axis, not the doc-space one.
|
|
2192
|
+
*/
|
|
2193
|
+
transform?: cmath.Transform;
|
|
2194
|
+
/**
|
|
2195
|
+
* RECT geometry only — the corner anchors this hit region
|
|
2196
|
+
* stands in for. Length 1 for a single-corner knob (sub-max
|
|
2197
|
+
* radii); length 2 for an oblong-max pair (TL/BL or TR/BR);
|
|
2198
|
+
* length 4 for square-max (all four collapsed to one). When
|
|
2199
|
+
* the length is > 1, the state machine resolves the user's
|
|
2200
|
+
* intended anchor from drag direction after a small
|
|
2201
|
+
* threshold, picking AMONG these candidates only.
|
|
2202
|
+
*/
|
|
2203
|
+
candidates?: readonly ("nw" | "ne" | "se" | "sw")[];
|
|
2204
|
+
/**
|
|
2205
|
+
* LINE geometry only — the line endpoints. `a` is the radius-
|
|
2206
|
+
* zero end; `b` is saturation. The gesture projects the cursor
|
|
2207
|
+
* onto a → b.
|
|
2208
|
+
*/
|
|
2209
|
+
a?: readonly [number, number];
|
|
2210
|
+
b?: readonly [number, number];
|
|
2211
|
+
} | {
|
|
2212
|
+
/**
|
|
2213
|
+
* Closed-loop "region" body within a path under content-edit. The
|
|
2214
|
+
* user can click the interior of a closed sub-path to select that
|
|
2215
|
+
* region. Drag from the region body promotes to
|
|
2216
|
+
* `translate_vector_selection` over the loop's segments.
|
|
2217
|
+
*
|
|
2218
|
+
* Hit detection is **polygon-in-screen-space**: the chrome
|
|
2219
|
+
* builder rasterises the cubic loop to N samples per segment,
|
|
2220
|
+
* registers the screen-space AABB of the rasterised polygon,
|
|
2221
|
+
* and pairs it with a `customHitTest` closure that runs
|
|
2222
|
+
* `pointInPolygon`. Mirrors the segment-strip's AABB-plus-
|
|
2223
|
+
* refinement model — see `surface/vector-chrome.ts`.
|
|
2224
|
+
*
|
|
2225
|
+
* Hit-priority (`REGION_PRIORITY = 9` in vector-chrome.ts):
|
|
2226
|
+
* sits just below `SEGMENT_STRIP_PRIORITY` (8), so any vertex /
|
|
2227
|
+
* tangent / ghost / segment-strip control within the loop wins.
|
|
2228
|
+
* The region claims only the "empty body" of the loop.
|
|
2229
|
+
*/
|
|
2230
|
+
kind: "region";
|
|
2231
|
+
node_id: NodeId; /** Region index within `VectorOverlay.regions`. */
|
|
2232
|
+
region: number;
|
|
2233
|
+
/** Segment indices forming the closed loop (carried so the host
|
|
2234
|
+
* can route a region select intent to its segment-aware
|
|
2235
|
+
* selection state without a separate lookup). */
|
|
2236
|
+
segments: readonly number[];
|
|
2237
|
+
/** Union of the loop's endpoint vertex indices. Used by drag
|
|
2238
|
+
* promotion to seed `translate_vector_selection.additional_vertex_indices`
|
|
2239
|
+
* so the gesture can translate the whole loop even before the
|
|
2240
|
+
* host has echoed the select_region back into the sub-selection
|
|
2241
|
+
* mirror. */
|
|
2242
|
+
vertices: readonly number[];
|
|
2243
|
+
} | {
|
|
2244
|
+
/**
|
|
2245
|
+
* Body of a padding-overlay side on a flex-parent container. The
|
|
2246
|
+
* user can hover the inset rect to see the diagonal-stripe affordance
|
|
2247
|
+
* (HUD-owned hover paint). Click without drag is a no-op; drag is
|
|
2248
|
+
* claimed by the paired `padding_handle` action at higher priority.
|
|
2249
|
+
*
|
|
2250
|
+
* The region itself emits no intent; only the handle does.
|
|
2251
|
+
*/
|
|
2252
|
+
kind: "padding_region";
|
|
2253
|
+
node_id: NodeId;
|
|
2254
|
+
side: cmath.RectangleSide;
|
|
2255
|
+
} | {
|
|
2256
|
+
/**
|
|
2257
|
+
* Mid-edge drag knob on a padding-overlay side. Pointer-down opens
|
|
2258
|
+
* a `padding_handle` gesture; drag emits `padding_handle` intents
|
|
2259
|
+
* with the current `value` (clamped at 0) and `mirror` flag (alt
|
|
2260
|
+
* latched per frame). Click-no-drag is a no-op (no commit).
|
|
2261
|
+
*
|
|
2262
|
+
* Carries the container `rect` and the dragged `side` so the gesture
|
|
2263
|
+
* can project the cursor onto the axis and derive `value` without
|
|
2264
|
+
* an extra round-trip through the chrome builder.
|
|
2265
|
+
*/
|
|
2266
|
+
kind: "padding_handle";
|
|
2267
|
+
node_id: NodeId;
|
|
2268
|
+
side: cmath.RectangleSide; /** Container rect at chrome build time (doc-space). */
|
|
2269
|
+
rect: Rect; /** Initial padding value at chrome build time (doc-space units). */
|
|
2270
|
+
initial_value: number;
|
|
2271
|
+
} | {
|
|
2272
|
+
/**
|
|
2273
|
+
* Body interior of a transform-box. Click + drag emits the
|
|
2274
|
+
* `translate` op for the bound transform; pointer-down without
|
|
2275
|
+
* drag is a no-op (no commit).
|
|
2276
|
+
*
|
|
2277
|
+
* `id` is host-defined and echoes back on the intent — see
|
|
2278
|
+
* `TransformBoxInput.id`.
|
|
2279
|
+
*/
|
|
2280
|
+
kind: "transform_box_body";
|
|
2281
|
+
id: string;
|
|
2282
|
+
} | {
|
|
2283
|
+
/**
|
|
2284
|
+
* Side mid-edge of a transform-box. Drag emits the `scale_side`
|
|
2285
|
+
* op. Hit AABB is 12px-thick × side-length, Fitts'-
|
|
2286
|
+
* reach over the visible 1px stroke (D3 — asymmetric outputs).
|
|
2287
|
+
*
|
|
2288
|
+
* `base_angle` is the box's effective screen-space rotation in
|
|
2289
|
+
* RADIANS (CCW positive), i.e. `container.rotation +
|
|
2290
|
+
* decompose(transform).rotation`. The cursor branch in
|
|
2291
|
+
* `decideIdleCursor` passes it through to the `resize` cursor so
|
|
2292
|
+
* the arrow tilts with the visual axis instead of staying
|
|
2293
|
+
* axis-aligned. Mirrors `resize_handle.initial_shape` → cursor
|
|
2294
|
+
* baseAngle.
|
|
2295
|
+
*/
|
|
2296
|
+
kind: "transform_box_side";
|
|
2297
|
+
id: string;
|
|
2298
|
+
side: cmath.RectangleSide;
|
|
2299
|
+
base_angle: number;
|
|
2300
|
+
} | {
|
|
2301
|
+
/**
|
|
2302
|
+
* Corner knob of a transform-box. Drag emits the `rotate` op.
|
|
2303
|
+
* Hit AABB is 16×16 screen-px, Fitts'-reach over the invisible
|
|
2304
|
+
* corner point. `base_angle` — see `transform_box_side`.
|
|
2305
|
+
*/
|
|
2306
|
+
kind: "transform_box_corner";
|
|
2307
|
+
id: string;
|
|
2308
|
+
corner: cmath.IntercardinalDirection;
|
|
2309
|
+
base_angle: number;
|
|
2310
|
+
} | {
|
|
2311
|
+
/**
|
|
2312
|
+
* Parametric handle knob — the user-facing element of a
|
|
2313
|
+
* `surface.setParametricHandles(...)` input. ONE region per
|
|
2314
|
+
* coincidence group (per `parametricHandleLayoutGroups`); when
|
|
2315
|
+
* the group has 2+ members the state machine resolves which
|
|
2316
|
+
* handle the pointer meant from drag direction.
|
|
2317
|
+
*
|
|
2318
|
+
* Carries everything the gesture needs to project the cursor
|
|
2319
|
+
* each frame without consulting the input table again:
|
|
2320
|
+
*
|
|
2321
|
+
* - `pos` — doc-space anchor of the knob at chrome build time.
|
|
2322
|
+
* Used as the screen-space hit anchor too (the gesture
|
|
2323
|
+
* computes a fresh `value` from `point_doc → track`).
|
|
2324
|
+
* - `candidates` — the layout entries this region stands in
|
|
2325
|
+
* for. Single-handle regions have one entry; coincident
|
|
2326
|
+
* regions have N. Each entry carries its handle id, doc-space
|
|
2327
|
+
* track (curve OR point set), and effective domain so the
|
|
2328
|
+
* gesture doesn't need to re-derive them.
|
|
2329
|
+
*/
|
|
2330
|
+
kind: "parametric_knob";
|
|
2331
|
+
node_id: NodeId;
|
|
2332
|
+
pos: readonly [number, number];
|
|
2333
|
+
candidates: readonly {
|
|
2334
|
+
handle_id: string;
|
|
2335
|
+
track_doc: cmath.ui.Curve | cmath.ui.PointSet;
|
|
2336
|
+
domain: {
|
|
2337
|
+
min: number;
|
|
2338
|
+
max: number;
|
|
2339
|
+
step?: number;
|
|
2340
|
+
};
|
|
2341
|
+
}[];
|
|
2342
|
+
};
|
|
2343
|
+
//#endregion
|
|
2344
|
+
//#region event/state.d.ts
|
|
2345
|
+
/**
|
|
2346
|
+
* Vector edit sub-selection — what vertices / segments / tangents of a path
|
|
2347
|
+
* are selected during content-edit. Host pushes this via
|
|
2348
|
+
* `Surface.setVectorSelection(...)` whenever its authoritative state changes;
|
|
2349
|
+
* the chrome reads it each frame.
|
|
2350
|
+
*/
|
|
2351
|
+
interface VectorSubSelection {
|
|
2352
|
+
/** Path node id under content-edit. */
|
|
2353
|
+
node_id: NodeId;
|
|
2354
|
+
vertices: readonly number[];
|
|
2355
|
+
segments: readonly number[];
|
|
2356
|
+
/** `[vertex_idx, 0]` for ta on segment whose a === vertex_idx;
|
|
2357
|
+
* `[vertex_idx, 1]` for tb where b === vertex_idx. */
|
|
2358
|
+
tangents: readonly (readonly [number, 0 | 1])[];
|
|
2359
|
+
/**
|
|
2360
|
+
* Selected region indices (= closed-loop ids in
|
|
2361
|
+
* `VectorOverlay.regions`). Optional for backward compat — hosts that
|
|
2362
|
+
* don't enumerate regions (or don't push a region mirror) leave it
|
|
2363
|
+
* undefined; the chrome treats `undefined` and `[]` identically.
|
|
2364
|
+
*
|
|
2365
|
+
* Drives the region's `selected` visual state. The chrome reads it
|
|
2366
|
+
* each frame.
|
|
2367
|
+
*/
|
|
2368
|
+
regions?: readonly number[];
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Which vector chrome control (if any) is currently under the pointer.
|
|
2372
|
+
* Derived from `hit_regions.hitTest(...)` on every idle pointer_move; the
|
|
2373
|
+
* vector chrome reads it to render hover affordances (segment highlight,
|
|
2374
|
+
* vertex/tangent stroke variant).
|
|
2375
|
+
*
|
|
2376
|
+
* Owned by the HUD — mirrors the "HUD owns hover, host owns selection"
|
|
2377
|
+
* boundary in the README.
|
|
2378
|
+
*/
|
|
2379
|
+
type VectorHover = {
|
|
2380
|
+
kind: "vertex";
|
|
2381
|
+
node_id: NodeId;
|
|
2382
|
+
index: number;
|
|
2383
|
+
} | {
|
|
2384
|
+
kind: "tangent";
|
|
2385
|
+
node_id: NodeId;
|
|
2386
|
+
tangent: readonly [number, 0 | 1];
|
|
2387
|
+
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Cursor is over the segment body, OFF the ghost insertion knob. Drives
|
|
2390
|
+
* the segment's hovered outline + emits the ghost in DEFAULT (idle)
|
|
2391
|
+
* render state at evaluate(t) so the user can see "the insertion point
|
|
2392
|
+
* lives HERE; reach for it."
|
|
2393
|
+
*/
|
|
2394
|
+
| {
|
|
2395
|
+
kind: "segment";
|
|
2396
|
+
node_id: NodeId;
|
|
2397
|
+
segment: number;
|
|
2398
|
+
t: number;
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Cursor is over the ghost insertion knob itself (a higher-priority hit
|
|
2402
|
+
* region that sits on top of the segment-strip — see chrome). Drives
|
|
2403
|
+
* the ghost render in HOVER state and gates the split-on-click: only
|
|
2404
|
+
* this hover variant deferred-clicks to `split_segment`.
|
|
2405
|
+
*/
|
|
2406
|
+
| {
|
|
2407
|
+
kind: "ghost";
|
|
2408
|
+
node_id: NodeId;
|
|
2409
|
+
segment: number;
|
|
2410
|
+
t: number;
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Cursor is inside a closed-loop "region" (a body hit, not a control
|
|
2414
|
+
* knob). Drives the region's hover paint (diagonal stripes). Loses
|
|
2415
|
+
* priority to vertex / tangent / ghost / segment-strip controls
|
|
2416
|
+
* within the same loop's bbox — those win on overlap so the user can
|
|
2417
|
+
* always reach a specific control without the region claiming the
|
|
2418
|
+
* pixel.
|
|
2419
|
+
*/
|
|
2420
|
+
| {
|
|
2421
|
+
kind: "region";
|
|
2422
|
+
node_id: NodeId;
|
|
2423
|
+
region: number;
|
|
2424
|
+
};
|
|
2425
|
+
//#endregion
|
|
2426
|
+
//#region event/overlay.d.ts
|
|
2427
|
+
/**
|
|
2428
|
+
* Minimum hit-target size in screen-px.
|
|
2429
|
+
*
|
|
2430
|
+
* Visual knobs are typically 8px, but the hit region is 16px so users don't
|
|
2431
|
+
* need pixel-perfect aim. Matches `MIN_HIT_SIZE` in the Rust overlay.
|
|
2432
|
+
*/
|
|
2433
|
+
declare const MIN_HIT_SIZE = 16;
|
|
2434
|
+
/**
|
|
2435
|
+
* Below this selection size (in screen-px on either axis), chrome is
|
|
2436
|
+
* suppressed — both the visual handles AND the hit regions. Matches
|
|
2437
|
+
* `MIN_HANDLES_VISIBLE_SIZE` in the Rust overlay.
|
|
2438
|
+
*/
|
|
2439
|
+
declare const MIN_CHROME_VISIBLE_SIZE = 12;
|
|
2440
|
+
/**
|
|
2441
|
+
* A hit region for one overlay element. Always screen-space; either anchored
|
|
2442
|
+
* to a doc-space point (so the hit region tracks the document under camera
|
|
2443
|
+
* changes) or expressed as a pre-projected screen-space AABB (for things
|
|
2444
|
+
* like edge regions whose layout is fundamentally screen-space).
|
|
2445
|
+
*/
|
|
2446
|
+
type HitShape =
|
|
2447
|
+
/**
|
|
2448
|
+
* Fixed screen-space rectangle, centered (or otherwise anchored) on a
|
|
2449
|
+
* doc-space point. The surface projects `anchor_doc` through the current
|
|
2450
|
+
* transform each frame; rect dimensions stay constant in CSS px.
|
|
2451
|
+
*/
|
|
2452
|
+
{
|
|
2453
|
+
kind: "screen_rect_at_doc";
|
|
2454
|
+
anchor_doc: cmath.Vector2; /** Screen-space size in CSS px. */
|
|
2455
|
+
width: number;
|
|
2456
|
+
height: number; /** Which point of the rect sits on the anchor. Default: "center". */
|
|
2457
|
+
placement?: "center" | "tl" | "tr" | "bl" | "br";
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Screen-space AABB at pre-projected screen coordinates. Used for elements
|
|
2461
|
+
* whose layout the chrome builder already projected (e.g. edge strips).
|
|
2462
|
+
*/
|
|
2463
|
+
| {
|
|
2464
|
+
kind: "screen_aabb";
|
|
2465
|
+
rect: Rect;
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Oriented bounding-box hit shape — an axis-aligned `rect` in a *shadow*
|
|
2469
|
+
* coordinate space, plus the affine that maps a screen-space pointer
|
|
2470
|
+
* INTO that space. The hit-test pipeline applies `inverse_transform` to
|
|
2471
|
+
* the pointer and then tests against `rect` with normal AABB containment.
|
|
2472
|
+
*
|
|
2473
|
+
* Used by transformed-chrome zones: the 9-slice runs in an axis-aligned
|
|
2474
|
+
* shadow rect centered at the chrome's screen center, and a rotation
|
|
2475
|
+
* (typically around the same center) maps shadow → screen. Storing the
|
|
2476
|
+
* inverse here lets hit-test stay exact at any rotation/skew without
|
|
2477
|
+
* inflating to an AABB-of-rotated-corners.
|
|
2478
|
+
*/
|
|
2479
|
+
| {
|
|
2480
|
+
kind: "screen_obb";
|
|
2481
|
+
rect: Rect; /** screen → shadow. Applied to the pointer before AABB containment. */
|
|
2482
|
+
inverse_transform: cmath.Transform;
|
|
2483
|
+
};
|
|
2484
|
+
/**
|
|
2485
|
+
* Visual representation for one overlay element. Maps directly to the
|
|
2486
|
+
* primitive layer's `HUDDraw` entries — the surface fans these out into the
|
|
2487
|
+
* one merged `HUDDraw` it hands to `HUDCanvas.draw()` each frame.
|
|
2488
|
+
*
|
|
2489
|
+
* Virtual elements (rotation, side resize) omit `render`.
|
|
2490
|
+
*/
|
|
2491
|
+
type RenderShape = /** Screen-space sized rect at doc anchor — e.g. resize knob. */{
|
|
2492
|
+
kind: "screen_rect";
|
|
2493
|
+
anchor_doc: cmath.Vector2;
|
|
2494
|
+
width: number;
|
|
2495
|
+
height: number;
|
|
2496
|
+
placement?: "center" | "tl" | "tr" | "bl" | "br";
|
|
2497
|
+
fill?: boolean;
|
|
2498
|
+
stroke?: boolean;
|
|
2499
|
+
fillColor?: string;
|
|
2500
|
+
strokeColor?: string;
|
|
2501
|
+
/**
|
|
2502
|
+
* Rotation around the rect's screen-space center, in radians (CCW).
|
|
2503
|
+
* Defaults to 0. Used for knobs/badges rendered for a transformed
|
|
2504
|
+
* selection so they rotate with the parent.
|
|
2505
|
+
*/
|
|
2506
|
+
angle?: number;
|
|
2507
|
+
/**
|
|
2508
|
+
* Render shape — `"rect"` (default) or `"circle"` (ellipse inscribed
|
|
2509
|
+
* in the same bbox). Hit shape is unaffected; this only changes what
|
|
2510
|
+
* the canvas paints. Used by vector chrome for round vertex knobs.
|
|
2511
|
+
*/
|
|
2512
|
+
shape?: "rect" | "circle";
|
|
2513
|
+
} /** Doc-space rect — e.g. selection outline, marquee. */ | {
|
|
2514
|
+
kind: "doc_rect";
|
|
2515
|
+
x: number;
|
|
2516
|
+
y: number;
|
|
2517
|
+
width: number;
|
|
2518
|
+
height: number;
|
|
2519
|
+
stroke?: boolean;
|
|
2520
|
+
fill?: boolean;
|
|
2521
|
+
fillOpacity?: number;
|
|
2522
|
+
dashed?: boolean;
|
|
2523
|
+
} /** Doc-space line — e.g. line-shape selection outline. */ | {
|
|
2524
|
+
kind: "doc_line";
|
|
2525
|
+
x1: number;
|
|
2526
|
+
y1: number;
|
|
2527
|
+
x2: number;
|
|
2528
|
+
y2: number;
|
|
2529
|
+
dashed?: boolean; /** Stroke width in screen-space CSS px. */
|
|
2530
|
+
strokeWidth?: number; /** Override the canvas color for this line. */
|
|
2531
|
+
color?: string;
|
|
2532
|
+
}
|
|
2533
|
+
/** Doc-space polyline — used to draw an open curve as a sequence of
|
|
2534
|
+
* flattened samples, or a closed polygon when `points[last] === points[0]`.
|
|
2535
|
+
* Stroke width is in screen-px (the renderer divides by zoom). Used by
|
|
2536
|
+
* vector chrome to outline each segment of a path under content-edit,
|
|
2537
|
+
* and to fill closed-loop regions with `HUDPaint` (stripes / solid). */
|
|
2538
|
+
| {
|
|
2539
|
+
kind: "doc_polyline";
|
|
2540
|
+
points: ReadonlyArray<readonly [number, number]>;
|
|
2541
|
+
stroke?: boolean;
|
|
2542
|
+
fill?: boolean;
|
|
2543
|
+
fillOpacity?: number;
|
|
2544
|
+
strokeOpacity?: number;
|
|
2545
|
+
strokeWidth?: number;
|
|
2546
|
+
dashed?: boolean;
|
|
2547
|
+
color?: string;
|
|
2548
|
+
/**
|
|
2549
|
+
* Paint applied to the fill. When set, takes precedence over
|
|
2550
|
+
* `color` + `fillOpacity` for the fill. Used by closed-loop
|
|
2551
|
+
* region overlays to apply `HUDPaintStripes` for hover / selected
|
|
2552
|
+
* affordance.
|
|
2553
|
+
*
|
|
2554
|
+
* @unstable
|
|
2555
|
+
*/
|
|
2556
|
+
fillPaint?: HUDPaint;
|
|
2557
|
+
};
|
|
2558
|
+
/**
|
|
2559
|
+
* One interactable element of overlay UI.
|
|
2560
|
+
*
|
|
2561
|
+
* Pairs (visual, event, action) into a single struct so the discipline of
|
|
2562
|
+
* "render box ≠ event box" is explicit. The chrome builder emits a list of
|
|
2563
|
+
* these per frame; the surface fans them into render commands and hit
|
|
2564
|
+
* regions.
|
|
2565
|
+
*
|
|
2566
|
+
* - **Virtual elements** (e.g. rotation handles, edge resize strips) omit
|
|
2567
|
+
* `render` — they exist only as hit regions.
|
|
2568
|
+
* - **Padded elements** have `hit` larger than the corresponding `render`
|
|
2569
|
+
* shape (e.g. 16px hit AABB around an 8px visual knob).
|
|
2570
|
+
*
|
|
2571
|
+
* TODO(rotation): when the first base-rotated overlay lands (e.g. an
|
|
2572
|
+
* in-canvas rotation pip), add an `orientation: "screen" | "base"` field.
|
|
2573
|
+
* Existing call sites default to `"screen"` and don't change.
|
|
2574
|
+
*/
|
|
2575
|
+
interface OverlayElement {
|
|
2576
|
+
/** Stable semantic identifier. Format: `"<kind>[:<param>]"`. Examples:
|
|
2577
|
+
* `"translate"`, `"resize_handle:nw"`, `"resize_edge:n"`,
|
|
2578
|
+
* `"rotate:ne"`, `"endpoint:p1"`. Used by tests and debug tooling. */
|
|
2579
|
+
label: string;
|
|
2580
|
+
/** Semantic owner for group-level visibility policy. */
|
|
2581
|
+
group?: HUDSemanticGroup;
|
|
2582
|
+
action: OverlayAction;
|
|
2583
|
+
hit: HitShape;
|
|
2584
|
+
render?: RenderShape;
|
|
2585
|
+
/** Lower wins. See `HUDHitPriority` in `selection-controls.ts`. */
|
|
2586
|
+
priority: number;
|
|
2587
|
+
cursor?: CursorIcon;
|
|
2588
|
+
/**
|
|
2589
|
+
* Optional refinement layered on top of the AABB hit check. Receives the
|
|
2590
|
+
* screen-space pointer (or shadow-space, when `hit.kind === "screen_obb"`
|
|
2591
|
+
* and the registry has applied `inverse_transform`). Returns false to
|
|
2592
|
+
* reject the hit even though the AABB matched.
|
|
2593
|
+
*
|
|
2594
|
+
* Used by curve-shaped hit regions (e.g. path segments) — the AABB is
|
|
2595
|
+
* the bezier's bbox, the refinement asks "are you actually near the
|
|
2596
|
+
* curve in screen-px?"
|
|
2597
|
+
*/
|
|
2598
|
+
customHitTest?: (point: cmath.Vector2) => boolean;
|
|
2599
|
+
}
|
|
2600
|
+
//#endregion
|
|
2601
|
+
//#region surface/chrome.d.ts
|
|
2602
|
+
interface SurfaceChromeGroups {
|
|
2603
|
+
hover?: HUDSemanticGroup;
|
|
2604
|
+
selection?: HUDSemanticGroup;
|
|
2605
|
+
selectionControls?: HUDSemanticGroup;
|
|
2606
|
+
marquee?: HUDSemanticGroup;
|
|
2607
|
+
/** Lasso polygon (sibling of `marquee`). */
|
|
2608
|
+
lasso?: HUDSemanticGroup;
|
|
2609
|
+
transformPreview?: HUDSemanticGroup;
|
|
2610
|
+
}
|
|
2611
|
+
//#endregion
|
|
2612
|
+
//#region classes/vector-path/input.d.ts
|
|
2613
|
+
/**
|
|
2614
|
+
* Doc-space POJO returned by the host's `vectorOf` callback. The HUD never
|
|
2615
|
+
* imports `@grida/vn` — this minimal shape carries everything chrome needs.
|
|
2616
|
+
*
|
|
2617
|
+
* All coordinates are in doc-space (the HUD's container CSS-px frame). The
|
|
2618
|
+
* host is responsible for projecting from its local frame (e.g. SVG viewBox)
|
|
2619
|
+
* through the camera CTM before handing the data over.
|
|
2620
|
+
*/
|
|
2621
|
+
interface VectorOverlay {
|
|
2622
|
+
/** Vertex positions in doc-space. Index === VertexId. */
|
|
2623
|
+
vertices: ReadonlyArray<readonly [number, number]>;
|
|
2624
|
+
/** Optional — present when the host wants segment chrome, tangent
|
|
2625
|
+
* handles, and segment hit-strips. Each segment carries the four
|
|
2626
|
+
* cubic control points in doc-space (already projected). */
|
|
2627
|
+
segments?: ReadonlyArray<{
|
|
2628
|
+
a: number;
|
|
2629
|
+
b: number;
|
|
2630
|
+
/** Absolute doc-space position of the first cubic control point
|
|
2631
|
+
* (= vertices[a] + ta_local, projected through the host's CTM). */
|
|
2632
|
+
a_control: readonly [number, number];
|
|
2633
|
+
/** Absolute doc-space position of the second cubic control point
|
|
2634
|
+
* (= vertices[b] + tb_local, projected). */
|
|
2635
|
+
b_control: readonly [number, number];
|
|
2636
|
+
}>;
|
|
2637
|
+
/** Vertices whose tangent handles should render. The host computes
|
|
2638
|
+
* this — selected vertices ∪ their 1-hop neighbours (see
|
|
2639
|
+
* `PathModel.neighbouringVertices`). Empty list = no tangent handles
|
|
2640
|
+
* rendered. Spelled `neighbours` (not `neighbouring_vertices`) for
|
|
2641
|
+
* brevity and so it doesn't collide with the main canvas editor's
|
|
2642
|
+
* `selection_neighbouring_vertices` state field — if/when the main
|
|
2643
|
+
* editor adopts this overlay shape, no field-name friction. */
|
|
2644
|
+
neighbours?: ReadonlyArray<number>;
|
|
2645
|
+
/**
|
|
2646
|
+
* Optional — closed-loop "regions" of the path. Each entry names the
|
|
2647
|
+
* segment indices forming one closed loop, in traversal order. The
|
|
2648
|
+
* loop must close (each segment's `b` must match the next segment's
|
|
2649
|
+
* `a`, and the last segment's `b` must match the first's `a`); the
|
|
2650
|
+
* HUD does not validate.
|
|
2651
|
+
*
|
|
2652
|
+
* Schema-level feature flag: absence of this field = backend doesn't
|
|
2653
|
+
* enumerate loops, no region chrome renders. Hosts that can derive
|
|
2654
|
+
* loops (e.g. via `vn.VectorNetworkEditor.getLoops()`) populate it
|
|
2655
|
+
* and pick up the chrome + intent + selection mirror for free.
|
|
2656
|
+
*/
|
|
2657
|
+
regions?: ReadonlyArray<{
|
|
2658
|
+
segments: ReadonlyArray<number>;
|
|
2659
|
+
}>;
|
|
2660
|
+
/** Doc-space offset to add to local vertex coords before rendering.
|
|
2661
|
+
* For hosts that already project to doc-space (most), pass `[0, 0]`. */
|
|
2662
|
+
origin?: readonly [number, number];
|
|
2663
|
+
}
|
|
2664
|
+
//#endregion
|
|
2665
|
+
//#region classes/padding/priority.d.ts
|
|
2666
|
+
/**
|
|
2667
|
+
* Padding drag-handle priority slot. Lower wins.
|
|
2668
|
+
*
|
|
2669
|
+
* 12 — wins over corner-radius (15), every resize control (≥30), translate
|
|
2670
|
+
* body (40), rotate (50). The handle sits at the inner edge of the padding
|
|
2671
|
+
* rect; in practice it doesn't overlap perimeter resize/rotate but the
|
|
2672
|
+
* priority is insurance.
|
|
2673
|
+
*/
|
|
2674
|
+
declare const PADDING_HANDLE_PRIORITY = 12;
|
|
2675
|
+
/**
|
|
2676
|
+
* Padding hover-region priority slot. Lower wins.
|
|
2677
|
+
*
|
|
2678
|
+
* 35 — wins over translate body (40), loses to all resize controls (30/31).
|
|
2679
|
+
* Clicking inside the padding zone of a selected flex container fires
|
|
2680
|
+
* padding hover instead of translate; clicking a corner still resizes.
|
|
2681
|
+
* The padding overlay paints over the selection chrome but loses to
|
|
2682
|
+
* the corner resize handles.
|
|
2683
|
+
*/
|
|
2684
|
+
declare const PADDING_REGION_PRIORITY = 35;
|
|
2685
|
+
/**
|
|
2686
|
+
* Screen-px length of the drag-handle's long axis. The short axis is
|
|
2687
|
+
* `PADDING_HANDLE_THICKNESS`. The 16×2 pill is sized for Fitts'-reach
|
|
2688
|
+
* without dominating the inset side rect visually.
|
|
2689
|
+
*/
|
|
2690
|
+
declare const PADDING_HANDLE_LENGTH = 16;
|
|
2691
|
+
/** Screen-px thickness of the drag-handle's short axis. */
|
|
2692
|
+
declare const PADDING_HANDLE_THICKNESS = 2;
|
|
2693
|
+
//#endregion
|
|
2694
|
+
//#region classes/padding/surface.d.ts
|
|
2695
|
+
/**
|
|
2696
|
+
* Build the per-frame padding overlay primitives.
|
|
2697
|
+
*
|
|
2698
|
+
* Emits one `OverlayElement` per side rect (hover-only, no intent) and
|
|
2699
|
+
* one per drag handle (per-side `padding_handle` action). The two are
|
|
2700
|
+
* keyed by side and share the same semantic `group`.
|
|
2701
|
+
*
|
|
2702
|
+
* Paint resolution:
|
|
2703
|
+
* - idle → no render (transparent body; hit still active)
|
|
2704
|
+
* - hover → `style.paddingHoverPaint`
|
|
2705
|
+
* - selected (= side === active_side, OR alt and side === opposite of
|
|
2706
|
+
* active_side) → outline stroke (no stripe)
|
|
2707
|
+
*
|
|
2708
|
+
* The drag handles are suppressed while any padding drag is in flight —
|
|
2709
|
+
* don't paint knobs the user can't grab.
|
|
2710
|
+
*
|
|
2711
|
+
* The region's hit shape is pre-projected to **screen-space AABB** here
|
|
2712
|
+
* — `transform` is needed so the hit area scales with zoom.
|
|
2713
|
+
*/
|
|
2714
|
+
declare function buildPaddingOverlay(input: {
|
|
2715
|
+
overlay: PaddingOverlayInput;
|
|
2716
|
+
style: HUDStyle;
|
|
2717
|
+
hover: PaddingHover | null;
|
|
2718
|
+
alt_held: boolean;
|
|
2719
|
+
transform: cmath.Transform;
|
|
2720
|
+
active_side?: cmath.RectangleSide;
|
|
2721
|
+
}): OverlayElement[];
|
|
2722
|
+
//#endregion
|
|
2723
|
+
//#region classes/transform-box/priority.d.ts
|
|
2724
|
+
/**
|
|
2725
|
+
* 13 — wins over corner-radius (15) and everything below; loses to
|
|
2726
|
+
* padding-handle (12). Rotate grab should win over corner-radius if
|
|
2727
|
+
* both models overlap on the same node (free-transform case).
|
|
2728
|
+
*/
|
|
2729
|
+
declare const TRANSFORM_BOX_CORNER_PRIORITY = 13;
|
|
2730
|
+
/** 14 — peer to corners on this model; wins over corner-radius (15). */
|
|
2731
|
+
declare const TRANSFORM_BOX_SIDE_PRIORITY = 14;
|
|
2732
|
+
/**
|
|
2733
|
+
* 38 — body translate beats marquee start (40) and selection-translate
|
|
2734
|
+
* body (40); loses to padding-region (35), every resize control (30/31),
|
|
2735
|
+
* and every other handle.
|
|
2736
|
+
*/
|
|
2737
|
+
declare const TRANSFORM_BOX_BODY_PRIORITY = 38;
|
|
2738
|
+
/** Screen-px size of a corner knob (the rotate hit AABB). */
|
|
2739
|
+
declare const TRANSFORM_BOX_CORNER_HIT_SIZE = 16;
|
|
2740
|
+
/** Screen-px thickness of a side mid-edge hit strip. */
|
|
2741
|
+
declare const TRANSFORM_BOX_SIDE_HIT_THICKNESS = 12;
|
|
2742
|
+
//#endregion
|
|
2743
|
+
//#region classes/transform-box/surface.d.ts
|
|
2744
|
+
/**
|
|
2745
|
+
* Build the per-frame transform-box overlay primitives.
|
|
2746
|
+
*
|
|
2747
|
+
* Emits:
|
|
2748
|
+
* - 1 quad outline (visible — `HUDPolyline` stroke).
|
|
2749
|
+
* - 1 body hit polygon (invisible, fill transparent; intercepts events).
|
|
2750
|
+
* - 4 corner hits (invisible 16×16 rects centered on each corner).
|
|
2751
|
+
* - 4 side hit strips (invisible 12px-thick rotated rects along each side).
|
|
2752
|
+
*/
|
|
2753
|
+
declare function buildTransformBox(input: {
|
|
2754
|
+
overlay: TransformBoxInput;
|
|
2755
|
+
style: HUDStyle;
|
|
2756
|
+
/** Reserved — handles are invisible today, so hover does not drive
|
|
2757
|
+
* render. Kept on the contract for the eventual visible-handle
|
|
2758
|
+
* variant (debug overlay, themed knobs). */
|
|
2759
|
+
hover: TransformBoxHover | null;
|
|
2760
|
+
transform: cmath.Transform; /** Reserved — see `hover`. */
|
|
2761
|
+
active_op?: TransformBoxActiveOp;
|
|
2762
|
+
}): OverlayElement[];
|
|
2763
|
+
//#endregion
|
|
2764
|
+
//#region surface/surface.d.ts
|
|
2765
|
+
interface SurfaceVisibilityContext {
|
|
2766
|
+
gesture: SurfaceGesture;
|
|
2767
|
+
}
|
|
2768
|
+
interface SurfaceVisibility {
|
|
2769
|
+
hidden?: Iterable<HUDSemanticGroup>;
|
|
2770
|
+
}
|
|
2771
|
+
type SurfaceVisibilityPolicy = (context: SurfaceVisibilityContext) => SurfaceVisibility | undefined;
|
|
2772
|
+
interface SurfaceOptions {
|
|
2773
|
+
/**
|
|
2774
|
+
* Content pick. Given a doc-space point, return the topmost node id under
|
|
2775
|
+
* the pointer, or `null`. Host wraps its scene query however it wants
|
|
2776
|
+
* (e.g. `elementFromPoint` + `data-id` for SVG-DOM hosts).
|
|
2777
|
+
*/
|
|
2778
|
+
pick: (point_doc: [number, number]) => NodeId | null;
|
|
2779
|
+
/**
|
|
2780
|
+
* Selection shape for a node — what the chrome should wrap. Most nodes
|
|
2781
|
+
* return `{ kind: "rect", rect }`; vector lines return
|
|
2782
|
+
* `{ kind: "line", p1, p2 }`.
|
|
2783
|
+
*/
|
|
2784
|
+
shapeOf: (id: NodeId) => SelectionShape | null;
|
|
2785
|
+
/**
|
|
2786
|
+
* Optional — vector geometry for a node under content-edit. When set
|
|
2787
|
+
* AND `setVectorSelection` has been called with a non-null mirror, the
|
|
2788
|
+
* surface renders vertex chrome (knobs, hit regions) for the named node.
|
|
2789
|
+
*
|
|
2790
|
+
* Hosts that never enter vector-edit mode (or that don't have path-aware
|
|
2791
|
+
* content-edit) can omit this. The chrome simply won't render.
|
|
2792
|
+
*/
|
|
2793
|
+
vectorOf?: (id: NodeId) => VectorOverlay | null;
|
|
2794
|
+
/** Surface emits intents the host commits. */
|
|
2795
|
+
onIntent: IntentHandler;
|
|
2796
|
+
/** Initial style (partial; merged with defaults). */
|
|
2797
|
+
style?: Partial<HUDStyle>;
|
|
2798
|
+
/** Initial readonly flag. Default `false`. */
|
|
2799
|
+
readonly?: boolean;
|
|
2800
|
+
/** Optional HUDCanvas color override. */
|
|
2801
|
+
color?: string;
|
|
2802
|
+
/**
|
|
2803
|
+
* Optional pixel-grid configuration. Drawn back-most in the HUD canvas
|
|
2804
|
+
* when `enabled` and the current zoom exceeds `zoomThreshold`. Hosts can
|
|
2805
|
+
* also call `surface.setPixelGrid(...)` later.
|
|
2806
|
+
*/
|
|
2807
|
+
pixelGrid?: PixelGridConfig | null;
|
|
2808
|
+
/**
|
|
2809
|
+
* Optional ruler configuration. Paints a top + left ruler strip (L-shape)
|
|
2810
|
+
* in screen-space, behind every other HUD primitive. Same two-transform
|
|
2811
|
+
* contract as `pixelGrid`. Hosts can also call `surface.setRuler(...)`
|
|
2812
|
+
* later. The corner square is deliberately left blank.
|
|
2813
|
+
*/
|
|
2814
|
+
ruler?: RulerConfig | null;
|
|
2815
|
+
/**
|
|
2816
|
+
* Optional semantic groups for surface-owned chrome. The HUD package does
|
|
2817
|
+
* not define a group vocabulary; hosts pass the strings they want to use.
|
|
2818
|
+
*/
|
|
2819
|
+
groups?: SurfaceChromeGroups;
|
|
2820
|
+
/**
|
|
2821
|
+
* Host-owned visibility policy. Called per frame with the current surface
|
|
2822
|
+
* gesture; returned groups are filtered from surface chrome and host extras.
|
|
2823
|
+
*/
|
|
2824
|
+
visibility?: SurfaceVisibilityPolicy;
|
|
2825
|
+
/**
|
|
2826
|
+
* Where a click on a segment inserts the new vertex.
|
|
2827
|
+
*
|
|
2828
|
+
* - `"midpoint"` (default) — always at `t=0.5`. The ghost preview appears
|
|
2829
|
+
* at the segment's geometric midpoint on hover; the position is
|
|
2830
|
+
* independent of where on the segment the cursor lands. Predictable
|
|
2831
|
+
* and matches the "owning segment hovers → midpoint becomes visible"
|
|
2832
|
+
* mental model.
|
|
2833
|
+
* - `"projected"` — at the nearest point on the curve to the cursor
|
|
2834
|
+
* (`cmath.bezier.project`). The ghost tracks the cursor along the
|
|
2835
|
+
* curve. More expressive but adds a "where exactly will this land?"
|
|
2836
|
+
* cognitive step.
|
|
2837
|
+
*
|
|
2838
|
+
* Both modes share the same hit-test, chrome rendering, and intent
|
|
2839
|
+
* vocabulary — only the projected `t` differs. Hosts that don't set
|
|
2840
|
+
* this get `"midpoint"` (the predictable default).
|
|
2841
|
+
*/
|
|
2842
|
+
vectorInsertionMode?: VectorInsertionMode;
|
|
2843
|
+
/**
|
|
2844
|
+
* Which gesture an empty-space drag promotes to:
|
|
2845
|
+
* - `"marquee"` (default) — axis-aligned rect selection.
|
|
2846
|
+
* - `"lasso"` — freeform polygon selection.
|
|
2847
|
+
*
|
|
2848
|
+
* Pushed by the host alongside its own tool toggle (e.g. when the host
|
|
2849
|
+
* swaps cursor ↔ lasso tools) via `setVectorSelectionMode`. The HUD reads
|
|
2850
|
+
* it only at empty-space drag promotion; no other branch consults it.
|
|
2851
|
+
* Symmetric with `vectorInsertionMode`.
|
|
2852
|
+
*/
|
|
2853
|
+
vectorSelectionMode?: VectorSelectionMode;
|
|
2854
|
+
/**
|
|
2855
|
+
* Sticky-bend toggle for segment-body drag:
|
|
2856
|
+
* - `"auto"` (default) — Meta-modifier gates the bend gesture.
|
|
2857
|
+
* No Meta → translate; Meta-held → bend.
|
|
2858
|
+
* - `"always"` — segment drag bends regardless of Meta. The host's
|
|
2859
|
+
* bend tool sets this. Released by setting back to `"auto"`.
|
|
2860
|
+
*
|
|
2861
|
+
* Same host-pushed pattern as `vectorSelectionMode`. Affects only the
|
|
2862
|
+
* NEXT segment-drag promotion; in-flight gestures keep their committed
|
|
2863
|
+
* mode.
|
|
2864
|
+
*/
|
|
2865
|
+
vectorBendMode?: VectorBendMode;
|
|
2866
|
+
}
|
|
2867
|
+
/** See {@link SurfaceOptions.vectorInsertionMode}. */
|
|
2868
|
+
type VectorInsertionMode = "midpoint" | "projected";
|
|
2869
|
+
/** See {@link SurfaceOptions.vectorSelectionMode}. */
|
|
2870
|
+
type VectorSelectionMode = "marquee" | "lasso";
|
|
2871
|
+
/** See {@link SurfaceOptions.vectorBendMode}. */
|
|
2872
|
+
type VectorBendMode = "auto" | "always";
|
|
2873
|
+
/**
|
|
2874
|
+
* Top-level wired surface.
|
|
2875
|
+
*
|
|
2876
|
+
* Owns an internal `HUDCanvas`, a `SurfaceState` (gesture/hover/...) and
|
|
2877
|
+
* the host providers. On every `dispatch`, the state machine runs;
|
|
2878
|
+
* `draw` composes surface chrome + host-fed extras into a single canvas
|
|
2879
|
+
* paint.
|
|
2880
|
+
*/
|
|
2881
|
+
declare class Surface {
|
|
2882
|
+
private hudCanvas;
|
|
2883
|
+
private state;
|
|
2884
|
+
private style;
|
|
2885
|
+
private opts;
|
|
2886
|
+
/**
|
|
2887
|
+
* Current corner-radius input, or `null`. Owned here (not in
|
|
2888
|
+
* `SurfaceState`) because the chrome builder needs it AND the
|
|
2889
|
+
* pointer_down handler needs it AND the registry rebuild needs
|
|
2890
|
+
* it — placing it on `SurfaceState` would pull a primitive type
|
|
2891
|
+
* into the event-layer's dependencies, which the README's
|
|
2892
|
+
* dependency arrow forbids. Surface is the wired class; the
|
|
2893
|
+
* setter passes the input to both the canvas (for paint) and
|
|
2894
|
+
* the state (via hit-region overlays each frame).
|
|
2895
|
+
*/
|
|
2896
|
+
private cornerRadius;
|
|
2897
|
+
private parametricHandles;
|
|
2898
|
+
/**
|
|
2899
|
+
* Padding-overlay input. `null` = no chrome drawn. Hosts push on
|
|
2900
|
+
* flex-parent selection; clear on deselect / mode exit. See
|
|
2901
|
+
* `setPaddingOverlay`.
|
|
2902
|
+
*/
|
|
2903
|
+
private paddingOverlay;
|
|
2904
|
+
private colorOverride;
|
|
2905
|
+
private width;
|
|
2906
|
+
private height;
|
|
2907
|
+
private cursor_renderer;
|
|
2908
|
+
constructor(canvas: HTMLCanvasElement, options: SurfaceOptions);
|
|
2909
|
+
/** Switch the vector insertion mode at runtime. Affects both the
|
|
2910
|
+
* hover preview position and the split/bend `t` for the NEXT
|
|
2911
|
+
* pointer event. See {@link SurfaceOptions.vectorInsertionMode}. */
|
|
2912
|
+
setVectorInsertionMode(mode: VectorInsertionMode): void;
|
|
2913
|
+
/** Switch the empty-space-drag selection gesture at runtime
|
|
2914
|
+
* (`marquee` vs `lasso`). Affects only the NEXT pending → drag
|
|
2915
|
+
* promotion; any in-flight gesture keeps running. See
|
|
2916
|
+
* {@link SurfaceOptions.vectorSelectionMode}. */
|
|
2917
|
+
setVectorSelectionMode(mode: VectorSelectionMode): void;
|
|
2918
|
+
/** Switch the sticky-bend toggle at runtime (`auto` vs `always`).
|
|
2919
|
+
* `"always"` is the host's bend tool talking — every segment-drag
|
|
2920
|
+
* bends as if Meta were held. Affects only the NEXT segment-drag
|
|
2921
|
+
* promotion. See {@link SurfaceOptions.vectorBendMode}. */
|
|
2922
|
+
setVectorBendMode(mode: VectorBendMode): void;
|
|
2923
|
+
/** Configure / disable the back-most pixel-grid layer. */
|
|
2924
|
+
setPixelGrid(config: PixelGridConfig | null): void;
|
|
2925
|
+
/**
|
|
2926
|
+
* Update just the pixel grid's transform. Cheap to call per camera tick.
|
|
2927
|
+
* No-op when no pixel-grid config is set.
|
|
2928
|
+
*/
|
|
2929
|
+
setPixelGridTransform(transform: Transform): void;
|
|
2930
|
+
/** Configure / disable the back-most ruler chrome (top + left strips). */
|
|
2931
|
+
setRuler(config: RulerConfig | null): void;
|
|
2932
|
+
/**
|
|
2933
|
+
* Configure or clear the built-in corner-radius chrome.
|
|
2934
|
+
*
|
|
2935
|
+
* Accepts a single input (one node's corner-radius chrome) OR an
|
|
2936
|
+
* array of inputs (multiple nodes editable at once — typical for
|
|
2937
|
+
* a multi-selection of rect-bearing nodes, or for demos that
|
|
2938
|
+
* compare axis-aligned vs rotated rects in the same viewport).
|
|
2939
|
+
* Pass `null` to remove everything. The chrome for each input is
|
|
2940
|
+
* independent: each input's handles, hit regions, and emitted
|
|
2941
|
+
* intents carry the input's own `node_id`. The host distinguishes
|
|
2942
|
+
* by `node_id` when applying the intent.
|
|
2943
|
+
*
|
|
2944
|
+
* The HUD paints handles along the corner→arc-center diagonal
|
|
2945
|
+
* (rect geometry) or the a→b axis (line geometry); on pointer_down
|
|
2946
|
+
* it owns the hit-test; on drag it emits one of `corner_radius` /
|
|
2947
|
+
* `corner_radius_explicit` / `corner_radius_uniform` per the
|
|
2948
|
+
* geometry + modifier table.
|
|
2949
|
+
*
|
|
2950
|
+
* The handle layout is recomputed every frame from the inputs —
|
|
2951
|
+
* camera changes and radius changes both reflect on the next
|
|
2952
|
+
* `draw()` without a host-side `setCornerRadius` round-trip.
|
|
2953
|
+
*
|
|
2954
|
+
* See `@grida/hud/primitives/corner-radius` for the input shape.
|
|
2955
|
+
*/
|
|
2956
|
+
setCornerRadius(input: CornerRadiusInput | readonly CornerRadiusInput[] | null): void;
|
|
2957
|
+
/**
|
|
2958
|
+
* Push one or more parametric-handle inputs — the universal
|
|
2959
|
+
* "scalar value on a 1D manifold" affordance. Each input declares
|
|
2960
|
+
* a node_id, one or more handles (each with curve + value +
|
|
2961
|
+
* optional domain + optional snap-back inset), optional coincidence
|
|
2962
|
+
* groups, and an optional local→doc transform.
|
|
2963
|
+
*
|
|
2964
|
+
* The HUD paints handles along their curves on every `draw()`, owns
|
|
2965
|
+
* the hit-test for the knobs, and emits `parametric_handle` intents
|
|
2966
|
+
* on drag with `{ node_id, handle_id, value, modifiers, phase }`.
|
|
2967
|
+
* Modifier semantics (alt → explicit, etc.) are host-decided —
|
|
2968
|
+
* the producer reports flags only.
|
|
2969
|
+
*
|
|
2970
|
+
* Pass `null` (or `[]`) to remove all parametric chrome. Single
|
|
2971
|
+
* input or array; arrays let one viewport edit multiple nodes
|
|
2972
|
+
* simultaneously. Intents carry their input's `node_id` so the
|
|
2973
|
+
* host routes per-node.
|
|
2974
|
+
*
|
|
2975
|
+
* Hosts typically build inputs through use-case-specific composers
|
|
2976
|
+
* (e.g. the corner-radius primitive builds a 4-handle input over a
|
|
2977
|
+
* rect). The shape itself is generic — anything that can be
|
|
2978
|
+
* expressed as scalars on 1D tracks fits.
|
|
2979
|
+
*/
|
|
2980
|
+
setParametricHandles(input: ParametricHandleInput | readonly ParametricHandleInput[] | null): void;
|
|
2981
|
+
/**
|
|
2982
|
+
* Configure or clear the built-in padding-overlay chrome — the
|
|
2983
|
+
* `padding` named class for flex-parent container padding editing.
|
|
2984
|
+
*
|
|
2985
|
+
* Pass an input to enable the chrome (four inset side rects with
|
|
2986
|
+
* diagonal-stripe hover/selected paint + four mid-edge drag handles).
|
|
2987
|
+
* Pass `null` to disable. Schema-level feature flag — absence is the
|
|
2988
|
+
* off-state, no separate boolean to drift.
|
|
2989
|
+
*
|
|
2990
|
+
* The HUD owns hover (including alt-held axis-mirror visual), reads
|
|
2991
|
+
* the `alt` modifier directly for the intent's `mirror` flag, and
|
|
2992
|
+
* emits `padding_handle` intents on drag (preview-stream + commit).
|
|
2993
|
+
* The host applies the value to its node's `layout_padding_{side}`
|
|
2994
|
+
* field and pushes a re-rendered input back via `setPaddingOverlay`
|
|
2995
|
+
* so the chrome reflects the new value.
|
|
2996
|
+
*
|
|
2997
|
+
* Mid-gesture state mirror: hosts SHOULD push `active_side` while a
|
|
2998
|
+
* `padding_handle` gesture is in flight so the dragged side paints
|
|
2999
|
+
* with the "selected" stripe variant (and the opposite side too,
|
|
3000
|
+
* when alt is held). Clear `active_side` on commit.
|
|
3001
|
+
*
|
|
3002
|
+
* See `@grida/hud/classes/padding` for the input shape and
|
|
3003
|
+
* paint/hit/priority details.
|
|
3004
|
+
*/
|
|
3005
|
+
setPaddingOverlay(input: PaddingOverlayInput | null): void;
|
|
3006
|
+
/**
|
|
3007
|
+
* Push a transform-box input — the `transform-box` named class for
|
|
3008
|
+
* a 2×3 affine transform manipulated via quad outline + 4 corners +
|
|
3009
|
+
* 4 sides + body. `null` = no chrome drawn (schema-level feature
|
|
3010
|
+
* flag).
|
|
3011
|
+
*
|
|
3012
|
+
* Hosts re-push input whenever the bound transform / size / origin
|
|
3013
|
+
* / rotation changes (driven by the host's reducer applying the
|
|
3014
|
+
* `transform_box` intent). The chrome rebuilds every draw.
|
|
3015
|
+
*
|
|
3016
|
+
* See `@grida/hud/classes/transform-box` for the input shape and
|
|
3017
|
+
* hit/priority/anti-goals details.
|
|
3018
|
+
*
|
|
3019
|
+
* @unstable
|
|
3020
|
+
*/
|
|
3021
|
+
setTransformBox(input: TransformBoxInput | null): void;
|
|
3022
|
+
/**
|
|
3023
|
+
* Update just the ruler's transform. Cheap to call per camera tick.
|
|
3024
|
+
* No-op when no ruler config is set.
|
|
3025
|
+
*/
|
|
3026
|
+
setRulerTransform(transform: Transform): void;
|
|
3027
|
+
setSize(w: number, h: number): void;
|
|
3028
|
+
setTransform(t: Transform): void;
|
|
3029
|
+
/**
|
|
3030
|
+
* Push a new selection from the host.
|
|
3031
|
+
*
|
|
3032
|
+
* Accepts either:
|
|
3033
|
+
* - `NodeId[]` — each id becomes its own single-member group, shape
|
|
3034
|
+
* resolved via `shapeOf(id)` by the chrome builder.
|
|
3035
|
+
* - `SelectionGroup[]` — pre-computed groups with their union shape.
|
|
3036
|
+
*
|
|
3037
|
+
* See `SurfaceState.setSelection` for details.
|
|
3038
|
+
*/
|
|
3039
|
+
setSelection(input: readonly NodeId[] | readonly SelectionGroup[]): void;
|
|
3040
|
+
/**
|
|
3041
|
+
* Push a vector-edit sub-selection. Pass `null` to exit vector chrome.
|
|
3042
|
+
*
|
|
3043
|
+
* Non-null: the surface renders vertex knobs for the named path on every
|
|
3044
|
+
* subsequent `draw()`, with selected vertices highlighted. Requires the
|
|
3045
|
+
* host to have wired `vectorOf` in `SurfaceOptions` — otherwise the
|
|
3046
|
+
* chrome silently skips.
|
|
3047
|
+
*
|
|
3048
|
+
* Host calls this on enter / exit of content-edit AND on every sub-
|
|
3049
|
+
* selection change (click, marquee, etc.). Cheap — just swaps a field.
|
|
3050
|
+
*/
|
|
3051
|
+
setVectorSelection(input: VectorSubSelection | null): void;
|
|
3052
|
+
setStyle(partial: Partial<HUDStyle>): void;
|
|
3053
|
+
/**
|
|
3054
|
+
* Set or clear the host color override. `null` clears the override and
|
|
3055
|
+
* lets `style.chromeColor` win on the next paint.
|
|
3056
|
+
*/
|
|
3057
|
+
setColor(color: string | null): void;
|
|
3058
|
+
setReadonly(v: boolean): void;
|
|
3059
|
+
/**
|
|
3060
|
+
* Set or clear a host-driven hover override.
|
|
3061
|
+
*
|
|
3062
|
+
* The surface tracks two hover sources:
|
|
3063
|
+
* - **Pointer pick** — what scene content is under the cursor (updated
|
|
3064
|
+
* automatically on `pointer_move`).
|
|
3065
|
+
* - **Host override** — what the host wants to show as hovered, e.g.
|
|
3066
|
+
* from a layers panel row mouseenter.
|
|
3067
|
+
*
|
|
3068
|
+
* The override (when non-null) wins. `hover()` returns the effective
|
|
3069
|
+
* value; chrome renders the effective value. Pass `null` to clear and
|
|
3070
|
+
* fall back to pointer pick.
|
|
3071
|
+
*
|
|
3072
|
+
* Returns the same response shape as `dispatch` so the host can react
|
|
3073
|
+
* to whether anything actually changed.
|
|
3074
|
+
*/
|
|
3075
|
+
setHoverOverride(id: NodeId | null): SurfaceResponse;
|
|
3076
|
+
dispose(): void;
|
|
3077
|
+
dispatch(event: SurfaceEvent): SurfaceResponse;
|
|
3078
|
+
draw(extra?: HUDDraw): void;
|
|
3079
|
+
/** Convenience: clear the canvas (e.g. when the host stops the surface). */
|
|
3080
|
+
clear(): void;
|
|
3081
|
+
gesture(): SurfaceGesture;
|
|
3082
|
+
/**
|
|
3083
|
+
* The effective hover: host override (when set) wins over pointer pick.
|
|
3084
|
+
* Use this for chrome decisions and host-side reads.
|
|
3085
|
+
*/
|
|
3086
|
+
hover(): NodeId | null;
|
|
3087
|
+
cursor(): CursorIcon;
|
|
3088
|
+
/**
|
|
3089
|
+
* Resolve the current cursor to a CSS `cursor:` value. Runs the
|
|
3090
|
+
* installed renderer (or the built-in `cursorToCss` if none installed).
|
|
3091
|
+
*
|
|
3092
|
+
* Host wires it like:
|
|
3093
|
+
*
|
|
3094
|
+
* const r = surface.dispatch(event);
|
|
3095
|
+
* if (r.cursorChanged) el.style.cursor = surface.cursorCss();
|
|
3096
|
+
*
|
|
3097
|
+
* Saves the host from re-importing `cursorToCss` after every dispatch
|
|
3098
|
+
* and gives one place to change behavior when a renderer is swapped in.
|
|
3099
|
+
*/
|
|
3100
|
+
cursorCss(): string;
|
|
3101
|
+
/**
|
|
3102
|
+
* Install (or clear) a custom cursor renderer.
|
|
3103
|
+
*
|
|
3104
|
+
* `null` restores the built-in `cursorToCss` behavior (native CSS
|
|
3105
|
+
* keywords for every variant). Pass `cursors.defaultRenderer()` from
|
|
3106
|
+
* `@grida/hud/cursors` for the bundled SVG cursor set.
|
|
3107
|
+
*
|
|
3108
|
+
* Re-callable mid-session; the next `cursorCss()` reads the new value.
|
|
3109
|
+
*/
|
|
3110
|
+
setCursorRenderer(fn: CursorRenderer | null): void;
|
|
3111
|
+
modifiers(): Modifiers;
|
|
3112
|
+
/**
|
|
3113
|
+
* Resolve the current corner-radius layout against `shapeOf` (for
|
|
3114
|
+
* the rect-fallback) and the camera. Always returns an array;
|
|
3115
|
+
* empty when the input is null or when the rect can't be
|
|
3116
|
+
* resolved.
|
|
3117
|
+
*/
|
|
3118
|
+
private buildCornerRadiusHandles;
|
|
3119
|
+
/**
|
|
3120
|
+
* Push one hit region per handle onto the state's registry. Called
|
|
3121
|
+
* AFTER `fanOverlays` so the regions survive the per-frame clear.
|
|
3122
|
+
*/
|
|
3123
|
+
private registerCornerRadiusHitRegions;
|
|
3124
|
+
/**
|
|
3125
|
+
* Resolve the current parametric-handle layout for one input. The
|
|
3126
|
+
* snap-back floor is lifted while this specific input is being
|
|
3127
|
+
* dragged — match by `node_id` so other inputs in the same canvas
|
|
3128
|
+
* keep their resting positions.
|
|
3129
|
+
*/
|
|
3130
|
+
private buildParametricHandleLayout;
|
|
3131
|
+
/**
|
|
3132
|
+
* Push one hit region per coincidence group onto the state's
|
|
3133
|
+
* registry. Coincidence is geometric — a declared group only
|
|
3134
|
+
* collapses when its members actually overlap in doc-space. Open
|
|
3135
|
+
* groups produce one region per handle.
|
|
3136
|
+
*/
|
|
3137
|
+
private registerParametricHandleHitRegions;
|
|
3138
|
+
}
|
|
3139
|
+
//#endregion
|
|
3140
|
+
export { computeStripesTileGeometry as $, DEFAULT_RULER_STRIP as $t, TransformBoxHover as A, resolveCenterDragAnchor as At, PointerButton as B, compose as Bt, MIN_CHROME_VISIBLE_SIZE as C, DrawCornerRadiusParams as Ct, VectorHover as D, cornerRadiusHandlePosRect as Dt, RenderShape as E, cornerRadiusHandlePosLine as Et, Intent as F, SelectionShape as Ft, measurementToHUDDraw as G, DEFAULT_RULER_ACCENT_BACKGROUND as Gt, SurfaceResponse as H, decompose as Ht, IntentPhase as I, AffineTransform as It, DEFAULT_STRIPES_ANGLE_DEG as J, DEFAULT_RULER_COLOR as Jt, snapGuideToHUDDraw as K, DEFAULT_RULER_ACCENT_COLOR as Kt, SelectMode as L, TransformBoxAction as Lt, PaddingHover as M, Rect as Mt, PaddingOverlayInput as N, SurfaceGesture as Nt, VectorSubSelection as O, cornerRadiusLayoutGroups as Ot, HUDStyle as P, SelectionGroup as Pt, buildStripesTile as Q, DEFAULT_RULER_STEPS as Qt, Modifiers as R, TransformBoxCorners as Rt, HitShape as S, DEFAULT_CORNER_RADIUS_HIT_SIZE as St, OverlayElement as T, cornerRadiusAnchorSign as Tt, lassoToHUDDraw as U, getTransformBoxCorners as Ut, SurfaceEvent as V, cornersToBoxTransform as Vt, marqueeToHUDDraw as W, reduceTransformBox as Wt, DEFAULT_STRIPES_THICKNESS_PX as X, DEFAULT_RULER_FONT as Xt, DEFAULT_STRIPES_SPACING_PX as Y, DEFAULT_RULER_DRAG_THRESHOLD as Yt, ResolvedPaint as Z, DEFAULT_RULER_OVERLAP_THRESHOLD as Zt, PADDING_HANDLE_PRIORITY as _, CornerRadiusHandleLayout as _t, SurfaceVisibilityPolicy as a, RulerMark as an, DEFAULT_PARAMETRIC_HIT_SIZE as at, VectorOverlay as b, DEFAULT_CORNER_RADIUS_HANDLE_INSET as bt, VectorSelectionMode as c, DEFAULT_PIXEL_GRID_COLOR as cn, ParametricHandleGroup as ct, TRANSFORM_BOX_CORNER_HIT_SIZE as d, PixelGridConfig as dn, computeParametricHandleLayout as dt, DEFAULT_RULER_TEXT_SIDE_OFFSET as en, resolvePaint as et, TRANSFORM_BOX_CORNER_PRIORITY as f, drawPixelGrid as fn, drawParametricHandles as ft, PADDING_HANDLE_LENGTH as g, CornerRadiusAnchor as gt, buildPaddingOverlay as h, resolveParametricHandleByDirection as ht, SurfaceVisibilityContext as i, RulerConfig as in, DEFAULT_PARAMETRIC_HANDLE_SIZE as it, TransformBoxInput as j, resolveCornerDragAnchor as jt, TransformBoxActiveOp as k, drawCornerRadius as kt, buildTransformBox as l, DEFAULT_PIXEL_GRID_STEPS as ln, ParametricHandleInput as lt, TRANSFORM_BOX_SIDE_PRIORITY as m, projectParametricHandleValue as mt, SurfaceOptions as n, DrawRulerParams as nn, HUDCanvasOptions as nt, VectorBendMode as o, RulerRange as on, DrawParametricHandlesParams as ot, TRANSFORM_BOX_SIDE_HIT_THICKNESS as p, parametricHandleLayoutGroups as pt, filterHUDDrawByGroup as q, DEFAULT_RULER_BACKGROUND as qt, SurfaceVisibility as r, RulerAxis as rn, DEFAULT_PARAMETRIC_HANDLE_INSET as rt, VectorInsertionMode as s, drawRuler as sn, ParametricHandle as st, Surface as t, DEFAULT_RULER_TICK_HEIGHT as tn, HUDCanvas as tt, TRANSFORM_BOX_BODY_PRIORITY as u, DrawPixelGridParams as un, ParametricHandleLayout as ut, PADDING_HANDLE_THICKNESS as v, CornerRadiusInput as vt, MIN_HIT_SIZE as w, computeCornerRadiusLayout as wt, SurfaceChromeGroups as x, DEFAULT_CORNER_RADIUS_HANDLE_SIZE as xt, PADDING_REGION_PRIORITY as y, CornerRadiusRectangular as yt, NO_MODS as z, TransformBoxOptions as zt };
|