@weasel-js/labkit 0.8.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/_dts/{DrawCommand-DACv9lhe.d.ts → DrawCommand-BwRCZYuo.d.ts} +72 -6
- 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-FBZCQXI2.js → chunk-FP5LYDVX.js} +1229 -1227
- package/dist/chunk-FP5LYDVX.js.map +1 -0
- package/dist/{chunk-OHGEOTCV.js → chunk-HNXVLKBG.js} +3 -3
- package/dist/{chunk-OHGEOTCV.js.map → chunk-HNXVLKBG.js.map} +1 -1
- package/dist/{chunk-6V7ROR7G.js → chunk-XVBXYKFJ.js} +1168 -557
- package/dist/chunk-XVBXYKFJ.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 +1 -1
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +97 -69
- 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/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/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.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,12 +388,12 @@ 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;
|
|
@@ -488,7 +488,7 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
488
488
|
const direction = spec.direction ?? "*";
|
|
489
489
|
if (direction === "up" && !(e.deltaY < 0)) return false;
|
|
490
490
|
if (direction === "down" && !(e.deltaY > 0)) return false;
|
|
491
|
-
return
|
|
491
|
+
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
492
492
|
}
|
|
493
493
|
case "click": {
|
|
494
494
|
if (e.kind !== "click") return false;
|
|
@@ -498,12 +498,12 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
498
498
|
case "doubleClick": {
|
|
499
499
|
if (e.kind !== "doubleclick") return false;
|
|
500
500
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
501
|
-
return matchTarget(e.
|
|
501
|
+
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
502
502
|
}
|
|
503
503
|
case "contextMenu": {
|
|
504
504
|
if (e.kind !== "contextmenu") return false;
|
|
505
505
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
506
|
-
return matchTarget(e.
|
|
506
|
+
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
507
507
|
}
|
|
508
508
|
case "longPress": {
|
|
509
509
|
if (e.kind !== "longpress") return false;
|
|
@@ -547,8 +547,6 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
547
547
|
}
|
|
548
548
|
}
|
|
549
549
|
var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
|
|
550
|
-
|
|
551
|
-
// ../core/dist/chunk-6DAUIMTO.js
|
|
552
550
|
var routing_exports = {};
|
|
553
551
|
__export(routing_exports, {
|
|
554
552
|
GESTURE_DESCRIPTORS: () => GESTURE_DESCRIPTORS,
|
|
@@ -597,7 +595,11 @@ function defineTool(def) {
|
|
|
597
595
|
};
|
|
598
596
|
return {
|
|
599
597
|
id: def.id,
|
|
600
|
-
|
|
598
|
+
eligibility: {
|
|
599
|
+
focus: true,
|
|
600
|
+
capabilities: def.capabilities,
|
|
601
|
+
...def.hotkey ? { offhand: def.hotkey } : {}
|
|
602
|
+
},
|
|
601
603
|
actions: def.actions,
|
|
602
604
|
def,
|
|
603
605
|
presentation: def.presentation,
|
|
@@ -683,6 +685,255 @@ function argOf(spec, fallback) {
|
|
|
683
685
|
if ("direction" in spec) return spec.direction ?? fallback;
|
|
684
686
|
return fallback;
|
|
685
687
|
}
|
|
688
|
+
var DepRegistryContext = createContext(null);
|
|
689
|
+
function DepRegistryProvider({ children }) {
|
|
690
|
+
const sourcesRef = useRef(/* @__PURE__ */ new Map());
|
|
691
|
+
const registry4 = useMemo(() => ({
|
|
692
|
+
register: (name, source) => {
|
|
693
|
+
sourcesRef.current.set(name, source);
|
|
694
|
+
return () => {
|
|
695
|
+
sourcesRef.current.delete(name);
|
|
696
|
+
};
|
|
697
|
+
},
|
|
698
|
+
get: (name) => sourcesRef.current.get(name)?.()
|
|
699
|
+
}), []);
|
|
700
|
+
return /* @__PURE__ */ jsx(DepRegistryContext.Provider, { value: registry4, children });
|
|
701
|
+
}
|
|
702
|
+
function useDepRegistry() {
|
|
703
|
+
const r = useContext(DepRegistryContext);
|
|
704
|
+
if (r === null) {
|
|
705
|
+
throw new Error("useDepRegistry: no DepRegistryProvider in scope. Wrap your tree with <DepRegistryProvider> (typically inside <SceneCanvas>).");
|
|
706
|
+
}
|
|
707
|
+
return r;
|
|
708
|
+
}
|
|
709
|
+
function useOptionalDepRegistry() {
|
|
710
|
+
return useContext(DepRegistryContext);
|
|
711
|
+
}
|
|
712
|
+
function useDepSource(name, source) {
|
|
713
|
+
const registry4 = useDepRegistry();
|
|
714
|
+
const sourceRef = useRef(source);
|
|
715
|
+
sourceRef.current = source;
|
|
716
|
+
useEffect(() => {
|
|
717
|
+
return registry4.register(name, () => sourceRef.current());
|
|
718
|
+
}, [name, registry4]);
|
|
719
|
+
}
|
|
720
|
+
var DEV = (() => {
|
|
721
|
+
try {
|
|
722
|
+
return Boolean(import.meta.env?.DEV);
|
|
723
|
+
} catch {
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
})();
|
|
727
|
+
function buildDepsFromRequires(action, depRegistry) {
|
|
728
|
+
const requires = action.requires ?? [];
|
|
729
|
+
const entries = requires.map((name) => [name, depRegistry.get(name)]);
|
|
730
|
+
const deps = Object.fromEntries(entries);
|
|
731
|
+
if (DEV) {
|
|
732
|
+
const declared = new Set(requires);
|
|
733
|
+
return new Proxy(deps, {
|
|
734
|
+
get(target, prop, receiver) {
|
|
735
|
+
if (typeof prop === "string" && !declared.has(prop) && prop !== "then") {
|
|
736
|
+
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.`;
|
|
737
|
+
console.warn(message);
|
|
738
|
+
throw new Error(message);
|
|
739
|
+
}
|
|
740
|
+
return Reflect.get(target, prop, receiver);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
return deps;
|
|
745
|
+
}
|
|
746
|
+
function actionBindings(action) {
|
|
747
|
+
const gs = action.defaultBinding;
|
|
748
|
+
if (!gs) return [];
|
|
749
|
+
const raw = Array.isArray(gs) ? gs : [gs];
|
|
750
|
+
return raw.map((entry) => {
|
|
751
|
+
const isBoundObj = !("kind" in entry);
|
|
752
|
+
const spec = isBoundObj ? entry.spec : entry;
|
|
753
|
+
const opts = isBoundObj ? entry.opts : void 0;
|
|
754
|
+
return { spec, actionId: action.id, ...opts !== void 0 ? { opts } : {} };
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
var ActionDisabledReason = {
|
|
758
|
+
SelectionRequired: "selection-required",
|
|
759
|
+
NotApplicable: "not-applicable",
|
|
760
|
+
/** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
|
|
761
|
+
PredicateThrew: "predicate-threw"
|
|
762
|
+
};
|
|
763
|
+
var enabledSlowWarned = /* @__PURE__ */ new Set();
|
|
764
|
+
var enabledThrewWarned = /* @__PURE__ */ new Set();
|
|
765
|
+
var ENABLED_BUDGET_MS = 4;
|
|
766
|
+
function evaluateEnabled(action, deps) {
|
|
767
|
+
if (!action.enabled) return { enabled: true };
|
|
768
|
+
const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
|
|
769
|
+
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
770
|
+
const start = isDev ? now() : 0;
|
|
771
|
+
try {
|
|
772
|
+
const result = action.enabled(deps);
|
|
773
|
+
if (isDev) {
|
|
774
|
+
const elapsed = now() - start;
|
|
775
|
+
if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
|
|
776
|
+
enabledSlowWarned.add(action.id);
|
|
777
|
+
console.warn(
|
|
778
|
+
`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.`
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (result === true) return { enabled: true };
|
|
783
|
+
return { enabled: false, reason: result };
|
|
784
|
+
} catch (e) {
|
|
785
|
+
if (isDev && !enabledThrewWarned.has(action.id)) {
|
|
786
|
+
enabledThrewWarned.add(action.id);
|
|
787
|
+
console.warn(
|
|
788
|
+
`weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
|
|
789
|
+
e
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function validateActionId(id) {
|
|
796
|
+
if (id.length === 0) {
|
|
797
|
+
throw new Error(`weasel: action id may not be empty`);
|
|
798
|
+
}
|
|
799
|
+
if (RESERVED_ID_PREFIXES.has(id[0])) {
|
|
800
|
+
throw new Error(
|
|
801
|
+
`weasel: action id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
if (RESERVED_ID_NAMES.has(id)) {
|
|
805
|
+
throw new Error(
|
|
806
|
+
`weasel: action id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
function validateActionDefaultBinding(action) {
|
|
811
|
+
const gs = action.defaultBinding;
|
|
812
|
+
if (!gs) return;
|
|
813
|
+
const entries = Array.isArray(gs) ? gs : [gs];
|
|
814
|
+
for (const entry of entries) {
|
|
815
|
+
const spec = "kind" in entry ? entry : entry.spec;
|
|
816
|
+
const phase = spec.phase;
|
|
817
|
+
if (phase === void 0) continue;
|
|
818
|
+
const atoms = Array.isArray(phase) ? phase : [{ channel: "&", phase }];
|
|
819
|
+
for (const atom of atoms) {
|
|
820
|
+
if (atom.channel === "&") {
|
|
821
|
+
throw new Error(
|
|
822
|
+
`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.`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
var ActionsContext = createContext(null);
|
|
829
|
+
function ActionsProvider({ children }) {
|
|
830
|
+
const actionsRef = useRef(/* @__PURE__ */ new Map());
|
|
831
|
+
const versionRef = useRef(0);
|
|
832
|
+
const cachedRef = useRef([]);
|
|
833
|
+
const cachedVerRef = useRef(-1);
|
|
834
|
+
const listenersRef = useRef(/* @__PURE__ */ new Set());
|
|
835
|
+
const depReg = useOptionalDepRegistry();
|
|
836
|
+
const depRegRef = useRef(depReg);
|
|
837
|
+
depRegRef.current = depReg;
|
|
838
|
+
const wiredDepRegRef = useRef(null);
|
|
839
|
+
const dispatcherRef = useRef(null);
|
|
840
|
+
const registry4 = useMemo(() => {
|
|
841
|
+
const snapshot = () => {
|
|
842
|
+
const v = versionRef.current;
|
|
843
|
+
if (cachedVerRef.current === v) return cachedRef.current;
|
|
844
|
+
const out = Object.freeze(Array.from(actionsRef.current.values()));
|
|
845
|
+
cachedRef.current = out;
|
|
846
|
+
cachedVerRef.current = v;
|
|
847
|
+
return out;
|
|
848
|
+
};
|
|
849
|
+
const notify2 = () => {
|
|
850
|
+
for (const l of listenersRef.current) {
|
|
851
|
+
try {
|
|
852
|
+
l();
|
|
853
|
+
} catch (err) {
|
|
854
|
+
console.error("weasel ActionsRegistry: subscriber threw", err);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
return {
|
|
859
|
+
register: (action) => {
|
|
860
|
+
validateActionId(action.id);
|
|
861
|
+
validateActionDefaultBinding(action);
|
|
862
|
+
actionsRef.current.set(action.id, action);
|
|
863
|
+
versionRef.current++;
|
|
864
|
+
notify2();
|
|
865
|
+
return () => {
|
|
866
|
+
const cur = actionsRef.current.get(action.id);
|
|
867
|
+
if (cur === action) {
|
|
868
|
+
actionsRef.current.delete(action.id);
|
|
869
|
+
versionRef.current++;
|
|
870
|
+
notify2();
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
},
|
|
874
|
+
unregister: (id) => {
|
|
875
|
+
if (actionsRef.current.delete(id)) {
|
|
876
|
+
versionRef.current++;
|
|
877
|
+
notify2();
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
list: () => snapshot(),
|
|
881
|
+
trigger: (id, params) => {
|
|
882
|
+
const a = actionsRef.current.get(id);
|
|
883
|
+
if (!a) return false;
|
|
884
|
+
try {
|
|
885
|
+
if (a.invoker && a.invoker.timing === "immediate") {
|
|
886
|
+
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
887
|
+
const deps = !r ? {} : a.requires ? buildDepsFromRequires(a, r) : {
|
|
888
|
+
selection: r.get("selection"),
|
|
889
|
+
scene: r.get("scene"),
|
|
890
|
+
history: r.get("history"),
|
|
891
|
+
view: r.get("view"),
|
|
892
|
+
pointer: r.get("pointer"),
|
|
893
|
+
activeTool: r.get("activeTool"),
|
|
894
|
+
booleansAdapter: r.get("booleansAdapter")
|
|
895
|
+
};
|
|
896
|
+
a.invoker.run(deps, params);
|
|
897
|
+
}
|
|
898
|
+
} catch (err) {
|
|
899
|
+
console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
|
|
900
|
+
}
|
|
901
|
+
return true;
|
|
902
|
+
},
|
|
903
|
+
subscribe: (listener) => {
|
|
904
|
+
listenersRef.current.add(listener);
|
|
905
|
+
return () => {
|
|
906
|
+
listenersRef.current.delete(listener);
|
|
907
|
+
};
|
|
908
|
+
},
|
|
909
|
+
setDispatcher: (d) => {
|
|
910
|
+
dispatcherRef.current = d;
|
|
911
|
+
},
|
|
912
|
+
setDepRegistry: (r) => {
|
|
913
|
+
wiredDepRegRef.current = r;
|
|
914
|
+
},
|
|
915
|
+
begin: (id, params) => {
|
|
916
|
+
const disp = dispatcherRef.current;
|
|
917
|
+
if (!disp) return null;
|
|
918
|
+
const r = wiredDepRegRef.current ?? depRegRef.current;
|
|
919
|
+
const deps = r ? {
|
|
920
|
+
selection: r.get("selection"),
|
|
921
|
+
scene: r.get("scene"),
|
|
922
|
+
history: r.get("history"),
|
|
923
|
+
view: r.get("view"),
|
|
924
|
+
pointer: r.get("pointer"),
|
|
925
|
+
activeTool: r.get("activeTool"),
|
|
926
|
+
booleansAdapter: r.get("booleansAdapter")
|
|
927
|
+
} : {};
|
|
928
|
+
return disp.beginUiOngoing(id, deps, params);
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
}, []);
|
|
932
|
+
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: registry4, children });
|
|
933
|
+
}
|
|
934
|
+
function useActionsRegistry() {
|
|
935
|
+
return useContext(ActionsContext);
|
|
936
|
+
}
|
|
686
937
|
function findConflicts(tools) {
|
|
687
938
|
const entries = buildRouteRegistry(tools);
|
|
688
939
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -724,9 +975,15 @@ function predicateId(pred) {
|
|
|
724
975
|
}
|
|
725
976
|
return id;
|
|
726
977
|
}
|
|
978
|
+
function actionAsTool(action) {
|
|
979
|
+
return { id: action.id, eligibility: {}, bindings: actionBindings(action) };
|
|
980
|
+
}
|
|
727
981
|
function findScopedConflicts(scopes) {
|
|
728
982
|
const registry4 = Array.isArray(scopes.registry) ? scopes.registry : Object.values(scopes.registry);
|
|
729
|
-
const
|
|
983
|
+
const actions = scopes.actions ?? [];
|
|
984
|
+
const ambientActions = actions.filter((a) => a.scope !== "hotkey").map(actionAsTool);
|
|
985
|
+
const hotkeyActions = actions.filter((a) => a.scope === "hotkey").map(actionAsTool);
|
|
986
|
+
const ambient = [...scopes.ambient ?? [], ...ambientActions];
|
|
730
987
|
const out = [];
|
|
731
988
|
const seen = /* @__PURE__ */ new Set();
|
|
732
989
|
const add = (conflicts) => {
|
|
@@ -737,12 +994,13 @@ function findScopedConflicts(scopes) {
|
|
|
737
994
|
out.push(c);
|
|
738
995
|
}
|
|
739
996
|
};
|
|
740
|
-
for (const tool of [...registry4, ...ambient]) add(findConflicts([tool]));
|
|
997
|
+
for (const tool of [...registry4, ...scopes.ambient ?? []]) add(findConflicts([tool]));
|
|
741
998
|
if (ambient.length > 1) add(findConflicts(ambient));
|
|
742
|
-
const
|
|
743
|
-
(t) => t.def?.hotkey !== void 0
|
|
744
|
-
|
|
745
|
-
|
|
999
|
+
const hotkey = [
|
|
1000
|
+
...registry4.filter((t) => t.def?.hotkey !== void 0),
|
|
1001
|
+
...hotkeyActions
|
|
1002
|
+
];
|
|
1003
|
+
if (hotkey.length > 1) add(findConflicts(hotkey));
|
|
746
1004
|
return out;
|
|
747
1005
|
}
|
|
748
1006
|
function formatConflict(conflict) {
|
|
@@ -882,7 +1140,7 @@ function transformCoords(coords, m) {
|
|
|
882
1140
|
return out;
|
|
883
1141
|
}
|
|
884
1142
|
|
|
885
|
-
// ../core/dist/chunk-
|
|
1143
|
+
// ../core/dist/chunk-6SHZHXXR.js
|
|
886
1144
|
function resolveUnit(v, unitSystem) {
|
|
887
1145
|
if (typeof v === "number") return v;
|
|
888
1146
|
if (!unitSystem) {
|
|
@@ -1453,6 +1711,28 @@ var RECT_POSE_DESCRIPTOR = {
|
|
|
1453
1711
|
translate: RECT_POSE_DESCRIPTOR.translate,
|
|
1454
1712
|
intersectsRect: RECT_POSE_DESCRIPTOR.intersectsRect,
|
|
1455
1713
|
lerp: RECT_POSE_DESCRIPTOR.lerp});
|
|
1714
|
+
function remapRotatedLeaf(pose, src, dst) {
|
|
1715
|
+
const sx = src.width === 0 ? 1 : dst.width / src.width;
|
|
1716
|
+
const sy = src.height === 0 ? 1 : dst.height / src.height;
|
|
1717
|
+
const theta = pose.rotation ?? 0;
|
|
1718
|
+
const cos = Math.cos(theta);
|
|
1719
|
+
const sin = Math.sin(theta);
|
|
1720
|
+
const ux = sx * cos, uy = sy * sin;
|
|
1721
|
+
const vx = -sx * sin, vy = sy * cos;
|
|
1722
|
+
const width = pose.width * Math.hypot(ux, uy);
|
|
1723
|
+
const height = pose.height * Math.hypot(vx, vy);
|
|
1724
|
+
const rotation = Math.atan2(uy, ux);
|
|
1725
|
+
const cxNew = dst.x + (pose.x + pose.width / 2 - src.x) * sx;
|
|
1726
|
+
const cyNew = dst.y + (pose.y + pose.height / 2 - src.y) * sy;
|
|
1727
|
+
return {
|
|
1728
|
+
...pose,
|
|
1729
|
+
x: cxNew - width / 2,
|
|
1730
|
+
y: cyNew - height / 2,
|
|
1731
|
+
width,
|
|
1732
|
+
height,
|
|
1733
|
+
rotation
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1456
1736
|
var pathPoseDescriptor = {
|
|
1457
1737
|
getBounds: (path) => boundsOfPath(path),
|
|
1458
1738
|
remapBounds: (path, src, dst) => {
|
|
@@ -1549,14 +1829,29 @@ var RECT_ORIGIN_PROJECTION = {
|
|
|
1549
1829
|
getOrigin: (p) => ({ x: p.x, y: p.y }),
|
|
1550
1830
|
translate: (p, dx, dy) => ({ ...p, x: p.x + dx, y: p.y + dy })
|
|
1551
1831
|
};
|
|
1552
|
-
|
|
1553
|
-
|
|
1832
|
+
var MIN_SCALE = 1e-9;
|
|
1833
|
+
function pxExtent(px, scale2) {
|
|
1834
|
+
return {
|
|
1835
|
+
x: px / Math.max(MIN_SCALE, Math.abs(scale2.x)),
|
|
1836
|
+
y: px / Math.max(MIN_SCALE, Math.abs(scale2.y))
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
function scaleDelta(dx, dy, scale2) {
|
|
1840
|
+
return { x: dx * scale2.x, y: dy * scale2.y };
|
|
1841
|
+
}
|
|
1842
|
+
function withinPxBox(dx, dy, px, scale2) {
|
|
1843
|
+
const s = scaleDelta(dx, dy, scale2);
|
|
1844
|
+
return Math.abs(s.x) <= px && Math.abs(s.y) <= px;
|
|
1845
|
+
}
|
|
1846
|
+
function withinPxRadius(dx, dy, px, scale2) {
|
|
1847
|
+
const s = scaleDelta(dx, dy, scale2);
|
|
1848
|
+
return s.x * s.x + s.y * s.y <= px * px;
|
|
1554
1849
|
}
|
|
1555
1850
|
|
|
1556
1851
|
// ../core/dist/chunk-GVCNT7UH.js
|
|
1557
1852
|
var FACTORIES = /* @__PURE__ */ new Map();
|
|
1558
|
-
function registerOpFactory(name,
|
|
1559
|
-
FACTORIES.set(name,
|
|
1853
|
+
function registerOpFactory(name, build2) {
|
|
1854
|
+
FACTORIES.set(name, build2);
|
|
1560
1855
|
}
|
|
1561
1856
|
function rebuildOp(name, args) {
|
|
1562
1857
|
const f = FACTORIES.get(name);
|
|
@@ -1593,7 +1888,7 @@ function createDeleteOp(args) {
|
|
|
1593
1888
|
}
|
|
1594
1889
|
registerOpFactory("delete", (args) => createDeleteOp(args));
|
|
1595
1890
|
|
|
1596
|
-
// ../core/dist/chunk-
|
|
1891
|
+
// ../core/dist/chunk-UB6A6L77.js
|
|
1597
1892
|
function createTransformOp(args) {
|
|
1598
1893
|
const { id, from, to, label, coalesceKey = `transform:${id}` } = args;
|
|
1599
1894
|
return {
|
|
@@ -1674,7 +1969,7 @@ function snap(strategy, opts = {}) {
|
|
|
1674
1969
|
};
|
|
1675
1970
|
}
|
|
1676
1971
|
|
|
1677
|
-
// ../core/dist/chunk-
|
|
1972
|
+
// ../core/dist/chunk-Z3KNFTTS.js
|
|
1678
1973
|
function lockAspectWithModifier(opts = {}) {
|
|
1679
1974
|
const { key = "shift" } = opts;
|
|
1680
1975
|
return {
|
|
@@ -1870,15 +2165,167 @@ function materializePaste(adapter, snapshot, opts) {
|
|
|
1870
2165
|
return { ops, newIds, created };
|
|
1871
2166
|
}
|
|
1872
2167
|
|
|
1873
|
-
// ../core/dist/chunk-
|
|
2168
|
+
// ../core/dist/chunk-IO5Z75X4.js
|
|
2169
|
+
var counter = 0;
|
|
1874
2170
|
var registry = /* @__PURE__ */ new Map();
|
|
1875
2171
|
function getTexture(id) {
|
|
1876
2172
|
return registry.get(id) ?? null;
|
|
1877
2173
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
2174
|
+
function registerTexture(image) {
|
|
2175
|
+
const id = `tex_${++counter}`;
|
|
2176
|
+
registry.set(id, { source: image });
|
|
2177
|
+
return { id };
|
|
2178
|
+
}
|
|
2179
|
+
function tileSize(spec) {
|
|
2180
|
+
if (spec.size !== void 0) return spec.size;
|
|
2181
|
+
switch (spec.tile) {
|
|
2182
|
+
case "hatch":
|
|
2183
|
+
return 5;
|
|
2184
|
+
case "crosshatch":
|
|
2185
|
+
return 6;
|
|
2186
|
+
case "dots":
|
|
2187
|
+
return 6;
|
|
2188
|
+
case "chunks":
|
|
2189
|
+
return 48;
|
|
2190
|
+
default:
|
|
2191
|
+
return 8;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
function mulberry32(seed) {
|
|
2195
|
+
let s = seed | 0;
|
|
2196
|
+
return () => {
|
|
2197
|
+
s = s + 1831565813 | 0;
|
|
2198
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
2199
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2200
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
function tileGeometry(spec) {
|
|
2204
|
+
const s = tileSize(spec);
|
|
2205
|
+
const { color } = spec;
|
|
2206
|
+
switch (spec.tile) {
|
|
2207
|
+
case "hatch": {
|
|
2208
|
+
const width = spec.lineWidth ?? 1;
|
|
2209
|
+
return { size: s, shapes: forwardDiagonals(s, width, color) };
|
|
2210
|
+
}
|
|
2211
|
+
case "crosshatch": {
|
|
2212
|
+
const width = spec.lineWidth ?? 0.8;
|
|
2213
|
+
return {
|
|
2214
|
+
size: s,
|
|
2215
|
+
shapes: [...forwardDiagonals(s, width, color), ...backwardDiagonals(s, width, color)]
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
case "dots": {
|
|
2219
|
+
const r = spec.radius ?? 1;
|
|
2220
|
+
return { size: s, shapes: [{ kind: "circle", cx: s / 2, cy: s / 2, r, color }] };
|
|
2221
|
+
}
|
|
2222
|
+
case "chunks": {
|
|
2223
|
+
const density = spec.density ?? 0.35;
|
|
2224
|
+
const chunkSize = spec.chunkSize ?? 3;
|
|
2225
|
+
const rand = mulberry32(spec.seed ?? 42);
|
|
2226
|
+
const count = Math.round(s * s * density / (chunkSize * chunkSize));
|
|
2227
|
+
const shapes = [];
|
|
2228
|
+
if (spec.bg !== void 0) {
|
|
2229
|
+
shapes.push({ kind: "rect", x: 0, y: 0, width: s, height: s, color: spec.bg });
|
|
2230
|
+
}
|
|
2231
|
+
for (let i = 0; i < count; i++) {
|
|
2232
|
+
const cx = rand() * s;
|
|
2233
|
+
const cy = rand() * s;
|
|
2234
|
+
const w = chunkSize * (0.5 + rand());
|
|
2235
|
+
const h = chunkSize * (0.5 + rand());
|
|
2236
|
+
const rotation = rand() * Math.PI;
|
|
2237
|
+
shapes.push({ kind: "ellipse", cx, cy, rx: w / 2, ry: h / 2, rotation, color });
|
|
2238
|
+
}
|
|
2239
|
+
return { size: s, shapes };
|
|
2240
|
+
}
|
|
2241
|
+
default:
|
|
2242
|
+
return { size: s, shapes: [] };
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
function forwardDiagonals(s, width, color) {
|
|
2246
|
+
return [
|
|
2247
|
+
{ kind: "line", x1: -1, y1: s + 1, x2: s + 1, y2: -1, width, color },
|
|
2248
|
+
{ kind: "line", x1: -1, y1: 1, x2: 1, y2: -1, width, color },
|
|
2249
|
+
{ kind: "line", x1: s - 1, y1: s + 1, x2: s + 1, y2: s - 1, width, color }
|
|
2250
|
+
];
|
|
2251
|
+
}
|
|
2252
|
+
function backwardDiagonals(s, width, color) {
|
|
2253
|
+
return [
|
|
2254
|
+
{ kind: "line", x1: -1, y1: -1, x2: s + 1, y2: s + 1, width, color },
|
|
2255
|
+
{ kind: "line", x1: s - 1, y1: -1, x2: s + 1, y2: 1, width, color },
|
|
2256
|
+
{ kind: "line", x1: -1, y1: s - 1, x2: 1, y2: s + 1, width, color }
|
|
2257
|
+
];
|
|
2258
|
+
}
|
|
2259
|
+
function drawTileShapes(oc, geometry) {
|
|
2260
|
+
for (const shape of geometry.shapes) {
|
|
2261
|
+
if (shape.kind === "line") {
|
|
2262
|
+
oc.strokeStyle = shape.color;
|
|
2263
|
+
oc.lineWidth = shape.width;
|
|
2264
|
+
oc.beginPath();
|
|
2265
|
+
oc.moveTo(shape.x1, shape.y1);
|
|
2266
|
+
oc.lineTo(shape.x2, shape.y2);
|
|
2267
|
+
oc.stroke();
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
oc.fillStyle = shape.color;
|
|
2271
|
+
if (shape.kind === "rect") {
|
|
2272
|
+
oc.fillRect(shape.x, shape.y, shape.width, shape.height);
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
oc.beginPath();
|
|
2276
|
+
if (shape.kind === "circle") {
|
|
2277
|
+
oc.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2);
|
|
2278
|
+
} else {
|
|
2279
|
+
oc.ellipse(shape.cx, shape.cy, shape.rx, shape.ry, shape.rotation, 0, Math.PI * 2);
|
|
2280
|
+
}
|
|
2281
|
+
oc.fill();
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
function createTilePattern(opts) {
|
|
2285
|
+
let off;
|
|
2286
|
+
try {
|
|
2287
|
+
off = new OffscreenCanvas(opts.size, opts.size);
|
|
2288
|
+
} catch {
|
|
2289
|
+
if (typeof document === "undefined") return null;
|
|
2290
|
+
const c = document.createElement("canvas");
|
|
2291
|
+
c.width = opts.size;
|
|
2292
|
+
c.height = opts.size;
|
|
2293
|
+
off = c;
|
|
2294
|
+
}
|
|
2295
|
+
const oc = off.getContext("2d");
|
|
2296
|
+
if (!oc) return null;
|
|
2297
|
+
opts.draw(oc, opts.size);
|
|
2298
|
+
let bitmap;
|
|
2299
|
+
if (typeof off.transferToImageBitmap === "function") {
|
|
2300
|
+
bitmap = off.transferToImageBitmap();
|
|
2301
|
+
} else {
|
|
2302
|
+
return null;
|
|
2303
|
+
}
|
|
2304
|
+
return registerTexture(bitmap);
|
|
2305
|
+
}
|
|
2306
|
+
function build(spec) {
|
|
2307
|
+
const geometry = tileGeometry(spec);
|
|
2308
|
+
return createTilePattern({
|
|
2309
|
+
size: tileSize(spec),
|
|
2310
|
+
draw: (oc) => drawTileShapes(oc, geometry)
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
function hatch(params) {
|
|
2314
|
+
return build({ tile: "hatch", ...params });
|
|
2315
|
+
}
|
|
2316
|
+
function crosshatch(params) {
|
|
2317
|
+
return build({ tile: "crosshatch", ...params });
|
|
2318
|
+
}
|
|
2319
|
+
function dots(params) {
|
|
2320
|
+
return build({ tile: "dots", ...params });
|
|
2321
|
+
}
|
|
2322
|
+
function chunks(params) {
|
|
2323
|
+
return build({ tile: "chunks", ...params });
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// ../font/dist/index.js
|
|
2327
|
+
var subscribers = /* @__PURE__ */ new Set();
|
|
2328
|
+
var generation = 0;
|
|
1882
2329
|
function glyphGeneration() {
|
|
1883
2330
|
return generation;
|
|
1884
2331
|
}
|
|
@@ -2012,8 +2459,8 @@ function alphaToSdf(alpha, width, height, radius, cutoff) {
|
|
|
2012
2459
|
edt2d(gridInner, width, height, f, d, v, z);
|
|
2013
2460
|
const out = new Uint8Array(n);
|
|
2014
2461
|
for (let i = 0; i < n; i++) {
|
|
2015
|
-
const
|
|
2016
|
-
const byte = Math.round(255 - 255 * (
|
|
2462
|
+
const dist3 = Math.sqrt(gridOuter[i]) - Math.sqrt(gridInner[i]);
|
|
2463
|
+
const byte = Math.round(255 - 255 * (dist3 / radius + cutoff));
|
|
2017
2464
|
out[i] = byte < 0 ? 0 : byte > 255 ? 255 : byte;
|
|
2018
2465
|
}
|
|
2019
2466
|
return out;
|
|
@@ -2219,17 +2666,17 @@ function dynamicPageTextureId(page) {
|
|
|
2219
2666
|
return `weasel-dyn-sdf-page-${page}`;
|
|
2220
2667
|
}
|
|
2221
2668
|
var uploadedVersions = /* @__PURE__ */ new WeakMap();
|
|
2222
|
-
function syncDynamicPageTexture(
|
|
2669
|
+
function syncDynamicPageTexture(cache7, pageIndex) {
|
|
2223
2670
|
const page = pages[pageIndex];
|
|
2224
2671
|
if (!page) return false;
|
|
2225
2672
|
const id = dynamicPageTextureId(pageIndex);
|
|
2226
|
-
let seen = uploadedVersions.get(
|
|
2673
|
+
let seen = uploadedVersions.get(cache7);
|
|
2227
2674
|
if (!seen) {
|
|
2228
2675
|
seen = /* @__PURE__ */ new Map();
|
|
2229
|
-
uploadedVersions.set(
|
|
2676
|
+
uploadedVersions.set(cache7, seen);
|
|
2230
2677
|
}
|
|
2231
|
-
if (!
|
|
2232
|
-
|
|
2678
|
+
if (!cache7.has(id)) {
|
|
2679
|
+
cache7.uploadR8(id, PAGE_SIZE, PAGE_SIZE, page.data);
|
|
2233
2680
|
seen.set(pageIndex, page.version);
|
|
2234
2681
|
return true;
|
|
2235
2682
|
}
|
|
@@ -2242,7 +2689,7 @@ function syncDynamicPageTexture(cache6, pageIndex) {
|
|
|
2242
2689
|
const src = (patch.y + row) * PAGE_SIZE + patch.x;
|
|
2243
2690
|
tight.set(page.data.subarray(src, src + patch.w), row * patch.w);
|
|
2244
2691
|
}
|
|
2245
|
-
|
|
2692
|
+
cache7.subImageR8(id, patch.x, patch.y, patch.w, patch.h, tight);
|
|
2246
2693
|
}
|
|
2247
2694
|
seen.set(pageIndex, page.version);
|
|
2248
2695
|
return true;
|
|
@@ -2639,8 +3086,8 @@ function eligibleForMode(mode, toolTags) {
|
|
|
2639
3086
|
}
|
|
2640
3087
|
return false;
|
|
2641
3088
|
}
|
|
2642
|
-
function
|
|
2643
|
-
return eligibleForMode(reg.current(),
|
|
3089
|
+
function eligibleContribution(reg, entry) {
|
|
3090
|
+
return eligibleForMode(reg.current(), entry.eligibility?.capabilities ?? []);
|
|
2644
3091
|
}
|
|
2645
3092
|
|
|
2646
3093
|
// ../history/dist/index.js
|
|
@@ -3009,7 +3456,7 @@ function serialToEntry(se, custom, logger) {
|
|
|
3009
3456
|
};
|
|
3010
3457
|
}
|
|
3011
3458
|
|
|
3012
|
-
// ../core/dist/chunk-
|
|
3459
|
+
// ../core/dist/chunk-D2C6K6FO.js
|
|
3013
3460
|
function worldToScreen(worldX, worldY, view) {
|
|
3014
3461
|
return [view.panX + worldX * view.zoom.x, view.panY + worldY * view.zoom.y];
|
|
3015
3462
|
}
|
|
@@ -3260,6 +3707,58 @@ var GRAD_FILL_UNIFORMS = [
|
|
|
3260
3707
|
"u_gradAngle"
|
|
3261
3708
|
];
|
|
3262
3709
|
var GRAD_FILL_ATTRIBUTES = ["a_position"];
|
|
3710
|
+
var PATTERN_VERT_SRC = (
|
|
3711
|
+
/* glsl */
|
|
3712
|
+
`#version 300 es
|
|
3713
|
+
in vec2 a_position;
|
|
3714
|
+
uniform mat3 u_proj;
|
|
3715
|
+
uniform mat3 u_model;
|
|
3716
|
+
uniform mat3 u_worldInv;
|
|
3717
|
+
out vec2 v_world;
|
|
3718
|
+
void main() {
|
|
3719
|
+
vec3 screen = u_model * vec3(a_position, 1.0);
|
|
3720
|
+
vec3 clip = u_proj * vec3(screen.xy, 1.0);
|
|
3721
|
+
gl_Position = vec4(clip.xy, 0.0, 1.0);
|
|
3722
|
+
vec3 world = u_worldInv * vec3(screen.xy, 1.0);
|
|
3723
|
+
v_world = world.xy;
|
|
3724
|
+
}
|
|
3725
|
+
`
|
|
3726
|
+
);
|
|
3727
|
+
var PATTERN_FRAG_SRC = (
|
|
3728
|
+
/* glsl */
|
|
3729
|
+
`#version 300 es
|
|
3730
|
+
precision highp float;
|
|
3731
|
+
in vec2 v_world;
|
|
3732
|
+
uniform sampler2D u_sampler;
|
|
3733
|
+
uniform vec2 u_tileOrigin;
|
|
3734
|
+
uniform vec2 u_tileSize;
|
|
3735
|
+
uniform float u_opacity;
|
|
3736
|
+
uniform float u_alpha;
|
|
3737
|
+
uniform mat4 u_colorMatrix;
|
|
3738
|
+
uniform vec4 u_colorBias;
|
|
3739
|
+
out vec4 outColor;
|
|
3740
|
+
void main() {
|
|
3741
|
+
vec2 uv = (v_world - u_tileOrigin) / max(u_tileSize, vec2(0.0001));
|
|
3742
|
+
vec4 texel = texture(u_sampler, uv);
|
|
3743
|
+
vec4 mapped = clamp(u_colorMatrix * texel + u_colorBias, 0.0, 1.0);
|
|
3744
|
+
float a = mapped.a * u_opacity * u_alpha;
|
|
3745
|
+
outColor = vec4(mapped.rgb * a, a);
|
|
3746
|
+
}
|
|
3747
|
+
`
|
|
3748
|
+
);
|
|
3749
|
+
var PATTERN_FILL_UNIFORMS = [
|
|
3750
|
+
"u_proj",
|
|
3751
|
+
"u_model",
|
|
3752
|
+
"u_worldInv",
|
|
3753
|
+
"u_sampler",
|
|
3754
|
+
"u_tileOrigin",
|
|
3755
|
+
"u_tileSize",
|
|
3756
|
+
"u_opacity",
|
|
3757
|
+
"u_alpha",
|
|
3758
|
+
"u_colorMatrix",
|
|
3759
|
+
"u_colorBias"
|
|
3760
|
+
];
|
|
3761
|
+
var PATTERN_FILL_ATTRIBUTES = ["a_position"];
|
|
3263
3762
|
var GLMeshCache = class {
|
|
3264
3763
|
constructor(gl, aPositionLoc) {
|
|
3265
3764
|
this.gl = gl;
|
|
@@ -3382,17 +3881,20 @@ var GLTextureCache = class {
|
|
|
3382
3881
|
has(id) {
|
|
3383
3882
|
return this.map.has(id);
|
|
3384
3883
|
}
|
|
3385
|
-
|
|
3884
|
+
/** Uploads once per id; `wrap` therefore applies on first upload only.
|
|
3885
|
+
* An id is either a pattern tile or a shader texture, never both. */
|
|
3886
|
+
upload(id, source, wrap = "clamp") {
|
|
3386
3887
|
if (this.map.has(id)) return id;
|
|
3387
3888
|
const gl = this.gl;
|
|
3388
3889
|
const tex = gl.createTexture();
|
|
3389
3890
|
if (!tex) throw new Error(`GLTextureCache: createTexture failed for id="${id}"`);
|
|
3891
|
+
const wrapMode = wrap === "repeat" ? gl.REPEAT : gl.CLAMP_TO_EDGE;
|
|
3390
3892
|
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
3391
3893
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
|
|
3392
3894
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
3393
3895
|
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,
|
|
3896
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapMode);
|
|
3897
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapMode);
|
|
3396
3898
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
3397
3899
|
this.map.set(id, tex);
|
|
3398
3900
|
return id;
|
|
@@ -3987,6 +4489,18 @@ function scale(m, sx, sy) {
|
|
|
3987
4489
|
const s = create(sx, 0, 0, sy, 0, 0);
|
|
3988
4490
|
return multiply2(m, s);
|
|
3989
4491
|
}
|
|
4492
|
+
function invert(m) {
|
|
4493
|
+
const a = m[0], b = m[1];
|
|
4494
|
+
const c = m[3], d = m[4];
|
|
4495
|
+
const tx = m[6], ty = m[7];
|
|
4496
|
+
const det = a * d - b * c;
|
|
4497
|
+
if (det === 0 || !Number.isFinite(det)) return identity();
|
|
4498
|
+
const ia = d / det;
|
|
4499
|
+
const ib = -b / det;
|
|
4500
|
+
const ic = -c / det;
|
|
4501
|
+
const id = a / det;
|
|
4502
|
+
return create(ia, ib, ic, id, -(ia * tx + ic * ty), -(ib * tx + id * ty));
|
|
4503
|
+
}
|
|
3990
4504
|
function apply(m, x, y) {
|
|
3991
4505
|
const a = m[0], b = m[1];
|
|
3992
4506
|
const c = m[3], d = m[4];
|
|
@@ -4014,6 +4528,7 @@ var mat3 = {
|
|
|
4014
4528
|
multiply: multiply2,
|
|
4015
4529
|
translate: translate2,
|
|
4016
4530
|
scale,
|
|
4531
|
+
invert,
|
|
4017
4532
|
apply,
|
|
4018
4533
|
screenToClip
|
|
4019
4534
|
};
|
|
@@ -5916,28 +6431,42 @@ function drawPathFillPattern(ctx, fill, handle) {
|
|
|
5916
6431
|
if (isDev3) console.warn(`weasel: pattern TextureHandle "${tex.id}" not registered`);
|
|
5917
6432
|
return;
|
|
5918
6433
|
}
|
|
5919
|
-
ctx.textureCache.upload(tex.id, entry.source);
|
|
6434
|
+
ctx.textureCache.upload(tex.id, entry.source, "repeat");
|
|
5920
6435
|
const gl = ctx.gl;
|
|
5921
|
-
gl.useProgram(ctx.
|
|
6436
|
+
gl.useProgram(ctx.patternFill.handle);
|
|
5922
6437
|
gl.bindVertexArray(handle.vao);
|
|
5923
|
-
setProjAndModel(ctx, ctx.
|
|
5924
|
-
setColorMatrixUniforms(ctx, ctx.
|
|
6438
|
+
setProjAndModel(ctx, ctx.patternFill);
|
|
6439
|
+
setColorMatrixUniforms(ctx, ctx.patternFill);
|
|
6440
|
+
gl.uniformMatrix3fv(ctx.patternFill.uniform("u_worldInv"), false, gradientSpaceInverse(ctx, fill.units));
|
|
6441
|
+
const [tw, th] = textureSize(entry.source);
|
|
6442
|
+
const origin = fill.origin ?? { x: 0, y: 0 };
|
|
6443
|
+
gl.uniform2f(ctx.patternFill.uniform("u_tileOrigin"), origin.x, origin.y);
|
|
6444
|
+
gl.uniform2f(ctx.patternFill.uniform("u_tileSize"), tw, th);
|
|
5925
6445
|
ctx.textureCache.bind(tex.id, 0);
|
|
5926
|
-
gl.uniform1i(ctx.
|
|
5927
|
-
gl.uniform1f(ctx.
|
|
5928
|
-
gl.uniform1f(ctx.
|
|
6446
|
+
gl.uniform1i(ctx.patternFill.uniform("u_sampler"), 0);
|
|
6447
|
+
gl.uniform1f(ctx.patternFill.uniform("u_opacity"), fill.opacity ?? 1);
|
|
6448
|
+
gl.uniform1f(ctx.patternFill.uniform("u_alpha"), ctx.state.alpha);
|
|
5929
6449
|
applyClipTest(ctx);
|
|
5930
6450
|
gl.drawElements(gl.TRIANGLES, handle.indexCount, gl.UNSIGNED_INT, 0);
|
|
5931
6451
|
gl.bindVertexArray(null);
|
|
5932
6452
|
}
|
|
6453
|
+
function textureSize(source) {
|
|
6454
|
+
const w = "naturalWidth" in source ? source.naturalWidth : source.width;
|
|
6455
|
+
const h = "naturalHeight" in source ? source.naturalHeight : source.height;
|
|
6456
|
+
return [w || 1, h || 1];
|
|
6457
|
+
}
|
|
6458
|
+
function gradientSpaceInverse(ctx, units) {
|
|
6459
|
+
if (units === "local") return mat3.invert(ctx.state.transform);
|
|
6460
|
+
if (units === "world" && ctx.viewMatrix) return mat3.invert(ctx.viewMatrix);
|
|
6461
|
+
return mat3.identity();
|
|
6462
|
+
}
|
|
5933
6463
|
function drawPathFillGradient(ctx, fill, handle) {
|
|
5934
6464
|
const gl = ctx.gl;
|
|
5935
6465
|
const key = ctx.gradRampCache.upload(fill.stops);
|
|
5936
6466
|
gl.useProgram(ctx.gradFill.handle);
|
|
5937
6467
|
gl.bindVertexArray(handle.vao);
|
|
5938
6468
|
setProjAndModel(ctx, ctx.gradFill);
|
|
5939
|
-
|
|
5940
|
-
gl.uniformMatrix3fv(ctx.gradFill.uniform("u_worldInv"), false, identity3);
|
|
6469
|
+
gl.uniformMatrix3fv(ctx.gradFill.uniform("u_worldInv"), false, gradientSpaceInverse(ctx, fill.units));
|
|
5941
6470
|
ctx.gradRampCache.bind(key, 0);
|
|
5942
6471
|
gl.uniform1i(ctx.gradFill.uniform("u_ramp"), 0);
|
|
5943
6472
|
gl.uniform1f(ctx.gradFill.uniform("u_alpha"), ctx.state.alpha);
|
|
@@ -6457,6 +6986,11 @@ function drawImage(ctx, cmd) {
|
|
|
6457
6986
|
setProjAndModel(ctx, ctx.imageFill);
|
|
6458
6987
|
setColorMatrixUniforms(ctx, ctx.imageFill);
|
|
6459
6988
|
ctx.imageCache.bind(cmd.image, 0);
|
|
6989
|
+
gl.texParameteri(
|
|
6990
|
+
gl.TEXTURE_2D,
|
|
6991
|
+
gl.TEXTURE_MAG_FILTER,
|
|
6992
|
+
cmd.sampling === "nearest" ? gl.NEAREST : gl.LINEAR
|
|
6993
|
+
);
|
|
6460
6994
|
gl.uniform1i(ctx.imageFill.uniform("u_sampler"), 0);
|
|
6461
6995
|
gl.uniform1f(ctx.imageFill.uniform("u_opacity"), cmd.opacity ?? 1);
|
|
6462
6996
|
gl.uniform1f(ctx.imageFill.uniform("u_alpha"), ctx.state.alpha);
|
|
@@ -6543,6 +7077,7 @@ var WeaselRenderer = class {
|
|
|
6543
7077
|
textSdfR8;
|
|
6544
7078
|
imageFill;
|
|
6545
7079
|
gradFill;
|
|
7080
|
+
patternFill;
|
|
6546
7081
|
meshCache;
|
|
6547
7082
|
textureCache;
|
|
6548
7083
|
imageCache;
|
|
@@ -6618,6 +7153,9 @@ var WeaselRenderer = class {
|
|
|
6618
7153
|
this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
|
|
6619
7154
|
this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
|
|
6620
7155
|
this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
|
|
7156
|
+
this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
|
|
7157
|
+
this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
|
|
7158
|
+
this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
|
|
6621
7159
|
const aPos = this.pathFill.attribute("a_position");
|
|
6622
7160
|
if (aPos === void 0) throw new Error("WeaselRenderer: a_position not found in path-fill shader");
|
|
6623
7161
|
this.meshCache = new GLMeshCache(this.gl, aPos);
|
|
@@ -6726,6 +7264,9 @@ var WeaselRenderer = class {
|
|
|
6726
7264
|
this.gradFill = new ShaderProgram(this.gl, GRAD_VERT_SRC, GRAD_FRAG_SRC);
|
|
6727
7265
|
this.gradFill.lookupUniforms(GRAD_FILL_UNIFORMS);
|
|
6728
7266
|
this.gradFill.lookupAttributes(GRAD_FILL_ATTRIBUTES);
|
|
7267
|
+
this.patternFill = new ShaderProgram(this.gl, PATTERN_VERT_SRC, PATTERN_FRAG_SRC);
|
|
7268
|
+
this.patternFill.lookupUniforms(PATTERN_FILL_UNIFORMS);
|
|
7269
|
+
this.patternFill.lookupAttributes(PATTERN_FILL_ATTRIBUTES);
|
|
6729
7270
|
const aPos = this.pathFill.attribute("a_position");
|
|
6730
7271
|
if (aPos === void 0) throw new Error("a_position missing after restore");
|
|
6731
7272
|
this.meshCache = new GLMeshCache(this.gl, aPos);
|
|
@@ -6797,7 +7338,15 @@ var WeaselRenderer = class {
|
|
|
6797
7338
|
if (this.rectIbo) gl.deleteBuffer(this.rectIbo);
|
|
6798
7339
|
if (this.rectVao) gl.deleteVertexArray(this.rectVao);
|
|
6799
7340
|
}
|
|
6800
|
-
|
|
7341
|
+
/**
|
|
7342
|
+
* Draw one frame.
|
|
7343
|
+
*
|
|
7344
|
+
* `viewMatrix` is the frame's world→screen transform. It is only consulted
|
|
7345
|
+
* by `units: 'world'` gradients, which fall back to screen space without
|
|
7346
|
+
* it — every other command carries its own transform in the stream, so
|
|
7347
|
+
* callers with no view concept can keep calling `render(commands)`.
|
|
7348
|
+
*/
|
|
7349
|
+
render(commands, viewMatrix) {
|
|
6801
7350
|
if (this.contextLost || this.disposed) return;
|
|
6802
7351
|
const gl = this.gl;
|
|
6803
7352
|
this.meshCache.drainPendingDeletes();
|
|
@@ -6812,6 +7361,7 @@ var WeaselRenderer = class {
|
|
|
6812
7361
|
textSdfR8: this.textSdfR8,
|
|
6813
7362
|
imageFill: this.imageFill,
|
|
6814
7363
|
gradFill: this.gradFill,
|
|
7364
|
+
patternFill: this.patternFill,
|
|
6815
7365
|
meshCache: this.meshCache,
|
|
6816
7366
|
textureCache: this.textureCache,
|
|
6817
7367
|
imageCache: this.imageCache,
|
|
@@ -6826,7 +7376,8 @@ var WeaselRenderer = class {
|
|
|
6826
7376
|
heightCss: this.heightCss,
|
|
6827
7377
|
clipDepth: 0,
|
|
6828
7378
|
flattenTolerance: this.flattenTolerance,
|
|
6829
|
-
textOutlineMinScreenSize: this.textOutlineMinScreenSize
|
|
7379
|
+
textOutlineMinScreenSize: this.textOutlineMinScreenSize,
|
|
7380
|
+
viewMatrix
|
|
6830
7381
|
};
|
|
6831
7382
|
for (const cmd of commands) dispatch(ctx, cmd);
|
|
6832
7383
|
this.meshCache.freeTransient();
|
|
@@ -6874,6 +7425,10 @@ var WeaselRenderer = class {
|
|
|
6874
7425
|
return this.gradFill;
|
|
6875
7426
|
}
|
|
6876
7427
|
/** @internal */
|
|
7428
|
+
_patternFill() {
|
|
7429
|
+
return this.patternFill;
|
|
7430
|
+
}
|
|
7431
|
+
/** @internal */
|
|
6877
7432
|
_meshCache() {
|
|
6878
7433
|
return this.meshCache;
|
|
6879
7434
|
}
|
|
@@ -6934,6 +7489,9 @@ function alignedStrokeRect(rect, align, width = 1) {
|
|
|
6934
7489
|
height: rect.height + 2 * d
|
|
6935
7490
|
};
|
|
6936
7491
|
}
|
|
7492
|
+
function meanScale(s) {
|
|
7493
|
+
return Math.sqrt(s.x * s.y);
|
|
7494
|
+
}
|
|
6937
7495
|
var DEFAULT_LINE = {
|
|
6938
7496
|
paint: { fill: "solid", color: "rgba(255, 255, 255, 0.08)" },
|
|
6939
7497
|
width: 1
|
|
@@ -7205,244 +7763,6 @@ function isEditableTarget2(target) {
|
|
|
7205
7763
|
const ce = target.getAttribute("contenteditable");
|
|
7206
7764
|
return ce === "true" || ce === "";
|
|
7207
7765
|
}
|
|
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
|
-
}
|
|
7439
|
-
};
|
|
7440
|
-
}, []);
|
|
7441
|
-
return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: registry22, children });
|
|
7442
|
-
}
|
|
7443
|
-
function useActionsRegistry() {
|
|
7444
|
-
return useContext(ActionsContext);
|
|
7445
|
-
}
|
|
7446
7766
|
function resolveParams(params) {
|
|
7447
7767
|
if (params === void 0) return void 0;
|
|
7448
7768
|
return typeof params === "function" ? params() : params;
|
|
@@ -7977,6 +8297,60 @@ var moveAction = {
|
|
|
7977
8297
|
*/
|
|
7978
8298
|
enabled: () => true
|
|
7979
8299
|
};
|
|
8300
|
+
function poseRotationOf(pose) {
|
|
8301
|
+
const p = pose;
|
|
8302
|
+
if (p.rotation && p.x != null && p.y != null && p.width != null && p.height != null) {
|
|
8303
|
+
return { cx: p.x + p.width / 2, cy: p.y + p.height / 2, rotation: p.rotation };
|
|
8304
|
+
}
|
|
8305
|
+
return null;
|
|
8306
|
+
}
|
|
8307
|
+
function rotatePathAround(path, cx, cy, rotation) {
|
|
8308
|
+
if (path.kind === "rect") {
|
|
8309
|
+
return rotateRectCorners(path.x, path.y, path.width, path.height, cx, cy, rotation);
|
|
8310
|
+
}
|
|
8311
|
+
return rotatePolygon(path, cx, cy, rotation);
|
|
8312
|
+
}
|
|
8313
|
+
function rotatePolygon(path, cx, cy, rotation) {
|
|
8314
|
+
const cos = Math.cos(rotation);
|
|
8315
|
+
const sin = Math.sin(rotation);
|
|
8316
|
+
const { commands, coords } = path;
|
|
8317
|
+
const next = new Float32Array(coords.length);
|
|
8318
|
+
let ci = 0;
|
|
8319
|
+
for (let i = 0; i < commands.length; i++) {
|
|
8320
|
+
const len = COORD_COUNT3[commands[i]];
|
|
8321
|
+
for (let k = 0; k < len; k += 2) {
|
|
8322
|
+
const dx = coords[ci + k] - cx;
|
|
8323
|
+
const dy = coords[ci + k + 1] - cy;
|
|
8324
|
+
next[ci + k] = cx + dx * cos - dy * sin;
|
|
8325
|
+
next[ci + k + 1] = cy + dx * sin + dy * cos;
|
|
8326
|
+
}
|
|
8327
|
+
ci += len;
|
|
8328
|
+
}
|
|
8329
|
+
return { kind: "polygon", commands: path.commands, coords: next, fillRule: path.fillRule };
|
|
8330
|
+
}
|
|
8331
|
+
function rotateRectCorners(x, y, w, h, cx, cy, rotation) {
|
|
8332
|
+
const cos = Math.cos(rotation);
|
|
8333
|
+
const sin = Math.sin(rotation);
|
|
8334
|
+
const rotate2 = (px, py) => {
|
|
8335
|
+
const dx = px - cx;
|
|
8336
|
+
const dy = py - cy;
|
|
8337
|
+
return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
|
|
8338
|
+
};
|
|
8339
|
+
const [x0, y0] = rotate2(x, y);
|
|
8340
|
+
const [x1, y1] = rotate2(x + w, y);
|
|
8341
|
+
const [x2, y2] = rotate2(x + w, y + h);
|
|
8342
|
+
const [x3, y3] = rotate2(x, y + h);
|
|
8343
|
+
const commands = new Uint8Array([PATH_M2, PATH_L2, PATH_L2, PATH_L2, PATH_Z2]);
|
|
8344
|
+
const coords = new Float32Array([x0, y0, x1, y1, x2, y2, x3, y3]);
|
|
8345
|
+
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
8346
|
+
}
|
|
8347
|
+
var COORD_COUNT3 = {
|
|
8348
|
+
[PATH_M2]: 2,
|
|
8349
|
+
[PATH_L2]: 2,
|
|
8350
|
+
[PATH_C2]: 6,
|
|
8351
|
+
[PATH_Q2]: 4,
|
|
8352
|
+
[PATH_Z2]: 0
|
|
8353
|
+
};
|
|
7980
8354
|
function aabbCenter(bounds) {
|
|
7981
8355
|
return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 };
|
|
7982
8356
|
}
|
|
@@ -8269,6 +8643,13 @@ var resizeAction = {
|
|
|
8269
8643
|
const computePose = (id) => {
|
|
8270
8644
|
const sp = scratch.startPoses.get(id);
|
|
8271
8645
|
if (sp === void 0) return void 0;
|
|
8646
|
+
if (isGroupPath && poseRotationOf(sp) !== null) {
|
|
8647
|
+
return remapRotatedLeaf(
|
|
8648
|
+
sp,
|
|
8649
|
+
scratch.originBounds,
|
|
8650
|
+
proposedBounds
|
|
8651
|
+
);
|
|
8652
|
+
}
|
|
8272
8653
|
return scratch.geometry.remapBounds(sp, scratch.originBounds, proposedBounds);
|
|
8273
8654
|
};
|
|
8274
8655
|
scratch.previews.clear();
|
|
@@ -8889,9 +9270,9 @@ var clearSelectionAction = {
|
|
|
8889
9270
|
return true;
|
|
8890
9271
|
}
|
|
8891
9272
|
};
|
|
8892
|
-
var
|
|
9273
|
+
var counter2 = 0;
|
|
8893
9274
|
function freshNodeId() {
|
|
8894
|
-
return asNodeId(`n${(
|
|
9275
|
+
return asNodeId(`n${(counter2++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
|
|
8895
9276
|
}
|
|
8896
9277
|
function translatePose(pose, dx, dy) {
|
|
8897
9278
|
const p = pose;
|
|
@@ -9108,7 +9489,13 @@ function makeViewportZoomAction(opts = {}) {
|
|
|
9108
9489
|
};
|
|
9109
9490
|
}
|
|
9110
9491
|
makeViewportZoomAction();
|
|
9111
|
-
|
|
9492
|
+
function bodyPredicate(fn) {
|
|
9493
|
+
return Object.assign(fn, { readsAffordance: false });
|
|
9494
|
+
}
|
|
9495
|
+
var isBody = bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body" || bodyTarget === "unselected-body");
|
|
9496
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "selected-body");
|
|
9497
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "unselected-body");
|
|
9498
|
+
bodyPredicate((_target, bodyTarget) => bodyTarget === "empty");
|
|
9112
9499
|
function kindOf(target) {
|
|
9113
9500
|
if (typeof target !== "object" || target === null) return void 0;
|
|
9114
9501
|
const k = target.kind;
|
|
@@ -9147,17 +9534,17 @@ function splitCubicAtT(p0, p1, p2, p3, t) {
|
|
|
9147
9534
|
function fitCubicThroughDeletion(prev, next) {
|
|
9148
9535
|
const dx = next.x - prev.x;
|
|
9149
9536
|
const dy = next.y - prev.y;
|
|
9150
|
-
const
|
|
9537
|
+
const dist3 = Math.sqrt(dx * dx + dy * dy);
|
|
9151
9538
|
const c1DirX = prev.outHandle ? prev.outHandle.x - prev.x : dx;
|
|
9152
9539
|
const c1DirY = prev.outHandle ? prev.outHandle.y - prev.y : dy;
|
|
9153
|
-
const c1 = controlOnSide(prev, c1DirX, c1DirY,
|
|
9540
|
+
const c1 = controlOnSide(prev, c1DirX, c1DirY, dist3, 1 / 3);
|
|
9154
9541
|
const c2DirX = next.inHandle ? next.inHandle.x - next.x : -dx;
|
|
9155
9542
|
const c2DirY = next.inHandle ? next.inHandle.y - next.y : -dy;
|
|
9156
|
-
const c2 = controlOnSide(next, c2DirX, c2DirY,
|
|
9543
|
+
const c2 = controlOnSide(next, c2DirX, c2DirY, dist3, 1 / 3);
|
|
9157
9544
|
return { c1, c2 };
|
|
9158
9545
|
}
|
|
9159
|
-
function controlOnSide(anchor, dirX, dirY,
|
|
9160
|
-
const targetDist =
|
|
9546
|
+
function controlOnSide(anchor, dirX, dirY, dist3, fraction) {
|
|
9547
|
+
const targetDist = dist3 * fraction;
|
|
9161
9548
|
const len = Math.hypot(dirX, dirY) || 1;
|
|
9162
9549
|
const ux = dirX / len, uy = dirY / len;
|
|
9163
9550
|
return { x: anchor.x + ux * targetDist, y: anchor.y + uy * targetDist };
|
|
@@ -9414,60 +9801,6 @@ function anchorsInRect(set, rect) {
|
|
|
9414
9801
|
}
|
|
9415
9802
|
return hits;
|
|
9416
9803
|
}
|
|
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
9804
|
function pathInPoseFrame(path, pose) {
|
|
9472
9805
|
if (path.kind === "rect") {
|
|
9473
9806
|
if (path.x === pose.x && path.y === pose.y && path.width === pose.width && path.height === pose.height) return path;
|
|
@@ -11020,7 +11353,7 @@ var LABEL = {
|
|
|
11020
11353
|
divide: "Divide",
|
|
11021
11354
|
crop: "Crop"
|
|
11022
11355
|
};
|
|
11023
|
-
function
|
|
11356
|
+
function isEmpty2(p) {
|
|
11024
11357
|
return p.commands.length === 0;
|
|
11025
11358
|
}
|
|
11026
11359
|
function applyBooleanOp(adapter, op) {
|
|
@@ -11058,7 +11391,7 @@ function applyBooleanOp(adapter, op) {
|
|
|
11058
11391
|
break;
|
|
11059
11392
|
}
|
|
11060
11393
|
}
|
|
11061
|
-
results = results.filter((p) => !
|
|
11394
|
+
results = results.filter((p) => !isEmpty2(p));
|
|
11062
11395
|
if (results.length === 0) return { kind: "noop", reason: "empty-result" };
|
|
11063
11396
|
const topmostId = entries[entries.length - 1].id;
|
|
11064
11397
|
const topAnchor = adapter.getZOrder?.(topmostId);
|
|
@@ -11594,14 +11927,24 @@ var DEFAULT_PALETTE = [
|
|
|
11594
11927
|
"#7ab8d4",
|
|
11595
11928
|
"#d47a7a"
|
|
11596
11929
|
];
|
|
11930
|
+
function resolveFill(scratch, prev) {
|
|
11931
|
+
if (scratch.currentPaint) return scratch.currentPaint;
|
|
11932
|
+
const prevColor = typeof prev?.fill === "string" ? prev.fill : DEFAULT_FILL_COLOR;
|
|
11933
|
+
return mergeAlphaFromPrev(scratch.currentColor, prevColor);
|
|
11934
|
+
}
|
|
11597
11935
|
function refreshPreviews(scratch) {
|
|
11598
11936
|
scratch.previews.clear();
|
|
11599
11937
|
for (const id of scratch.ids) {
|
|
11600
11938
|
const prev = scratch.startData.get(id);
|
|
11601
|
-
|
|
11602
|
-
scratch.previews.set(id, { ...prev ?? {}, fill: next });
|
|
11939
|
+
scratch.previews.set(id, { ...prev ?? {}, fill: resolveFill(scratch, prev) });
|
|
11603
11940
|
}
|
|
11604
11941
|
}
|
|
11942
|
+
function readParams(params) {
|
|
11943
|
+
return {
|
|
11944
|
+
color: params?.color,
|
|
11945
|
+
paint: params?.paint
|
|
11946
|
+
};
|
|
11947
|
+
}
|
|
11605
11948
|
var setFillAction = {
|
|
11606
11949
|
id: "setFill",
|
|
11607
11950
|
label: "Set fill",
|
|
@@ -11616,9 +11959,10 @@ var setFillAction = {
|
|
|
11616
11959
|
if (!selection || !scene) return {};
|
|
11617
11960
|
const ids = selection.get();
|
|
11618
11961
|
if (ids.length === 0) return {};
|
|
11619
|
-
const
|
|
11620
|
-
const
|
|
11621
|
-
const
|
|
11962
|
+
const fromCtx = readParams(ctx.params);
|
|
11963
|
+
const fromOpts = readParams(opts?.params);
|
|
11964
|
+
const initialPaint = fromCtx.paint ?? fromOpts.paint;
|
|
11965
|
+
const initialColor = fromCtx.color ?? fromOpts.color ?? DEFAULT_STROKE_COLOR;
|
|
11622
11966
|
const startData = /* @__PURE__ */ new Map();
|
|
11623
11967
|
for (const id of ids) {
|
|
11624
11968
|
const node = scene.get(id);
|
|
@@ -11629,15 +11973,20 @@ var setFillAction = {
|
|
|
11629
11973
|
scene,
|
|
11630
11974
|
startData,
|
|
11631
11975
|
currentColor: initialColor,
|
|
11976
|
+
currentPaint: initialPaint,
|
|
11632
11977
|
previews: /* @__PURE__ */ new Map(),
|
|
11633
11978
|
applyOps
|
|
11634
11979
|
};
|
|
11635
11980
|
refreshPreviews(scratch);
|
|
11636
11981
|
return {
|
|
11637
11982
|
onMove(moveCtx) {
|
|
11638
|
-
const
|
|
11639
|
-
if (
|
|
11640
|
-
scratch.
|
|
11983
|
+
const { color, paint } = readParams(moveCtx.params);
|
|
11984
|
+
if (color === void 0 && paint === void 0) return;
|
|
11985
|
+
if (paint !== void 0) scratch.currentPaint = paint;
|
|
11986
|
+
if (color !== void 0) {
|
|
11987
|
+
scratch.currentColor = color;
|
|
11988
|
+
if (paint === void 0) scratch.currentPaint = void 0;
|
|
11989
|
+
}
|
|
11641
11990
|
refreshPreviews(scratch);
|
|
11642
11991
|
},
|
|
11643
11992
|
onEnd(_endCtx, reason) {
|
|
@@ -11647,8 +11996,7 @@ var setFillAction = {
|
|
|
11647
11996
|
}
|
|
11648
11997
|
const ops = [];
|
|
11649
11998
|
for (const id of scratch.ids) {
|
|
11650
|
-
const
|
|
11651
|
-
const merged = mergeAlphaFromPrev(scratch.currentColor, prev?.fill ?? DEFAULT_FILL_COLOR);
|
|
11999
|
+
const merged = resolveFill(scratch, scratch.startData.get(id));
|
|
11652
12000
|
const nodeNow = scratch.scene.get(id);
|
|
11653
12001
|
if (!nodeNow) continue;
|
|
11654
12002
|
const from = { ...nodeNow.data };
|
|
@@ -12508,6 +12856,54 @@ function targetRank(target) {
|
|
|
12508
12856
|
if (target.startsWith("affordance:")) return 2;
|
|
12509
12857
|
return 1;
|
|
12510
12858
|
}
|
|
12859
|
+
function targetConsultsAffordance(specTarget) {
|
|
12860
|
+
if (specTarget === void 0) return false;
|
|
12861
|
+
if (typeof specTarget === "object" && specTarget !== null && "kindOf" in specTarget) {
|
|
12862
|
+
const kindOf2 = specTarget.kindOf;
|
|
12863
|
+
return kindOf2?.readsAffordance !== false;
|
|
12864
|
+
}
|
|
12865
|
+
return typeof specTarget === "string" && specTarget.startsWith("affordance:");
|
|
12866
|
+
}
|
|
12867
|
+
function specTargetOf(spec) {
|
|
12868
|
+
return "target" in spec ? spec.target : void 0;
|
|
12869
|
+
}
|
|
12870
|
+
function claimOf(e) {
|
|
12871
|
+
return "affordance" in e ? e.affordance : void 0;
|
|
12872
|
+
}
|
|
12873
|
+
function claimGestureOf(e) {
|
|
12874
|
+
switch (e.kind) {
|
|
12875
|
+
case "pointerdown":
|
|
12876
|
+
case "click":
|
|
12877
|
+
return "pointer";
|
|
12878
|
+
case "doubleclick":
|
|
12879
|
+
return "doubleClick";
|
|
12880
|
+
case "contextmenu":
|
|
12881
|
+
return "contextMenu";
|
|
12882
|
+
case "longpress":
|
|
12883
|
+
return "longPress";
|
|
12884
|
+
case "wheel":
|
|
12885
|
+
return "wheel";
|
|
12886
|
+
default:
|
|
12887
|
+
return null;
|
|
12888
|
+
}
|
|
12889
|
+
}
|
|
12890
|
+
function isExclusiveClaim(e) {
|
|
12891
|
+
const claim = claimOf(e);
|
|
12892
|
+
if (claim?.strength !== "exclusive") return false;
|
|
12893
|
+
const gesture = claimGestureOf(e);
|
|
12894
|
+
if (gesture === null) return false;
|
|
12895
|
+
return claim.claimedKinds === void 0 || claim.claimedKinds.includes(gesture);
|
|
12896
|
+
}
|
|
12897
|
+
var warnedDeadClaims = /* @__PURE__ */ new Set();
|
|
12898
|
+
function reportDeadClaim(owner, warn) {
|
|
12899
|
+
if (process.env.NODE_ENV === "production") return;
|
|
12900
|
+
const key = String(owner);
|
|
12901
|
+
if (warnedDeadClaims.has(key)) return;
|
|
12902
|
+
warnedDeadClaims.add(key);
|
|
12903
|
+
warn(
|
|
12904
|
+
`[weasel] exclusive claim by "${key}" matched no binding: no \`affordance:\` or \`kindOf\` target resolved against it, so the press was dropped.`
|
|
12905
|
+
);
|
|
12906
|
+
}
|
|
12511
12907
|
function specificity(spec) {
|
|
12512
12908
|
const t = targetRank("target" in spec ? spec.target : void 0);
|
|
12513
12909
|
const mods = "mods" in spec ? spec.mods : void 0;
|
|
@@ -12524,12 +12920,14 @@ function compareSpecificity(a, b) {
|
|
|
12524
12920
|
}
|
|
12525
12921
|
return 0;
|
|
12526
12922
|
}
|
|
12527
|
-
function matchSorted(e, bindings, isMac, engagedChannels) {
|
|
12923
|
+
function matchSorted(e, bindings, isMac, engagedChannels, warn = (m) => console.warn(m)) {
|
|
12528
12924
|
const out = [];
|
|
12529
12925
|
const engaged = engagedChannels ?? EMPTY_ENGAGED2;
|
|
12926
|
+
const exclusive = isExclusiveClaim(e);
|
|
12927
|
+
const pool = exclusive ? bindings.filter((sb) => targetConsultsAffordance(specTargetOf(sb.binding.spec))) : bindings;
|
|
12530
12928
|
for (const scope of SCOPE_PRIORITY) {
|
|
12531
12929
|
const scopeMatches = [];
|
|
12532
|
-
for (const sb of
|
|
12930
|
+
for (const sb of pool) {
|
|
12533
12931
|
if (sb.scope !== scope) continue;
|
|
12534
12932
|
const phaseCtx = { selfChannel: sb.ownerToolId, engagedChannels: engaged };
|
|
12535
12933
|
if (matchSpec(e, sb.binding.spec, isMac, phaseCtx)) {
|
|
@@ -12539,9 +12937,32 @@ function matchSorted(e, bindings, isMac, engagedChannels) {
|
|
|
12539
12937
|
scopeMatches.sort((a, b) => compareSpecificity(a.binding.spec, b.binding.spec));
|
|
12540
12938
|
out.push(...scopeMatches);
|
|
12541
12939
|
}
|
|
12940
|
+
if (exclusive && out.length === 0 && bindings.length > 0) {
|
|
12941
|
+
reportDeadClaim(claimOf(e)?.owner, warn);
|
|
12942
|
+
}
|
|
12542
12943
|
return out;
|
|
12543
12944
|
}
|
|
12544
12945
|
var EMPTY_ENGAGED2 = /* @__PURE__ */ new Set();
|
|
12946
|
+
function liveScope(id, eligibility, state) {
|
|
12947
|
+
const tags = eligibility.capabilities;
|
|
12948
|
+
if (tags && tags.length > 0 && state.allows && !state.allows(tags)) return null;
|
|
12949
|
+
if (state.engagedIds?.has(id)) return "hotkey";
|
|
12950
|
+
if (eligibility.offhand && state.heldTriggers.has(eligibility.offhand)) return "hotkey";
|
|
12951
|
+
if (eligibility.focus && state.focusedId === id) return "active";
|
|
12952
|
+
if (eligibility.always || eligibility.claimed) return "ambient";
|
|
12953
|
+
return null;
|
|
12954
|
+
}
|
|
12955
|
+
function scopeBindings(entries, state) {
|
|
12956
|
+
const out = [];
|
|
12957
|
+
for (const entry of entries) {
|
|
12958
|
+
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
12959
|
+
if (scope === null) continue;
|
|
12960
|
+
for (const binding of entry.bindings ?? []) {
|
|
12961
|
+
out.push({ binding, scope, ownerToolId: entry.id });
|
|
12962
|
+
}
|
|
12963
|
+
}
|
|
12964
|
+
return out;
|
|
12965
|
+
}
|
|
12545
12966
|
var ALWAYS = { all: [] };
|
|
12546
12967
|
var NEVER = { any: [] };
|
|
12547
12968
|
function isAllRule(r) {
|
|
@@ -12836,6 +13257,7 @@ function filterEligible(matches, actionLookup, ruleCtx) {
|
|
|
12836
13257
|
return isEligible(action, ruleCtx);
|
|
12837
13258
|
});
|
|
12838
13259
|
}
|
|
13260
|
+
var EMPTY_TRIGGERS = /* @__PURE__ */ new Set();
|
|
12839
13261
|
var EMPTY_ENGAGED22 = /* @__PURE__ */ new Set();
|
|
12840
13262
|
function modifiersOf(e) {
|
|
12841
13263
|
return { alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey };
|
|
@@ -12943,34 +13365,23 @@ function createDispatcher(opts) {
|
|
|
12943
13365
|
return `ongoing-${event.kind}`;
|
|
12944
13366
|
}
|
|
12945
13367
|
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
|
-
}
|
|
13368
|
+
const ordered = [];
|
|
13369
|
+
for (const id of ctx.hotkeyStack) {
|
|
13370
|
+
const tool = ctx.toolsById.get(id);
|
|
13371
|
+
if (tool && !ordered.includes(tool)) ordered.push(tool);
|
|
13372
|
+
}
|
|
13373
|
+
for (const tool of ctx.toolsById.values()) {
|
|
13374
|
+
if (!ordered.includes(tool)) ordered.push(tool);
|
|
13375
|
+
}
|
|
13376
|
+
const result = scopeBindings(ordered, {
|
|
13377
|
+
focusedId: ctx.activeToolId,
|
|
13378
|
+
heldTriggers: EMPTY_TRIGGERS,
|
|
13379
|
+
engagedIds: new Set(ctx.hotkeyStack)
|
|
13380
|
+
});
|
|
12963
13381
|
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 });
|
|
13382
|
+
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
13383
|
+
for (const binding of actionBindings(action)) {
|
|
13384
|
+
result.push({ binding, scope: targetScope, ownerToolId: null });
|
|
12974
13385
|
}
|
|
12975
13386
|
}
|
|
12976
13387
|
return result;
|
|
@@ -13125,15 +13536,26 @@ function createDispatcher(opts) {
|
|
|
13125
13536
|
deltaY: event.deltaY,
|
|
13126
13537
|
clientX: event.clientX,
|
|
13127
13538
|
clientY: event.clientY,
|
|
13539
|
+
affordance: event.affordance,
|
|
13128
13540
|
...resolved
|
|
13129
13541
|
};
|
|
13130
13542
|
} else if (event.kind === "click" || event.kind === "doubleclick") {
|
|
13131
13543
|
params = {
|
|
13132
13544
|
worldX: event.worldX,
|
|
13133
13545
|
worldY: event.worldY,
|
|
13546
|
+
affordance: event.affordance,
|
|
13134
13547
|
// Press point as well as release point — an action that places
|
|
13135
13548
|
// geometry at the click wants the former. See `ClickEvent`.
|
|
13136
|
-
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY
|
|
13549
|
+
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
13550
|
+
mods: modifiersOf(event),
|
|
13551
|
+
...resolved
|
|
13552
|
+
};
|
|
13553
|
+
} else if (event.kind === "contextmenu" || event.kind === "longpress") {
|
|
13554
|
+
params = {
|
|
13555
|
+
worldX: event.kind === "contextmenu" ? event.worldX : event.x,
|
|
13556
|
+
worldY: event.kind === "contextmenu" ? event.worldY : event.y,
|
|
13557
|
+
affordance: event.affordance,
|
|
13558
|
+
bodyTarget: event.bodyTarget,
|
|
13137
13559
|
mods: modifiersOf(event),
|
|
13138
13560
|
...resolved
|
|
13139
13561
|
};
|
|
@@ -13501,7 +13923,7 @@ function computeMultiTouchGeometry(positions) {
|
|
|
13501
13923
|
return { centroid, spread };
|
|
13502
13924
|
}
|
|
13503
13925
|
function useGestureDispatcher(opts) {
|
|
13504
|
-
const { canvasRef, actions, toolsById,
|
|
13926
|
+
const { canvasRef, actions, toolsById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld: clientToWorld2, requestRedraw, getRuleCtx, onDoubleClick } = opts;
|
|
13505
13927
|
const onDoubleClickRef = useRef(onDoubleClick);
|
|
13506
13928
|
onDoubleClickRef.current = onDoubleClick;
|
|
13507
13929
|
const activeTool = useActiveToolContext();
|
|
@@ -13516,7 +13938,6 @@ function useGestureDispatcher(opts) {
|
|
|
13516
13938
|
activeToolId: activeTool.active,
|
|
13517
13939
|
hotkeyStack: activeTool.hotkeyStack,
|
|
13518
13940
|
toolsById,
|
|
13519
|
-
ambientToolIds,
|
|
13520
13941
|
isMac: IS_MAC,
|
|
13521
13942
|
getRuleCtx
|
|
13522
13943
|
});
|
|
@@ -13526,7 +13947,6 @@ function useGestureDispatcher(opts) {
|
|
|
13526
13947
|
activeToolId: activeTool.active,
|
|
13527
13948
|
hotkeyStack: activeTool.hotkeyStack,
|
|
13528
13949
|
toolsById,
|
|
13529
|
-
ambientToolIds,
|
|
13530
13950
|
isMac: IS_MAC,
|
|
13531
13951
|
getRuleCtx
|
|
13532
13952
|
};
|
|
@@ -13604,6 +14024,7 @@ function useGestureDispatcher(opts) {
|
|
|
13604
14024
|
ctrlKey: down.ctrlKey,
|
|
13605
14025
|
metaKey: down.metaKey,
|
|
13606
14026
|
shiftKey: down.shiftKey,
|
|
14027
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
13607
14028
|
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
13608
14029
|
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
13609
14030
|
};
|
|
@@ -13613,11 +14034,15 @@ function useGestureDispatcher(opts) {
|
|
|
13613
14034
|
y: down.worldY,
|
|
13614
14035
|
clientX: down.clientX,
|
|
13615
14036
|
clientY: down.clientY,
|
|
13616
|
-
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
13617
14037
|
...shared
|
|
13618
14038
|
});
|
|
13619
14039
|
if (result === "unhandled") {
|
|
13620
|
-
dispatch2({
|
|
14040
|
+
dispatch2({
|
|
14041
|
+
kind: "contextmenu",
|
|
14042
|
+
worldX: down.worldX,
|
|
14043
|
+
worldY: down.worldY,
|
|
14044
|
+
...shared
|
|
14045
|
+
});
|
|
13621
14046
|
}
|
|
13622
14047
|
};
|
|
13623
14048
|
const DOUBLE_CLICK_MAX_MS = 600;
|
|
@@ -13672,6 +14097,9 @@ function useGestureDispatcher(opts) {
|
|
|
13672
14097
|
const onWheel = (e) => {
|
|
13673
14098
|
const rect = canvas?.getBoundingClientRect();
|
|
13674
14099
|
const [localX, localY] = rect ? clientToCanvasRect(rect, e.clientX, e.clientY) : [e.clientX, e.clientY];
|
|
14100
|
+
const screenPoint = { x: e.clientX, y: e.clientY };
|
|
14101
|
+
const affordance = affordanceAtRef.current?.(screenPoint) ?? void 0;
|
|
14102
|
+
const body = classifyTargetRef.current?.(screenPoint);
|
|
13675
14103
|
const ev = {
|
|
13676
14104
|
kind: "wheel",
|
|
13677
14105
|
altKey: e.altKey,
|
|
@@ -13681,7 +14109,10 @@ function useGestureDispatcher(opts) {
|
|
|
13681
14109
|
deltaX: e.deltaX,
|
|
13682
14110
|
deltaY: e.deltaY,
|
|
13683
14111
|
clientX: localX,
|
|
13684
|
-
clientY: localY
|
|
14112
|
+
clientY: localY,
|
|
14113
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
14114
|
+
...body?.body !== void 0 ? { bodyTarget: body.body } : {},
|
|
14115
|
+
...body?.kind !== void 0 ? { bodyKind: body.kind } : {}
|
|
13685
14116
|
};
|
|
13686
14117
|
const result = dispatch2(ev);
|
|
13687
14118
|
if (result === "handled") {
|
|
@@ -13975,6 +14406,7 @@ function useGestureDispatcher(opts) {
|
|
|
13975
14406
|
shiftKey: e.shiftKey,
|
|
13976
14407
|
worldX: wClick.x,
|
|
13977
14408
|
worldY: wClick.y,
|
|
14409
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
13978
14410
|
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
13979
14411
|
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
13980
14412
|
};
|
|
@@ -14016,8 +14448,10 @@ function useGestureDispatcher(opts) {
|
|
|
14016
14448
|
};
|
|
14017
14449
|
const onContextMenu = (e) => {
|
|
14018
14450
|
e.preventDefault();
|
|
14019
|
-
const
|
|
14020
|
-
const
|
|
14451
|
+
const screenPoint = { x: e.clientX, y: e.clientY };
|
|
14452
|
+
const affordance = affordanceAtRef.current?.(screenPoint) ?? void 0;
|
|
14453
|
+
const menuBody = classifyTargetRef.current?.(screenPoint);
|
|
14454
|
+
const w = toWorld(e.clientX, e.clientY);
|
|
14021
14455
|
const ev = {
|
|
14022
14456
|
kind: "contextmenu",
|
|
14023
14457
|
target: e.target,
|
|
@@ -14025,6 +14459,9 @@ function useGestureDispatcher(opts) {
|
|
|
14025
14459
|
ctrlKey: e.ctrlKey,
|
|
14026
14460
|
metaKey: e.metaKey,
|
|
14027
14461
|
shiftKey: e.shiftKey,
|
|
14462
|
+
worldX: w.x,
|
|
14463
|
+
worldY: w.y,
|
|
14464
|
+
...affordance !== void 0 ? { affordance } : {},
|
|
14028
14465
|
...menuBody?.body !== void 0 ? { bodyTarget: menuBody.body } : {},
|
|
14029
14466
|
...menuBody?.kind !== void 0 ? { bodyKind: menuBody.kind } : {}
|
|
14030
14467
|
};
|
|
@@ -14323,47 +14760,203 @@ function usePinchGesture(canvasRef, onPinch, enabled = true) {
|
|
|
14323
14760
|
};
|
|
14324
14761
|
}, [canvasRef, enabled]);
|
|
14325
14762
|
}
|
|
14326
|
-
|
|
14327
|
-
|
|
14328
|
-
|
|
14763
|
+
var TOOL_OFFHAND_ID = "tool.offhand";
|
|
14764
|
+
function offhandKeyFor(trigger) {
|
|
14765
|
+
switch (trigger) {
|
|
14766
|
+
case "space":
|
|
14767
|
+
return " ";
|
|
14768
|
+
case "alt":
|
|
14769
|
+
return "Alt";
|
|
14770
|
+
case "ctrl":
|
|
14771
|
+
return "Control";
|
|
14772
|
+
case "meta":
|
|
14773
|
+
return "Meta";
|
|
14774
|
+
case "shift":
|
|
14775
|
+
return "Shift";
|
|
14329
14776
|
}
|
|
14777
|
+
}
|
|
14778
|
+
function buildToolOffhandBindings(specs) {
|
|
14779
|
+
return specs.map(({ toolId, key }) => ({
|
|
14780
|
+
spec: { kind: "key-held", key },
|
|
14781
|
+
opts: { params: { toolId } }
|
|
14782
|
+
}));
|
|
14783
|
+
}
|
|
14784
|
+
function makeToolOffhandAction(bindings) {
|
|
14785
|
+
return {
|
|
14786
|
+
id: TOOL_OFFHAND_ID,
|
|
14787
|
+
label: "Engage tool (offhand)",
|
|
14788
|
+
defaultBinding: bindings,
|
|
14789
|
+
scope: "hotkey",
|
|
14790
|
+
invoker: {
|
|
14791
|
+
timing: "ongoing",
|
|
14792
|
+
start: (ctx, opts) => {
|
|
14793
|
+
const activeTool = ctx.deps.activeTool;
|
|
14794
|
+
const toolId = resolveParams(opts?.params)?.toolId;
|
|
14795
|
+
if (!activeTool || !toolId) return {};
|
|
14796
|
+
activeTool.pushHotkey(toolId);
|
|
14797
|
+
return {
|
|
14798
|
+
onEnd: () => {
|
|
14799
|
+
activeTool.popHotkey();
|
|
14800
|
+
}
|
|
14801
|
+
};
|
|
14802
|
+
}
|
|
14803
|
+
},
|
|
14804
|
+
requires: ["activeTool"]
|
|
14805
|
+
};
|
|
14806
|
+
}
|
|
14807
|
+
var NO_TRIGGERS = /* @__PURE__ */ new Set();
|
|
14808
|
+
function useContributions(opts) {
|
|
14330
14809
|
const ctx = useActiveToolContext();
|
|
14810
|
+
const actionsRegistry = useActionsRegistry();
|
|
14331
14811
|
const hasInitializedRef = useRef(false);
|
|
14332
14812
|
const isFirstRender = !hasInitializedRef.current;
|
|
14333
14813
|
const needsFirstMountSyncRef = useRef(false);
|
|
14334
14814
|
if (isFirstRender) {
|
|
14335
14815
|
hasInitializedRef.current = true;
|
|
14336
|
-
needsFirstMountSyncRef.current = ctx.active === "select" && opts.
|
|
14816
|
+
needsFirstMountSyncRef.current = ctx.active === "select" && opts.focused !== "select";
|
|
14337
14817
|
}
|
|
14338
14818
|
useEffect(() => {
|
|
14339
14819
|
if (!needsFirstMountSyncRef.current) return;
|
|
14340
14820
|
needsFirstMountSyncRef.current = false;
|
|
14341
|
-
ctx.setActive(opts.
|
|
14821
|
+
ctx.setActive(opts.focused);
|
|
14822
|
+
}, []);
|
|
14823
|
+
const focused2 = isFirstRender && ctx.active === "select" && opts.focused !== "select" ? opts.focused : ctx.active;
|
|
14824
|
+
const entriesRef = useRef(opts.entries);
|
|
14825
|
+
entriesRef.current = opts.entries;
|
|
14826
|
+
const focusedRef = useRef(focused2);
|
|
14827
|
+
focusedRef.current = focused2;
|
|
14828
|
+
const engagedRef = useRef(ctx.hotkeyStack);
|
|
14829
|
+
engagedRef.current = ctx.hotkeyStack;
|
|
14830
|
+
const setFocused = useCallback((id) => {
|
|
14831
|
+
ctx.setActive(id);
|
|
14832
|
+
}, [ctx]);
|
|
14833
|
+
const snapshot = useCallback(() => {
|
|
14834
|
+
const engaged = engagedRef.current;
|
|
14835
|
+
const engagedIds = new Set(engaged);
|
|
14836
|
+
const ordered = [];
|
|
14837
|
+
for (const id of engaged) {
|
|
14838
|
+
const entry = entriesRef.current.find((e) => e.id === id);
|
|
14839
|
+
if (entry && !ordered.includes(entry)) ordered.push(entry);
|
|
14840
|
+
}
|
|
14841
|
+
for (const entry of entriesRef.current) {
|
|
14842
|
+
if (!ordered.includes(entry)) ordered.push(entry);
|
|
14843
|
+
}
|
|
14844
|
+
return {
|
|
14845
|
+
ordered,
|
|
14846
|
+
state: { focusedId: focusedRef.current, heldTriggers: NO_TRIGGERS, engagedIds }
|
|
14847
|
+
};
|
|
14342
14848
|
}, []);
|
|
14343
|
-
const
|
|
14344
|
-
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
|
|
14351
|
-
|
|
14849
|
+
const scopedBindings = useCallback(() => {
|
|
14850
|
+
const { ordered, state } = snapshot();
|
|
14851
|
+
return scopeBindings(ordered, state);
|
|
14852
|
+
}, [snapshot]);
|
|
14853
|
+
const overlays = useCallback(() => {
|
|
14854
|
+
const { ordered, state } = snapshot();
|
|
14855
|
+
const active = [];
|
|
14856
|
+
const hotkey = [];
|
|
14857
|
+
const ambient = [];
|
|
14858
|
+
for (const entry of ordered) {
|
|
14859
|
+
if (!entry.overlay) continue;
|
|
14860
|
+
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
14861
|
+
if (scope === "active") active.push(entry.overlay);
|
|
14862
|
+
else if (scope === "hotkey") hotkey.push(entry.overlay);
|
|
14863
|
+
else if (scope === "ambient") ambient.push(entry.overlay);
|
|
14864
|
+
}
|
|
14865
|
+
return [...active, ...hotkey, ...ambient];
|
|
14866
|
+
}, [snapshot]);
|
|
14867
|
+
const offhandSig = opts.entries.map((e) => e.eligibility?.offhand ? `${e.id}:${e.eligibility.offhand}` : "").filter(Boolean).join(",");
|
|
14868
|
+
useEffect(() => {
|
|
14869
|
+
if (!actionsRegistry || offhandSig === "") return;
|
|
14870
|
+
const specs = offhandSig.split(",").map((entry) => {
|
|
14871
|
+
const [toolId, trigger] = entry.split(":");
|
|
14872
|
+
return { toolId, key: offhandKeyFor(trigger) };
|
|
14873
|
+
});
|
|
14874
|
+
return actionsRegistry.register(makeToolOffhandAction(buildToolOffhandBindings(specs)));
|
|
14875
|
+
}, [actionsRegistry, offhandSig]);
|
|
14876
|
+
const lastConflictSigRef = useRef(null);
|
|
14877
|
+
const entrySig = opts.entries.map((e) => e.id).join(",");
|
|
14878
|
+
useEffect(() => {
|
|
14879
|
+
if (process.env.NODE_ENV === "production") return;
|
|
14880
|
+
if (lastConflictSigRef.current === entrySig) return;
|
|
14881
|
+
lastConflictSigRef.current = entrySig;
|
|
14882
|
+
const registry22 = [];
|
|
14883
|
+
const ambient = [];
|
|
14884
|
+
for (const entry of entriesRef.current) {
|
|
14885
|
+
(entry.eligibility?.focus ? registry22 : ambient).push(entry);
|
|
14886
|
+
}
|
|
14887
|
+
reportRouteConflicts({
|
|
14888
|
+
registry: registry22,
|
|
14889
|
+
ambient,
|
|
14890
|
+
actions: actionsRegistry?.list() ?? []
|
|
14891
|
+
});
|
|
14892
|
+
}, [entrySig, actionsRegistry]);
|
|
14893
|
+
return useMemo(
|
|
14894
|
+
() => ({
|
|
14895
|
+
entries: entriesRef.current,
|
|
14896
|
+
focused: focused2,
|
|
14897
|
+
setFocused,
|
|
14898
|
+
scopedBindings,
|
|
14899
|
+
overlays
|
|
14900
|
+
}),
|
|
14901
|
+
[focused2, setFocused, scopedBindings, overlays]
|
|
14902
|
+
);
|
|
14903
|
+
}
|
|
14904
|
+
function declareSlot(tool, slot) {
|
|
14905
|
+
const hotkey = tool.def?.hotkey;
|
|
14906
|
+
const eligibility = {
|
|
14907
|
+
...tool.eligibility,
|
|
14908
|
+
[slot]: true,
|
|
14909
|
+
...hotkey ? { offhand: hotkey } : {}
|
|
14910
|
+
};
|
|
14911
|
+
return sameEligibility(tool.eligibility, eligibility) ? tool : { ...tool, eligibility };
|
|
14912
|
+
}
|
|
14913
|
+
function sameEligibility(a, b) {
|
|
14914
|
+
if (!a) return false;
|
|
14915
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
14916
|
+
for (const key of keys) if (a[key] !== b[key]) return false;
|
|
14917
|
+
return true;
|
|
14918
|
+
}
|
|
14919
|
+
function useTools(opts) {
|
|
14920
|
+
if (!(opts.active in opts.registry)) {
|
|
14921
|
+
throw new Error(`useTools: active "${opts.active}" not in registry`);
|
|
14922
|
+
}
|
|
14923
|
+
const ctx = useActiveToolContext();
|
|
14924
|
+
const slotted = useMemo(() => {
|
|
14925
|
+
const registry22 = {};
|
|
14926
|
+
for (const [id, tool] of Object.entries(opts.registry)) {
|
|
14927
|
+
registry22[id] = declareSlot(tool, "focus");
|
|
14928
|
+
}
|
|
14929
|
+
const ambient = (opts.ambient ?? []).map((t) => declareSlot(t, "always"));
|
|
14930
|
+
const byId2 = /* @__PURE__ */ new Map();
|
|
14931
|
+
for (const tool of [...Object.values(registry22), ...ambient]) {
|
|
14932
|
+
const prior = byId2.get(tool.id);
|
|
14933
|
+
if (prior) byId2.set(tool.id, { ...tool, eligibility: { ...prior.eligibility, ...tool.eligibility } });
|
|
14934
|
+
else byId2.set(tool.id, tool);
|
|
14935
|
+
}
|
|
14936
|
+
return { registry: registry22, ambient, entries: [...byId2.values()] };
|
|
14937
|
+
}, [opts.registry, opts.ambient]);
|
|
14938
|
+
const contributions = useContributions({ entries: slotted.entries, focused: opts.active });
|
|
14939
|
+
const hotkeyEngaged = ctx.hotkeyStack.at(-1) ?? null;
|
|
14940
|
+
const slottedRef = useRef(slotted);
|
|
14941
|
+
slottedRef.current = slotted;
|
|
14942
|
+
const activeRef = useRef(contributions.focused);
|
|
14943
|
+
activeRef.current = contributions.focused;
|
|
14352
14944
|
const hotkeyRef = useRef(hotkeyEngaged);
|
|
14353
14945
|
hotkeyRef.current = hotkeyEngaged;
|
|
14946
|
+
const setFocused = contributions.setFocused;
|
|
14354
14947
|
const setActive = useCallback(
|
|
14355
14948
|
(id) => {
|
|
14356
|
-
if (!(id in
|
|
14949
|
+
if (!(id in slottedRef.current.registry)) {
|
|
14357
14950
|
throw new Error(`setActive: "${id}" not in registry`);
|
|
14358
14951
|
}
|
|
14359
14952
|
dlog("tools", "active:", activeRef.current, "\u2192", id);
|
|
14360
|
-
|
|
14953
|
+
setFocused(id);
|
|
14361
14954
|
},
|
|
14362
|
-
[
|
|
14955
|
+
[setFocused]
|
|
14363
14956
|
);
|
|
14364
14957
|
const engageHotkey = useCallback(
|
|
14365
14958
|
(id) => {
|
|
14366
|
-
if (!(id in
|
|
14959
|
+
if (!(id in slottedRef.current.registry)) {
|
|
14367
14960
|
throw new Error(`engageHotkey: "${id}" not in registry`);
|
|
14368
14961
|
}
|
|
14369
14962
|
dlog("tools", "hotkey engaged:", id);
|
|
@@ -14375,32 +14968,12 @@ function useTools(opts) {
|
|
|
14375
14968
|
if (hotkeyRef.current) dlog("tools", "hotkey disengaged:", hotkeyRef.current);
|
|
14376
14969
|
ctx.popHotkey();
|
|
14377
14970
|
}, [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
14971
|
const has = useCallback(
|
|
14390
|
-
(id) => id in
|
|
14972
|
+
(id) => id in slottedRef.current.registry || slottedRef.current.ambient.some((t) => t.id === id),
|
|
14391
14973
|
[]
|
|
14392
14974
|
);
|
|
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
|
-
}, []);
|
|
14975
|
+
const active = contributions.focused;
|
|
14976
|
+
const getActiveOverlays = contributions.overlays;
|
|
14404
14977
|
return useMemo(
|
|
14405
14978
|
() => ({
|
|
14406
14979
|
active,
|
|
@@ -14408,44 +14981,14 @@ function useTools(opts) {
|
|
|
14408
14981
|
hotkeyEngaged,
|
|
14409
14982
|
engageHotkey,
|
|
14410
14983
|
disengageHotkey,
|
|
14411
|
-
ambient:
|
|
14412
|
-
registry:
|
|
14984
|
+
ambient: slottedRef.current.ambient,
|
|
14985
|
+
registry: slottedRef.current.registry,
|
|
14413
14986
|
has,
|
|
14414
14987
|
getActiveOverlays
|
|
14415
14988
|
}),
|
|
14416
14989
|
[active, setActive, hotkeyEngaged, engageHotkey, disengageHotkey, has, getActiveOverlays]
|
|
14417
14990
|
);
|
|
14418
14991
|
}
|
|
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
14992
|
var TOOL_ACTIVATE_ID = "tool.activate";
|
|
14450
14993
|
function buildToolActivateBindings(specs) {
|
|
14451
14994
|
return specs.map(({ toolId, keyOpts }) => {
|
|
@@ -14517,9 +15060,6 @@ var BUILTIN_SELECT_KEYS = {
|
|
|
14517
15060
|
hand: { key: "H" },
|
|
14518
15061
|
pen: { key: "P" }
|
|
14519
15062
|
};
|
|
14520
|
-
var BUILTIN_OFFHAND_ACTIONS = {
|
|
14521
|
-
hand: " "
|
|
14522
|
-
};
|
|
14523
15063
|
function useKeybindings(tools, options = {}) {
|
|
14524
15064
|
const toolsRef = useRef(tools);
|
|
14525
15065
|
toolsRef.current = tools;
|
|
@@ -14527,19 +15067,6 @@ function useKeybindings(tools, options = {}) {
|
|
|
14527
15067
|
optionsRef.current = options;
|
|
14528
15068
|
const initialActiveRef = useRef(tools.active);
|
|
14529
15069
|
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
15070
|
useEffect(() => {
|
|
14544
15071
|
if (optionsRef.current.disable) return;
|
|
14545
15072
|
if (!registry22) return;
|
|
@@ -14934,7 +15461,82 @@ function textLineBoxes(pose, opts = {}) {
|
|
|
14934
15461
|
}
|
|
14935
15462
|
return out;
|
|
14936
15463
|
}
|
|
15464
|
+
function fillInPoseFrame(fill, box) {
|
|
15465
|
+
if (isBoundsPattern(fill)) {
|
|
15466
|
+
const o = fill.origin ?? { x: 0, y: 0 };
|
|
15467
|
+
return { ...fill, origin: { x: box.x + o.x, y: box.y + o.y }, units: "local" };
|
|
15468
|
+
}
|
|
15469
|
+
if (!isBoundsGradient(fill)) return fill;
|
|
15470
|
+
const toBox = (p) => ({
|
|
15471
|
+
x: box.x + p.x * box.width,
|
|
15472
|
+
y: box.y + p.y * box.height
|
|
15473
|
+
});
|
|
15474
|
+
if (fill.fill === "linear-gradient") {
|
|
15475
|
+
return { ...fill, from: toBox(fill.from), to: toBox(fill.to), units: "local" };
|
|
15476
|
+
}
|
|
15477
|
+
if (fill.fill === "radial-gradient") {
|
|
15478
|
+
const scale2 = Math.hypot(box.width, box.height) / Math.SQRT2;
|
|
15479
|
+
return { ...fill, center: toBox(fill.center), radius: fill.radius * scale2, units: "local" };
|
|
15480
|
+
}
|
|
15481
|
+
return { ...fill, center: toBox(fill.center), units: "local" };
|
|
15482
|
+
}
|
|
15483
|
+
function isBoundsPattern(fill) {
|
|
15484
|
+
return fill.fill === "pattern" && fill.units === "bounds";
|
|
15485
|
+
}
|
|
15486
|
+
function isBoundsGradient(fill) {
|
|
15487
|
+
if (fill === void 0) return false;
|
|
15488
|
+
const kind = fill.fill;
|
|
15489
|
+
if (kind !== "linear-gradient" && kind !== "radial-gradient" && kind !== "conic-gradient") {
|
|
15490
|
+
return false;
|
|
15491
|
+
}
|
|
15492
|
+
return fill.units === "bounds";
|
|
15493
|
+
}
|
|
14937
15494
|
var cache5 = /* @__PURE__ */ new Map();
|
|
15495
|
+
function specKey(spec) {
|
|
15496
|
+
const keys = Object.keys(spec).sort();
|
|
15497
|
+
const bag = spec;
|
|
15498
|
+
return keys.map((k) => `${k}=${String(bag[k])}`).join("&");
|
|
15499
|
+
}
|
|
15500
|
+
function resolvePatternSpec(spec) {
|
|
15501
|
+
const key = specKey(spec);
|
|
15502
|
+
const hit = cache5.get(key);
|
|
15503
|
+
if (hit !== void 0) return hit;
|
|
15504
|
+
const built = buildTile(spec);
|
|
15505
|
+
cache5.set(key, built);
|
|
15506
|
+
return built;
|
|
15507
|
+
}
|
|
15508
|
+
function buildTile(spec) {
|
|
15509
|
+
const { color, size } = spec;
|
|
15510
|
+
switch (spec.tile) {
|
|
15511
|
+
case "hatch":
|
|
15512
|
+
return hatch({ color, size, lineWidth: spec.lineWidth });
|
|
15513
|
+
case "crosshatch":
|
|
15514
|
+
return crosshatch({ color, size, lineWidth: spec.lineWidth });
|
|
15515
|
+
case "dots":
|
|
15516
|
+
return dots({ color, size, radius: spec.radius });
|
|
15517
|
+
case "chunks":
|
|
15518
|
+
return chunks({
|
|
15519
|
+
color,
|
|
15520
|
+
size,
|
|
15521
|
+
bg: spec.bg,
|
|
15522
|
+
density: spec.density,
|
|
15523
|
+
chunkSize: spec.chunkSize,
|
|
15524
|
+
seed: spec.seed
|
|
15525
|
+
});
|
|
15526
|
+
default:
|
|
15527
|
+
return null;
|
|
15528
|
+
}
|
|
15529
|
+
}
|
|
15530
|
+
function isPatternSpec(payload) {
|
|
15531
|
+
return "tile" in payload;
|
|
15532
|
+
}
|
|
15533
|
+
function resolveFillPattern(fill) {
|
|
15534
|
+
if (fill.fill !== "pattern") return fill;
|
|
15535
|
+
if (!isPatternSpec(fill.pattern)) return fill;
|
|
15536
|
+
const handle = resolvePatternSpec(fill.pattern);
|
|
15537
|
+
return handle ? { ...fill, pattern: handle } : null;
|
|
15538
|
+
}
|
|
15539
|
+
var cache6 = /* @__PURE__ */ new Map();
|
|
14938
15540
|
var subscribers2 = /* @__PURE__ */ new Set();
|
|
14939
15541
|
var defaultLoader = async (src) => {
|
|
14940
15542
|
if (typeof Image === "undefined" || typeof createImageBitmap === "undefined") {
|
|
@@ -14952,28 +15554,28 @@ function notify() {
|
|
|
14952
15554
|
}
|
|
14953
15555
|
function startLoad(src) {
|
|
14954
15556
|
const entry = { status: "loading" };
|
|
14955
|
-
|
|
15557
|
+
cache6.set(src, entry);
|
|
14956
15558
|
loader(src).then(
|
|
14957
15559
|
(bitmap) => {
|
|
14958
|
-
if (
|
|
14959
|
-
|
|
15560
|
+
if (cache6.get(src) !== entry) return;
|
|
15561
|
+
cache6.set(src, { status: "ready", bitmap });
|
|
14960
15562
|
notify();
|
|
14961
15563
|
},
|
|
14962
15564
|
() => {
|
|
14963
|
-
if (
|
|
14964
|
-
|
|
15565
|
+
if (cache6.get(src) !== entry) return;
|
|
15566
|
+
cache6.set(src, { status: "error" });
|
|
14965
15567
|
notify();
|
|
14966
15568
|
}
|
|
14967
15569
|
);
|
|
14968
15570
|
}
|
|
14969
15571
|
function getImageBitmap(src) {
|
|
14970
|
-
const entry =
|
|
15572
|
+
const entry = cache6.get(src);
|
|
14971
15573
|
if (entry) return entry.bitmap;
|
|
14972
15574
|
startLoad(src);
|
|
14973
15575
|
return void 0;
|
|
14974
15576
|
}
|
|
14975
15577
|
function imageStatus(src) {
|
|
14976
|
-
return
|
|
15578
|
+
return cache6.get(src)?.status ?? "idle";
|
|
14977
15579
|
}
|
|
14978
15580
|
function subscribeImageReady(cb) {
|
|
14979
15581
|
subscribers2.add(cb);
|
|
@@ -15151,6 +15753,17 @@ function rectsToPath(rects) {
|
|
|
15151
15753
|
}
|
|
15152
15754
|
return { kind: "polygon", commands, coords, fillRule: "nonzero" };
|
|
15153
15755
|
}
|
|
15756
|
+
function isFillStyle(v) {
|
|
15757
|
+
return typeof v === "object" && v !== null;
|
|
15758
|
+
}
|
|
15759
|
+
function resolveNodeFill(fill, color, hasStroke, fallback) {
|
|
15760
|
+
if (isFillStyle(fill)) return fill;
|
|
15761
|
+
const c = fill ?? color;
|
|
15762
|
+
if (c === "none") return null;
|
|
15763
|
+
if (c !== void 0) return { color: c };
|
|
15764
|
+
if (hasStroke && fallback === null) return null;
|
|
15765
|
+
return fallback === null ? null : { color: fallback };
|
|
15766
|
+
}
|
|
15154
15767
|
var PATH_PAINTER = {
|
|
15155
15768
|
id: "kit:path",
|
|
15156
15769
|
matches: (node) => {
|
|
@@ -15164,12 +15777,12 @@ var PATH_PAINTER = {
|
|
|
15164
15777
|
const d = node.data;
|
|
15165
15778
|
const projected = pathInPoseFrame(d.path, pose);
|
|
15166
15779
|
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
15167
|
-
const
|
|
15168
|
-
const
|
|
15780
|
+
const declared = resolveNodeFill(d.fill, d.color, hasStroke, hasStroke ? null : "#888");
|
|
15781
|
+
const fill = declared && resolveFillPattern(fillInPoseFrame(declared, pose));
|
|
15169
15782
|
const cmd = {
|
|
15170
15783
|
kind: "path",
|
|
15171
15784
|
path: projected,
|
|
15172
|
-
...
|
|
15785
|
+
...fill ? { fill } : {},
|
|
15173
15786
|
...hasStroke ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
15174
15787
|
};
|
|
15175
15788
|
return [cmd];
|
|
@@ -15185,9 +15798,8 @@ var PATH_PAINTER = {
|
|
|
15185
15798
|
ink: (node) => {
|
|
15186
15799
|
const d = node.data;
|
|
15187
15800
|
const hasStroke = !!d.stroke && d.stroke !== "none" && (d.strokeWidth ?? 0) > 0;
|
|
15188
|
-
const fillColor = d.fill ?? d.color;
|
|
15189
15801
|
return {
|
|
15190
|
-
filled:
|
|
15802
|
+
filled: resolveNodeFill(d.fill, d.color, hasStroke, hasStroke ? null : "#888") !== null,
|
|
15191
15803
|
strokeWidth: hasStroke ? d.strokeWidth ?? 1 : 0
|
|
15192
15804
|
};
|
|
15193
15805
|
}
|
|
@@ -15204,10 +15816,12 @@ var SHAPE_PAINTER = {
|
|
|
15204
15816
|
paint: (node, pose) => nodeMemo(node, PAINT_SLOT, pose, () => {
|
|
15205
15817
|
const d = node.data;
|
|
15206
15818
|
const path = pathForShape(d, pose);
|
|
15819
|
+
const declaredFill = resolveNodeFill(d.fill, d.color, false, "#888");
|
|
15820
|
+
const shapeFill = declaredFill && resolveFillPattern(fillInPoseFrame(declaredFill, pose));
|
|
15207
15821
|
return [{
|
|
15208
15822
|
kind: "path",
|
|
15209
15823
|
path,
|
|
15210
|
-
|
|
15824
|
+
...shapeFill ? { fill: shapeFill } : {},
|
|
15211
15825
|
...d.stroke && (d.strokeWidth ?? 0) > 0 ? { stroke: { paint: { color: d.stroke }, width: d.strokeWidth ?? 1 } } : {}
|
|
15212
15826
|
}];
|
|
15213
15827
|
}),
|
|
@@ -15603,11 +16217,8 @@ function useSelectTool(adapter, options) {
|
|
|
15603
16217
|
...moveOpts
|
|
15604
16218
|
},
|
|
15605
16219
|
{ 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.
|
|
16220
|
+
// resize/rotate bind only drag (no click), so a click on either handle
|
|
16221
|
+
// (body genuinely empty) would hit target:'empty' and wrongly clear selection.
|
|
15611
16222
|
{
|
|
15612
16223
|
spec: {
|
|
15613
16224
|
kind: "click",
|
|
@@ -15691,17 +16302,17 @@ function worldToLocal(xf, wx, wy) {
|
|
|
15691
16302
|
function annulusSemiAxes(shape, view) {
|
|
15692
16303
|
const bandPx = shape.minBandPx ?? 0;
|
|
15693
16304
|
if (bandPx === 0) return { rx: shape.rx, ry: shape.ry };
|
|
15694
|
-
const band = bandPx
|
|
16305
|
+
const band = pxExtent(bandPx, view.scale);
|
|
15695
16306
|
return {
|
|
15696
|
-
rx: Math.max(shape.rx, shape.innerWidth / 2 + band),
|
|
15697
|
-
ry: Math.max(shape.ry, shape.innerHeight / 2 + band)
|
|
16307
|
+
rx: Math.max(shape.rx, shape.innerWidth / 2 + band.x),
|
|
16308
|
+
ry: Math.max(shape.ry, shape.innerHeight / 2 + band.y)
|
|
15698
16309
|
};
|
|
15699
16310
|
}
|
|
15700
16311
|
function hitRegion(region, wx, wy, xf, view) {
|
|
15701
16312
|
const local = worldToLocal(xf, wx, wy);
|
|
15702
16313
|
if (region.shape.kind === "point") {
|
|
15703
|
-
const
|
|
15704
|
-
return
|
|
16314
|
+
const anchor = localToWorld(xf, region.shape.x, region.shape.y);
|
|
16315
|
+
return withinPxBox(anchor.x - wx, anchor.y - wy, region.shape.hitRadiusPx, view.scale);
|
|
15705
16316
|
}
|
|
15706
16317
|
if (region.shape.kind === "annulus") {
|
|
15707
16318
|
const s = region.shape;
|
|
@@ -15765,14 +16376,13 @@ function asIsVisible(data) {
|
|
|
15765
16376
|
function recordRegionHitbox(debug, affordanceId, region, xf, view) {
|
|
15766
16377
|
if (region.shape.kind === "point") {
|
|
15767
16378
|
const w2 = localToWorld(xf, region.shape.x, region.shape.y);
|
|
15768
|
-
const
|
|
16379
|
+
const e = pxExtent(region.shape.hitRadiusPx, view.scale);
|
|
15769
16380
|
debug.recordHitbox(affordanceId, "handle", {
|
|
15770
16381
|
kind: "rect",
|
|
15771
|
-
x: w2.x -
|
|
15772
|
-
y: w2.y -
|
|
15773
|
-
width:
|
|
15774
|
-
height:
|
|
15775
|
-
...xf.identity ? {} : { rotation: Math.atan2(xf.sin, xf.cos) }
|
|
16382
|
+
x: w2.x - e.x,
|
|
16383
|
+
y: w2.y - e.y,
|
|
16384
|
+
width: e.x * 2,
|
|
16385
|
+
height: e.y * 2
|
|
15776
16386
|
});
|
|
15777
16387
|
return;
|
|
15778
16388
|
}
|
|
@@ -15831,7 +16441,7 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
15831
16441
|
if (paint.kind === "annulus") {
|
|
15832
16442
|
if (region.shape.kind !== "annulus") return;
|
|
15833
16443
|
const s = region.shape;
|
|
15834
|
-
const insetLocal = (paint.insetPx ?? 0
|
|
16444
|
+
const insetLocal = pxExtent(paint.insetPx ?? 0, view.scale);
|
|
15835
16445
|
const cmd = annulusCommand(s, annulusSemiAxes(s, view), insetLocal, xf, viewT, paint.fill, paint.stroke);
|
|
15836
16446
|
if (cmd) out.push(cmd);
|
|
15837
16447
|
return;
|
|
@@ -15848,13 +16458,13 @@ function paintRegion(region, xf, view, state, viewT, out) {
|
|
|
15848
16458
|
}
|
|
15849
16459
|
}
|
|
15850
16460
|
function annulusCommand(shape, semiAxes, insetLocal, xf, viewT, fill, stroke) {
|
|
15851
|
-
const rx = semiAxes.rx - insetLocal;
|
|
15852
|
-
const ry = semiAxes.ry - insetLocal;
|
|
16461
|
+
const rx = semiAxes.rx - insetLocal.x;
|
|
16462
|
+
const ry = semiAxes.ry - insetLocal.y;
|
|
15853
16463
|
if (rx <= 0 || ry <= 0) return null;
|
|
15854
16464
|
const cx = shape.cx;
|
|
15855
16465
|
const cy = shape.cy;
|
|
15856
|
-
const rectHW = shape.innerWidth / 2 + insetLocal;
|
|
15857
|
-
const rectHH = shape.innerHeight / 2 + insetLocal;
|
|
16466
|
+
const rectHW = shape.innerWidth / 2 + insetLocal.x;
|
|
16467
|
+
const rectHH = shape.innerHeight / 2 + insetLocal.y;
|
|
15858
16468
|
const haveTopBottom = rectHH < ry;
|
|
15859
16469
|
const haveLeftRight = rectHW < rx;
|
|
15860
16470
|
if (!haveTopBottom && !haveLeftRight) return null;
|
|
@@ -16101,6 +16711,7 @@ function useHandTool(opts = {}) {
|
|
|
16101
16711
|
() => Object.assign(defineViewportTool({
|
|
16102
16712
|
id: "hand",
|
|
16103
16713
|
capabilities: ["navigation"],
|
|
16714
|
+
hotkey: "space",
|
|
16104
16715
|
hookName: "useHandTool",
|
|
16105
16716
|
presentation: {
|
|
16106
16717
|
label: "Hand",
|
|
@@ -16221,10 +16832,6 @@ function mirrorHandle(anchor, out) {
|
|
|
16221
16832
|
if (!out || anchor.altBroken) return void 0;
|
|
16222
16833
|
return { x: 2 * anchor.x - out.x, y: 2 * anchor.y - out.y };
|
|
16223
16834
|
}
|
|
16224
|
-
function dist2(ax, ay, bx, by) {
|
|
16225
|
-
const dx = ax - bx, dy = ay - by;
|
|
16226
|
-
return Math.hypot(dx, dy);
|
|
16227
|
-
}
|
|
16228
16835
|
function usePenTool(options) {
|
|
16229
16836
|
const { wrapPath, adapter, autoSelect = true, autoCommitOnClose = true, closeHitRadius = 8, snapPoint: snapPoint2 } = options;
|
|
16230
16837
|
const scratchRef = useRef(null);
|
|
@@ -16248,11 +16855,14 @@ function usePenTool(options) {
|
|
|
16248
16855
|
const fn = optsRef.current.snapPoint;
|
|
16249
16856
|
return fn ? fn({ x, y }) : { x, y };
|
|
16250
16857
|
}, []);
|
|
16251
|
-
const
|
|
16252
|
-
|
|
16253
|
-
|
|
16254
|
-
|
|
16255
|
-
|
|
16858
|
+
const withinCloseRadius = useCallback(
|
|
16859
|
+
(deps, ax, ay, wx, wy) => {
|
|
16860
|
+
const view = deps.view?.get();
|
|
16861
|
+
const scale2 = view?.scale ?? { x: 1, y: 1 };
|
|
16862
|
+
return withinPxRadius(ax - wx, ay - wy, optsRef.current.closeHitRadius, scale2);
|
|
16863
|
+
},
|
|
16864
|
+
[]
|
|
16865
|
+
);
|
|
16256
16866
|
const actions = useMemo(() => {
|
|
16257
16867
|
const s = () => scratchRef.current;
|
|
16258
16868
|
return [
|
|
@@ -16268,10 +16878,9 @@ function usePenTool(options) {
|
|
|
16268
16878
|
if (p?.pressX === void 0 || p.pressY === void 0) return;
|
|
16269
16879
|
const { x: wx, y: wy } = snap2(p.pressX, p.pressY);
|
|
16270
16880
|
const scratch = s();
|
|
16271
|
-
const radius = closeRadius(deps);
|
|
16272
16881
|
if (scratch.current && scratch.current.anchors.length >= 3) {
|
|
16273
16882
|
const first = scratch.current.anchors[0];
|
|
16274
|
-
if (
|
|
16883
|
+
if (withinCloseRadius(deps, first.x, first.y, wx, wy)) {
|
|
16275
16884
|
scratch.current.closed = true;
|
|
16276
16885
|
scratch.finishedSubpaths.push(scratch.current);
|
|
16277
16886
|
scratch.current = null;
|
|
@@ -16390,7 +16999,7 @@ function usePenTool(options) {
|
|
|
16390
16999
|
}
|
|
16391
17000
|
}
|
|
16392
17001
|
];
|
|
16393
|
-
}, [commit, snap2,
|
|
17002
|
+
}, [commit, snap2, withinCloseRadius]);
|
|
16394
17003
|
return useMemo(() => {
|
|
16395
17004
|
return defineTool({
|
|
16396
17005
|
id: "pen",
|
|
@@ -17956,8 +18565,8 @@ function CanvasInner(props, ref) {
|
|
|
17956
18565
|
const isVisible = getIsVisibleRef.current?.() ?? alwaysVisible;
|
|
17957
18566
|
for (const layer of layers2) {
|
|
17958
18567
|
if (!layer.hitTest) continue;
|
|
17959
|
-
const
|
|
17960
|
-
if (
|
|
18568
|
+
const hit = layer.hitTest(worldX, worldY, void 0, view, dims, isVisible);
|
|
18569
|
+
if (hit) return { layerId: layer.id, hit };
|
|
17961
18570
|
}
|
|
17962
18571
|
return null;
|
|
17963
18572
|
}, []);
|
|
@@ -18393,7 +19002,7 @@ function CanvasInner(props, ref) {
|
|
|
18393
19002
|
effectiveView,
|
|
18394
19003
|
{ width, height }
|
|
18395
19004
|
);
|
|
18396
|
-
renderer.render(commands);
|
|
19005
|
+
renderer.render(commands, viewToMat3(effectiveView));
|
|
18397
19006
|
}, [layersWithDebug, width, height, effectiveView, debugSink, redrawNonce, dprProp]);
|
|
18398
19007
|
const shaderIdKey = shaders?.map((h) => h.id).join("|") ?? "";
|
|
18399
19008
|
useEffect(() => {
|
|
@@ -20774,6 +21383,8 @@ function toAffordanceHit(hit) {
|
|
|
20774
21383
|
const scratch = hit.binding.initialScratch ?? {};
|
|
20775
21384
|
return {
|
|
20776
21385
|
kind: hit.region.hitKind ?? `${hit.affordanceId}:${hit.regionId}`,
|
|
21386
|
+
owner: hit.affordanceId,
|
|
21387
|
+
strength: "shared",
|
|
20777
21388
|
...scratch.targetId !== void 0 ? { targetIds: [scratch.targetId] } : {},
|
|
20778
21389
|
...scratch.anchor !== void 0 ? { anchor: scratch.anchor } : {},
|
|
20779
21390
|
...scratch.fixedPoint !== void 0 ? { fixedPoint: scratch.fixedPoint } : {},
|
|
@@ -21294,7 +21905,7 @@ function chordLengthParam(samples) {
|
|
|
21294
21905
|
ts[0] = 0;
|
|
21295
21906
|
let total = 0;
|
|
21296
21907
|
for (let i = 1; i < n; i++) {
|
|
21297
|
-
total +=
|
|
21908
|
+
total += dist2(samples[i - 1], samples[i]);
|
|
21298
21909
|
ts[i] = total;
|
|
21299
21910
|
}
|
|
21300
21911
|
if (total === 0) return null;
|
|
@@ -21327,13 +21938,13 @@ function solveLS(samples, ts, a, e, tStart, tEnd) {
|
|
|
21327
21938
|
let alpha1;
|
|
21328
21939
|
let alpha2;
|
|
21329
21940
|
if (Math.abs(det) < 1e-12) {
|
|
21330
|
-
const chord =
|
|
21941
|
+
const chord = dist2(a, e);
|
|
21331
21942
|
alpha1 = alpha2 = chord / 3;
|
|
21332
21943
|
} else {
|
|
21333
21944
|
alpha1 = (c22 * x1 - c12 * x2) / det;
|
|
21334
21945
|
alpha2 = (c11 * x2 - c12 * x1) / det;
|
|
21335
21946
|
if (alpha1 < 1e-6 || alpha2 < 1e-6) {
|
|
21336
|
-
const chord =
|
|
21947
|
+
const chord = dist2(a, e);
|
|
21337
21948
|
alpha1 = alpha2 = chord / 3;
|
|
21338
21949
|
}
|
|
21339
21950
|
}
|
|
@@ -21369,7 +21980,7 @@ function newtonRefineT(p, t, p0, p1, p2, p3) {
|
|
|
21369
21980
|
function sub(a, b) {
|
|
21370
21981
|
return { x: a.x - b.x, y: a.y - b.y };
|
|
21371
21982
|
}
|
|
21372
|
-
function
|
|
21983
|
+
function dist2(a, b) {
|
|
21373
21984
|
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
21374
21985
|
}
|
|
21375
21986
|
function unit(v) {
|
|
@@ -22329,7 +22940,7 @@ function SceneCanvasInner(props, ref) {
|
|
|
22329
22940
|
const getMode = getActiveModeRef.current;
|
|
22330
22941
|
if (!getMode) return true;
|
|
22331
22942
|
const registry22 = toolsForEligibilityRef.current?.registry;
|
|
22332
|
-
const caps = registry22?.[toolId]?.capabilities ?? [];
|
|
22943
|
+
const caps = registry22?.[toolId]?.eligibility.capabilities ?? [];
|
|
22333
22944
|
if (caps.length === 0) return false;
|
|
22334
22945
|
const allowed = getMode().allowedCapabilities;
|
|
22335
22946
|
for (const c of caps) if (allowed.has(c)) return true;
|
|
@@ -22803,10 +23414,6 @@ function GestureDispatcherMounter({
|
|
|
22803
23414
|
for (const tool of tools.ambient) m.set(tool.id, tool);
|
|
22804
23415
|
return m;
|
|
22805
23416
|
}, [tools.registry, tools.ambient]);
|
|
22806
|
-
const ambientToolIds = useMemo(
|
|
22807
|
-
() => tools.ambient.map((t) => t.id),
|
|
22808
|
-
[tools.ambient]
|
|
22809
|
-
);
|
|
22810
23417
|
const boundsOfRef = useRef(boundsOf2);
|
|
22811
23418
|
boundsOfRef.current = boundsOf2;
|
|
22812
23419
|
const pickEveryRef = useRef(pickEvery);
|
|
@@ -22918,9 +23525,14 @@ function GestureDispatcherMounter({
|
|
|
22918
23525
|
const worldPoint = clientToWorld2(screenPoint.x, screenPoint.y);
|
|
22919
23526
|
const extra = canvasApiRef?.current?.hitTestExtras?.(worldPoint.x, worldPoint.y);
|
|
22920
23527
|
if (extra) {
|
|
23528
|
+
const claim = extra.hit;
|
|
22921
23529
|
return {
|
|
22922
23530
|
kind: `layer:${extra.layerId}`,
|
|
22923
|
-
|
|
23531
|
+
owner: extra.layerId,
|
|
23532
|
+
strength: claim.strength ?? "shared",
|
|
23533
|
+
...claim.claimedKinds !== void 0 ? { claimedKinds: claim.claimedKinds } : {},
|
|
23534
|
+
...claim.cursor !== void 0 ? { cursor: claim.cursor } : {},
|
|
23535
|
+
...claim.initialScratch !== void 0 ? { payload: claim.initialScratch } : {}
|
|
22924
23536
|
};
|
|
22925
23537
|
}
|
|
22926
23538
|
return affordanceAt ? affordanceAt(worldPoint) : null;
|
|
@@ -22940,7 +23552,6 @@ function GestureDispatcherMounter({
|
|
|
22940
23552
|
canvasRef,
|
|
22941
23553
|
actions: registry22,
|
|
22942
23554
|
toolsById,
|
|
22943
|
-
ambientToolIds,
|
|
22944
23555
|
enabled,
|
|
22945
23556
|
keyboard,
|
|
22946
23557
|
affordanceAt: wrappedAffordanceAt,
|
|
@@ -23171,7 +23782,7 @@ function renderSceneToCanvas(args) {
|
|
|
23171
23782
|
entry.dpr = dpr;
|
|
23172
23783
|
}
|
|
23173
23784
|
const commands = buildSceneViewCommands(scene, view, drawOne, extraCommands);
|
|
23174
|
-
entry.renderer.render(commands);
|
|
23785
|
+
entry.renderer.render(commands, viewToMat3(view));
|
|
23175
23786
|
}
|
|
23176
23787
|
function SceneViewCanvasInner(props) {
|
|
23177
23788
|
const { scene, view, width, height, drawOne, extraCommands, className, canvasRef } = props;
|
|
@@ -23545,6 +24156,6 @@ function oklchToOklab(L, C, h) {
|
|
|
23545
24156
|
return [L, C * Math.cos(h), C * Math.sin(h)];
|
|
23546
24157
|
}
|
|
23547
24158
|
|
|
23548
|
-
export { FALLBACK_FIT_VIEW, MinimapCanvas, SceneViewCanvas, UnknownIcon, buildSceneViewCommands, computeFitView2, computeIndicatorCommand,
|
|
23549
|
-
//# sourceMappingURL=chunk-
|
|
23550
|
-
//# sourceMappingURL=chunk-
|
|
24159
|
+
export { FALLBACK_FIT_VIEW, MinimapCanvas, SceneViewCanvas, UnknownIcon, buildSceneViewCommands, computeFitView2, computeIndicatorCommand, eligibleContribution, evaluateEnabled, oklabToSrgbU8, oklchToOklab, renderSceneToCanvas, rgbaToHex, useActionsRegistry, useOptionalDepRegistry };
|
|
24160
|
+
//# sourceMappingURL=chunk-XVBXYKFJ.js.map
|
|
24161
|
+
//# sourceMappingURL=chunk-XVBXYKFJ.js.map
|