gesso-core 0.1.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.
@@ -0,0 +1,560 @@
1
+ import { N as Constraints, X as UiPlatformAdapter, _t as UiInputDispatcher, b as LayoutEngine, cr as UiGraph, ct as UiFocusManager, it as UiWheelController, lt as UiHitTester, nt as UiGestureRecognizer, ot as UiKeyboardController, r as Canvas2DRenderer, st as UiPointerController, t as CanvasSurface, tt as UiTouchScroller, wt as CharacterCountTextMeasurer } from "./CanvasSurface-CrG-uJ5o.js";
2
+ //#region src/layout/LayoutTestUtils.ts
3
+ /**
4
+ * Shared helpers for layout specs: builds UiNode trees through
5
+ * the real graph and drives LayoutEngine.
6
+ */
7
+ var LayoutHarness = class {
8
+ graph = new UiGraph();
9
+ engine;
10
+ constructor(textMeasurer) {
11
+ this.engine = new LayoutEngine(textMeasurer);
12
+ }
13
+ createNode(id, type) {
14
+ return this.graph.createNode(id, type);
15
+ }
16
+ append(parent, ...children) {
17
+ for (const child of children) this.graph.appendChild(parent, child);
18
+ }
19
+ layout(node, constraints = Constraints.unbounded()) {
20
+ return this.engine.layout(node, constraints);
21
+ }
22
+ record(node) {
23
+ const record = this.engine.recordFor(node);
24
+ if (record === void 0) throw new Error(`No layout record for '${node.id}'.`);
25
+ return record;
26
+ }
27
+ box(node) {
28
+ const record = this.record(node);
29
+ return {
30
+ x: record.x,
31
+ y: record.y,
32
+ width: record.width,
33
+ height: record.height
34
+ };
35
+ }
36
+ boxOf(node) {
37
+ return this.engine.worldBox(node);
38
+ }
39
+ /** Where the node is seen: scroll offsets and sticky shifts applied. */
40
+ visibleBox(node) {
41
+ return this.engine.visibleBox(node);
42
+ }
43
+ };
44
+ //#endregion
45
+ //#region src/input/UiInputTestUtils.ts
46
+ /**
47
+ * Shared harness for input specs.
48
+ *
49
+ * Builds real UiNode trees through UiGraph + LayoutEngine so hit
50
+ * testing, dispatch, focus and gestures are tested against actual
51
+ * layout geometry rather than hand-written boxes.
52
+ */
53
+ var InputTestHarness = class {
54
+ layout;
55
+ root;
56
+ dispatcher = new UiInputDispatcher();
57
+ /** Shared, so a spec can read back what a controller scrolled. */
58
+ scrollSink = new HarnessScrollSink(this);
59
+ /** A measurer is needed only by specs whose trees contain real text. */
60
+ constructor(width = 400, height = 400, textMeasurer) {
61
+ this.layout = new LayoutHarness(textMeasurer);
62
+ this.root = this.layout.createNode("app", "column");
63
+ this.root.setProperty("width", width);
64
+ this.root.setProperty("height", height);
65
+ }
66
+ node(id, type, props = {}) {
67
+ const node = this.layout.createNode(id, type);
68
+ for (const [key, value] of Object.entries(props)) node.setProperty(key, value);
69
+ return node;
70
+ }
71
+ add(parent, ...children) {
72
+ this.layout.append(parent, ...children);
73
+ }
74
+ layoutTree() {
75
+ this.layout.layout(this.root, Constraints.loose(400, 400));
76
+ }
77
+ createHitTester() {
78
+ return new UiHitTester(this.layout.engine, this.root);
79
+ }
80
+ createPointerController() {
81
+ return new UiPointerController(this.createHitTester(), this.dispatcher);
82
+ }
83
+ createGestureRecognizer() {
84
+ return new UiGestureRecognizer(this.dispatcher);
85
+ }
86
+ createFocusManager() {
87
+ return new UiFocusManager(this.root, this.dispatcher);
88
+ }
89
+ createKeyboardController() {
90
+ return new UiKeyboardController(this.dispatcher, this.createFocusManager(), this.root);
91
+ }
92
+ createWheelController() {
93
+ return new UiWheelController(this.createHitTester(), this.dispatcher, this.scrollSink, () => this.root);
94
+ }
95
+ /**
96
+ * A pointer controller with a gesture recognizer behind it, for the
97
+ * specs that need a real Pan rather than a hand-made one.
98
+ */
99
+ createGesturePointerController(options = {}) {
100
+ return new UiPointerController(this.createHitTester(), this.dispatcher, {
101
+ gestures: new UiGestureRecognizer(this.dispatcher),
102
+ scrollSink: this.scrollSink,
103
+ ...options
104
+ });
105
+ }
106
+ createTouchScroller(options = {}) {
107
+ return new UiTouchScroller(this.dispatcher, this.root, this.scrollSink, options);
108
+ }
109
+ createPlatformAdapter() {
110
+ return new UiPlatformAdapter({
111
+ pointerController: this.createPointerController(),
112
+ wheelController: this.createWheelController(),
113
+ keyboardController: this.createKeyboardController()
114
+ });
115
+ }
116
+ box(node) {
117
+ return this.layout.boxOf(node);
118
+ }
119
+ };
120
+ /**
121
+ * Minimal fake event target for testing the platform adapter.
122
+ */
123
+ var FakeEventTarget = class {
124
+ listeners = [];
125
+ addEventListener(type, listener) {
126
+ if (listener !== null) this.listeners.push({
127
+ type,
128
+ listener
129
+ });
130
+ }
131
+ removeEventListener(type, listener) {
132
+ if (listener === null) return;
133
+ const index = this.listeners.findIndex((entry) => entry.type === type && entry.listener === listener);
134
+ if (index !== -1) this.listeners.splice(index, 1);
135
+ }
136
+ emit(type, event) {
137
+ for (const entry of this.listeners) if (entry.type === type) entry.listener(event);
138
+ }
139
+ };
140
+ /**
141
+ * Fake PlatformSurface for tests. Emits DOM-like events from plain
142
+ * objects so PointerEvent/WheelEvent/KeyboardEvent constructors are
143
+ * not required in the test environment.
144
+ */
145
+ var FakePlatformSurface = class {
146
+ pointerTarget = new FakeEventTarget();
147
+ keyboardTarget = new FakeEventTarget();
148
+ localX = 0;
149
+ localY = 0;
150
+ /** The last `touch-action` the adapter asked for; '' before any. */
151
+ touchAction = "";
152
+ clientToLocal(_clientX, _clientY) {
153
+ return {
154
+ x: this.localX,
155
+ y: this.localY
156
+ };
157
+ }
158
+ setTouchAction(value) {
159
+ this.touchAction = value;
160
+ }
161
+ };
162
+ /**
163
+ * ScrollSink backed by the harness LayoutEngine. Updates the
164
+ * container's scrollX/scrollY properties, then re-lays out so the
165
+ * engine re-reads and clamps the offset, mirroring how the app layer
166
+ * drives scroll through node properties.
167
+ */
168
+ var HarnessScrollSink = class {
169
+ harness;
170
+ /** Every scroll asked for, in order, so a spec can read the behaviour hint. */
171
+ calls = [];
172
+ /** Containers whose scrollbars were revealed. */
173
+ revealed = [];
174
+ constructor(harness) {
175
+ this.harness = harness;
176
+ }
177
+ containerState(node) {
178
+ const record = this.harness.layout.engine.recordFor(node);
179
+ if (record === void 0) return;
180
+ const horizontal = node.getProperty("direction") === "row";
181
+ return {
182
+ scrollX: record.scrollX,
183
+ scrollY: record.scrollY,
184
+ maxScrollX: Math.max(0, record.contentWidth - record.width),
185
+ maxScrollY: Math.max(0, record.contentHeight - record.height),
186
+ horizontal,
187
+ viewportWidth: record.width,
188
+ viewportHeight: record.height
189
+ };
190
+ }
191
+ scrollBy(node, dx, dy, behavior = "instant") {
192
+ this.calls.push({
193
+ node,
194
+ dx,
195
+ dy,
196
+ behavior
197
+ });
198
+ const record = this.harness.layout.engine.recordFor(node);
199
+ if (record === void 0) return;
200
+ const maxX = Math.max(0, record.contentWidth - record.width);
201
+ const maxY = Math.max(0, record.contentHeight - record.height);
202
+ node.setProperty("scrollX", clamp(record.scrollX + dx, 0, maxX));
203
+ node.setProperty("scrollY", clamp(record.scrollY + dy, 0, maxY));
204
+ this.harness.layoutTree();
205
+ }
206
+ scrollContainers() {
207
+ return this.harness.layout.engine.scrollContainers();
208
+ }
209
+ revealScrollbars(node) {
210
+ this.revealed.push(node);
211
+ }
212
+ };
213
+ function clamp(value, min, max) {
214
+ return Math.min(max, Math.max(min, value));
215
+ }
216
+ //#endregion
217
+ //#region src/rendering/RenderTestUtils.ts
218
+ /**
219
+ * The gradient a recording context hands back, carrying what it was
220
+ * asked for so a test can read the whole paint rather than a `[object
221
+ * CanvasGradient]`. A real one is opaque, which is exactly what makes
222
+ * a gradient hard to assert on without this.
223
+ */
224
+ var RecordedGradient = class {
225
+ kind;
226
+ args;
227
+ stops = [];
228
+ constructor(kind, args) {
229
+ this.kind = kind;
230
+ this.args = args;
231
+ }
232
+ addColorStop(offset, color) {
233
+ this.stops.push({
234
+ offset,
235
+ color
236
+ });
237
+ }
238
+ };
239
+ /**
240
+ * Deterministic Canvas2D spy: records every call and style
241
+ * assignment so tests can assert the exact draw sequence.
242
+ */
243
+ var RecordingCanvasContext = class {
244
+ calls = [];
245
+ _fillStyle = "#000";
246
+ _strokeStyle = "#000";
247
+ _lineWidth = 1;
248
+ _lineJoin = "miter";
249
+ _globalAlpha = 1;
250
+ _font = "";
251
+ _textAlign = "start";
252
+ _textBaseline = "alphabetic";
253
+ /**
254
+ * Tracking, which is a context property rather than part of the font
255
+ * shorthand — and sticky, so a spec can check it was cleared as well
256
+ * as that it was set.
257
+ */
258
+ letterSpacing = "0px";
259
+ get fillStyle() {
260
+ return this._fillStyle;
261
+ }
262
+ set fillStyle(value) {
263
+ this._fillStyle = value;
264
+ this.record("set:fillStyle", [value]);
265
+ }
266
+ get strokeStyle() {
267
+ return this._strokeStyle;
268
+ }
269
+ set strokeStyle(value) {
270
+ this._strokeStyle = value;
271
+ this.record("set:strokeStyle", [value]);
272
+ }
273
+ get lineWidth() {
274
+ return this._lineWidth;
275
+ }
276
+ set lineWidth(value) {
277
+ this._lineWidth = value;
278
+ this.record("set:lineWidth", [value]);
279
+ }
280
+ get lineJoin() {
281
+ return this._lineJoin;
282
+ }
283
+ set lineJoin(value) {
284
+ this._lineJoin = value;
285
+ this.record("set:lineJoin", [value]);
286
+ }
287
+ get globalAlpha() {
288
+ return this._globalAlpha;
289
+ }
290
+ set globalAlpha(value) {
291
+ this._globalAlpha = value;
292
+ this.record("set:globalAlpha", [value]);
293
+ }
294
+ get font() {
295
+ return this._font;
296
+ }
297
+ set font(value) {
298
+ this._font = value;
299
+ this.record("set:font", [value]);
300
+ }
301
+ get textAlign() {
302
+ return this._textAlign;
303
+ }
304
+ set textAlign(value) {
305
+ this._textAlign = value;
306
+ this.record("set:textAlign", [value]);
307
+ }
308
+ get textBaseline() {
309
+ return this._textBaseline;
310
+ }
311
+ set textBaseline(value) {
312
+ this._textBaseline = value;
313
+ this.record("set:textBaseline", [value]);
314
+ }
315
+ save() {
316
+ this.record("save", []);
317
+ }
318
+ restore() {
319
+ this.record("restore", []);
320
+ }
321
+ translate(x, y) {
322
+ this.record("translate", [x, y]);
323
+ }
324
+ scale(x, y) {
325
+ this.record("scale", [x, y]);
326
+ }
327
+ rotate(angle) {
328
+ this.record("rotate", [angle]);
329
+ }
330
+ setTransform(a, b, c, d, e, f) {
331
+ this.record("setTransform", [
332
+ a,
333
+ b,
334
+ c,
335
+ d,
336
+ e,
337
+ f
338
+ ]);
339
+ }
340
+ clearRect(x, y, width, height) {
341
+ this.record("clearRect", [
342
+ x,
343
+ y,
344
+ width,
345
+ height
346
+ ]);
347
+ }
348
+ fillRect(x, y, width, height) {
349
+ this.record("fillRect", [
350
+ x,
351
+ y,
352
+ width,
353
+ height
354
+ ]);
355
+ }
356
+ strokeRect(x, y, width, height) {
357
+ this.record("strokeRect", [
358
+ x,
359
+ y,
360
+ width,
361
+ height
362
+ ]);
363
+ }
364
+ beginPath() {
365
+ this.record("beginPath", []);
366
+ }
367
+ moveTo(x, y) {
368
+ this.record("moveTo", [x, y]);
369
+ }
370
+ lineTo(x, y) {
371
+ this.record("lineTo", [x, y]);
372
+ }
373
+ arcTo(x1, y1, x2, y2, radius) {
374
+ this.record("arcTo", [
375
+ x1,
376
+ y1,
377
+ x2,
378
+ y2,
379
+ radius
380
+ ]);
381
+ }
382
+ closePath() {
383
+ this.record("closePath", []);
384
+ }
385
+ rect(x, y, width, height) {
386
+ this.record("rect", [
387
+ x,
388
+ y,
389
+ width,
390
+ height
391
+ ]);
392
+ }
393
+ clip() {
394
+ this.record("clip", []);
395
+ }
396
+ fill() {
397
+ this.record("fill", []);
398
+ }
399
+ stroke() {
400
+ this.record("stroke", []);
401
+ }
402
+ fillText(text, x, y, maxWidth) {
403
+ const args = maxWidth === void 0 ? [
404
+ text,
405
+ x,
406
+ y
407
+ ] : [
408
+ text,
409
+ x,
410
+ y,
411
+ maxWidth
412
+ ];
413
+ this.record("fillText", args);
414
+ }
415
+ measureText(text) {
416
+ this.record("measureText", [text]);
417
+ return { width: text.length * 8 };
418
+ }
419
+ drawImage(image, dx, dy, dw, dh) {
420
+ this.record("drawImage", [
421
+ image,
422
+ dx,
423
+ dy,
424
+ dw,
425
+ dh
426
+ ]);
427
+ }
428
+ createLinearGradient(x0, y0, x1, y1) {
429
+ const gradient = new RecordedGradient("linear", [
430
+ x0,
431
+ y0,
432
+ x1,
433
+ y1
434
+ ]);
435
+ this.record("createLinearGradient", [
436
+ x0,
437
+ y0,
438
+ x1,
439
+ y1
440
+ ]);
441
+ return gradient;
442
+ }
443
+ createRadialGradient(x0, y0, r0, x1, y1, r1) {
444
+ const gradient = new RecordedGradient("radial", [
445
+ x0,
446
+ y0,
447
+ r0,
448
+ x1,
449
+ y1,
450
+ r1
451
+ ]);
452
+ this.record("createRadialGradient", [
453
+ x0,
454
+ y0,
455
+ r0,
456
+ x1,
457
+ y1,
458
+ r1
459
+ ]);
460
+ return gradient;
461
+ }
462
+ record(name, args) {
463
+ this.calls.push({
464
+ name,
465
+ args: args.map(normalizeNumber)
466
+ });
467
+ }
468
+ };
469
+ /**
470
+ * Normalizes -0 to 0 so scroll translations assert cleanly.
471
+ */
472
+ function normalizeNumber(value) {
473
+ if (typeof value === "number" && Object.is(value, -0)) return 0;
474
+ return value;
475
+ }
476
+ function callNames(context) {
477
+ return context.calls.map((call) => call.name);
478
+ }
479
+ function callsOf(context, name) {
480
+ return context.calls.filter((call) => call.name === name);
481
+ }
482
+ function callArgs(context, name) {
483
+ return callsOf(context, name).map((call) => call.name.startsWith("set:") ? call.args[0] : call.args);
484
+ }
485
+ function savedDepth(context) {
486
+ let depth = 0;
487
+ for (const call of context.calls) if (call.name === "save") depth++;
488
+ else if (call.name === "restore") depth--;
489
+ return depth;
490
+ }
491
+ /**
492
+ * A canvas host backed by a recording context.
493
+ */
494
+ var FakeCanvasHost = class {
495
+ context;
496
+ width = 0;
497
+ height = 0;
498
+ contextRequests = 0;
499
+ constructor(context) {
500
+ this.context = context;
501
+ }
502
+ getContext(_contextId) {
503
+ this.contextRequests++;
504
+ return this.context;
505
+ }
506
+ get contextRequestCount() {
507
+ return this.contextRequests;
508
+ }
509
+ };
510
+ /**
511
+ * Shared harness: a real UiGraph + LayoutEngine + Canvas2DRenderer
512
+ * over a recording context. Deterministic layout comes from the
513
+ * CharacterCountTextMeasurer, shared by layout and renderer.
514
+ */
515
+ var RenderHarness = class {
516
+ graph = new UiGraph();
517
+ measurer = new CharacterCountTextMeasurer();
518
+ engine = new LayoutEngine(this.measurer);
519
+ context = new RecordingCanvasContext();
520
+ host = new FakeCanvasHost(this.context);
521
+ surface = new CanvasSurface(this.host);
522
+ renderer = new Canvas2DRenderer({ surface: this.surface });
523
+ constructor(logicalWidth = 800, logicalHeight = 600, dpr = 1) {
524
+ this.surface.setLogicalSize(logicalWidth, logicalHeight, dpr);
525
+ }
526
+ createNode(id, type) {
527
+ return this.graph.createNode(id, type);
528
+ }
529
+ append(parent, ...children) {
530
+ for (const child of children) this.graph.appendChild(parent, child);
531
+ }
532
+ layout(node, constraints = Constraints.tight(800, 600)) {
533
+ return this.engine.layout(node, constraints);
534
+ }
535
+ render(node) {
536
+ this.renderer.render(node, {
537
+ layout: this.engine,
538
+ text: this.measurer,
539
+ now: Number.MAX_SAFE_INTEGER
540
+ });
541
+ }
542
+ record(node) {
543
+ const record = this.engine.recordFor(node);
544
+ if (record === void 0) throw new Error(`No layout record for '${node.id}'.`);
545
+ return record;
546
+ }
547
+ box(node) {
548
+ const record = this.record(node);
549
+ return {
550
+ x: record.x,
551
+ y: record.y,
552
+ width: record.width,
553
+ height: record.height
554
+ };
555
+ }
556
+ };
557
+ //#endregion
558
+ export { FakeCanvasHost, FakeEventTarget, FakePlatformSurface, InputTestHarness, RecordingCanvasContext, RenderHarness, callArgs, callNames, callsOf, savedDepth };
559
+
560
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","names":[],"sources":["../src/layout/LayoutTestUtils.ts","../src/input/UiInputTestUtils.ts","../src/rendering/RenderTestUtils.ts"],"sourcesContent":["import { UiGraph } from '../graph/UiGraph';\nimport type { UiNode } from '../graph/UiNode';\nimport { UiNodeType } from '../graph/UiNodeType';\nimport { LayoutEngine } from './LayoutEngine';\nimport { Constraints } from './LayoutTypes';\nimport type { LayoutBox, LayoutResult } from './LayoutTypes';\nimport type { LayoutRecord } from './LayoutRecord';\nimport type { TextMeasurer } from './TextMeasurer';\n\n/**\n * Shared helpers for layout specs: builds UiNode trees through\n * the real graph and drives LayoutEngine.\n */\nexport class LayoutHarness {\n readonly graph = new UiGraph();\n readonly engine: LayoutEngine;\n\n constructor(textMeasurer?: TextMeasurer) {\n this.engine = new LayoutEngine(textMeasurer);\n }\n\n createNode(id: string, type: UiNodeType): UiNode {\n return this.graph.createNode(id, type);\n }\n\n append(parent: UiNode, ...children: UiNode[]): void {\n for (const child of children) {\n this.graph.appendChild(parent, child);\n }\n }\n\n layout(node: UiNode, constraints: Constraints = Constraints.unbounded()): LayoutResult {\n return this.engine.layout(node, constraints);\n }\n\n record(node: UiNode): LayoutRecord {\n const record = this.engine.recordFor(node);\n if (record === undefined) {\n throw new Error(`No layout record for '${node.id}'.`);\n }\n return record;\n }\n\n box(node: UiNode): LayoutBox {\n const record = this.record(node);\n return { x: record.x, y: record.y, width: record.width, height: record.height };\n }\n\n boxOf(node: UiNode): LayoutBox {\n return this.engine.worldBox(node);\n }\n\n /** Where the node is seen: scroll offsets and sticky shifts applied. */\n visibleBox(node: UiNode): LayoutBox {\n return this.engine.visibleBox(node);\n }\n}\n","import { UiNodeType } from '../graph/UiNodeType';\nimport type { UiNode } from '../graph/UiNode';\nimport { Constraints } from '../layout/LayoutTypes';\nimport { LayoutHarness } from '../layout/LayoutTestUtils';\nimport { UiHitTester } from './UiHitTester';\nimport { UiInputDispatcher } from './UiInputDispatcher';\nimport { UiPointerController, type PointerControllerOptions } from './UiPointerController';\nimport { UiGestureRecognizer } from './UiGestureRecognizer';\nimport { UiTouchScroller, type TouchScrollerOptions } from './UiTouchScroller';\nimport { UiFocusManager } from './UiFocusManager';\nimport { UiKeyboardController } from './UiKeyboardController';\nimport { UiWheelController, type ScrollContainerState, type ScrollSink } from './UiWheelController';\nimport { UiPlatformAdapter, type PlatformEventTarget, type PlatformSurface } from './UiPlatformAdapter';\nimport type { TextMeasurer } from '../layout/TextMeasurer';\n\n/**\n * Shared harness for input specs.\n *\n * Builds real UiNode trees through UiGraph + LayoutEngine so hit\n * testing, dispatch, focus and gestures are tested against actual\n * layout geometry rather than hand-written boxes.\n */\nexport class InputTestHarness {\n readonly layout: LayoutHarness;\n readonly root: UiNode;\n readonly dispatcher = new UiInputDispatcher();\n /** Shared, so a spec can read back what a controller scrolled. */\n readonly scrollSink = new HarnessScrollSink(this);\n\n /** A measurer is needed only by specs whose trees contain real text. */\n constructor(width = 400, height = 400, textMeasurer?: TextMeasurer) {\n this.layout = new LayoutHarness(textMeasurer);\n this.root = this.layout.createNode('app', UiNodeType.Column);\n this.root.setProperty('width', width);\n this.root.setProperty('height', height);\n }\n\n node(id: string, type: UiNodeType, props: Record<string, unknown> = {}): UiNode {\n const node = this.layout.createNode(id, type);\n for (const [key, value] of Object.entries(props)) {\n node.setProperty(key, value);\n }\n return node;\n }\n\n add(parent: UiNode, ...children: UiNode[]): void {\n this.layout.append(parent, ...children);\n }\n\n layoutTree(): void {\n this.layout.layout(this.root, Constraints.loose(400, 400));\n }\n\n createHitTester(): UiHitTester {\n return new UiHitTester(this.layout.engine, this.root);\n }\n\n createPointerController(): UiPointerController {\n return new UiPointerController(this.createHitTester(), this.dispatcher);\n }\n\n createGestureRecognizer(): UiGestureRecognizer {\n return new UiGestureRecognizer(this.dispatcher);\n }\n\n createFocusManager(): UiFocusManager {\n return new UiFocusManager(this.root, this.dispatcher);\n }\n\n createKeyboardController(): UiKeyboardController {\n return new UiKeyboardController(this.dispatcher, this.createFocusManager(), this.root);\n }\n\n createWheelController(): UiWheelController {\n return new UiWheelController(this.createHitTester(), this.dispatcher, this.scrollSink, () => this.root);\n }\n\n /**\n * A pointer controller with a gesture recognizer behind it, for the\n * specs that need a real Pan rather than a hand-made one.\n */\n createGesturePointerController(options: PointerControllerOptions = {}): UiPointerController {\n return new UiPointerController(this.createHitTester(), this.dispatcher, {\n gestures: new UiGestureRecognizer(this.dispatcher),\n scrollSink: this.scrollSink,\n ...options\n });\n }\n\n createTouchScroller(options: TouchScrollerOptions = {}): UiTouchScroller {\n return new UiTouchScroller(this.dispatcher, this.root, this.scrollSink, options);\n }\n\n createPlatformAdapter(): UiPlatformAdapter {\n return new UiPlatformAdapter({\n pointerController: this.createPointerController(),\n wheelController: this.createWheelController(),\n keyboardController: this.createKeyboardController()\n });\n }\n\n box(node: UiNode): { x: number; y: number; width: number; height: number } {\n return this.layout.boxOf(node);\n }\n}\n\n/**\n * Minimal fake event target for testing the platform adapter.\n */\nexport class FakeEventTarget implements PlatformEventTarget {\n private readonly listeners: { type: string; listener: (event: Event) => void }[] = [];\n\n addEventListener(type: string, listener: ((event: Event) => void) | null): void {\n if (listener !== null) {\n this.listeners.push({ type, listener });\n }\n }\n\n removeEventListener(type: string, listener: ((event: Event) => void) | null): void {\n if (listener === null) return;\n const index = this.listeners.findIndex(entry => entry.type === type && entry.listener === listener);\n if (index !== -1) {\n this.listeners.splice(index, 1);\n }\n }\n\n emit(type: string, event: Event): void {\n for (const entry of this.listeners) {\n if (entry.type === type) {\n entry.listener(event);\n }\n }\n }\n}\n\n/**\n * Fake PlatformSurface for tests. Emits DOM-like events from plain\n * objects so PointerEvent/WheelEvent/KeyboardEvent constructors are\n * not required in the test environment.\n */\nexport class FakePlatformSurface implements PlatformSurface {\n readonly pointerTarget = new FakeEventTarget();\n readonly keyboardTarget = new FakeEventTarget();\n localX = 0;\n localY = 0;\n /** The last `touch-action` the adapter asked for; '' before any. */\n touchAction = '';\n\n clientToLocal(_clientX: number, _clientY: number): { x: number; y: number } {\n return { x: this.localX, y: this.localY };\n }\n\n setTouchAction(value: string): void {\n this.touchAction = value;\n }\n}\n\n/**\n * ScrollSink backed by the harness LayoutEngine. Updates the\n * container's scrollX/scrollY properties, then re-lays out so the\n * engine re-reads and clamps the offset, mirroring how the app layer\n * drives scroll through node properties.\n */\nexport class HarnessScrollSink implements ScrollSink {\n /** Every scroll asked for, in order, so a spec can read the behaviour hint. */\n readonly calls: { node: UiNode; dx: number; dy: number; behavior: string }[] = [];\n /** Containers whose scrollbars were revealed. */\n readonly revealed: UiNode[] = [];\n\n constructor(private readonly harness: InputTestHarness) {}\n\n containerState(node: UiNode): ScrollContainerState | undefined {\n const record = this.harness.layout.engine.recordFor(node);\n if (record === undefined) {\n return undefined;\n }\n const horizontal = node.getProperty('direction') === 'row';\n return {\n scrollX: record.scrollX,\n scrollY: record.scrollY,\n maxScrollX: Math.max(0, record.contentWidth - record.width),\n maxScrollY: Math.max(0, record.contentHeight - record.height),\n horizontal,\n viewportWidth: record.width,\n viewportHeight: record.height\n };\n }\n\n scrollBy(node: UiNode, dx: number, dy: number, behavior: string = 'instant'): void {\n this.calls.push({ node, dx, dy, behavior });\n const record = this.harness.layout.engine.recordFor(node);\n if (record === undefined) {\n return;\n }\n const maxX = Math.max(0, record.contentWidth - record.width);\n const maxY = Math.max(0, record.contentHeight - record.height);\n node.setProperty('scrollX', clamp(record.scrollX + dx, 0, maxX));\n node.setProperty('scrollY', clamp(record.scrollY + dy, 0, maxY));\n this.harness.layoutTree();\n }\n\n scrollContainers(): Iterable<UiNode> {\n return this.harness.layout.engine.scrollContainers();\n }\n\n revealScrollbars(node: UiNode): void {\n this.revealed.push(node);\n }\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n","import { UiGraph } from '../graph/UiGraph';\nimport { UiNodeType } from '../graph/UiNodeType';\nimport type { UiNode } from '../graph/UiNode';\nimport { LayoutEngine } from '../layout/LayoutEngine';\nimport { CharacterCountTextMeasurer } from '../layout/TextMeasurer';\nimport type { TextMeasurer } from '../layout/TextMeasurer';\nimport { Constraints } from '../layout/LayoutTypes';\nimport type { LayoutBox, LayoutResult } from '../layout/LayoutTypes';\nimport type { LayoutRecord } from '../layout/LayoutRecord';\nimport { Canvas2DRenderer } from './canvas2d/Canvas2DRenderer';\nimport type { Canvas2DContext, Canvas2DGradient } from './canvas2d/Canvas2DContext';\nimport { CanvasSurface } from './canvas2d/CanvasSurface';\nimport type { CanvasHost } from './canvas2d/CanvasSurface';\nimport type { PaintContext2D, PaintGradient } from './PaintTarget';\nimport type { PaintCanvasFactory } from './PaintPicture';\nimport type { PaintLineCap, PaintLineJoin } from './PaintSurface';\n\nexport interface RecordedCall {\n name: string;\n args: unknown[];\n}\n\n/**\n * The gradient a recording context hands back, carrying what it was\n * asked for so a test can read the whole paint rather than a `[object\n * CanvasGradient]`. A real one is opaque, which is exactly what makes\n * a gradient hard to assert on without this.\n */\nexport class RecordedGradient implements Canvas2DGradient {\n readonly stops: { offset: number; color: string }[] = [];\n\n constructor(\n readonly kind: 'linear' | 'radial',\n readonly args: readonly number[]\n ) {}\n\n addColorStop(offset: number, color: string): void {\n this.stops.push({ offset, color });\n }\n}\n\n/**\n * Deterministic Canvas2D spy: records every call and style\n * assignment so tests can assert the exact draw sequence.\n */\nexport class RecordingCanvasContext implements Canvas2DContext {\n readonly calls: RecordedCall[] = [];\n\n private _fillStyle: string | Canvas2DGradient | CanvasPattern = '#000';\n private _strokeStyle: string | Canvas2DGradient | CanvasPattern = '#000';\n private _lineWidth = 1;\n private _lineJoin: CanvasLineJoin = 'miter';\n private _globalAlpha = 1;\n private _font = '';\n private _textAlign: CanvasTextAlign = 'start';\n private _textBaseline: CanvasTextBaseline = 'alphabetic';\n /**\n * Tracking, which is a context property rather than part of the font\n * shorthand — and sticky, so a spec can check it was cleared as well\n * as that it was set.\n */\n letterSpacing = '0px';\n\n get fillStyle(): string | Canvas2DGradient | CanvasPattern {\n return this._fillStyle;\n }\n\n set fillStyle(value: string | Canvas2DGradient | CanvasPattern) {\n this._fillStyle = value;\n this.record('set:fillStyle', [value]);\n }\n\n get strokeStyle(): string | Canvas2DGradient | CanvasPattern {\n return this._strokeStyle;\n }\n\n set strokeStyle(value: string | Canvas2DGradient | CanvasPattern) {\n this._strokeStyle = value;\n this.record('set:strokeStyle', [value]);\n }\n\n get lineWidth(): number {\n return this._lineWidth;\n }\n\n set lineWidth(value: number) {\n this._lineWidth = value;\n this.record('set:lineWidth', [value]);\n }\n\n get lineJoin(): CanvasLineJoin {\n return this._lineJoin;\n }\n\n set lineJoin(value: CanvasLineJoin) {\n this._lineJoin = value;\n this.record('set:lineJoin', [value]);\n }\n\n get globalAlpha(): number {\n return this._globalAlpha;\n }\n\n set globalAlpha(value: number) {\n this._globalAlpha = value;\n this.record('set:globalAlpha', [value]);\n }\n\n get font(): string {\n return this._font;\n }\n\n set font(value: string) {\n this._font = value;\n this.record('set:font', [value]);\n }\n\n get textAlign(): CanvasTextAlign {\n return this._textAlign;\n }\n\n set textAlign(value: CanvasTextAlign) {\n this._textAlign = value;\n this.record('set:textAlign', [value]);\n }\n\n get textBaseline(): CanvasTextBaseline {\n return this._textBaseline;\n }\n\n set textBaseline(value: CanvasTextBaseline) {\n this._textBaseline = value;\n this.record('set:textBaseline', [value]);\n }\n\n save(): void {\n this.record('save', []);\n }\n\n restore(): void {\n this.record('restore', []);\n }\n\n translate(x: number, y: number): void {\n this.record('translate', [x, y]);\n }\n\n scale(x: number, y: number): void {\n this.record('scale', [x, y]);\n }\n\n rotate(angle: number): void {\n this.record('rotate', [angle]);\n }\n\n setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void {\n this.record('setTransform', [a, b, c, d, e, f]);\n }\n\n clearRect(x: number, y: number, width: number, height: number): void {\n this.record('clearRect', [x, y, width, height]);\n }\n\n fillRect(x: number, y: number, width: number, height: number): void {\n this.record('fillRect', [x, y, width, height]);\n }\n\n strokeRect(x: number, y: number, width: number, height: number): void {\n this.record('strokeRect', [x, y, width, height]);\n }\n\n beginPath(): void {\n this.record('beginPath', []);\n }\n\n moveTo(x: number, y: number): void {\n this.record('moveTo', [x, y]);\n }\n\n lineTo(x: number, y: number): void {\n this.record('lineTo', [x, y]);\n }\n\n arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {\n this.record('arcTo', [x1, y1, x2, y2, radius]);\n }\n\n closePath(): void {\n this.record('closePath', []);\n }\n\n rect(x: number, y: number, width: number, height: number): void {\n this.record('rect', [x, y, width, height]);\n }\n\n clip(): void {\n this.record('clip', []);\n }\n\n fill(): void {\n this.record('fill', []);\n }\n\n stroke(): void {\n this.record('stroke', []);\n }\n\n fillText(text: string, x: number, y: number, maxWidth?: number): void {\n const args = maxWidth === undefined ? [text, x, y] : [text, x, y, maxWidth];\n this.record('fillText', args);\n }\n\n measureText(text: string): TextMetrics {\n this.record('measureText', [text]);\n return { width: text.length * 8 } as TextMetrics;\n }\n\n drawImage(image: ImageBitmap, dx: number, dy: number, dw: number, dh: number): void {\n this.record('drawImage', [image, dx, dy, dw, dh]);\n }\n\n createLinearGradient(x0: number, y0: number, x1: number, y1: number): RecordedGradient {\n const gradient = new RecordedGradient('linear', [x0, y0, x1, y1]);\n this.record('createLinearGradient', [x0, y0, x1, y1]);\n return gradient;\n }\n\n createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): RecordedGradient {\n const gradient = new RecordedGradient('radial', [x0, y0, r0, x1, y1, r1]);\n this.record('createRadialGradient', [x0, y0, r0, x1, y1, r1]);\n return gradient;\n }\n\n private record(name: string, args: unknown[]): void {\n this.calls.push({ name, args: args.map(normalizeNumber) });\n }\n}\n\n/**\n * Normalizes -0 to 0 so scroll translations assert cleanly.\n */\nfunction normalizeNumber(value: unknown): unknown {\n if (typeof value === 'number' && Object.is(value, -0)) {\n return 0;\n }\n return value;\n}\n\nexport function callNames(context: RecordingCanvasContext): string[] {\n return context.calls.map(call => call.name);\n}\n\nexport function callsOf(context: RecordingCanvasContext, name: string): RecordedCall[] {\n return context.calls.filter(call => call.name === name);\n}\n\nexport function callArgs(context: RecordingCanvasContext, name: string): unknown[][] {\n return callsOf(context, name).map(call => (call.name.startsWith('set:') ? call.args[0] : call.args) as unknown[]);\n}\n\nexport function savedDepth(context: RecordingCanvasContext): number {\n let depth = 0;\n for (const call of context.calls) {\n if (call.name === 'save') {\n depth++;\n } else if (call.name === 'restore') {\n depth--;\n }\n }\n return depth;\n}\n\n/**\n * Deterministic `PaintContext2D` spy: the picture rasteriser's\n * equivalent of `RecordingCanvasContext`, so a spec can assert what a\n * painter actually drew without a browser.\n */\nexport class RecordingPaintContext implements PaintContext2D {\n readonly calls: RecordedCall[] = [];\n\n lineDashOffset = 0;\n lineWidth = 1;\n lineCap: PaintLineCap = 'butt';\n lineJoin: PaintLineJoin = 'miter';\n miterLimit = 10;\n globalAlpha = 1;\n filter = 'none';\n fillStyle: string | PaintGradient = '#000';\n strokeStyle: string | PaintGradient = '#000';\n font = '';\n textAlign: 'left' | 'center' | 'right' = 'left';\n textBaseline = 'alphabetic' as const;\n\n save(): void {\n this.record('save', []);\n }\n\n restore(): void {\n this.record('restore', []);\n }\n\n translate(x: number, y: number): void {\n this.record('translate', [x, y]);\n }\n\n scale(x: number, y: number): void {\n this.record('scale', [x, y]);\n }\n\n rotate(angle: number): void {\n this.record('rotate', [angle]);\n }\n\n transform(a: number, b: number, c: number, d: number, e: number, f: number): void {\n this.record('transform', [a, b, c, d, e, f]);\n }\n\n beginPath(): void {\n this.record('beginPath', []);\n }\n\n moveTo(x: number, y: number): void {\n this.record('moveTo', [x, y]);\n }\n\n lineTo(x: number, y: number): void {\n this.record('lineTo', [x, y]);\n }\n\n quadraticCurveTo(cx: number, cy: number, x: number, y: number): void {\n this.record('quadraticCurveTo', [cx, cy, x, y]);\n }\n\n bezierCurveTo(c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number): void {\n this.record('bezierCurveTo', [c1x, c1y, c2x, c2y, x, y]);\n }\n\n arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise = false): void {\n this.record('arc', [x, y, radius, startAngle, endAngle, counterclockwise]);\n }\n\n arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void {\n this.record('arcTo', [x1, y1, x2, y2, radius]);\n }\n\n rect(x: number, y: number, width: number, height: number): void {\n this.record('rect', [x, y, width, height]);\n }\n\n closePath(): void {\n this.record('closePath', []);\n }\n\n fill(fillRule?: 'nonzero' | 'evenodd'): void {\n this.record('fill', [fillRule ?? 'nonzero', this.fillStyle]);\n }\n\n stroke(): void {\n this.record('stroke', [this.strokeStyle, this.lineWidth]);\n }\n\n clip(fillRule?: 'nonzero' | 'evenodd'): void {\n this.record('clip', [fillRule ?? 'nonzero']);\n }\n\n fillText(text: string, x: number, y: number): void {\n this.record('fillText', [text, x, y, this.font, this.textAlign]);\n }\n\n drawImage(image: ImageBitmap, dx: number, dy: number, dw: number, dh: number): void {\n this.record('drawImage', [image, dx, dy, dw, dh]);\n }\n\n setLineDash(segments: readonly number[]): void {\n this.record('setLineDash', [[...segments]]);\n }\n\n createLinearGradient(x0: number, y0: number, x1: number, y1: number): RecordedGradient {\n const gradient = new RecordedGradient('linear', [x0, y0, x1, y1]);\n this.record('createLinearGradient', [x0, y0, x1, y1]);\n return gradient;\n }\n\n createRadialGradient(x0: number, y0: number, r0: number, x1: number, y1: number, r1: number): RecordedGradient {\n const gradient = new RecordedGradient('radial', [x0, y0, r0, x1, y1, r1]);\n this.record('createRadialGradient', [x0, y0, r0, x1, y1, r1]);\n return gradient;\n }\n\n private record(name: string, args: unknown[]): void {\n this.calls.push({ name, args: args.map(normalizeNumber) });\n }\n}\n\n/**\n * A picture that never becomes real pixels.\n *\n * `take()` hands back a stand-in for the `ImageBitmap` a browser would\n * transfer out of an `OffscreenCanvas`, carrying only the size and a\n * `close`, which is all either renderer reads off one. Every context\n * the factory hands out is kept, so a spec can assert both what was\n * drawn and how many times anything was drawn at all.\n */\nexport class FakePaintCanvases {\n readonly contexts: RecordingPaintContext[] = [];\n readonly bitmaps: { width: number; height: number; closed: boolean }[] = [];\n\n readonly create: PaintCanvasFactory = (width, height) => {\n const context = new RecordingPaintContext();\n this.contexts.push(context);\n const bitmap = { width, height, closed: false };\n this.bitmaps.push(bitmap);\n return {\n context: () => context,\n take: () =>\n ({\n width,\n height,\n close: () => {\n bitmap.closed = true;\n }\n }) as unknown as ImageBitmap\n };\n };\n\n /** The calls made into the most recent picture. */\n get last(): RecordingPaintContext {\n const context = this.contexts.at(-1);\n if (context === undefined) {\n throw new Error('No picture has been rasterised.');\n }\n return context;\n }\n\n reset(): void {\n this.contexts.length = 0;\n this.bitmaps.length = 0;\n }\n}\n\n/**\n * A canvas host backed by a recording context.\n */\nexport class FakeCanvasHost implements CanvasHost {\n width = 0;\n height = 0;\n private contextRequests = 0;\n\n constructor(private readonly context: Canvas2DContext | null) {}\n\n getContext(_contextId: '2d'): CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null {\n this.contextRequests++;\n return this.context as unknown as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n }\n\n get contextRequestCount(): number {\n return this.contextRequests;\n }\n}\n\n/**\n * Shared harness: a real UiGraph + LayoutEngine + Canvas2DRenderer\n * over a recording context. Deterministic layout comes from the\n * CharacterCountTextMeasurer, shared by layout and renderer.\n */\nexport class RenderHarness {\n readonly graph = new UiGraph();\n readonly measurer: TextMeasurer = new CharacterCountTextMeasurer();\n readonly engine = new LayoutEngine(this.measurer);\n readonly context = new RecordingCanvasContext();\n readonly host = new FakeCanvasHost(this.context);\n readonly surface = new CanvasSurface(this.host);\n readonly renderer = new Canvas2DRenderer({ surface: this.surface });\n\n constructor(logicalWidth: number = 800, logicalHeight: number = 600, dpr: number = 1) {\n this.surface.setLogicalSize(logicalWidth, logicalHeight, dpr);\n }\n\n createNode(id: string, type: UiNodeType): UiNode {\n return this.graph.createNode(id, type);\n }\n\n append(parent: UiNode, ...children: UiNode[]): void {\n for (const child of children) {\n this.graph.appendChild(parent, child);\n }\n }\n\n layout(node: UiNode, constraints: Constraints = Constraints.tight(800, 600)): LayoutResult {\n return this.engine.layout(node, constraints);\n }\n\n render(node: UiNode): void {\n // Far in the future: overlay scrollbars have faded, so draw-call\n // assertions see only the scene. Pass `now` yourself to test them.\n this.renderer.render(node, { layout: this.engine, text: this.measurer, now: Number.MAX_SAFE_INTEGER });\n }\n\n record(node: UiNode): LayoutRecord {\n const record = this.engine.recordFor(node);\n if (record === undefined) {\n throw new Error(`No layout record for '${node.id}'.`);\n }\n return record;\n }\n\n box(node: UiNode): LayoutBox {\n const record = this.record(node);\n return { x: record.x, y: record.y, width: record.width, height: record.height };\n }\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "gesso-core",
3
+ "version": "0.1.0",
4
+ "description": "The retained UI graph, the layout engine, the two renderers and input — everything below a component.",
5
+ "license": "MIT",
6
+ "author": "Kevin Baker",
7
+ "homepage": "https://github.com/kevinpbaker/gesso#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kevinpbaker/gesso.git",
11
+ "directory": "packages/core"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/kevinpbaker/gesso/issues"
15
+ },
16
+ "keywords": [
17
+ "gesso",
18
+ "canvas",
19
+ "ui",
20
+ "typescript",
21
+ "retained-mode",
22
+ "layout",
23
+ "flexbox",
24
+ "css-grid",
25
+ "canvas2d",
26
+ "webgpu",
27
+ "text-layout",
28
+ "renderer"
29
+ ],
30
+ "type": "module",
31
+ "sideEffects": false,
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ },
37
+ "./testing": {
38
+ "types": "./dist/testing.d.ts",
39
+ "default": "./dist/testing.js"
40
+ }
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "files": [
46
+ "README.md",
47
+ "CHANGELOG.md",
48
+ "dist",
49
+ "LICENSE"
50
+ ],
51
+ "dependencies": {
52
+ "@webgpu/types": "^0.1.71"
53
+ },
54
+ "peerDependencies": {
55
+ "rxjs": "^7.8.2"
56
+ },
57
+ "devDependencies": {
58
+ "rxjs": "^7.8.2"
59
+ },
60
+ "scripts": {
61
+ "build": "tsdown"
62
+ }
63
+ }