@weasel-js/labkit 0.8.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/_dts/{DrawCommand-DACv9lhe.d.ts → DrawCommand-CitWPxMY.d.ts} +80 -7
- package/dist/_dts/{useExperimentState-eSvpwV_P.d.ts → useExperimentState-CJn2hHzd.d.ts} +6 -4
- package/dist/{chunk-SSMHPMDV.js → chunk-53XSBIUK.js} +12 -11
- package/dist/chunk-53XSBIUK.js.map +1 -0
- package/dist/{chunk-OHGEOTCV.js → chunk-EXBV7A6X.js} +3 -3
- package/dist/{chunk-OHGEOTCV.js.map → chunk-EXBV7A6X.js.map} +1 -1
- package/dist/{chunk-6V7ROR7G.js → chunk-ISEK5LXT.js} +1931 -904
- package/dist/chunk-ISEK5LXT.js.map +1 -0
- package/dist/chunk-NRD3TDNQ.js +5946 -0
- package/dist/chunk-NRD3TDNQ.js.map +1 -0
- package/dist/index.d.ts +37 -13
- package/dist/index.js +776 -27
- package/dist/index.js.map +1 -1
- package/dist/passthrough/weasel-canvas.d.ts +29 -6
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +169 -79
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/state/index.d.ts +2 -2
- package/dist/state/index.js +2 -2
- package/dist/styles.css +228 -287
- package/dist/ui/layers/index.js +3 -3
- package/package.json +4 -6
- package/src/canvas/CanvasStack.less +1 -1
- package/src/controls/ControlPanel.less +12 -12
- package/src/dragdrop/DragGhost.less +2 -2
- package/src/dragdrop/Palette.less +6 -6
- package/src/index.ts +2 -0
- package/src/lab/Lab.test.tsx +20 -15
- package/src/lab/Lab.tsx +33 -32
- package/src/lab/LabContext.ts +3 -3
- package/src/lab/LabShell.less +14 -14
- package/src/lab/LabShell.test.tsx +7 -20
- package/src/lab/LabShell.tsx +22 -13
- package/src/lab/WorkspaceGrid.less +1 -1
- package/src/lab/WorkspaceGrid.stories.tsx +4 -4
- package/src/lab/index.ts +1 -1
- package/src/lab/useSystemMode.ts +30 -0
- package/src/layers/LayerList.less +9 -9
- package/src/passthrough/weasel-ui.ts +4 -0
- package/src/primitives/FpsMeter.less +5 -5
- package/src/primitives/ScaleIndicator.less +7 -7
- package/src/primitives/Sidebar.less +12 -12
- package/src/primitives/StatusBar.less +10 -10
- package/src/primitives/Toolbar.less +16 -16
- package/src/state/store.test.ts +17 -9
- package/src/state/store.ts +13 -10
- package/src/state/types.ts +5 -2
- package/src/theme/Interstellar.stories.less +20 -20
- package/src/theme/Interstellar.stories.tsx +44 -59
- package/src/theme/base.less +26 -26
- package/src/theme/interstellar.test.ts +30 -0
- package/src/theme/interstellar.tokens.json +57 -0
- package/src/theme/interstellar.ts +9 -0
- package/src/ui/layers/LayerStack.less +21 -21
- package/src/ui/properties/CurveField.less +5 -5
- package/src/ui/properties/EffectCard.tsx +2 -2
- package/src/ui/properties/PropertyGroup.less +6 -6
- package/src/ui/properties/PropertyPanel.less +70 -70
- package/src/ui/properties/PropertyPanel.stories.tsx +0 -1
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +1 -1
- package/src/ui/properties/storyLayouts.tsx +1 -1
- package/src/workspace/Workspace.less +14 -14
- package/src/workspace/Workspace.stories.tsx +2 -2
- package/src/workspace/Workspace.test.tsx +2 -2
- package/dist/chunk-6V7ROR7G.js.map +0 -1
- package/dist/chunk-FBZCQXI2.js +0 -6037
- package/dist/chunk-FBZCQXI2.js.map +0 -1
- package/dist/chunk-SSMHPMDV.js.map +0 -1
- package/dist/theme-interstellar.css +0 -25
- package/dist/theme-light.css +0 -13
- package/src/theme/interstellar.less +0 -34
- package/src/theme/light.less +0 -14
- package/src/theme/tokens.less +0 -85
|
@@ -16,7 +16,7 @@ var GESTURE_DESCRIPTORS = [
|
|
|
16
16
|
{ name: "pointerDown", hasTarget: true },
|
|
17
17
|
{ name: "dblTap", hasTarget: true },
|
|
18
18
|
{ name: "drag", hasTarget: true },
|
|
19
|
-
{ name: "wheel", hasTarget:
|
|
19
|
+
{ name: "wheel", hasTarget: true, arg: { name: "direction", values: ["up", "down", "*"], default: "*" } },
|
|
20
20
|
{ name: "keyDown", hasTarget: false, arg: { name: "key", values: "free" } },
|
|
21
21
|
{ name: "keyUp", hasTarget: false, arg: { name: "key", values: "free" } },
|
|
22
22
|
{ name: "keyHeld", hasTarget: false, arg: { name: "key", values: "free" } },
|
|
@@ -388,44 +388,65 @@ function checkKey(req, held) {
|
|
|
388
388
|
if (req === "forbidden") return !held;
|
|
389
389
|
return true;
|
|
390
390
|
}
|
|
391
|
-
function matchKey(eventKey,
|
|
391
|
+
function matchKey(eventKey, specKey2) {
|
|
392
392
|
const lower = eventKey.toLowerCase();
|
|
393
|
-
if (Array.isArray(
|
|
394
|
-
return
|
|
393
|
+
if (Array.isArray(specKey2)) {
|
|
394
|
+
return specKey2.some((k) => k.toLowerCase() === lower);
|
|
395
395
|
}
|
|
396
|
-
return
|
|
396
|
+
return specKey2.toLowerCase() === lower;
|
|
397
397
|
}
|
|
398
398
|
function affordanceKindOf(target) {
|
|
399
399
|
if (typeof target !== "object" || target === null) return void 0;
|
|
400
400
|
const k = target.kind;
|
|
401
401
|
return typeof k === "string" ? k : void 0;
|
|
402
402
|
}
|
|
403
|
-
function
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
403
|
+
function hasPrefix(s, prefix) {
|
|
404
|
+
return s.startsWith(prefix);
|
|
405
|
+
}
|
|
406
|
+
function parseTargetSpec(spec) {
|
|
407
|
+
if (typeof spec === "object") {
|
|
408
|
+
return spec !== null && typeof spec.kindOf === "function" ? { form: "predicate", kindOf: spec.kindOf } : null;
|
|
409
|
+
}
|
|
410
|
+
if (typeof spec !== "string") return null;
|
|
411
|
+
if (spec === "empty" || spec === "selected-body" || spec === "unselected-body") {
|
|
412
|
+
return { form: "body", body: spec };
|
|
407
413
|
}
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
if (bodyTarget === void 0) return false;
|
|
411
|
-
return bodyTarget === specTarget;
|
|
414
|
+
if (hasPrefix(spec, "affordance:")) {
|
|
415
|
+
return { form: "affordance", kind: spec.slice("affordance:".length) };
|
|
412
416
|
}
|
|
413
|
-
if (
|
|
414
|
-
const
|
|
415
|
-
|
|
417
|
+
if (hasPrefix(spec, "kind:")) {
|
|
418
|
+
const rest = spec.slice("kind:".length);
|
|
419
|
+
const requireSelected = rest.endsWith(":selected");
|
|
420
|
+
return {
|
|
421
|
+
form: "kind",
|
|
422
|
+
kind: requireSelected ? rest.slice(0, -":selected".length) : rest,
|
|
423
|
+
requireSelected
|
|
424
|
+
};
|
|
416
425
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
function matchTarget(target, specTarget, bodyTarget, bodyKind) {
|
|
429
|
+
if (specTarget === void 0) return true;
|
|
430
|
+
const form = parseTargetSpec(specTarget);
|
|
431
|
+
if (form === null) return false;
|
|
432
|
+
switch (form.form) {
|
|
433
|
+
// Receives the raw target (affordance hit for drag, e.target otherwise)
|
|
434
|
+
// plus the bodyTarget classification.
|
|
435
|
+
case "predicate":
|
|
436
|
+
return form.kindOf(target, bodyTarget);
|
|
437
|
+
// Resolved from the `classifyTarget` result packed into the event's
|
|
438
|
+
// `bodyTarget`. Unwired classifyTarget → absent → no match.
|
|
439
|
+
case "body":
|
|
440
|
+
return bodyTarget !== void 0 && bodyTarget === form.body;
|
|
441
|
+
case "affordance":
|
|
442
|
+
return affordanceKindOf(target) === form.kind;
|
|
443
|
+
case "kind":
|
|
444
|
+
if (bodyKind === void 0 || bodyKind !== form.kind) return false;
|
|
445
|
+
return form.requireSelected ? bodyTarget === "selected-body" : true;
|
|
446
|
+
default: {
|
|
447
|
+
return false;
|
|
424
448
|
}
|
|
425
|
-
if (bodyKind !== wanted) return false;
|
|
426
|
-
return requireSelected ? bodyTarget === "selected-body" : true;
|
|
427
449
|
}
|
|
428
|
-
return false;
|
|
429
450
|
}
|
|
430
451
|
function normalizePhase(spec) {
|
|
431
452
|
if (typeof spec === "string") {
|
|
@@ -488,7 +509,7 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
488
509
|
const direction = spec.direction ?? "*";
|
|
489
510
|
if (direction === "up" && !(e.deltaY < 0)) return false;
|
|
490
511
|
if (direction === "down" && !(e.deltaY > 0)) return false;
|
|
491
|
-
return
|
|
512
|
+
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
492
513
|
}
|
|
493
514
|
case "click": {
|
|
494
515
|
if (e.kind !== "click") return false;
|
|
@@ -498,12 +519,12 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
498
519
|
case "doubleClick": {
|
|
499
520
|
if (e.kind !== "doubleclick") return false;
|
|
500
521
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
501
|
-
return matchTarget(e.
|
|
522
|
+
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
502
523
|
}
|
|
503
524
|
case "contextMenu": {
|
|
504
525
|
if (e.kind !== "contextmenu") return false;
|
|
505
526
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
506
|
-
return matchTarget(e.
|
|
527
|
+
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
507
528
|
}
|
|
508
529
|
case "longPress": {
|
|
509
530
|
if (e.kind !== "longpress") return false;
|
|
@@ -547,8 +568,6 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
547
568
|
}
|
|
548
569
|
}
|
|
549
570
|
var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
|
|
550
|
-
|
|
551
|
-
// ../core/dist/chunk-6DAUIMTO.js
|
|
552
571
|
var routing_exports = {};
|
|
553
572
|
__export(routing_exports, {
|
|
554
573
|
GESTURE_DESCRIPTORS: () => GESTURE_DESCRIPTORS,
|
|
@@ -597,7 +616,11 @@ function defineTool(def) {
|
|
|
597
616
|
};
|
|
598
617
|
return {
|
|
599
618
|
id: def.id,
|
|
600
|
-
|
|
619
|
+
eligibility: {
|
|
620
|
+
focus: true,
|
|
621
|
+
capabilities: def.capabilities,
|
|
622
|
+
...def.hotkey ? { offhand: def.hotkey } : {}
|
|
623
|
+
},
|
|
601
624
|
actions: def.actions,
|
|
602
625
|
def,
|
|
603
626
|
presentation: def.presentation,
|
|
@@ -683,6 +706,255 @@ function argOf(spec, fallback) {
|
|
|
683
706
|
if ("direction" in spec) return spec.direction ?? fallback;
|
|
684
707
|
return fallback;
|
|
685
708
|
}
|
|
709
|
+
var DepRegistryContext = createContext(null);
|
|
710
|
+
function DepRegistryProvider({ children }) {
|
|
711
|
+
const sourcesRef = useRef(/* @__PURE__ */ new Map());
|
|
712
|
+
const registry4 = useMemo(() => ({
|
|
713
|
+
register: (name, source) => {
|
|
714
|
+
sourcesRef.current.set(name, source);
|
|
715
|
+
return () => {
|
|
716
|
+
sourcesRef.current.delete(name);
|
|
717
|
+
};
|
|
718
|
+
},
|
|
719
|
+
get: (name) => sourcesRef.current.get(name)?.()
|
|
720
|
+
}), []);
|
|
721
|
+
return /* @__PURE__ */ jsx(DepRegistryContext.Provider, { value: registry4, children });
|
|
722
|
+
}
|
|
723
|
+
function useDepRegistry() {
|
|
724
|
+
const r = useContext(DepRegistryContext);
|
|
725
|
+
if (r === null) {
|
|
726
|
+
throw new Error("useDepRegistry: no DepRegistryProvider in scope. Wrap your tree with <DepRegistryProvider> (typically inside <SceneCanvas>).");
|
|
727
|
+
}
|
|
728
|
+
return r;
|
|
729
|
+
}
|
|
730
|
+
function useOptionalDepRegistry() {
|
|
731
|
+
return useContext(DepRegistryContext);
|
|
732
|
+
}
|
|
733
|
+
function useDepSource(name, source) {
|
|
734
|
+
const registry4 = useDepRegistry();
|
|
735
|
+
const sourceRef = useRef(source);
|
|
736
|
+
sourceRef.current = source;
|
|
737
|
+
useEffect(() => {
|
|
738
|
+
return registry4.register(name, () => sourceRef.current());
|
|
739
|
+
}, [name, registry4]);
|
|
740
|
+
}
|
|
741
|
+
var DEV = (() => {
|
|
742
|
+
try {
|
|
743
|
+
return Boolean(import.meta.env?.DEV);
|
|
744
|
+
} catch {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
})();
|
|
748
|
+
function buildDepsFromRequires(action, depRegistry) {
|
|
749
|
+
const requires = action.requires ?? [];
|
|
750
|
+
const entries = requires.map((name) => [name, depRegistry.get(name)]);
|
|
751
|
+
const deps = Object.fromEntries(entries);
|
|
752
|
+
if (DEV) {
|
|
753
|
+
const declared = new Set(requires);
|
|
754
|
+
return new Proxy(deps, {
|
|
755
|
+
get(target, prop, receiver) {
|
|
756
|
+
if (typeof prop === "string" && !declared.has(prop) && prop !== "then") {
|
|
757
|
+
const message = `[weasel:dispatcher] action "${action.id}" read deps.${prop} but did not declare it in \`requires\`. Add \`requires: [...'${prop}']\` to the descriptor or the dep will be undefined at runtime.`;
|
|
758
|
+
console.warn(message);
|
|
759
|
+
throw new Error(message);
|
|
760
|
+
}
|
|
761
|
+
return Reflect.get(target, prop, receiver);
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
return deps;
|
|
766
|
+
}
|
|
767
|
+
function actionBindings(action) {
|
|
768
|
+
const gs = action.defaultBinding;
|
|
769
|
+
if (!gs) return [];
|
|
770
|
+
const raw = Array.isArray(gs) ? gs : [gs];
|
|
771
|
+
return raw.map((entry) => {
|
|
772
|
+
const isBoundObj = !("kind" in entry);
|
|
773
|
+
const spec = isBoundObj ? entry.spec : entry;
|
|
774
|
+
const opts = isBoundObj ? entry.opts : void 0;
|
|
775
|
+
return { spec, actionId: action.id, ...opts !== void 0 ? { opts } : {} };
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
var ActionDisabledReason = {
|
|
779
|
+
SelectionRequired: "selection-required",
|
|
780
|
+
NotApplicable: "not-applicable",
|
|
781
|
+
/** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
|
|
782
|
+
PredicateThrew: "predicate-threw"
|
|
783
|
+
};
|
|
784
|
+
var enabledSlowWarned = /* @__PURE__ */ new Set();
|
|
785
|
+
var enabledThrewWarned = /* @__PURE__ */ new Set();
|
|
786
|
+
var ENABLED_BUDGET_MS = 4;
|
|
787
|
+
function evaluateEnabled(action, deps) {
|
|
788
|
+
if (!action.enabled) return { enabled: true };
|
|
789
|
+
const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
790
|
+
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
791
|
+
const start = isDev ? now() : 0;
|
|
792
|
+
try {
|
|
793
|
+
const result = action.enabled(deps);
|
|
794
|
+
if (isDev) {
|
|
795
|
+
const elapsed = now() - start;
|
|
796
|
+
if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
|
|
797
|
+
enabledSlowWarned.add(action.id);
|
|
798
|
+
console.warn(
|
|
799
|
+
`weasel actions: enabled() for action "${action.id}" took ${elapsed.toFixed(2)}ms (budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast \u2014 see Action.enabled JSDoc.`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
if (result === true) return { enabled: true };
|
|
804
|
+
return { enabled: false, reason: result };
|
|
805
|
+
} catch (e) {
|
|
806
|
+
if (isDev && !enabledThrewWarned.has(action.id)) {
|
|
807
|
+
enabledThrewWarned.add(action.id);
|
|
808
|
+
console.warn(
|
|
809
|
+
`weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
|
|
810
|
+
e
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
function validateActionId(id) {
|
|
817
|
+
if (id.length === 0) {
|
|
818
|
+
throw new Error(`weasel: action id may not be empty`);
|
|
819
|
+
}
|
|
820
|
+
if (RESERVED_ID_PREFIXES.has(id[0])) {
|
|
821
|
+
throw new Error(
|
|
822
|
+
`weasel: action id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
if (RESERVED_ID_NAMES.has(id)) {
|
|
826
|
+
throw new Error(
|
|
827
|
+
`weasel: action id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function validateActionDefaultBinding(action) {
|
|
832
|
+
const gs = action.defaultBinding;
|
|
833
|
+
if (!gs) return;
|
|
834
|
+
const entries = Array.isArray(gs) ? gs : [gs];
|
|
835
|
+
for (const entry of entries) {
|
|
836
|
+
const spec = "kind" in entry ? entry : entry.spec;
|
|
837
|
+
const phase = spec.phase;
|
|
838
|
+
if (phase === void 0) continue;
|
|
839
|
+
const atoms = Array.isArray(phase) ? phase : [{ channel: "&", phase }];
|
|
840
|
+
for (const atom of atoms) {
|
|
841
|
+
if (atom.channel === "&") {
|
|
842
|
+
throw new Error(
|
|
843
|
+
`weasel: action "${action.id}" defaultBinding uses '&' channel which has no owning tool to resolve to. Use a named tool channel (e.g. '[rect:engaged]') or '*' instead, or move this binding to the tool's own Tool.bindings.`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
var ActionsContext = createContext(null);
|
|
850
|
+
function ActionsProvider({ children }) {
|
|
851
|
+
const actionsRef = useRef(/* @__PURE__ */ new Map());
|
|
852
|
+
const versionRef = useRef(0);
|
|
853
|
+
const cachedRef = useRef([]);
|
|
854
|
+
const cachedVerRef = useRef(-1);
|
|
855
|
+
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
856
|
+
const depReg = useOptionalDepRegistry();
|
|
857
|
+
const depRegRef = useRef(depReg);
|
|
858
|
+
depRegRef.current = depReg;
|
|
859
|
+
const wiredDepRegRef = useRef(null);
|
|
860
|
+
const dispatcherRef = useRef(null);
|
|
861
|
+
const registry4 = useMemo(() => {
|
|
862
|
+
const snapshot = () => {
|
|
863
|
+
const v = versionRef.current;
|
|
864
|
+
if (cachedVerRef.current === v) return cachedRef.current;
|
|
865
|
+
const out = Object.freeze(Array.from(actionsRef.current.values()));
|
|
866
|
+
cachedRef.current = out;
|
|
867
|
+
cachedVerRef.current = v;
|
|
868
|
+
return out;
|
|
869
|
+
};
|
|
870
|
+
const notify2 = () => {
|
|
871
|
+
for (const l of listenersRef.current) {
|
|
872
|
+
try {
|
|
873
|
+
l();
|
|
874
|
+
} catch (err) {
|
|
875
|
+
console.error("weasel ActionsRegistry: subscriber threw", err);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
return {
|
|
880
|
+
register: (action) => {
|
|
881
|
+
validateActionId(action.id);
|
|
882
|
+
validateActionDefaultBinding(action);
|
|
883
|
+
actionsRef.current.set(action.id, action);
|
|
884
|
+
versionRef.current++;
|
|
885
|
+
notify2();
|
|
886
|
+
return () => {
|
|
887
|
+
const cur = actionsRef.current.get(action.id);
|
|
888
|
+
if (cur === action) {
|
|
889
|
+
actionsRef.current.delete(action.id);
|
|
890
|
+
versionRef.current++;
|
|
891
|
+
notify2();
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
},
|
|
895
|
+
unregister: (id) => {
|
|
896
|
+
if (actionsRef.current.delete(id)) {
|
|
897
|
+
versionRef.current++;
|
|
898
|
+
notify2();
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
list: () => snapshot(),
|
|
902
|
+
trigger: (id, params) => {
|
|
903
|
+
const a = actionsRef.current.get(id);
|
|
904
|
+
if (!a) return false;
|
|
905
|
+
try {
|
|
906
|
+
if (a.invoker && a.invoker.timing === "immediate") {
|
|
907
|
+
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
908
|
+
const deps = !r ? {} : a.requires ? buildDepsFromRequires(a, r) : {
|
|
909
|
+
selection: r.get("selection"),
|
|
910
|
+
scene: r.get("scene"),
|
|
911
|
+
history: r.get("history"),
|
|
912
|
+
view: r.get("view"),
|
|
913
|
+
pointer: r.get("pointer"),
|
|
914
|
+
activeTool: r.get("activeTool"),
|
|
915
|
+
booleansAdapter: r.get("booleansAdapter")
|
|
916
|
+
};
|
|
917
|
+
a.invoker.run(deps, params);
|
|
918
|
+
}
|
|
919
|
+
} catch (err) {
|
|
920
|
+
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
921
|
+
}
|
|
922
|
+
return true;
|
|
923
|
+
},
|
|
924
|
+
subscribe: (listener) => {
|
|
925
|
+
listenersRef.current.add(listener);
|
|
926
|
+
return () => {
|
|
927
|
+
listenersRef.current.delete(listener);
|
|
928
|
+
};
|
|
929
|
+
},
|
|
930
|
+
setDispatcher: (d) => {
|
|
931
|
+
dispatcherRef.current = d;
|
|
932
|
+
},
|
|
933
|
+
setDepRegistry: (r) => {
|
|
934
|
+
wiredDepRegRef.current = r;
|
|
935
|
+
},
|
|
936
|
+
begin: (id, params) => {
|
|
937
|
+
const disp = dispatcherRef.current;
|
|
938
|
+
if (!disp) return null;
|
|
939
|
+
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
940
|
+
const deps = r ? {
|
|
941
|
+
selection: r.get("selection"),
|
|
942
|
+
scene: r.get("scene"),
|
|
943
|
+
history: r.get("history"),
|
|
944
|
+
view: r.get("view"),
|
|
945
|
+
pointer: r.get("pointer"),
|
|
946
|
+
activeTool: r.get("activeTool"),
|
|
947
|
+
booleansAdapter: r.get("booleansAdapter")
|
|
948
|
+
} : {};
|
|
949
|
+
return disp.beginUiOngoing(id, deps, params);
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
}, []);
|
|
953
|
+
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: registry4, children });
|
|
954
|
+
}
|
|
955
|
+
function useActionsRegistry() {
|
|
956
|
+
return useContext(ActionsContext);
|
|
957
|
+
}
|
|
686
958
|
function findConflicts(tools) {
|
|
687
959
|
const entries = buildRouteRegistry(tools);
|
|
688
960
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -724,9 +996,15 @@ function predicateId(pred) {
|
|
|
724
996
|
}
|
|
725
997
|
return id;
|
|
726
998
|
}
|
|
999
|
+
function actionAsTool(action) {
|
|
1000
|
+
return { id: action.id, eligibility: {}, bindings: actionBindings(action) };
|
|
1001
|
+
}
|
|
727
1002
|
function findScopedConflicts(scopes) {
|
|
728
1003
|
const registry4 = Array.isArray(scopes.registry) ? scopes.registry : Object.values(scopes.registry);
|
|
729
|
-
const
|
|
1004
|
+
const actions = scopes.actions ?? [];
|
|
1005
|
+
const ambientActions = actions.filter((a) => a.scope !== "hotkey").map(actionAsTool);
|
|
1006
|
+
const hotkeyActions = actions.filter((a) => a.scope === "hotkey").map(actionAsTool);
|
|
1007
|
+
const ambient = [...scopes.ambient ?? [], ...ambientActions];
|
|
730
1008
|
const out = [];
|
|
731
1009
|
const seen = /* @__PURE__ */ new Set();
|
|
732
1010
|
const add = (conflicts) => {
|
|
@@ -737,12 +1015,13 @@ function findScopedConflicts(scopes) {
|
|
|
737
1015
|
out.push(c);
|
|
738
1016
|
}
|
|
739
1017
|
};
|
|
740
|
-
for (const tool of [...registry4, ...ambient]) add(findConflicts([tool]));
|
|
1018
|
+
for (const tool of [...registry4, ...scopes.ambient ?? []]) add(findConflicts([tool]));
|
|
741
1019
|
if (ambient.length > 1) add(findConflicts(ambient));
|
|
742
|
-
const
|
|
743
|
-
(t) => t.def?.hotkey !== void 0
|
|
744
|
-
|
|
745
|
-
|
|
1020
|
+
const hotkey = [
|
|
1021
|
+
...registry4.filter((t) => t.def?.hotkey !== void 0),
|
|
1022
|
+
...hotkeyActions
|
|
1023
|
+
];
|
|
1024
|
+
if (hotkey.length > 1) add(findConflicts(hotkey));
|
|
746
1025
|
return out;
|
|
747
1026
|
}
|
|
748
1027
|
function formatConflict(conflict) {
|
|
@@ -882,7 +1161,7 @@ function transformCoords(coords, m) {
|
|
|
882
1161
|
return out;
|
|
883
1162
|
}
|
|
884
1163
|
|
|
885
|
-
// ../core/dist/chunk-
|
|
1164
|
+
// ../core/dist/chunk-6SHZHXXR.js
|
|
886
1165
|
function resolveUnit(v, unitSystem) {
|
|
887
1166
|
if (typeof v === "number") return v;
|
|
888
1167
|
if (!unitSystem) {
|
|
@@ -1453,10 +1732,32 @@ var RECT_POSE_DESCRIPTOR = {
|
|
|
1453
1732
|
translate: RECT_POSE_DESCRIPTOR.translate,
|
|
1454
1733
|
intersectsRect: RECT_POSE_DESCRIPTOR.intersectsRect,
|
|
1455
1734
|
lerp: RECT_POSE_DESCRIPTOR.lerp});
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1735
|
+
function remapRotatedLeaf(pose, src, dst) {
|
|
1736
|
+
const sx = src.width === 0 ? 1 : dst.width / src.width;
|
|
1737
|
+
const sy = src.height === 0 ? 1 : dst.height / src.height;
|
|
1738
|
+
const theta = pose.rotation ?? 0;
|
|
1739
|
+
const cos = Math.cos(theta);
|
|
1740
|
+
const sin = Math.sin(theta);
|
|
1741
|
+
const ux = sx * cos, uy = sy * sin;
|
|
1742
|
+
const vx = -sx * sin, vy = sy * cos;
|
|
1743
|
+
const width = pose.width * Math.hypot(ux, uy);
|
|
1744
|
+
const height = pose.height * Math.hypot(vx, vy);
|
|
1745
|
+
const rotation = Math.atan2(uy, ux);
|
|
1746
|
+
const cxNew = dst.x + (pose.x + pose.width / 2 - src.x) * sx;
|
|
1747
|
+
const cyNew = dst.y + (pose.y + pose.height / 2 - src.y) * sy;
|
|
1748
|
+
return {
|
|
1749
|
+
...pose,
|
|
1750
|
+
x: cxNew - width / 2,
|
|
1751
|
+
y: cyNew - height / 2,
|
|
1752
|
+
width,
|
|
1753
|
+
height,
|
|
1754
|
+
rotation
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
var pathPoseDescriptor = {
|
|
1758
|
+
getBounds: (path) => boundsOfPath(path),
|
|
1759
|
+
remapBounds: (path, src, dst) => {
|
|
1760
|
+
const sx = src.width === 0 ? 0 : dst.width / src.width;
|
|
1460
1761
|
const sy = src.height === 0 ? 0 : dst.height / src.height;
|
|
1461
1762
|
if (path.kind === "rect") {
|
|
1462
1763
|
return {
|
|
@@ -1549,14 +1850,29 @@ var RECT_ORIGIN_PROJECTION = {
|
|
|
1549
1850
|
getOrigin: (p) => ({ x: p.x, y: p.y }),
|
|
1550
1851
|
translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy })
|
|
1551
1852
|
};
|
|
1552
|
-
|
|
1553
|
-
|
|
1853
|
+
var MIN_SCALE = 1e-9;
|
|
1854
|
+
function pxExtent(px, scale2) {
|
|
1855
|
+
return {
|
|
1856
|
+
x: px / Math.max(MIN_SCALE, Math.abs(scale2.x)),
|
|
1857
|
+
y: px / Math.max(MIN_SCALE, Math.abs(scale2.y))
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
function scaleDelta(dx, dy, scale2) {
|
|
1861
|
+
return { x: dx * scale2.x, y: dy * scale2.y };
|
|
1862
|
+
}
|
|
1863
|
+
function withinPxBox(dx, dy, px, scale2) {
|
|
1864
|
+
const s = scaleDelta(dx, dy, scale2);
|
|
1865
|
+
return Math.abs(s.x) <= px && Math.abs(s.y) <= px;
|
|
1866
|
+
}
|
|
1867
|
+
function withinPxRadius(dx, dy, px, scale2) {
|
|
1868
|
+
const s = scaleDelta(dx, dy, scale2);
|
|
1869
|
+
return s.x * s.x + s.y * s.y <= px * px;
|
|
1554
1870
|
}
|
|
1555
1871
|
|
|
1556
1872
|
// ../core/dist/chunk-GVCNT7UH.js
|
|
1557
1873
|
var FACTORIES = /* @__PURE__ */ new Map();
|
|
1558
|
-
function registerOpFactory(name,
|
|
1559
|
-
FACTORIES.set(name,
|
|
1874
|
+
function registerOpFactory(name, build2) {
|
|
1875
|
+
FACTORIES.set(name, build2);
|
|
1560
1876
|
}
|
|
1561
1877
|
function rebuildOp(name, args) {
|
|
1562
1878
|
const f = FACTORIES.get(name);
|
|
@@ -1593,7 +1909,7 @@ function createDeleteOp(args) {
|
|
|
1593
1909
|
}
|
|
1594
1910
|
registerOpFactory("delete", (args) => createDeleteOp(args));
|
|
1595
1911
|
|
|
1596
|
-
// ../core/dist/chunk-
|
|
1912
|
+
// ../core/dist/chunk-UB6A6L77.js
|
|
1597
1913
|
function createTransformOp(args) {
|
|
1598
1914
|
const { id, from, to, label, coalesceKey = `transform:${id}` } = args;
|
|
1599
1915
|
return {
|
|
@@ -1674,7 +1990,7 @@ function snap(strategy, opts = {}) {
|
|
|
1674
1990
|
};
|
|
1675
1991
|
}
|
|
1676
1992
|
|
|
1677
|
-
// ../core/dist/chunk-
|
|
1993
|
+
// ../core/dist/chunk-Z3KNFTTS.js
|
|
1678
1994
|
function lockAspectWithModifier(opts = {}) {
|
|
1679
1995
|
const { key = "shift" } = opts;
|
|
1680
1996
|
return {
|
|
@@ -1870,11 +2186,163 @@ function materializePaste(adapter, snapshot, opts) {
|
|
|
1870
2186
|
return { ops, newIds, created };
|
|
1871
2187
|
}
|
|
1872
2188
|
|
|
1873
|
-
// ../core/dist/chunk-
|
|
2189
|
+
// ../core/dist/chunk-IO5Z75X4.js
|
|
2190
|
+
var counter = 0;
|
|
1874
2191
|
var registry = /* @__PURE__ */ new Map();
|
|
1875
2192
|
function getTexture(id) {
|
|
1876
2193
|
return registry.get(id) ?? null;
|
|
1877
2194
|
}
|
|
2195
|
+
function registerTexture(image) {
|
|
2196
|
+
const id = `tex_${++counter}`;
|
|
2197
|
+
registry.set(id, { source: image });
|
|
2198
|
+
return { id };
|
|
2199
|
+
}
|
|
2200
|
+
function tileSize(spec) {
|
|
2201
|
+
if (spec.size !== void 0) return spec.size;
|
|
2202
|
+
switch (spec.tile) {
|
|
2203
|
+
case "hatch":
|
|
2204
|
+
return 5;
|
|
2205
|
+
case "crosshatch":
|
|
2206
|
+
return 6;
|
|
2207
|
+
case "dots":
|
|
2208
|
+
return 6;
|
|
2209
|
+
case "chunks":
|
|
2210
|
+
return 48;
|
|
2211
|
+
default:
|
|
2212
|
+
return 8;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
function mulberry32(seed) {
|
|
2216
|
+
let s = seed | 0;
|
|
2217
|
+
return () => {
|
|
2218
|
+
s = s + 1831565813 | 0;
|
|
2219
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
2220
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2221
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
function tileGeometry(spec) {
|
|
2225
|
+
const s = tileSize(spec);
|
|
2226
|
+
const { color } = spec;
|
|
2227
|
+
switch (spec.tile) {
|
|
2228
|
+
case "hatch": {
|
|
2229
|
+
const width = spec.lineWidth ?? 1;
|
|
2230
|
+
return { size: s, shapes: forwardDiagonals(s, width, color) };
|
|
2231
|
+
}
|
|
2232
|
+
case "crosshatch": {
|
|
2233
|
+
const width = spec.lineWidth ?? 0.8;
|
|
2234
|
+
return {
|
|
2235
|
+
size: s,
|
|
2236
|
+
shapes: [...forwardDiagonals(s, width, color), ...backwardDiagonals(s, width, color)]
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
case "dots": {
|
|
2240
|
+
const r = spec.radius ?? 1;
|
|
2241
|
+
return { size: s, shapes: [{ kind: "circle", cx: s / 2, cy: s / 2, r, color }] };
|
|
2242
|
+
}
|
|
2243
|
+
case "chunks": {
|
|
2244
|
+
const density = spec.density ?? 0.35;
|
|
2245
|
+
const chunkSize = spec.chunkSize ?? 3;
|
|
2246
|
+
const rand = mulberry32(spec.seed ?? 42);
|
|
2247
|
+
const count = Math.round(s * s * density / (chunkSize * chunkSize));
|
|
2248
|
+
const shapes = [];
|
|
2249
|
+
if (spec.bg !== void 0) {
|
|
2250
|
+
shapes.push({ kind: "rect", x: 0, y: 0, width: s, height: s, color: spec.bg });
|
|
2251
|
+
}
|
|
2252
|
+
for (let i = 0; i < count; i++) {
|
|
2253
|
+
const cx = rand() * s;
|
|
2254
|
+
const cy = rand() * s;
|
|
2255
|
+
const w = chunkSize * (0.5 + rand());
|
|
2256
|
+
const h = chunkSize * (0.5 + rand());
|
|
2257
|
+
const rotation = rand() * Math.PI;
|
|
2258
|
+
shapes.push({ kind: "ellipse", cx, cy, rx: w / 2, ry: h / 2, rotation, color });
|
|
2259
|
+
}
|
|
2260
|
+
return { size: s, shapes };
|
|
2261
|
+
}
|
|
2262
|
+
default:
|
|
2263
|
+
return { size: s, shapes: [] };
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
function forwardDiagonals(s, width, color) {
|
|
2267
|
+
return [
|
|
2268
|
+
{ kind: "line", x1: -1, y1: s + 1, x2: s + 1, y2: -1, width, color },
|
|
2269
|
+
{ kind: "line", x1: -1, y1: 1, x2: 1, y2: -1, width, color },
|
|
2270
|
+
{ kind: "line", x1: s - 1, y1: s + 1, x2: s + 1, y2: s - 1, width, color }
|
|
2271
|
+
];
|
|
2272
|
+
}
|
|
2273
|
+
function backwardDiagonals(s, width, color) {
|
|
2274
|
+
return [
|
|
2275
|
+
{ kind: "line", x1: -1, y1: -1, x2: s + 1, y2: s + 1, width, color },
|
|
2276
|
+
{ kind: "line", x1: s - 1, y1: -1, x2: s + 1, y2: 1, width, color },
|
|
2277
|
+
{ kind: "line", x1: -1, y1: s - 1, x2: 1, y2: s + 1, width, color }
|
|
2278
|
+
];
|
|
2279
|
+
}
|
|
2280
|
+
function drawTileShapes(oc, geometry) {
|
|
2281
|
+
for (const shape of geometry.shapes) {
|
|
2282
|
+
if (shape.kind === "line") {
|
|
2283
|
+
oc.strokeStyle = shape.color;
|
|
2284
|
+
oc.lineWidth = shape.width;
|
|
2285
|
+
oc.beginPath();
|
|
2286
|
+
oc.moveTo(shape.x1, shape.y1);
|
|
2287
|
+
oc.lineTo(shape.x2, shape.y2);
|
|
2288
|
+
oc.stroke();
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2291
|
+
oc.fillStyle = shape.color;
|
|
2292
|
+
if (shape.kind === "rect") {
|
|
2293
|
+
oc.fillRect(shape.x, shape.y, shape.width, shape.height);
|
|
2294
|
+
continue;
|
|
2295
|
+
}
|
|
2296
|
+
oc.beginPath();
|
|
2297
|
+
if (shape.kind === "circle") {
|
|
2298
|
+
oc.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2);
|
|
2299
|
+
} else {
|
|
2300
|
+
oc.ellipse(shape.cx, shape.cy, shape.rx, shape.ry, shape.rotation, 0, Math.PI * 2);
|
|
2301
|
+
}
|
|
2302
|
+
oc.fill();
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
function createTilePattern(opts) {
|
|
2306
|
+
let off;
|
|
2307
|
+
try {
|
|
2308
|
+
off = new OffscreenCanvas(opts.size, opts.size);
|
|
2309
|
+
} catch {
|
|
2310
|
+
if (typeof document === "undefined") return null;
|
|
2311
|
+
const c = document.createElement("canvas");
|
|
2312
|
+
c.width = opts.size;
|
|
2313
|
+
c.height = opts.size;
|
|
2314
|
+
off = c;
|
|
2315
|
+
}
|
|
2316
|
+
const oc = off.getContext("2d");
|
|
2317
|
+
if (!oc) return null;
|
|
2318
|
+
opts.draw(oc, opts.size);
|
|
2319
|
+
let bitmap;
|
|
2320
|
+
if (typeof off.transferToImageBitmap === "function") {
|
|
2321
|
+
bitmap = off.transferToImageBitmap();
|
|
2322
|
+
} else {
|
|
2323
|
+
return null;
|
|
2324
|
+
}
|
|
2325
|
+
return registerTexture(bitmap);
|
|
2326
|
+
}
|
|
2327
|
+
function build(spec) {
|
|
2328
|
+
const geometry = tileGeometry(spec);
|
|
2329
|
+
return createTilePattern({
|
|
2330
|
+
size: tileSize(spec),
|
|
2331
|
+
draw: (oc) => drawTileShapes(oc, geometry)
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
function hatch(params) {
|
|
2335
|
+
return build({ tile: "hatch", ...params });
|
|
2336
|
+
}
|
|
2337
|
+
function crosshatch(params) {
|
|
2338
|
+
return build({ tile: "crosshatch", ...params });
|
|
2339
|
+
}
|
|
2340
|
+
function dots(params) {
|
|
2341
|
+
return build({ tile: "dots", ...params });
|
|
2342
|
+
}
|
|
2343
|
+
function chunks(params) {
|
|
2344
|
+
return build({ tile: "chunks", ...params });
|
|
2345
|
+
}
|
|
1878
2346
|
|
|
1879
2347
|
// ../font/dist/index.js
|
|
1880
2348
|
var subscribers = /* @__PURE__ */ new Set();
|
|
@@ -2012,8 +2480,8 @@ function alphaToSdf(alpha, width, height, radius, cutoff) {
|
|
|
2012
2480
|
edt2d(gridInner, width, height, f, d, v, z);
|
|
2013
2481
|
const out = new Uint8Array(n);
|
|
2014
2482
|
for (let i = 0; i < n; i++) {
|
|
2015
|
-
const
|
|
2016
|
-
const byte = Math.round(255 - 255 * (
|
|
2483
|
+
const dist3 = Math.sqrt(gridOuter[i]) - Math.sqrt(gridInner[i]);
|
|
2484
|
+
const byte = Math.round(255 - 255 * (dist3 / radius + cutoff));
|
|
2017
2485
|
out[i] = byte < 0 ? 0 : byte > 255 ? 255 : byte;
|
|
2018
2486
|
}
|
|
2019
2487
|
return out;
|
|
@@ -2219,17 +2687,17 @@ function dynamicPageTextureId(page) {
|
|
|
2219
2687
|
return `weasel-dyn-sdf-page-${page}`;
|
|
2220
2688
|
}
|
|
2221
2689
|
var uploadedVersions = /* @__PURE__ */ new WeakMap();
|
|
2222
|
-
function syncDynamicPageTexture(
|
|
2690
|
+
function syncDynamicPageTexture(cache7, pageIndex) {
|
|
2223
2691
|
const page = pages[pageIndex];
|
|
2224
2692
|
if (!page) return false;
|
|
2225
2693
|
const id = dynamicPageTextureId(pageIndex);
|
|
2226
|
-
let seen = uploadedVersions.get(
|
|
2694
|
+
let seen = uploadedVersions.get(cache7);
|
|
2227
2695
|
if (!seen) {
|
|
2228
2696
|
seen = /* @__PURE__ */ new Map();
|
|
2229
|
-
uploadedVersions.set(
|
|
2697
|
+
uploadedVersions.set(cache7, seen);
|
|
2230
2698
|
}
|
|
2231
|
-
if (!
|
|
2232
|
-
|
|
2699
|
+
if (!cache7.has(id)) {
|
|
2700
|
+
cache7.uploadR8(id, PAGE_SIZE, PAGE_SIZE, page.data);
|
|
2233
2701
|
seen.set(pageIndex, page.version);
|
|
2234
2702
|
return true;
|
|
2235
2703
|
}
|
|
@@ -2242,7 +2710,7 @@ function syncDynamicPageTexture(cache6, pageIndex) {
|
|
|
2242
2710
|
const src = (patch.y + row) * PAGE_SIZE + patch.x;
|
|
2243
2711
|
tight.set(page.data.subarray(src, src + patch.w), row * patch.w);
|
|
2244
2712
|
}
|
|
2245
|
-
|
|
2713
|
+
cache7.subImageR8(id, patch.x, patch.y, patch.w, patch.h, tight);
|
|
2246
2714
|
}
|
|
2247
2715
|
seen.set(pageIndex, page.version);
|
|
2248
2716
|
return true;
|
|
@@ -2639,8 +3107,8 @@ function eligibleForMode(mode, toolTags) {
|
|
|
2639
3107
|
}
|
|
2640
3108
|
return false;
|
|
2641
3109
|
}
|
|
2642
|
-
function
|
|
2643
|
-
return eligibleForMode(reg.current(),
|
|
3110
|
+
function eligibleContribution(reg, entry) {
|
|
3111
|
+
return eligibleForMode(reg.current(), entry.eligibility?.capabilities ?? []);
|
|
2644
3112
|
}
|
|
2645
3113
|
|
|
2646
3114
|
// ../history/dist/index.js
|
|
@@ -3009,7 +3477,7 @@ function serialToEntry(se, custom, logger) {
|
|
|
3009
3477
|
};
|
|
3010
3478
|
}
|
|
3011
3479
|
|
|
3012
|
-
// ../core/dist/chunk-
|
|
3480
|
+
// ../core/dist/chunk-FVNWK7U3.js
|
|
3013
3481
|
function worldToScreen(worldX, worldY, view) {
|
|
3014
3482
|
return [view.panX + worldX * view.zoom.x, view.panY + worldY * view.zoom.y];
|
|
3015
3483
|
}
|
|
@@ -3260,6 +3728,58 @@ var GRAD_FILL_UNIFORMS = [
|
|
|
3260
3728
|
"u_gradAngle"
|
|
3261
3729
|
];
|
|
3262
3730
|
var GRAD_FILL_ATTRIBUTES = ["a_position"];
|
|
3731
|
+
var PATTERN_VERT_SRC = (
|
|
3732
|
+
/* glsl */
|
|
3733
|
+
`#version 300 es
|
|
3734
|
+
in vec2 a_position;
|
|
3735
|
+
uniform mat3 u_proj;
|
|
3736
|
+
uniform mat3 u_model;
|
|
3737
|
+
uniform mat3 u_worldInv;
|
|
3738
|
+
out vec2 v_world;
|
|
3739
|
+
void main() {
|
|
3740
|
+
vec3 screen = u_model * vec3(a_position, 1.0);
|
|
3741
|
+
vec3 clip = u_proj * vec3(screen.xy, 1.0);
|
|
3742
|
+
gl_Position = vec4(clip.xy, 0.0, 1.0);
|
|
3743
|
+
vec3 world = u_worldInv * vec3(screen.xy, 1.0);
|
|
3744
|
+
v_world = world.xy;
|
|
3745
|
+
}
|
|
3746
|
+
`
|
|
3747
|
+
);
|
|
3748
|
+
var PATTERN_FRAG_SRC = (
|
|
3749
|
+
/* glsl */
|
|
3750
|
+
`#version 300 es
|
|
3751
|
+
precision highp float;
|
|
3752
|
+
in vec2 v_world;
|
|
3753
|
+
uniform sampler2D u_sampler;
|
|
3754
|
+
uniform vec2 u_tileOrigin;
|
|
3755
|
+
uniform vec2 u_tileSize;
|
|
3756
|
+
uniform float u_opacity;
|
|
3757
|
+
uniform float u_alpha;
|
|
3758
|
+
uniform mat4 u_colorMatrix;
|
|
3759
|
+
uniform vec4 u_colorBias;
|
|
3760
|
+
out vec4 outColor;
|
|
3761
|
+
void main() {
|
|
3762
|
+
vec2 uv = (v_world - u_tileOrigin) / max(u_tileSize, vec2(0.0001));
|
|
3763
|
+
vec4 texel = texture(u_sampler, uv);
|
|
3764
|
+
vec4 mapped = clamp(u_colorMatrix * texel + u_colorBias, 0.0, 1.0);
|
|
3765
|
+
float a = mapped.a * u_opacity * u_alpha;
|
|
3766
|
+
outColor = vec4(mapped.rgb * a, a);
|
|
3767
|
+
}
|
|
3768
|
+
`
|
|
3769
|
+
);
|
|
3770
|
+
var PATTERN_FILL_UNIFORMS = [
|
|
3771
|
+
"u_proj",
|
|
3772
|
+
"u_model",
|
|
3773
|
+
"u_worldInv",
|
|
3774
|
+
"u_sampler",
|
|
3775
|
+
"u_tileOrigin",
|
|
3776
|
+
"u_tileSize",
|
|
3777
|
+
"u_opacity",
|
|
3778
|
+
"u_alpha",
|
|
3779
|
+
"u_colorMatrix",
|
|
3780
|
+
"u_colorBias"
|
|
3781
|
+
];
|
|
3782
|
+
var PATTERN_FILL_ATTRIBUTES = ["a_position"];
|
|
3263
3783
|
var GLMeshCache = class {
|
|
3264
3784
|
constructor(gl, aPositionLoc) {
|
|
3265
3785
|
this.gl = gl;
|
|
@@ -3382,17 +3902,20 @@ var GLTextureCache = class {
|
|
|
3382
3902
|
has(id) {
|
|
3383
3903
|
return this.map.has(id);
|
|
3384
3904
|
}
|
|
3385
|
-
|
|
3905
|
+
/** Uploads once per id; `wrap` therefore applies on first upload only.
|
|
3906
|
+
* An id is either a pattern tile or a shader texture, never both. */
|
|
3907
|
+
upload(id, source, wrap = "clamp") {
|
|
3386
3908
|
if (this.map.has(id)) return id;
|
|
3387
3909
|
const gl = this.gl;
|
|
3388
3910
|
const tex = gl.createTexture();
|
|
3389
3911
|
if (!tex) throw new Error(`GLTextureCache: createTexture failed for id="${id}"`);
|
|
3912
|
+
const wrapMode = wrap === "repeat" ? gl.REPEAT : gl.CLAMP_TO_EDGE;
|
|
3390
3913
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
3391
3914
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
|
3392
3915
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
3393
3916
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
3394
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S,
|
|
3395
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T,
|
|
3917
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapMode);
|
|
3918
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapMode);
|
|
3396
3919
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
3397
3920
|
this.map.set(id, tex);
|
|
3398
3921
|
return id;
|
|
@@ -3987,6 +4510,18 @@ function scale(m, sx, sy) {
|
|
|
3987
4510
|
const s = create(sx, 0, 0, sy, 0, 0);
|
|
3988
4511
|
return multiply2(m, s);
|
|
3989
4512
|
}
|
|
4513
|
+
function invert(m) {
|
|
4514
|
+
const a = m[0], b = m[1];
|
|
4515
|
+
const c = m[3], d = m[4];
|
|
4516
|
+
const tx = m[6], ty = m[7];
|
|
4517
|
+
const det = a * d - b * c;
|
|
4518
|
+
if (det === 0 || !Number.isFinite(det)) return identity();
|
|
4519
|
+
const ia = d / det;
|
|
4520
|
+
const ib = -b / det;
|
|
4521
|
+
const ic = -c / det;
|
|
4522
|
+
const id = a / det;
|
|
4523
|
+
return create(ia, ib, ic, id, -(ia * tx + ic * ty), -(ib * tx + id * ty));
|
|
4524
|
+
}
|
|
3990
4525
|
function apply(m, x, y) {
|
|
3991
4526
|
const a = m[0], b = m[1];
|
|
3992
4527
|
const c = m[3], d = m[4];
|
|
@@ -4014,6 +4549,7 @@ var mat3 = {
|
|
|
4014
4549
|
multiply: multiply2,
|
|
4015
4550
|
translate: translate2,
|
|
4016
4551
|
scale,
|
|
4552
|
+
invert,
|
|
4017
4553
|
apply,
|
|
4018
4554
|
screenToClip
|
|
4019
4555
|
};
|
|
@@ -5033,7 +5569,7 @@ function warnMissingGlyphOnce(family, cp) {
|
|
|
5033
5569
|
`weasel layoutRuns: no glyph for U+${cp.toString(16).toUpperCase().padStart(4, "0")} (${JSON.stringify(ch)}) in "${family}", and the dynamic tier could not rasterize it \u2014 skipping the character. Bake it into the atlas, or call registerCanvasFont("${family}") to serve missing codepoints from installed fonts.`
|
|
5034
5570
|
);
|
|
5035
5571
|
}
|
|
5036
|
-
function layoutRuns(runs, opts
|
|
5572
|
+
function layoutRuns(runs, opts) {
|
|
5037
5573
|
const ctx = { groups: /* @__PURE__ */ new Map() };
|
|
5038
5574
|
const entries = [];
|
|
5039
5575
|
let prevCp;
|
|
@@ -5203,7 +5739,7 @@ function layoutRuns(runs, opts, origin) {
|
|
|
5203
5739
|
}
|
|
5204
5740
|
}
|
|
5205
5741
|
const lineBoxes = [];
|
|
5206
|
-
let penY =
|
|
5742
|
+
let penY = 0;
|
|
5207
5743
|
let maxLineWidth = 0;
|
|
5208
5744
|
const finiteWidth = Number.isFinite(opts.maxWidth) ? opts.maxWidth : 0;
|
|
5209
5745
|
for (const line of lines) {
|
|
@@ -5215,7 +5751,7 @@ function layoutRuns(runs, opts, origin) {
|
|
|
5215
5751
|
const slack = finiteWidth - line.width;
|
|
5216
5752
|
return opts.align === "center" ? slack / 2 : slack;
|
|
5217
5753
|
})();
|
|
5218
|
-
const lineX0 =
|
|
5754
|
+
const lineX0 = alignShift;
|
|
5219
5755
|
const baselineSource = line.entries[0] ?? line.blank;
|
|
5220
5756
|
const lineBaselineY = baselineSource ? penY + baselineSource.font.common.base * (baselineSource.fontSize / baselineSource.font.info.size) : penY;
|
|
5221
5757
|
let penX = lineX0;
|
|
@@ -5289,7 +5825,7 @@ function layoutRuns(runs, opts, origin) {
|
|
|
5289
5825
|
groups: [...ctx.groups.values()],
|
|
5290
5826
|
decorations,
|
|
5291
5827
|
lines: lineBoxes,
|
|
5292
|
-
bounds: { width: maxLineWidth, height: penY
|
|
5828
|
+
bounds: { width: maxLineWidth, height: penY }
|
|
5293
5829
|
};
|
|
5294
5830
|
}
|
|
5295
5831
|
var LAYOUT_CACHE_VARIANT_LIMIT = 8;
|
|
@@ -5302,10 +5838,10 @@ function outlineBucket(runs, min) {
|
|
|
5302
5838
|
for (const size of sizes) if (size >= min) n++;
|
|
5303
5839
|
return n;
|
|
5304
5840
|
}
|
|
5305
|
-
function variantKey2(runs, opts
|
|
5306
|
-
return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}
|
|
5841
|
+
function variantKey2(runs, opts) {
|
|
5842
|
+
return `${opts.maxWidth}|${opts.lineHeight}|${opts.align}|${outlineBucket(runs, opts.outlineMinSize)}`;
|
|
5307
5843
|
}
|
|
5308
|
-
function cachedLayoutRuns(runs, opts
|
|
5844
|
+
function cachedLayoutRuns(runs, opts) {
|
|
5309
5845
|
const fonts = glyphGeneration();
|
|
5310
5846
|
let entry = cache2.get(runs);
|
|
5311
5847
|
if (entry === void 0) {
|
|
@@ -5315,10 +5851,10 @@ function cachedLayoutRuns(runs, opts, origin) {
|
|
|
5315
5851
|
entry.generation = fonts;
|
|
5316
5852
|
entry.byVariant.clear();
|
|
5317
5853
|
}
|
|
5318
|
-
const key = variantKey2(runs, opts
|
|
5854
|
+
const key = variantKey2(runs, opts);
|
|
5319
5855
|
const hit = entry.byVariant.get(key);
|
|
5320
5856
|
if (hit !== void 0) return hit;
|
|
5321
|
-
const laid = layoutRuns(runs, opts
|
|
5857
|
+
const laid = layoutRuns(runs, opts);
|
|
5322
5858
|
if (entry.byVariant.size >= LAYOUT_CACHE_VARIANT_LIMIT) entry.byVariant.clear();
|
|
5323
5859
|
entry.byVariant.set(key, laid);
|
|
5324
5860
|
return laid;
|
|
@@ -5626,25 +6162,264 @@ function outlineStrokeMesh(glyphKey, d, emWidth, stroke) {
|
|
|
5626
6162
|
cache4.set(key, mesh);
|
|
5627
6163
|
return mesh.indices.length === 0 ? null : mesh;
|
|
5628
6164
|
}
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
6165
|
+
var MAX_VERTICES_PER_BATCH = 32768;
|
|
6166
|
+
var FLOATS_PER_VERTEX = 6;
|
|
6167
|
+
var INITIAL_VERTICES = 256;
|
|
6168
|
+
var INITIAL_INDICES = 384;
|
|
6169
|
+
var SolidBatch = class {
|
|
6170
|
+
gl;
|
|
6171
|
+
vao;
|
|
6172
|
+
vbo;
|
|
6173
|
+
ibo;
|
|
6174
|
+
verts = new Float32Array(INITIAL_VERTICES * FLOATS_PER_VERTEX);
|
|
6175
|
+
idx = new Uint32Array(INITIAL_INDICES);
|
|
6176
|
+
nVerts = 0;
|
|
6177
|
+
nIdx = 0;
|
|
6178
|
+
/** What the GPU buffers are currently sized for. */
|
|
6179
|
+
vboVerts = 0;
|
|
6180
|
+
iboIdx = 0;
|
|
6181
|
+
constructor(gl, prog) {
|
|
6182
|
+
const aPos = prog.attribute("a_position");
|
|
6183
|
+
const aColor = prog.attribute("a_vertexColor");
|
|
6184
|
+
if (aPos === void 0 || aColor === void 0) {
|
|
6185
|
+
throw new Error("SolidBatch: vertex-color program is missing a_position / a_vertexColor");
|
|
6186
|
+
}
|
|
6187
|
+
const vao = gl.createVertexArray();
|
|
6188
|
+
const vbo = gl.createBuffer();
|
|
6189
|
+
const ibo = gl.createBuffer();
|
|
6190
|
+
if (!vao || !vbo || !ibo) throw new Error("SolidBatch: failed to create GL objects");
|
|
6191
|
+
this.gl = gl;
|
|
6192
|
+
this.vao = vao;
|
|
6193
|
+
this.vbo = vbo;
|
|
6194
|
+
this.ibo = ibo;
|
|
6195
|
+
const stride = FLOATS_PER_VERTEX * 4;
|
|
6196
|
+
gl.bindVertexArray(vao);
|
|
6197
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
6198
|
+
gl.enableVertexAttribArray(aPos);
|
|
6199
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, stride, 0);
|
|
6200
|
+
gl.enableVertexAttribArray(aColor);
|
|
6201
|
+
gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, stride, 8);
|
|
6202
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
|
|
6203
|
+
gl.bindVertexArray(null);
|
|
6204
|
+
this.growGpu(INITIAL_VERTICES, INITIAL_INDICES);
|
|
6205
|
+
}
|
|
6206
|
+
get length() {
|
|
6207
|
+
return this.nIdx;
|
|
6208
|
+
}
|
|
6209
|
+
/** Whether staging `vertices` more would put the run past the per-flush cap. */
|
|
6210
|
+
wouldOverflow(vertices) {
|
|
6211
|
+
return this.nVerts + vertices > MAX_VERTICES_PER_BATCH;
|
|
6212
|
+
}
|
|
6213
|
+
/**
|
|
6214
|
+
* Append one rect's four corners through `m`, all carrying `rgba` (straight
|
|
6215
|
+
* alpha). An affine maps a rect to a parallelogram, so two triangles still
|
|
6216
|
+
* cover it and the batch draws at `u_model` identity.
|
|
6217
|
+
*/
|
|
6218
|
+
pushRect(x, y, w, h, m, r, g, b, a) {
|
|
6219
|
+
this.reserve(4, 6);
|
|
6220
|
+
const v = this.verts;
|
|
6221
|
+
let i = this.nVerts * FLOATS_PER_VERTEX;
|
|
6222
|
+
const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];
|
|
6223
|
+
const x1 = x + w;
|
|
6224
|
+
const y1 = y + h;
|
|
6225
|
+
const ax = ma * x + mc * y + mtx, ay = mb * x + md * y + mty;
|
|
6226
|
+
const bx = ma * x1 + mc * y + mtx, by = mb * x1 + md * y + mty;
|
|
6227
|
+
const cx = ma * x1 + mc * y1 + mtx, cy = mb * x1 + md * y1 + mty;
|
|
6228
|
+
const dx = ma * x + mc * y1 + mtx, dy = mb * x + md * y1 + mty;
|
|
6229
|
+
v[i++] = ax;
|
|
6230
|
+
v[i++] = ay;
|
|
6231
|
+
v[i++] = r;
|
|
6232
|
+
v[i++] = g;
|
|
6233
|
+
v[i++] = b;
|
|
6234
|
+
v[i++] = a;
|
|
6235
|
+
v[i++] = bx;
|
|
6236
|
+
v[i++] = by;
|
|
6237
|
+
v[i++] = r;
|
|
6238
|
+
v[i++] = g;
|
|
6239
|
+
v[i++] = b;
|
|
6240
|
+
v[i++] = a;
|
|
6241
|
+
v[i++] = cx;
|
|
6242
|
+
v[i++] = cy;
|
|
6243
|
+
v[i++] = r;
|
|
6244
|
+
v[i++] = g;
|
|
6245
|
+
v[i++] = b;
|
|
6246
|
+
v[i++] = a;
|
|
6247
|
+
v[i++] = dx;
|
|
6248
|
+
v[i++] = dy;
|
|
6249
|
+
v[i++] = r;
|
|
6250
|
+
v[i++] = g;
|
|
6251
|
+
v[i++] = b;
|
|
6252
|
+
v[i++] = a;
|
|
6253
|
+
const base = this.nVerts;
|
|
6254
|
+
let j = this.nIdx;
|
|
6255
|
+
this.idx[j++] = base;
|
|
6256
|
+
this.idx[j++] = base + 1;
|
|
6257
|
+
this.idx[j++] = base + 2;
|
|
6258
|
+
this.idx[j++] = base;
|
|
6259
|
+
this.idx[j++] = base + 2;
|
|
6260
|
+
this.idx[j++] = base + 3;
|
|
6261
|
+
this.nVerts += 4;
|
|
6262
|
+
this.nIdx += 6;
|
|
6263
|
+
}
|
|
6264
|
+
/**
|
|
6265
|
+
* Append a tessellated mesh through `m`, all vertices carrying `rgba`. The
|
|
6266
|
+
* mesh's own indices are rebased onto the staged vertices, which is why the
|
|
6267
|
+
* index buffer is uploaded per flush rather than written once.
|
|
6268
|
+
*/
|
|
6269
|
+
pushMesh(mesh, m, r, g, b, a) {
|
|
6270
|
+
const src = mesh.vertices;
|
|
6271
|
+
const srcIdx = mesh.indices;
|
|
6272
|
+
const n = src.length >> 1;
|
|
6273
|
+
this.reserve(n, srcIdx.length);
|
|
6274
|
+
const v = this.verts;
|
|
6275
|
+
let i = this.nVerts * FLOATS_PER_VERTEX;
|
|
6276
|
+
const ma = m[0], mb = m[1], mc = m[3], md = m[4], mtx = m[6], mty = m[7];
|
|
6277
|
+
for (let k = 0; k < n; k++) {
|
|
6278
|
+
const x = src[k * 2];
|
|
6279
|
+
const y = src[k * 2 + 1];
|
|
6280
|
+
v[i++] = ma * x + mc * y + mtx;
|
|
6281
|
+
v[i++] = mb * x + md * y + mty;
|
|
6282
|
+
v[i++] = r;
|
|
6283
|
+
v[i++] = g;
|
|
6284
|
+
v[i++] = b;
|
|
6285
|
+
v[i++] = a;
|
|
6286
|
+
}
|
|
6287
|
+
const base = this.nVerts;
|
|
6288
|
+
const out = this.idx;
|
|
6289
|
+
let j = this.nIdx;
|
|
6290
|
+
for (let k = 0; k < srcIdx.length; k++) out[j++] = base + srcIdx[k];
|
|
6291
|
+
this.nVerts += n;
|
|
6292
|
+
this.nIdx += srcIdx.length;
|
|
6293
|
+
}
|
|
6294
|
+
/** Upload the staged geometry and bind the batch VAO. Returns the index
|
|
6295
|
+
* count for the caller's `drawElements`. */
|
|
6296
|
+
uploadAndBind() {
|
|
6297
|
+
const gl = this.gl;
|
|
6298
|
+
if (this.nVerts > this.vboVerts || this.nIdx > this.iboIdx) {
|
|
6299
|
+
this.growGpu(this.nVerts, this.nIdx);
|
|
6300
|
+
}
|
|
6301
|
+
gl.bindVertexArray(this.vao);
|
|
6302
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
|
|
6303
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.verts, 0, this.nVerts * FLOATS_PER_VERTEX);
|
|
6304
|
+
gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, this.idx, 0, this.nIdx);
|
|
6305
|
+
return this.nIdx;
|
|
6306
|
+
}
|
|
6307
|
+
reset() {
|
|
6308
|
+
this.nVerts = 0;
|
|
6309
|
+
this.nIdx = 0;
|
|
6310
|
+
}
|
|
6311
|
+
dispose() {
|
|
6312
|
+
const gl = this.gl;
|
|
6313
|
+
gl.deleteBuffer(this.vbo);
|
|
6314
|
+
gl.deleteBuffer(this.ibo);
|
|
6315
|
+
gl.deleteVertexArray(this.vao);
|
|
6316
|
+
}
|
|
6317
|
+
/** Grow the CPU arrays so `vertices` / `indices` more fit. */
|
|
6318
|
+
reserve(vertices, indices) {
|
|
6319
|
+
const needVerts = (this.nVerts + vertices) * FLOATS_PER_VERTEX;
|
|
6320
|
+
if (needVerts > this.verts.length) {
|
|
6321
|
+
let len = this.verts.length;
|
|
6322
|
+
while (len < needVerts) len *= 2;
|
|
6323
|
+
const grown = new Float32Array(len);
|
|
6324
|
+
grown.set(this.verts);
|
|
6325
|
+
this.verts = grown;
|
|
6326
|
+
}
|
|
6327
|
+
const needIdx = this.nIdx + indices;
|
|
6328
|
+
if (needIdx > this.idx.length) {
|
|
6329
|
+
let len = this.idx.length;
|
|
6330
|
+
while (len < needIdx) len *= 2;
|
|
6331
|
+
const grown = new Uint32Array(len);
|
|
6332
|
+
grown.set(this.idx);
|
|
6333
|
+
this.idx = grown;
|
|
6334
|
+
}
|
|
6335
|
+
}
|
|
6336
|
+
/** Size both GPU buffers for at least what is staged, doubling to amortize. */
|
|
6337
|
+
growGpu(vertices, indices) {
|
|
6338
|
+
const gl = this.gl;
|
|
6339
|
+
if (vertices > this.vboVerts) {
|
|
6340
|
+
let capacity = Math.max(this.vboVerts, INITIAL_VERTICES);
|
|
6341
|
+
while (capacity < vertices) capacity *= 2;
|
|
6342
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
|
|
6343
|
+
gl.bufferData(gl.ARRAY_BUFFER, capacity * FLOATS_PER_VERTEX * 4, gl.DYNAMIC_DRAW);
|
|
6344
|
+
this.vboVerts = capacity;
|
|
6345
|
+
}
|
|
6346
|
+
if (indices > this.iboIdx) {
|
|
6347
|
+
let capacity = Math.max(this.iboIdx, INITIAL_INDICES);
|
|
6348
|
+
while (capacity < indices) capacity *= 2;
|
|
6349
|
+
gl.bindVertexArray(this.vao);
|
|
6350
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo);
|
|
6351
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, capacity * 4, gl.DYNAMIC_DRAW);
|
|
6352
|
+
gl.bindVertexArray(null);
|
|
6353
|
+
this.iboIdx = capacity;
|
|
5636
6354
|
}
|
|
5637
6355
|
}
|
|
6356
|
+
};
|
|
6357
|
+
var FRAME_UPLOADS = /* @__PURE__ */ new WeakMap();
|
|
6358
|
+
var FRAME_PROJ = /* @__PURE__ */ new WeakMap();
|
|
6359
|
+
function uploadedFor(ctx, prog) {
|
|
6360
|
+
let byProgram = FRAME_UPLOADS.get(ctx);
|
|
6361
|
+
if (!byProgram) {
|
|
6362
|
+
byProgram = /* @__PURE__ */ new WeakMap();
|
|
6363
|
+
FRAME_UPLOADS.set(ctx, byProgram);
|
|
6364
|
+
}
|
|
6365
|
+
let uploaded = byProgram.get(prog);
|
|
6366
|
+
if (!uploaded) {
|
|
6367
|
+
uploaded = {};
|
|
6368
|
+
byProgram.set(prog, uploaded);
|
|
6369
|
+
}
|
|
6370
|
+
return uploaded;
|
|
6371
|
+
}
|
|
6372
|
+
function sameValues(prev, next) {
|
|
6373
|
+
if (prev === void 0 || prev.length !== next.length) return false;
|
|
6374
|
+
for (let i = 0; i < prev.length; i++) if (prev[i] !== next[i]) return false;
|
|
6375
|
+
return true;
|
|
6376
|
+
}
|
|
6377
|
+
var BATCH_MODEL = mat3.identity();
|
|
6378
|
+
function projFor(ctx) {
|
|
6379
|
+
let proj = FRAME_PROJ.get(ctx);
|
|
6380
|
+
if (!proj) {
|
|
6381
|
+
proj = mat3.screenToClip(ctx.widthCss, ctx.heightCss);
|
|
6382
|
+
FRAME_PROJ.set(ctx, proj);
|
|
6383
|
+
}
|
|
6384
|
+
return proj;
|
|
6385
|
+
}
|
|
6386
|
+
var COLOR_MATRIX_SCRATCH = new Float32Array(16);
|
|
6387
|
+
function setColorMatrixUniforms(ctx, prog, cm = ctx.state.colorMatrix) {
|
|
6388
|
+
const gl = ctx.gl;
|
|
6389
|
+
const uploaded = uploadedFor(ctx, prog);
|
|
5638
6390
|
const mLoc = prog.uniform("u_colorMatrix");
|
|
6391
|
+
if (mLoc !== void 0) {
|
|
6392
|
+
for (let row = 0; row < 4; row++) {
|
|
6393
|
+
for (let col = 0; col < 4; col++) {
|
|
6394
|
+
COLOR_MATRIX_SCRATCH[col * 4 + row] = cm[row * 5 + col];
|
|
6395
|
+
}
|
|
6396
|
+
}
|
|
6397
|
+
if (!sameValues(uploaded.colorMatrix, COLOR_MATRIX_SCRATCH)) {
|
|
6398
|
+
gl.uniformMatrix4fv(mLoc, false, COLOR_MATRIX_SCRATCH);
|
|
6399
|
+
uploaded.colorMatrix = Float32Array.from(COLOR_MATRIX_SCRATCH);
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
5639
6402
|
const bLoc = prog.uniform("u_colorBias");
|
|
5640
|
-
if (
|
|
5641
|
-
|
|
6403
|
+
if (bLoc !== void 0) {
|
|
6404
|
+
const bias = [cm[4], cm[9], cm[14], cm[19]];
|
|
6405
|
+
if (!sameValues(uploaded.colorBias, bias)) {
|
|
6406
|
+
gl.uniform4f(bLoc, bias[0], bias[1], bias[2], bias[3]);
|
|
6407
|
+
uploaded.colorBias = Float32Array.from(bias);
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
5642
6410
|
}
|
|
5643
|
-
function
|
|
6411
|
+
function fillMesh(ctx, path) {
|
|
5644
6412
|
if (ctx.flattenTolerance !== void 0) {
|
|
5645
|
-
return
|
|
6413
|
+
return tessellate(path, { flattenTolerance: ctx.flattenTolerance });
|
|
5646
6414
|
}
|
|
5647
|
-
return
|
|
6415
|
+
return getMesh(path);
|
|
6416
|
+
}
|
|
6417
|
+
function meshHandle(ctx, mesh) {
|
|
6418
|
+
if (ctx.flattenTolerance !== void 0) return ctx.meshCache.uploadTransient(mesh);
|
|
6419
|
+
return ctx.meshCache.handleFor(mesh);
|
|
6420
|
+
}
|
|
6421
|
+
function fillMeshHandle(ctx, path) {
|
|
6422
|
+
return meshHandle(ctx, fillMesh(ctx, path));
|
|
5648
6423
|
}
|
|
5649
6424
|
function dispatch(ctx, cmd) {
|
|
5650
6425
|
switch (cmd.kind) {
|
|
@@ -5653,10 +6428,13 @@ function dispatch(ctx, cmd) {
|
|
|
5653
6428
|
case "path":
|
|
5654
6429
|
return drawPath(ctx, cmd);
|
|
5655
6430
|
case "text":
|
|
6431
|
+
flushSolids(ctx);
|
|
5656
6432
|
return drawText(ctx, cmd);
|
|
5657
6433
|
case "image":
|
|
6434
|
+
flushSolids(ctx);
|
|
5658
6435
|
return drawImage(ctx, cmd);
|
|
5659
6436
|
case "shader":
|
|
6437
|
+
flushSolids(ctx);
|
|
5660
6438
|
return drawShader(ctx, cmd);
|
|
5661
6439
|
}
|
|
5662
6440
|
}
|
|
@@ -5785,11 +6563,13 @@ function drawGroup(ctx, cmd) {
|
|
|
5785
6563
|
"weasel: clip nesting depth exceeded (max 7). You can't nest more than 7 levels of clipped containers in a single draw tree. Flatten the hierarchy or compose poses outside the scene graph."
|
|
5786
6564
|
);
|
|
5787
6565
|
}
|
|
6566
|
+
flushSolids(ctx);
|
|
5788
6567
|
pushClip(ctx, cmd.clip, newDepth);
|
|
5789
6568
|
ctx.clipDepth = newDepth;
|
|
5790
6569
|
}
|
|
5791
6570
|
for (const child of cmd.children) dispatch(ctx, child);
|
|
5792
6571
|
if (cmd.clip) {
|
|
6572
|
+
flushSolids(ctx);
|
|
5793
6573
|
popClip(ctx, cmd.clip, ctx.clipDepth - 1);
|
|
5794
6574
|
ctx.clipDepth -= 1;
|
|
5795
6575
|
}
|
|
@@ -5798,38 +6578,98 @@ function drawGroup(ctx, cmd) {
|
|
|
5798
6578
|
function drawPath(ctx, cmd) {
|
|
5799
6579
|
if (!cmd.fill && !cmd.stroke) return;
|
|
5800
6580
|
if (cmd.fill) {
|
|
5801
|
-
const
|
|
5802
|
-
|
|
5803
|
-
|
|
6581
|
+
const batchablePaint = (cmd.fill.fill === void 0 || cmd.fill.fill === "solid") && (!cmd.vertexColors || cmd.vertexColors.length === 0);
|
|
6582
|
+
const solid = cmd.fill;
|
|
6583
|
+
if (batchablePaint && cmd.path.kind === "rect") {
|
|
6584
|
+
pushRect(ctx, cmd.path, solid);
|
|
5804
6585
|
} else {
|
|
5805
|
-
const
|
|
5806
|
-
if (
|
|
5807
|
-
|
|
5808
|
-
} else if (handle.requiresStencil) {
|
|
5809
|
-
drawPathFillStencil(ctx, cmd.fill, handle);
|
|
6586
|
+
const mesh = fillMesh(ctx, cmd.path);
|
|
6587
|
+
if (batchablePaint && canBatchMesh(mesh)) {
|
|
6588
|
+
pushMesh(ctx, mesh, solid);
|
|
5810
6589
|
} else {
|
|
5811
|
-
|
|
6590
|
+
flushSolids(ctx);
|
|
6591
|
+
const handle = meshHandle(ctx, mesh);
|
|
6592
|
+
if (cmd.vertexColors && cmd.vertexColors.length > 0 && (cmd.fill.fill === void 0 || cmd.fill.fill === "solid")) {
|
|
6593
|
+
drawPathFillVColor(ctx, cmd, solid, handle);
|
|
6594
|
+
} else if (handle.requiresStencil) {
|
|
6595
|
+
drawPathFillStencil(ctx, cmd.fill, handle);
|
|
6596
|
+
} else {
|
|
6597
|
+
drawPathFillByKind(ctx, cmd.fill, handle);
|
|
6598
|
+
}
|
|
5812
6599
|
}
|
|
5813
6600
|
}
|
|
5814
6601
|
}
|
|
5815
|
-
if (cmd.stroke)
|
|
5816
|
-
|
|
6602
|
+
if (cmd.stroke) drawPathStroke(ctx, cmd);
|
|
6603
|
+
}
|
|
6604
|
+
function isIdentityColorMatrix(cm) {
|
|
6605
|
+
return cm === IDENTITY_COLOR_MATRIX || sameValues(IDENTITY_COLOR_MATRIX, cm);
|
|
6606
|
+
}
|
|
6607
|
+
function stagedStateIsLive(ctx, staged) {
|
|
6608
|
+
if (staged.clipDepth !== ctx.clipDepth) return false;
|
|
6609
|
+
if (!staged.foldsAlpha && staged.alpha !== ctx.state.alpha) return false;
|
|
6610
|
+
const colorMatrix = ctx.state.colorMatrix;
|
|
6611
|
+
return staged.colorMatrix === colorMatrix || sameValues(staged.colorMatrix, colorMatrix);
|
|
6612
|
+
}
|
|
6613
|
+
var MAX_BATCHED_MESH_VERTICES = 256;
|
|
6614
|
+
function canBatchMesh(mesh) {
|
|
6615
|
+
return !mesh.requiresStencil && mesh.vertices.length >> 1 <= MAX_BATCHED_MESH_VERTICES;
|
|
6616
|
+
}
|
|
6617
|
+
function stageSolid(ctx, vertices) {
|
|
6618
|
+
if (ctx.solidState !== void 0 && !stagedStateIsLive(ctx, ctx.solidState)) flushSolids(ctx);
|
|
6619
|
+
if (ctx.solidBatch.wouldOverflow(vertices)) flushSolids(ctx);
|
|
6620
|
+
if (ctx.solidState === void 0) {
|
|
6621
|
+
const colorMatrix = ctx.state.colorMatrix;
|
|
6622
|
+
const foldsAlpha = isIdentityColorMatrix(colorMatrix);
|
|
6623
|
+
ctx.solidState = {
|
|
6624
|
+
alpha: foldsAlpha ? 1 : ctx.state.alpha,
|
|
6625
|
+
colorMatrix,
|
|
6626
|
+
clipDepth: ctx.clipDepth,
|
|
6627
|
+
foldsAlpha
|
|
6628
|
+
};
|
|
5817
6629
|
}
|
|
6630
|
+
return ctx.solidState;
|
|
6631
|
+
}
|
|
6632
|
+
function stagedColor(ctx, staged, paint) {
|
|
6633
|
+
const [r, g, b, a] = resolveColor(paint.color);
|
|
6634
|
+
return [r, g, b, a * (paint.opacity ?? 1) * (staged.foldsAlpha ? ctx.state.alpha : 1)];
|
|
6635
|
+
}
|
|
6636
|
+
function pushRect(ctx, rect, fill) {
|
|
6637
|
+
const staged = stageSolid(ctx, 4);
|
|
6638
|
+
const [r, g, b, a] = stagedColor(ctx, staged, fill);
|
|
6639
|
+
ctx.solidBatch.pushRect(
|
|
6640
|
+
rect.x,
|
|
6641
|
+
rect.y,
|
|
6642
|
+
rect.width,
|
|
6643
|
+
rect.height,
|
|
6644
|
+
ctx.state.transform,
|
|
6645
|
+
r,
|
|
6646
|
+
g,
|
|
6647
|
+
b,
|
|
6648
|
+
a
|
|
6649
|
+
);
|
|
6650
|
+
}
|
|
6651
|
+
function pushMesh(ctx, mesh, paint) {
|
|
6652
|
+
const staged = stageSolid(ctx, mesh.vertices.length >> 1);
|
|
6653
|
+
const [r, g, b, a] = stagedColor(ctx, staged, paint);
|
|
6654
|
+
ctx.solidBatch.pushMesh(mesh, ctx.state.transform, r, g, b, a);
|
|
5818
6655
|
}
|
|
5819
|
-
function
|
|
6656
|
+
function flushSolids(ctx) {
|
|
6657
|
+
const batch = ctx.solidBatch;
|
|
6658
|
+
const staged = ctx.solidState;
|
|
6659
|
+
if (batch.length === 0 || staged === void 0) return;
|
|
5820
6660
|
const gl = ctx.gl;
|
|
5821
|
-
const
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
gl.
|
|
5826
|
-
gl.
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
applyClipTest(ctx);
|
|
5831
|
-
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
|
|
6661
|
+
const prog = ctx.pathFillVColor;
|
|
6662
|
+
gl.useProgram(prog.handle);
|
|
6663
|
+
const indexCount = batch.uploadAndBind();
|
|
6664
|
+
setProjAndModel(ctx, prog, BATCH_MODEL);
|
|
6665
|
+
gl.uniform4f(prog.uniform("u_color"), 1, 1, 1, 1);
|
|
6666
|
+
gl.uniform1f(prog.uniform("u_alpha"), staged.alpha);
|
|
6667
|
+
setColorMatrixUniforms(ctx, prog, staged.colorMatrix);
|
|
6668
|
+
applyClipTest(ctx, staged.clipDepth);
|
|
6669
|
+
gl.drawElements(gl.TRIANGLES, indexCount, gl.UNSIGNED_INT, 0);
|
|
5832
6670
|
gl.bindVertexArray(null);
|
|
6671
|
+
batch.reset();
|
|
6672
|
+
ctx.solidState = void 0;
|
|
5833
6673
|
}
|
|
5834
6674
|
function expandAnchorColors(perAnchor, handle) {
|
|
5835
6675
|
const aA = handle.anchorA;
|
|
@@ -5874,11 +6714,18 @@ function drawPathFillVColor(ctx, cmd, fill, handle) {
|
|
|
5874
6714
|
gl.bindVertexArray(null);
|
|
5875
6715
|
gl.deleteBuffer(colorVbo);
|
|
5876
6716
|
}
|
|
5877
|
-
function setProjAndModel(ctx, prog) {
|
|
6717
|
+
function setProjAndModel(ctx, prog, model = ctx.state.transform) {
|
|
5878
6718
|
const gl = ctx.gl;
|
|
5879
|
-
const
|
|
5880
|
-
|
|
5881
|
-
|
|
6719
|
+
const uploaded = uploadedFor(ctx, prog);
|
|
6720
|
+
const proj = projFor(ctx);
|
|
6721
|
+
if (!sameValues(uploaded.proj, proj)) {
|
|
6722
|
+
gl.uniformMatrix3fv(prog.uniform("u_proj"), false, proj);
|
|
6723
|
+
uploaded.proj = Float32Array.from(proj);
|
|
6724
|
+
}
|
|
6725
|
+
if (!sameValues(uploaded.model, model)) {
|
|
6726
|
+
gl.uniformMatrix3fv(prog.uniform("u_model"), false, model);
|
|
6727
|
+
uploaded.model = Float32Array.from(model);
|
|
6728
|
+
}
|
|
5882
6729
|
}
|
|
5883
6730
|
function setSolidPaintUniforms(ctx, prog, color, opacity) {
|
|
5884
6731
|
const gl = ctx.gl;
|
|
@@ -5916,28 +6763,42 @@ function drawPathFillPattern(ctx, fill, handle) {
|
|
|
5916
6763
|
if (isDev3) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
|
|
5917
6764
|
return;
|
|
5918
6765
|
}
|
|
5919
|
-
ctx.textureCache.upload(tex.id, entry.source);
|
|
6766
|
+
ctx.textureCache.upload(tex.id, entry.source, "repeat");
|
|
5920
6767
|
const gl = ctx.gl;
|
|
5921
|
-
gl.useProgram(ctx.
|
|
6768
|
+
gl.useProgram(ctx.patternFill.handle);
|
|
5922
6769
|
gl.bindVertexArray(handle.vao);
|
|
5923
|
-
setProjAndModel(ctx, ctx.
|
|
5924
|
-
setColorMatrixUniforms(ctx, ctx.
|
|
6770
|
+
setProjAndModel(ctx, ctx.patternFill);
|
|
6771
|
+
setColorMatrixUniforms(ctx, ctx.patternFill);
|
|
6772
|
+
gl.uniformMatrix3fv(ctx.patternFill.uniform("u_worldInv"), false, gradientSpaceInverse(ctx, fill.units));
|
|
6773
|
+
const [tw, th] = textureSize(entry.source);
|
|
6774
|
+
const origin = fill.origin ?? { x: 0, y: 0 };
|
|
6775
|
+
gl.uniform2f(ctx.patternFill.uniform("u_tileOrigin"), origin.x, origin.y);
|
|
6776
|
+
gl.uniform2f(ctx.patternFill.uniform("u_tileSize"), tw, th);
|
|
5925
6777
|
ctx.textureCache.bind(tex.id, 0);
|
|
5926
|
-
gl.uniform1i(ctx.
|
|
5927
|
-
gl.uniform1f(ctx.
|
|
5928
|
-
gl.uniform1f(ctx.
|
|
6778
|
+
gl.uniform1i(ctx.patternFill.uniform("u_sampler"), 0);
|
|
6779
|
+
gl.uniform1f(ctx.patternFill.uniform("u_opacity"), fill.opacity ?? 1);
|
|
6780
|
+
gl.uniform1f(ctx.patternFill.uniform("u_alpha"), ctx.state.alpha);
|
|
5929
6781
|
applyClipTest(ctx);
|
|
5930
6782
|
gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
|
|
5931
6783
|
gl.bindVertexArray(null);
|
|
5932
6784
|
}
|
|
6785
|
+
function textureSize(source) {
|
|
6786
|
+
const w = "naturalWidth" in source ? source.naturalWidth : source.width;
|
|
6787
|
+
const h = "naturalHeight" in source ? source.naturalHeight : source.height;
|
|
6788
|
+
return [w || 1, h || 1];
|
|
6789
|
+
}
|
|
6790
|
+
function gradientSpaceInverse(ctx, units) {
|
|
6791
|
+
if (units === "local") return mat3.invert(ctx.state.transform);
|
|
6792
|
+
if (units === "world" && ctx.viewMatrix) return mat3.invert(ctx.viewMatrix);
|
|
6793
|
+
return mat3.identity();
|
|
6794
|
+
}
|
|
5933
6795
|
function drawPathFillGradient(ctx, fill, handle) {
|
|
5934
6796
|
const gl = ctx.gl;
|
|
5935
6797
|
const key = ctx.gradRampCache.upload(fill.stops);
|
|
5936
6798
|
gl.useProgram(ctx.gradFill.handle);
|
|
5937
6799
|
gl.bindVertexArray(handle.vao);
|
|
5938
6800
|
setProjAndModel(ctx, ctx.gradFill);
|
|
5939
|
-
|
|
5940
|
-
gl.uniformMatrix3fv(ctx.gradFill.uniform("u_worldInv"), false, identity3);
|
|
6801
|
+
gl.uniformMatrix3fv(ctx.gradFill.uniform("u_worldInv"), false, gradientSpaceInverse(ctx, fill.units));
|
|
5941
6802
|
ctx.gradRampCache.bind(key, 0);
|
|
5942
6803
|
gl.uniform1i(ctx.gradFill.uniform("u_ramp"), 0);
|
|
5943
6804
|
gl.uniform1f(ctx.gradFill.uniform("u_alpha"), ctx.state.alpha);
|
|
@@ -5971,9 +6832,8 @@ function drawPathFillGradient(ctx, fill, handle) {
|
|
|
5971
6832
|
gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
|
|
5972
6833
|
gl.bindVertexArray(null);
|
|
5973
6834
|
}
|
|
5974
|
-
function applyClipTest(ctx) {
|
|
6835
|
+
function applyClipTest(ctx, depth = ctx.clipDepth) {
|
|
5975
6836
|
const gl = ctx.gl;
|
|
5976
|
-
const depth = ctx.clipDepth;
|
|
5977
6837
|
if (depth === 0) {
|
|
5978
6838
|
gl.disable(gl.STENCIL_TEST);
|
|
5979
6839
|
return;
|
|
@@ -6057,6 +6917,7 @@ function drawPathStroke(ctx, cmd) {
|
|
|
6057
6917
|
}
|
|
6058
6918
|
const align = stroke.align ?? "center";
|
|
6059
6919
|
if (cmd.path.kind === "polygon" && align !== "center") {
|
|
6920
|
+
flushSolids(ctx);
|
|
6060
6921
|
drawPathStrokeStenciled(ctx, cmd, align);
|
|
6061
6922
|
return;
|
|
6062
6923
|
}
|
|
@@ -6067,6 +6928,12 @@ function drawPathStrokeUnclipped(ctx, cmd) {
|
|
|
6067
6928
|
const solid = stroke.paint;
|
|
6068
6929
|
const mesh = tessellateStroke(cmd.path, stroke, { flattenTolerance: ctx.flattenTolerance });
|
|
6069
6930
|
if (mesh.indices.length === 0) return;
|
|
6931
|
+
const hasVColors = !!(stroke.vertexColors && stroke.vertexColors.length > 0);
|
|
6932
|
+
if (!hasVColors && canBatchMesh(mesh)) {
|
|
6933
|
+
pushMesh(ctx, mesh, solid);
|
|
6934
|
+
return;
|
|
6935
|
+
}
|
|
6936
|
+
flushSolids(ctx);
|
|
6070
6937
|
const handle = ctx.meshCache.uploadTransient(mesh);
|
|
6071
6938
|
const gl = ctx.gl;
|
|
6072
6939
|
if (stroke.vertexColors && stroke.vertexColors.length > 0) {
|
|
@@ -6164,20 +7031,17 @@ function drawText(ctx, cmd) {
|
|
|
6164
7031
|
const maxWidth = cmd.maxWidth ?? Infinity;
|
|
6165
7032
|
const minScreen = ctx.textOutlineMinScreenSize ?? OUTLINE_MIN_SCREEN_PX;
|
|
6166
7033
|
const outlineMinSize = Number.isFinite(minScreen) ? minScreen / modelScale(ctx.state.transform) : void 0;
|
|
6167
|
-
const laid = cachedLayoutRuns(
|
|
6168
|
-
cmd.runs,
|
|
6169
|
-
{ maxWidth, lineHeight, align, outlineMinSize },
|
|
6170
|
-
{ x: cmd.x, y: cmd.y }
|
|
6171
|
-
);
|
|
7034
|
+
const laid = cachedLayoutRuns(cmd.runs, { maxWidth, lineHeight, align, outlineMinSize });
|
|
6172
7035
|
if (laid.groups.length === 0 && laid.decorations.length === 0) return;
|
|
6173
|
-
const
|
|
7036
|
+
const dx = cmd.x;
|
|
7037
|
+
const dy = cmd.y + verticalAlignOffset(cmd.verticalAlign, cmd.height, laid.bounds.height);
|
|
6174
7038
|
const gl = ctx.gl;
|
|
6175
7039
|
applyClipTest(ctx);
|
|
6176
7040
|
const preparedPrograms = /* @__PURE__ */ new Set();
|
|
6177
7041
|
let currentProg = null;
|
|
6178
7042
|
for (const group of laid.groups) {
|
|
6179
7043
|
if (group.source === "outline") {
|
|
6180
|
-
drawTextOutlineGroup(ctx, group, dy);
|
|
7044
|
+
drawTextOutlineGroup(ctx, group, dx, dy);
|
|
6181
7045
|
currentProg = null;
|
|
6182
7046
|
continue;
|
|
6183
7047
|
}
|
|
@@ -6192,31 +7056,31 @@ function drawText(ctx, cmd) {
|
|
|
6192
7056
|
setColorMatrixUniforms(ctx, prog);
|
|
6193
7057
|
gl.uniform1f(prog.uniform("u_alpha"), ctx.state.alpha);
|
|
6194
7058
|
}
|
|
6195
|
-
drawTextGroup(ctx, group, prog, dy);
|
|
7059
|
+
drawTextGroup(ctx, group, prog, dx, dy);
|
|
6196
7060
|
}
|
|
6197
|
-
drawTextDecorations(ctx, laid.decorations, dy);
|
|
7061
|
+
drawTextDecorations(ctx, laid.decorations, dx, dy);
|
|
6198
7062
|
}
|
|
6199
7063
|
var SYNTHETIC_ITALIC_RADIANS = 0.2094;
|
|
6200
|
-
function drawTextOutlineGroup(ctx, group, dy) {
|
|
6201
|
-
const mesh = outlineGroupMesh(group, dy);
|
|
7064
|
+
function drawTextOutlineGroup(ctx, group, dx, dy) {
|
|
7065
|
+
const mesh = outlineGroupMesh(group, dx, dy);
|
|
6202
7066
|
if (mesh) drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
|
|
6203
7067
|
if (!group.stroke) return;
|
|
6204
|
-
const strokeMesh = outlineGroupStrokeMesh(group, dy);
|
|
7068
|
+
const strokeMesh = outlineGroupStrokeMesh(group, dx, dy);
|
|
6205
7069
|
if (strokeMesh) {
|
|
6206
7070
|
drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(strokeMesh));
|
|
6207
7071
|
}
|
|
6208
7072
|
}
|
|
6209
|
-
function outlineGroupMesh(group, dy) {
|
|
6210
|
-
return mergeGlyphMeshes(group, dy, (glyph) => outlineMesh(glyph.key, glyph.d));
|
|
7073
|
+
function outlineGroupMesh(group, dx, dy) {
|
|
7074
|
+
return mergeGlyphMeshes(group, dx, dy, (glyph) => outlineMesh(glyph.key, glyph.d));
|
|
6211
7075
|
}
|
|
6212
|
-
function outlineGroupStrokeMesh(group, dy) {
|
|
7076
|
+
function outlineGroupStrokeMesh(group, dx, dy) {
|
|
6213
7077
|
const stroke = group.stroke;
|
|
6214
7078
|
if (!stroke) return null;
|
|
6215
7079
|
const width = stroke.width ?? 1;
|
|
6216
7080
|
if (!(width > 0)) return null;
|
|
6217
|
-
return mergeGlyphMeshes(group, dy, (glyph) => glyph.scale > 0 ? outlineStrokeMesh(glyph.key, glyph.d, quantizeEmWidth(width / glyph.scale), stroke) : null);
|
|
7081
|
+
return mergeGlyphMeshes(group, dx, dy, (glyph) => glyph.scale > 0 ? outlineStrokeMesh(glyph.key, glyph.d, quantizeEmWidth(width / glyph.scale), stroke) : null);
|
|
6218
7082
|
}
|
|
6219
|
-
function mergeGlyphMeshes(group, dy, meshFor) {
|
|
7083
|
+
function mergeGlyphMeshes(group, dx, dy, meshFor) {
|
|
6220
7084
|
const parts = [];
|
|
6221
7085
|
let vertexFloats = 0;
|
|
6222
7086
|
let indexCount = 0;
|
|
@@ -6239,7 +7103,7 @@ function mergeGlyphMeshes(group, dy, meshFor) {
|
|
|
6239
7103
|
for (let k = 0; k < mesh.vertices.length; k += 2) {
|
|
6240
7104
|
const ex = mesh.vertices[k];
|
|
6241
7105
|
const ey = mesh.vertices[k + 1];
|
|
6242
|
-
vertices[vi++] = x + (ex - ey * shear) * scale2;
|
|
7106
|
+
vertices[vi++] = x + dx + (ex - ey * shear) * scale2;
|
|
6243
7107
|
vertices[vi++] = baselineY + dy + ey * scale2;
|
|
6244
7108
|
}
|
|
6245
7109
|
for (let k = 0; k < mesh.indices.length; k++) indices[ii++] = base + mesh.indices[k];
|
|
@@ -6247,7 +7111,7 @@ function mergeGlyphMeshes(group, dy, meshFor) {
|
|
|
6247
7111
|
}
|
|
6248
7112
|
return { vertices, indices };
|
|
6249
7113
|
}
|
|
6250
|
-
function drawTextDecorations(ctx, decorations, dy) {
|
|
7114
|
+
function drawTextDecorations(ctx, decorations, dx, dy) {
|
|
6251
7115
|
if (decorations.length === 0) return;
|
|
6252
7116
|
const batches = /* @__PURE__ */ new Map();
|
|
6253
7117
|
for (const d of decorations) {
|
|
@@ -6270,14 +7134,15 @@ function drawTextDecorations(ctx, decorations, dy) {
|
|
|
6270
7134
|
const vertices = new Float32Array(rects.length * 4 * 2);
|
|
6271
7135
|
let vi = 0;
|
|
6272
7136
|
for (const d of rects) {
|
|
7137
|
+
const dx0 = d.x0 + dx, dx1 = d.x1 + dx;
|
|
6273
7138
|
const dy0 = d.y0 + dy, dy1 = d.y1 + dy;
|
|
6274
|
-
vertices[vi++] =
|
|
7139
|
+
vertices[vi++] = dx0;
|
|
6275
7140
|
vertices[vi++] = dy0;
|
|
6276
|
-
vertices[vi++] =
|
|
7141
|
+
vertices[vi++] = dx1;
|
|
6277
7142
|
vertices[vi++] = dy0;
|
|
6278
|
-
vertices[vi++] =
|
|
7143
|
+
vertices[vi++] = dx0;
|
|
6279
7144
|
vertices[vi++] = dy1;
|
|
6280
|
-
vertices[vi++] =
|
|
7145
|
+
vertices[vi++] = dx1;
|
|
6281
7146
|
vertices[vi++] = dy1;
|
|
6282
7147
|
}
|
|
6283
7148
|
const indices = new Uint32Array(rects.length * 6);
|
|
@@ -6316,7 +7181,7 @@ function drawTextDecorations(ctx, decorations, dy) {
|
|
|
6316
7181
|
gl.deleteBuffer(ibo);
|
|
6317
7182
|
}
|
|
6318
7183
|
}
|
|
6319
|
-
function drawTextGroup(ctx, group, prog, dy) {
|
|
7184
|
+
function drawTextGroup(ctx, group, prog, dx, dy) {
|
|
6320
7185
|
if (group.source === "canvas") {
|
|
6321
7186
|
if (!syncDynamicPageTexture(ctx.textureCache, group.page)) return;
|
|
6322
7187
|
} else {
|
|
@@ -6327,23 +7192,24 @@ function drawTextGroup(ctx, group, prog, dy) {
|
|
|
6327
7192
|
const vertices = new Float32Array(group.quads.length * 4 * 5);
|
|
6328
7193
|
let vi = 0;
|
|
6329
7194
|
for (const q of group.quads) {
|
|
7195
|
+
const x0 = q.x0 + dx, x1 = q.x1 + dx;
|
|
6330
7196
|
const y0 = q.y0 + dy, y1 = q.y1 + dy, by = q.baselineY + dy;
|
|
6331
|
-
vertices[vi++] =
|
|
7197
|
+
vertices[vi++] = x0;
|
|
6332
7198
|
vertices[vi++] = y0;
|
|
6333
7199
|
vertices[vi++] = q.u0;
|
|
6334
7200
|
vertices[vi++] = q.v0;
|
|
6335
7201
|
vertices[vi++] = by;
|
|
6336
|
-
vertices[vi++] =
|
|
7202
|
+
vertices[vi++] = x1;
|
|
6337
7203
|
vertices[vi++] = y0;
|
|
6338
7204
|
vertices[vi++] = q.u1;
|
|
6339
7205
|
vertices[vi++] = q.v0;
|
|
6340
7206
|
vertices[vi++] = by;
|
|
6341
|
-
vertices[vi++] =
|
|
7207
|
+
vertices[vi++] = x0;
|
|
6342
7208
|
vertices[vi++] = y1;
|
|
6343
7209
|
vertices[vi++] = q.u0;
|
|
6344
7210
|
vertices[vi++] = q.v1;
|
|
6345
7211
|
vertices[vi++] = by;
|
|
6346
|
-
vertices[vi++] =
|
|
7212
|
+
vertices[vi++] = x1;
|
|
6347
7213
|
vertices[vi++] = y1;
|
|
6348
7214
|
vertices[vi++] = q.u1;
|
|
6349
7215
|
vertices[vi++] = q.v1;
|
|
@@ -6457,6 +7323,11 @@ function drawImage(ctx, cmd) {
|
|
|
6457
7323
|
setProjAndModel(ctx, ctx.imageFill);
|
|
6458
7324
|
setColorMatrixUniforms(ctx, ctx.imageFill);
|
|
6459
7325
|
ctx.imageCache.bind(cmd.image, 0);
|
|
7326
|
+
gl.texParameteri(
|
|
7327
|
+
gl.TEXTURE_2D,
|
|
7328
|
+
gl.TEXTURE_MAG_FILTER,
|
|
7329
|
+
cmd.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
|
|
7330
|
+
);
|
|
6460
7331
|
gl.uniform1i(ctx.imageFill.uniform("u_sampler"), 0);
|
|
6461
7332
|
gl.uniform1f(ctx.imageFill.uniform("u_opacity"), cmd.opacity ?? 1);
|
|
6462
7333
|
gl.uniform1f(ctx.imageFill.uniform("u_alpha"), ctx.state.alpha);
|
|
@@ -6543,6 +7414,7 @@ var WeaselRenderer = class {
|
|
|
6543
7414
|
textSdfR8;
|
|
6544
7415
|
imageFill;
|
|
6545
7416
|
gradFill;
|
|
7417
|
+
patternFill;
|
|
6546
7418
|
meshCache;
|
|
6547
7419
|
textureCache;
|
|
6548
7420
|
imageCache;
|
|
@@ -6550,10 +7422,7 @@ var WeaselRenderer = class {
|
|
|
6550
7422
|
programRegistry = /* @__PURE__ */ new Map();
|
|
6551
7423
|
quadVbo = null;
|
|
6552
7424
|
quadIbo = null;
|
|
6553
|
-
|
|
6554
|
-
rectVao = null;
|
|
6555
|
-
rectVbo = null;
|
|
6556
|
-
rectIbo = null;
|
|
7425
|
+
solidBatch;
|
|
6557
7426
|
groupState = new GroupState();
|
|
6558
7427
|
widthCss;
|
|
6559
7428
|
heightCss;
|
|
@@ -6618,6 +7487,9 @@ var WeaselRenderer = class {
|
|
|
6618
7487
|
this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
|
|
6619
7488
|
this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
|
|
6620
7489
|
this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
|
|
7490
|
+
this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
|
|
7491
|
+
this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
|
|
7492
|
+
this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
|
|
6621
7493
|
const aPos = this.pathFill.attribute("a_position");
|
|
6622
7494
|
if (aPos === void 0) throw new Error("WeaselRenderer: a_position not found in path-fill shader");
|
|
6623
7495
|
this.meshCache = new GLMeshCache(this.gl, aPos);
|
|
@@ -6625,31 +7497,7 @@ var WeaselRenderer = class {
|
|
|
6625
7497
|
this.imageCache = new GLImageCache(this.gl, this.imageMinification);
|
|
6626
7498
|
this.gradRampCache = new GradientRampCache(this.gl);
|
|
6627
7499
|
this.uploadQuadGeometry();
|
|
6628
|
-
this.
|
|
6629
|
-
}
|
|
6630
|
-
/** Allocate the shared rect VAO + dynamic VBO (4 verts × 2 floats) + static
|
|
6631
|
-
* IBO (6 indices). drawRectFast bufferSubData's the 4 corner coords each
|
|
6632
|
-
* draw, avoiding per-rect buffer allocation in animated demos. */
|
|
6633
|
-
uploadRectGeometry(aPositionLoc) {
|
|
6634
|
-
const gl = this.gl;
|
|
6635
|
-
this.rectVao = gl.createVertexArray();
|
|
6636
|
-
this.rectVbo = gl.createBuffer();
|
|
6637
|
-
this.rectIbo = gl.createBuffer();
|
|
6638
|
-
if (!this.rectVao || !this.rectVbo || !this.rectIbo) {
|
|
6639
|
-
throw new Error("WeaselRenderer: failed to create rect-fast geometry");
|
|
6640
|
-
}
|
|
6641
|
-
gl.bindVertexArray(this.rectVao);
|
|
6642
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, this.rectVbo);
|
|
6643
|
-
gl.bufferData(gl.ARRAY_BUFFER, 8 * 4, gl.DYNAMIC_DRAW);
|
|
6644
|
-
gl.enableVertexAttribArray(aPositionLoc);
|
|
6645
|
-
gl.vertexAttribPointer(aPositionLoc, 2, gl.FLOAT, false, 0, 0);
|
|
6646
|
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.rectIbo);
|
|
6647
|
-
gl.bufferData(
|
|
6648
|
-
gl.ELEMENT_ARRAY_BUFFER,
|
|
6649
|
-
new Uint32Array([0, 1, 2, 0, 2, 3]),
|
|
6650
|
-
gl.STATIC_DRAW
|
|
6651
|
-
);
|
|
6652
|
-
gl.bindVertexArray(null);
|
|
7500
|
+
this.solidBatch = new SolidBatch(this.gl, this.pathFillVColor);
|
|
6653
7501
|
}
|
|
6654
7502
|
uploadQuadGeometry() {
|
|
6655
7503
|
const gl = this.gl;
|
|
@@ -6726,6 +7574,9 @@ var WeaselRenderer = class {
|
|
|
6726
7574
|
this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
|
|
6727
7575
|
this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
|
|
6728
7576
|
this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
|
|
7577
|
+
this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
|
|
7578
|
+
this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
|
|
7579
|
+
this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
|
|
6729
7580
|
const aPos = this.pathFill.attribute("a_position");
|
|
6730
7581
|
if (aPos === void 0) throw new Error("a_position missing after restore");
|
|
6731
7582
|
this.meshCache = new GLMeshCache(this.gl, aPos);
|
|
@@ -6733,7 +7584,7 @@ var WeaselRenderer = class {
|
|
|
6733
7584
|
this.imageCache = new GLImageCache(this.gl, this.imageMinification);
|
|
6734
7585
|
this.gradRampCache = new GradientRampCache(this.gl);
|
|
6735
7586
|
this.uploadQuadGeometry();
|
|
6736
|
-
this.
|
|
7587
|
+
this.solidBatch = new SolidBatch(this.gl, this.pathFillVColor);
|
|
6737
7588
|
for (const id of this.programRegistry.keys()) {
|
|
6738
7589
|
const src = getProgramSource(id);
|
|
6739
7590
|
if (!src) continue;
|
|
@@ -6793,11 +7644,17 @@ var WeaselRenderer = class {
|
|
|
6793
7644
|
this.gradRampCache.free();
|
|
6794
7645
|
if (this.quadVbo) gl.deleteBuffer(this.quadVbo);
|
|
6795
7646
|
if (this.quadIbo) gl.deleteBuffer(this.quadIbo);
|
|
6796
|
-
|
|
6797
|
-
if (this.rectIbo) gl.deleteBuffer(this.rectIbo);
|
|
6798
|
-
if (this.rectVao) gl.deleteVertexArray(this.rectVao);
|
|
7647
|
+
this.solidBatch.dispose();
|
|
6799
7648
|
}
|
|
6800
|
-
|
|
7649
|
+
/**
|
|
7650
|
+
* Draw one frame.
|
|
7651
|
+
*
|
|
7652
|
+
* `viewMatrix` is the frame's world→screen transform. It is only consulted
|
|
7653
|
+
* by `units: 'world'` gradients, which fall back to screen space without
|
|
7654
|
+
* it — every other command carries its own transform in the stream, so
|
|
7655
|
+
* callers with no view concept can keep calling `render(commands)`.
|
|
7656
|
+
*/
|
|
7657
|
+
render(commands, viewMatrix) {
|
|
6801
7658
|
if (this.contextLost || this.disposed) return;
|
|
6802
7659
|
const gl = this.gl;
|
|
6803
7660
|
this.meshCache.drainPendingDeletes();
|
|
@@ -6812,6 +7669,7 @@ var WeaselRenderer = class {
|
|
|
6812
7669
|
textSdfR8: this.textSdfR8,
|
|
6813
7670
|
imageFill: this.imageFill,
|
|
6814
7671
|
gradFill: this.gradFill,
|
|
7672
|
+
patternFill: this.patternFill,
|
|
6815
7673
|
meshCache: this.meshCache,
|
|
6816
7674
|
textureCache: this.textureCache,
|
|
6817
7675
|
imageCache: this.imageCache,
|
|
@@ -6819,16 +7677,17 @@ var WeaselRenderer = class {
|
|
|
6819
7677
|
programRegistry: this.programRegistry,
|
|
6820
7678
|
quadVbo: this.quadVbo,
|
|
6821
7679
|
quadIbo: this.quadIbo,
|
|
6822
|
-
|
|
6823
|
-
rectVbo: this.rectVbo,
|
|
7680
|
+
solidBatch: this.solidBatch,
|
|
6824
7681
|
state: this.groupState,
|
|
6825
7682
|
widthCss: this.widthCss,
|
|
6826
7683
|
heightCss: this.heightCss,
|
|
6827
7684
|
clipDepth: 0,
|
|
6828
7685
|
flattenTolerance: this.flattenTolerance,
|
|
6829
|
-
textOutlineMinScreenSize: this.textOutlineMinScreenSize
|
|
7686
|
+
textOutlineMinScreenSize: this.textOutlineMinScreenSize,
|
|
7687
|
+
viewMatrix
|
|
6830
7688
|
};
|
|
6831
7689
|
for (const cmd of commands) dispatch(ctx, cmd);
|
|
7690
|
+
flushSolids(ctx);
|
|
6832
7691
|
this.meshCache.freeTransient();
|
|
6833
7692
|
}
|
|
6834
7693
|
resize(dims) {
|
|
@@ -6858,6 +7717,10 @@ var WeaselRenderer = class {
|
|
|
6858
7717
|
return this.pathFillVColor;
|
|
6859
7718
|
}
|
|
6860
7719
|
/** @internal */
|
|
7720
|
+
_solidBatch() {
|
|
7721
|
+
return this.solidBatch;
|
|
7722
|
+
}
|
|
7723
|
+
/** @internal */
|
|
6861
7724
|
_textSdf() {
|
|
6862
7725
|
return this.textSdf;
|
|
6863
7726
|
}
|
|
@@ -6874,6 +7737,10 @@ var WeaselRenderer = class {
|
|
|
6874
7737
|
return this.gradFill;
|
|
6875
7738
|
}
|
|
6876
7739
|
/** @internal */
|
|
7740
|
+
_patternFill() {
|
|
7741
|
+
return this.patternFill;
|
|
7742
|
+
}
|
|
7743
|
+
/** @internal */
|
|
6877
7744
|
_meshCache() {
|
|
6878
7745
|
return this.meshCache;
|
|
6879
7746
|
}
|
|
@@ -6934,6 +7801,9 @@ function alignedStrokeRect(rect, align, width = 1) {
|
|
|
6934
7801
|
height: rect.height + 2 * d
|
|
6935
7802
|
};
|
|
6936
7803
|
}
|
|
7804
|
+
function meanScale(s) {
|
|
7805
|
+
return Math.sqrt(s.x * s.y);
|
|
7806
|
+
}
|
|
6937
7807
|
var DEFAULT_LINE = {
|
|
6938
7808
|
paint: { fill: "solid", color: "rgba(255, 255, 255, 0.08)" },
|
|
6939
7809
|
width: 1
|
|
@@ -7074,374 +7944,136 @@ var DEFAULT_DEVICE_PROFILE = resolveDeviceProfile({
|
|
|
7074
7944
|
coarsePointer: false,
|
|
7075
7945
|
canHover: true,
|
|
7076
7946
|
dpr: 1
|
|
7077
|
-
});
|
|
7078
|
-
var COARSE_QUERY = "(pointer: coarse)";
|
|
7079
|
-
var HOVER_QUERY = "(hover: hover)";
|
|
7080
|
-
function hasMatchMedia() {
|
|
7081
|
-
return typeof window !== "undefined" && typeof window.matchMedia === "function";
|
|
7082
|
-
}
|
|
7083
|
-
function detectOnce() {
|
|
7084
|
-
const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
|
|
7085
|
-
if (!hasMatchMedia()) {
|
|
7086
|
-
return {
|
|
7087
|
-
coarsePointer: DEFAULT_DEVICE_PROFILE.coarsePointer,
|
|
7088
|
-
canHover: DEFAULT_DEVICE_PROFILE.canHover,
|
|
7089
|
-
dpr
|
|
7090
|
-
};
|
|
7091
|
-
}
|
|
7092
|
-
return {
|
|
7093
|
-
coarsePointer: window.matchMedia(COARSE_QUERY).matches,
|
|
7094
|
-
canHover: window.matchMedia(HOVER_QUERY).matches,
|
|
7095
|
-
dpr
|
|
7096
|
-
};
|
|
7097
|
-
}
|
|
7098
|
-
function useDetectedFacts() {
|
|
7099
|
-
const [facts, setFacts] = useState(detectOnce);
|
|
7100
|
-
useEffect(() => {
|
|
7101
|
-
if (!hasMatchMedia()) return;
|
|
7102
|
-
const coarseMq = window.matchMedia(COARSE_QUERY);
|
|
7103
|
-
const hoverMq = window.matchMedia(HOVER_QUERY);
|
|
7104
|
-
const onCoarse = (e) => setFacts((f) => ({ ...f, coarsePointer: e.matches }));
|
|
7105
|
-
const onHover = (e) => setFacts((f) => ({ ...f, canHover: e.matches }));
|
|
7106
|
-
coarseMq.addEventListener("change", onCoarse);
|
|
7107
|
-
hoverMq.addEventListener("change", onHover);
|
|
7108
|
-
let disposed = false;
|
|
7109
|
-
let densityMq = null;
|
|
7110
|
-
const onDensity = () => {
|
|
7111
|
-
if (disposed) return;
|
|
7112
|
-
const next = window.devicePixelRatio || 1;
|
|
7113
|
-
setFacts((f) => f.dpr === next ? f : { ...f, dpr: next });
|
|
7114
|
-
arm();
|
|
7115
|
-
};
|
|
7116
|
-
function arm() {
|
|
7117
|
-
if (disposed) return;
|
|
7118
|
-
densityMq?.removeEventListener("change", onDensity);
|
|
7119
|
-
densityMq = window.matchMedia(
|
|
7120
|
-
`(resolution: ${window.devicePixelRatio || 1}dppx)`
|
|
7121
|
-
);
|
|
7122
|
-
densityMq.addEventListener("change", onDensity);
|
|
7123
|
-
}
|
|
7124
|
-
arm();
|
|
7125
|
-
setFacts({
|
|
7126
|
-
coarsePointer: coarseMq.matches,
|
|
7127
|
-
canHover: hoverMq.matches,
|
|
7128
|
-
dpr: window.devicePixelRatio || 1
|
|
7129
|
-
});
|
|
7130
|
-
return () => {
|
|
7131
|
-
disposed = true;
|
|
7132
|
-
coarseMq.removeEventListener("change", onCoarse);
|
|
7133
|
-
hoverMq.removeEventListener("change", onHover);
|
|
7134
|
-
densityMq?.removeEventListener("change", onDensity);
|
|
7135
|
-
};
|
|
7136
|
-
}, []);
|
|
7137
|
-
return facts;
|
|
7138
|
-
}
|
|
7139
|
-
var DeviceProfileContext = createContext(null);
|
|
7140
|
-
function useDeviceProfile(overrides) {
|
|
7141
|
-
const provided = useContext(DeviceProfileContext);
|
|
7142
|
-
const detected = useDetectedFacts();
|
|
7143
|
-
const base = provided ?? detected;
|
|
7144
|
-
const { coarsePointer: coarsePointer2, canHover: canHover2, dpr, targetScale } = overrides ?? {};
|
|
7145
|
-
return useMemo(
|
|
7146
|
-
() => resolveDeviceProfile(base, {
|
|
7147
|
-
...coarsePointer2 !== void 0 ? { coarsePointer: coarsePointer2 } : {},
|
|
7148
|
-
...canHover2 !== void 0 ? { canHover: canHover2 } : {},
|
|
7149
|
-
...dpr !== void 0 ? { dpr } : {},
|
|
7150
|
-
...targetScale !== void 0 ? { targetScale } : {}
|
|
7151
|
-
}),
|
|
7152
|
-
[base.coarsePointer, base.canHover, base.dpr, coarsePointer2, canHover2, dpr, targetScale]
|
|
7153
|
-
);
|
|
7154
|
-
}
|
|
7155
|
-
function DeviceProfileProvider({ value, children }) {
|
|
7156
|
-
const profile = useDeviceProfile(value);
|
|
7157
|
-
return createElement(DeviceProfileContext.Provider, { value: profile }, children);
|
|
7158
|
-
}
|
|
7159
|
-
var HANDLE_BASE_PX = 8;
|
|
7160
|
-
var ANCHOR_HIT_BASE_PX = 8;
|
|
7161
|
-
var ROTATION_HANDLE_BASE_PX = 24;
|
|
7162
|
-
function zoomAt(view, anchor, factor, opts) {
|
|
7163
|
-
const fx = typeof factor === "number" ? factor : factor.x;
|
|
7164
|
-
const fy = typeof factor === "number" ? factor : factor.y;
|
|
7165
|
-
const minX = typeof opts?.min === "number" ? opts.min : opts?.min?.x ?? 0.1;
|
|
7166
|
-
const minY = typeof opts?.min === "number" ? opts.min : opts?.min?.y ?? 0.1;
|
|
7167
|
-
const maxX = typeof opts?.max === "number" ? opts.max : opts?.max?.x ?? 8;
|
|
7168
|
-
const maxY = typeof opts?.max === "number" ? opts.max : opts?.max?.y ?? 8;
|
|
7169
|
-
const nextX = Math.min(maxX, Math.max(minX, view.scale.x * fx));
|
|
7170
|
-
const nextY = Math.min(maxY, Math.max(minY, view.scale.y * fy));
|
|
7171
|
-
const worldX = anchor.x / view.scale.x + view.x;
|
|
7172
|
-
const worldY = anchor.y / view.scale.y + view.y;
|
|
7173
|
-
return {
|
|
7174
|
-
scale: { x: nextX, y: nextY },
|
|
7175
|
-
x: worldX - anchor.x / nextX,
|
|
7176
|
-
y: worldY - anchor.y / nextY
|
|
7177
|
-
};
|
|
7178
|
-
}
|
|
7179
|
-
function clampView(view, bounds, canvas) {
|
|
7180
|
-
const visW = canvas.width / view.scale.x;
|
|
7181
|
-
const visH = canvas.height / view.scale.y;
|
|
7182
|
-
let x;
|
|
7183
|
-
if (visW >= bounds.width) {
|
|
7184
|
-
x = bounds.x + (bounds.width - visW) / 2;
|
|
7185
|
-
} else {
|
|
7186
|
-
const minX = bounds.x;
|
|
7187
|
-
const maxX = bounds.x + bounds.width - visW;
|
|
7188
|
-
x = view.x < minX ? minX : view.x > maxX ? maxX : view.x;
|
|
7189
|
-
}
|
|
7190
|
-
let y;
|
|
7191
|
-
if (visH >= bounds.height) {
|
|
7192
|
-
y = bounds.y + (bounds.height - visH) / 2;
|
|
7193
|
-
} else {
|
|
7194
|
-
const minY = bounds.y;
|
|
7195
|
-
const maxY = bounds.y + bounds.height - visH;
|
|
7196
|
-
y = view.y < minY ? minY : view.y > maxY ? maxY : view.y;
|
|
7197
|
-
}
|
|
7198
|
-
return x === view.x && y === view.y ? view : { x, y, scale: view.scale };
|
|
7199
|
-
}
|
|
7200
|
-
function isEditableTarget2(target) {
|
|
7201
|
-
if (!target || !(target instanceof Element)) return false;
|
|
7202
|
-
const tag = target.tagName;
|
|
7203
|
-
if (tag === "INPUT" || tag === "TEXTAREA") return true;
|
|
7204
|
-
if (target.isContentEditable) return true;
|
|
7205
|
-
const ce = target.getAttribute("contenteditable");
|
|
7206
|
-
return ce === "true" || ce === "";
|
|
7207
|
-
}
|
|
7208
|
-
var DepRegistryContext = createContext(null);
|
|
7209
|
-
function DepRegistryProvider({ children }) {
|
|
7210
|
-
const sourcesRef = useRef(/* @__PURE__ */ new Map());
|
|
7211
|
-
const registry22 = useMemo(() => ({
|
|
7212
|
-
register: (name, source) => {
|
|
7213
|
-
sourcesRef.current.set(name, source);
|
|
7214
|
-
return () => {
|
|
7215
|
-
sourcesRef.current.delete(name);
|
|
7216
|
-
};
|
|
7217
|
-
},
|
|
7218
|
-
get: (name) => sourcesRef.current.get(name)?.()
|
|
7219
|
-
}), []);
|
|
7220
|
-
return /* @__PURE__ */ jsx(DepRegistryContext.Provider, { value: registry22, children });
|
|
7221
|
-
}
|
|
7222
|
-
function useDepRegistry() {
|
|
7223
|
-
const r = useContext(DepRegistryContext);
|
|
7224
|
-
if (r === null) {
|
|
7225
|
-
throw new Error("useDepRegistry: no DepRegistryProvider in scope. Wrap your tree with <DepRegistryProvider> (typically inside <SceneCanvas>).");
|
|
7226
|
-
}
|
|
7227
|
-
return r;
|
|
7228
|
-
}
|
|
7229
|
-
function useOptionalDepRegistry() {
|
|
7230
|
-
return useContext(DepRegistryContext);
|
|
7231
|
-
}
|
|
7232
|
-
function useDepSource(name, source) {
|
|
7233
|
-
const registry22 = useDepRegistry();
|
|
7234
|
-
const sourceRef = useRef(source);
|
|
7235
|
-
sourceRef.current = source;
|
|
7236
|
-
useEffect(() => {
|
|
7237
|
-
return registry22.register(name, () => sourceRef.current());
|
|
7238
|
-
}, [name, registry22]);
|
|
7239
|
-
}
|
|
7240
|
-
var DEV = (() => {
|
|
7241
|
-
try {
|
|
7242
|
-
return Boolean(import.meta.env?.DEV);
|
|
7243
|
-
} catch {
|
|
7244
|
-
return false;
|
|
7245
|
-
}
|
|
7246
|
-
})();
|
|
7247
|
-
function buildDepsFromRequires(action, depRegistry) {
|
|
7248
|
-
const requires = action.requires ?? [];
|
|
7249
|
-
const entries = requires.map((name) => [name, depRegistry.get(name)]);
|
|
7250
|
-
const deps = Object.fromEntries(entries);
|
|
7251
|
-
if (DEV) {
|
|
7252
|
-
const declared = new Set(requires);
|
|
7253
|
-
return new Proxy(deps, {
|
|
7254
|
-
get(target, prop, receiver) {
|
|
7255
|
-
if (typeof prop === "string" && !declared.has(prop) && prop !== "then") {
|
|
7256
|
-
const message = `[weasel:dispatcher] action "${action.id}" read deps.${prop} but did not declare it in \`requires\`. Add \`requires: [...'${prop}']\` to the descriptor or the dep will be undefined at runtime.`;
|
|
7257
|
-
console.warn(message);
|
|
7258
|
-
throw new Error(message);
|
|
7259
|
-
}
|
|
7260
|
-
return Reflect.get(target, prop, receiver);
|
|
7261
|
-
}
|
|
7262
|
-
});
|
|
7263
|
-
}
|
|
7264
|
-
return deps;
|
|
7265
|
-
}
|
|
7266
|
-
var ActionDisabledReason = {
|
|
7267
|
-
SelectionRequired: "selection-required",
|
|
7268
|
-
NotApplicable: "not-applicable",
|
|
7269
|
-
/** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
|
|
7270
|
-
PredicateThrew: "predicate-threw"
|
|
7271
|
-
};
|
|
7272
|
-
var enabledSlowWarned = /* @__PURE__ */ new Set();
|
|
7273
|
-
var enabledThrewWarned = /* @__PURE__ */ new Set();
|
|
7274
|
-
var ENABLED_BUDGET_MS = 4;
|
|
7275
|
-
function evaluateEnabled(action, deps) {
|
|
7276
|
-
if (!action.enabled) return { enabled: true };
|
|
7277
|
-
const isDev3 = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
7278
|
-
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
7279
|
-
const start = isDev3 ? now() : 0;
|
|
7280
|
-
try {
|
|
7281
|
-
const result = action.enabled(deps);
|
|
7282
|
-
if (isDev3) {
|
|
7283
|
-
const elapsed = now() - start;
|
|
7284
|
-
if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
|
|
7285
|
-
enabledSlowWarned.add(action.id);
|
|
7286
|
-
console.warn(
|
|
7287
|
-
`weasel actions: enabled() for action "${action.id}" took ${elapsed.toFixed(2)}ms (budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast \u2014 see Action.enabled JSDoc.`
|
|
7288
|
-
);
|
|
7289
|
-
}
|
|
7290
|
-
}
|
|
7291
|
-
if (result === true) return { enabled: true };
|
|
7292
|
-
return { enabled: false, reason: result };
|
|
7293
|
-
} catch (e) {
|
|
7294
|
-
if (isDev3 && !enabledThrewWarned.has(action.id)) {
|
|
7295
|
-
enabledThrewWarned.add(action.id);
|
|
7296
|
-
console.warn(
|
|
7297
|
-
`weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
|
|
7298
|
-
e
|
|
7299
|
-
);
|
|
7300
|
-
}
|
|
7301
|
-
return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
|
|
7302
|
-
}
|
|
7303
|
-
}
|
|
7304
|
-
function validateActionId(id) {
|
|
7305
|
-
if (id.length === 0) {
|
|
7306
|
-
throw new Error(`weasel: action id may not be empty`);
|
|
7307
|
-
}
|
|
7308
|
-
if (RESERVED_ID_PREFIXES.has(id[0])) {
|
|
7309
|
-
throw new Error(
|
|
7310
|
-
`weasel: action id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
|
|
7311
|
-
);
|
|
7312
|
-
}
|
|
7313
|
-
if (RESERVED_ID_NAMES.has(id)) {
|
|
7314
|
-
throw new Error(
|
|
7315
|
-
`weasel: action id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
|
|
7316
|
-
);
|
|
7317
|
-
}
|
|
7318
|
-
}
|
|
7319
|
-
function validateActionDefaultBinding(action) {
|
|
7320
|
-
const gs = action.defaultBinding;
|
|
7321
|
-
if (!gs) return;
|
|
7322
|
-
const entries = Array.isArray(gs) ? gs : [gs];
|
|
7323
|
-
for (const entry of entries) {
|
|
7324
|
-
const spec = "kind" in entry ? entry : entry.spec;
|
|
7325
|
-
const phase = spec.phase;
|
|
7326
|
-
if (phase === void 0) continue;
|
|
7327
|
-
const atoms = Array.isArray(phase) ? phase : [{ channel: "&", phase }];
|
|
7328
|
-
for (const atom of atoms) {
|
|
7329
|
-
if (atom.channel === "&") {
|
|
7330
|
-
throw new Error(
|
|
7331
|
-
`weasel: action "${action.id}" defaultBinding uses '&' channel which has no owning tool to resolve to. Use a named tool channel (e.g. '[rect:engaged]') or '*' instead, or move this binding to the tool's own Tool.bindings.`
|
|
7332
|
-
);
|
|
7333
|
-
}
|
|
7334
|
-
}
|
|
7335
|
-
}
|
|
7336
|
-
}
|
|
7337
|
-
var ActionsContext = createContext(null);
|
|
7338
|
-
function ActionsProvider({ children }) {
|
|
7339
|
-
const actionsRef = useRef(/* @__PURE__ */ new Map());
|
|
7340
|
-
const versionRef = useRef(0);
|
|
7341
|
-
const cachedRef = useRef([]);
|
|
7342
|
-
const cachedVerRef = useRef(-1);
|
|
7343
|
-
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
7344
|
-
const depReg = useOptionalDepRegistry();
|
|
7345
|
-
const depRegRef = useRef(depReg);
|
|
7346
|
-
depRegRef.current = depReg;
|
|
7347
|
-
const wiredDepRegRef = useRef(null);
|
|
7348
|
-
const dispatcherRef = useRef(null);
|
|
7349
|
-
const registry22 = useMemo(() => {
|
|
7350
|
-
const snapshot = () => {
|
|
7351
|
-
const v = versionRef.current;
|
|
7352
|
-
if (cachedVerRef.current === v) return cachedRef.current;
|
|
7353
|
-
const out = Object.freeze(Array.from(actionsRef.current.values()));
|
|
7354
|
-
cachedRef.current = out;
|
|
7355
|
-
cachedVerRef.current = v;
|
|
7356
|
-
return out;
|
|
7357
|
-
};
|
|
7358
|
-
const notify2 = () => {
|
|
7359
|
-
for (const l of listenersRef.current) {
|
|
7360
|
-
try {
|
|
7361
|
-
l();
|
|
7362
|
-
} catch (err) {
|
|
7363
|
-
console.error("weasel ActionsRegistry: subscriber threw", err);
|
|
7364
|
-
}
|
|
7365
|
-
}
|
|
7366
|
-
};
|
|
7367
|
-
return {
|
|
7368
|
-
register: (action) => {
|
|
7369
|
-
validateActionId(action.id);
|
|
7370
|
-
validateActionDefaultBinding(action);
|
|
7371
|
-
actionsRef.current.set(action.id, action);
|
|
7372
|
-
versionRef.current++;
|
|
7373
|
-
notify2();
|
|
7374
|
-
return () => {
|
|
7375
|
-
const cur = actionsRef.current.get(action.id);
|
|
7376
|
-
if (cur === action) {
|
|
7377
|
-
actionsRef.current.delete(action.id);
|
|
7378
|
-
versionRef.current++;
|
|
7379
|
-
notify2();
|
|
7380
|
-
}
|
|
7381
|
-
};
|
|
7382
|
-
},
|
|
7383
|
-
unregister: (id) => {
|
|
7384
|
-
if (actionsRef.current.delete(id)) {
|
|
7385
|
-
versionRef.current++;
|
|
7386
|
-
notify2();
|
|
7387
|
-
}
|
|
7388
|
-
},
|
|
7389
|
-
list: () => snapshot(),
|
|
7390
|
-
trigger: (id, params) => {
|
|
7391
|
-
const a = actionsRef.current.get(id);
|
|
7392
|
-
if (!a) return false;
|
|
7393
|
-
try {
|
|
7394
|
-
if (a.invoker && a.invoker.timing === "immediate") {
|
|
7395
|
-
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
7396
|
-
const deps = !r ? {} : a.requires ? buildDepsFromRequires(a, r) : {
|
|
7397
|
-
selection: r.get("selection"),
|
|
7398
|
-
scene: r.get("scene"),
|
|
7399
|
-
history: r.get("history"),
|
|
7400
|
-
view: r.get("view"),
|
|
7401
|
-
pointer: r.get("pointer"),
|
|
7402
|
-
activeTool: r.get("activeTool"),
|
|
7403
|
-
booleansAdapter: r.get("booleansAdapter")
|
|
7404
|
-
};
|
|
7405
|
-
a.invoker.run(deps, params);
|
|
7406
|
-
}
|
|
7407
|
-
} catch (err) {
|
|
7408
|
-
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
7409
|
-
}
|
|
7410
|
-
return true;
|
|
7411
|
-
},
|
|
7412
|
-
subscribe: (listener) => {
|
|
7413
|
-
listenersRef.current.add(listener);
|
|
7414
|
-
return () => {
|
|
7415
|
-
listenersRef.current.delete(listener);
|
|
7416
|
-
};
|
|
7417
|
-
},
|
|
7418
|
-
setDispatcher: (d) => {
|
|
7419
|
-
dispatcherRef.current = d;
|
|
7420
|
-
},
|
|
7421
|
-
setDepRegistry: (r) => {
|
|
7422
|
-
wiredDepRegRef.current = r;
|
|
7423
|
-
},
|
|
7424
|
-
begin: (id, params) => {
|
|
7425
|
-
const disp = dispatcherRef.current;
|
|
7426
|
-
if (!disp) return null;
|
|
7427
|
-
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
7428
|
-
const deps = r ? {
|
|
7429
|
-
selection: r.get("selection"),
|
|
7430
|
-
scene: r.get("scene"),
|
|
7431
|
-
history: r.get("history"),
|
|
7432
|
-
view: r.get("view"),
|
|
7433
|
-
pointer: r.get("pointer"),
|
|
7434
|
-
activeTool: r.get("activeTool"),
|
|
7435
|
-
booleansAdapter: r.get("booleansAdapter")
|
|
7436
|
-
} : {};
|
|
7437
|
-
return disp.beginUiOngoing(id, deps, params);
|
|
7438
|
-
}
|
|
7947
|
+
});
|
|
7948
|
+
var COARSE_QUERY = "(pointer: coarse)";
|
|
7949
|
+
var HOVER_QUERY = "(hover: hover)";
|
|
7950
|
+
function hasMatchMedia() {
|
|
7951
|
+
return typeof window !== "undefined" && typeof window.matchMedia === "function";
|
|
7952
|
+
}
|
|
7953
|
+
function detectOnce() {
|
|
7954
|
+
const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
|
|
7955
|
+
if (!hasMatchMedia()) {
|
|
7956
|
+
return {
|
|
7957
|
+
coarsePointer: DEFAULT_DEVICE_PROFILE.coarsePointer,
|
|
7958
|
+
canHover: DEFAULT_DEVICE_PROFILE.canHover,
|
|
7959
|
+
dpr
|
|
7960
|
+
};
|
|
7961
|
+
}
|
|
7962
|
+
return {
|
|
7963
|
+
coarsePointer: window.matchMedia(COARSE_QUERY).matches,
|
|
7964
|
+
canHover: window.matchMedia(HOVER_QUERY).matches,
|
|
7965
|
+
dpr
|
|
7966
|
+
};
|
|
7967
|
+
}
|
|
7968
|
+
function useDetectedFacts() {
|
|
7969
|
+
const [facts, setFacts] = useState(detectOnce);
|
|
7970
|
+
useEffect(() => {
|
|
7971
|
+
if (!hasMatchMedia()) return;
|
|
7972
|
+
const coarseMq = window.matchMedia(COARSE_QUERY);
|
|
7973
|
+
const hoverMq = window.matchMedia(HOVER_QUERY);
|
|
7974
|
+
const onCoarse = (e) => setFacts((f) => ({ ...f, coarsePointer: e.matches }));
|
|
7975
|
+
const onHover = (e) => setFacts((f) => ({ ...f, canHover: e.matches }));
|
|
7976
|
+
coarseMq.addEventListener("change", onCoarse);
|
|
7977
|
+
hoverMq.addEventListener("change", onHover);
|
|
7978
|
+
let disposed = false;
|
|
7979
|
+
let densityMq = null;
|
|
7980
|
+
const onDensity = () => {
|
|
7981
|
+
if (disposed) return;
|
|
7982
|
+
const next = window.devicePixelRatio || 1;
|
|
7983
|
+
setFacts((f) => f.dpr === next ? f : { ...f, dpr: next });
|
|
7984
|
+
arm();
|
|
7985
|
+
};
|
|
7986
|
+
function arm() {
|
|
7987
|
+
if (disposed) return;
|
|
7988
|
+
densityMq?.removeEventListener("change", onDensity);
|
|
7989
|
+
densityMq = window.matchMedia(
|
|
7990
|
+
`(resolution: ${window.devicePixelRatio || 1}dppx)`
|
|
7991
|
+
);
|
|
7992
|
+
densityMq.addEventListener("change", onDensity);
|
|
7993
|
+
}
|
|
7994
|
+
arm();
|
|
7995
|
+
setFacts({
|
|
7996
|
+
coarsePointer: coarseMq.matches,
|
|
7997
|
+
canHover: hoverMq.matches,
|
|
7998
|
+
dpr: window.devicePixelRatio || 1
|
|
7999
|
+
});
|
|
8000
|
+
return () => {
|
|
8001
|
+
disposed = true;
|
|
8002
|
+
coarseMq.removeEventListener("change", onCoarse);
|
|
8003
|
+
hoverMq.removeEventListener("change", onHover);
|
|
8004
|
+
densityMq?.removeEventListener("change", onDensity);
|
|
7439
8005
|
};
|
|
7440
8006
|
}, []);
|
|
7441
|
-
return
|
|
8007
|
+
return facts;
|
|
7442
8008
|
}
|
|
7443
|
-
|
|
7444
|
-
|
|
8009
|
+
var DeviceProfileContext = createContext(null);
|
|
8010
|
+
function useDeviceProfile(overrides) {
|
|
8011
|
+
const provided = useContext(DeviceProfileContext);
|
|
8012
|
+
const detected = useDetectedFacts();
|
|
8013
|
+
const base = provided ?? detected;
|
|
8014
|
+
const { coarsePointer: coarsePointer2, canHover: canHover2, dpr, targetScale } = overrides ?? {};
|
|
8015
|
+
return useMemo(
|
|
8016
|
+
() => resolveDeviceProfile(base, {
|
|
8017
|
+
...coarsePointer2 !== void 0 ? { coarsePointer: coarsePointer2 } : {},
|
|
8018
|
+
...canHover2 !== void 0 ? { canHover: canHover2 } : {},
|
|
8019
|
+
...dpr !== void 0 ? { dpr } : {},
|
|
8020
|
+
...targetScale !== void 0 ? { targetScale } : {}
|
|
8021
|
+
}),
|
|
8022
|
+
[base.coarsePointer, base.canHover, base.dpr, coarsePointer2, canHover2, dpr, targetScale]
|
|
8023
|
+
);
|
|
8024
|
+
}
|
|
8025
|
+
function DeviceProfileProvider({ value, children }) {
|
|
8026
|
+
const profile = useDeviceProfile(value);
|
|
8027
|
+
return createElement(DeviceProfileContext.Provider, { value: profile }, children);
|
|
8028
|
+
}
|
|
8029
|
+
var HANDLE_BASE_PX = 8;
|
|
8030
|
+
var ANCHOR_HIT_BASE_PX = 8;
|
|
8031
|
+
var ROTATION_HANDLE_BASE_PX = 24;
|
|
8032
|
+
function zoomAt(view, anchor, factor, opts) {
|
|
8033
|
+
const fx = typeof factor === "number" ? factor : factor.x;
|
|
8034
|
+
const fy = typeof factor === "number" ? factor : factor.y;
|
|
8035
|
+
const minX = typeof opts?.min === "number" ? opts.min : opts?.min?.x ?? 0.1;
|
|
8036
|
+
const minY = typeof opts?.min === "number" ? opts.min : opts?.min?.y ?? 0.1;
|
|
8037
|
+
const maxX = typeof opts?.max === "number" ? opts.max : opts?.max?.x ?? 8;
|
|
8038
|
+
const maxY = typeof opts?.max === "number" ? opts.max : opts?.max?.y ?? 8;
|
|
8039
|
+
const nextX = Math.min(maxX, Math.max(minX, view.scale.x * fx));
|
|
8040
|
+
const nextY = Math.min(maxY, Math.max(minY, view.scale.y * fy));
|
|
8041
|
+
const worldX = anchor.x / view.scale.x + view.x;
|
|
8042
|
+
const worldY = anchor.y / view.scale.y + view.y;
|
|
8043
|
+
return {
|
|
8044
|
+
scale: { x: nextX, y: nextY },
|
|
8045
|
+
x: worldX - anchor.x / nextX,
|
|
8046
|
+
y: worldY - anchor.y / nextY
|
|
8047
|
+
};
|
|
8048
|
+
}
|
|
8049
|
+
function clampView(view, bounds, canvas) {
|
|
8050
|
+
const visW = canvas.width / view.scale.x;
|
|
8051
|
+
const visH = canvas.height / view.scale.y;
|
|
8052
|
+
let x;
|
|
8053
|
+
if (visW >= bounds.width) {
|
|
8054
|
+
x = bounds.x + (bounds.width - visW) / 2;
|
|
8055
|
+
} else {
|
|
8056
|
+
const minX = bounds.x;
|
|
8057
|
+
const maxX = bounds.x + bounds.width - visW;
|
|
8058
|
+
x = view.x < minX ? minX : view.x > maxX ? maxX : view.x;
|
|
8059
|
+
}
|
|
8060
|
+
let y;
|
|
8061
|
+
if (visH >= bounds.height) {
|
|
8062
|
+
y = bounds.y + (bounds.height - visH) / 2;
|
|
8063
|
+
} else {
|
|
8064
|
+
const minY = bounds.y;
|
|
8065
|
+
const maxY = bounds.y + bounds.height - visH;
|
|
8066
|
+
y = view.y < minY ? minY : view.y > maxY ? maxY : view.y;
|
|
8067
|
+
}
|
|
8068
|
+
return x === view.x && y === view.y ? view : { x, y, scale: view.scale };
|
|
8069
|
+
}
|
|
8070
|
+
function isEditableTarget2(target) {
|
|
8071
|
+
if (!target || !(target instanceof Element)) return false;
|
|
8072
|
+
const tag = target.tagName;
|
|
8073
|
+
if (tag === "INPUT" || tag === "TEXTAREA") return true;
|
|
8074
|
+
if (target.isContentEditable) return true;
|
|
8075
|
+
const ce = target.getAttribute("contenteditable");
|
|
8076
|
+
return ce === "true" || ce === "";
|
|
7445
8077
|
}
|
|
7446
8078
|
function resolveParams(params) {
|
|
7447
8079
|
if (params === void 0) return void 0;
|
|
@@ -7474,12 +8106,7 @@ function moveGestureAdapter(scene) {
|
|
|
7474
8106
|
return {
|
|
7475
8107
|
getNode: (id) => scene.get(asNodeId(id)),
|
|
7476
8108
|
getNodes: () => {
|
|
7477
|
-
|
|
7478
|
-
for (const id of scene.renderOrder()) {
|
|
7479
|
-
const n = scene.get(id);
|
|
7480
|
-
if (n) out.push(n);
|
|
7481
|
-
}
|
|
7482
|
-
return out;
|
|
8109
|
+
return [...scene.renderOrderNodes()];
|
|
7483
8110
|
},
|
|
7484
8111
|
getPose: (id) => scene.get(asNodeId(id)).pose,
|
|
7485
8112
|
getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,
|
|
@@ -7977,6 +8604,60 @@ var moveAction = {
|
|
|
7977
8604
|
*/
|
|
7978
8605
|
enabled: () => true
|
|
7979
8606
|
};
|
|
8607
|
+
function poseRotationOf(pose) {
|
|
8608
|
+
const p = pose;
|
|
8609
|
+
if (p.rotation && p.x != null && p.y != null && p.width != null && p.height != null) {
|
|
8610
|
+
return { cx: p.x + p.width / 2, cy: p.y + p.height / 2, rotation: p.rotation };
|
|
8611
|
+
}
|
|
8612
|
+
return null;
|
|
8613
|
+
}
|
|
8614
|
+
function rotatePathAround(path, cx, cy, rotation) {
|
|
8615
|
+
if (path.kind === "rect") {
|
|
8616
|
+
return rotateRectCorners(path.x, path.y, path.width, path.height, cx, cy, rotation);
|
|
8617
|
+
}
|
|
8618
|
+
return rotatePolygon(path, cx, cy, rotation);
|
|
8619
|
+
}
|
|
8620
|
+
function rotatePolygon(path, cx, cy, rotation) {
|
|
8621
|
+
const cos = Math.cos(rotation);
|
|
8622
|
+
const sin = Math.sin(rotation);
|
|
8623
|
+
const { commands, coords } = path;
|
|
8624
|
+
const next = new Float32Array(coords.length);
|
|
8625
|
+
let ci = 0;
|
|
8626
|
+
for (let i = 0; i < commands.length; i++) {
|
|
8627
|
+
const len = COORD_COUNT3[commands[i]];
|
|
8628
|
+
for (let k = 0; k < len; k += 2) {
|
|
8629
|
+
const dx = coords[ci + k] - cx;
|
|
8630
|
+
const dy = coords[ci + k + 1] - cy;
|
|
8631
|
+
next[ci + k] = cx + dx * cos - dy * sin;
|
|
8632
|
+
next[ci + k + 1] = cy + dx * sin + dy * cos;
|
|
8633
|
+
}
|
|
8634
|
+
ci += len;
|
|
8635
|
+
}
|
|
8636
|
+
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
8637
|
+
}
|
|
8638
|
+
function rotateRectCorners(x, y, w, h, cx, cy, rotation) {
|
|
8639
|
+
const cos = Math.cos(rotation);
|
|
8640
|
+
const sin = Math.sin(rotation);
|
|
8641
|
+
const rotate2 = (px, py) => {
|
|
8642
|
+
const dx = px - cx;
|
|
8643
|
+
const dy = py - cy;
|
|
8644
|
+
return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
|
|
8645
|
+
};
|
|
8646
|
+
const [x0, y0] = rotate2(x, y);
|
|
8647
|
+
const [x1, y1] = rotate2(x + w, y);
|
|
8648
|
+
const [x2, y2] = rotate2(x + w, y + h);
|
|
8649
|
+
const [x3, y3] = rotate2(x, y + h);
|
|
8650
|
+
const commands = new Uint8Array([PATH_M2, PATH_L2, PATH_L2, PATH_L2, PATH_Z2]);
|
|
8651
|
+
const coords = new Float32Array([x0, y0, x1, y1, x2, y2, x3, y3]);
|
|
8652
|
+
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
8653
|
+
}
|
|
8654
|
+
var COORD_COUNT3 = {
|
|
8655
|
+
[PATH_M2]: 2,
|
|
8656
|
+
[PATH_L2]: 2,
|
|
8657
|
+
[PATH_C2]: 6,
|
|
8658
|
+
[PATH_Q2]: 4,
|
|
8659
|
+
[PATH_Z2]: 0
|
|
8660
|
+
};
|
|
7980
8661
|
function aabbCenter(bounds) {
|
|
7981
8662
|
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
7982
8663
|
}
|
|
@@ -7991,12 +8672,7 @@ function defaultCommitAdapter(scene) {
|
|
|
7991
8672
|
return {
|
|
7992
8673
|
getNode: (id) => scene.get(asNodeId(id)),
|
|
7993
8674
|
getNodes: () => {
|
|
7994
|
-
|
|
7995
|
-
for (const id of scene.renderOrder()) {
|
|
7996
|
-
const n = scene.get(id);
|
|
7997
|
-
if (n) out.push(n);
|
|
7998
|
-
}
|
|
7999
|
-
return out;
|
|
8675
|
+
return [...scene.renderOrderNodes()];
|
|
8000
8676
|
},
|
|
8001
8677
|
getPose: (id) => scene.get(asNodeId(id)).pose,
|
|
8002
8678
|
getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,
|
|
@@ -8269,6 +8945,13 @@ var resizeAction = {
|
|
|
8269
8945
|
const computePose = (id) => {
|
|
8270
8946
|
const sp = scratch.startPoses.get(id);
|
|
8271
8947
|
if (sp === void 0) return void 0;
|
|
8948
|
+
if (isGroupPath && poseRotationOf(sp) !== null) {
|
|
8949
|
+
return remapRotatedLeaf(
|
|
8950
|
+
sp,
|
|
8951
|
+
scratch.originBounds,
|
|
8952
|
+
proposedBounds
|
|
8953
|
+
);
|
|
8954
|
+
}
|
|
8272
8955
|
return scratch.geometry.remapBounds(sp, scratch.originBounds, proposedBounds);
|
|
8273
8956
|
};
|
|
8274
8957
|
scratch.previews.clear();
|
|
@@ -8889,9 +9572,9 @@ var clearSelectionAction = {
|
|
|
8889
9572
|
return true;
|
|
8890
9573
|
}
|
|
8891
9574
|
};
|
|
8892
|
-
var
|
|
9575
|
+
var counter2 = 0;
|
|
8893
9576
|
function freshNodeId() {
|
|
8894
|
-
return asNodeId(`n${(
|
|
9577
|
+
return asNodeId(`n${(counter2++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
|
|
8895
9578
|
}
|
|
8896
9579
|
function translatePose(pose, dx, dy) {
|
|
8897
9580
|
const p = pose;
|
|
@@ -9108,7 +9791,13 @@ function makeViewportZoomAction(opts = {}) {
|
|
|
9108
9791
|
};
|
|
9109
9792
|
}
|
|
9110
9793
|
makeViewportZoomAction();
|
|
9111
|
-
|
|
9794
|
+
function bodyPredicate(fn) {
|
|
9795
|
+
return Object.assign(fn, { readsAffordance: false });
|
|
9796
|
+
}
|
|
9797
|
+
var isBody = bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body" || bodyTarget === "unselected-body");
|
|
9798
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body");
|
|
9799
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "unselected-body");
|
|
9800
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "empty");
|
|
9112
9801
|
function kindOf(target) {
|
|
9113
9802
|
if (typeof target !== "object" || target === null) return void 0;
|
|
9114
9803
|
const k = target.kind;
|
|
@@ -9147,17 +9836,17 @@ function splitCubicAtT(p0, p1, p2, p3, t) {
|
|
|
9147
9836
|
function fitCubicThroughDeletion(prev, next) {
|
|
9148
9837
|
const dx = next.x - prev.x;
|
|
9149
9838
|
const dy = next.y - prev.y;
|
|
9150
|
-
const
|
|
9839
|
+
const dist3 = Math.sqrt(dx * dx + dy * dy);
|
|
9151
9840
|
const c1DirX = prev.outHandle ? prev.outHandle.x - prev.x : dx;
|
|
9152
9841
|
const c1DirY = prev.outHandle ? prev.outHandle.y - prev.y : dy;
|
|
9153
|
-
const c1 = controlOnSide(prev, c1DirX, c1DirY,
|
|
9842
|
+
const c1 = controlOnSide(prev, c1DirX, c1DirY, dist3, 1 / 3);
|
|
9154
9843
|
const c2DirX = next.inHandle ? next.inHandle.x - next.x : -dx;
|
|
9155
9844
|
const c2DirY = next.inHandle ? next.inHandle.y - next.y : -dy;
|
|
9156
|
-
const c2 = controlOnSide(next, c2DirX, c2DirY,
|
|
9845
|
+
const c2 = controlOnSide(next, c2DirX, c2DirY, dist3, 1 / 3);
|
|
9157
9846
|
return { c1, c2 };
|
|
9158
9847
|
}
|
|
9159
|
-
function controlOnSide(anchor, dirX, dirY,
|
|
9160
|
-
const targetDist =
|
|
9848
|
+
function controlOnSide(anchor, dirX, dirY, dist3, fraction) {
|
|
9849
|
+
const targetDist = dist3 * fraction;
|
|
9161
9850
|
const len = Math.hypot(dirX, dirY) || 1;
|
|
9162
9851
|
const ux = dirX / len, uy = dirY / len;
|
|
9163
9852
|
return { x: anchor.x + ux * targetDist, y: anchor.y + uy * targetDist };
|
|
@@ -9414,60 +10103,6 @@ function anchorsInRect(set, rect) {
|
|
|
9414
10103
|
}
|
|
9415
10104
|
return hits;
|
|
9416
10105
|
}
|
|
9417
|
-
function poseRotationOf(pose) {
|
|
9418
|
-
const p = pose;
|
|
9419
|
-
if (p.rotation && p.x != null && p.y != null && p.width != null && p.height != null) {
|
|
9420
|
-
return { cx: p.x + p.width / 2, cy: p.y + p.height / 2, rotation: p.rotation };
|
|
9421
|
-
}
|
|
9422
|
-
return null;
|
|
9423
|
-
}
|
|
9424
|
-
function rotatePathAround(path, cx, cy, rotation) {
|
|
9425
|
-
if (path.kind === "rect") {
|
|
9426
|
-
return rotateRectCorners(path.x, path.y, path.width, path.height, cx, cy, rotation);
|
|
9427
|
-
}
|
|
9428
|
-
return rotatePolygon(path, cx, cy, rotation);
|
|
9429
|
-
}
|
|
9430
|
-
function rotatePolygon(path, cx, cy, rotation) {
|
|
9431
|
-
const cos = Math.cos(rotation);
|
|
9432
|
-
const sin = Math.sin(rotation);
|
|
9433
|
-
const { commands, coords } = path;
|
|
9434
|
-
const next = new Float32Array(coords.length);
|
|
9435
|
-
let ci = 0;
|
|
9436
|
-
for (let i = 0; i < commands.length; i++) {
|
|
9437
|
-
const len = COORD_COUNT3[commands[i]];
|
|
9438
|
-
for (let k = 0; k < len; k += 2) {
|
|
9439
|
-
const dx = coords[ci + k] - cx;
|
|
9440
|
-
const dy = coords[ci + k + 1] - cy;
|
|
9441
|
-
next[ci + k] = cx + dx * cos - dy * sin;
|
|
9442
|
-
next[ci + k + 1] = cy + dx * sin + dy * cos;
|
|
9443
|
-
}
|
|
9444
|
-
ci += len;
|
|
9445
|
-
}
|
|
9446
|
-
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
9447
|
-
}
|
|
9448
|
-
function rotateRectCorners(x, y, w, h, cx, cy, rotation) {
|
|
9449
|
-
const cos = Math.cos(rotation);
|
|
9450
|
-
const sin = Math.sin(rotation);
|
|
9451
|
-
const rotate2 = (px, py) => {
|
|
9452
|
-
const dx = px - cx;
|
|
9453
|
-
const dy = py - cy;
|
|
9454
|
-
return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
|
|
9455
|
-
};
|
|
9456
|
-
const [x0, y0] = rotate2(x, y);
|
|
9457
|
-
const [x1, y1] = rotate2(x + w, y);
|
|
9458
|
-
const [x2, y2] = rotate2(x + w, y + h);
|
|
9459
|
-
const [x3, y3] = rotate2(x, y + h);
|
|
9460
|
-
const commands = new Uint8Array([PATH_M2, PATH_L2, PATH_L2, PATH_L2, PATH_Z2]);
|
|
9461
|
-
const coords = new Float32Array([x0, y0, x1, y1, x2, y2, x3, y3]);
|
|
9462
|
-
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
9463
|
-
}
|
|
9464
|
-
var COORD_COUNT3 = {
|
|
9465
|
-
[PATH_M2]: 2,
|
|
9466
|
-
[PATH_L2]: 2,
|
|
9467
|
-
[PATH_C2]: 6,
|
|
9468
|
-
[PATH_Q2]: 4,
|
|
9469
|
-
[PATH_Z2]: 0
|
|
9470
|
-
};
|
|
9471
10106
|
function pathInPoseFrame(path, pose) {
|
|
9472
10107
|
if (path.kind === "rect") {
|
|
9473
10108
|
if (path.x === pose.x && path.y === pose.y && path.width === pose.width && path.height === pose.height) return path;
|
|
@@ -11020,7 +11655,7 @@ var LABEL = {
|
|
|
11020
11655
|
divide: "Divide",
|
|
11021
11656
|
crop: "Crop"
|
|
11022
11657
|
};
|
|
11023
|
-
function
|
|
11658
|
+
function isEmpty2(p) {
|
|
11024
11659
|
return p.commands.length === 0;
|
|
11025
11660
|
}
|
|
11026
11661
|
function applyBooleanOp(adapter, op) {
|
|
@@ -11058,7 +11693,7 @@ function applyBooleanOp(adapter, op) {
|
|
|
11058
11693
|
break;
|
|
11059
11694
|
}
|
|
11060
11695
|
}
|
|
11061
|
-
results = results.filter((p) => !
|
|
11696
|
+
results = results.filter((p) => !isEmpty2(p));
|
|
11062
11697
|
if (results.length === 0) return { kind: "noop", reason: "empty-result" };
|
|
11063
11698
|
const topmostId = entries[entries.length - 1].id;
|
|
11064
11699
|
const topAnchor = adapter.getZOrder?.(topmostId);
|
|
@@ -11594,14 +12229,24 @@ var DEFAULT_PALETTE = [
|
|
|
11594
12229
|
"#7ab8d4",
|
|
11595
12230
|
"#d47a7a"
|
|
11596
12231
|
];
|
|
12232
|
+
function resolveFill(scratch, prev) {
|
|
12233
|
+
if (scratch.currentPaint) return scratch.currentPaint;
|
|
12234
|
+
const prevColor = typeof prev?.fill === "string" ? prev.fill : DEFAULT_FILL_COLOR;
|
|
12235
|
+
return mergeAlphaFromPrev(scratch.currentColor, prevColor);
|
|
12236
|
+
}
|
|
11597
12237
|
function refreshPreviews(scratch) {
|
|
11598
12238
|
scratch.previews.clear();
|
|
11599
12239
|
for (const id of scratch.ids) {
|
|
11600
12240
|
const prev = scratch.startData.get(id);
|
|
11601
|
-
|
|
11602
|
-
scratch.previews.set(id, { ...prev ?? {}, fill: next });
|
|
12241
|
+
scratch.previews.set(id, { ...prev ?? {}, fill: resolveFill(scratch, prev) });
|
|
11603
12242
|
}
|
|
11604
12243
|
}
|
|
12244
|
+
function readParams(params) {
|
|
12245
|
+
return {
|
|
12246
|
+
color: params?.color,
|
|
12247
|
+
paint: params?.paint
|
|
12248
|
+
};
|
|
12249
|
+
}
|
|
11605
12250
|
var setFillAction = {
|
|
11606
12251
|
id: "setFill",
|
|
11607
12252
|
label: "Set fill",
|
|
@@ -11615,10 +12260,11 @@ var setFillAction = {
|
|
|
11615
12260
|
const applyOps = ctx.deps.applyOps;
|
|
11616
12261
|
if (!selection || !scene) return {};
|
|
11617
12262
|
const ids = selection.get();
|
|
11618
|
-
if (ids.length === 0) return {};
|
|
11619
|
-
const
|
|
11620
|
-
const
|
|
11621
|
-
const
|
|
12263
|
+
if (ids.length === 0) return {};
|
|
12264
|
+
const fromCtx = readParams(ctx.params);
|
|
12265
|
+
const fromOpts = readParams(opts?.params);
|
|
12266
|
+
const initialPaint = fromCtx.paint ?? fromOpts.paint;
|
|
12267
|
+
const initialColor = fromCtx.color ?? fromOpts.color ?? DEFAULT_STROKE_COLOR;
|
|
11622
12268
|
const startData = /* @__PURE__ */ new Map();
|
|
11623
12269
|
for (const id of ids) {
|
|
11624
12270
|
const node = scene.get(id);
|
|
@@ -11629,15 +12275,20 @@ var setFillAction = {
|
|
|
11629
12275
|
scene,
|
|
11630
12276
|
startData,
|
|
11631
12277
|
currentColor: initialColor,
|
|
12278
|
+
currentPaint: initialPaint,
|
|
11632
12279
|
previews: /* @__PURE__ */ new Map(),
|
|
11633
12280
|
applyOps
|
|
11634
12281
|
};
|
|
11635
12282
|
refreshPreviews(scratch);
|
|
11636
12283
|
return {
|
|
11637
12284
|
onMove(moveCtx) {
|
|
11638
|
-
const
|
|
11639
|
-
if (
|
|
11640
|
-
scratch.
|
|
12285
|
+
const { color, paint } = readParams(moveCtx.params);
|
|
12286
|
+
if (color === void 0 && paint === void 0) return;
|
|
12287
|
+
if (paint !== void 0) scratch.currentPaint = paint;
|
|
12288
|
+
if (color !== void 0) {
|
|
12289
|
+
scratch.currentColor = color;
|
|
12290
|
+
if (paint === void 0) scratch.currentPaint = void 0;
|
|
12291
|
+
}
|
|
11641
12292
|
refreshPreviews(scratch);
|
|
11642
12293
|
},
|
|
11643
12294
|
onEnd(_endCtx, reason) {
|
|
@@ -11647,8 +12298,7 @@ var setFillAction = {
|
|
|
11647
12298
|
}
|
|
11648
12299
|
const ops = [];
|
|
11649
12300
|
for (const id of scratch.ids) {
|
|
11650
|
-
const
|
|
11651
|
-
const merged = mergeAlphaFromPrev(scratch.currentColor, prev?.fill ?? DEFAULT_FILL_COLOR);
|
|
12301
|
+
const merged = resolveFill(scratch, scratch.startData.get(id));
|
|
11652
12302
|
const nodeNow = scratch.scene.get(id);
|
|
11653
12303
|
if (!nodeNow) continue;
|
|
11654
12304
|
const from = { ...nodeNow.data };
|
|
@@ -12503,13 +13153,82 @@ function modsCount(mods) {
|
|
|
12503
13153
|
}
|
|
12504
13154
|
function targetRank(target) {
|
|
12505
13155
|
if (target === void 0) return 0;
|
|
12506
|
-
|
|
12507
|
-
if (
|
|
12508
|
-
|
|
12509
|
-
|
|
13156
|
+
const form = parseTargetSpec(target);
|
|
13157
|
+
if (form === null) return 1;
|
|
13158
|
+
switch (form.form) {
|
|
13159
|
+
case "kind":
|
|
13160
|
+
return form.requireSelected ? 3 : 2;
|
|
13161
|
+
case "affordance":
|
|
13162
|
+
return 2;
|
|
13163
|
+
case "body":
|
|
13164
|
+
return 1;
|
|
13165
|
+
case "predicate":
|
|
13166
|
+
return 1;
|
|
13167
|
+
default: {
|
|
13168
|
+
return 1;
|
|
13169
|
+
}
|
|
13170
|
+
}
|
|
13171
|
+
}
|
|
13172
|
+
function targetConsultsAffordance(specTarget) {
|
|
13173
|
+
if (specTarget === void 0) return false;
|
|
13174
|
+
const form = parseTargetSpec(specTarget);
|
|
13175
|
+
if (form === null) return false;
|
|
13176
|
+
switch (form.form) {
|
|
13177
|
+
case "predicate":
|
|
13178
|
+
return form.kindOf.readsAffordance !== false;
|
|
13179
|
+
case "affordance":
|
|
13180
|
+
return true;
|
|
13181
|
+
case "body":
|
|
13182
|
+
return false;
|
|
13183
|
+
case "kind":
|
|
13184
|
+
return false;
|
|
13185
|
+
default: {
|
|
13186
|
+
return false;
|
|
13187
|
+
}
|
|
13188
|
+
}
|
|
13189
|
+
}
|
|
13190
|
+
function specTargetOf(spec) {
|
|
13191
|
+
return "target" in spec ? spec.target : void 0;
|
|
13192
|
+
}
|
|
13193
|
+
function claimOf(e) {
|
|
13194
|
+
return "affordance" in e ? e.affordance : void 0;
|
|
13195
|
+
}
|
|
13196
|
+
function claimGestureOf(e) {
|
|
13197
|
+
switch (e.kind) {
|
|
13198
|
+
case "pointerdown":
|
|
13199
|
+
case "click":
|
|
13200
|
+
return "pointer";
|
|
13201
|
+
case "doubleclick":
|
|
13202
|
+
return "doubleClick";
|
|
13203
|
+
case "contextmenu":
|
|
13204
|
+
return "contextMenu";
|
|
13205
|
+
case "longpress":
|
|
13206
|
+
return "longPress";
|
|
13207
|
+
case "wheel":
|
|
13208
|
+
return "wheel";
|
|
13209
|
+
default:
|
|
13210
|
+
return null;
|
|
13211
|
+
}
|
|
13212
|
+
}
|
|
13213
|
+
function isExclusiveClaim(e) {
|
|
13214
|
+
const claim = claimOf(e);
|
|
13215
|
+
if (claim?.strength !== "exclusive") return false;
|
|
13216
|
+
const gesture = claimGestureOf(e);
|
|
13217
|
+
if (gesture === null) return false;
|
|
13218
|
+
return claim.claimedKinds === void 0 || claim.claimedKinds.includes(gesture);
|
|
13219
|
+
}
|
|
13220
|
+
var warnedDeadClaims = /* @__PURE__ */ new Set();
|
|
13221
|
+
function reportDeadClaim(owner, warn) {
|
|
13222
|
+
if (process.env.NODE_ENV === "production") return;
|
|
13223
|
+
const key = String(owner);
|
|
13224
|
+
if (warnedDeadClaims.has(key)) return;
|
|
13225
|
+
warnedDeadClaims.add(key);
|
|
13226
|
+
warn(
|
|
13227
|
+
`[weasel] exclusive claim by "${key}" matched no binding: no \`affordance:\` or \`kindOf\` target resolved against it, so the press was dropped.`
|
|
13228
|
+
);
|
|
12510
13229
|
}
|
|
12511
13230
|
function specificity(spec) {
|
|
12512
|
-
const t = targetRank(
|
|
13231
|
+
const t = targetRank(specTargetOf(spec));
|
|
12513
13232
|
const mods = "mods" in spec ? spec.mods : void 0;
|
|
12514
13233
|
const m = modsCount(mods);
|
|
12515
13234
|
const p = "phase" in spec && spec.phase !== void 0 ? 1 : 0;
|
|
@@ -12524,12 +13243,14 @@ function compareSpecificity(a, b) {
|
|
|
12524
13243
|
}
|
|
12525
13244
|
return 0;
|
|
12526
13245
|
}
|
|
12527
|
-
function matchSorted(e, bindings, isMac, engagedChannels) {
|
|
13246
|
+
function matchSorted(e, bindings, isMac, engagedChannels, warn = (m) => console.warn(m)) {
|
|
12528
13247
|
const out = [];
|
|
12529
13248
|
const engaged = engagedChannels ?? EMPTY_ENGAGED2;
|
|
13249
|
+
const exclusive = isExclusiveClaim(e);
|
|
13250
|
+
const pool = exclusive ? bindings.filter((sb) => targetConsultsAffordance(specTargetOf(sb.binding.spec))) : bindings;
|
|
12530
13251
|
for (const scope of SCOPE_PRIORITY) {
|
|
12531
13252
|
const scopeMatches = [];
|
|
12532
|
-
for (const sb of
|
|
13253
|
+
for (const sb of pool) {
|
|
12533
13254
|
if (sb.scope !== scope) continue;
|
|
12534
13255
|
const phaseCtx = { selfChannel: sb.ownerToolId, engagedChannels: engaged };
|
|
12535
13256
|
if (matchSpec(e, sb.binding.spec, isMac, phaseCtx)) {
|
|
@@ -12539,9 +13260,32 @@ function matchSorted(e, bindings, isMac, engagedChannels) {
|
|
|
12539
13260
|
scopeMatches.sort((a, b) => compareSpecificity(a.binding.spec, b.binding.spec));
|
|
12540
13261
|
out.push(...scopeMatches);
|
|
12541
13262
|
}
|
|
13263
|
+
if (exclusive && out.length === 0 && bindings.length > 0) {
|
|
13264
|
+
reportDeadClaim(claimOf(e)?.owner, warn);
|
|
13265
|
+
}
|
|
12542
13266
|
return out;
|
|
12543
13267
|
}
|
|
12544
13268
|
var EMPTY_ENGAGED2 = /* @__PURE__ */ new Set();
|
|
13269
|
+
function liveScope(id, eligibility, state) {
|
|
13270
|
+
const tags = eligibility.capabilities;
|
|
13271
|
+
if (tags && tags.length > 0 && state.allows && !state.allows(tags)) return null;
|
|
13272
|
+
if (state.engagedIds?.has(id)) return "hotkey";
|
|
13273
|
+
if (eligibility.offhand && state.heldTriggers.has(eligibility.offhand)) return "hotkey";
|
|
13274
|
+
if (eligibility.focus && state.focusedId === id) return "active";
|
|
13275
|
+
if (eligibility.always || eligibility.claimed) return "ambient";
|
|
13276
|
+
return null;
|
|
13277
|
+
}
|
|
13278
|
+
function scopeBindings(entries, state) {
|
|
13279
|
+
const out = [];
|
|
13280
|
+
for (const entry of entries) {
|
|
13281
|
+
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
13282
|
+
if (scope === null) continue;
|
|
13283
|
+
for (const binding of entry.bindings ?? []) {
|
|
13284
|
+
out.push({ binding, scope, ownerToolId: entry.id });
|
|
13285
|
+
}
|
|
13286
|
+
}
|
|
13287
|
+
return out;
|
|
13288
|
+
}
|
|
12545
13289
|
var ALWAYS = { all: [] };
|
|
12546
13290
|
var NEVER = { any: [] };
|
|
12547
13291
|
function isAllRule(r) {
|
|
@@ -12836,6 +13580,7 @@ function filterEligible(matches, actionLookup, ruleCtx) {
|
|
|
12836
13580
|
return isEligible(action, ruleCtx);
|
|
12837
13581
|
});
|
|
12838
13582
|
}
|
|
13583
|
+
var EMPTY_TRIGGERS = /* @__PURE__ */ new Set();
|
|
12839
13584
|
var EMPTY_ENGAGED22 = /* @__PURE__ */ new Set();
|
|
12840
13585
|
function modifiersOf(e) {
|
|
12841
13586
|
return { alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey };
|
|
@@ -12943,34 +13688,23 @@ function createDispatcher(opts) {
|
|
|
12943
13688
|
return `ongoing-${event.kind}`;
|
|
12944
13689
|
}
|
|
12945
13690
|
function assembleScopedBindings(ctx) {
|
|
12946
|
-
const
|
|
12947
|
-
for (const
|
|
12948
|
-
const tool = ctx.toolsById.get(
|
|
12949
|
-
|
|
12950
|
-
|
|
12951
|
-
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
|
|
12957
|
-
|
|
12958
|
-
|
|
12959
|
-
for (const binding of tool?.bindings ?? []) {
|
|
12960
|
-
result.push({ binding, scope: "ambient", ownerToolId: toolId });
|
|
12961
|
-
}
|
|
12962
|
-
}
|
|
13691
|
+
const ordered = [];
|
|
13692
|
+
for (const id of ctx.hotkeyStack) {
|
|
13693
|
+
const tool = ctx.toolsById.get(id);
|
|
13694
|
+
if (tool && !ordered.includes(tool)) ordered.push(tool);
|
|
13695
|
+
}
|
|
13696
|
+
for (const tool of ctx.toolsById.values()) {
|
|
13697
|
+
if (!ordered.includes(tool)) ordered.push(tool);
|
|
13698
|
+
}
|
|
13699
|
+
const result = scopeBindings(ordered, {
|
|
13700
|
+
focusedId: ctx.activeToolId,
|
|
13701
|
+
heldTriggers: EMPTY_TRIGGERS,
|
|
13702
|
+
engagedIds: new Set(ctx.hotkeyStack)
|
|
13703
|
+
});
|
|
12963
13704
|
for (const action of ctx.actions.list()) {
|
|
12964
|
-
const
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
for (const entry of raw) {
|
|
12968
|
-
const isBoundObj = !("kind" in entry);
|
|
12969
|
-
const spec = isBoundObj ? entry.spec : entry;
|
|
12970
|
-
const opts2 = isBoundObj ? entry.opts : void 0;
|
|
12971
|
-
const defaultBinding = { spec, actionId: action.id, ...opts2 !== void 0 ? { opts: opts2 } : {} };
|
|
12972
|
-
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
12973
|
-
result.push({ binding: defaultBinding, scope: targetScope, ownerToolId: null });
|
|
13705
|
+
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
13706
|
+
for (const binding of actionBindings(action)) {
|
|
13707
|
+
result.push({ binding, scope: targetScope, ownerToolId: null });
|
|
12974
13708
|
}
|
|
12975
13709
|
}
|
|
12976
13710
|
return result;
|
|
@@ -13125,15 +13859,26 @@ function createDispatcher(opts) {
|
|
|
13125
13859
|
deltaY: event.deltaY,
|
|
13126
13860
|
clientX: event.clientX,
|
|
13127
13861
|
clientY: event.clientY,
|
|
13862
|
+
affordance: event.affordance,
|
|
13128
13863
|
...resolved
|
|
13129
13864
|
};
|
|
13130
13865
|
} else if (event.kind === "click" || event.kind === "doubleclick") {
|
|
13131
13866
|
params = {
|
|
13132
13867
|
worldX: event.worldX,
|
|
13133
13868
|
worldY: event.worldY,
|
|
13869
|
+
affordance: event.affordance,
|
|
13134
13870
|
// Press point as well as release point — an action that places
|
|
13135
13871
|
// geometry at the click wants the former. See `ClickEvent`.
|
|
13136
|
-
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY
|
|
13872
|
+
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
13873
|
+
mods: modifiersOf(event),
|
|
13874
|
+
...resolved
|
|
13875
|
+
};
|
|
13876
|
+
} else if (event.kind === "contextmenu" || event.kind === "longpress") {
|
|
13877
|
+
params = {
|
|
13878
|
+
worldX: event.kind === "contextmenu" ? event.worldX : event.x,
|
|
13879
|
+
worldY: event.kind === "contextmenu" ? event.worldY : event.y,
|
|
13880
|
+
affordance: event.affordance,
|
|
13881
|
+
bodyTarget: event.bodyTarget,
|
|
13137
13882
|
mods: modifiersOf(event),
|
|
13138
13883
|
...resolved
|
|
13139
13884
|
};
|
|
@@ -13501,7 +14246,7 @@ function computeMultiTouchGeometry(positions) {
|
|
|
13501
14246
|
return { centroid, spread };
|
|
13502
14247
|
}
|
|
13503
14248
|
function useGestureDispatcher(opts) {
|
|
13504
|
-
const { canvasRef, actions, toolsById,
|
|
14249
|
+
const { canvasRef, actions, toolsById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld: clientToWorld2, requestRedraw, getRuleCtx, onDoubleClick } = opts;
|
|
13505
14250
|
const onDoubleClickRef = useRef(onDoubleClick);
|
|
13506
14251
|
onDoubleClickRef.current = onDoubleClick;
|
|
13507
14252
|
const activeTool = useActiveToolContext();
|
|
@@ -13516,7 +14261,6 @@ function useGestureDispatcher(opts) {
|
|
|
13516
14261
|
activeToolId: activeTool.active,
|
|
13517
14262
|
hotkeyStack: activeTool.hotkeyStack,
|
|
13518
14263
|
toolsById,
|
|
13519
|
-
ambientToolIds,
|
|
13520
14264
|
isMac: IS_MAC,
|
|
13521
14265
|
getRuleCtx
|
|
13522
14266
|
});
|
|
@@ -13526,7 +14270,6 @@ function useGestureDispatcher(opts) {
|
|
|
13526
14270
|
activeToolId: activeTool.active,
|
|
13527
14271
|
hotkeyStack: activeTool.hotkeyStack,
|
|
13528
14272
|
toolsById,
|
|
13529
|
-
ambientToolIds,
|
|
13530
14273
|
isMac: IS_MAC,
|
|
13531
14274
|
getRuleCtx
|
|
13532
14275
|
};
|
|
@@ -13604,6 +14347,7 @@ function useGestureDispatcher(opts) {
|
|
|
13604
14347
|
ctrlKey: down.ctrlKey,
|
|
13605
14348
|
metaKey: down.metaKey,
|
|
13606
14349
|
shiftKey: down.shiftKey,
|
|
14350
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
13607
14351
|
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
13608
14352
|
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
13609
14353
|
};
|
|
@@ -13613,11 +14357,15 @@ function useGestureDispatcher(opts) {
|
|
|
13613
14357
|
y: down.worldY,
|
|
13614
14358
|
clientX: down.clientX,
|
|
13615
14359
|
clientY: down.clientY,
|
|
13616
|
-
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
13617
14360
|
...shared
|
|
13618
14361
|
});
|
|
13619
14362
|
if (result === "unhandled") {
|
|
13620
|
-
dispatch2({
|
|
14363
|
+
dispatch2({
|
|
14364
|
+
kind: "contextmenu",
|
|
14365
|
+
worldX: down.worldX,
|
|
14366
|
+
worldY: down.worldY,
|
|
14367
|
+
...shared
|
|
14368
|
+
});
|
|
13621
14369
|
}
|
|
13622
14370
|
};
|
|
13623
14371
|
const DOUBLE_CLICK_MAX_MS = 600;
|
|
@@ -13672,6 +14420,9 @@ function useGestureDispatcher(opts) {
|
|
|
13672
14420
|
const onWheel = (e) => {
|
|
13673
14421
|
const rect = canvas?.getBoundingClientRect();
|
|
13674
14422
|
const [localX, localY] = rect ? clientToCanvasRect(rect, e.clientX, e.clientY) : [e.clientX, e.clientY];
|
|
14423
|
+
const screenPoint = { x: e.clientX, y: e.clientY };
|
|
14424
|
+
const affordance = affordanceAtRef.current?.(screenPoint) ?? void 0;
|
|
14425
|
+
const body = classifyTargetRef.current?.(screenPoint);
|
|
13675
14426
|
const ev = {
|
|
13676
14427
|
kind: "wheel",
|
|
13677
14428
|
altKey: e.altKey,
|
|
@@ -13681,7 +14432,10 @@ function useGestureDispatcher(opts) {
|
|
|
13681
14432
|
deltaX: e.deltaX,
|
|
13682
14433
|
deltaY: e.deltaY,
|
|
13683
14434
|
clientX: localX,
|
|
13684
|
-
clientY: localY
|
|
14435
|
+
clientY: localY,
|
|
14436
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
14437
|
+
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
14438
|
+
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
13685
14439
|
};
|
|
13686
14440
|
const result = dispatch2(ev);
|
|
13687
14441
|
if (result === "handled") {
|
|
@@ -13975,6 +14729,7 @@ function useGestureDispatcher(opts) {
|
|
|
13975
14729
|
shiftKey: e.shiftKey,
|
|
13976
14730
|
worldX: wClick.x,
|
|
13977
14731
|
worldY: wClick.y,
|
|
14732
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
13978
14733
|
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
13979
14734
|
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
13980
14735
|
};
|
|
@@ -14016,8 +14771,10 @@ function useGestureDispatcher(opts) {
|
|
|
14016
14771
|
};
|
|
14017
14772
|
const onContextMenu = (e) => {
|
|
14018
14773
|
e.preventDefault();
|
|
14019
|
-
const
|
|
14020
|
-
const
|
|
14774
|
+
const screenPoint = { x: e.clientX, y: e.clientY };
|
|
14775
|
+
const affordance = affordanceAtRef.current?.(screenPoint) ?? void 0;
|
|
14776
|
+
const menuBody = classifyTargetRef.current?.(screenPoint);
|
|
14777
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
14021
14778
|
const ev = {
|
|
14022
14779
|
kind: "contextmenu",
|
|
14023
14780
|
target: e.target,
|
|
@@ -14025,6 +14782,9 @@ function useGestureDispatcher(opts) {
|
|
|
14025
14782
|
ctrlKey: e.ctrlKey,
|
|
14026
14783
|
metaKey: e.metaKey,
|
|
14027
14784
|
shiftKey: e.shiftKey,
|
|
14785
|
+
worldX: w.x,
|
|
14786
|
+
worldY: w.y,
|
|
14787
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
14028
14788
|
...menuBody?.body !== void 0 ? { bodyTarget: menuBody.body } : {},
|
|
14029
14789
|
...menuBody?.kind !== void 0 ? { bodyKind: menuBody.kind } : {}
|
|
14030
14790
|
};
|
|
@@ -14323,47 +15083,203 @@ function usePinchGesture(canvasRef, onPinch, enabled = true) {
|
|
|
14323
15083
|
};
|
|
14324
15084
|
}, [canvasRef, enabled]);
|
|
14325
15085
|
}
|
|
14326
|
-
|
|
14327
|
-
|
|
14328
|
-
|
|
15086
|
+
var TOOL_OFFHAND_ID = "tool.offhand";
|
|
15087
|
+
function offhandKeyFor(trigger) {
|
|
15088
|
+
switch (trigger) {
|
|
15089
|
+
case "space":
|
|
15090
|
+
return " ";
|
|
15091
|
+
case "alt":
|
|
15092
|
+
return "Alt";
|
|
15093
|
+
case "ctrl":
|
|
15094
|
+
return "Control";
|
|
15095
|
+
case "meta":
|
|
15096
|
+
return "Meta";
|
|
15097
|
+
case "shift":
|
|
15098
|
+
return "Shift";
|
|
14329
15099
|
}
|
|
15100
|
+
}
|
|
15101
|
+
function buildToolOffhandBindings(specs) {
|
|
15102
|
+
return specs.map(({ toolId, key }) => ({
|
|
15103
|
+
spec: { kind: "key-held", key },
|
|
15104
|
+
opts: { params: { toolId } }
|
|
15105
|
+
}));
|
|
15106
|
+
}
|
|
15107
|
+
function makeToolOffhandAction(bindings) {
|
|
15108
|
+
return {
|
|
15109
|
+
id: TOOL_OFFHAND_ID,
|
|
15110
|
+
label: "Engage tool (offhand)",
|
|
15111
|
+
defaultBinding: bindings,
|
|
15112
|
+
scope: "hotkey",
|
|
15113
|
+
invoker: {
|
|
15114
|
+
timing: "ongoing",
|
|
15115
|
+
start: (ctx, opts) => {
|
|
15116
|
+
const activeTool = ctx.deps.activeTool;
|
|
15117
|
+
const toolId = resolveParams(opts?.params)?.toolId;
|
|
15118
|
+
if (!activeTool || !toolId) return {};
|
|
15119
|
+
activeTool.pushHotkey(toolId);
|
|
15120
|
+
return {
|
|
15121
|
+
onEnd: () => {
|
|
15122
|
+
activeTool.popHotkey();
|
|
15123
|
+
}
|
|
15124
|
+
};
|
|
15125
|
+
}
|
|
15126
|
+
},
|
|
15127
|
+
requires: ["activeTool"]
|
|
15128
|
+
};
|
|
15129
|
+
}
|
|
15130
|
+
var NO_TRIGGERS = /* @__PURE__ */ new Set();
|
|
15131
|
+
function useContributions(opts) {
|
|
14330
15132
|
const ctx = useActiveToolContext();
|
|
15133
|
+
const actionsRegistry = useActionsRegistry();
|
|
14331
15134
|
const hasInitializedRef = useRef(false);
|
|
14332
15135
|
const isFirstRender = !hasInitializedRef.current;
|
|
14333
15136
|
const needsFirstMountSyncRef = useRef(false);
|
|
14334
15137
|
if (isFirstRender) {
|
|
14335
15138
|
hasInitializedRef.current = true;
|
|
14336
|
-
needsFirstMountSyncRef.current = ctx.active === "select" && opts.
|
|
15139
|
+
needsFirstMountSyncRef.current = ctx.active === "select" && opts.focused !== "select";
|
|
14337
15140
|
}
|
|
14338
15141
|
useEffect(() => {
|
|
14339
15142
|
if (!needsFirstMountSyncRef.current) return;
|
|
14340
15143
|
needsFirstMountSyncRef.current = false;
|
|
14341
|
-
ctx.setActive(opts.
|
|
15144
|
+
ctx.setActive(opts.focused);
|
|
15145
|
+
}, []);
|
|
15146
|
+
const focused2 = isFirstRender && ctx.active === "select" && opts.focused !== "select" ? opts.focused : ctx.active;
|
|
15147
|
+
const entriesRef = useRef(opts.entries);
|
|
15148
|
+
entriesRef.current = opts.entries;
|
|
15149
|
+
const focusedRef = useRef(focused2);
|
|
15150
|
+
focusedRef.current = focused2;
|
|
15151
|
+
const engagedRef = useRef(ctx.hotkeyStack);
|
|
15152
|
+
engagedRef.current = ctx.hotkeyStack;
|
|
15153
|
+
const setFocused = useCallback((id) => {
|
|
15154
|
+
ctx.setActive(id);
|
|
15155
|
+
}, [ctx]);
|
|
15156
|
+
const snapshot = useCallback(() => {
|
|
15157
|
+
const engaged = engagedRef.current;
|
|
15158
|
+
const engagedIds = new Set(engaged);
|
|
15159
|
+
const ordered = [];
|
|
15160
|
+
for (const id of engaged) {
|
|
15161
|
+
const entry = entriesRef.current.find((e) => e.id === id);
|
|
15162
|
+
if (entry && !ordered.includes(entry)) ordered.push(entry);
|
|
15163
|
+
}
|
|
15164
|
+
for (const entry of entriesRef.current) {
|
|
15165
|
+
if (!ordered.includes(entry)) ordered.push(entry);
|
|
15166
|
+
}
|
|
15167
|
+
return {
|
|
15168
|
+
ordered,
|
|
15169
|
+
state: { focusedId: focusedRef.current, heldTriggers: NO_TRIGGERS, engagedIds }
|
|
15170
|
+
};
|
|
14342
15171
|
}, []);
|
|
14343
|
-
const
|
|
14344
|
-
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
|
|
14351
|
-
|
|
15172
|
+
const scopedBindings = useCallback(() => {
|
|
15173
|
+
const { ordered, state } = snapshot();
|
|
15174
|
+
return scopeBindings(ordered, state);
|
|
15175
|
+
}, [snapshot]);
|
|
15176
|
+
const overlays = useCallback(() => {
|
|
15177
|
+
const { ordered, state } = snapshot();
|
|
15178
|
+
const active = [];
|
|
15179
|
+
const hotkey = [];
|
|
15180
|
+
const ambient = [];
|
|
15181
|
+
for (const entry of ordered) {
|
|
15182
|
+
if (!entry.overlay) continue;
|
|
15183
|
+
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
15184
|
+
if (scope === "active") active.push(entry.overlay);
|
|
15185
|
+
else if (scope === "hotkey") hotkey.push(entry.overlay);
|
|
15186
|
+
else if (scope === "ambient") ambient.push(entry.overlay);
|
|
15187
|
+
}
|
|
15188
|
+
return [...active, ...hotkey, ...ambient];
|
|
15189
|
+
}, [snapshot]);
|
|
15190
|
+
const offhandSig = opts.entries.map((e) => e.eligibility?.offhand ? `${e.id}:${e.eligibility.offhand}` : "").filter(Boolean).join(",");
|
|
15191
|
+
useEffect(() => {
|
|
15192
|
+
if (!actionsRegistry || offhandSig === "") return;
|
|
15193
|
+
const specs = offhandSig.split(",").map((entry) => {
|
|
15194
|
+
const [toolId, trigger] = entry.split(":");
|
|
15195
|
+
return { toolId, key: offhandKeyFor(trigger) };
|
|
15196
|
+
});
|
|
15197
|
+
return actionsRegistry.register(makeToolOffhandAction(buildToolOffhandBindings(specs)));
|
|
15198
|
+
}, [actionsRegistry, offhandSig]);
|
|
15199
|
+
const lastConflictSigRef = useRef(null);
|
|
15200
|
+
const entrySig = opts.entries.map((e) => e.id).join(",");
|
|
15201
|
+
useEffect(() => {
|
|
15202
|
+
if (process.env.NODE_ENV === "production") return;
|
|
15203
|
+
if (lastConflictSigRef.current === entrySig) return;
|
|
15204
|
+
lastConflictSigRef.current = entrySig;
|
|
15205
|
+
const registry22 = [];
|
|
15206
|
+
const ambient = [];
|
|
15207
|
+
for (const entry of entriesRef.current) {
|
|
15208
|
+
(entry.eligibility?.focus ? registry22 : ambient).push(entry);
|
|
15209
|
+
}
|
|
15210
|
+
reportRouteConflicts({
|
|
15211
|
+
registry: registry22,
|
|
15212
|
+
ambient,
|
|
15213
|
+
actions: actionsRegistry?.list() ?? []
|
|
15214
|
+
});
|
|
15215
|
+
}, [entrySig, actionsRegistry]);
|
|
15216
|
+
return useMemo(
|
|
15217
|
+
() => ({
|
|
15218
|
+
entries: entriesRef.current,
|
|
15219
|
+
focused: focused2,
|
|
15220
|
+
setFocused,
|
|
15221
|
+
scopedBindings,
|
|
15222
|
+
overlays
|
|
15223
|
+
}),
|
|
15224
|
+
[focused2, setFocused, scopedBindings, overlays]
|
|
15225
|
+
);
|
|
15226
|
+
}
|
|
15227
|
+
function declareSlot(tool, slot) {
|
|
15228
|
+
const hotkey = tool.def?.hotkey;
|
|
15229
|
+
const eligibility = {
|
|
15230
|
+
...tool.eligibility,
|
|
15231
|
+
[slot]: true,
|
|
15232
|
+
...hotkey ? { offhand: hotkey } : {}
|
|
15233
|
+
};
|
|
15234
|
+
return sameEligibility(tool.eligibility, eligibility) ? tool : { ...tool, eligibility };
|
|
15235
|
+
}
|
|
15236
|
+
function sameEligibility(a, b) {
|
|
15237
|
+
if (!a) return false;
|
|
15238
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
15239
|
+
for (const key of keys) if (a[key] !== b[key]) return false;
|
|
15240
|
+
return true;
|
|
15241
|
+
}
|
|
15242
|
+
function useTools(opts) {
|
|
15243
|
+
if (!(opts.active in opts.registry)) {
|
|
15244
|
+
throw new Error(`useTools: active "${opts.active}" not in registry`);
|
|
15245
|
+
}
|
|
15246
|
+
const ctx = useActiveToolContext();
|
|
15247
|
+
const slotted = useMemo(() => {
|
|
15248
|
+
const registry22 = {};
|
|
15249
|
+
for (const [id, tool] of Object.entries(opts.registry)) {
|
|
15250
|
+
registry22[id] = declareSlot(tool, "focus");
|
|
15251
|
+
}
|
|
15252
|
+
const ambient = (opts.ambient ?? []).map((t) => declareSlot(t, "always"));
|
|
15253
|
+
const byId2 = /* @__PURE__ */ new Map();
|
|
15254
|
+
for (const tool of [...Object.values(registry22), ...ambient]) {
|
|
15255
|
+
const prior = byId2.get(tool.id);
|
|
15256
|
+
if (prior) byId2.set(tool.id, { ...tool, eligibility: { ...prior.eligibility, ...tool.eligibility } });
|
|
15257
|
+
else byId2.set(tool.id, tool);
|
|
15258
|
+
}
|
|
15259
|
+
return { registry: registry22, ambient, entries: [...byId2.values()] };
|
|
15260
|
+
}, [opts.registry, opts.ambient]);
|
|
15261
|
+
const contributions = useContributions({ entries: slotted.entries, focused: opts.active });
|
|
15262
|
+
const hotkeyEngaged = ctx.hotkeyStack.at(-1) ?? null;
|
|
15263
|
+
const slottedRef = useRef(slotted);
|
|
15264
|
+
slottedRef.current = slotted;
|
|
15265
|
+
const activeRef = useRef(contributions.focused);
|
|
15266
|
+
activeRef.current = contributions.focused;
|
|
14352
15267
|
const hotkeyRef = useRef(hotkeyEngaged);
|
|
14353
15268
|
hotkeyRef.current = hotkeyEngaged;
|
|
15269
|
+
const setFocused = contributions.setFocused;
|
|
14354
15270
|
const setActive = useCallback(
|
|
14355
15271
|
(id) => {
|
|
14356
|
-
if (!(id in
|
|
15272
|
+
if (!(id in slottedRef.current.registry)) {
|
|
14357
15273
|
throw new Error(`setActive: "${id}" not in registry`);
|
|
14358
15274
|
}
|
|
14359
15275
|
dlog("tools", "active:", activeRef.current, "\u2192", id);
|
|
14360
|
-
|
|
15276
|
+
setFocused(id);
|
|
14361
15277
|
},
|
|
14362
|
-
[
|
|
15278
|
+
[setFocused]
|
|
14363
15279
|
);
|
|
14364
15280
|
const engageHotkey = useCallback(
|
|
14365
15281
|
(id) => {
|
|
14366
|
-
if (!(id in
|
|
15282
|
+
if (!(id in slottedRef.current.registry)) {
|
|
14367
15283
|
throw new Error(`engageHotkey: "${id}" not in registry`);
|
|
14368
15284
|
}
|
|
14369
15285
|
dlog("tools", "hotkey engaged:", id);
|
|
@@ -14375,32 +15291,12 @@ function useTools(opts) {
|
|
|
14375
15291
|
if (hotkeyRef.current) dlog("tools", "hotkey disengaged:", hotkeyRef.current);
|
|
14376
15292
|
ctx.popHotkey();
|
|
14377
15293
|
}, [ctx]);
|
|
14378
|
-
const lastConflictSigRef = useRef(null);
|
|
14379
|
-
const toolSetSig = Object.keys(opts.registry).join(",") + "|" + (opts.ambient ?? []).map((t) => t.id).join(",");
|
|
14380
|
-
useEffect(() => {
|
|
14381
|
-
if (process.env.NODE_ENV === "production") return;
|
|
14382
|
-
if (lastConflictSigRef.current === toolSetSig) return;
|
|
14383
|
-
lastConflictSigRef.current = toolSetSig;
|
|
14384
|
-
reportRouteConflicts({
|
|
14385
|
-
registry: registryRef.current,
|
|
14386
|
-
ambient: ambientRef.current
|
|
14387
|
-
});
|
|
14388
|
-
}, [toolSetSig]);
|
|
14389
15294
|
const has = useCallback(
|
|
14390
|
-
(id) => id in
|
|
15295
|
+
(id) => id in slottedRef.current.registry || slottedRef.current.ambient.some((t) => t.id === id),
|
|
14391
15296
|
[]
|
|
14392
15297
|
);
|
|
14393
|
-
const
|
|
14394
|
-
|
|
14395
|
-
const activeTool = registryRef.current[activeRef.current];
|
|
14396
|
-
if (activeTool?.overlay) out.push(activeTool.overlay);
|
|
14397
|
-
const mod = hotkeyRef.current ? registryRef.current[hotkeyRef.current] : null;
|
|
14398
|
-
if (mod?.overlay) out.push(mod.overlay);
|
|
14399
|
-
for (const t of ambientRef.current) {
|
|
14400
|
-
if (t.overlay) out.push(t.overlay);
|
|
14401
|
-
}
|
|
14402
|
-
return out;
|
|
14403
|
-
}, []);
|
|
15298
|
+
const active = contributions.focused;
|
|
15299
|
+
const getActiveOverlays = contributions.overlays;
|
|
14404
15300
|
return useMemo(
|
|
14405
15301
|
() => ({
|
|
14406
15302
|
active,
|
|
@@ -14408,44 +15304,14 @@ function useTools(opts) {
|
|
|
14408
15304
|
hotkeyEngaged,
|
|
14409
15305
|
engageHotkey,
|
|
14410
15306
|
disengageHotkey,
|
|
14411
|
-
ambient:
|
|
14412
|
-
registry:
|
|
15307
|
+
ambient: slottedRef.current.ambient,
|
|
15308
|
+
registry: slottedRef.current.registry,
|
|
14413
15309
|
has,
|
|
14414
15310
|
getActiveOverlays
|
|
14415
15311
|
}),
|
|
14416
15312
|
[active, setActive, hotkeyEngaged, engageHotkey, disengageHotkey, has, getActiveOverlays]
|
|
14417
15313
|
);
|
|
14418
15314
|
}
|
|
14419
|
-
var TOOL_OFFHAND_ID = "tool.offhand";
|
|
14420
|
-
function buildToolOffhandBindings(specs) {
|
|
14421
|
-
return specs.map(({ toolId, key }) => ({
|
|
14422
|
-
spec: { kind: "key-held", key },
|
|
14423
|
-
opts: { params: { toolId } }
|
|
14424
|
-
}));
|
|
14425
|
-
}
|
|
14426
|
-
function makeToolOffhandAction(bindings) {
|
|
14427
|
-
return {
|
|
14428
|
-
id: TOOL_OFFHAND_ID,
|
|
14429
|
-
label: "Engage tool (offhand)",
|
|
14430
|
-
defaultBinding: bindings,
|
|
14431
|
-
scope: "hotkey",
|
|
14432
|
-
invoker: {
|
|
14433
|
-
timing: "ongoing",
|
|
14434
|
-
start: (ctx, opts) => {
|
|
14435
|
-
const activeTool = ctx.deps.activeTool;
|
|
14436
|
-
const toolId = resolveParams(opts?.params)?.toolId;
|
|
14437
|
-
if (!activeTool || !toolId) return {};
|
|
14438
|
-
activeTool.pushHotkey(toolId);
|
|
14439
|
-
return {
|
|
14440
|
-
onEnd: () => {
|
|
14441
|
-
activeTool.popHotkey();
|
|
14442
|
-
}
|
|
14443
|
-
};
|
|
14444
|
-
}
|
|
14445
|
-
},
|
|
14446
|
-
requires: ["activeTool"]
|
|
14447
|
-
};
|
|
14448
|
-
}
|
|
14449
15315
|
var TOOL_ACTIVATE_ID = "tool.activate";
|
|
14450
15316
|
function buildToolActivateBindings(specs) {
|
|
14451
15317
|
return specs.map(({ toolId, keyOpts }) => {
|
|
@@ -14517,9 +15383,6 @@ var BUILTIN_SELECT_KEYS = {
|
|
|
14517
15383
|
hand: { key: "H" },
|
|
14518
15384
|
pen: { key: "P" }
|
|
14519
15385
|
};
|
|
14520
|
-
var BUILTIN_OFFHAND_ACTIONS = {
|
|
14521
|
-
hand: " "
|
|
14522
|
-
};
|
|
14523
15386
|
function useKeybindings(tools, options = {}) {
|
|
14524
15387
|
const toolsRef = useRef(tools);
|
|
14525
15388
|
toolsRef.current = tools;
|
|
@@ -14527,19 +15390,6 @@ function useKeybindings(tools, options = {}) {
|
|
|
14527
15390
|
optionsRef.current = options;
|
|
14528
15391
|
const initialActiveRef = useRef(tools.active);
|
|
14529
15392
|
const registry22 = useActionsRegistry();
|
|
14530
|
-
useEffect(() => {
|
|
14531
|
-
if (optionsRef.current.disable) return;
|
|
14532
|
-
if (!registry22) return;
|
|
14533
|
-
const specs = [];
|
|
14534
|
-
for (const [toolId, key] of Object.entries(BUILTIN_OFFHAND_ACTIONS)) {
|
|
14535
|
-
if (toolsRef.current.has(toolId)) {
|
|
14536
|
-
specs.push({ toolId, key });
|
|
14537
|
-
}
|
|
14538
|
-
}
|
|
14539
|
-
if (specs.length === 0) return;
|
|
14540
|
-
const bindings = buildToolOffhandBindings(specs);
|
|
14541
|
-
return registry22.register(makeToolOffhandAction(bindings));
|
|
14542
|
-
}, [registry22, tools]);
|
|
14543
15393
|
useEffect(() => {
|
|
14544
15394
|
if (optionsRef.current.disable) return;
|
|
14545
15395
|
if (!registry22) return;
|
|
@@ -14912,21 +15762,18 @@ function textLineBoxes(pose, opts = {}) {
|
|
|
14912
15762
|
const style = resolveTextStyle(pose.style);
|
|
14913
15763
|
const source = pose.runs && pose.runs.length > 0 ? pose.runs : pose.text;
|
|
14914
15764
|
const runs = resolveRuns(toRuns(source), style);
|
|
14915
|
-
const laid = layoutRuns(
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
{ x: pose.x, y: pose.y }
|
|
14923
|
-
);
|
|
14924
|
-
const dy = verticalAlignOffset(pose.verticalAlign, pose.height, laid.bounds.height);
|
|
15765
|
+
const laid = layoutRuns(runs, {
|
|
15766
|
+
maxWidth: opts.maxWidth ?? pose.width,
|
|
15767
|
+
lineHeight: style.lineHeight,
|
|
15768
|
+
align: style.align
|
|
15769
|
+
});
|
|
15770
|
+
const dx = pose.x;
|
|
15771
|
+
const dy = pose.y + verticalAlignOffset(pose.verticalAlign, pose.height, laid.bounds.height);
|
|
14925
15772
|
const out = [];
|
|
14926
15773
|
for (const line of laid.lines) {
|
|
14927
15774
|
if (!opts.includeEmpty && line.x1 <= line.x0) continue;
|
|
14928
15775
|
out.push({
|
|
14929
|
-
x: line.x0 - padding,
|
|
15776
|
+
x: line.x0 + dx - padding,
|
|
14930
15777
|
y: line.y0 + dy - padding,
|
|
14931
15778
|
width: line.x1 - line.x0 + padding * 2,
|
|
14932
15779
|
height: line.y1 - line.y0 + padding * 2
|
|
@@ -14934,7 +15781,82 @@ function textLineBoxes(pose, opts = {}) {
|
|
|
14934
15781
|
}
|
|
14935
15782
|
return out;
|
|
14936
15783
|
}
|
|
15784
|
+
function fillInPoseFrame(fill, box) {
|
|
15785
|
+
if (isBoundsPattern(fill)) {
|
|
15786
|
+
const o = fill.origin ?? { x: 0, y: 0 };
|
|
15787
|
+
return { ...fill, origin: { x: box.x + o.x, y: box.y + o.y }, units: "local" };
|
|
15788
|
+
}
|
|
15789
|
+
if (!isBoundsGradient(fill)) return fill;
|
|
15790
|
+
const toBox = (p) => ({
|
|
15791
|
+
x: box.x + p.x * box.width,
|
|
15792
|
+
y: box.y + p.y * box.height
|
|
15793
|
+
});
|
|
15794
|
+
if (fill.fill === "linear-gradient") {
|
|
15795
|
+
return { ...fill, from: toBox(fill.from), to: toBox(fill.to), units: "local" };
|
|
15796
|
+
}
|
|
15797
|
+
if (fill.fill === "radial-gradient") {
|
|
15798
|
+
const scale2 = Math.hypot(box.width, box.height) / Math.SQRT2;
|
|
15799
|
+
return { ...fill, center: toBox(fill.center), radius: fill.radius * scale2, units: "local" };
|
|
15800
|
+
}
|
|
15801
|
+
return { ...fill, center: toBox(fill.center), units: "local" };
|
|
15802
|
+
}
|
|
15803
|
+
function isBoundsPattern(fill) {
|
|
15804
|
+
return fill.fill === "pattern" && fill.units === "bounds";
|
|
15805
|
+
}
|
|
15806
|
+
function isBoundsGradient(fill) {
|
|
15807
|
+
if (fill === void 0) return false;
|
|
15808
|
+
const kind = fill.fill;
|
|
15809
|
+
if (kind !== "linear-gradient" && kind !== "radial-gradient" && kind !== "conic-gradient") {
|
|
15810
|
+
return false;
|
|
15811
|
+
}
|
|
15812
|
+
return fill.units === "bounds";
|
|
15813
|
+
}
|
|
14937
15814
|
var cache5 = /* @__PURE__ */ new Map();
|
|
15815
|
+
function specKey(spec) {
|
|
15816
|
+
const keys = Object.keys(spec).sort();
|
|
15817
|
+
const bag = spec;
|
|
15818
|
+
return keys.map((k) => `${k}=${String(bag[k])}`).join("&");
|
|
15819
|
+
}
|
|
15820
|
+
function resolvePatternSpec(spec) {
|
|
15821
|
+
const key = specKey(spec);
|
|
15822
|
+
const hit = cache5.get(key);
|
|
15823
|
+
if (hit !== void 0) return hit;
|
|
15824
|
+
const built = buildTile(spec);
|
|
15825
|
+
cache5.set(key, built);
|
|
15826
|
+
return built;
|
|
15827
|
+
}
|
|
15828
|
+
function buildTile(spec) {
|
|
15829
|
+
const { color, size } = spec;
|
|
15830
|
+
switch (spec.tile) {
|
|
15831
|
+
case "hatch":
|
|
15832
|
+
return hatch({ color, size, lineWidth: spec.lineWidth });
|
|
15833
|
+
case "crosshatch":
|
|
15834
|
+
return crosshatch({ color, size, lineWidth: spec.lineWidth });
|
|
15835
|
+
case "dots":
|
|
15836
|
+
return dots({ color, size, radius: spec.radius });
|
|
15837
|
+
case "chunks":
|
|
15838
|
+
return chunks({
|
|
15839
|
+
color,
|
|
15840
|
+
size,
|
|
15841
|
+
bg: spec.bg,
|
|
15842
|
+
density: spec.density,
|
|
15843
|
+
chunkSize: spec.chunkSize,
|
|
15844
|
+
seed: spec.seed
|
|
15845
|
+
});
|
|
15846
|
+
default:
|
|
15847
|
+
return null;
|
|
15848
|
+
}
|
|
15849
|
+
}
|
|
15850
|
+
function isPatternSpec(payload) {
|
|
15851
|
+
return "tile" in payload;
|
|
15852
|
+
}
|
|
15853
|
+
function resolveFillPattern(fill) {
|
|
15854
|
+
if (fill.fill !== "pattern") return fill;
|
|
15855
|
+
if (!isPatternSpec(fill.pattern)) return fill;
|
|
15856
|
+
const handle = resolvePatternSpec(fill.pattern);
|
|
15857
|
+
return handle ? { ...fill, pattern: handle } : null;
|
|
15858
|
+
}
|
|
15859
|
+
var cache6 = /* @__PURE__ */ new Map();
|
|
14938
15860
|
var subscribers2 = /* @__PURE__ */ new Set();
|
|
14939
15861
|
var defaultLoader = async (src) => {
|
|
14940
15862
|
if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
|
|
@@ -14952,28 +15874,28 @@ function notify() {
|
|
|
14952
15874
|
}
|
|
14953
15875
|
function startLoad(src) {
|
|
14954
15876
|
const entry = { status: "loading" };
|
|
14955
|
-
|
|
15877
|
+
cache6.set(src, entry);
|
|
14956
15878
|
loader(src).then(
|
|
14957
15879
|
(bitmap) => {
|
|
14958
|
-
if (
|
|
14959
|
-
|
|
15880
|
+
if (cache6.get(src) !== entry) return;
|
|
15881
|
+
cache6.set(src, { status: "ready", bitmap });
|
|
14960
15882
|
notify();
|
|
14961
15883
|
},
|
|
14962
15884
|
() => {
|
|
14963
|
-
if (
|
|
14964
|
-
|
|
15885
|
+
if (cache6.get(src) !== entry) return;
|
|
15886
|
+
cache6.set(src, { status: "error" });
|
|
14965
15887
|
notify();
|
|
14966
15888
|
}
|
|
14967
15889
|
);
|
|
14968
15890
|
}
|
|
14969
15891
|
function getImageBitmap(src) {
|
|
14970
|
-
const entry =
|
|
15892
|
+
const entry = cache6.get(src);
|
|
14971
15893
|
if (entry) return entry.bitmap;
|
|
14972
15894
|
startLoad(src);
|
|
14973
15895
|
return void 0;
|
|
14974
15896
|
}
|
|
14975
15897
|
function imageStatus(src) {
|
|
14976
|
-
return
|
|
15898
|
+
return cache6.get(src)?.status ?? "idle";
|
|
14977
15899
|
}
|
|
14978
15900
|
function subscribeImageReady(cb) {
|
|
14979
15901
|
subscribers2.add(cb);
|
|
@@ -15151,6 +16073,17 @@ function rectsToPath(rects) {
|
|
|
15151
16073
|
}
|
|
15152
16074
|
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
15153
16075
|
}
|
|
16076
|
+
function isFillStyle(v) {
|
|
16077
|
+
return typeof v === "object" && v !== null;
|
|
16078
|
+
}
|
|
16079
|
+
function resolveNodeFill(fill, color, hasStroke, fallback) {
|
|
16080
|
+
if (isFillStyle(fill)) return fill;
|
|
16081
|
+
const c = fill ?? color;
|
|
16082
|
+
if (c === "none") return null;
|
|
16083
|
+
if (c !== void 0) return { color: c };
|
|
16084
|
+
if (hasStroke && fallback === null) return null;
|
|
16085
|
+
return fallback === null ? null : { color: fallback };
|
|
16086
|
+
}
|
|
15154
16087
|
var PATH_PAINTER = {
|
|
15155
16088
|
id: "kit:path",
|
|
15156
16089
|
matches: (node) => {
|
|
@@ -15164,12 +16097,12 @@ var PATH_PAINTER = {
|
|
|
15164
16097
|
const d = node.data;
|
|
15165
16098
|
const projected = pathInPoseFrame(d.path, pose);
|
|
15166
16099
|
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
15167
|
-
const
|
|
15168
|
-
const
|
|
16100
|
+
const declared = resolveNodeFill(d.fill, d.color, hasStroke, hasStroke ? null : "#888");
|
|
16101
|
+
const fill = declared && resolveFillPattern(fillInPoseFrame(declared, pose));
|
|
15169
16102
|
const cmd = {
|
|
15170
16103
|
kind: "path",
|
|
15171
16104
|
path: projected,
|
|
15172
|
-
...
|
|
16105
|
+
...fill ? { fill } : {},
|
|
15173
16106
|
...hasStroke ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
15174
16107
|
};
|
|
15175
16108
|
return [cmd];
|
|
@@ -15185,9 +16118,8 @@ var PATH_PAINTER = {
|
|
|
15185
16118
|
ink: (node) => {
|
|
15186
16119
|
const d = node.data;
|
|
15187
16120
|
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
15188
|
-
const fillColor = d.fill ?? d.color;
|
|
15189
16121
|
return {
|
|
15190
|
-
filled:
|
|
16122
|
+
filled: resolveNodeFill(d.fill, d.color, hasStroke, hasStroke ? null : "#888") !== null,
|
|
15191
16123
|
strokeWidth: hasStroke ? d.strokeWidth ?? 1 : 0
|
|
15192
16124
|
};
|
|
15193
16125
|
}
|
|
@@ -15204,10 +16136,12 @@ var SHAPE_PAINTER = {
|
|
|
15204
16136
|
paint: (node, pose) => nodeMemo(node, PAINT_SLOT, pose, () => {
|
|
15205
16137
|
const d = node.data;
|
|
15206
16138
|
const path = pathForShape(d, pose);
|
|
16139
|
+
const declaredFill = resolveNodeFill(d.fill, d.color, false, "#888");
|
|
16140
|
+
const shapeFill = declaredFill && resolveFillPattern(fillInPoseFrame(declaredFill, pose));
|
|
15207
16141
|
return [{
|
|
15208
16142
|
kind: "path",
|
|
15209
16143
|
path,
|
|
15210
|
-
|
|
16144
|
+
...shapeFill ? { fill: shapeFill } : {},
|
|
15211
16145
|
...d.stroke && (d.strokeWidth ?? 0) > 0 ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
15212
16146
|
}];
|
|
15213
16147
|
}),
|
|
@@ -15603,11 +16537,8 @@ function useSelectTool(adapter, options) {
|
|
|
15603
16537
|
...moveOpts
|
|
15604
16538
|
},
|
|
15605
16539
|
{ spec: { kind: "drag", target: "empty" }, actionId: "areaSelect" },
|
|
15606
|
-
//
|
|
15607
|
-
//
|
|
15608
|
-
// widget that happens to float over empty canvas isn't a click on
|
|
15609
|
-
// empty canvas, and declining in the SPEC lets the chrome's owner
|
|
15610
|
-
// have the click instead of it being consumed here.
|
|
16540
|
+
// resize/rotate bind only drag (no click), so a click on either handle
|
|
16541
|
+
// (body genuinely empty) would hit target:'empty' and wrongly clear selection.
|
|
15611
16542
|
{
|
|
15612
16543
|
spec: {
|
|
15613
16544
|
kind: "click",
|
|
@@ -15691,17 +16622,17 @@ function worldToLocal(xf, wx, wy) {
|
|
|
15691
16622
|
function annulusSemiAxes(shape, view) {
|
|
15692
16623
|
const bandPx = shape.minBandPx ?? 0;
|
|
15693
16624
|
if (bandPx === 0) return { rx: shape.rx, ry: shape.ry };
|
|
15694
|
-
const band = bandPx
|
|
16625
|
+
const band = pxExtent(bandPx, view.scale);
|
|
15695
16626
|
return {
|
|
15696
|
-
rx: Math.max(shape.rx, shape.innerWidth / 2 + band),
|
|
15697
|
-
ry: Math.max(shape.ry, shape.innerHeight / 2 + band)
|
|
16627
|
+
rx: Math.max(shape.rx, shape.innerWidth / 2 + band.x),
|
|
16628
|
+
ry: Math.max(shape.ry, shape.innerHeight / 2 + band.y)
|
|
15698
16629
|
};
|
|
15699
16630
|
}
|
|
15700
16631
|
function hitRegion(region, wx, wy, xf, view) {
|
|
15701
16632
|
const local = worldToLocal(xf, wx, wy);
|
|
15702
16633
|
if (region.shape.kind === "point") {
|
|
15703
|
-
const
|
|
15704
|
-
return
|
|
16634
|
+
const anchor = localToWorld(xf, region.shape.x, region.shape.y);
|
|
16635
|
+
return withinPxBox(anchor.x - wx, anchor.y - wy, region.shape.hitRadiusPx, view.scale);
|
|
15705
16636
|
}
|
|
15706
16637
|
if (region.shape.kind === "annulus") {
|
|
15707
16638
|
const s = region.shape;
|
|
@@ -15765,14 +16696,13 @@ function asIsVisible(data) {
|
|
|
15765
16696
|
function recordRegionHitbox(debug, affordanceId, region, xf, view) {
|
|
15766
16697
|
if (region.shape.kind === "point") {
|
|
15767
16698
|
const w2 = localToWorld(xf, region.shape.x, region.shape.y);
|
|
15768
|
-
const
|
|
16699
|
+
const e = pxExtent(region.shape.hitRadiusPx, view.scale);
|
|
15769
16700
|
debug.recordHitbox(affordanceId, "handle", {
|
|
15770
16701
|
kind: "rect",
|
|
15771
|
-
x: w2.x -
|
|
15772
|
-
y: w2.y -
|
|
15773
|
-
width:
|
|
15774
|
-
height:
|
|
15775
|
-
...xf.identity ? {} : { rotation: Math.atan2(xf.sin, xf.cos) }
|
|
16702
|
+
x: w2.x - e.x,
|
|
16703
|
+
y: w2.y - e.y,
|
|
16704
|
+
width: e.x * 2,
|
|
16705
|
+
height: e.y * 2
|
|
15776
16706
|
});
|
|
15777
16707
|
return;
|
|
15778
16708
|
}
|
|
@@ -15831,7 +16761,7 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
15831
16761
|
if (paint.kind === "annulus") {
|
|
15832
16762
|
if (region.shape.kind !== "annulus") return;
|
|
15833
16763
|
const s = region.shape;
|
|
15834
|
-
const insetLocal = (paint.insetPx ?? 0
|
|
16764
|
+
const insetLocal = pxExtent(paint.insetPx ?? 0, view.scale);
|
|
15835
16765
|
const cmd = annulusCommand(s, annulusSemiAxes(s, view), insetLocal, xf, viewT, paint.fill, paint.stroke);
|
|
15836
16766
|
if (cmd) out.push(cmd);
|
|
15837
16767
|
return;
|
|
@@ -15848,13 +16778,13 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
15848
16778
|
}
|
|
15849
16779
|
}
|
|
15850
16780
|
function annulusCommand(shape, semiAxes, insetLocal, xf, viewT, fill, stroke) {
|
|
15851
|
-
const rx = semiAxes.rx - insetLocal;
|
|
15852
|
-
const ry = semiAxes.ry - insetLocal;
|
|
16781
|
+
const rx = semiAxes.rx - insetLocal.x;
|
|
16782
|
+
const ry = semiAxes.ry - insetLocal.y;
|
|
15853
16783
|
if (rx <= 0 || ry <= 0) return null;
|
|
15854
16784
|
const cx = shape.cx;
|
|
15855
16785
|
const cy = shape.cy;
|
|
15856
|
-
const rectHW = shape.innerWidth / 2 + insetLocal;
|
|
15857
|
-
const rectHH = shape.innerHeight / 2 + insetLocal;
|
|
16786
|
+
const rectHW = shape.innerWidth / 2 + insetLocal.x;
|
|
16787
|
+
const rectHH = shape.innerHeight / 2 + insetLocal.y;
|
|
15858
16788
|
const haveTopBottom = rectHH < ry;
|
|
15859
16789
|
const haveLeftRight = rectHW < rx;
|
|
15860
16790
|
if (!haveTopBottom && !haveLeftRight) return null;
|
|
@@ -16101,6 +17031,7 @@ function useHandTool(opts = {}) {
|
|
|
16101
17031
|
() => Object.assign(defineViewportTool({
|
|
16102
17032
|
id: "hand",
|
|
16103
17033
|
capabilities: ["navigation"],
|
|
17034
|
+
hotkey: "space",
|
|
16104
17035
|
hookName: "useHandTool",
|
|
16105
17036
|
presentation: {
|
|
16106
17037
|
label: "Hand",
|
|
@@ -16221,10 +17152,6 @@ function mirrorHandle(anchor, out) {
|
|
|
16221
17152
|
if (!out || anchor.altBroken) return void 0;
|
|
16222
17153
|
return { x: 2 * anchor.x - out.x, y: 2 * anchor.y - out.y };
|
|
16223
17154
|
}
|
|
16224
|
-
function dist2(ax, ay, bx, by) {
|
|
16225
|
-
const dx = ax - bx, dy = ay - by;
|
|
16226
|
-
return Math.hypot(dx, dy);
|
|
16227
|
-
}
|
|
16228
17155
|
function usePenTool(options) {
|
|
16229
17156
|
const { wrapPath, adapter, autoSelect = true, autoCommitOnClose = true, closeHitRadius = 8, snapPoint: snapPoint2 } = options;
|
|
16230
17157
|
const scratchRef = useRef(null);
|
|
@@ -16248,11 +17175,14 @@ function usePenTool(options) {
|
|
|
16248
17175
|
const fn = optsRef.current.snapPoint;
|
|
16249
17176
|
return fn ? fn({ x, y }) : { x, y };
|
|
16250
17177
|
}, []);
|
|
16251
|
-
const
|
|
16252
|
-
|
|
16253
|
-
|
|
16254
|
-
|
|
16255
|
-
|
|
17178
|
+
const withinCloseRadius = useCallback(
|
|
17179
|
+
(deps, ax, ay, wx, wy) => {
|
|
17180
|
+
const view = deps.view?.get();
|
|
17181
|
+
const scale2 = view?.scale ?? { x: 1, y: 1 };
|
|
17182
|
+
return withinPxRadius(ax - wx, ay - wy, optsRef.current.closeHitRadius, scale2);
|
|
17183
|
+
},
|
|
17184
|
+
[]
|
|
17185
|
+
);
|
|
16256
17186
|
const actions = useMemo(() => {
|
|
16257
17187
|
const s = () => scratchRef.current;
|
|
16258
17188
|
return [
|
|
@@ -16268,10 +17198,9 @@ function usePenTool(options) {
|
|
|
16268
17198
|
if (p?.pressX === void 0 || p.pressY === void 0) return;
|
|
16269
17199
|
const { x: wx, y: wy } = snap2(p.pressX, p.pressY);
|
|
16270
17200
|
const scratch = s();
|
|
16271
|
-
const radius = closeRadius(deps);
|
|
16272
17201
|
if (scratch.current && scratch.current.anchors.length >= 3) {
|
|
16273
17202
|
const first = scratch.current.anchors[0];
|
|
16274
|
-
if (
|
|
17203
|
+
if (withinCloseRadius(deps, first.x, first.y, wx, wy)) {
|
|
16275
17204
|
scratch.current.closed = true;
|
|
16276
17205
|
scratch.finishedSubpaths.push(scratch.current);
|
|
16277
17206
|
scratch.current = null;
|
|
@@ -16390,7 +17319,7 @@ function usePenTool(options) {
|
|
|
16390
17319
|
}
|
|
16391
17320
|
}
|
|
16392
17321
|
];
|
|
16393
|
-
}, [commit, snap2,
|
|
17322
|
+
}, [commit, snap2, withinCloseRadius]);
|
|
16394
17323
|
return useMemo(() => {
|
|
16395
17324
|
return defineTool({
|
|
16396
17325
|
id: "pen",
|
|
@@ -17559,6 +18488,13 @@ function wrapWithPoseRotation(cmds, pose) {
|
|
|
17559
18488
|
children: cmds
|
|
17560
18489
|
}];
|
|
17561
18490
|
}
|
|
18491
|
+
function wrapNodeOutput(cmds, pose, alpha) {
|
|
18492
|
+
const rotated = wrapWithPoseRotation(cmds, pose);
|
|
18493
|
+
if (alpha !== 1 && rotated.length > 0) {
|
|
18494
|
+
return [{ kind: "group", alpha, children: rotated }];
|
|
18495
|
+
}
|
|
18496
|
+
return rotated;
|
|
18497
|
+
}
|
|
17562
18498
|
function CursorCoordsHud({ canvasRef, viewRef, offset }) {
|
|
17563
18499
|
const [state, setState] = useState({
|
|
17564
18500
|
client: { x: 0, y: 0 },
|
|
@@ -17806,12 +18742,7 @@ function buildSceneLayer(cfg, adapter, debugSink, boundsOfFn, hideIds, slot) {
|
|
|
17806
18742
|
const oy = pose.y ?? (b ? b.y : 0);
|
|
17807
18743
|
debugSink.recordOrigin(obj.id, { x: ox, y: oy });
|
|
17808
18744
|
}
|
|
17809
|
-
|
|
17810
|
-
const alpha = cfg.alphaFor ? cfg.alphaFor(obj.id) : 1;
|
|
17811
|
-
if (alpha !== 1 && rotated.length > 0) {
|
|
17812
|
-
return [{ kind: "group", alpha, children: rotated }];
|
|
17813
|
-
}
|
|
17814
|
-
return rotated;
|
|
18745
|
+
return wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);
|
|
17815
18746
|
};
|
|
17816
18747
|
const hierarchicalAdapter = cfg.toPose ? {
|
|
17817
18748
|
...a,
|
|
@@ -17835,13 +18766,8 @@ function buildSceneLayer(cfg, adapter, debugSink, boundsOfFn, hideIds, slot) {
|
|
|
17835
18766
|
const pose = toPose(obj);
|
|
17836
18767
|
if (drawOne) {
|
|
17837
18768
|
const cmds = drawOne(obj, pose, view);
|
|
17838
|
-
const
|
|
17839
|
-
const
|
|
17840
|
-
if (alpha !== 1 && rotated.length > 0) {
|
|
17841
|
-
children.push({ kind: "group", alpha, children: rotated });
|
|
17842
|
-
} else {
|
|
17843
|
-
for (const cmd of rotated) children.push(cmd);
|
|
17844
|
-
}
|
|
18769
|
+
const wrapped = wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);
|
|
18770
|
+
for (const cmd of wrapped) children.push(cmd);
|
|
17845
18771
|
}
|
|
17846
18772
|
if (debugSink) {
|
|
17847
18773
|
const b = boundsOfFn ? boundsOfFn(obj.id) : null;
|
|
@@ -17956,8 +18882,8 @@ function CanvasInner(props, ref) {
|
|
|
17956
18882
|
const isVisible = getIsVisibleRef.current?.() ?? alwaysVisible;
|
|
17957
18883
|
for (const layer of layers2) {
|
|
17958
18884
|
if (!layer.hitTest) continue;
|
|
17959
|
-
const
|
|
17960
|
-
if (
|
|
18885
|
+
const hit = layer.hitTest(worldX, worldY, void 0, view, dims, isVisible);
|
|
18886
|
+
if (hit) return { layerId: layer.id, hit };
|
|
17961
18887
|
}
|
|
17962
18888
|
return null;
|
|
17963
18889
|
}, []);
|
|
@@ -18393,7 +19319,7 @@ function CanvasInner(props, ref) {
|
|
|
18393
19319
|
effectiveView,
|
|
18394
19320
|
{ width, height }
|
|
18395
19321
|
);
|
|
18396
|
-
renderer.render(commands);
|
|
19322
|
+
renderer.render(commands, viewToMat3(effectiveView));
|
|
18397
19323
|
}, [layersWithDebug, width, height, effectiveView, debugSink, redrawNonce, dprProp]);
|
|
18398
19324
|
const shaderIdKey = shaders?.map((h) => h.id).join("|") ?? "";
|
|
18399
19325
|
useEffect(() => {
|
|
@@ -18555,6 +19481,7 @@ function createScene(options) {
|
|
|
18555
19481
|
return i;
|
|
18556
19482
|
}
|
|
18557
19483
|
function rebuildLayerIndex() {
|
|
19484
|
+
invalidateOrder();
|
|
18558
19485
|
state.layerIndex.clear();
|
|
18559
19486
|
for (let i = 0; i < state.layers.length; i++) {
|
|
18560
19487
|
state.layerIndex.set(state.layers[i].id, i);
|
|
@@ -18580,6 +19507,7 @@ function createScene(options) {
|
|
|
18580
19507
|
const idx = sibs.indexOf(id);
|
|
18581
19508
|
if (idx < 0) throw new Error(`Scene: node "${id}" not found in its parent's children`);
|
|
18582
19509
|
sibs.splice(idx, 1);
|
|
19510
|
+
invalidateOrder();
|
|
18583
19511
|
return { parent: node.parent, index: idx };
|
|
18584
19512
|
}
|
|
18585
19513
|
function attach(id, parent, index) {
|
|
@@ -18588,6 +19516,7 @@ function createScene(options) {
|
|
|
18588
19516
|
sibs.splice(i, 0, id);
|
|
18589
19517
|
const node = requireNode(id);
|
|
18590
19518
|
node.parent = parent;
|
|
19519
|
+
invalidateOrder();
|
|
18591
19520
|
}
|
|
18592
19521
|
function descendants(id, out) {
|
|
18593
19522
|
const n = state.nodes.get(id);
|
|
@@ -18667,9 +19596,11 @@ function createScene(options) {
|
|
|
18667
19596
|
apply: (p) => {
|
|
18668
19597
|
requireLayerIndex(p.to);
|
|
18669
19598
|
requireNode(p.id).layer = p.to;
|
|
19599
|
+
invalidateOrder();
|
|
18670
19600
|
},
|
|
18671
19601
|
revert: (p) => {
|
|
18672
19602
|
requireNode(p.id).layer = p.from;
|
|
19603
|
+
invalidateOrder();
|
|
18673
19604
|
}
|
|
18674
19605
|
});
|
|
18675
19606
|
registerKitOp("kit:move", {
|
|
@@ -18808,21 +19739,98 @@ function createScene(options) {
|
|
|
18808
19739
|
registered.set(k, h);
|
|
18809
19740
|
}
|
|
18810
19741
|
}
|
|
18811
|
-
function
|
|
18812
|
-
|
|
18813
|
-
|
|
18814
|
-
|
|
18815
|
-
|
|
18816
|
-
|
|
18817
|
-
|
|
18818
|
-
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
18822
|
-
|
|
19742
|
+
function renderOrderInternal() {
|
|
19743
|
+
return state.layers.length === 1 ? renderOrderFlat() : renderOrderBucketed();
|
|
19744
|
+
}
|
|
19745
|
+
function renderOrderNodesInternal() {
|
|
19746
|
+
return state.layers.length === 1 ? renderOrderNodesFlat() : renderOrderNodesBucketed();
|
|
19747
|
+
}
|
|
19748
|
+
let structureGeneration = 0;
|
|
19749
|
+
let orderCache = { gen: 0 };
|
|
19750
|
+
function invalidateOrder() {
|
|
19751
|
+
structureGeneration++;
|
|
19752
|
+
}
|
|
19753
|
+
function currentOrderCache() {
|
|
19754
|
+
if (orderCache.gen !== structureGeneration) {
|
|
19755
|
+
orderCache = { gen: structureGeneration };
|
|
19756
|
+
}
|
|
19757
|
+
return orderCache;
|
|
19758
|
+
}
|
|
19759
|
+
function renderOrderFlat() {
|
|
19760
|
+
const only = state.layers[0].id;
|
|
19761
|
+
const out = [];
|
|
19762
|
+
const stack = [...state.roots].reverse();
|
|
19763
|
+
while (stack.length > 0) {
|
|
19764
|
+
const id = stack.pop();
|
|
19765
|
+
const node = state.nodes.get(id);
|
|
19766
|
+
if (!node) continue;
|
|
19767
|
+
if (node.layer === only) out.push(id);
|
|
19768
|
+
if (node.kind === "container") {
|
|
19769
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
19770
|
+
stack.push(node.children[i]);
|
|
19771
|
+
}
|
|
19772
|
+
}
|
|
19773
|
+
}
|
|
19774
|
+
return out;
|
|
19775
|
+
}
|
|
19776
|
+
function renderOrderNodesFlat() {
|
|
19777
|
+
const only = state.layers[0].id;
|
|
19778
|
+
const out = [];
|
|
19779
|
+
const stack = [...state.roots].reverse();
|
|
19780
|
+
while (stack.length > 0) {
|
|
19781
|
+
const id = stack.pop();
|
|
19782
|
+
const node = state.nodes.get(id);
|
|
19783
|
+
if (!node) continue;
|
|
19784
|
+
if (node.layer === only) out.push(node);
|
|
19785
|
+
if (node.kind === "container") {
|
|
19786
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
19787
|
+
stack.push(node.children[i]);
|
|
19788
|
+
}
|
|
19789
|
+
}
|
|
19790
|
+
}
|
|
19791
|
+
return out;
|
|
19792
|
+
}
|
|
19793
|
+
function renderOrderBucketed() {
|
|
19794
|
+
const buckets = state.layers.map(() => []);
|
|
19795
|
+
const stack = [...state.roots].reverse();
|
|
19796
|
+
while (stack.length > 0) {
|
|
19797
|
+
const id = stack.pop();
|
|
19798
|
+
const node = state.nodes.get(id);
|
|
19799
|
+
if (!node) continue;
|
|
19800
|
+
const bucket = buckets[state.layerIndex.get(node.layer) ?? -1];
|
|
19801
|
+
if (bucket) bucket.push(id);
|
|
19802
|
+
if (node.kind === "container") {
|
|
19803
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
19804
|
+
stack.push(node.children[i]);
|
|
19805
|
+
}
|
|
19806
|
+
}
|
|
19807
|
+
}
|
|
19808
|
+
const out = [];
|
|
19809
|
+
for (const bucket of buckets) {
|
|
19810
|
+
for (const id of bucket) out.push(id);
|
|
19811
|
+
}
|
|
19812
|
+
return out;
|
|
19813
|
+
}
|
|
19814
|
+
function renderOrderNodesBucketed() {
|
|
19815
|
+
const buckets = state.layers.map(() => []);
|
|
19816
|
+
const stack = [...state.roots].reverse();
|
|
19817
|
+
while (stack.length > 0) {
|
|
19818
|
+
const id = stack.pop();
|
|
19819
|
+
const node = state.nodes.get(id);
|
|
19820
|
+
if (!node) continue;
|
|
19821
|
+
const bucket = buckets[state.layerIndex.get(node.layer) ?? -1];
|
|
19822
|
+
if (bucket) bucket.push(node);
|
|
19823
|
+
if (node.kind === "container") {
|
|
19824
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
19825
|
+
stack.push(node.children[i]);
|
|
18823
19826
|
}
|
|
18824
19827
|
}
|
|
18825
19828
|
}
|
|
19829
|
+
const out = [];
|
|
19830
|
+
for (const bucket of buckets) {
|
|
19831
|
+
for (const node of bucket) out.push(node);
|
|
19832
|
+
}
|
|
19833
|
+
return out;
|
|
18826
19834
|
}
|
|
18827
19835
|
const scene = {
|
|
18828
19836
|
get nodes() {
|
|
@@ -18851,7 +19859,12 @@ function createScene(options) {
|
|
|
18851
19859
|
return out;
|
|
18852
19860
|
},
|
|
18853
19861
|
renderOrder() {
|
|
18854
|
-
|
|
19862
|
+
const c = currentOrderCache();
|
|
19863
|
+
return c.ids ??= renderOrderInternal();
|
|
19864
|
+
},
|
|
19865
|
+
renderOrderNodes() {
|
|
19866
|
+
const c = currentOrderCache();
|
|
19867
|
+
return c.nodes ??= renderOrderNodesInternal();
|
|
18855
19868
|
},
|
|
18856
19869
|
add(spec) {
|
|
18857
19870
|
const id = spec.id ?? generateId();
|
|
@@ -19159,9 +20172,8 @@ function createScene(options) {
|
|
|
19159
20172
|
},
|
|
19160
20173
|
toJSON() {
|
|
19161
20174
|
const nodes = [];
|
|
19162
|
-
for (const
|
|
19163
|
-
const
|
|
19164
|
-
if (!n) continue;
|
|
20175
|
+
for (const n of renderOrderNodesInternal()) {
|
|
20176
|
+
const id = n.id;
|
|
19165
20177
|
const out = {
|
|
19166
20178
|
id,
|
|
19167
20179
|
kind: n.kind,
|
|
@@ -19195,6 +20207,7 @@ function createScene(options) {
|
|
|
19195
20207
|
state.roots.length = 0;
|
|
19196
20208
|
state.layers.length = 0;
|
|
19197
20209
|
state.layerIndex.clear();
|
|
20210
|
+
invalidateOrder();
|
|
19198
20211
|
for (let i = 0; i < json.systemLayers.length; i++) {
|
|
19199
20212
|
const spec = json.systemLayers[i];
|
|
19200
20213
|
if (state.layerIndex.has(spec.id)) {
|
|
@@ -19463,9 +20476,8 @@ function sceneToAdapter(scene, options = {}) {
|
|
|
19463
20476
|
getNodes() {
|
|
19464
20477
|
const visible = visibleLayers();
|
|
19465
20478
|
const out = [];
|
|
19466
|
-
for (const
|
|
19467
|
-
|
|
19468
|
-
if (n && visible.has(n.layer)) out.push(n);
|
|
20479
|
+
for (const n of scene.renderOrderNodes()) {
|
|
20480
|
+
if (visible.has(n.layer)) out.push(n);
|
|
19469
20481
|
}
|
|
19470
20482
|
return out;
|
|
19471
20483
|
},
|
|
@@ -19796,9 +20808,7 @@ function useSceneSelectTool(args) {
|
|
|
19796
20808
|
// path descriptor for a tighter test.
|
|
19797
20809
|
hitTestArea: (rect) => {
|
|
19798
20810
|
const hits = [];
|
|
19799
|
-
for (const
|
|
19800
|
-
const n = scene.get(nid);
|
|
19801
|
-
if (!n) continue;
|
|
20811
|
+
for (const n of scene.renderOrderNodes()) {
|
|
19802
20812
|
if (isPathLike2(n.pose) && pathPoseDescriptor.intersectsRect) {
|
|
19803
20813
|
if (pathPoseDescriptor.intersectsRect(n.pose, rect)) hits.push(n.id);
|
|
19804
20814
|
continue;
|
|
@@ -19836,9 +20846,8 @@ function useSceneSelectTool(args) {
|
|
|
19836
20846
|
}
|
|
19837
20847
|
const tolerance = pickTolerancePx / meanScale(getView?.()?.scale ?? { x: 1, y: 1 });
|
|
19838
20848
|
const out = [];
|
|
19839
|
-
for (const
|
|
19840
|
-
|
|
19841
|
-
if (!n || !poseContainsRotated(n.pose, wx, wy, tolerance)) continue;
|
|
20849
|
+
for (const n of scene.renderOrderNodes()) {
|
|
20850
|
+
if (!poseContainsRotated(n.pose, wx, wy, tolerance)) continue;
|
|
19842
20851
|
if (shapePicking && !shapeCoversPoint(n, n.pose, wx, wy, { tolerance })) continue;
|
|
19843
20852
|
out.push(n.id);
|
|
19844
20853
|
}
|
|
@@ -20774,6 +21783,8 @@ function toAffordanceHit(hit) {
|
|
|
20774
21783
|
const scratch = hit.binding.initialScratch ?? {};
|
|
20775
21784
|
return {
|
|
20776
21785
|
kind: hit.region.hitKind ?? `${hit.affordanceId}:${hit.regionId}`,
|
|
21786
|
+
owner: hit.affordanceId,
|
|
21787
|
+
strength: "shared",
|
|
20777
21788
|
...scratch.targetId !== void 0 ? { targetIds: [scratch.targetId] } : {},
|
|
20778
21789
|
...scratch.anchor !== void 0 ? { anchor: scratch.anchor } : {},
|
|
20779
21790
|
...scratch.fixedPoint !== void 0 ? { fixedPoint: scratch.fixedPoint } : {},
|
|
@@ -20953,23 +21964,25 @@ function hitTestAreaPolygon(scene, area, areaBounds) {
|
|
|
20953
21964
|
const ab = areaBounds ?? boundsOf(area);
|
|
20954
21965
|
if (!ab) return [];
|
|
20955
21966
|
const hits = [];
|
|
20956
|
-
|
|
20957
|
-
|
|
20958
|
-
|
|
21967
|
+
const order = scene.renderOrderNodes();
|
|
21968
|
+
for (let i = 0; i < order.length; i++) {
|
|
21969
|
+
const node = order[i];
|
|
21970
|
+
if (node.kind === "container") continue;
|
|
20959
21971
|
const pose = node.pose;
|
|
20960
|
-
const
|
|
21972
|
+
const silhouette = pose !== null && typeof pose === "object" && pose.kind === "polygon";
|
|
21973
|
+
const b = silhouette ? nodeMemo(node, "aabb", pose, () => aabbOfPose(pose)) : aabbOfPose(pose);
|
|
20961
21974
|
if (!Number.isFinite(b.x) || !Number.isFinite(b.y) || !Number.isFinite(b.width) || !Number.isFinite(b.height)) {
|
|
20962
21975
|
continue;
|
|
20963
21976
|
}
|
|
20964
21977
|
if (b.x >= ab.x + ab.width || b.x + b.width <= ab.x || b.y >= ab.y + ab.height || b.y + b.height <= ab.y) {
|
|
20965
21978
|
continue;
|
|
20966
21979
|
}
|
|
20967
|
-
if (!
|
|
20968
|
-
hits.push(id);
|
|
21980
|
+
if (!silhouette) {
|
|
21981
|
+
hits.push(node.id);
|
|
20969
21982
|
continue;
|
|
20970
21983
|
}
|
|
20971
21984
|
if (silhouetteOverlapsArea(pose.coords, area)) {
|
|
20972
|
-
hits.push(id);
|
|
21985
|
+
hits.push(node.id);
|
|
20973
21986
|
}
|
|
20974
21987
|
}
|
|
20975
21988
|
return hits;
|
|
@@ -21294,7 +22307,7 @@ function chordLengthParam(samples) {
|
|
|
21294
22307
|
ts[0] = 0;
|
|
21295
22308
|
let total = 0;
|
|
21296
22309
|
for (let i = 1; i < n; i++) {
|
|
21297
|
-
total +=
|
|
22310
|
+
total += dist2(samples[i - 1], samples[i]);
|
|
21298
22311
|
ts[i] = total;
|
|
21299
22312
|
}
|
|
21300
22313
|
if (total === 0) return null;
|
|
@@ -21327,13 +22340,13 @@ function solveLS(samples, ts, a, e, tStart, tEnd) {
|
|
|
21327
22340
|
let alpha1;
|
|
21328
22341
|
let alpha2;
|
|
21329
22342
|
if (Math.abs(det) < 1e-12) {
|
|
21330
|
-
const chord =
|
|
22343
|
+
const chord = dist2(a, e);
|
|
21331
22344
|
alpha1 = alpha2 = chord / 3;
|
|
21332
22345
|
} else {
|
|
21333
22346
|
alpha1 = (c22 * x1 - c12 * x2) / det;
|
|
21334
22347
|
alpha2 = (c11 * x2 - c12 * x1) / det;
|
|
21335
22348
|
if (alpha1 < 1e-6 || alpha2 < 1e-6) {
|
|
21336
|
-
const chord =
|
|
22349
|
+
const chord = dist2(a, e);
|
|
21337
22350
|
alpha1 = alpha2 = chord / 3;
|
|
21338
22351
|
}
|
|
21339
22352
|
}
|
|
@@ -21369,7 +22382,7 @@ function newtonRefineT(p, t, p0, p1, p2, p3) {
|
|
|
21369
22382
|
function sub(a, b) {
|
|
21370
22383
|
return { x: a.x - b.x, y: a.y - b.y };
|
|
21371
22384
|
}
|
|
21372
|
-
function
|
|
22385
|
+
function dist2(a, b) {
|
|
21373
22386
|
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
21374
22387
|
}
|
|
21375
22388
|
function unit(v) {
|
|
@@ -22329,7 +23342,7 @@ function SceneCanvasInner(props, ref) {
|
|
|
22329
23342
|
const getMode = getActiveModeRef.current;
|
|
22330
23343
|
if (!getMode) return true;
|
|
22331
23344
|
const registry22 = toolsForEligibilityRef.current?.registry;
|
|
22332
|
-
const caps = registry22?.[toolId]?.capabilities ?? [];
|
|
23345
|
+
const caps = registry22?.[toolId]?.eligibility.capabilities ?? [];
|
|
22333
23346
|
if (caps.length === 0) return false;
|
|
22334
23347
|
const allowed = getMode().allowedCapabilities;
|
|
22335
23348
|
for (const c of caps) if (allowed.has(c)) return true;
|
|
@@ -22803,10 +23816,6 @@ function GestureDispatcherMounter({
|
|
|
22803
23816
|
for (const tool of tools.ambient) m.set(tool.id, tool);
|
|
22804
23817
|
return m;
|
|
22805
23818
|
}, [tools.registry, tools.ambient]);
|
|
22806
|
-
const ambientToolIds = useMemo(
|
|
22807
|
-
() => tools.ambient.map((t) => t.id),
|
|
22808
|
-
[tools.ambient]
|
|
22809
|
-
);
|
|
22810
23819
|
const boundsOfRef = useRef(boundsOf2);
|
|
22811
23820
|
boundsOfRef.current = boundsOf2;
|
|
22812
23821
|
const pickEveryRef = useRef(pickEvery);
|
|
@@ -22918,9 +23927,14 @@ function GestureDispatcherMounter({
|
|
|
22918
23927
|
const worldPoint = clientToWorld2(screenPoint.x, screenPoint.y);
|
|
22919
23928
|
const extra = canvasApiRef?.current?.hitTestExtras?.(worldPoint.x, worldPoint.y);
|
|
22920
23929
|
if (extra) {
|
|
23930
|
+
const claim = extra.hit;
|
|
22921
23931
|
return {
|
|
22922
23932
|
kind: `layer:${extra.layerId}`,
|
|
22923
|
-
|
|
23933
|
+
owner: extra.layerId,
|
|
23934
|
+
strength: claim.strength ?? "shared",
|
|
23935
|
+
...claim.claimedKinds !== void 0 ? { claimedKinds: claim.claimedKinds } : {},
|
|
23936
|
+
...claim.cursor !== void 0 ? { cursor: claim.cursor } : {},
|
|
23937
|
+
...claim.initialScratch !== void 0 ? { payload: claim.initialScratch } : {}
|
|
22924
23938
|
};
|
|
22925
23939
|
}
|
|
22926
23940
|
return affordanceAt ? affordanceAt(worldPoint) : null;
|
|
@@ -22940,7 +23954,6 @@ function GestureDispatcherMounter({
|
|
|
22940
23954
|
canvasRef,
|
|
22941
23955
|
actions: registry22,
|
|
22942
23956
|
toolsById,
|
|
22943
|
-
ambientToolIds,
|
|
22944
23957
|
enabled,
|
|
22945
23958
|
keyboard,
|
|
22946
23959
|
affordanceAt: wrappedAffordanceAt,
|
|
@@ -23124,14 +24137,25 @@ inferredNodeRouting.map((e) => ({
|
|
|
23124
24137
|
schema: shapeSchema({ text: e.name === "text" })
|
|
23125
24138
|
}));
|
|
23126
24139
|
var RENDERER_CACHE = /* @__PURE__ */ new WeakMap();
|
|
23127
|
-
function
|
|
23128
|
-
|
|
23129
|
-
|
|
23130
|
-
|
|
23131
|
-
|
|
23132
|
-
|
|
23133
|
-
|
|
23134
|
-
|
|
24140
|
+
function sceneAsHierarchy(scene) {
|
|
24141
|
+
return {
|
|
24142
|
+
getLayers: () => scene.layers.map((l) => ({ id: l.id, visible: l.visible })),
|
|
24143
|
+
getNode: (id) => scene.get(id),
|
|
24144
|
+
getChildren: (parentId) => {
|
|
24145
|
+
if (parentId === null) return scene.roots;
|
|
24146
|
+
const node = scene.get(parentId);
|
|
24147
|
+
return node && node.kind === "container" ? node.children : [];
|
|
24148
|
+
},
|
|
24149
|
+
getPose: (id) => scene.get(id).pose
|
|
24150
|
+
};
|
|
24151
|
+
}
|
|
24152
|
+
function buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor) {
|
|
24153
|
+
const wrappedDrawOne = (node, pose, v) => wrapNodeOutput(drawOne(node, pose, v), pose, alphaFor ? alphaFor(node.id) : 1);
|
|
24154
|
+
const children = buildSceneTree(
|
|
24155
|
+
sceneAsHierarchy(scene),
|
|
24156
|
+
wrappedDrawOne,
|
|
24157
|
+
view
|
|
24158
|
+
);
|
|
23135
24159
|
if (extraCommands && extraCommands.length > 0) {
|
|
23136
24160
|
for (const cmd of extraCommands) children.push(cmd);
|
|
23137
24161
|
}
|
|
@@ -23142,7 +24166,7 @@ function buildSceneViewCommands(scene, view, drawOne, extraCommands) {
|
|
|
23142
24166
|
}];
|
|
23143
24167
|
}
|
|
23144
24168
|
function renderSceneToCanvas(args) {
|
|
23145
|
-
const { canvas, scene, view, width, height, drawOne, extraCommands } = args;
|
|
24169
|
+
const { canvas, scene, view, width, height, drawOne, extraCommands, alphaFor } = args;
|
|
23146
24170
|
const dpr = args.dpr ?? (typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1);
|
|
23147
24171
|
let entry = RENDERER_CACHE.get(canvas);
|
|
23148
24172
|
if (!entry) {
|
|
@@ -23170,11 +24194,11 @@ function renderSceneToCanvas(args) {
|
|
|
23170
24194
|
entry.height = height;
|
|
23171
24195
|
entry.dpr = dpr;
|
|
23172
24196
|
}
|
|
23173
|
-
const commands = buildSceneViewCommands(scene, view, drawOne, extraCommands);
|
|
23174
|
-
entry.renderer.render(commands);
|
|
24197
|
+
const commands = buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor);
|
|
24198
|
+
entry.renderer.render(commands, viewToMat3(view));
|
|
23175
24199
|
}
|
|
23176
24200
|
function SceneViewCanvasInner(props) {
|
|
23177
|
-
const { scene, view, width, height, drawOne, extraCommands, className, canvasRef } = props;
|
|
24201
|
+
const { scene, view, width, height, drawOne, extraCommands, alphaFor, className, canvasRef } = props;
|
|
23178
24202
|
useSyncExternalStore(scene.subscribe, scene.getVersion, scene.getVersion);
|
|
23179
24203
|
const localRef = useRef(null);
|
|
23180
24204
|
const setCanvasRef = (el) => {
|
|
@@ -23195,7 +24219,8 @@ function SceneViewCanvasInner(props) {
|
|
|
23195
24219
|
width,
|
|
23196
24220
|
height,
|
|
23197
24221
|
drawOne,
|
|
23198
|
-
extraCommands
|
|
24222
|
+
extraCommands,
|
|
24223
|
+
alphaFor
|
|
23199
24224
|
});
|
|
23200
24225
|
});
|
|
23201
24226
|
return /* @__PURE__ */ jsx(
|
|
@@ -23292,6 +24317,7 @@ function MinimapCanvasInner(props) {
|
|
|
23292
24317
|
width,
|
|
23293
24318
|
height,
|
|
23294
24319
|
drawOne,
|
|
24320
|
+
alphaFor,
|
|
23295
24321
|
fit = "scene",
|
|
23296
24322
|
poseBounds,
|
|
23297
24323
|
indicatorStyle,
|
|
@@ -23403,6 +24429,7 @@ function MinimapCanvasInner(props) {
|
|
|
23403
24429
|
height,
|
|
23404
24430
|
drawOne,
|
|
23405
24431
|
extraCommands,
|
|
24432
|
+
alphaFor,
|
|
23406
24433
|
className,
|
|
23407
24434
|
canvasRef: setCanvasRef
|
|
23408
24435
|
}
|
|
@@ -23545,6 +24572,6 @@ function oklchToOklab(L, C, h) {
|
|
|
23545
24572
|
return [L, C * Math.cos(h), C * Math.sin(h)];
|
|
23546
24573
|
}
|
|
23547
24574
|
|
|
23548
|
-
export { FALLBACK_FIT_VIEW, MinimapCanvas, SceneViewCanvas, UnknownIcon, buildSceneViewCommands, computeFitView2, computeIndicatorCommand,
|
|
23549
|
-
//# sourceMappingURL=chunk-
|
|
23550
|
-
//# sourceMappingURL=chunk-
|
|
24575
|
+
export { FALLBACK_FIT_VIEW, MinimapCanvas, SceneViewCanvas, UnknownIcon, buildSceneViewCommands, computeFitView2, computeIndicatorCommand, eligibleContribution, evaluateEnabled, oklabToSrgbU8, oklchToOklab, renderSceneToCanvas, rgbaToHex, useActionsRegistry, useOptionalDepRegistry };
|
|
24576
|
+
//# sourceMappingURL=chunk-ISEK5LXT.js.map
|
|
24577
|
+
//# sourceMappingURL=chunk-ISEK5LXT.js.map
|