insomni 0.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +674 -0
- package/README.md +234 -0
- package/dist/advanced.d.mts +76 -0
- package/dist/advanced.mjs +81 -0
- package/dist/assemble-BT3CXbSx.mjs +1574 -0
- package/dist/camera-view-DHmMiKvP.d.mts +326 -0
- package/dist/frame-mHNdKRpF.mjs +135 -0
- package/dist/index-CmMZCMJT.d.mts +39 -0
- package/dist/index-DkJfpntS.d.mts +2417 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +6612 -0
- package/dist/internal.d.mts +892 -0
- package/dist/internal.mjs +566 -0
- package/dist/logger-DSyBF3Y_.mjs +15 -0
- package/dist/particles.d.mts +816 -0
- package/dist/particles.mjs +4804 -0
- package/dist/pipeline-BWCAZTKx.mjs +470 -0
- package/dist/pipeline-DE3a1Pnk.d.mts +115 -0
- package/dist/reactivity-B7I0pvzm.mjs +191 -0
- package/dist/reactivity.d.mts +2 -0
- package/dist/reactivity.mjs +2 -0
- package/dist/renderer-DzZqd1bY.d.mts +4566 -0
- package/dist/root-CHradZKM.mjs +30 -0
- package/dist/shape-DfZP9Jdk.mjs +349 -0
- package/dist/space-CeDnj6eu.mjs +11240 -0
- package/dist/spatial-Bd3Ay8I2.d.mts +85 -0
- package/dist/spatial-hash-C1crBjTo.mjs +77 -0
- package/dist/spatial.d.mts +2 -0
- package/dist/spatial.mjs +121 -0
- package/dist/text-font-D7GGDtTK.d.mts +185 -0
- package/dist/text-ttf.d.mts +91 -0
- package/dist/text-ttf.mjs +298 -0
- package/dist/texture-dABoqFoP.mjs +131 -0
- package/dist/viewport.d.mts +2 -0
- package/dist/viewport.mjs +274 -0
- package/package.json +69 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
//#region src/math/vec2.d.ts
|
|
2
|
+
interface Vec2 {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
declare function vec2(x: number, y: number): Vec2;
|
|
7
|
+
declare function add(a: Vec2, b: Vec2): Vec2;
|
|
8
|
+
declare function sub(a: Vec2, b: Vec2): Vec2;
|
|
9
|
+
declare function scale(v: Vec2, s: number): Vec2;
|
|
10
|
+
declare function length(v: Vec2): number;
|
|
11
|
+
declare function normalize(v: Vec2): Vec2;
|
|
12
|
+
declare function dot(a: Vec2, b: Vec2): number;
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/frame.d.ts
|
|
15
|
+
interface FrameRect {
|
|
16
|
+
readonly x: number;
|
|
17
|
+
readonly y: number;
|
|
18
|
+
readonly width: number;
|
|
19
|
+
readonly height: number;
|
|
20
|
+
}
|
|
21
|
+
declare const ZERO_RECT: FrameRect;
|
|
22
|
+
/**
|
|
23
|
+
* Padding value for `Frame.padded()`:
|
|
24
|
+
*
|
|
25
|
+
* - `number` — applied to all four sides (absolute pixels).
|
|
26
|
+
* - `{ all }` — same as a number.
|
|
27
|
+
* - `{ top, right, bottom, left }` — per-side overrides (any missing key is 0).
|
|
28
|
+
* - `{ ratio }` — fractional inset based on the frame's shorter side
|
|
29
|
+
* (e.g. `{ ratio: 0.1 }` = 10% of min(width, height) on every side).
|
|
30
|
+
*/
|
|
31
|
+
type Padding = number | {
|
|
32
|
+
all?: number;
|
|
33
|
+
top?: number;
|
|
34
|
+
right?: number;
|
|
35
|
+
bottom?: number;
|
|
36
|
+
left?: number;
|
|
37
|
+
} | {
|
|
38
|
+
ratio: number;
|
|
39
|
+
};
|
|
40
|
+
interface Frame extends FrameRect {
|
|
41
|
+
/** Top-left corner of the frame. */
|
|
42
|
+
readonly topLeft: Vec2;
|
|
43
|
+
/** Top-right corner of the frame. */
|
|
44
|
+
readonly topRight: Vec2;
|
|
45
|
+
/** Bottom-left corner of the frame. */
|
|
46
|
+
readonly bottomLeft: Vec2;
|
|
47
|
+
/** Bottom-right corner of the frame. */
|
|
48
|
+
readonly bottomRight: Vec2;
|
|
49
|
+
/** Geometric center of the frame. */
|
|
50
|
+
readonly center: Vec2;
|
|
51
|
+
/** Plain-object rect (useful for `layer.pushRect`). */
|
|
52
|
+
readonly rect: FrameRect;
|
|
53
|
+
/**
|
|
54
|
+
* Return a nested frame inset by `padding` on each side. The outer frame is
|
|
55
|
+
* not mutated.
|
|
56
|
+
*/
|
|
57
|
+
padded(padding: Padding): Frame;
|
|
58
|
+
/**
|
|
59
|
+
* Return a frame translated by `(dx, dy)` without changing width or height.
|
|
60
|
+
*/
|
|
61
|
+
translated(dx: number, dy: number): Frame;
|
|
62
|
+
/**
|
|
63
|
+
* Return a frame whose origin is the same but width/height are replaced.
|
|
64
|
+
*/
|
|
65
|
+
resized(width: number, height: number): Frame;
|
|
66
|
+
}
|
|
67
|
+
/** Create a frame from an `{x, y, width, height}` rect. */
|
|
68
|
+
declare function createFrame(rect: FrameRect): Frame;
|
|
69
|
+
/**
|
|
70
|
+
* Convenience: create a frame covering `(0, 0)` to `(width, height)`.
|
|
71
|
+
* Useful as the outermost container for a canvas or SVG viewport.
|
|
72
|
+
*/
|
|
73
|
+
declare function viewportFrame(width: number, height: number): Frame;
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/math/mat3.d.ts
|
|
76
|
+
/**
|
|
77
|
+
* 3×3 column-major matrix for 2D affine transforms.
|
|
78
|
+
*
|
|
79
|
+
* Layout: [col0.x, col0.y, col0.z, col1.x, col1.y, col1.z, col2.x, col2.y, col2.z]
|
|
80
|
+
*
|
|
81
|
+
* | m[0] m[3] m[6] |
|
|
82
|
+
* | m[1] m[4] m[7] |
|
|
83
|
+
* | m[2] m[5] m[8] |
|
|
84
|
+
*/
|
|
85
|
+
type Mat3 = readonly [number, number, number, number, number, number, number, number, number];
|
|
86
|
+
declare const IDENTITY: Mat3;
|
|
87
|
+
declare function translation(tx: number, ty: number): Mat3;
|
|
88
|
+
declare function scaling(sx: number, sy?: number): Mat3;
|
|
89
|
+
declare function rotation(radians: number): Mat3;
|
|
90
|
+
/** Scale around an arbitrary center point. */
|
|
91
|
+
declare function scalingAt(sx: number, sy: number, cx: number, cy: number): Mat3;
|
|
92
|
+
/** Rotate around an arbitrary center point. */
|
|
93
|
+
declare function rotationAt(radians: number, cx: number, cy: number): Mat3;
|
|
94
|
+
/** Matrix multiplication: result applies `b` first, then `a`. */
|
|
95
|
+
declare function multiply(a: Mat3, b: Mat3): Mat3;
|
|
96
|
+
/** Returns the inverse, or null if the matrix is singular. */
|
|
97
|
+
declare function invert(m: Mat3): Mat3 | null;
|
|
98
|
+
/** Apply the matrix to a 2D point. */
|
|
99
|
+
declare function transformPoint(m: Mat3, x: number, y: number): Vec2;
|
|
100
|
+
/** Orthographic projection: maps pixel coords (0,0)→NDC(-1,1), (w,h)→NDC(1,-1). */
|
|
101
|
+
declare function projection(width: number, height: number): Mat3;
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/viewport.d.ts
|
|
104
|
+
/**
|
|
105
|
+
* Per-axis margin spec. A bare number applies to both axes. Values are
|
|
106
|
+
* fractions of the *visible* span and apply symmetrically to both edges.
|
|
107
|
+
*/
|
|
108
|
+
type PanBoundsMargin = number | {
|
|
109
|
+
x?: number;
|
|
110
|
+
y?: number;
|
|
111
|
+
};
|
|
112
|
+
interface PanBoundsMargins {
|
|
113
|
+
/** Pan-past-content allowance when content is larger than the viewport. */
|
|
114
|
+
overshoot?: PanBoundsMargin;
|
|
115
|
+
/** Content drift allowance when content fits inside the viewport. */
|
|
116
|
+
drift?: PanBoundsMargin;
|
|
117
|
+
}
|
|
118
|
+
interface CameraPanBoundsOptions extends PanBoundsMargins {
|
|
119
|
+
/**
|
|
120
|
+
* World-space rect that the camera is constrained to keep in view.
|
|
121
|
+
* Required for camera-mode clamping (no implicit content bounds exist).
|
|
122
|
+
*/
|
|
123
|
+
content: FrameRect;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Structural shape the renderer / Layer / interaction pipeline needs from a
|
|
127
|
+
* viewport. Both core's `CameraViewport` and plot's `DataViewport` satisfy
|
|
128
|
+
* this — keeps core decoupled from the data-mode viewport that lives in plot.
|
|
129
|
+
*/
|
|
130
|
+
interface ViewportLike {
|
|
131
|
+
/** `"camera"` for core viewports; plot viewports report their own mode. */
|
|
132
|
+
readonly mode: string;
|
|
133
|
+
readonly frame: Frame;
|
|
134
|
+
readonly absoluteFrame: Frame;
|
|
135
|
+
readonly parent: ViewportLike | null;
|
|
136
|
+
readonly camera: ResolvedCameraState;
|
|
137
|
+
readonly clipRect: FrameRect;
|
|
138
|
+
onChange(cb: () => void): () => void;
|
|
139
|
+
}
|
|
140
|
+
/** Structural-shape alias for {@link ViewportLike}. */
|
|
141
|
+
type Viewport = ViewportLike;
|
|
142
|
+
interface CameraViewportOptions {
|
|
143
|
+
/** Frame in pixel space (relative to parent if `parent` is supplied). */
|
|
144
|
+
frame: Frame;
|
|
145
|
+
/** Optional parent viewport — `frame` is interpreted in the parent's space. */
|
|
146
|
+
parent?: ViewportLike;
|
|
147
|
+
/** Initial camera state. Any missing field defaults to its identity (0 / 1). */
|
|
148
|
+
camera?: CameraState;
|
|
149
|
+
/**
|
|
150
|
+
* Constrain the camera so that `content` stays in (or near) the viewport.
|
|
151
|
+
* When zoomed in, panning stops at content edges (plus margin); when zoomed
|
|
152
|
+
* out so content fits, content cannot leave the viewport. Zoom remains
|
|
153
|
+
* unrestricted.
|
|
154
|
+
*/
|
|
155
|
+
panBounds?: CameraPanBoundsOptions;
|
|
156
|
+
}
|
|
157
|
+
type ZoomFactor = number | {
|
|
158
|
+
x?: number;
|
|
159
|
+
y?: number;
|
|
160
|
+
};
|
|
161
|
+
interface CameraViewport {
|
|
162
|
+
readonly mode: "camera";
|
|
163
|
+
/** Frame relative to `parent` (or absolute if no parent). */
|
|
164
|
+
readonly frame: Frame;
|
|
165
|
+
/** Frame in root / canvas space — walks the parent chain on each read. */
|
|
166
|
+
readonly absoluteFrame: Frame;
|
|
167
|
+
/** Parent viewport, if this is a nested viewport. */
|
|
168
|
+
readonly parent: ViewportLike | null;
|
|
169
|
+
/** Mutable rect equal to `absoluteFrame`, refreshed in place on each access. */
|
|
170
|
+
readonly clipRect: FrameRect;
|
|
171
|
+
/** Resolved camera state. */
|
|
172
|
+
readonly camera: ResolvedCameraState;
|
|
173
|
+
/** Pan by pixel deltas in this viewport's frame space. */
|
|
174
|
+
panBy(dxPx: number, dyPx: number): void;
|
|
175
|
+
/** Zoom around a screen-pixel anchor in the viewport's absolute frame space. */
|
|
176
|
+
zoomAt(anchorSx: number, anchorSy: number, factor: ZoomFactor): void;
|
|
177
|
+
/** Reset to initial camera. */
|
|
178
|
+
reset(): void;
|
|
179
|
+
/** Replace the frame (in the same space as the original frame). */
|
|
180
|
+
setFrame(frame: Frame): void;
|
|
181
|
+
/** Set the camera (any omitted field keeps its current value). */
|
|
182
|
+
setCamera(state: CameraState): void;
|
|
183
|
+
/** Alias for {@link setCamera}. */
|
|
184
|
+
jumpCamera(state: CameraState): void;
|
|
185
|
+
/**
|
|
186
|
+
* Convert a screen-space coordinate to world coordinates.
|
|
187
|
+
* Rotation is not applied, matching the interaction system's hit-testing.
|
|
188
|
+
*/
|
|
189
|
+
screenToWorld(sx: number, sy: number): Vec2;
|
|
190
|
+
/** Inverse of {@link screenToWorld}. */
|
|
191
|
+
worldToScreen(wx: number, wy: number): Vec2;
|
|
192
|
+
/** Replace the pan-bound config (or clear it with `null`). */
|
|
193
|
+
setPanBounds(options: CameraPanBoundsOptions | null): void;
|
|
194
|
+
/** Subscribe to any view-state change. */
|
|
195
|
+
onChange(cb: () => void): () => void;
|
|
196
|
+
/** @internal */
|
|
197
|
+
_state: CameraInternal;
|
|
198
|
+
}
|
|
199
|
+
interface CameraData {
|
|
200
|
+
x: number;
|
|
201
|
+
y: number;
|
|
202
|
+
zoom: number;
|
|
203
|
+
rotation: number;
|
|
204
|
+
}
|
|
205
|
+
interface MutableFrameRect {
|
|
206
|
+
x: number;
|
|
207
|
+
y: number;
|
|
208
|
+
width: number;
|
|
209
|
+
height: number;
|
|
210
|
+
}
|
|
211
|
+
interface ResolvedAxisMargin {
|
|
212
|
+
x: number;
|
|
213
|
+
y: number;
|
|
214
|
+
}
|
|
215
|
+
interface ResolvedMargins {
|
|
216
|
+
overshoot: ResolvedAxisMargin;
|
|
217
|
+
drift: ResolvedAxisMargin;
|
|
218
|
+
}
|
|
219
|
+
interface CameraInternal {
|
|
220
|
+
mode: "camera";
|
|
221
|
+
frame: Frame;
|
|
222
|
+
parent: ViewportLike | null;
|
|
223
|
+
listeners: Set<() => void>;
|
|
224
|
+
notifying: boolean;
|
|
225
|
+
clipRect: MutableFrameRect;
|
|
226
|
+
camera: CameraData;
|
|
227
|
+
baseCamera: CameraData;
|
|
228
|
+
panBounds: {
|
|
229
|
+
content: FrameRect;
|
|
230
|
+
margins: ResolvedMargins;
|
|
231
|
+
} | null;
|
|
232
|
+
}
|
|
233
|
+
declare function createViewport(options: CameraViewportOptions): CameraViewport;
|
|
234
|
+
/**
|
|
235
|
+
* Compute the world-space AABB visible within the camera viewport's frame
|
|
236
|
+
* (or a sub-rect of it). Useful for hit-testing and manual culling logic —
|
|
237
|
+
* `Renderer2D` applies render-time cull automatically.
|
|
238
|
+
*
|
|
239
|
+
* Rotation is ignored — the returned rect is the non-rotated AABB, which is
|
|
240
|
+
* a conservative over-approximation when the camera is rotated.
|
|
241
|
+
*/
|
|
242
|
+
declare function worldCullBounds(viewport: CameraViewport, rect?: FrameRect): FrameRect;
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/shared/camera-view.d.ts
|
|
245
|
+
interface CameraState {
|
|
246
|
+
/** World x coordinate shown at the viewport center. Default: 0 */
|
|
247
|
+
x?: number;
|
|
248
|
+
/** World y coordinate shown at the viewport center. Default: 0 */
|
|
249
|
+
y?: number;
|
|
250
|
+
/** Zoom factor — zooms around the viewport center. Default: 1 */
|
|
251
|
+
zoom?: number;
|
|
252
|
+
/** Rotation in radians around the viewport center. Default: 0 */
|
|
253
|
+
rotation?: number;
|
|
254
|
+
}
|
|
255
|
+
interface Bounds2D {
|
|
256
|
+
minX: number;
|
|
257
|
+
minY: number;
|
|
258
|
+
maxX: number;
|
|
259
|
+
maxY: number;
|
|
260
|
+
}
|
|
261
|
+
interface FitCameraToBoundsOptions {
|
|
262
|
+
/** Fraction of the viewport to use. 1 fills the viewport, 0.85 leaves 15% slack. */
|
|
263
|
+
padding?: number;
|
|
264
|
+
}
|
|
265
|
+
/** Shared camera state shape returned by `getCamera`. */
|
|
266
|
+
type ResolvedCameraState = Readonly<{
|
|
267
|
+
x: number;
|
|
268
|
+
y: number;
|
|
269
|
+
zoom: number;
|
|
270
|
+
rotation: number;
|
|
271
|
+
}>;
|
|
272
|
+
/**
|
|
273
|
+
* Build the view matrix (world → screen-pixel) from camera state. Column-major
|
|
274
|
+
* 3×3: scale/rotate around the viewport center, translate so the camera
|
|
275
|
+
* position lands at the center.
|
|
276
|
+
*/
|
|
277
|
+
declare function viewFromCamera(cam: {
|
|
278
|
+
x: number;
|
|
279
|
+
y: number;
|
|
280
|
+
zoom: number;
|
|
281
|
+
rotation: number;
|
|
282
|
+
}, viewportWidth: number, viewportHeight: number): Mat3;
|
|
283
|
+
/**
|
|
284
|
+
* World → CSS-px view matrix matching what the renderer draws: the world camera
|
|
285
|
+
* baseline is **CSS px** (zoom=1 ⇒ 1 world unit = 1 CSS px), so this is simply
|
|
286
|
+
* `viewFromCamera(cam, deviceW/dpr, deviceH/dpr)`.
|
|
287
|
+
*
|
|
288
|
+
* The renderer builds the world view-projection over the CSS-px viewport and
|
|
289
|
+
* applies dpr once, matrix-side, when targeting the device backbuffer (see
|
|
290
|
+
* `viewProjectionForSpace`). The CSS-px screen position of a world point is
|
|
291
|
+
* therefore `viewFromCamera(cam, cssW, cssH) ∘ point` — independent of dpr. The
|
|
292
|
+
* `deviceW/deviceH/dpr` signature is retained so callers can keep passing device
|
|
293
|
+
* dims (`canvas.width/height`); CSS dims are recovered as `device ÷ dpr`.
|
|
294
|
+
*/
|
|
295
|
+
declare function worldToCssMatrix(cam: {
|
|
296
|
+
x: number;
|
|
297
|
+
y: number;
|
|
298
|
+
zoom: number;
|
|
299
|
+
rotation: number;
|
|
300
|
+
}, deviceW: number, deviceH: number, dpr: number): Mat3;
|
|
301
|
+
/**
|
|
302
|
+
* Build the world → screen-pixel view matrix for a camera anchored at the
|
|
303
|
+
* center of an arbitrary rect (`rx`, `ry`, `rw`, `rh`). World `(cam.x, cam.y)`
|
|
304
|
+
* maps to the rect center; `zoom` / `rotation` apply around that center.
|
|
305
|
+
* `viewFromCamera` is the special case where the rect covers the full target.
|
|
306
|
+
*/
|
|
307
|
+
declare function viewFromCameraInRect(cam: {
|
|
308
|
+
x: number;
|
|
309
|
+
y: number;
|
|
310
|
+
zoom: number;
|
|
311
|
+
rotation: number;
|
|
312
|
+
}, rx: number, ry: number, rw: number, rh: number): Mat3;
|
|
313
|
+
/** Resolve an `absoluteFrame`-shaped rect from a CameraViewport. */
|
|
314
|
+
declare function cameraViewportRect(viewport: CameraViewport): FrameRect;
|
|
315
|
+
/**
|
|
316
|
+
* Compute the camera state that fits `bounds` into `viewportWidth × viewportHeight`
|
|
317
|
+
* with optional padding factor in (0, 1].
|
|
318
|
+
*/
|
|
319
|
+
declare function cameraStateForBounds(bounds: Bounds2D, viewportWidth: number, viewportHeight: number, options?: FitCameraToBoundsOptions): {
|
|
320
|
+
x: number;
|
|
321
|
+
y: number;
|
|
322
|
+
zoom: number;
|
|
323
|
+
rotation: number;
|
|
324
|
+
};
|
|
325
|
+
//#endregion
|
|
326
|
+
export { transformPoint as A, dot as B, invert as C, rotationAt as D, rotation as E, ZERO_RECT as F, vec2 as G, normalize as H, createFrame as I, viewportFrame as L, Frame as M, FrameRect as N, scaling as O, Padding as P, Vec2 as R, Mat3 as S, projection as T, scale as U, length as V, sub as W, ViewportLike as _, cameraStateForBounds as a, worldCullBounds as b, viewFromCameraInRect as c, CameraPanBoundsOptions as d, CameraViewport as f, Viewport as g, PanBoundsMargins as h, ResolvedCameraState as i, translation as j, scalingAt as k, worldToCssMatrix as l, PanBoundsMargin as m, CameraState as n, cameraViewportRect as o, CameraViewportOptions as p, FitCameraToBoundsOptions as r, viewFromCamera as s, Bounds2D as t, CameraInternal as u, ZoomFactor as v, multiply as w, IDENTITY as x, createViewport as y, add as z };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//#region src/frame.ts
|
|
2
|
+
const ZERO_RECT = {
|
|
3
|
+
x: 0,
|
|
4
|
+
y: 0,
|
|
5
|
+
width: 0,
|
|
6
|
+
height: 0
|
|
7
|
+
};
|
|
8
|
+
function resolvePadding(padding, width, height) {
|
|
9
|
+
if (typeof padding === "number") return {
|
|
10
|
+
top: padding,
|
|
11
|
+
right: padding,
|
|
12
|
+
bottom: padding,
|
|
13
|
+
left: padding
|
|
14
|
+
};
|
|
15
|
+
if ("ratio" in padding) {
|
|
16
|
+
const side = Math.min(width, height) * padding.ratio;
|
|
17
|
+
return {
|
|
18
|
+
top: side,
|
|
19
|
+
right: side,
|
|
20
|
+
bottom: side,
|
|
21
|
+
left: side
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const all = padding.all ?? 0;
|
|
25
|
+
return {
|
|
26
|
+
top: padding.top ?? all,
|
|
27
|
+
right: padding.right ?? all,
|
|
28
|
+
bottom: padding.bottom ?? all,
|
|
29
|
+
left: padding.left ?? all
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
var FrameImpl = class FrameImpl {
|
|
33
|
+
x;
|
|
34
|
+
y;
|
|
35
|
+
width;
|
|
36
|
+
height;
|
|
37
|
+
constructor(x, y, width, height) {
|
|
38
|
+
this.x = x;
|
|
39
|
+
this.y = y;
|
|
40
|
+
this.width = Math.max(0, width);
|
|
41
|
+
this.height = Math.max(0, height);
|
|
42
|
+
}
|
|
43
|
+
get topLeft() {
|
|
44
|
+
return {
|
|
45
|
+
x: this.x,
|
|
46
|
+
y: this.y
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
get topRight() {
|
|
50
|
+
return {
|
|
51
|
+
x: this.x + this.width,
|
|
52
|
+
y: this.y
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
get bottomLeft() {
|
|
56
|
+
return {
|
|
57
|
+
x: this.x,
|
|
58
|
+
y: this.y + this.height
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
get bottomRight() {
|
|
62
|
+
return {
|
|
63
|
+
x: this.x + this.width,
|
|
64
|
+
y: this.y + this.height
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
get center() {
|
|
68
|
+
return {
|
|
69
|
+
x: this.x + this.width / 2,
|
|
70
|
+
y: this.y + this.height / 2
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
get rect() {
|
|
74
|
+
return {
|
|
75
|
+
x: this.x,
|
|
76
|
+
y: this.y,
|
|
77
|
+
width: this.width,
|
|
78
|
+
height: this.height
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
padded(padding) {
|
|
82
|
+
const p = resolvePadding(padding, this.width, this.height);
|
|
83
|
+
return new FrameImpl(this.x + p.left, this.y + p.top, this.width - p.left - p.right, this.height - p.top - p.bottom);
|
|
84
|
+
}
|
|
85
|
+
translated(dx, dy) {
|
|
86
|
+
return new FrameImpl(this.x + dx, this.y + dy, this.width, this.height);
|
|
87
|
+
}
|
|
88
|
+
resized(width, height) {
|
|
89
|
+
return new FrameImpl(this.x, this.y, width, height);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/** Create a frame from an `{x, y, width, height}` rect. */
|
|
93
|
+
function createFrame(rect) {
|
|
94
|
+
return new FrameImpl(rect.x, rect.y, rect.width, rect.height);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Convenience: create a frame covering `(0, 0)` to `(width, height)`.
|
|
98
|
+
* Useful as the outermost container for a canvas or SVG viewport.
|
|
99
|
+
*/
|
|
100
|
+
function viewportFrame(width, height) {
|
|
101
|
+
return new FrameImpl(0, 0, width, height);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Grow a rect outward by `pad` on every side. Damage rects are padded before
|
|
105
|
+
* scissoring so the analytic anti-aliased fringe of geometry at the rect edge
|
|
106
|
+
* is fully repainted and no hairline seam survives at the boundary.
|
|
107
|
+
*/
|
|
108
|
+
function padFrameRect(r, pad) {
|
|
109
|
+
return {
|
|
110
|
+
x: r.x - pad,
|
|
111
|
+
y: r.y - pad,
|
|
112
|
+
width: r.width + pad * 2,
|
|
113
|
+
height: r.height + pad * 2
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Geometric intersection of two rects. A `null` second rect means "no clip", so
|
|
118
|
+
* the first rect passes through unchanged. Non-overlapping rects yield a
|
|
119
|
+
* zero-area rect (scissors to nothing). Width/height are floored at zero.
|
|
120
|
+
*/
|
|
121
|
+
function intersectFrameRect(a, b) {
|
|
122
|
+
if (!b) return a;
|
|
123
|
+
const x = Math.max(a.x, b.x);
|
|
124
|
+
const y = Math.max(a.y, b.y);
|
|
125
|
+
const right = Math.min(a.x + a.width, b.x + b.width);
|
|
126
|
+
const bottom = Math.min(a.y + a.height, b.y + b.height);
|
|
127
|
+
return {
|
|
128
|
+
x,
|
|
129
|
+
y,
|
|
130
|
+
width: Math.max(0, right - x),
|
|
131
|
+
height: Math.max(0, bottom - y)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { viewportFrame as a, padFrameRect as i, createFrame as n, intersectFrameRect as r, ZERO_RECT as t };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/reactivity/signals.d.ts
|
|
2
|
+
interface ReadableSignal<T> {
|
|
3
|
+
get(): T;
|
|
4
|
+
peek(): T;
|
|
5
|
+
subscribe(fn: (v: T) => void): () => void;
|
|
6
|
+
}
|
|
7
|
+
interface Signal<T> extends ReadableSignal<T> {
|
|
8
|
+
set(v: T): void;
|
|
9
|
+
update(fn: (cur: T) => T): void;
|
|
10
|
+
/**
|
|
11
|
+
* Drop all subscribers (push + tracking) and silence further `set` / `update`.
|
|
12
|
+
* Existing unsubscribe closures stay valid (they were already idempotent).
|
|
13
|
+
* Use when an owner is being torn down and you want to break references that
|
|
14
|
+
* external subscribers might hold to the owner's internals.
|
|
15
|
+
*/
|
|
16
|
+
dispose(): void;
|
|
17
|
+
}
|
|
18
|
+
interface Computed<T> extends ReadableSignal<T> {}
|
|
19
|
+
interface SignalOptions<T> {
|
|
20
|
+
equals?: (a: T, b: T) => boolean;
|
|
21
|
+
}
|
|
22
|
+
declare function signal<T>(initial: T, opts?: SignalOptions<T>): Signal<T>;
|
|
23
|
+
declare function computed<T>(fn: () => T, opts?: SignalOptions<T>): Computed<T>;
|
|
24
|
+
type Scheduler = (run: () => void) => void;
|
|
25
|
+
type EffectDisposer = () => void;
|
|
26
|
+
type EffectFn = () => void | EffectDisposer;
|
|
27
|
+
declare function setDefaultScheduler(s: Scheduler): void;
|
|
28
|
+
declare function syncScheduler(): Scheduler;
|
|
29
|
+
declare function microtaskFlush(): Scheduler;
|
|
30
|
+
interface EffectOptions {
|
|
31
|
+
scheduler?: Scheduler;
|
|
32
|
+
}
|
|
33
|
+
declare function effect(fn: EffectFn, options?: EffectOptions): EffectDisposer;
|
|
34
|
+
/** Run `fn` with no current tracking context. Reads inside don't register. */
|
|
35
|
+
declare function untrack<T>(fn: () => T): T;
|
|
36
|
+
declare function isSignal<T>(v: unknown): v is ReadableSignal<T>;
|
|
37
|
+
declare function toSignal<T>(value: T | ReadableSignal<T>): ReadableSignal<T>;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { untrack as _, ReadableSignal as a, SignalOptions as c, isSignal as d, microtaskFlush as f, toSignal as g, syncScheduler as h, EffectOptions as i, computed as l, signal as m, EffectDisposer as n, Scheduler as o, setDefaultScheduler as p, EffectFn as r, Signal as s, Computed as t, effect as u };
|