@jamesyong42/infinite-canvas 1.3.0 → 1.6.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/dist/advanced.cjs +2 -2
- package/dist/advanced.d.cts +2 -2
- package/dist/advanced.d.mts +2 -2
- package/dist/advanced.mjs +2 -2
- package/dist/devtools.cjs +3 -6
- package/dist/devtools.cjs.map +1 -1
- package/dist/devtools.d.cts +1 -1
- package/dist/devtools.d.mts +1 -1
- package/dist/devtools.mjs +2 -5
- package/dist/devtools.mjs.map +1 -1
- package/dist/{ecs-B4QrqfvQ.cjs → ecs-BtX_rCS3.cjs} +57 -2
- package/dist/ecs-BtX_rCS3.cjs.map +1 -0
- package/dist/{ecs-3kimUV5Z.mjs → ecs-O6AR7iFp.mjs} +33 -2
- package/dist/ecs-O6AR7iFp.mjs.map +1 -0
- package/dist/{hooks-CtP02JNt.cjs → hooks-B-UPFgGj.cjs} +2 -5
- package/dist/{hooks-CtP02JNt.cjs.map → hooks-B-UPFgGj.cjs.map} +1 -1
- package/dist/{hooks-gsQDDE56.mjs → hooks-Cpu0rEMv.mjs} +2 -5
- package/dist/{hooks-gsQDDE56.mjs.map → hooks-Cpu0rEMv.mjs.map} +1 -1
- package/dist/{index-DSdbSQ_t.d.cts → index-BXRsEYL9.d.cts} +248 -242
- package/dist/index-BXRsEYL9.d.cts.map +1 -0
- package/dist/{index-Dj9odADH.d.mts → index-Cxrz-hoe.d.mts} +248 -242
- package/dist/index-Cxrz-hoe.d.mts.map +1 -0
- package/dist/{index-B7B1tRPl.d.cts → index-DSGDEjP7.d.cts} +2 -2
- package/dist/{index-3GY7T8JM.d.mts.map → index-DSGDEjP7.d.cts.map} +1 -1
- package/dist/{index-3GY7T8JM.d.mts → index-h56yzXZy.d.mts} +2 -2
- package/dist/{index-B7B1tRPl.d.cts.map → index-h56yzXZy.d.mts.map} +1 -1
- package/dist/index.cjs +434 -173
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +435 -174
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/ecs-3kimUV5Z.mjs.map +0 -1
- package/dist/ecs-B4QrqfvQ.cjs.map +0 -1
- package/dist/index-DSdbSQ_t.d.cts.map +0 -1
- package/dist/index-Dj9odADH.d.mts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_hooks = require("./hooks-
|
|
3
|
-
const require_ecs = require("./ecs-
|
|
2
|
+
const require_hooks = require("./hooks-B-UPFgGj.cjs");
|
|
3
|
+
const require_ecs = require("./ecs-BtX_rCS3.cjs");
|
|
4
4
|
let _jamesyong42_reactive_ecs = require("@jamesyong42/reactive-ecs");
|
|
5
5
|
let react = require("react");
|
|
6
6
|
react = require_hooks.__toESM(react, 1);
|
|
@@ -281,6 +281,7 @@ function clamp(value, min, max) {
|
|
|
281
281
|
*/
|
|
282
282
|
const breakpointSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
283
283
|
name: "breakpoint",
|
|
284
|
+
phase: "derive",
|
|
284
285
|
after: "cull",
|
|
285
286
|
execute: (world) => {
|
|
286
287
|
const camera = world.getResource(require_ecs.CameraResource);
|
|
@@ -325,6 +326,7 @@ const breakpointSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
|
325
326
|
*/
|
|
326
327
|
const cardSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
327
328
|
name: "card",
|
|
329
|
+
phase: "derive",
|
|
328
330
|
execute: (world) => {
|
|
329
331
|
const resource = world.getResource(require_ecs.CardPresetsResource);
|
|
330
332
|
if (!resource) return;
|
|
@@ -343,6 +345,94 @@ const cardSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
|
343
345
|
}
|
|
344
346
|
});
|
|
345
347
|
//#endregion
|
|
348
|
+
//#region src/ecs/systems/cleanup.ts
|
|
349
|
+
/**
|
|
350
|
+
* `cleanup`-phase systems that close out a tick. They run in this order
|
|
351
|
+
* (enforced via within-phase `after:` constraints):
|
|
352
|
+
*
|
|
353
|
+
* clearDirty → incrementTick → emitFrame → tweenKeepalive
|
|
354
|
+
*
|
|
355
|
+
* 1. `clearDirty` — clear the World's per-frame `queryChanged` /
|
|
356
|
+
* `queryAdded` buffers AND reset the engine-level
|
|
357
|
+
* `EngineDirtyResource.dirty` flag.
|
|
358
|
+
* 2. `incrementTick` — bump `world.currentTick`. Must come after
|
|
359
|
+
* `clearDirty` so subscribers triggered by
|
|
360
|
+
* `emitFrame` see the new tick number.
|
|
361
|
+
* 3. `emitFrame` — notify `onFrame` subscribers (`EngineInvalidator`,
|
|
362
|
+
* widget state machine, React hooks).
|
|
363
|
+
* 4. `tweenKeepalive` — RFC-004 § Phase 2/4. If any `TransformTween` is
|
|
364
|
+
* still alive, re-set `EngineDirtyResource.dirty`
|
|
365
|
+
* so the next rAF wakes the engine and the
|
|
366
|
+
* animation keeps running. Must run *after*
|
|
367
|
+
* `clearDirty`'s reset; otherwise its write would
|
|
368
|
+
* be clobbered.
|
|
369
|
+
*
|
|
370
|
+
* RFC-010 Phase 4 — replaces lines 887–903 of the inline tail of
|
|
371
|
+
* `engine.tick()`.
|
|
372
|
+
*/
|
|
373
|
+
const clearDirtySystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
374
|
+
name: "clearDirty",
|
|
375
|
+
phase: "cleanup",
|
|
376
|
+
execute: (world) => {
|
|
377
|
+
world.clearDirty();
|
|
378
|
+
world.setResource(require_ecs.EngineDirtyResource, { dirty: false });
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
const incrementTickSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
382
|
+
name: "incrementTick",
|
|
383
|
+
phase: "cleanup",
|
|
384
|
+
after: "clearDirty",
|
|
385
|
+
execute: (world) => {
|
|
386
|
+
world.incrementTick();
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
const emitFrameSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
390
|
+
name: "emitFrame",
|
|
391
|
+
phase: "cleanup",
|
|
392
|
+
after: "incrementTick",
|
|
393
|
+
execute: (world) => {
|
|
394
|
+
world.emitFrame();
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
const tweenKeepaliveSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
398
|
+
name: "tweenKeepalive",
|
|
399
|
+
phase: "cleanup",
|
|
400
|
+
after: "emitFrame",
|
|
401
|
+
execute: (world) => {
|
|
402
|
+
for (const _ of world.query(require_ecs.TransformTween)) {
|
|
403
|
+
world.setResource(require_ecs.EngineDirtyResource, { dirty: true });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/ecs/systems/container-camera.ts
|
|
410
|
+
/**
|
|
411
|
+
* Auto-attach a default `ContainerCamera` on every entity that just received
|
|
412
|
+
* `Container`, so each container has a usable per-frame camera from birth.
|
|
413
|
+
* Serialization round-trips cleanly; `enterContainer` can read without
|
|
414
|
+
* falling back.
|
|
415
|
+
*
|
|
416
|
+
* Diff signal: `world.queryAdded(Container)` — only entities that received
|
|
417
|
+
* the component *this tick*, matching the original observer's
|
|
418
|
+
* `prev === undefined` guard. Mutating an existing `Container.enterable`
|
|
419
|
+
* does not re-stamp a fresh camera.
|
|
420
|
+
*
|
|
421
|
+
* RFC-010 Phase 3 — migrates the `Container` add observer at
|
|
422
|
+
* `LayoutEngine.ts:147–155` into a `react`-phase system.
|
|
423
|
+
*/
|
|
424
|
+
const containerCameraSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
425
|
+
name: "containerCamera",
|
|
426
|
+
phase: "react",
|
|
427
|
+
execute: (world) => {
|
|
428
|
+
for (const entity of world.queryAdded(require_ecs.Container)) if (!world.hasComponent(entity, require_ecs.ContainerCamera)) world.addComponent(entity, require_ecs.ContainerCamera, {
|
|
429
|
+
x: 0,
|
|
430
|
+
y: 0,
|
|
431
|
+
zoom: 1
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
//#endregion
|
|
346
436
|
//#region src/ecs/systems/cull.ts
|
|
347
437
|
/**
|
|
348
438
|
* Viewport culling — for every `Active` entity, sets exactly one of `Visible`
|
|
@@ -355,7 +445,7 @@ const cardSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
|
355
445
|
*/
|
|
356
446
|
const cullSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
357
447
|
name: "cull",
|
|
358
|
-
|
|
448
|
+
phase: "derive",
|
|
359
449
|
execute: (world) => {
|
|
360
450
|
const camera = world.getResource(require_ecs.CameraResource);
|
|
361
451
|
const viewport = world.getResource(require_ecs.ViewportResource);
|
|
@@ -400,14 +490,13 @@ const cullSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
|
400
490
|
* Dragging present, no PreDragLayer → promote (stash old layer, set overlay)
|
|
401
491
|
* PreDragLayer present, no Dragging → restore (write back stashed layer)
|
|
402
492
|
*
|
|
403
|
-
* RFC-010
|
|
404
|
-
* `
|
|
405
|
-
* `
|
|
406
|
-
* and becomes implicit once RFC-010 Phase 2 stamps `phase: 'react'`.
|
|
493
|
+
* RFC-010 — runs in the `react` phase so the promote/restore is settled
|
|
494
|
+
* before `derive`-phase systems (`cull`, `breakpoint`, `sort`) and the
|
|
495
|
+
* `present`-phase visibility / frame-changes assembly run.
|
|
407
496
|
*/
|
|
408
497
|
const dragPromoteSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
409
498
|
name: "dragPromote",
|
|
410
|
-
|
|
499
|
+
phase: "react",
|
|
411
500
|
execute: (world) => {
|
|
412
501
|
for (const entity of world.queryTagged(require_ecs.Dragging)) {
|
|
413
502
|
if (world.hasComponent(entity, require_ecs.PreDragLayer)) continue;
|
|
@@ -428,6 +517,89 @@ const dragPromoteSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
|
428
517
|
}
|
|
429
518
|
});
|
|
430
519
|
//#endregion
|
|
520
|
+
//#region src/ecs/systems/frame-changes.ts
|
|
521
|
+
/**
|
|
522
|
+
* Assemble the `FrameChanges` snapshot consumed by `<InfiniteCanvas>`'s
|
|
523
|
+
* rAF tail (DOM transform writes, R3F invalidate, layer re-bucket, event
|
|
524
|
+
* callbacks). Runs in `present` after `visibility` so the prev/current
|
|
525
|
+
* sets are populated.
|
|
526
|
+
*
|
|
527
|
+
* Inputs:
|
|
528
|
+
* - `world.queryChanged(Transform2D | WidgetBreakpoint | ZIndex | Layer)`
|
|
529
|
+
* — entityId arrays for each tracked component change.
|
|
530
|
+
* - `VisibleEntitiesResource.{current, prev}` — set diffing for entered
|
|
531
|
+
* and exited.
|
|
532
|
+
* - `TickFlagsResource` — camera / selection / navigation flags set out
|
|
533
|
+
* of band by engine APIs and `navStackCaptureSystem`.
|
|
534
|
+
*
|
|
535
|
+
* Side effects:
|
|
536
|
+
* - Writes `FrameChangesResource.changes`.
|
|
537
|
+
* - Resets `TickFlagsResource.cameraChanged` and `selectionChanged` to
|
|
538
|
+
* `false` (the flags are per-tick; consuming them clears them so the
|
|
539
|
+
* next tick starts fresh). `navigationChangedSnapshot` is overwritten
|
|
540
|
+
* by `navStackCaptureSystem` next tick — no need to reset here.
|
|
541
|
+
*
|
|
542
|
+
* RFC-010 Phase 4 — replaces lines 859–883 of the inline tail of
|
|
543
|
+
* `engine.tick()`.
|
|
544
|
+
*/
|
|
545
|
+
const frameChangesSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
546
|
+
name: "frameChanges",
|
|
547
|
+
phase: "present",
|
|
548
|
+
after: "visibility",
|
|
549
|
+
execute: (world) => {
|
|
550
|
+
const visible = world.getResource(require_ecs.VisibleEntitiesResource);
|
|
551
|
+
const flags = world.getResource(require_ecs.TickFlagsResource);
|
|
552
|
+
const newSet = /* @__PURE__ */ new Set();
|
|
553
|
+
for (const v of visible.current) newSet.add(v.entityId);
|
|
554
|
+
const entered = [];
|
|
555
|
+
const exited = [];
|
|
556
|
+
for (const id of newSet) if (!visible.prev.has(id)) entered.push(id);
|
|
557
|
+
for (const id of visible.prev) if (!newSet.has(id)) exited.push(id);
|
|
558
|
+
world.setResource(require_ecs.FrameChangesResource, { changes: {
|
|
559
|
+
positionsChanged: world.queryChanged(require_ecs.Transform2D),
|
|
560
|
+
breakpointsChanged: world.queryChanged(require_ecs.WidgetBreakpoint),
|
|
561
|
+
zIndicesChanged: world.queryChanged(require_ecs.ZIndex),
|
|
562
|
+
entered,
|
|
563
|
+
exited,
|
|
564
|
+
cameraChanged: flags.cameraChanged,
|
|
565
|
+
navigationChanged: flags.navigationChangedSnapshot,
|
|
566
|
+
selectionChanged: flags.selectionChanged,
|
|
567
|
+
layersChanged: world.queryChanged(require_ecs.Layer).length > 0
|
|
568
|
+
} });
|
|
569
|
+
world.setResource(require_ecs.TickFlagsResource, {
|
|
570
|
+
cameraChanged: false,
|
|
571
|
+
selectionChanged: false
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
//#endregion
|
|
576
|
+
//#region src/ecs/systems/nav-stack-capture.ts
|
|
577
|
+
/**
|
|
578
|
+
* Snapshot `NavigationStackResource.changed` into
|
|
579
|
+
* `TickFlagsResource.navigationChangedSnapshot` BEFORE
|
|
580
|
+
* `navigationFilterSystem` (`control` phase) resets the flag mid-tick.
|
|
581
|
+
*
|
|
582
|
+
* RFC-004 § Phase 0c invariant — `navigationFilter` mutates
|
|
583
|
+
* `navStack.changed = false` as its reset signal; reading the flag after
|
|
584
|
+
* `control` would always see false and this-tick navigation pushes/pops
|
|
585
|
+
* would silently miss their `FrameChanges.navigationChanged` notification.
|
|
586
|
+
*
|
|
587
|
+
* Lives in `input` (the earliest phase) so the snapshot is taken before
|
|
588
|
+
* any other engine system runs. `frameChangesSystem` (`present` phase)
|
|
589
|
+
* later reads the snapshot when assembling `FrameChanges`.
|
|
590
|
+
*
|
|
591
|
+
* RFC-010 Phase 4 — replaces the inline pre-`scheduler.execute` capture
|
|
592
|
+
* at lines 815–816 of the previous `engine.tick()` body.
|
|
593
|
+
*/
|
|
594
|
+
const navStackCaptureSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
595
|
+
name: "navStackCapture",
|
|
596
|
+
phase: "input",
|
|
597
|
+
execute: (world) => {
|
|
598
|
+
const navStack = world.getResource(require_ecs.NavigationStackResource);
|
|
599
|
+
world.setResource(require_ecs.TickFlagsResource, { navigationChangedSnapshot: navStack?.changed ?? false });
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
//#endregion
|
|
431
603
|
//#region src/ecs/systems/navigation-filter.ts
|
|
432
604
|
/**
|
|
433
605
|
* Reconcile `Active` for a single entity against the given nav frame.
|
|
@@ -455,6 +627,7 @@ function reconcileEntityActive(world, entity) {
|
|
|
455
627
|
*/
|
|
456
628
|
const navigationFilterSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
457
629
|
name: "navigationFilter",
|
|
630
|
+
phase: "control",
|
|
458
631
|
execute: (world) => {
|
|
459
632
|
const navStack = world.getResource(require_ecs.NavigationStackResource);
|
|
460
633
|
const stackChanged = navStack.changed;
|
|
@@ -473,12 +646,101 @@ const navigationFilterSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
|
473
646
|
}
|
|
474
647
|
});
|
|
475
648
|
//#endregion
|
|
649
|
+
//#region src/ecs/systems/parent-frame-active.ts
|
|
650
|
+
/**
|
|
651
|
+
* Keep `Active` in sync with mid-session `ParentFrame` mutations (RFC-004
|
|
652
|
+
* § Phase 5). Covers the consume path (a child gets `ParentFrame` and
|
|
653
|
+
* should leave the current frame) and re-parenting (the id changes).
|
|
654
|
+
* Without this, a consumed card would retain `Active` at root until the
|
|
655
|
+
* next nav-stack change and would render on top of its own container.
|
|
656
|
+
*
|
|
657
|
+
* Diff signal: `world.queryChanged(ParentFrame)` — entities whose
|
|
658
|
+
* `ParentFrame` was added or set this tick (same set the previous
|
|
659
|
+
* `onComponentChanged` observer fired for).
|
|
660
|
+
*
|
|
661
|
+
* **Limitation: `ParentFrame` removal is not handled here.** Neither
|
|
662
|
+
* `reactive-ecs`'s `onComponentChanged` observer (the previous
|
|
663
|
+
* implementation) nor `queryChanged` emits on `removeComponent`, so the
|
|
664
|
+
* "undo (ParentFrame removed, child returns to root)" case described in
|
|
665
|
+
* the original RFC-004 § Phase 5 comment was never actually covered by
|
|
666
|
+
* this code path. It is recovered by `navigationFilterSystem`'s full
|
|
667
|
+
* refilter on the next `navStack.changed` event. If an `Active` invariant
|
|
668
|
+
* needs to be live across removes, either add `world.queryRemoved` to
|
|
669
|
+
* reactive-ecs or push `navStack.changed = true` from the remover.
|
|
670
|
+
*
|
|
671
|
+
* RFC-010 Phase 3 — migrates the `ParentFrame` observer at
|
|
672
|
+
* `LayoutEngine.ts:165–170` into a `react`-phase system.
|
|
673
|
+
*/
|
|
674
|
+
const parentFrameActiveSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
675
|
+
name: "parentFrameActive",
|
|
676
|
+
phase: "react",
|
|
677
|
+
execute: (world) => {
|
|
678
|
+
for (const entity of world.queryChanged(require_ecs.ParentFrame)) reconcileEntityActive(world, entity);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
//#endregion
|
|
682
|
+
//#region src/ecs/systems/role-refresh.ts
|
|
683
|
+
/**
|
|
684
|
+
* Reconcile `InteractionRole` and `CursorHint` for a single entity against
|
|
685
|
+
* its current `Draggable` / `Selectable` tag state. Bails if the entity has
|
|
686
|
+
* a custom role (rotate, connect, etc.) — only the default drag/select/
|
|
687
|
+
* canvas roles are auto-managed.
|
|
688
|
+
*/
|
|
689
|
+
function refreshInteractionRole(world, entity) {
|
|
690
|
+
const current = world.getComponent(entity, require_ecs.InteractionRole);
|
|
691
|
+
if (current && current.role.type !== "drag" && current.role.type !== "select" && current.role.type !== "canvas") return;
|
|
692
|
+
const hasDraggable = world.hasTag(entity, require_ecs.Draggable);
|
|
693
|
+
const hasSelectable = world.hasTag(entity, require_ecs.Selectable);
|
|
694
|
+
const desiredRole = hasDraggable ? { type: "drag" } : hasSelectable ? { type: "select" } : null;
|
|
695
|
+
if (desiredRole === null) {
|
|
696
|
+
if (current) world.removeComponent(entity, require_ecs.InteractionRole);
|
|
697
|
+
if (world.hasComponent(entity, require_ecs.CursorHint)) world.removeComponent(entity, require_ecs.CursorHint);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (!current) world.addComponent(entity, require_ecs.InteractionRole, {
|
|
701
|
+
layer: 5,
|
|
702
|
+
role: desiredRole
|
|
703
|
+
});
|
|
704
|
+
else if (current.role.type !== desiredRole.type) world.setComponent(entity, require_ecs.InteractionRole, { role: desiredRole });
|
|
705
|
+
if (desiredRole.type === "drag" && !world.hasComponent(entity, require_ecs.CursorHint)) world.addComponent(entity, require_ecs.CursorHint, {
|
|
706
|
+
hover: "grab",
|
|
707
|
+
active: "grabbing"
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Auto-attach `InteractionRole` and `CursorHint` based on `Draggable` /
|
|
712
|
+
* `Selectable` tag presence. Entities with an explicit non-drag/non-select
|
|
713
|
+
* role (rotate/connect/etc.) are left alone.
|
|
714
|
+
*
|
|
715
|
+
* Diff signal: `reactive-ecs` has no `queryAddedTag` / `queryRemovedTag`
|
|
716
|
+
* primitive, so this system recomputes idempotently each tick over the
|
|
717
|
+
* union {Draggable ∪ Selectable ∪ InteractionRole}. The refresh helper
|
|
718
|
+
* skips entities whose role is already correct, so steady-state writes are
|
|
719
|
+
* zero.
|
|
720
|
+
*
|
|
721
|
+
* RFC-010 Phase 3 — migrates the four tag observers at
|
|
722
|
+
* `LayoutEngine.ts:175–213` (Draggable/Selectable add/remove) into a
|
|
723
|
+
* `react`-phase system.
|
|
724
|
+
*/
|
|
725
|
+
const roleRefreshSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
726
|
+
name: "roleRefresh",
|
|
727
|
+
phase: "react",
|
|
728
|
+
execute: (world) => {
|
|
729
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
730
|
+
for (const e of world.queryTagged(require_ecs.Draggable)) candidates.add(e);
|
|
731
|
+
for (const e of world.queryTagged(require_ecs.Selectable)) candidates.add(e);
|
|
732
|
+
for (const e of world.query(require_ecs.InteractionRole)) candidates.add(e);
|
|
733
|
+
for (const entity of candidates) refreshInteractionRole(world, entity);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
//#endregion
|
|
476
737
|
//#region src/ecs/systems/sort.ts
|
|
477
738
|
/**
|
|
478
739
|
* Sort visible entities by z-index (handled in engine.tick()).
|
|
479
740
|
*/
|
|
480
741
|
const sortSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
481
742
|
name: "sort",
|
|
743
|
+
phase: "derive",
|
|
482
744
|
after: "breakpoint",
|
|
483
745
|
execute: (_world) => {}
|
|
484
746
|
});
|
|
@@ -516,6 +778,7 @@ function applyEasing(p, easing) {
|
|
|
516
778
|
*/
|
|
517
779
|
const transformTweenSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
518
780
|
name: "transformTween",
|
|
781
|
+
phase: "simulate",
|
|
519
782
|
execute: (world) => {
|
|
520
783
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
521
784
|
for (const entity of world.query(require_ecs.TransformTween)) {
|
|
@@ -544,6 +807,60 @@ const transformTweenSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
|
544
807
|
}
|
|
545
808
|
});
|
|
546
809
|
//#endregion
|
|
810
|
+
//#region src/ecs/systems/visibility.ts
|
|
811
|
+
/**
|
|
812
|
+
* Build the per-frame visible-entity list consumed by `InfiniteCanvas`.
|
|
813
|
+
*
|
|
814
|
+
* Iterates `query(Widget, Visible)` (the latter tag is set by `cullSystem`
|
|
815
|
+
* in the same `derive` phase that ran earlier this tick), pulls render-
|
|
816
|
+
* relevant fields off `Transform2D` / `WidgetBreakpoint` / `ZIndex`, sorts
|
|
817
|
+
* by `zIndex`, and writes the resulting array into `VisibleEntitiesResource.
|
|
818
|
+
* current`. The previous tick's entityId set is also preserved on
|
|
819
|
+
* `.prev` so `frameChangesSystem` can compute entered/exited.
|
|
820
|
+
*
|
|
821
|
+
* RFC-010 Phase 4 — replaces lines 830–857 of the inline tail of
|
|
822
|
+
* `engine.tick()`. Profiler instrumentation flows automatically through
|
|
823
|
+
* `SystemProfiler.beginSystem('visibility')`; the previously-dedicated
|
|
824
|
+
* `Profiler.beginVisibility / endVisibility` calls are dropped (the
|
|
825
|
+
* exported `visibilityMs` stat field becomes vestigial — `systemAvg.
|
|
826
|
+
* visibility` carries the same signal).
|
|
827
|
+
*/
|
|
828
|
+
const visibilitySystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
829
|
+
name: "visibility",
|
|
830
|
+
phase: "present",
|
|
831
|
+
execute: (world) => {
|
|
832
|
+
const newVisible = [];
|
|
833
|
+
const newVisibleSet = /* @__PURE__ */ new Set();
|
|
834
|
+
for (const entity of world.query(require_ecs.Widget, require_ecs.Visible)) {
|
|
835
|
+
const t = world.getComponent(entity, require_ecs.Transform2D);
|
|
836
|
+
const widget = world.getComponent(entity, require_ecs.Widget);
|
|
837
|
+
const bp = world.getComponent(entity, require_ecs.WidgetBreakpoint);
|
|
838
|
+
const zIdx = world.getComponent(entity, require_ecs.ZIndex);
|
|
839
|
+
if (!t || !widget) continue;
|
|
840
|
+
newVisibleSet.add(entity);
|
|
841
|
+
newVisible.push({
|
|
842
|
+
entityId: entity,
|
|
843
|
+
x: t.x,
|
|
844
|
+
y: t.y,
|
|
845
|
+
width: t.width,
|
|
846
|
+
height: t.height,
|
|
847
|
+
breakpoint: bp?.current ?? "normal",
|
|
848
|
+
zIndex: zIdx?.value ?? 0,
|
|
849
|
+
surface: widget.surface,
|
|
850
|
+
widgetType: widget.type
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
newVisible.sort((a, b) => a.zIndex - b.zIndex);
|
|
854
|
+
const previous = world.getResource(require_ecs.VisibleEntitiesResource);
|
|
855
|
+
const prev = /* @__PURE__ */ new Set();
|
|
856
|
+
for (const v of previous.current) prev.add(v.entityId);
|
|
857
|
+
world.setResource(require_ecs.VisibleEntitiesResource, {
|
|
858
|
+
current: newVisible,
|
|
859
|
+
prev
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
//#endregion
|
|
547
864
|
//#region src/ecs/engine/interaction.ts
|
|
548
865
|
/**
|
|
549
866
|
* Hit-zone size for resize hotspots (full width, screen px). Deliberately
|
|
@@ -913,6 +1230,17 @@ function createInteractionRuntime(ctx) {
|
|
|
913
1230
|
}
|
|
914
1231
|
world.setResource(require_ecs.CursorResource, { cursor });
|
|
915
1232
|
}
|
|
1233
|
+
const flyBackSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
1234
|
+
name: "flyBack",
|
|
1235
|
+
phase: "simulate",
|
|
1236
|
+
after: "transformTween",
|
|
1237
|
+
execute: () => runFlyBackSystem()
|
|
1238
|
+
});
|
|
1239
|
+
const cursorSystem = (0, _jamesyong42_reactive_ecs.defineSystem)({
|
|
1240
|
+
name: "cursor",
|
|
1241
|
+
phase: "present",
|
|
1242
|
+
execute: () => runCursorSystem()
|
|
1243
|
+
});
|
|
916
1244
|
/**
|
|
917
1245
|
* Transition to `dragging` mode. Snapshots ZIndex + Transform2D for every
|
|
918
1246
|
* `Selected` entity, elevates them to `maxZ + 1`, tags `Dragging`, opens a
|
|
@@ -1440,8 +1768,8 @@ function createInteractionRuntime(ctx) {
|
|
|
1440
1768
|
handlePointerMove,
|
|
1441
1769
|
handlePointerUp,
|
|
1442
1770
|
handlePointerCancel,
|
|
1443
|
-
|
|
1444
|
-
|
|
1771
|
+
flyBackSystem,
|
|
1772
|
+
cursorSystem,
|
|
1445
1773
|
selectEntity,
|
|
1446
1774
|
clearSelection,
|
|
1447
1775
|
getHoveredEntity: () => hoveredEntity,
|
|
@@ -1483,6 +1811,35 @@ function createInteractionRuntime(ctx) {
|
|
|
1483
1811
|
};
|
|
1484
1812
|
}
|
|
1485
1813
|
//#endregion
|
|
1814
|
+
//#region src/ecs/engine/phases.ts
|
|
1815
|
+
/**
|
|
1816
|
+
* Pipeline phases for the infinite-canvas `LayoutEngine`.
|
|
1817
|
+
*
|
|
1818
|
+
* `reactive-ecs` ships zero phase vocabulary — phase names and order are the
|
|
1819
|
+
* consumer's responsibility. These names are infinite-canvas's choice; a UI
|
|
1820
|
+
* tool, a game engine, and an agent simulator would each pick differently.
|
|
1821
|
+
*
|
|
1822
|
+
* Phase intent:
|
|
1823
|
+
* - `input` — drain external intent (gestures, raw flag captures) into the world
|
|
1824
|
+
* - `react` — maintain invariants in response to mutations from prior writes
|
|
1825
|
+
* - `control` — state machines, intent resolution, navigation
|
|
1826
|
+
* - `simulate` — time-driven mutations (tweens, animation)
|
|
1827
|
+
* - `derive` — compute frame-local derived state (visibility, sort, layout)
|
|
1828
|
+
* - `present` — build outputs for renderers (frame-changes, visible lists)
|
|
1829
|
+
* - `cleanup` — end-of-frame bookkeeping (clearDirty, incrementTick, emitFrame)
|
|
1830
|
+
*
|
|
1831
|
+
* See RFC-010 for the full architectural rationale.
|
|
1832
|
+
*/
|
|
1833
|
+
const ENGINE_PHASES = [
|
|
1834
|
+
"input",
|
|
1835
|
+
"react",
|
|
1836
|
+
"control",
|
|
1837
|
+
"simulate",
|
|
1838
|
+
"derive",
|
|
1839
|
+
"present",
|
|
1840
|
+
"cleanup"
|
|
1841
|
+
];
|
|
1842
|
+
//#endregion
|
|
1486
1843
|
//#region src/ecs/engine/widget-binding.ts
|
|
1487
1844
|
function createWidgetRegistry(defs = []) {
|
|
1488
1845
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -1506,8 +1863,31 @@ function createWidgetRegistry(defs = []) {
|
|
|
1506
1863
|
* This is the main entry point for the infinite canvas library.
|
|
1507
1864
|
*/
|
|
1508
1865
|
function createLayoutEngine(config) {
|
|
1509
|
-
const
|
|
1510
|
-
const
|
|
1866
|
+
const rawWorld = (0, _jamesyong42_reactive_ecs.createWorld)();
|
|
1867
|
+
const DIRTYING_METHODS = new Set([
|
|
1868
|
+
"createEntity",
|
|
1869
|
+
"destroyEntity",
|
|
1870
|
+
"addComponent",
|
|
1871
|
+
"removeComponent",
|
|
1872
|
+
"setComponent",
|
|
1873
|
+
"addTag",
|
|
1874
|
+
"removeTag"
|
|
1875
|
+
]);
|
|
1876
|
+
const world = new Proxy(rawWorld, { get(target, prop, receiver) {
|
|
1877
|
+
if (typeof prop === "string" && DIRTYING_METHODS.has(prop)) {
|
|
1878
|
+
const fn = Reflect.get(target, prop, target);
|
|
1879
|
+
return (...args) => {
|
|
1880
|
+
const result = fn.apply(target, args);
|
|
1881
|
+
rawWorld.setResource(require_ecs.EngineDirtyResource, { dirty: true });
|
|
1882
|
+
return result;
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
return Reflect.get(target, prop, receiver);
|
|
1886
|
+
} });
|
|
1887
|
+
const scheduler = new _jamesyong42_reactive_ecs.PhasedScheduler({
|
|
1888
|
+
phases: ENGINE_PHASES,
|
|
1889
|
+
defaultPhase: "derive"
|
|
1890
|
+
});
|
|
1511
1891
|
const spatialIndex = new require_hooks.SpatialIndex();
|
|
1512
1892
|
const profiler = new require_hooks.Profiler();
|
|
1513
1893
|
scheduler.profiler = profiler;
|
|
@@ -1530,13 +1910,23 @@ function createLayoutEngine(config) {
|
|
|
1530
1910
|
let snapEnabled = config?.snap?.enabled ?? true;
|
|
1531
1911
|
let snapThreshold = config?.snap?.threshold ?? 5;
|
|
1532
1912
|
let snapGuidesVisible = config?.snap?.guidesVisible ?? true;
|
|
1533
|
-
scheduler.register(
|
|
1534
|
-
scheduler.register(
|
|
1913
|
+
scheduler.register(navStackCaptureSystem);
|
|
1914
|
+
scheduler.register(dragPromoteSystem);
|
|
1915
|
+
scheduler.register(containerCameraSystem);
|
|
1916
|
+
scheduler.register(parentFrameActiveSystem);
|
|
1917
|
+
scheduler.register(roleRefreshSystem);
|
|
1535
1918
|
scheduler.register(navigationFilterSystem);
|
|
1919
|
+
scheduler.register(transformTweenSystem);
|
|
1920
|
+
scheduler.register(cardSystem);
|
|
1536
1921
|
scheduler.register(cullSystem);
|
|
1537
1922
|
scheduler.register(breakpointSystem);
|
|
1538
1923
|
scheduler.register(sortSystem);
|
|
1539
|
-
scheduler.register(
|
|
1924
|
+
scheduler.register(visibilitySystem);
|
|
1925
|
+
scheduler.register(frameChangesSystem);
|
|
1926
|
+
scheduler.register(clearDirtySystem);
|
|
1927
|
+
scheduler.register(incrementTickSystem);
|
|
1928
|
+
scheduler.register(emitFrameSystem);
|
|
1929
|
+
scheduler.register(tweenKeepaliveSystem);
|
|
1540
1930
|
const unsubscribers = [];
|
|
1541
1931
|
unsubscribers.push(world.onComponentChanged(require_ecs.Transform2D, (entityId, _prev, t) => {
|
|
1542
1932
|
if (t) spatialIndex.upsert(entityId, rectToAABB(t));
|
|
@@ -1544,65 +1934,14 @@ function createLayoutEngine(config) {
|
|
|
1544
1934
|
unsubscribers.push(world.onEntityDestroyed((entity) => {
|
|
1545
1935
|
spatialIndex.remove(entity);
|
|
1546
1936
|
}));
|
|
1547
|
-
unsubscribers.push(world.onComponentChanged(require_ecs.Container, (entityId, prev, next) => {
|
|
1548
|
-
if (prev === void 0 && next !== void 0) {
|
|
1549
|
-
if (!world.hasComponent(entityId, require_ecs.ContainerCamera)) world.addComponent(entityId, require_ecs.ContainerCamera, {
|
|
1550
|
-
x: 0,
|
|
1551
|
-
y: 0,
|
|
1552
|
-
zoom: 1
|
|
1553
|
-
});
|
|
1554
|
-
}
|
|
1555
|
-
}));
|
|
1556
|
-
unsubscribers.push(world.onComponentChanged(require_ecs.ParentFrame, (entityId) => {
|
|
1557
|
-
reconcileEntityActive(world, entityId);
|
|
1558
|
-
markDirtyInternal();
|
|
1559
|
-
}));
|
|
1560
|
-
function refreshInteractionRole(entity) {
|
|
1561
|
-
const current = world.getComponent(entity, require_ecs.InteractionRole);
|
|
1562
|
-
if (current && current.role.type !== "drag" && current.role.type !== "select" && current.role.type !== "canvas") return;
|
|
1563
|
-
const hasDraggable = world.hasTag(entity, require_ecs.Draggable);
|
|
1564
|
-
const hasSelectable = world.hasTag(entity, require_ecs.Selectable);
|
|
1565
|
-
const desiredRole = hasDraggable ? { type: "drag" } : hasSelectable ? { type: "select" } : null;
|
|
1566
|
-
if (desiredRole === null) {
|
|
1567
|
-
if (current) world.removeComponent(entity, require_ecs.InteractionRole);
|
|
1568
|
-
if (world.hasComponent(entity, require_ecs.CursorHint)) world.removeComponent(entity, require_ecs.CursorHint);
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
if (!current) world.addComponent(entity, require_ecs.InteractionRole, {
|
|
1572
|
-
layer: 5,
|
|
1573
|
-
role: desiredRole
|
|
1574
|
-
});
|
|
1575
|
-
else if (current.role.type !== desiredRole.type) world.setComponent(entity, require_ecs.InteractionRole, { role: desiredRole });
|
|
1576
|
-
if (desiredRole.type === "drag" && !world.hasComponent(entity, require_ecs.CursorHint)) world.addComponent(entity, require_ecs.CursorHint, {
|
|
1577
|
-
hover: "grab",
|
|
1578
|
-
active: "grabbing"
|
|
1579
|
-
});
|
|
1580
|
-
}
|
|
1581
|
-
unsubscribers.push(world.onTagAdded(require_ecs.Draggable, refreshInteractionRole));
|
|
1582
|
-
unsubscribers.push(world.onTagRemoved(require_ecs.Draggable, refreshInteractionRole));
|
|
1583
|
-
unsubscribers.push(world.onTagAdded(require_ecs.Selectable, refreshInteractionRole));
|
|
1584
|
-
unsubscribers.push(world.onTagRemoved(require_ecs.Selectable, refreshInteractionRole));
|
|
1585
1937
|
if (config?.widgets) for (const w of config.widgets) widgetRegistry.register(w);
|
|
1586
1938
|
if (config?.archetypes) for (const a of config.archetypes) archetypeRegistry.register(a);
|
|
1587
1939
|
world.setResource(require_ecs.NavigationStackResource, { changed: true });
|
|
1588
|
-
let dirty = false;
|
|
1589
|
-
let cameraChangedThisTick = false;
|
|
1590
|
-
let selectionChangedThisTick = false;
|
|
1591
|
-
let prevVisible = /* @__PURE__ */ new Set();
|
|
1592
|
-
let currentVisible = [];
|
|
1593
|
-
let frameChanges = {
|
|
1594
|
-
positionsChanged: [],
|
|
1595
|
-
breakpointsChanged: [],
|
|
1596
|
-
zIndicesChanged: [],
|
|
1597
|
-
entered: [],
|
|
1598
|
-
exited: [],
|
|
1599
|
-
cameraChanged: false,
|
|
1600
|
-
navigationChanged: false,
|
|
1601
|
-
selectionChanged: false,
|
|
1602
|
-
layersChanged: false
|
|
1603
|
-
};
|
|
1604
1940
|
function markDirtyInternal() {
|
|
1605
|
-
dirty
|
|
1941
|
+
world.setResource(require_ecs.EngineDirtyResource, { dirty: true });
|
|
1942
|
+
}
|
|
1943
|
+
function markCameraChanged() {
|
|
1944
|
+
world.setResource(require_ecs.TickFlagsResource, { cameraChanged: true });
|
|
1606
1945
|
}
|
|
1607
1946
|
const interaction = createInteractionRuntime({
|
|
1608
1947
|
world,
|
|
@@ -1610,12 +1949,14 @@ function createLayoutEngine(config) {
|
|
|
1610
1949
|
commandBuffer,
|
|
1611
1950
|
markDirty: markDirtyInternal,
|
|
1612
1951
|
notifySelectionChanged: () => {
|
|
1613
|
-
|
|
1952
|
+
world.setResource(require_ecs.TickFlagsResource, { selectionChanged: true });
|
|
1614
1953
|
},
|
|
1615
1954
|
getSnapEnabled: () => snapEnabled,
|
|
1616
1955
|
getSnapThreshold: () => snapThreshold,
|
|
1617
1956
|
getWidgetInteraction: (type) => widgetRegistry.get(type)?.interaction
|
|
1618
1957
|
});
|
|
1958
|
+
scheduler.register(interaction.flyBackSystem);
|
|
1959
|
+
scheduler.register(interaction.cursorSystem);
|
|
1619
1960
|
const engine = {
|
|
1620
1961
|
world,
|
|
1621
1962
|
createEntity(inits) {
|
|
@@ -1625,7 +1966,6 @@ function createLayoutEngine(config) {
|
|
|
1625
1966
|
if (type.__kind === "tag") world.addTag(entity, type);
|
|
1626
1967
|
else world.addComponent(entity, type, init[1] ?? {});
|
|
1627
1968
|
}
|
|
1628
|
-
markDirtyInternal();
|
|
1629
1969
|
return entity;
|
|
1630
1970
|
},
|
|
1631
1971
|
spawn(id, opts = {}) {
|
|
@@ -1741,14 +2081,12 @@ function createLayoutEngine(config) {
|
|
|
1741
2081
|
destroyEntity(id) {
|
|
1742
2082
|
spatialIndex.remove(id);
|
|
1743
2083
|
world.destroyEntity(id);
|
|
1744
|
-
markDirtyInternal();
|
|
1745
2084
|
},
|
|
1746
2085
|
get(entity, type) {
|
|
1747
2086
|
return world.getComponent(entity, type);
|
|
1748
2087
|
},
|
|
1749
2088
|
set(entity, type, data) {
|
|
1750
2089
|
world.setComponent(entity, type, data);
|
|
1751
|
-
markDirtyInternal();
|
|
1752
2090
|
},
|
|
1753
2091
|
has(entity, type) {
|
|
1754
2092
|
if (type.__kind === "tag") return world.hasTag(entity, type);
|
|
@@ -1756,19 +2094,15 @@ function createLayoutEngine(config) {
|
|
|
1756
2094
|
},
|
|
1757
2095
|
addComponent(entity, type, data) {
|
|
1758
2096
|
world.addComponent(entity, type, data ?? type.defaults);
|
|
1759
|
-
markDirtyInternal();
|
|
1760
2097
|
},
|
|
1761
2098
|
removeComponent(entity, type) {
|
|
1762
2099
|
world.removeComponent(entity, type);
|
|
1763
|
-
markDirtyInternal();
|
|
1764
2100
|
},
|
|
1765
2101
|
addTag(entity, type) {
|
|
1766
2102
|
world.addTag(entity, type);
|
|
1767
|
-
markDirtyInternal();
|
|
1768
2103
|
},
|
|
1769
2104
|
removeTag(entity, type) {
|
|
1770
2105
|
world.removeTag(entity, type);
|
|
1771
|
-
markDirtyInternal();
|
|
1772
2106
|
},
|
|
1773
2107
|
getSchemaFor(entity) {
|
|
1774
2108
|
const w = world.getComponent(entity, require_ecs.Widget);
|
|
@@ -1788,7 +2122,7 @@ function createLayoutEngine(config) {
|
|
|
1788
2122
|
const camera = world.getResource(require_ecs.CameraResource);
|
|
1789
2123
|
camera.x -= dx / camera.zoom;
|
|
1790
2124
|
camera.y -= dy / camera.zoom;
|
|
1791
|
-
|
|
2125
|
+
markCameraChanged();
|
|
1792
2126
|
markDirtyInternal();
|
|
1793
2127
|
},
|
|
1794
2128
|
panTo(worldX, worldY) {
|
|
@@ -1796,7 +2130,7 @@ function createLayoutEngine(config) {
|
|
|
1796
2130
|
const viewport = world.getResource(require_ecs.ViewportResource);
|
|
1797
2131
|
camera.x = worldX - viewport.width / (2 * camera.zoom);
|
|
1798
2132
|
camera.y = worldY - viewport.height / (2 * camera.zoom);
|
|
1799
|
-
|
|
2133
|
+
markCameraChanged();
|
|
1800
2134
|
markDirtyInternal();
|
|
1801
2135
|
},
|
|
1802
2136
|
zoomAtPoint(screenX, screenY, delta) {
|
|
@@ -1807,7 +2141,7 @@ function createLayoutEngine(config) {
|
|
|
1807
2141
|
camera.zoom = newZoom;
|
|
1808
2142
|
camera.x = worldBefore.x - screenX / newZoom;
|
|
1809
2143
|
camera.y = worldBefore.y - screenY / newZoom;
|
|
1810
|
-
|
|
2144
|
+
markCameraChanged();
|
|
1811
2145
|
markDirtyInternal();
|
|
1812
2146
|
},
|
|
1813
2147
|
zoomTo(zoom) {
|
|
@@ -1819,7 +2153,7 @@ function createLayoutEngine(config) {
|
|
|
1819
2153
|
camera.zoom = clamp(zoom, zoomConfig.min, zoomConfig.max);
|
|
1820
2154
|
camera.x = centerWorldX - viewport.width / (2 * camera.zoom);
|
|
1821
2155
|
camera.y = centerWorldY - viewport.height / (2 * camera.zoom);
|
|
1822
|
-
|
|
2156
|
+
markCameraChanged();
|
|
1823
2157
|
markDirtyInternal();
|
|
1824
2158
|
},
|
|
1825
2159
|
/**
|
|
@@ -1833,7 +2167,7 @@ function createLayoutEngine(config) {
|
|
|
1833
2167
|
const camera = world.getResource(require_ecs.CameraResource);
|
|
1834
2168
|
if (camera.gesturing === active) return;
|
|
1835
2169
|
camera.gesturing = active;
|
|
1836
|
-
|
|
2170
|
+
markCameraChanged();
|
|
1837
2171
|
markDirtyInternal();
|
|
1838
2172
|
},
|
|
1839
2173
|
zoomToFit(entityIds, padding = 50) {
|
|
@@ -1862,7 +2196,7 @@ function createLayoutEngine(config) {
|
|
|
1862
2196
|
camera.zoom = zoom;
|
|
1863
2197
|
camera.x = minX - padding - (viewport.width / zoom - contentWidth) / 2;
|
|
1864
2198
|
camera.y = minY - padding - (viewport.height / zoom - contentHeight) / 2;
|
|
1865
|
-
|
|
2199
|
+
markCameraChanged();
|
|
1866
2200
|
markDirtyInternal();
|
|
1867
2201
|
},
|
|
1868
2202
|
setViewport(width, height, dpr) {
|
|
@@ -1884,14 +2218,10 @@ function createLayoutEngine(config) {
|
|
|
1884
2218
|
commandBuffer.endGroup();
|
|
1885
2219
|
},
|
|
1886
2220
|
undo() {
|
|
1887
|
-
|
|
1888
|
-
if (did) markDirtyInternal();
|
|
1889
|
-
return did;
|
|
2221
|
+
return commandBuffer.undo(world);
|
|
1890
2222
|
},
|
|
1891
2223
|
redo() {
|
|
1892
|
-
|
|
1893
|
-
if (did) markDirtyInternal();
|
|
1894
|
-
return did;
|
|
2224
|
+
return commandBuffer.redo(world);
|
|
1895
2225
|
},
|
|
1896
2226
|
canUndo() {
|
|
1897
2227
|
return commandBuffer.canUndo();
|
|
@@ -2024,7 +2354,7 @@ function createLayoutEngine(config) {
|
|
|
2024
2354
|
camera.y = incoming.y;
|
|
2025
2355
|
camera.zoom = incoming.zoom;
|
|
2026
2356
|
interaction.clearSelection();
|
|
2027
|
-
|
|
2357
|
+
markCameraChanged();
|
|
2028
2358
|
markDirtyInternal();
|
|
2029
2359
|
},
|
|
2030
2360
|
exitContainer() {
|
|
@@ -2049,7 +2379,7 @@ function createLayoutEngine(config) {
|
|
|
2049
2379
|
camera.y = incoming.y;
|
|
2050
2380
|
camera.zoom = incoming.zoom;
|
|
2051
2381
|
interaction.clearSelection();
|
|
2052
|
-
|
|
2382
|
+
markCameraChanged();
|
|
2053
2383
|
markDirtyInternal();
|
|
2054
2384
|
},
|
|
2055
2385
|
getActiveContainer() {
|
|
@@ -2059,79 +2389,25 @@ function createLayoutEngine(config) {
|
|
|
2059
2389
|
getNavigationDepth() {
|
|
2060
2390
|
return world.getResource(require_ecs.NavigationStackResource).frames.length - 1;
|
|
2061
2391
|
},
|
|
2062
|
-
|
|
2392
|
+
invalidatePresent() {
|
|
2063
2393
|
markDirtyInternal();
|
|
2064
2394
|
},
|
|
2065
2395
|
profiler,
|
|
2066
2396
|
tick() {
|
|
2067
2397
|
profiler.beginFrame(world.currentTick);
|
|
2068
|
-
const navigationChangedThisTick = world.getResource(require_ecs.NavigationStackResource)?.changed ?? false;
|
|
2069
2398
|
scheduler.execute(world);
|
|
2070
|
-
|
|
2071
|
-
interaction.runCursorSystem();
|
|
2072
|
-
profiler.beginVisibility();
|
|
2073
|
-
const newVisible = [];
|
|
2074
|
-
const newVisibleSet = /* @__PURE__ */ new Set();
|
|
2075
|
-
for (const entity of world.query(require_ecs.Widget, require_ecs.Visible)) {
|
|
2076
|
-
const t = world.getComponent(entity, require_ecs.Transform2D);
|
|
2077
|
-
const widget = world.getComponent(entity, require_ecs.Widget);
|
|
2078
|
-
const bp = world.getComponent(entity, require_ecs.WidgetBreakpoint);
|
|
2079
|
-
const zIdx = world.getComponent(entity, require_ecs.ZIndex);
|
|
2080
|
-
if (!t || !widget) continue;
|
|
2081
|
-
newVisibleSet.add(entity);
|
|
2082
|
-
newVisible.push({
|
|
2083
|
-
entityId: entity,
|
|
2084
|
-
x: t.x,
|
|
2085
|
-
y: t.y,
|
|
2086
|
-
width: t.width,
|
|
2087
|
-
height: t.height,
|
|
2088
|
-
breakpoint: bp?.current ?? "normal",
|
|
2089
|
-
zIndex: zIdx?.value ?? 0,
|
|
2090
|
-
surface: widget.surface,
|
|
2091
|
-
widgetType: widget.type
|
|
2092
|
-
});
|
|
2093
|
-
}
|
|
2094
|
-
newVisible.sort((a, b) => a.zIndex - b.zIndex);
|
|
2095
|
-
profiler.endVisibility();
|
|
2096
|
-
const entered = [];
|
|
2097
|
-
const exited = [];
|
|
2098
|
-
for (const entity of newVisibleSet) if (!prevVisible.has(entity)) entered.push(entity);
|
|
2099
|
-
for (const entity of prevVisible) if (!newVisibleSet.has(entity)) exited.push(entity);
|
|
2100
|
-
frameChanges = {
|
|
2101
|
-
positionsChanged: world.queryChanged(require_ecs.Transform2D),
|
|
2102
|
-
breakpointsChanged: world.queryChanged(require_ecs.WidgetBreakpoint),
|
|
2103
|
-
zIndicesChanged: world.queryChanged(require_ecs.ZIndex),
|
|
2104
|
-
entered,
|
|
2105
|
-
exited,
|
|
2106
|
-
cameraChanged: cameraChangedThisTick,
|
|
2107
|
-
navigationChanged: navigationChangedThisTick,
|
|
2108
|
-
selectionChanged: selectionChangedThisTick,
|
|
2109
|
-
layersChanged: world.queryChanged(require_ecs.Layer).length > 0
|
|
2110
|
-
};
|
|
2111
|
-
currentVisible = newVisible;
|
|
2112
|
-
prevVisible = newVisibleSet;
|
|
2113
|
-
cameraChangedThisTick = false;
|
|
2114
|
-
selectionChangedThisTick = false;
|
|
2115
|
-
profiler.endFrame(world.entityCount, newVisible.length);
|
|
2116
|
-
world.clearDirty();
|
|
2117
|
-
world.incrementTick();
|
|
2118
|
-
world.emitFrame();
|
|
2119
|
-
dirty = false;
|
|
2120
|
-
for (const _ of world.query(require_ecs.TransformTween)) {
|
|
2121
|
-
dirty = true;
|
|
2122
|
-
break;
|
|
2123
|
-
}
|
|
2399
|
+
profiler.endFrame(world.entityCount, world.getResource(require_ecs.VisibleEntitiesResource).current.length);
|
|
2124
2400
|
},
|
|
2125
2401
|
flushIfDirty() {
|
|
2126
|
-
if (!dirty) return false;
|
|
2402
|
+
if (!world.getResource(require_ecs.EngineDirtyResource).dirty) return false;
|
|
2127
2403
|
engine.tick();
|
|
2128
2404
|
return true;
|
|
2129
2405
|
},
|
|
2130
2406
|
getVisibleEntities() {
|
|
2131
|
-
return
|
|
2407
|
+
return world.getResource(require_ecs.VisibleEntitiesResource).current;
|
|
2132
2408
|
},
|
|
2133
2409
|
getFrameChanges() {
|
|
2134
|
-
return
|
|
2410
|
+
return world.getResource(require_ecs.FrameChangesResource).changes;
|
|
2135
2411
|
},
|
|
2136
2412
|
getSpatialIndex() {
|
|
2137
2413
|
return spatialIndex;
|
|
@@ -3529,26 +3805,11 @@ const InfiniteCanvas = react.default.forwardRef(function InfiniteCanvas({ engine
|
|
|
3529
3805
|
snapGuidesRef.current = snapGuides;
|
|
3530
3806
|
}, [snapGuides]);
|
|
3531
3807
|
(0, react.useImperativeHandle)(ref, () => ({
|
|
3532
|
-
panTo: (x, y) =>
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
engine.zoomTo(zoom);
|
|
3538
|
-
engine.markDirty();
|
|
3539
|
-
},
|
|
3540
|
-
zoomToFit: (padding) => {
|
|
3541
|
-
engine.zoomToFit(void 0, padding);
|
|
3542
|
-
engine.markDirty();
|
|
3543
|
-
},
|
|
3544
|
-
undo: () => {
|
|
3545
|
-
engine.undo();
|
|
3546
|
-
engine.markDirty();
|
|
3547
|
-
},
|
|
3548
|
-
redo: () => {
|
|
3549
|
-
engine.redo();
|
|
3550
|
-
engine.markDirty();
|
|
3551
|
-
},
|
|
3808
|
+
panTo: (x, y) => engine.panTo(x, y),
|
|
3809
|
+
zoomTo: (zoom) => engine.zoomTo(zoom),
|
|
3810
|
+
zoomToFit: (padding) => engine.zoomToFit(void 0, padding),
|
|
3811
|
+
undo: () => engine.undo(),
|
|
3812
|
+
redo: () => engine.redo(),
|
|
3552
3813
|
getEngine: () => engine
|
|
3553
3814
|
}), [engine]);
|
|
3554
3815
|
const webglCanvasRef = (0, react.useRef)(null);
|
|
@@ -3610,7 +3871,7 @@ const InfiniteCanvas = react.default.forwardRef(function InfiniteCanvas({ engine
|
|
|
3610
3871
|
}
|
|
3611
3872
|
if (selection) manager.setSelectionConfig(selection);
|
|
3612
3873
|
if (snapGuides) manager.setSnapGuideConfig(snapGuides);
|
|
3613
|
-
engine.
|
|
3874
|
+
engine.invalidatePresent();
|
|
3614
3875
|
}, [
|
|
3615
3876
|
engine,
|
|
3616
3877
|
grid,
|
|
@@ -3622,7 +3883,7 @@ const InfiniteCanvas = react.default.forwardRef(function InfiniteCanvas({ engine
|
|
|
3622
3883
|
const container = containerRef.current;
|
|
3623
3884
|
if (container) applyOverlapGlowVars(container, merged);
|
|
3624
3885
|
applyOverlapGlowShaderUniforms(merged);
|
|
3625
|
-
engine.
|
|
3886
|
+
engine.invalidatePresent();
|
|
3626
3887
|
}, [engine, overlapGlow]);
|
|
3627
3888
|
const r3fEventManagerRef = (0, react.useRef)(null);
|
|
3628
3889
|
(0, react.useEffect)(() => {
|