@almadar/ui 6.57.0 → 6.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AwaitingSkeletonContext-DPgW__WV.d.ts → AwaitingSkeletonContext-Bh399p1x.d.ts} +1 -3
- package/dist/{Canvas3DHost-DfAhg6EH.d.ts → Canvas3DHost-CNjANoaU.d.ts} +1 -1
- package/dist/{GameAudioProvider-DyBvYP2c.d.ts → GameAudioProvider-Dv_L-PHt.d.ts} +1 -1
- package/dist/{UISlotRenderer-NCGDIXN4.js → UISlotRenderer-YNY7V2UJ.js} +2 -2
- package/dist/avl/index.cjs +137 -71
- package/dist/avl/index.js +24 -8
- package/dist/{chunk-GZGDBS4S.js → chunk-7CVXM2KS.js} +1 -1
- package/dist/{chunk-PYJ2UQWM.js → chunk-E6MWYQVZ.js} +42 -35
- package/dist/{chunk-HA7CJWQX.js → chunk-J755QADM.js} +1184 -1184
- package/dist/{chunk-VHTJRS2N.js → chunk-L3MTZ3RH.js} +7 -0
- package/dist/{chunk-RHP5WL55.js → chunk-QJIAJ6EH.js} +77 -42
- package/dist/components/index.cjs +76 -32
- package/dist/components/index.d.ts +9 -7
- package/dist/components/index.js +5 -5
- package/dist/context/index.cjs +7 -0
- package/dist/context/index.js +1 -1
- package/dist/hooks/index.cjs +7 -0
- package/dist/hooks/index.js +1 -1
- package/dist/lib/drawable/three/index.d.ts +3 -3
- package/dist/lib/drawable/three/index.js +3 -3
- package/dist/lib/index.d.ts +2 -2
- package/dist/{paintDispatch-_6EhJCiL.d.ts → paintDispatch-Cp1_S93j.d.ts} +30 -2
- package/dist/providers/index.cjs +51 -50
- package/dist/providers/index.d.ts +4 -4
- package/dist/providers/index.js +8 -21
- package/dist/runtime/index.cjs +120 -70
- package/dist/runtime/index.d.ts +4 -4
- package/dist/runtime/index.js +4 -4
- package/dist/ssr/index.cjs +2 -0
- package/dist/ssr/index.js +2 -0
- package/dist/{verificationRegistry-CD7Xb40L.d.ts → verificationRegistry-B3dj1Zl8.d.ts} +1 -1
- package/package.json +9 -8
|
@@ -4,7 +4,7 @@ import { updateAssetStatus } from './chunk-Q47ZN2DW.js';
|
|
|
4
4
|
import { cn, domPassthrough } from './chunk-64WACXM7.js';
|
|
5
5
|
import { __publicField } from './chunk-QPXIAUJD.js';
|
|
6
6
|
import { createLogger } from '@almadar/logger';
|
|
7
|
-
import React, { useCallback, createContext, useState, useLayoutEffect
|
|
7
|
+
import React, { useCallback, createContext, useContext, useState, useLayoutEffect } from 'react';
|
|
8
8
|
import { useThemeScope, useTheme } from '@almadar/ui/context';
|
|
9
9
|
import { jsx } from 'react/jsx-runtime';
|
|
10
10
|
import { renderUiEntriesOf } from '@almadar/core/patterns';
|
|
@@ -1000,1305 +1000,1305 @@ function parseTransitionLevel(schema, orbitalName, traitName, transitionIndex) {
|
|
|
1000
1000
|
};
|
|
1001
1001
|
}
|
|
1002
1002
|
|
|
1003
|
-
// lib/
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
return typeof a.tileWidth === "number";
|
|
1003
|
+
// lib/drawable/contract.ts
|
|
1004
|
+
function isValidScenePos(pos) {
|
|
1005
|
+
return Number.isFinite(pos?.x) && Number.isFinite(pos?.y);
|
|
1007
1006
|
}
|
|
1008
|
-
function
|
|
1009
|
-
|
|
1010
|
-
return typeof
|
|
1007
|
+
function spriteAssetState(node) {
|
|
1008
|
+
if (!isValidScenePos(node.position)) return "invalid-position";
|
|
1009
|
+
return typeof node.asset?.url === "string" && node.asset.url.length > 0 ? "ready" : "asset-unset";
|
|
1011
1010
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
return
|
|
1011
|
+
var SPRITE_FALLBACK_FILL = "#9b8f7f";
|
|
1012
|
+
var SPRITE_FALLBACK_STROKE = "#5e564b";
|
|
1013
|
+
function spriteRect(projector, node, natural) {
|
|
1014
|
+
const tw = projector.tileWidth;
|
|
1015
|
+
const fallbackW = projector.worldPixelDirect ? natural?.w ?? 0 : tw;
|
|
1016
|
+
const fallbackH = projector.worldPixelDirect ? natural?.h ?? 0 : tw;
|
|
1017
|
+
const w = node.width !== void 0 ? node.width * tw : fallbackW;
|
|
1018
|
+
const h = node.height !== void 0 ? node.height * tw : fallbackH;
|
|
1019
|
+
const anchor = node.anchor ?? "top-left";
|
|
1020
|
+
const p = projector.anchorPoint(node.position, anchor);
|
|
1021
|
+
return {
|
|
1022
|
+
x: anchor === "top-left" ? p.x : p.x - w / 2,
|
|
1023
|
+
y: anchor === "ground" ? p.y - h : anchor === "center" ? p.y - h / 2 : p.y,
|
|
1024
|
+
w,
|
|
1025
|
+
h
|
|
1026
|
+
};
|
|
1023
1027
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1028
|
+
var DrawableRegistryContext = createContext(null);
|
|
1029
|
+
var NUMERIC_TRACKS = [
|
|
1030
|
+
"offsetX",
|
|
1031
|
+
"offsetY",
|
|
1032
|
+
"rotate",
|
|
1033
|
+
"opacity",
|
|
1034
|
+
"radiusX",
|
|
1035
|
+
"radiusY",
|
|
1036
|
+
"width",
|
|
1037
|
+
"height",
|
|
1038
|
+
"strokeWidth",
|
|
1039
|
+
"strokeDashOffset",
|
|
1040
|
+
"blur"
|
|
1041
|
+
];
|
|
1042
|
+
function isAnimatedShape(node) {
|
|
1043
|
+
return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
|
|
1026
1044
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1045
|
+
var lerp = (a, b, k) => a + (b - a) * k;
|
|
1046
|
+
function applyShapeAnimation(node, timeMs) {
|
|
1047
|
+
const anim = node.animation;
|
|
1048
|
+
if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return node;
|
|
1049
|
+
const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
|
|
1050
|
+
const cycle = timeMs / anim.durationMs;
|
|
1051
|
+
const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
|
|
1052
|
+
const out = { ...node };
|
|
1053
|
+
const trackValue = (key) => {
|
|
1054
|
+
const defined = frames.filter((f) => f[key] !== void 0);
|
|
1055
|
+
if (defined.length === 0) return void 0;
|
|
1056
|
+
let prev;
|
|
1057
|
+
let next;
|
|
1058
|
+
for (const f of defined) {
|
|
1059
|
+
if (f.at <= t) prev = f;
|
|
1060
|
+
else if (!next) next = f;
|
|
1040
1061
|
}
|
|
1041
|
-
if (!
|
|
1042
|
-
|
|
1043
|
-
const
|
|
1062
|
+
if (!prev) return defined[0][key];
|
|
1063
|
+
if (!next) return prev[key];
|
|
1064
|
+
const span = next.at - prev.at;
|
|
1065
|
+
const k = span > 0 ? (t - prev.at) / span : 1;
|
|
1066
|
+
const a = prev[key];
|
|
1067
|
+
const b = next[key];
|
|
1068
|
+
if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
|
|
1069
|
+
return a;
|
|
1070
|
+
};
|
|
1071
|
+
for (const key of NUMERIC_TRACKS) {
|
|
1072
|
+
const v = trackValue(key);
|
|
1073
|
+
if (v !== void 0) out[key] = v;
|
|
1074
|
+
}
|
|
1075
|
+
const fill = trackValue("fill");
|
|
1076
|
+
if (fill !== void 0) out.fill = fill;
|
|
1077
|
+
const stroke = trackValue("stroke");
|
|
1078
|
+
if (stroke !== void 0) out.stroke = stroke;
|
|
1079
|
+
const shadowFrames = frames.filter((f) => f.shadow !== void 0);
|
|
1080
|
+
if (shadowFrames.length > 0) {
|
|
1081
|
+
const sh = trackValue("shadow");
|
|
1082
|
+
if (sh !== void 0) {
|
|
1083
|
+
let prevSh;
|
|
1084
|
+
let nextSh;
|
|
1085
|
+
for (const f of shadowFrames) {
|
|
1086
|
+
if (f.at <= t) prevSh = f;
|
|
1087
|
+
else if (!nextSh) nextSh = f;
|
|
1088
|
+
}
|
|
1089
|
+
if (prevSh?.shadow && nextSh?.shadow) {
|
|
1090
|
+
const span = nextSh.at - prevSh.at;
|
|
1091
|
+
const k = span > 0 ? (t - prevSh.at) / span : 1;
|
|
1092
|
+
out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
|
|
1093
|
+
} else {
|
|
1094
|
+
out.shadow = sh;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return out;
|
|
1099
|
+
}
|
|
1100
|
+
function gradientStyle(g, ox, oy, scale) {
|
|
1101
|
+
if (g.kind === "linear") {
|
|
1102
|
+
if (!g.from || !g.to) return void 0;
|
|
1044
1103
|
return {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1104
|
+
kind: "linear",
|
|
1105
|
+
x1: ox + g.from.x * scale,
|
|
1106
|
+
y1: oy + g.from.y * scale,
|
|
1107
|
+
x2: ox + g.to.x * scale,
|
|
1108
|
+
y2: oy + g.to.y * scale,
|
|
1109
|
+
stops: g.stops
|
|
1049
1110
|
};
|
|
1050
1111
|
}
|
|
1051
|
-
if (
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
return atlasUrlOf(asset) !== void 0 && typeof asset?.sprite === "string" && asset.sprite.length > 0;
|
|
1061
|
-
}
|
|
1062
|
-
function resolveAssetSource(img, asset, onReady) {
|
|
1063
|
-
if (isAtlasAsset(asset)) {
|
|
1064
|
-
const atlas = getAtlas(asset.atlas, onReady);
|
|
1065
|
-
if (!atlas) return null;
|
|
1066
|
-
const rect = subRectFor(atlas, asset.sprite);
|
|
1067
|
-
if (!rect) return null;
|
|
1068
|
-
return { img, rect, aspect: rect.sw / rect.sh };
|
|
1112
|
+
if (g.kind === "conic") {
|
|
1113
|
+
if (!g.center) return void 0;
|
|
1114
|
+
return {
|
|
1115
|
+
kind: "conic",
|
|
1116
|
+
cx: ox + g.center.x * scale,
|
|
1117
|
+
cy: oy + g.center.y * scale,
|
|
1118
|
+
angle: g.angle ?? 0,
|
|
1119
|
+
stops: g.stops
|
|
1120
|
+
};
|
|
1069
1121
|
}
|
|
1070
|
-
|
|
1071
|
-
const natH = img.naturalHeight || 1;
|
|
1072
|
-
return { img, rect: null, aspect: natW / natH };
|
|
1073
|
-
}
|
|
1074
|
-
function blit(ctx, src, dx, dy, dw, dh) {
|
|
1075
|
-
if (src.rect) ctx.drawImage(src.img, src.rect.sx, src.rect.sy, src.rect.sw, src.rect.sh, dx, dy, dw, dh);
|
|
1076
|
-
else ctx.drawImage(src.img, dx, dy, dw, dh);
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// components/core/atoms/fx.ts
|
|
1080
|
-
function resolveFxView(item, presets) {
|
|
1081
|
-
const row = presets?.find((p) => p.type === item.type);
|
|
1082
|
-
if (!row) return item;
|
|
1122
|
+
if (!g.center || g.radius === void 0) return void 0;
|
|
1083
1123
|
return {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
vx: item.vx ?? row.vx,
|
|
1090
|
-
vy: item.vy ?? row.vy,
|
|
1091
|
-
vz: item.vz ?? row.vz,
|
|
1092
|
-
particleCount: item.particleCount ?? row.particleCount,
|
|
1093
|
-
animation: item.animation ?? row.animation,
|
|
1094
|
-
shape: row.shape,
|
|
1095
|
-
count: row.count,
|
|
1096
|
-
glow: row.glow,
|
|
1097
|
-
gravity: row.gravity,
|
|
1098
|
-
color2: row.color2
|
|
1124
|
+
kind: "radial",
|
|
1125
|
+
cx: ox + g.center.x * scale,
|
|
1126
|
+
cy: oy + g.center.y * scale,
|
|
1127
|
+
r: g.radius * scale,
|
|
1128
|
+
stops: g.stops
|
|
1099
1129
|
};
|
|
1100
1130
|
}
|
|
1101
|
-
function
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
|
|
1106
|
-
return { lifeMs, ageMs, progress, fade: 1 - progress };
|
|
1107
|
-
}
|
|
1108
|
-
function fxHash01(seed, salt) {
|
|
1109
|
-
let h = 2166136261 ^ salt;
|
|
1110
|
-
for (let i = 0; i < seed.length; i++) {
|
|
1111
|
-
h ^= seed.charCodeAt(i);
|
|
1112
|
-
h = Math.imul(h, 16777619);
|
|
1131
|
+
function patternStyle(p, unit) {
|
|
1132
|
+
if (p.kind === "image") {
|
|
1133
|
+
if (!p.url) return void 0;
|
|
1134
|
+
return { kind: "image", url: p.url, scale: (p.scale ?? 1) / unit };
|
|
1113
1135
|
}
|
|
1114
|
-
|
|
1115
|
-
h = Math.imul(h, 1274126177);
|
|
1116
|
-
h ^= h >>> 16;
|
|
1117
|
-
return (h >>> 0) / 4294967296;
|
|
1118
|
-
}
|
|
1119
|
-
var CONFETTI_COLORS = [
|
|
1120
|
-
"var(--color-primary)",
|
|
1121
|
-
"var(--color-success)",
|
|
1122
|
-
"var(--color-warning)",
|
|
1123
|
-
"var(--color-error)",
|
|
1124
|
-
"gold",
|
|
1125
|
-
"dodgerblue"
|
|
1126
|
-
];
|
|
1127
|
-
function createConfettiParticles(count, seed) {
|
|
1128
|
-
return Array.from({ length: count }, (_, i) => ({
|
|
1129
|
-
id: i,
|
|
1130
|
-
color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
|
|
1131
|
-
left: 30 + fxHash01(seed, i * 7 + 2) * 40,
|
|
1132
|
-
delay: fxHash01(seed, i * 7 + 3) * 300,
|
|
1133
|
-
angle: fxHash01(seed, i * 7 + 4) * 360,
|
|
1134
|
-
distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
|
|
1135
|
-
rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
|
|
1136
|
-
size: 4 + fxHash01(seed, i * 7 + 7) * 6
|
|
1137
|
-
}));
|
|
1136
|
+
return { kind: "noise", scale: (p.scale ?? 2) / unit, alpha: p.alpha, color: p.color };
|
|
1138
1137
|
}
|
|
1139
|
-
var
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
var log2 = createLogger("almadar:ui:canvas-theme");
|
|
1155
|
-
var toneVar = (tone) => `--color-${DIAGRAM_TONE_COLOR_KEYS[tone]}`;
|
|
1156
|
-
var SERIES_TONES = ["series-1", "series-2", "series-3", "series-4", "series-5", "series-6", "series-7", "series-8"];
|
|
1157
|
-
var TONE_SET = new Set(DIAGRAM_TONES);
|
|
1158
|
-
function isDiagramTone(value) {
|
|
1159
|
-
return TONE_SET.has(value);
|
|
1160
|
-
}
|
|
1161
|
-
function colorCss(c) {
|
|
1162
|
-
return c.a >= 1 ? c.css : `rgba(${c.r}, ${c.g}, ${c.b}, ${c.a})`;
|
|
1163
|
-
}
|
|
1164
|
-
function oneOf(value, allowed, fallback, name) {
|
|
1165
|
-
const hit = allowed.find((a) => a === value);
|
|
1166
|
-
if (hit !== void 0) return hit;
|
|
1167
|
-
if (value !== "") log2.warn("invalid-theme-keyword", { var: name, value, allowed });
|
|
1168
|
-
return fallback;
|
|
1169
|
-
}
|
|
1170
|
-
var PLAIN_LENGTH = /^(-?[\d.]+)(px)?$/;
|
|
1171
|
-
var ScopeReader = class {
|
|
1172
|
-
constructor(scope) {
|
|
1173
|
-
__publicField(this, "scope", scope);
|
|
1174
|
-
__publicField(this, "style");
|
|
1175
|
-
__publicField(this, "probe");
|
|
1176
|
-
this.style = getComputedStyle(scope);
|
|
1177
|
-
const doc = scope.ownerDocument;
|
|
1178
|
-
this.probe = doc.createElement("span");
|
|
1179
|
-
this.probe.style.position = "absolute";
|
|
1180
|
-
this.probe.style.visibility = "hidden";
|
|
1181
|
-
this.probe.style.pointerEvents = "none";
|
|
1182
|
-
scope.appendChild(this.probe);
|
|
1183
|
-
}
|
|
1184
|
-
dispose() {
|
|
1185
|
-
this.probe.remove();
|
|
1186
|
-
}
|
|
1187
|
-
raw(name) {
|
|
1188
|
-
return this.style.getPropertyValue(name).trim();
|
|
1189
|
-
}
|
|
1190
|
-
/** A length var in px: a plain value parses directly, a derived one (`calc`, `var`) through the probe. */
|
|
1191
|
-
length(name, fallback) {
|
|
1192
|
-
const raw = this.raw(name);
|
|
1193
|
-
const plain = PLAIN_LENGTH.exec(raw);
|
|
1194
|
-
if (plain) return Number(plain[1]);
|
|
1195
|
-
if (raw === "") return fallback;
|
|
1196
|
-
this.probe.style.width = `var(${name})`;
|
|
1197
|
-
const computed = PLAIN_LENGTH.exec(getComputedStyle(this.probe).width.trim());
|
|
1198
|
-
this.probe.style.width = "";
|
|
1199
|
-
if (computed) return Number(computed[1]);
|
|
1200
|
-
log2.warn("unresolvable-theme-length", { var: name, raw });
|
|
1201
|
-
return fallback;
|
|
1202
|
-
}
|
|
1203
|
-
number(name, fallback) {
|
|
1204
|
-
const raw = this.raw(name);
|
|
1205
|
-
if (raw === "") return fallback;
|
|
1206
|
-
const n = Number(raw);
|
|
1207
|
-
if (Number.isFinite(n)) return n;
|
|
1208
|
-
log2.warn("unresolvable-theme-number", { var: name, raw });
|
|
1209
|
-
return fallback;
|
|
1210
|
-
}
|
|
1211
|
-
/** A duration var in ms (`250ms`, `0.25s`, or a `var()` alias resolved through the probe). */
|
|
1212
|
-
durationMs(name, fallback) {
|
|
1213
|
-
const parse = (v) => {
|
|
1214
|
-
const m = /^([\d.]+)(ms|s)$/.exec(v.trim());
|
|
1215
|
-
return m ? Number(m[1]) * (m[2] === "s" ? 1e3 : 1) : null;
|
|
1216
|
-
};
|
|
1217
|
-
const raw = this.raw(name);
|
|
1218
|
-
const direct = parse(raw);
|
|
1219
|
-
if (direct !== null) return direct;
|
|
1220
|
-
if (raw === "") return fallback;
|
|
1221
|
-
this.probe.style.transitionDuration = `var(${name})`;
|
|
1222
|
-
const computed = parse(getComputedStyle(this.probe).transitionDuration.split(",")[0] ?? "");
|
|
1223
|
-
this.probe.style.transitionDuration = "";
|
|
1224
|
-
return computed ?? fallback;
|
|
1225
|
-
}
|
|
1226
|
-
color(name) {
|
|
1227
|
-
const raw = this.raw(name);
|
|
1228
|
-
return parseComputedColor(raw) ?? resolveThemeColor(`var(${name})`, this.scope);
|
|
1229
|
-
}
|
|
1230
|
-
shadow(name) {
|
|
1231
|
-
const raw = this.raw(name);
|
|
1232
|
-
if (raw === "" || raw === "none") return null;
|
|
1233
|
-
this.probe.style.boxShadow = `var(${name})`;
|
|
1234
|
-
const computed = getComputedStyle(this.probe).boxShadow.trim() || raw;
|
|
1235
|
-
this.probe.style.boxShadow = "";
|
|
1236
|
-
const colorMatch = /(rgba?\([^)]*\)|#[0-9a-f]{3,8}|oklch\([^)]*\))/i.exec(computed);
|
|
1237
|
-
const lengths = computed.replace(colorMatch?.[0] ?? "", "").trim().split(/\s+/).map((t) => PLAIN_LENGTH.exec(t)).filter((m) => m !== null).map((m) => Number(m[1]));
|
|
1238
|
-
if (lengths.length < 2) return null;
|
|
1239
|
-
const color = colorMatch ? parseComputedColor(colorMatch[0]) : null;
|
|
1240
|
-
return { offsetX: lengths[0], offsetY: lengths[1], blur: lengths[2] ?? 0, color: color ? colorCss(color) : "rgba(0, 0, 0, 0.25)" };
|
|
1138
|
+
var paintShape = (painter, rawNode, dctx) => {
|
|
1139
|
+
const node = dctx.time > 0 ? applyShapeAnimation(rawNode, dctx.time) : rawNode;
|
|
1140
|
+
if (!isValidScenePos(node.position)) return;
|
|
1141
|
+
painter.save();
|
|
1142
|
+
if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
|
|
1143
|
+
const origin = dctx.projector.project(node.position);
|
|
1144
|
+
const tileWidth = dctx.projector.tileWidth;
|
|
1145
|
+
const groupScale = dctx.groupScale ?? 1;
|
|
1146
|
+
const strokePx = (node.strokeWidth ?? 1) / groupScale;
|
|
1147
|
+
if (node.blendMode) painter.setBlend(node.blendMode);
|
|
1148
|
+
if (node.strokeDash && node.strokeDash.length > 0) {
|
|
1149
|
+
painter.setLineDash(
|
|
1150
|
+
node.strokeDash.map((v) => v / groupScale),
|
|
1151
|
+
(node.strokeDashOffset ?? 0) / groupScale
|
|
1152
|
+
);
|
|
1241
1153
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1154
|
+
if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
|
|
1155
|
+
if (node.rotate) {
|
|
1156
|
+
const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
|
|
1157
|
+
const px = origin.x + pivot.x * tileWidth;
|
|
1158
|
+
const py = origin.y + pivot.y * tileWidth;
|
|
1159
|
+
painter.translate(px, py);
|
|
1160
|
+
painter.rotate(node.rotate);
|
|
1161
|
+
painter.translate(-px, -py);
|
|
1244
1162
|
}
|
|
1245
|
-
};
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
const tones = {};
|
|
1259
|
-
for (const tone of DIAGRAM_TONES) {
|
|
1260
|
-
const c = r.color(toneVar(tone));
|
|
1261
|
-
tones[tone] = c ? colorCss(c) : "currentColor";
|
|
1163
|
+
if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
|
|
1164
|
+
const fill = node.fill === "none" ? void 0 : node.fill;
|
|
1165
|
+
const stroke = node.stroke === "none" ? void 0 : node.stroke;
|
|
1166
|
+
const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
|
|
1167
|
+
const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
|
|
1168
|
+
const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
|
|
1169
|
+
switch (node.shape) {
|
|
1170
|
+
case "cell": {
|
|
1171
|
+
const pts = dctx.projector.cellPath(node.position);
|
|
1172
|
+
if (pxFill) painter.fillPoly(pts, pxFill);
|
|
1173
|
+
if (patFill) painter.fillPoly(pts, patFill);
|
|
1174
|
+
if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
|
|
1175
|
+
break;
|
|
1262
1176
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
},
|
|
1307
|
-
scene: {
|
|
1308
|
-
roughness: r.number("--material-roughness", 0.6),
|
|
1309
|
-
metalness: r.number("--material-metalness", 0),
|
|
1310
|
-
flat: r.number("--material-flat", 0) >= 1,
|
|
1311
|
-
outline: r.number("--material-outline", 0),
|
|
1312
|
-
ambient: r.number("--light-ambient", 0.55),
|
|
1313
|
-
key: r.number("--light-key", 1),
|
|
1314
|
-
keyColor: (() => {
|
|
1315
|
-
const c = r.color("--light-key-color");
|
|
1316
|
-
return c ? colorCss(c) : "#ffffff";
|
|
1317
|
-
})(),
|
|
1318
|
-
fog: r.number("--scene-fog", 0)
|
|
1177
|
+
case "rect": {
|
|
1178
|
+
const p = dctx.projector.anchorPoint(node.position, node.anchor ?? "top-left");
|
|
1179
|
+
const tw = dctx.projector.tileWidth;
|
|
1180
|
+
const x = p.x + (node.offsetX ?? 0) * tw;
|
|
1181
|
+
const y = p.y + (node.offsetY ?? 0) * tw;
|
|
1182
|
+
const w = (node.width ?? 0) * tw;
|
|
1183
|
+
const h = (node.height ?? 0) * tw;
|
|
1184
|
+
if (pxFill) painter.fillRect(x, y, w, h, pxFill);
|
|
1185
|
+
if (patFill) painter.fillRect(x, y, w, h, patFill);
|
|
1186
|
+
if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
|
|
1187
|
+
break;
|
|
1188
|
+
}
|
|
1189
|
+
case "ellipse": {
|
|
1190
|
+
const p = dctx.projector.anchorPoint(node.position, node.anchor ?? "ground");
|
|
1191
|
+
const tw = dctx.projector.tileWidth;
|
|
1192
|
+
const cx = p.x + (node.offsetX ?? 0) * tw;
|
|
1193
|
+
const cy = p.y + (node.offsetY ?? 0) * tw;
|
|
1194
|
+
const rx = (node.radiusX ?? 0) * tw;
|
|
1195
|
+
const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
|
|
1196
|
+
if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
|
|
1197
|
+
if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
|
|
1198
|
+
if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
case "poly": {
|
|
1202
|
+
const pts = (node.points ?? []).map((pt) => ({
|
|
1203
|
+
x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
|
|
1204
|
+
y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
|
|
1205
|
+
}));
|
|
1206
|
+
if (pxFill) painter.fillPoly(pts, pxFill);
|
|
1207
|
+
if (patFill) painter.fillPoly(pts, patFill);
|
|
1208
|
+
if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
|
|
1209
|
+
break;
|
|
1210
|
+
}
|
|
1211
|
+
case "path": {
|
|
1212
|
+
if (!node.d) break;
|
|
1213
|
+
painter.translate(origin.x + (node.offsetX ?? 0) * tileWidth, origin.y + (node.offsetY ?? 0) * tileWidth);
|
|
1214
|
+
painter.scale(tileWidth, tileWidth);
|
|
1215
|
+
if (node.strokeDash && node.strokeDash.length > 0) {
|
|
1216
|
+
painter.setLineDash(
|
|
1217
|
+
node.strokeDash.map((v) => v / (groupScale * tileWidth)),
|
|
1218
|
+
(node.strokeDashOffset ?? 0) / (groupScale * tileWidth)
|
|
1219
|
+
);
|
|
1319
1220
|
}
|
|
1221
|
+
if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / (groupScale * tileWidth));
|
|
1222
|
+
const localFill = node.gradient ? gradientStyle(node.gradient, 0, 0, 1) ?? fill : fill;
|
|
1223
|
+
const localStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, 0, 0, 1) ?? stroke : stroke;
|
|
1224
|
+
const localPat = node.fillPattern ? patternStyle(node.fillPattern, groupScale * tileWidth) : void 0;
|
|
1225
|
+
if (localFill) painter.fillPath(node.d, localFill);
|
|
1226
|
+
if (localPat) painter.fillPath(node.d, localPat);
|
|
1227
|
+
if (localStroke) painter.strokePath(node.d, localStroke, strokePx / tileWidth);
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
painter.restore();
|
|
1232
|
+
};
|
|
1233
|
+
function DrawShape(props) {
|
|
1234
|
+
const register = useContext(DrawableRegistryContext);
|
|
1235
|
+
if (register) {
|
|
1236
|
+
const { opacity, ...rest } = props;
|
|
1237
|
+
const node = {
|
|
1238
|
+
...rest,
|
|
1239
|
+
type: "draw-shape",
|
|
1240
|
+
...opacity !== void 0 && opacity > 0 ? { opacity } : {}
|
|
1320
1241
|
};
|
|
1321
|
-
|
|
1322
|
-
r.dispose();
|
|
1242
|
+
register(node);
|
|
1323
1243
|
}
|
|
1244
|
+
return null;
|
|
1324
1245
|
}
|
|
1325
|
-
function
|
|
1326
|
-
|
|
1327
|
-
const colors = series.map((c, i) => markColor(theme, c, scope, SERIES_TONES[i % SERIES_TONES.length]));
|
|
1328
|
-
const tones = { ...theme.tones };
|
|
1329
|
-
SERIES_TONES.forEach((tone, i) => {
|
|
1330
|
-
tones[tone] = colors[i % colors.length];
|
|
1331
|
-
});
|
|
1332
|
-
return { ...theme, tones, series: SERIES_TONES.map((t) => tones[t]) };
|
|
1333
|
-
}
|
|
1334
|
-
function markColor(theme, value, scope, fallback) {
|
|
1335
|
-
if (value === void 0 || value === "") return theme.tones[fallback];
|
|
1336
|
-
if (isDiagramTone(value)) return theme.tones[value];
|
|
1337
|
-
if (value.startsWith("var(") || value.startsWith("color-mix(")) {
|
|
1338
|
-
const resolved = resolveThemeColor(value, scope);
|
|
1339
|
-
return resolved ? colorCss(resolved) : theme.tones[fallback];
|
|
1340
|
-
}
|
|
1341
|
-
return value;
|
|
1246
|
+
function isAnimatedGroup(node) {
|
|
1247
|
+
return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
|
|
1342
1248
|
}
|
|
1343
|
-
function
|
|
1344
|
-
|
|
1249
|
+
function DrawGroup(props) {
|
|
1250
|
+
const register = useContext(DrawableRegistryContext);
|
|
1251
|
+
if (register) register({ ...props, type: "draw-group" });
|
|
1252
|
+
return null;
|
|
1345
1253
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1254
|
+
|
|
1255
|
+
// lib/imageCache.ts
|
|
1256
|
+
var RETRY_AFTER_MS = 5e3;
|
|
1257
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1258
|
+
function startLoad(url, onReady, existing) {
|
|
1259
|
+
const img = new Image();
|
|
1260
|
+
img.crossOrigin = "anonymous";
|
|
1261
|
+
const entry = existing ?? { img, status: "pending" };
|
|
1262
|
+
entry.img = img;
|
|
1263
|
+
if (onReady) entry.onReady = onReady;
|
|
1264
|
+
img.onload = () => {
|
|
1265
|
+
entry.status = "loaded";
|
|
1266
|
+
updateAssetStatus(url, "loaded");
|
|
1267
|
+
entry.onReady?.();
|
|
1268
|
+
};
|
|
1269
|
+
img.onerror = () => {
|
|
1270
|
+
entry.status = "failed";
|
|
1271
|
+
entry.failedAt = Date.now();
|
|
1272
|
+
updateAssetStatus(url, "failed");
|
|
1273
|
+
entry.onReady?.();
|
|
1274
|
+
};
|
|
1275
|
+
img.src = url;
|
|
1276
|
+
return entry;
|
|
1350
1277
|
}
|
|
1351
|
-
function
|
|
1352
|
-
|
|
1278
|
+
function getOrLoadImage(url, onReady) {
|
|
1279
|
+
if (!url || typeof window === "undefined") return null;
|
|
1280
|
+
const existing = cache.get(url);
|
|
1281
|
+
if (existing) {
|
|
1282
|
+
if (existing.status === "loaded") return existing.img;
|
|
1283
|
+
if (existing.status === "pending" && onReady) existing.onReady = onReady;
|
|
1284
|
+
if (existing.status === "failed" && Date.now() - (existing.failedAt ?? 0) >= RETRY_AFTER_MS) {
|
|
1285
|
+
existing.failedAt = Date.now();
|
|
1286
|
+
startLoad(url, onReady, existing);
|
|
1287
|
+
}
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
const entry = startLoad(url, onReady);
|
|
1291
|
+
cache.set(url, entry);
|
|
1292
|
+
updateAssetStatus(url, "pending");
|
|
1293
|
+
return null;
|
|
1353
1294
|
}
|
|
1354
|
-
function
|
|
1355
|
-
|
|
1356
|
-
ctx.save();
|
|
1357
|
-
ctx.strokeStyle = theme.ground;
|
|
1358
|
-
ctx.lineWidth = Math.max(2, theme.text.xs * 0.3);
|
|
1359
|
-
ctx.lineJoin = "round";
|
|
1360
|
-
ctx.strokeText(text, x, y);
|
|
1361
|
-
ctx.restore();
|
|
1362
|
-
ctx.fillText(text, x, y);
|
|
1295
|
+
function getImageStatus(url) {
|
|
1296
|
+
return cache.get(url)?.status;
|
|
1363
1297
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1298
|
+
|
|
1299
|
+
// lib/skinning.ts
|
|
1300
|
+
var IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
1301
|
+
function composeAffine(a, b) {
|
|
1302
|
+
return [
|
|
1303
|
+
a[0] * b[0] + a[2] * b[1],
|
|
1304
|
+
a[1] * b[0] + a[3] * b[1],
|
|
1305
|
+
a[0] * b[2] + a[2] * b[3],
|
|
1306
|
+
a[1] * b[2] + a[3] * b[3],
|
|
1307
|
+
a[0] * b[4] + a[2] * b[5] + a[4],
|
|
1308
|
+
a[1] * b[4] + a[3] * b[5] + a[5]
|
|
1309
|
+
];
|
|
1366
1310
|
}
|
|
1367
|
-
function
|
|
1368
|
-
|
|
1311
|
+
function invertAffine(m) {
|
|
1312
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
1313
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return IDENTITY;
|
|
1314
|
+
const inv = 1 / det;
|
|
1315
|
+
return [
|
|
1316
|
+
m[3] * inv,
|
|
1317
|
+
-m[1] * inv,
|
|
1318
|
+
-m[2] * inv,
|
|
1319
|
+
m[0] * inv,
|
|
1320
|
+
(m[2] * m[5] - m[3] * m[4]) * inv,
|
|
1321
|
+
(m[1] * m[4] - m[0] * m[5]) * inv
|
|
1322
|
+
];
|
|
1369
1323
|
}
|
|
1370
|
-
function
|
|
1371
|
-
|
|
1372
|
-
ctx.lineJoin = theme.lineJoin;
|
|
1373
|
-
if (theme.glow > 0) {
|
|
1374
|
-
ctx.shadowColor = strokeColor;
|
|
1375
|
-
ctx.shadowBlur = theme.glow;
|
|
1376
|
-
ctx.shadowOffsetX = 0;
|
|
1377
|
-
ctx.shadowOffsetY = 0;
|
|
1378
|
-
}
|
|
1324
|
+
function applyAffine(m, x, y) {
|
|
1325
|
+
return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
|
|
1379
1326
|
}
|
|
1380
|
-
function
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1327
|
+
function rotationAbout(pivot, degrees) {
|
|
1328
|
+
const r = degrees * Math.PI / 180;
|
|
1329
|
+
const cos = Math.cos(r);
|
|
1330
|
+
const sin = Math.sin(r);
|
|
1331
|
+
const [px, py] = pivot;
|
|
1332
|
+
return [cos, sin, -sin, cos, px - cos * px + sin * py, py - sin * px - cos * py];
|
|
1384
1333
|
}
|
|
1385
|
-
function
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1334
|
+
function computeWorldMatrices(bones, pose) {
|
|
1335
|
+
const indexByName = /* @__PURE__ */ new Map();
|
|
1336
|
+
bones.forEach((b, i) => indexByName.set(b.name, i));
|
|
1337
|
+
const world = new Array(bones.length);
|
|
1338
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1339
|
+
const resolve = (i) => {
|
|
1340
|
+
const cached = world[i];
|
|
1341
|
+
if (cached) return cached;
|
|
1342
|
+
if (visiting.has(i)) return IDENTITY;
|
|
1343
|
+
visiting.add(i);
|
|
1344
|
+
const b = bones[i];
|
|
1345
|
+
const parentIndex = b.parent === null ? void 0 : indexByName.get(b.parent);
|
|
1346
|
+
const parentWorld = parentIndex === void 0 ? IDENTITY : resolve(parentIndex);
|
|
1347
|
+
const local = rotationAbout(b.pivot, pose[b.name] ?? 0);
|
|
1348
|
+
const m = composeAffine(parentWorld, local);
|
|
1349
|
+
visiting.delete(i);
|
|
1350
|
+
world[i] = m;
|
|
1351
|
+
return m;
|
|
1352
|
+
};
|
|
1353
|
+
return bones.map((_, i) => resolve(i));
|
|
1388
1354
|
}
|
|
1389
|
-
function
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const a = pts[i - 1];
|
|
1397
|
-
const b = pts[i];
|
|
1398
|
-
if (amp === 0) {
|
|
1399
|
-
ctx.lineTo(b.x, b.y);
|
|
1400
|
-
continue;
|
|
1401
|
-
}
|
|
1402
|
-
const steps = Math.max(1, Math.round(Math.hypot(b.x - a.x, b.y - a.y) / 24));
|
|
1403
|
-
for (let s = 1; s <= steps; s++) {
|
|
1404
|
-
const t = s / steps;
|
|
1405
|
-
const x = a.x + (b.x - a.x) * t;
|
|
1406
|
-
const y = a.y + (b.y - a.y) * t;
|
|
1407
|
-
ctx.lineTo(x + amp * wobble(x, y, 2), y + amp * wobble(y, x, 3));
|
|
1355
|
+
function skinVertices(mesh, bindWorld, curWorld) {
|
|
1356
|
+
const skinMats = /* @__PURE__ */ new Map();
|
|
1357
|
+
const skinMat = (bone) => {
|
|
1358
|
+
let m = skinMats.get(bone);
|
|
1359
|
+
if (!m) {
|
|
1360
|
+
m = composeAffine(curWorld[bone] ?? IDENTITY, invertAffine(bindWorld[bone] ?? IDENTITY));
|
|
1361
|
+
skinMats.set(bone, m);
|
|
1408
1362
|
}
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
return out;
|
|
1420
|
-
}
|
|
1421
|
-
function fillMark(ctx, theme, color, style = theme.fillStyle) {
|
|
1422
|
-
if (style === "outline") return;
|
|
1423
|
-
ctx.save();
|
|
1424
|
-
ctx.shadowBlur = 0;
|
|
1425
|
-
ctx.shadowColor = "transparent";
|
|
1426
|
-
if (theme.shadow) {
|
|
1427
|
-
ctx.shadowColor = theme.shadow.color;
|
|
1428
|
-
ctx.shadowOffsetX = theme.shadow.offsetX;
|
|
1429
|
-
ctx.shadowOffsetY = theme.shadow.offsetY;
|
|
1430
|
-
ctx.shadowBlur = theme.shadow.blur;
|
|
1431
|
-
}
|
|
1432
|
-
if (style === "hatch") {
|
|
1433
|
-
ctx.clip();
|
|
1434
|
-
const span = ctx.canvas.width + ctx.canvas.height;
|
|
1435
|
-
ctx.strokeStyle = color;
|
|
1436
|
-
ctx.lineWidth = theme.strokes.thin;
|
|
1437
|
-
ctx.setLineDash([]);
|
|
1438
|
-
ctx.beginPath();
|
|
1439
|
-
for (let d = -span; d < span; d += 6) {
|
|
1440
|
-
ctx.moveTo(d, 0);
|
|
1441
|
-
ctx.lineTo(d + span, span);
|
|
1363
|
+
return m;
|
|
1364
|
+
};
|
|
1365
|
+
return mesh.vertices.map((v) => {
|
|
1366
|
+
if (v.weights.length === 0) return [v.x, v.y];
|
|
1367
|
+
let x = 0;
|
|
1368
|
+
let y = 0;
|
|
1369
|
+
for (const { bone, w } of v.weights) {
|
|
1370
|
+
const [bx, by] = applyAffine(skinMat(bone), v.x, v.y);
|
|
1371
|
+
x += w * bx;
|
|
1372
|
+
y += w * by;
|
|
1442
1373
|
}
|
|
1443
|
-
|
|
1444
|
-
}
|
|
1445
|
-
ctx.globalAlpha *= style === "tint" ? theme.fillOpacity : 1;
|
|
1446
|
-
ctx.fillStyle = color;
|
|
1447
|
-
ctx.fill();
|
|
1448
|
-
}
|
|
1449
|
-
ctx.restore();
|
|
1374
|
+
return [x, y];
|
|
1375
|
+
});
|
|
1450
1376
|
}
|
|
1451
|
-
function
|
|
1452
|
-
const
|
|
1453
|
-
const
|
|
1454
|
-
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
ctx.beginPath();
|
|
1464
|
-
ctx.moveTo(x2, y2);
|
|
1465
|
-
ctx.lineTo(left.x, left.y);
|
|
1466
|
-
ctx.lineTo(right.x, right.y);
|
|
1467
|
-
ctx.closePath();
|
|
1468
|
-
ctx.fill();
|
|
1469
|
-
break;
|
|
1470
|
-
}
|
|
1471
|
-
case "open":
|
|
1472
|
-
case "line": {
|
|
1473
|
-
ctx.beginPath();
|
|
1474
|
-
ctx.moveTo(left.x, left.y);
|
|
1475
|
-
ctx.lineTo(x2, y2);
|
|
1476
|
-
ctx.lineTo(right.x, right.y);
|
|
1477
|
-
if (theme.marker === "open") ctx.stroke();
|
|
1478
|
-
else {
|
|
1479
|
-
ctx.moveTo(x2 - size * 0.5 * Math.cos(angle + Math.PI / 2), y2 - size * 0.5 * Math.sin(angle + Math.PI / 2));
|
|
1480
|
-
ctx.lineTo(x2 + size * 0.5 * Math.cos(angle + Math.PI / 2), y2 + size * 0.5 * Math.sin(angle + Math.PI / 2));
|
|
1481
|
-
ctx.stroke();
|
|
1482
|
-
}
|
|
1483
|
-
break;
|
|
1484
|
-
}
|
|
1485
|
-
case "dot": {
|
|
1486
|
-
ctx.beginPath();
|
|
1487
|
-
ctx.arc(x2, y2, Math.max(2.5, width * 1.5), 0, Math.PI * 2);
|
|
1488
|
-
ctx.fill();
|
|
1489
|
-
break;
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
ctx.restore();
|
|
1377
|
+
function triangleAffine(src, dst) {
|
|
1378
|
+
const [s0, s1, s2] = src;
|
|
1379
|
+
const det = s0[0] * (s1[1] - s2[1]) + s1[0] * (s2[1] - s0[1]) + s2[0] * (s0[1] - s1[1]);
|
|
1380
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return null;
|
|
1381
|
+
const [d0, d1, d2] = dst;
|
|
1382
|
+
const a = (d0[0] * (s1[1] - s2[1]) + d1[0] * (s2[1] - s0[1]) + d2[0] * (s0[1] - s1[1])) / det;
|
|
1383
|
+
const c = (d0[0] * (s2[0] - s1[0]) + d1[0] * (s0[0] - s2[0]) + d2[0] * (s1[0] - s0[0])) / det;
|
|
1384
|
+
const e = (d0[0] * (s1[0] * s2[1] - s2[0] * s1[1]) + d1[0] * (s2[0] * s0[1] - s0[0] * s2[1]) + d2[0] * (s0[0] * s1[1] - s1[0] * s0[1])) / det;
|
|
1385
|
+
const b = (d0[1] * (s1[1] - s2[1]) + d1[1] * (s2[1] - s0[1]) + d2[1] * (s0[1] - s1[1])) / det;
|
|
1386
|
+
const d = (d0[1] * (s2[0] - s1[0]) + d1[1] * (s0[0] - s2[0]) + d2[1] * (s1[0] - s0[0])) / det;
|
|
1387
|
+
const f = (d0[1] * (s1[0] * s2[1] - s2[0] * s1[1]) + d1[1] * (s2[0] * s0[1] - s0[0] * s2[1]) + d2[1] * (s0[0] * s1[1] - s1[0] * s0[1])) / det;
|
|
1388
|
+
return [a, b, c, d, e, f];
|
|
1493
1389
|
}
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
const [state, setState] = useState({ theme: null, version: 0 });
|
|
1498
|
-
useLayoutEffect(() => {
|
|
1499
|
-
const scope = scopeRef.current;
|
|
1500
|
-
if (!scope) return;
|
|
1501
|
-
const theme = resolveCanvasTheme(scope);
|
|
1502
|
-
setState((prev) => ({ theme, version: prev.version + 1 }));
|
|
1503
|
-
}, [appliedTheme, scopeKey, scopeRef]);
|
|
1504
|
-
return state;
|
|
1390
|
+
var meshCache = /* @__PURE__ */ new Map();
|
|
1391
|
+
function isRigBundle(json) {
|
|
1392
|
+
return json.mesh !== void 0 || json.bones !== void 0;
|
|
1505
1393
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1394
|
+
function getRigBundle(url, onReady) {
|
|
1395
|
+
if (meshCache.has(url)) return meshCache.get(url) ?? void 0;
|
|
1396
|
+
meshCache.set(url, void 0);
|
|
1397
|
+
fetch(url).then((r) => {
|
|
1398
|
+
if (!r.ok) throw new Error(`rigbundle fetch failed: HTTP ${r.status}`);
|
|
1399
|
+
return r.json();
|
|
1400
|
+
}).then((json) => {
|
|
1401
|
+
const ok = isRigBundle(json) || Array.isArray(json.vertices);
|
|
1402
|
+
if (!ok) throw new Error("rigbundle payload is neither a SkinMesh nor a rigbundle");
|
|
1403
|
+
meshCache.set(url, json);
|
|
1404
|
+
onReady();
|
|
1405
|
+
}).catch(() => {
|
|
1406
|
+
meshCache.set(url, null);
|
|
1407
|
+
onReady();
|
|
1408
|
+
});
|
|
1409
|
+
return void 0;
|
|
1510
1410
|
}
|
|
1511
|
-
function
|
|
1512
|
-
|
|
1513
|
-
return typeof node.asset?.url === "string" && node.asset.url.length > 0 ? "ready" : "asset-unset";
|
|
1411
|
+
function skinMeshFailed(url) {
|
|
1412
|
+
return meshCache.get(url) === null;
|
|
1514
1413
|
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
const
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
w,
|
|
1529
|
-
h
|
|
1530
|
-
};
|
|
1414
|
+
function vertexBounds(points) {
|
|
1415
|
+
if (points.length === 0) return null;
|
|
1416
|
+
let minX = Infinity;
|
|
1417
|
+
let minY = Infinity;
|
|
1418
|
+
let maxX = -Infinity;
|
|
1419
|
+
let maxY = -Infinity;
|
|
1420
|
+
for (const [x, y] of points) {
|
|
1421
|
+
if (x < minX) minX = x;
|
|
1422
|
+
if (y < minY) minY = y;
|
|
1423
|
+
if (x > maxX) maxX = x;
|
|
1424
|
+
if (y > maxY) maxY = y;
|
|
1425
|
+
}
|
|
1426
|
+
return { minX, minY, maxX, maxY };
|
|
1531
1427
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
"
|
|
1541
|
-
"height",
|
|
1542
|
-
"strokeWidth",
|
|
1543
|
-
"strokeDashOffset",
|
|
1544
|
-
"blur"
|
|
1545
|
-
];
|
|
1546
|
-
function isAnimatedShape(node) {
|
|
1547
|
-
return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
|
|
1428
|
+
|
|
1429
|
+
// components/game/atoms/DrawSkinnedMesh.tsx
|
|
1430
|
+
var skinnedLog = createLogger("almadar:ui:draw-skinned-mesh");
|
|
1431
|
+
var loggedMissing = /* @__PURE__ */ new Set();
|
|
1432
|
+
function warnMissingOnce(reason, node) {
|
|
1433
|
+
const key = `${reason}:${node.asset.url}:${String(node.meshUrl)}`;
|
|
1434
|
+
if (loggedMissing.has(key)) return;
|
|
1435
|
+
loggedMissing.add(key);
|
|
1436
|
+
skinnedLog.warn("draw-skinned-mesh unresolvable \u2014 painting fallback", { reason, url: node.asset.url, meshUrl: node.meshUrl });
|
|
1548
1437
|
}
|
|
1549
|
-
var
|
|
1550
|
-
function
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
const trackValue = (key) => {
|
|
1558
|
-
const defined = frames.filter((f) => f[key] !== void 0);
|
|
1559
|
-
if (defined.length === 0) return void 0;
|
|
1560
|
-
let prev;
|
|
1561
|
-
let next;
|
|
1562
|
-
for (const f of defined) {
|
|
1563
|
-
if (f.at <= t) prev = f;
|
|
1564
|
-
else if (!next) next = f;
|
|
1565
|
-
}
|
|
1566
|
-
if (!prev) return defined[0][key];
|
|
1567
|
-
if (!next) return prev[key];
|
|
1568
|
-
const span = next.at - prev.at;
|
|
1569
|
-
const k = span > 0 ? (t - prev.at) / span : 1;
|
|
1570
|
-
const a = prev[key];
|
|
1571
|
-
const b = next[key];
|
|
1572
|
-
if (typeof a === "number" && typeof b === "number") return lerp(a, b, k);
|
|
1573
|
-
return a;
|
|
1574
|
-
};
|
|
1575
|
-
for (const key of NUMERIC_TRACKS) {
|
|
1576
|
-
const v = trackValue(key);
|
|
1577
|
-
if (v !== void 0) out[key] = v;
|
|
1578
|
-
}
|
|
1579
|
-
const fill = trackValue("fill");
|
|
1580
|
-
if (fill !== void 0) out.fill = fill;
|
|
1581
|
-
const stroke = trackValue("stroke");
|
|
1582
|
-
if (stroke !== void 0) out.stroke = stroke;
|
|
1583
|
-
const shadowFrames = frames.filter((f) => f.shadow !== void 0);
|
|
1584
|
-
if (shadowFrames.length > 0) {
|
|
1585
|
-
const sh = trackValue("shadow");
|
|
1586
|
-
if (sh !== void 0) {
|
|
1587
|
-
let prevSh;
|
|
1588
|
-
let nextSh;
|
|
1589
|
-
for (const f of shadowFrames) {
|
|
1590
|
-
if (f.at <= t) prevSh = f;
|
|
1591
|
-
else if (!nextSh) nextSh = f;
|
|
1592
|
-
}
|
|
1593
|
-
if (prevSh?.shadow && nextSh?.shadow) {
|
|
1594
|
-
const span = nextSh.at - prevSh.at;
|
|
1595
|
-
const k = span > 0 ? (t - prevSh.at) / span : 1;
|
|
1596
|
-
out.shadow = { color: prevSh.shadow.color, blur: lerp(prevSh.shadow.blur, nextSh.shadow.blur, k) };
|
|
1597
|
-
} else {
|
|
1598
|
-
out.shadow = sh;
|
|
1599
|
-
}
|
|
1438
|
+
var WEIGHT_PALETTE = Array.from({ length: 10 }, (_, i) => `hsl(${i * 36 % 360}, 70%, 55%)`);
|
|
1439
|
+
function dominantBone(v) {
|
|
1440
|
+
let best = -1;
|
|
1441
|
+
let bestW = 0;
|
|
1442
|
+
for (const { bone, w } of v.weights) {
|
|
1443
|
+
if (w > bestW) {
|
|
1444
|
+
bestW = w;
|
|
1445
|
+
best = bone;
|
|
1600
1446
|
}
|
|
1601
1447
|
}
|
|
1602
|
-
return
|
|
1448
|
+
return best;
|
|
1603
1449
|
}
|
|
1604
|
-
function
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
return
|
|
1608
|
-
kind: "linear",
|
|
1609
|
-
x1: ox + g.from.x * scale,
|
|
1610
|
-
y1: oy + g.from.y * scale,
|
|
1611
|
-
x2: ox + g.to.x * scale,
|
|
1612
|
-
y2: oy + g.to.y * scale,
|
|
1613
|
-
stops: g.stops
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
if (g.kind === "conic") {
|
|
1617
|
-
if (!g.center) return void 0;
|
|
1618
|
-
return {
|
|
1619
|
-
kind: "conic",
|
|
1620
|
-
cx: ox + g.center.x * scale,
|
|
1621
|
-
cy: oy + g.center.y * scale,
|
|
1622
|
-
angle: g.angle ?? 0,
|
|
1623
|
-
stops: g.stops
|
|
1624
|
-
};
|
|
1450
|
+
function resolveSkinPose(node, clip) {
|
|
1451
|
+
const effectiveClip = clip ?? node.clip;
|
|
1452
|
+
if (effectiveClip && node.frame !== void 0 && effectiveClip.frames.length > 0) {
|
|
1453
|
+
return effectiveClip.frames[node.frame % effectiveClip.frames.length];
|
|
1625
1454
|
}
|
|
1626
|
-
|
|
1627
|
-
return {
|
|
1628
|
-
kind: "radial",
|
|
1629
|
-
cx: ox + g.center.x * scale,
|
|
1630
|
-
cy: oy + g.center.y * scale,
|
|
1631
|
-
r: g.radius * scale,
|
|
1632
|
-
stops: g.stops
|
|
1633
|
-
};
|
|
1455
|
+
return node.pose ?? {};
|
|
1634
1456
|
}
|
|
1635
|
-
function
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1457
|
+
function resolveRig(node, dctx) {
|
|
1458
|
+
const fetched = node.meshUrl ? getRigBundle(node.meshUrl, dctx.invalidate) : void 0;
|
|
1459
|
+
if (node.meshUrl && fetched === void 0) return void 0;
|
|
1460
|
+
const rb = fetched && isRigBundle(fetched) ? fetched : void 0;
|
|
1461
|
+
const mesh = node.mesh ?? rb?.mesh ?? (!rb ? fetched : void 0);
|
|
1462
|
+
if (!mesh) return void 0;
|
|
1463
|
+
const bones = node.bones.length > 0 ? node.bones : rb?.bones ?? [];
|
|
1464
|
+
if (bones.length === 0) return void 0;
|
|
1465
|
+
const clip = node.clip ?? (node.clipName ? rb?.clips?.[node.clipName] : void 0);
|
|
1466
|
+
return { mesh, bones, clip };
|
|
1641
1467
|
}
|
|
1642
|
-
var
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
const
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
const strokePx = (node.strokeWidth ?? 1) / groupScale;
|
|
1651
|
-
if (node.blendMode) painter.setBlend(node.blendMode);
|
|
1652
|
-
if (node.strokeDash && node.strokeDash.length > 0) {
|
|
1653
|
-
painter.setLineDash(
|
|
1654
|
-
node.strokeDash.map((v) => v / groupScale),
|
|
1655
|
-
(node.strokeDashOffset ?? 0) / groupScale
|
|
1656
|
-
);
|
|
1657
|
-
}
|
|
1658
|
-
if (node.blur !== void 0 && node.blur > 0) painter.setBlur(node.blur / groupScale);
|
|
1659
|
-
if (node.rotate) {
|
|
1660
|
-
const pivot = node.pivot ?? { x: 0.5, y: 0.5 };
|
|
1661
|
-
const px = origin.x + pivot.x * tileWidth;
|
|
1662
|
-
const py = origin.y + pivot.y * tileWidth;
|
|
1663
|
-
painter.translate(px, py);
|
|
1664
|
-
painter.rotate(node.rotate);
|
|
1665
|
-
painter.translate(-px, -py);
|
|
1666
|
-
}
|
|
1667
|
-
if (node.shadow) painter.setShadow({ color: node.shadow.color, blur: node.shadow.blur * tileWidth * groupScale });
|
|
1668
|
-
const fill = node.fill === "none" ? void 0 : node.fill;
|
|
1669
|
-
const stroke = node.stroke === "none" ? void 0 : node.stroke;
|
|
1670
|
-
const pxFill = node.gradient ? gradientStyle(node.gradient, origin.x, origin.y, tileWidth) ?? fill : fill;
|
|
1671
|
-
const pxStroke = node.strokeGradient ? gradientStyle(node.strokeGradient, origin.x, origin.y, tileWidth) ?? stroke : stroke;
|
|
1672
|
-
const patFill = node.fillPattern ? patternStyle(node.fillPattern, groupScale) : void 0;
|
|
1673
|
-
switch (node.shape) {
|
|
1674
|
-
case "cell": {
|
|
1675
|
-
const pts = dctx.projector.cellPath(node.position);
|
|
1676
|
-
if (pxFill) painter.fillPoly(pts, pxFill);
|
|
1677
|
-
if (patFill) painter.fillPoly(pts, patFill);
|
|
1678
|
-
if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
|
|
1679
|
-
break;
|
|
1680
|
-
}
|
|
1681
|
-
case "rect": {
|
|
1682
|
-
const p = dctx.projector.anchorPoint(node.position, node.anchor ?? "top-left");
|
|
1683
|
-
const tw = dctx.projector.tileWidth;
|
|
1684
|
-
const x = p.x + (node.offsetX ?? 0) * tw;
|
|
1685
|
-
const y = p.y + (node.offsetY ?? 0) * tw;
|
|
1686
|
-
const w = (node.width ?? 0) * tw;
|
|
1687
|
-
const h = (node.height ?? 0) * tw;
|
|
1688
|
-
if (pxFill) painter.fillRect(x, y, w, h, pxFill);
|
|
1689
|
-
if (patFill) painter.fillRect(x, y, w, h, patFill);
|
|
1690
|
-
if (pxStroke) painter.strokeRect(x, y, w, h, pxStroke, strokePx);
|
|
1691
|
-
break;
|
|
1692
|
-
}
|
|
1693
|
-
case "ellipse": {
|
|
1694
|
-
const p = dctx.projector.anchorPoint(node.position, node.anchor ?? "ground");
|
|
1695
|
-
const tw = dctx.projector.tileWidth;
|
|
1696
|
-
const cx = p.x + (node.offsetX ?? 0) * tw;
|
|
1697
|
-
const cy = p.y + (node.offsetY ?? 0) * tw;
|
|
1698
|
-
const rx = (node.radiusX ?? 0) * tw;
|
|
1699
|
-
const ry = (node.radiusY ?? node.radiusX ?? 0) * tw;
|
|
1700
|
-
if (pxFill) painter.fillEllipse(cx, cy, rx, ry, pxFill);
|
|
1701
|
-
if (patFill) painter.fillEllipse(cx, cy, rx, ry, patFill);
|
|
1702
|
-
if (pxStroke) painter.strokeEllipse(cx, cy, rx, ry, pxStroke, strokePx);
|
|
1703
|
-
break;
|
|
1704
|
-
}
|
|
1705
|
-
case "poly": {
|
|
1706
|
-
const pts = (node.points ?? []).map((pt) => ({
|
|
1707
|
-
x: origin.x + ((node.offsetX ?? 0) + pt.x) * tileWidth,
|
|
1708
|
-
y: origin.y + ((node.offsetY ?? 0) + pt.y) * tileWidth
|
|
1709
|
-
}));
|
|
1710
|
-
if (pxFill) painter.fillPoly(pts, pxFill);
|
|
1711
|
-
if (patFill) painter.fillPoly(pts, patFill);
|
|
1712
|
-
if (pxStroke) painter.strokePoly(pts, pxStroke, strokePx, true);
|
|
1468
|
+
var pixelUVByMesh = /* @__PURE__ */ new WeakMap();
|
|
1469
|
+
function meshHasPixelUVs(mesh) {
|
|
1470
|
+
const cached = pixelUVByMesh.get(mesh);
|
|
1471
|
+
if (cached !== void 0) return cached;
|
|
1472
|
+
let pixel = false;
|
|
1473
|
+
for (const v of mesh.vertices) {
|
|
1474
|
+
if (v.u < 0 || v.u > 1 || v.v < 0 || v.v > 1) {
|
|
1475
|
+
pixel = true;
|
|
1713
1476
|
break;
|
|
1714
1477
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1478
|
+
}
|
|
1479
|
+
pixelUVByMesh.set(mesh, pixel);
|
|
1480
|
+
return pixel;
|
|
1481
|
+
}
|
|
1482
|
+
function paintMeshPass(painter, node, bones, mesh, tex, dctx, pose, opacity) {
|
|
1483
|
+
const bindWorld = computeWorldMatrices(bones, {});
|
|
1484
|
+
const curWorld = computeWorldMatrices(bones, pose);
|
|
1485
|
+
const skinned = skinVertices(mesh, bindWorld, curWorld);
|
|
1486
|
+
const origin = dctx.projector.project(node.position);
|
|
1487
|
+
const tw = dctx.projector.tileWidth;
|
|
1488
|
+
painter.save();
|
|
1489
|
+
if (opacity !== 1) painter.setAlpha(opacity);
|
|
1490
|
+
for (const [i0, i1, i2] of mesh.triangles) {
|
|
1491
|
+
const v0 = mesh.vertices[i0];
|
|
1492
|
+
const v1 = mesh.vertices[i1];
|
|
1493
|
+
const v2 = mesh.vertices[i2];
|
|
1494
|
+
const p0 = skinned[i0];
|
|
1495
|
+
const p1 = skinned[i1];
|
|
1496
|
+
const p2 = skinned[i2];
|
|
1497
|
+
if (!v0 || !v1 || !v2 || !p0 || !p1 || !p2) continue;
|
|
1498
|
+
const dst = [
|
|
1499
|
+
[origin.x + p0[0] * tw, origin.y + p0[1] * tw],
|
|
1500
|
+
[origin.x + p1[0] * tw, origin.y + p1[1] * tw],
|
|
1501
|
+
[origin.x + p2[0] * tw, origin.y + p2[1] * tw]
|
|
1502
|
+
];
|
|
1503
|
+
if (node.weightsOverlay || !tex) {
|
|
1504
|
+
if (!node.weightsOverlay) continue;
|
|
1505
|
+
const bone = dominantBone(v0);
|
|
1506
|
+
painter.fillPoly(
|
|
1507
|
+
dst.map(([x, y]) => ({ x, y })),
|
|
1508
|
+
WEIGHT_PALETTE[(bone % WEIGHT_PALETTE.length + WEIGHT_PALETTE.length) % WEIGHT_PALETTE.length]
|
|
1509
|
+
);
|
|
1510
|
+
continue;
|
|
1733
1511
|
}
|
|
1512
|
+
const pixelUV = meshHasPixelUVs(mesh);
|
|
1513
|
+
const uv = pixelUV ? [[v0.u, v0.v], [v1.u, v1.v], [v2.u, v2.v]] : [
|
|
1514
|
+
[v0.u * tex.width, v0.v * tex.height],
|
|
1515
|
+
[v1.u * tex.width, v1.v * tex.height],
|
|
1516
|
+
[v2.u * tex.width, v2.v * tex.height]
|
|
1517
|
+
];
|
|
1518
|
+
const affine = triangleAffine(uv, dst);
|
|
1519
|
+
if (!affine) continue;
|
|
1520
|
+
painter.save();
|
|
1521
|
+
painter.clipPath(`M ${dst[0][0]} ${dst[0][1]} L ${dst[1][0]} ${dst[1][1]} L ${dst[2][0]} ${dst[2][1]} Z`);
|
|
1522
|
+
painter.blitTransformed(tex, { x: 0, y: 0, w: tex.width, h: tex.height }, affine);
|
|
1523
|
+
painter.restore();
|
|
1734
1524
|
}
|
|
1735
1525
|
painter.restore();
|
|
1526
|
+
}
|
|
1527
|
+
var paintSkinnedMesh = (painter, node, dctx) => {
|
|
1528
|
+
if (!isValidScenePos(node.position)) return;
|
|
1529
|
+
const rig = resolveRig(node, dctx);
|
|
1530
|
+
if (!rig) {
|
|
1531
|
+
if (node.meshUrl && skinMeshFailed(node.meshUrl)) warnMissingOnce("mesh-failed", node);
|
|
1532
|
+
else if (!node.mesh && !node.meshUrl) warnMissingOnce("mesh-missing", node);
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
const tex = node.weightsOverlay ? null : painter.resolveTexture(node.asset.url);
|
|
1536
|
+
if (!node.weightsOverlay && !tex) {
|
|
1537
|
+
if (getImageStatus(node.asset.url) === "failed") warnMissingOnce("texture-failed", node);
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
if (node.ghost && rig.clip && rig.clip.frames.length > 0) {
|
|
1541
|
+
paintMeshPass(painter, node, rig.bones, rig.mesh, tex, dctx, rig.clip.frames[node.ghost.frame % rig.clip.frames.length], node.ghost.opacity);
|
|
1542
|
+
}
|
|
1543
|
+
paintMeshPass(painter, node, rig.bones, rig.mesh, tex, dctx, resolveSkinPose(node, rig.clip), node.opacity ?? 1);
|
|
1736
1544
|
};
|
|
1737
|
-
function
|
|
1545
|
+
function DrawSkinnedMesh(props) {
|
|
1738
1546
|
const register = useContext(DrawableRegistryContext);
|
|
1739
|
-
if (register) {
|
|
1740
|
-
const { opacity, ...rest } = props;
|
|
1741
|
-
const node = {
|
|
1742
|
-
...rest,
|
|
1743
|
-
type: "draw-shape",
|
|
1744
|
-
...opacity !== void 0 && opacity > 0 ? { opacity } : {}
|
|
1745
|
-
};
|
|
1746
|
-
register(node);
|
|
1747
|
-
}
|
|
1547
|
+
if (register) register({ ...props, type: "draw-skinned-mesh" });
|
|
1748
1548
|
return null;
|
|
1749
1549
|
}
|
|
1750
|
-
|
|
1550
|
+
var MESH_SEGMENTS_DEFAULT = 24;
|
|
1551
|
+
function polyhedronBounds(vertices) {
|
|
1552
|
+
if (!vertices || vertices.length < 3) return null;
|
|
1553
|
+
const min = [Infinity, Infinity, Infinity];
|
|
1554
|
+
const max = [-Infinity, -Infinity, -Infinity];
|
|
1555
|
+
for (const v of vertices) {
|
|
1556
|
+
if (v.length < 3 || !v.every((c) => Number.isFinite(c))) return null;
|
|
1557
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
1558
|
+
min[axis] = Math.min(min[axis], v[axis]);
|
|
1559
|
+
max[axis] = Math.max(max[axis], v[axis]);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return { min, max };
|
|
1563
|
+
}
|
|
1564
|
+
function clampSegments(segments) {
|
|
1565
|
+
const s = segments ?? MESH_SEGMENTS_DEFAULT;
|
|
1566
|
+
return Math.min(64, Math.max(3, Math.round(s)));
|
|
1567
|
+
}
|
|
1568
|
+
function isAnimatedMesh(node) {
|
|
1751
1569
|
return Boolean(node.animation && node.animation.durationMs > 0 && node.animation.keyframes.length > 0);
|
|
1752
1570
|
}
|
|
1753
|
-
|
|
1571
|
+
var lerp2 = (a, b, k) => a + (b - a) * k;
|
|
1572
|
+
var NUMERIC_TRACKS2 = [
|
|
1573
|
+
"offsetX",
|
|
1574
|
+
"offsetY",
|
|
1575
|
+
"offsetZ",
|
|
1576
|
+
"rotateX",
|
|
1577
|
+
"rotateY",
|
|
1578
|
+
"rotateZ",
|
|
1579
|
+
"scale",
|
|
1580
|
+
"opacity",
|
|
1581
|
+
"emissiveIntensity"
|
|
1582
|
+
];
|
|
1583
|
+
function applyMeshAnimation(node, timeMs) {
|
|
1584
|
+
const anim = node.animation;
|
|
1585
|
+
if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return null;
|
|
1586
|
+
const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
|
|
1587
|
+
const cycle = timeMs / anim.durationMs;
|
|
1588
|
+
const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
|
|
1589
|
+
const trackValue = (key) => {
|
|
1590
|
+
const defined = frames.filter((f) => f[key] !== void 0);
|
|
1591
|
+
if (defined.length === 0) return void 0;
|
|
1592
|
+
let prev;
|
|
1593
|
+
let next;
|
|
1594
|
+
for (const f of defined) {
|
|
1595
|
+
if (f.at <= t) prev = f;
|
|
1596
|
+
else if (!next) next = f;
|
|
1597
|
+
}
|
|
1598
|
+
if (!prev) return defined[0][key];
|
|
1599
|
+
if (!next) return prev[key];
|
|
1600
|
+
const span = next.at - prev.at;
|
|
1601
|
+
const k = span > 0 ? (t - prev.at) / span : 1;
|
|
1602
|
+
const a = prev[key];
|
|
1603
|
+
const b = next[key];
|
|
1604
|
+
if (typeof a === "number" && typeof b === "number") return lerp2(a, b, k);
|
|
1605
|
+
return a;
|
|
1606
|
+
};
|
|
1607
|
+
const num = {};
|
|
1608
|
+
for (const key of NUMERIC_TRACKS2) {
|
|
1609
|
+
const v = trackValue(key);
|
|
1610
|
+
if (v !== void 0) num[key] = v;
|
|
1611
|
+
}
|
|
1612
|
+
return {
|
|
1613
|
+
offset: [num.offsetX ?? 0, num.offsetY ?? 0, num.offsetZ ?? 0],
|
|
1614
|
+
rotate: [num.rotateX ?? 0, num.rotateY ?? 0, num.rotateZ ?? 0],
|
|
1615
|
+
scale: num.scale ?? 1,
|
|
1616
|
+
opacity: num.opacity,
|
|
1617
|
+
emissiveIntensity: num.emissiveIntensity,
|
|
1618
|
+
color: trackValue("color"),
|
|
1619
|
+
emissive: trackValue("emissive")
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
function DrawMesh(props) {
|
|
1754
1623
|
const register = useContext(DrawableRegistryContext);
|
|
1755
|
-
if (register) register({ ...props, type: "draw-
|
|
1624
|
+
if (register) register({ ...props, type: "draw-mesh" });
|
|
1756
1625
|
return null;
|
|
1757
1626
|
}
|
|
1758
1627
|
|
|
1759
|
-
//
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1628
|
+
// components/core/atoms/fx.ts
|
|
1629
|
+
function resolveFxView(item, presets) {
|
|
1630
|
+
const row = presets?.find((p) => p.type === item.type);
|
|
1631
|
+
if (!row) return item;
|
|
1632
|
+
return {
|
|
1633
|
+
...item,
|
|
1634
|
+
space: item.space ?? row.space,
|
|
1635
|
+
effect: item.effect ?? row.effect,
|
|
1636
|
+
color: item.color ?? row.color,
|
|
1637
|
+
size: item.size ?? row.size,
|
|
1638
|
+
vx: item.vx ?? row.vx,
|
|
1639
|
+
vy: item.vy ?? row.vy,
|
|
1640
|
+
vz: item.vz ?? row.vz,
|
|
1641
|
+
particleCount: item.particleCount ?? row.particleCount,
|
|
1642
|
+
animation: item.animation ?? row.animation,
|
|
1643
|
+
shape: row.shape,
|
|
1644
|
+
count: row.count,
|
|
1645
|
+
glow: row.glow,
|
|
1646
|
+
gravity: row.gravity,
|
|
1647
|
+
color2: row.color2
|
|
1778
1648
|
};
|
|
1779
|
-
img.src = url;
|
|
1780
|
-
return entry;
|
|
1781
|
-
}
|
|
1782
|
-
function getOrLoadImage(url, onReady) {
|
|
1783
|
-
if (!url || typeof window === "undefined") return null;
|
|
1784
|
-
const existing = cache.get(url);
|
|
1785
|
-
if (existing) {
|
|
1786
|
-
if (existing.status === "loaded") return existing.img;
|
|
1787
|
-
if (existing.status === "pending" && onReady) existing.onReady = onReady;
|
|
1788
|
-
if (existing.status === "failed" && Date.now() - (existing.failedAt ?? 0) >= RETRY_AFTER_MS) {
|
|
1789
|
-
existing.failedAt = Date.now();
|
|
1790
|
-
startLoad(url, onReady, existing);
|
|
1791
|
-
}
|
|
1792
|
-
return null;
|
|
1793
|
-
}
|
|
1794
|
-
const entry = startLoad(url, onReady);
|
|
1795
|
-
cache.set(url, entry);
|
|
1796
|
-
updateAssetStatus(url, "pending");
|
|
1797
|
-
return null;
|
|
1798
1649
|
}
|
|
1799
|
-
function
|
|
1800
|
-
|
|
1650
|
+
function fxLifecycle(item, epochNowMs, tickMs) {
|
|
1651
|
+
const maxTtl = Math.max(item.maxTtl ?? item.ttl, item.ttl, 1);
|
|
1652
|
+
const lifeMs = maxTtl * tickMs;
|
|
1653
|
+
const ageMs = item.bornAt !== void 0 && epochNowMs > 0 ? Math.max(0, epochNowMs - item.bornAt) : (1 - item.ttl / maxTtl) * lifeMs;
|
|
1654
|
+
const progress = Math.min(1, Math.max(0, ageMs / lifeMs));
|
|
1655
|
+
return { lifeMs, ageMs, progress, fade: 1 - progress };
|
|
1801
1656
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
a[1] * b[4] + a[3] * b[5] + a[5]
|
|
1813
|
-
];
|
|
1657
|
+
function fxHash01(seed, salt) {
|
|
1658
|
+
let h = 2166136261 ^ salt;
|
|
1659
|
+
for (let i = 0; i < seed.length; i++) {
|
|
1660
|
+
h ^= seed.charCodeAt(i);
|
|
1661
|
+
h = Math.imul(h, 16777619);
|
|
1662
|
+
}
|
|
1663
|
+
h ^= h >>> 13;
|
|
1664
|
+
h = Math.imul(h, 1274126177);
|
|
1665
|
+
h ^= h >>> 16;
|
|
1666
|
+
return (h >>> 0) / 4294967296;
|
|
1814
1667
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1668
|
+
var CONFETTI_COLORS = [
|
|
1669
|
+
"var(--color-primary)",
|
|
1670
|
+
"var(--color-success)",
|
|
1671
|
+
"var(--color-warning)",
|
|
1672
|
+
"var(--color-error)",
|
|
1673
|
+
"gold",
|
|
1674
|
+
"dodgerblue"
|
|
1675
|
+
];
|
|
1676
|
+
function createConfettiParticles(count, seed) {
|
|
1677
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
1678
|
+
id: i,
|
|
1679
|
+
color: CONFETTI_COLORS[Math.floor(fxHash01(seed, i * 7 + 1) * CONFETTI_COLORS.length)],
|
|
1680
|
+
left: 30 + fxHash01(seed, i * 7 + 2) * 40,
|
|
1681
|
+
delay: fxHash01(seed, i * 7 + 3) * 300,
|
|
1682
|
+
angle: fxHash01(seed, i * 7 + 4) * 360,
|
|
1683
|
+
distance: 40 + fxHash01(seed, i * 7 + 5) * 80,
|
|
1684
|
+
rotation: fxHash01(seed, i * 7 + 6) * 720 - 360,
|
|
1685
|
+
size: 4 + fxHash01(seed, i * 7 + 7) * 6
|
|
1686
|
+
}));
|
|
1827
1687
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1688
|
+
var CONFETTI_BURST_KEYFRAMES = `
|
|
1689
|
+
@keyframes confetti-burst {
|
|
1690
|
+
0% {
|
|
1691
|
+
opacity: 1;
|
|
1692
|
+
transform: translate(0, 0) rotate(0deg) scale(1);
|
|
1693
|
+
}
|
|
1694
|
+
70% {
|
|
1695
|
+
opacity: 1;
|
|
1696
|
+
}
|
|
1697
|
+
100% {
|
|
1698
|
+
opacity: 0;
|
|
1699
|
+
transform: translate(var(--confetti-tx), var(--confetti-ty)) rotate(var(--confetti-rotate)) scale(0.5);
|
|
1700
|
+
}
|
|
1830
1701
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1702
|
+
`;
|
|
1703
|
+
|
|
1704
|
+
// components/game/molecules/DrawFxLayer.tsx
|
|
1705
|
+
var DEFAULT_TICK_MS = 500;
|
|
1706
|
+
var DEFAULT_TEXT_COLOR = "#ffe066";
|
|
1707
|
+
var DEFAULT_SPARK_COLOR = "#ffffff";
|
|
1708
|
+
var easeOut = (t) => 1 - (1 - t) * (1 - t);
|
|
1709
|
+
function fxPosition(view, dim, ageSec) {
|
|
1710
|
+
const g = view.gravity ?? 0;
|
|
1711
|
+
const fall = 0.5 * g * ageSec * ageSec;
|
|
1712
|
+
if (dim === "2d") {
|
|
1713
|
+
const base2 = view.position ?? { x: view.x, y: view.z ?? view.y ?? 0 };
|
|
1714
|
+
if (!Number.isFinite(base2.x) || !Number.isFinite(base2.y)) return void 0;
|
|
1715
|
+
return {
|
|
1716
|
+
x: base2.x + (view.vx ?? 0) * ageSec,
|
|
1717
|
+
y: base2.y + (view.vz ?? 0) * ageSec + fall
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
const base = view.position ?? { x: view.x, y: view.z ?? 0, z: view.y };
|
|
1721
|
+
if (!Number.isFinite(base.x) || !Number.isFinite(base.y)) return void 0;
|
|
1722
|
+
return {
|
|
1723
|
+
x: base.x + (view.vx ?? 0) * ageSec,
|
|
1724
|
+
y: base.y + (view.vz ?? 0) * ageSec,
|
|
1725
|
+
...base.z !== void 0 || view.vy !== void 0 || g !== 0 ? { z: (base.z ?? 0) + (view.vy ?? 0) * ageSec - fall } : {}
|
|
1726
|
+
};
|
|
1837
1727
|
}
|
|
1838
|
-
function
|
|
1839
|
-
const
|
|
1840
|
-
|
|
1841
|
-
const
|
|
1842
|
-
const
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
world[i] = m;
|
|
1855
|
-
return m;
|
|
1728
|
+
function sparkShape(view, pos, i, fade, progress) {
|
|
1729
|
+
const size = view.size ?? 0.5;
|
|
1730
|
+
const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
|
|
1731
|
+
const dist = size * (0.4 + 0.6 * fxHash01(view.id, i * 3 + 1)) * easeOut(progress);
|
|
1732
|
+
const r = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
|
|
1733
|
+
const color = view.color ?? DEFAULT_SPARK_COLOR;
|
|
1734
|
+
return {
|
|
1735
|
+
type: "draw-shape",
|
|
1736
|
+
shape: "ellipse",
|
|
1737
|
+
position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
|
|
1738
|
+
anchor: "center",
|
|
1739
|
+
radiusX: r,
|
|
1740
|
+
fill: color,
|
|
1741
|
+
blendMode: "lighter",
|
|
1742
|
+
opacity: fade,
|
|
1743
|
+
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
1856
1744
|
};
|
|
1857
|
-
return bones.map((_, i) => resolve(i));
|
|
1858
1745
|
}
|
|
1859
|
-
function
|
|
1860
|
-
const
|
|
1861
|
-
const
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1746
|
+
function proceduralShapes(view, pos, fade, progress) {
|
|
1747
|
+
const size = view.size ?? 0.5;
|
|
1748
|
+
const color = view.color ?? DEFAULT_SPARK_COLOR;
|
|
1749
|
+
switch (view.shape ?? "spark") {
|
|
1750
|
+
case "ring": {
|
|
1751
|
+
return [
|
|
1752
|
+
{
|
|
1753
|
+
type: "draw-shape",
|
|
1754
|
+
shape: "ellipse",
|
|
1755
|
+
position: pos,
|
|
1756
|
+
anchor: "center",
|
|
1757
|
+
radiusX: size * easeOut(progress),
|
|
1758
|
+
stroke: view.color2 ?? color,
|
|
1759
|
+
strokeWidth: 2,
|
|
1760
|
+
blendMode: "lighter",
|
|
1761
|
+
opacity: fade,
|
|
1762
|
+
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
1763
|
+
}
|
|
1764
|
+
];
|
|
1866
1765
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1766
|
+
case "puff": {
|
|
1767
|
+
const count = view.count ?? 3;
|
|
1768
|
+
return Array.from({ length: count }, (_, i) => {
|
|
1769
|
+
const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
|
|
1770
|
+
const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut(progress);
|
|
1771
|
+
return {
|
|
1772
|
+
type: "draw-shape",
|
|
1773
|
+
shape: "ellipse",
|
|
1774
|
+
position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
|
|
1775
|
+
anchor: "center",
|
|
1776
|
+
radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
|
|
1777
|
+
fill: i === 0 && view.color2 ? view.color2 : color,
|
|
1778
|
+
opacity: fade * 0.6,
|
|
1779
|
+
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
1780
|
+
};
|
|
1781
|
+
});
|
|
1877
1782
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1783
|
+
case "streak": {
|
|
1784
|
+
const count = view.count ?? 5;
|
|
1785
|
+
return Array.from({ length: count }, (_, i) => {
|
|
1786
|
+
const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
|
|
1787
|
+
const inner = size * (0.2 + 0.8 * easeOut(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
|
|
1788
|
+
const len = size * 0.35;
|
|
1789
|
+
return {
|
|
1790
|
+
type: "draw-shape",
|
|
1791
|
+
shape: "ellipse",
|
|
1792
|
+
position: pos,
|
|
1793
|
+
anchor: "center",
|
|
1794
|
+
offsetX: inner + len / 2,
|
|
1795
|
+
offsetY: 0,
|
|
1796
|
+
radiusX: len / 2,
|
|
1797
|
+
radiusY: size * 0.04,
|
|
1798
|
+
rotate: angle,
|
|
1799
|
+
fill: color,
|
|
1800
|
+
blendMode: "lighter",
|
|
1801
|
+
opacity: fade,
|
|
1802
|
+
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
1803
|
+
};
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
default: {
|
|
1807
|
+
const count = view.count ?? 6;
|
|
1808
|
+
return Array.from({ length: count }, (_, i) => sparkShape(view, pos, i, fade, progress));
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
function expandFxItem(view, node, epochNowMs, dim, projector, fontFamily) {
|
|
1813
|
+
if (view.space === "screen") return [];
|
|
1814
|
+
const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
|
|
1815
|
+
const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
|
|
1816
|
+
if (progress >= 1) return [];
|
|
1817
|
+
const pos = fxPosition(view, dim, ageMs / 1e3);
|
|
1818
|
+
if (!pos) return [];
|
|
1819
|
+
const out = [];
|
|
1820
|
+
const artItems = node.art?.[view.type]?.["idle"];
|
|
1821
|
+
const sprite = node.sprites?.[view.type];
|
|
1822
|
+
if (Array.isArray(artItems)) {
|
|
1823
|
+
out.push({
|
|
1824
|
+
type: "draw-group",
|
|
1825
|
+
position: pos,
|
|
1826
|
+
opacity: fade,
|
|
1827
|
+
...view.size !== void 0 ? { scale: view.size } : {},
|
|
1828
|
+
items: artItems
|
|
1829
|
+
});
|
|
1830
|
+
} else if (sprite?.url) {
|
|
1831
|
+
const spriteSize = view.size ?? 0.6;
|
|
1832
|
+
out.push({
|
|
1833
|
+
type: "draw-sprite",
|
|
1834
|
+
position: pos,
|
|
1835
|
+
asset: sprite,
|
|
1836
|
+
anchor: "center",
|
|
1837
|
+
width: spriteSize,
|
|
1838
|
+
height: spriteSize,
|
|
1839
|
+
opacity: fade,
|
|
1840
|
+
animation: view.animation ?? sprite.animations?.[0],
|
|
1841
|
+
// One-shot fx rows (burst) play from the item's birth, not wall-time.
|
|
1842
|
+
clockMs: ageMs
|
|
1843
|
+
});
|
|
1844
|
+
} else {
|
|
1845
|
+
out.push(...proceduralShapes(view, pos, fade, progress));
|
|
1846
|
+
}
|
|
1847
|
+
if (view.message) {
|
|
1848
|
+
const pxSize = projector && view.size !== void 0 ? Math.max(10, Math.round(view.size * projector.tileWidth)) : void 0;
|
|
1849
|
+
const family = fontFamily ?? "system-ui, sans-serif";
|
|
1850
|
+
const text = {
|
|
1851
|
+
type: "draw-text",
|
|
1852
|
+
text: view.message,
|
|
1853
|
+
position: pos,
|
|
1854
|
+
offsetY: -(0.15 + 0.55 * progress),
|
|
1855
|
+
color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
|
|
1856
|
+
opacity: fade,
|
|
1857
|
+
font: `bold ${pxSize ?? 14}px ${family}`
|
|
1858
|
+
};
|
|
1859
|
+
out.push(text);
|
|
1860
|
+
}
|
|
1861
|
+
return out;
|
|
1862
|
+
}
|
|
1863
|
+
function expandFxLayer(node, epochNowMs, dim, projector, fontFamily) {
|
|
1864
|
+
if (!Array.isArray(node.items)) return [];
|
|
1865
|
+
const out = [];
|
|
1866
|
+
for (const item of node.items) {
|
|
1867
|
+
if (!item || typeof item.id !== "string") continue;
|
|
1868
|
+
out.push(...expandFxItem(resolveFxView(item, node.presets), node, epochNowMs, dim, projector, fontFamily));
|
|
1869
|
+
}
|
|
1870
|
+
return out;
|
|
1880
1871
|
}
|
|
1881
|
-
function
|
|
1882
|
-
|
|
1883
|
-
const det = s0[0] * (s1[1] - s2[1]) + s1[0] * (s2[1] - s0[1]) + s2[0] * (s0[1] - s1[1]);
|
|
1884
|
-
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return null;
|
|
1885
|
-
const [d0, d1, d2] = dst;
|
|
1886
|
-
const a = (d0[0] * (s1[1] - s2[1]) + d1[0] * (s2[1] - s0[1]) + d2[0] * (s0[1] - s1[1])) / det;
|
|
1887
|
-
const c = (d0[0] * (s2[0] - s1[0]) + d1[0] * (s0[0] - s2[0]) + d2[0] * (s1[0] - s0[0])) / det;
|
|
1888
|
-
const e = (d0[0] * (s1[0] * s2[1] - s2[0] * s1[1]) + d1[0] * (s2[0] * s0[1] - s0[0] * s2[1]) + d2[0] * (s0[0] * s1[1] - s1[0] * s0[1])) / det;
|
|
1889
|
-
const b = (d0[1] * (s1[1] - s2[1]) + d1[1] * (s2[1] - s0[1]) + d2[1] * (s0[1] - s1[1])) / det;
|
|
1890
|
-
const d = (d0[1] * (s2[0] - s1[0]) + d1[1] * (s0[0] - s2[0]) + d2[1] * (s1[0] - s0[0])) / det;
|
|
1891
|
-
const f = (d0[1] * (s1[0] * s2[1] - s2[0] * s1[1]) + d1[1] * (s2[0] * s0[1] - s0[0] * s2[1]) + d2[1] * (s0[0] * s1[1] - s1[0] * s0[1])) / det;
|
|
1892
|
-
return [a, b, c, d, e, f];
|
|
1872
|
+
function DrawFxLayer(_props) {
|
|
1873
|
+
return null;
|
|
1893
1874
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1875
|
+
|
|
1876
|
+
// lib/atlasSlice.ts
|
|
1877
|
+
var atlasCache = /* @__PURE__ */ new Map();
|
|
1878
|
+
function isTilesheet(a) {
|
|
1879
|
+
return typeof a.tileWidth === "number";
|
|
1897
1880
|
}
|
|
1898
|
-
function
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
meshCache.set(url, json);
|
|
1881
|
+
function isSpriteSheetAtlas(a) {
|
|
1882
|
+
const s = a;
|
|
1883
|
+
return typeof s.frameWidth === "number" && typeof s.frameHeight === "number" && typeof s.animations === "object";
|
|
1884
|
+
}
|
|
1885
|
+
function getAtlas(url, onReady) {
|
|
1886
|
+
if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
|
|
1887
|
+
atlasCache.set(url, void 0);
|
|
1888
|
+
fetch(url).then((r) => r.json()).then((json) => {
|
|
1889
|
+
atlasCache.set(url, json);
|
|
1908
1890
|
onReady();
|
|
1909
1891
|
}).catch(() => {
|
|
1910
|
-
|
|
1892
|
+
atlasCache.set(url, null);
|
|
1911
1893
|
onReady();
|
|
1912
1894
|
});
|
|
1913
1895
|
return void 0;
|
|
1914
1896
|
}
|
|
1915
|
-
function
|
|
1916
|
-
return
|
|
1897
|
+
function atlasFailed(url) {
|
|
1898
|
+
return atlasCache.get(url) === null;
|
|
1917
1899
|
}
|
|
1918
|
-
function
|
|
1919
|
-
if (
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1900
|
+
function subRectFor(atlas, sprite) {
|
|
1901
|
+
if (isTilesheet(atlas)) {
|
|
1902
|
+
let col;
|
|
1903
|
+
let row;
|
|
1904
|
+
if (sprite.includes(",")) {
|
|
1905
|
+
const [c, r] = sprite.split(",").map((n) => Number(n.trim()));
|
|
1906
|
+
col = c;
|
|
1907
|
+
row = r;
|
|
1908
|
+
} else {
|
|
1909
|
+
const i = Number(sprite);
|
|
1910
|
+
if (!Number.isFinite(i)) return null;
|
|
1911
|
+
col = i % atlas.columns;
|
|
1912
|
+
row = Math.floor(i / atlas.columns);
|
|
1913
|
+
}
|
|
1914
|
+
if (!Number.isFinite(col) || !Number.isFinite(row) || col < 0 || row < 0 || col >= atlas.columns || row >= atlas.rows) return null;
|
|
1915
|
+
const margin = atlas.margin ?? 0;
|
|
1916
|
+
const spacing = atlas.spacing ?? 0;
|
|
1917
|
+
return {
|
|
1918
|
+
sx: margin + col * (atlas.tileWidth + spacing),
|
|
1919
|
+
sy: margin + row * (atlas.tileHeight + spacing),
|
|
1920
|
+
sw: atlas.tileWidth,
|
|
1921
|
+
sh: atlas.tileHeight
|
|
1922
|
+
};
|
|
1929
1923
|
}
|
|
1930
|
-
|
|
1924
|
+
if (isSpriteSheetAtlas(atlas)) return null;
|
|
1925
|
+
const st = atlas.subTextures[sprite];
|
|
1926
|
+
if (!st) return null;
|
|
1927
|
+
return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
|
|
1931
1928
|
}
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
var skinnedLog = createLogger("almadar:ui:draw-skinned-mesh");
|
|
1935
|
-
var loggedMissing = /* @__PURE__ */ new Set();
|
|
1936
|
-
function warnMissingOnce(reason, node) {
|
|
1937
|
-
const key = `${reason}:${node.asset.url}:${String(node.meshUrl)}`;
|
|
1938
|
-
if (loggedMissing.has(key)) return;
|
|
1939
|
-
loggedMissing.add(key);
|
|
1940
|
-
skinnedLog.warn("draw-skinned-mesh unresolvable \u2014 painting fallback", { reason, url: node.asset.url, meshUrl: node.meshUrl });
|
|
1929
|
+
function atlasUrlOf(asset) {
|
|
1930
|
+
return typeof asset?.atlas === "string" && asset.atlas.length > 0 ? asset.atlas : void 0;
|
|
1941
1931
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
let best = -1;
|
|
1945
|
-
let bestW = 0;
|
|
1946
|
-
for (const { bone, w } of v.weights) {
|
|
1947
|
-
if (w > bestW) {
|
|
1948
|
-
bestW = w;
|
|
1949
|
-
best = bone;
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
return best;
|
|
1932
|
+
function isAtlasAsset(asset) {
|
|
1933
|
+
return atlasUrlOf(asset) !== void 0 && typeof asset?.sprite === "string" && asset.sprite.length > 0;
|
|
1953
1934
|
}
|
|
1954
|
-
function
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1935
|
+
function resolveAssetSource(img, asset, onReady) {
|
|
1936
|
+
if (isAtlasAsset(asset)) {
|
|
1937
|
+
const atlas = getAtlas(asset.atlas, onReady);
|
|
1938
|
+
if (!atlas) return null;
|
|
1939
|
+
const rect = subRectFor(atlas, asset.sprite);
|
|
1940
|
+
if (!rect) return null;
|
|
1941
|
+
return { img, rect, aspect: rect.sw / rect.sh };
|
|
1958
1942
|
}
|
|
1959
|
-
|
|
1943
|
+
const natW = img.naturalWidth || 1;
|
|
1944
|
+
const natH = img.naturalHeight || 1;
|
|
1945
|
+
return { img, rect: null, aspect: natW / natH };
|
|
1960
1946
|
}
|
|
1961
|
-
function
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
const rb = fetched && isRigBundle(fetched) ? fetched : void 0;
|
|
1965
|
-
const mesh = node.mesh ?? rb?.mesh ?? (!rb ? fetched : void 0);
|
|
1966
|
-
if (!mesh) return void 0;
|
|
1967
|
-
const bones = node.bones.length > 0 ? node.bones : rb?.bones ?? [];
|
|
1968
|
-
if (bones.length === 0) return void 0;
|
|
1969
|
-
const clip = node.clip ?? (node.clipName ? rb?.clips?.[node.clipName] : void 0);
|
|
1970
|
-
return { mesh, bones, clip };
|
|
1947
|
+
function blit(ctx, src, dx, dy, dw, dh) {
|
|
1948
|
+
if (src.rect) ctx.drawImage(src.img, src.rect.sx, src.rect.sy, src.rect.sw, src.rect.sh, dx, dy, dw, dh);
|
|
1949
|
+
else ctx.drawImage(src.img, dx, dy, dw, dh);
|
|
1971
1950
|
}
|
|
1972
|
-
var
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
if (v.u < 0 || v.u > 1 || v.v < 0 || v.v > 1) {
|
|
1979
|
-
pixel = true;
|
|
1980
|
-
break;
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
pixelUVByMesh.set(mesh, pixel);
|
|
1984
|
-
return pixel;
|
|
1951
|
+
var log2 = createLogger("almadar:ui:canvas-theme");
|
|
1952
|
+
var toneVar = (tone) => `--color-${DIAGRAM_TONE_COLOR_KEYS[tone]}`;
|
|
1953
|
+
var SERIES_TONES = ["series-1", "series-2", "series-3", "series-4", "series-5", "series-6", "series-7", "series-8"];
|
|
1954
|
+
var TONE_SET = new Set(DIAGRAM_TONES);
|
|
1955
|
+
function isDiagramTone(value) {
|
|
1956
|
+
return TONE_SET.has(value);
|
|
1985
1957
|
}
|
|
1986
|
-
function
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
const
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
const
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
const
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
1958
|
+
function colorCss(c) {
|
|
1959
|
+
return c.a >= 1 ? c.css : `rgba(${c.r}, ${c.g}, ${c.b}, ${c.a})`;
|
|
1960
|
+
}
|
|
1961
|
+
function oneOf(value, allowed, fallback, name) {
|
|
1962
|
+
const hit = allowed.find((a) => a === value);
|
|
1963
|
+
if (hit !== void 0) return hit;
|
|
1964
|
+
if (value !== "") log2.warn("invalid-theme-keyword", { var: name, value, allowed });
|
|
1965
|
+
return fallback;
|
|
1966
|
+
}
|
|
1967
|
+
var PLAIN_LENGTH = /^(-?[\d.]+)(px)?$/;
|
|
1968
|
+
var ScopeReader = class {
|
|
1969
|
+
constructor(scope) {
|
|
1970
|
+
__publicField(this, "scope", scope);
|
|
1971
|
+
__publicField(this, "style");
|
|
1972
|
+
__publicField(this, "probe");
|
|
1973
|
+
this.style = getComputedStyle(scope);
|
|
1974
|
+
const doc = scope.ownerDocument;
|
|
1975
|
+
this.probe = doc.createElement("span");
|
|
1976
|
+
this.probe.style.position = "absolute";
|
|
1977
|
+
this.probe.style.visibility = "hidden";
|
|
1978
|
+
this.probe.style.pointerEvents = "none";
|
|
1979
|
+
scope.appendChild(this.probe);
|
|
1980
|
+
}
|
|
1981
|
+
dispose() {
|
|
1982
|
+
this.probe.remove();
|
|
1983
|
+
}
|
|
1984
|
+
raw(name) {
|
|
1985
|
+
return this.style.getPropertyValue(name).trim();
|
|
1986
|
+
}
|
|
1987
|
+
/** A length var in px: a plain value parses directly, a derived one (`calc`, `var`) through the probe. */
|
|
1988
|
+
length(name, fallback) {
|
|
1989
|
+
const raw = this.raw(name);
|
|
1990
|
+
const plain = PLAIN_LENGTH.exec(raw);
|
|
1991
|
+
if (plain) return Number(plain[1]);
|
|
1992
|
+
if (raw === "") return fallback;
|
|
1993
|
+
this.probe.style.width = `var(${name})`;
|
|
1994
|
+
const computed = PLAIN_LENGTH.exec(getComputedStyle(this.probe).width.trim());
|
|
1995
|
+
this.probe.style.width = "";
|
|
1996
|
+
if (computed) return Number(computed[1]);
|
|
1997
|
+
log2.warn("unresolvable-theme-length", { var: name, raw });
|
|
1998
|
+
return fallback;
|
|
1999
|
+
}
|
|
2000
|
+
number(name, fallback) {
|
|
2001
|
+
const raw = this.raw(name);
|
|
2002
|
+
if (raw === "") return fallback;
|
|
2003
|
+
const n = Number(raw);
|
|
2004
|
+
if (Number.isFinite(n)) return n;
|
|
2005
|
+
log2.warn("unresolvable-theme-number", { var: name, raw });
|
|
2006
|
+
return fallback;
|
|
2007
|
+
}
|
|
2008
|
+
/** A duration var in ms (`250ms`, `0.25s`, or a `var()` alias resolved through the probe). */
|
|
2009
|
+
durationMs(name, fallback) {
|
|
2010
|
+
const parse = (v) => {
|
|
2011
|
+
const m = /^([\d.]+)(ms|s)$/.exec(v.trim());
|
|
2012
|
+
return m ? Number(m[1]) * (m[2] === "s" ? 1e3 : 1) : null;
|
|
2013
|
+
};
|
|
2014
|
+
const raw = this.raw(name);
|
|
2015
|
+
const direct = parse(raw);
|
|
2016
|
+
if (direct !== null) return direct;
|
|
2017
|
+
if (raw === "") return fallback;
|
|
2018
|
+
this.probe.style.transitionDuration = `var(${name})`;
|
|
2019
|
+
const computed = parse(getComputedStyle(this.probe).transitionDuration.split(",")[0] ?? "");
|
|
2020
|
+
this.probe.style.transitionDuration = "";
|
|
2021
|
+
return computed ?? fallback;
|
|
2028
2022
|
}
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
if (!isValidScenePos(node.position)) return;
|
|
2033
|
-
const rig = resolveRig(node, dctx);
|
|
2034
|
-
if (!rig) {
|
|
2035
|
-
if (node.meshUrl && skinMeshFailed(node.meshUrl)) warnMissingOnce("mesh-failed", node);
|
|
2036
|
-
else if (!node.mesh && !node.meshUrl) warnMissingOnce("mesh-missing", node);
|
|
2037
|
-
return;
|
|
2023
|
+
color(name) {
|
|
2024
|
+
const raw = this.raw(name);
|
|
2025
|
+
return parseComputedColor(raw) ?? resolveThemeColor(`var(${name})`, this.scope);
|
|
2038
2026
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
if (
|
|
2042
|
-
|
|
2027
|
+
shadow(name) {
|
|
2028
|
+
const raw = this.raw(name);
|
|
2029
|
+
if (raw === "" || raw === "none") return null;
|
|
2030
|
+
this.probe.style.boxShadow = `var(${name})`;
|
|
2031
|
+
const computed = getComputedStyle(this.probe).boxShadow.trim() || raw;
|
|
2032
|
+
this.probe.style.boxShadow = "";
|
|
2033
|
+
const colorMatch = /(rgba?\([^)]*\)|#[0-9a-f]{3,8}|oklch\([^)]*\))/i.exec(computed);
|
|
2034
|
+
const lengths = computed.replace(colorMatch?.[0] ?? "", "").trim().split(/\s+/).map((t) => PLAIN_LENGTH.exec(t)).filter((m) => m !== null).map((m) => Number(m[1]));
|
|
2035
|
+
if (lengths.length < 2) return null;
|
|
2036
|
+
const color = colorMatch ? parseComputedColor(colorMatch[0]) : null;
|
|
2037
|
+
return { offsetX: lengths[0], offsetY: lengths[1], blur: lengths[2] ?? 0, color: color ? colorCss(color) : "rgba(0, 0, 0, 0.25)" };
|
|
2043
2038
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2039
|
+
family(name, fallback) {
|
|
2040
|
+
return this.raw(name) || fallback;
|
|
2046
2041
|
}
|
|
2047
|
-
paintMeshPass(painter, node, rig.bones, rig.mesh, tex, dctx, resolveSkinPose(node, rig.clip), node.opacity ?? 1);
|
|
2048
2042
|
};
|
|
2049
|
-
function
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
return
|
|
2043
|
+
function dashList(raw, fallback) {
|
|
2044
|
+
if (raw === "") return fallback;
|
|
2045
|
+
const parts = raw.split(/[\s,]+/).map(Number);
|
|
2046
|
+
return parts.every((n) => Number.isFinite(n) && n >= 0) ? parts : fallback;
|
|
2053
2047
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2048
|
+
function prefersReducedMotion(scope) {
|
|
2049
|
+
const view = scope.ownerDocument.defaultView;
|
|
2050
|
+
return typeof view?.matchMedia === "function" && view.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2051
|
+
}
|
|
2052
|
+
function resolveCanvasTheme(scope) {
|
|
2053
|
+
const r = new ScopeReader(scope);
|
|
2054
|
+
try {
|
|
2055
|
+
const tones = {};
|
|
2056
|
+
for (const tone of DIAGRAM_TONES) {
|
|
2057
|
+
const c = r.color(toneVar(tone));
|
|
2058
|
+
tones[tone] = c ? colorCss(c) : "currentColor";
|
|
2064
2059
|
}
|
|
2060
|
+
const ground = r.color("--surface-diagram");
|
|
2061
|
+
const text = {};
|
|
2062
|
+
const TEXT_FALLBACK = { xs: 12, sm: 14, base: 16, lg: 18, xl: 20, "2xl": 24 };
|
|
2063
|
+
for (const size of DIAGRAM_TEXT_SIZES) text[size] = r.length(`--text-${size}`, TEXT_FALLBACK[size]);
|
|
2064
|
+
const body = r.family("--font-family-body", r.family("--font-family", "system-ui, sans-serif"));
|
|
2065
|
+
return {
|
|
2066
|
+
ground: ground ? colorCss(ground) : "transparent",
|
|
2067
|
+
tones,
|
|
2068
|
+
series: SERIES_TONES.map((t) => tones[t]),
|
|
2069
|
+
strokes: {
|
|
2070
|
+
thin: r.length("--diagram-stroke-thin", 1),
|
|
2071
|
+
normal: r.length("--diagram-stroke-normal", 1.5),
|
|
2072
|
+
bold: r.length("--diagram-stroke-bold", 3)
|
|
2073
|
+
},
|
|
2074
|
+
lineCap: oneOf(r.raw("--diagram-line-cap"), ["butt", "round", "square"], "round", "--diagram-line-cap"),
|
|
2075
|
+
lineJoin: oneOf(r.raw("--diagram-line-join"), ["miter", "round", "bevel"], "round", "--diagram-line-join"),
|
|
2076
|
+
dash: dashList(r.raw("--diagram-dash"), [6, 4]),
|
|
2077
|
+
dot: dashList(r.raw("--diagram-dot"), [1, 4]),
|
|
2078
|
+
fillStyle: oneOf(r.raw("--diagram-fill-style"), ["solid", "tint", "outline", "hatch"], "tint", "--diagram-fill-style"),
|
|
2079
|
+
fillOpacity: r.number("--diagram-fill-opacity", 0.18),
|
|
2080
|
+
roughness: r.number("--diagram-roughness", 0),
|
|
2081
|
+
glow: r.number("--diagram-glow", 0),
|
|
2082
|
+
shadow: r.shadow("--diagram-shadow"),
|
|
2083
|
+
marker: oneOf(r.raw("--diagram-marker"), ["triangle", "open", "line", "dot"], "triangle", "--diagram-marker"),
|
|
2084
|
+
faces: {
|
|
2085
|
+
body,
|
|
2086
|
+
display: r.family("--font-family-display", body),
|
|
2087
|
+
mono: r.family("--font-family-mono", "ui-monospace, monospace")
|
|
2088
|
+
},
|
|
2089
|
+
label: {
|
|
2090
|
+
font: oneOf(r.raw("--diagram-label-font"), ["body", "display", "mono"], "body", "--diagram-label-font"),
|
|
2091
|
+
size: oneOf(r.raw("--diagram-label-size"), DIAGRAM_TEXT_SIZES, "sm", "--diagram-label-size"),
|
|
2092
|
+
weight: r.raw("--diagram-label-weight") || "500",
|
|
2093
|
+
uppercase: oneOf(r.raw("--diagram-label-case"), ["none", "uppercase"], "none", "--diagram-label-case") === "uppercase"
|
|
2094
|
+
},
|
|
2095
|
+
text,
|
|
2096
|
+
corner: r.length("--diagram-corner", 4),
|
|
2097
|
+
motion: {
|
|
2098
|
+
enabled: r.raw("--motion-enable") !== "off" && !prefersReducedMotion(scope),
|
|
2099
|
+
fastMs: r.durationMs("--duration-fast", 150),
|
|
2100
|
+
normalMs: r.durationMs("--duration-normal", 250),
|
|
2101
|
+
slowMs: r.durationMs("--duration-slow", 400),
|
|
2102
|
+
easing: r.raw("--easing-standard") || "ease"
|
|
2103
|
+
},
|
|
2104
|
+
scene: {
|
|
2105
|
+
roughness: r.number("--material-roughness", 0.6),
|
|
2106
|
+
metalness: r.number("--material-metalness", 0),
|
|
2107
|
+
flat: r.number("--material-flat", 0) >= 1,
|
|
2108
|
+
outline: r.number("--material-outline", 0),
|
|
2109
|
+
ambient: r.number("--light-ambient", 0.55),
|
|
2110
|
+
key: r.number("--light-key", 1),
|
|
2111
|
+
keyColor: (() => {
|
|
2112
|
+
const c = r.color("--light-key-color");
|
|
2113
|
+
return c ? colorCss(c) : "#ffffff";
|
|
2114
|
+
})(),
|
|
2115
|
+
fog: r.number("--scene-fog", 0)
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
} finally {
|
|
2119
|
+
r.dispose();
|
|
2065
2120
|
}
|
|
2066
|
-
return { min, max };
|
|
2067
|
-
}
|
|
2068
|
-
function clampSegments(segments) {
|
|
2069
|
-
const s = segments ?? MESH_SEGMENTS_DEFAULT;
|
|
2070
|
-
return Math.min(64, Math.max(3, Math.round(s)));
|
|
2071
2121
|
}
|
|
2072
|
-
function
|
|
2073
|
-
|
|
2122
|
+
function withSeries(theme, series, scope) {
|
|
2123
|
+
if (!theme || !series || series.length === 0 || !scope) return theme;
|
|
2124
|
+
const colors = series.map((c, i) => markColor(theme, c, scope, SERIES_TONES[i % SERIES_TONES.length]));
|
|
2125
|
+
const tones = { ...theme.tones };
|
|
2126
|
+
SERIES_TONES.forEach((tone, i) => {
|
|
2127
|
+
tones[tone] = colors[i % colors.length];
|
|
2128
|
+
});
|
|
2129
|
+
return { ...theme, tones, series: SERIES_TONES.map((t) => tones[t]) };
|
|
2074
2130
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
"
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
"rotateY",
|
|
2082
|
-
"rotateZ",
|
|
2083
|
-
"scale",
|
|
2084
|
-
"opacity",
|
|
2085
|
-
"emissiveIntensity"
|
|
2086
|
-
];
|
|
2087
|
-
function applyMeshAnimation(node, timeMs) {
|
|
2088
|
-
const anim = node.animation;
|
|
2089
|
-
if (!anim || !(anim.durationMs > 0) || anim.keyframes.length === 0) return null;
|
|
2090
|
-
const frames = [...anim.keyframes].sort((a, b) => a.at - b.at);
|
|
2091
|
-
const cycle = timeMs / anim.durationMs;
|
|
2092
|
-
const t = anim.loop === false ? Math.min(cycle, 1) : cycle - Math.floor(cycle);
|
|
2093
|
-
const trackValue = (key) => {
|
|
2094
|
-
const defined = frames.filter((f) => f[key] !== void 0);
|
|
2095
|
-
if (defined.length === 0) return void 0;
|
|
2096
|
-
let prev;
|
|
2097
|
-
let next;
|
|
2098
|
-
for (const f of defined) {
|
|
2099
|
-
if (f.at <= t) prev = f;
|
|
2100
|
-
else if (!next) next = f;
|
|
2101
|
-
}
|
|
2102
|
-
if (!prev) return defined[0][key];
|
|
2103
|
-
if (!next) return prev[key];
|
|
2104
|
-
const span = next.at - prev.at;
|
|
2105
|
-
const k = span > 0 ? (t - prev.at) / span : 1;
|
|
2106
|
-
const a = prev[key];
|
|
2107
|
-
const b = next[key];
|
|
2108
|
-
if (typeof a === "number" && typeof b === "number") return lerp2(a, b, k);
|
|
2109
|
-
return a;
|
|
2110
|
-
};
|
|
2111
|
-
const num = {};
|
|
2112
|
-
for (const key of NUMERIC_TRACKS2) {
|
|
2113
|
-
const v = trackValue(key);
|
|
2114
|
-
if (v !== void 0) num[key] = v;
|
|
2131
|
+
function markColor(theme, value, scope, fallback) {
|
|
2132
|
+
if (value === void 0 || value === "") return theme.tones[fallback];
|
|
2133
|
+
if (isDiagramTone(value)) return theme.tones[value];
|
|
2134
|
+
if (value.startsWith("var(") || value.startsWith("color-mix(")) {
|
|
2135
|
+
const resolved = resolveThemeColor(value, scope);
|
|
2136
|
+
return resolved ? colorCss(resolved) : theme.tones[fallback];
|
|
2115
2137
|
}
|
|
2116
|
-
return
|
|
2117
|
-
offset: [num.offsetX ?? 0, num.offsetY ?? 0, num.offsetZ ?? 0],
|
|
2118
|
-
rotate: [num.rotateX ?? 0, num.rotateY ?? 0, num.rotateZ ?? 0],
|
|
2119
|
-
scale: num.scale ?? 1,
|
|
2120
|
-
opacity: num.opacity,
|
|
2121
|
-
emissiveIntensity: num.emissiveIntensity,
|
|
2122
|
-
color: trackValue("color"),
|
|
2123
|
-
emissive: trackValue("emissive")
|
|
2124
|
-
};
|
|
2138
|
+
return value;
|
|
2125
2139
|
}
|
|
2126
|
-
function
|
|
2127
|
-
|
|
2128
|
-
if (register) register({ ...props, type: "draw-mesh" });
|
|
2129
|
-
return null;
|
|
2140
|
+
function markStrokeWidth(theme, literal, role, fallback = "normal") {
|
|
2141
|
+
return literal ?? theme.strokes[role ?? fallback];
|
|
2130
2142
|
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
if (
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2143
|
+
function labelFont(theme, roles, literalPx, literalFamily) {
|
|
2144
|
+
const px = literalPx ?? theme.text[roles.textSize ?? theme.label.size];
|
|
2145
|
+
const family = literalFamily ?? theme.faces[roles.font ?? theme.label.font];
|
|
2146
|
+
return { font: `${theme.label.weight} ${px}px ${family}`, px };
|
|
2147
|
+
}
|
|
2148
|
+
function labelText(theme, text, textCase = "theme") {
|
|
2149
|
+
return theme.label.uppercase && textCase === "theme" ? text.toUpperCase() : text;
|
|
2150
|
+
}
|
|
2151
|
+
function fillLabel(ctx, theme, text, x, y) {
|
|
2152
|
+
if (!text) return;
|
|
2153
|
+
ctx.save();
|
|
2154
|
+
ctx.strokeStyle = theme.ground;
|
|
2155
|
+
ctx.lineWidth = Math.max(2, theme.text.xs * 0.3);
|
|
2156
|
+
ctx.lineJoin = "round";
|
|
2157
|
+
ctx.strokeText(text, x, y);
|
|
2158
|
+
ctx.restore();
|
|
2159
|
+
ctx.fillText(text, x, y);
|
|
2160
|
+
}
|
|
2161
|
+
function outlineStroke(stroke, fill, style, fillTone) {
|
|
2162
|
+
return outlineCarriesFill(fill, style, fillTone) && fill !== void 0 ? fill : stroke;
|
|
2163
|
+
}
|
|
2164
|
+
function outlineCarriesFill(fill, style, fillTone) {
|
|
2165
|
+
return style === "outline" && fill !== void 0 && fillTone !== "fill";
|
|
2166
|
+
}
|
|
2167
|
+
function applyLineCharacter(ctx, theme, strokeColor) {
|
|
2168
|
+
ctx.lineCap = theme.lineCap;
|
|
2169
|
+
ctx.lineJoin = theme.lineJoin;
|
|
2170
|
+
if (theme.glow > 0) {
|
|
2171
|
+
ctx.shadowColor = strokeColor;
|
|
2172
|
+
ctx.shadowBlur = theme.glow;
|
|
2173
|
+
ctx.shadowOffsetX = 0;
|
|
2174
|
+
ctx.shadowOffsetY = 0;
|
|
2147
2175
|
}
|
|
2148
|
-
const base = view.position ?? { x: view.x, y: view.z ?? 0, z: view.y };
|
|
2149
|
-
if (!Number.isFinite(base.x) || !Number.isFinite(base.y)) return void 0;
|
|
2150
|
-
return {
|
|
2151
|
-
x: base.x + (view.vx ?? 0) * ageSec,
|
|
2152
|
-
y: base.y + (view.vz ?? 0) * ageSec,
|
|
2153
|
-
...base.z !== void 0 || view.vy !== void 0 || g !== 0 ? { z: (base.z ?? 0) + (view.vy ?? 0) * ageSec - fall } : {}
|
|
2154
|
-
};
|
|
2155
2176
|
}
|
|
2156
|
-
function
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
const r = size * (0.06 + 0.06 * fxHash01(view.id, i * 3 + 2));
|
|
2161
|
-
const color = view.color ?? DEFAULT_SPARK_COLOR;
|
|
2162
|
-
return {
|
|
2163
|
-
type: "draw-shape",
|
|
2164
|
-
shape: "ellipse",
|
|
2165
|
-
position: { ...pos, x: pos.x + Math.cos(angle) * dist, y: pos.y + Math.sin(angle) * dist },
|
|
2166
|
-
anchor: "center",
|
|
2167
|
-
radiusX: r,
|
|
2168
|
-
fill: color,
|
|
2169
|
-
blendMode: "lighter",
|
|
2170
|
-
opacity: fade,
|
|
2171
|
-
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
2172
|
-
};
|
|
2177
|
+
function markDash(theme, dash) {
|
|
2178
|
+
if (dash === "dashed") return theme.dash;
|
|
2179
|
+
if (dash === "dotted") return theme.dot;
|
|
2180
|
+
return [];
|
|
2173
2181
|
}
|
|
2174
|
-
function
|
|
2175
|
-
const
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
2191
|
-
}
|
|
2192
|
-
];
|
|
2193
|
-
}
|
|
2194
|
-
case "puff": {
|
|
2195
|
-
const count = view.count ?? 3;
|
|
2196
|
-
return Array.from({ length: count }, (_, i) => {
|
|
2197
|
-
const angle = fxHash01(view.id, i * 5) * Math.PI * 2;
|
|
2198
|
-
const drift = size * 0.3 * fxHash01(view.id, i * 5 + 1) * easeOut(progress);
|
|
2199
|
-
return {
|
|
2200
|
-
type: "draw-shape",
|
|
2201
|
-
shape: "ellipse",
|
|
2202
|
-
position: { ...pos, x: pos.x + Math.cos(angle) * drift, y: pos.y + Math.sin(angle) * drift },
|
|
2203
|
-
anchor: "center",
|
|
2204
|
-
radiusX: size * (0.15 + 0.35 * progress) * (0.7 + 0.6 * fxHash01(view.id, i * 5 + 2)),
|
|
2205
|
-
fill: i === 0 && view.color2 ? view.color2 : color,
|
|
2206
|
-
opacity: fade * 0.6,
|
|
2207
|
-
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
2208
|
-
};
|
|
2209
|
-
});
|
|
2210
|
-
}
|
|
2211
|
-
case "streak": {
|
|
2212
|
-
const count = view.count ?? 5;
|
|
2213
|
-
return Array.from({ length: count }, (_, i) => {
|
|
2214
|
-
const angle = fxHash01(view.id, i * 3) * Math.PI * 2;
|
|
2215
|
-
const inner = size * (0.2 + 0.8 * easeOut(progress)) * (0.6 + 0.4 * fxHash01(view.id, i * 3 + 1));
|
|
2216
|
-
const len = size * 0.35;
|
|
2217
|
-
return {
|
|
2218
|
-
type: "draw-shape",
|
|
2219
|
-
shape: "ellipse",
|
|
2220
|
-
position: pos,
|
|
2221
|
-
anchor: "center",
|
|
2222
|
-
offsetX: inner + len / 2,
|
|
2223
|
-
offsetY: 0,
|
|
2224
|
-
radiusX: len / 2,
|
|
2225
|
-
radiusY: size * 0.04,
|
|
2226
|
-
rotate: angle,
|
|
2227
|
-
fill: color,
|
|
2228
|
-
blendMode: "lighter",
|
|
2229
|
-
opacity: fade,
|
|
2230
|
-
...view.glow ? { shadow: { color, blur: view.glow } } : {}
|
|
2231
|
-
};
|
|
2232
|
-
});
|
|
2182
|
+
function wobble(x, y, k) {
|
|
2183
|
+
const s = Math.sin(x * 12.9898 + y * 78.233 + k * 37.719) * 43758.5453;
|
|
2184
|
+
return (s - Math.floor(s)) * 2 - 1;
|
|
2185
|
+
}
|
|
2186
|
+
function tracePolyline(ctx, theme, points, closed) {
|
|
2187
|
+
ctx.beginPath();
|
|
2188
|
+
if (points.length === 0) return;
|
|
2189
|
+
const amp = theme.roughness;
|
|
2190
|
+
const pts = closed ? [...points, points[0]] : [...points];
|
|
2191
|
+
ctx.moveTo(pts[0].x + amp * wobble(pts[0].x, pts[0].y, 0), pts[0].y + amp * wobble(pts[0].y, pts[0].x, 1));
|
|
2192
|
+
for (let i = 1; i < pts.length; i++) {
|
|
2193
|
+
const a = pts[i - 1];
|
|
2194
|
+
const b = pts[i];
|
|
2195
|
+
if (amp === 0) {
|
|
2196
|
+
ctx.lineTo(b.x, b.y);
|
|
2197
|
+
continue;
|
|
2233
2198
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2199
|
+
const steps = Math.max(1, Math.round(Math.hypot(b.x - a.x, b.y - a.y) / 24));
|
|
2200
|
+
for (let s = 1; s <= steps; s++) {
|
|
2201
|
+
const t = s / steps;
|
|
2202
|
+
const x = a.x + (b.x - a.x) * t;
|
|
2203
|
+
const y = a.y + (b.y - a.y) * t;
|
|
2204
|
+
ctx.lineTo(x + amp * wobble(x, y, 2), y + amp * wobble(y, x, 3));
|
|
2237
2205
|
}
|
|
2238
2206
|
}
|
|
2207
|
+
if (closed) ctx.closePath();
|
|
2239
2208
|
}
|
|
2240
|
-
function
|
|
2241
|
-
|
|
2242
|
-
const tickMs = node.tickMs ?? DEFAULT_TICK_MS;
|
|
2243
|
-
const { ageMs, progress, fade } = fxLifecycle(view, epochNowMs, tickMs);
|
|
2244
|
-
if (progress >= 1) return [];
|
|
2245
|
-
const pos = fxPosition(view, dim, ageMs / 1e3);
|
|
2246
|
-
if (!pos) return [];
|
|
2209
|
+
function arcPoints(cx, cy, rx, ry, startRad = 0, endRad = Math.PI * 2) {
|
|
2210
|
+
const steps = Math.max(12, Math.round(Math.abs(endRad - startRad) * Math.max(rx, ry) / 8));
|
|
2247
2211
|
const out = [];
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
out.push({
|
|
2252
|
-
type: "draw-group",
|
|
2253
|
-
position: pos,
|
|
2254
|
-
opacity: fade,
|
|
2255
|
-
...view.size !== void 0 ? { scale: view.size } : {},
|
|
2256
|
-
items: artItems
|
|
2257
|
-
});
|
|
2258
|
-
} else if (sprite?.url) {
|
|
2259
|
-
const spriteSize = view.size ?? 0.6;
|
|
2260
|
-
out.push({
|
|
2261
|
-
type: "draw-sprite",
|
|
2262
|
-
position: pos,
|
|
2263
|
-
asset: sprite,
|
|
2264
|
-
anchor: "center",
|
|
2265
|
-
width: spriteSize,
|
|
2266
|
-
height: spriteSize,
|
|
2267
|
-
opacity: fade,
|
|
2268
|
-
animation: view.animation ?? sprite.animations?.[0],
|
|
2269
|
-
// One-shot fx rows (burst) play from the item's birth, not wall-time.
|
|
2270
|
-
clockMs: ageMs
|
|
2271
|
-
});
|
|
2272
|
-
} else {
|
|
2273
|
-
out.push(...proceduralShapes(view, pos, fade, progress));
|
|
2274
|
-
}
|
|
2275
|
-
if (view.message) {
|
|
2276
|
-
const pxSize = projector && view.size !== void 0 ? Math.max(10, Math.round(view.size * projector.tileWidth)) : void 0;
|
|
2277
|
-
const family = fontFamily ?? "system-ui, sans-serif";
|
|
2278
|
-
const text = {
|
|
2279
|
-
type: "draw-text",
|
|
2280
|
-
text: view.message,
|
|
2281
|
-
position: pos,
|
|
2282
|
-
offsetY: -(0.15 + 0.55 * progress),
|
|
2283
|
-
color: view.color ?? node.textColor ?? DEFAULT_TEXT_COLOR,
|
|
2284
|
-
opacity: fade,
|
|
2285
|
-
font: `bold ${pxSize ?? 14}px ${family}`
|
|
2286
|
-
};
|
|
2287
|
-
out.push(text);
|
|
2212
|
+
for (let i = 0; i <= steps; i++) {
|
|
2213
|
+
const t = startRad + (endRad - startRad) * i / steps;
|
|
2214
|
+
out.push({ x: cx + rx * Math.cos(t), y: cy + ry * Math.sin(t) });
|
|
2288
2215
|
}
|
|
2289
2216
|
return out;
|
|
2290
2217
|
}
|
|
2291
|
-
function
|
|
2292
|
-
if (
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2218
|
+
function fillMark(ctx, theme, color, style = theme.fillStyle) {
|
|
2219
|
+
if (style === "outline") return;
|
|
2220
|
+
ctx.save();
|
|
2221
|
+
ctx.shadowBlur = 0;
|
|
2222
|
+
ctx.shadowColor = "transparent";
|
|
2223
|
+
if (theme.shadow) {
|
|
2224
|
+
ctx.shadowColor = theme.shadow.color;
|
|
2225
|
+
ctx.shadowOffsetX = theme.shadow.offsetX;
|
|
2226
|
+
ctx.shadowOffsetY = theme.shadow.offsetY;
|
|
2227
|
+
ctx.shadowBlur = theme.shadow.blur;
|
|
2297
2228
|
}
|
|
2298
|
-
|
|
2229
|
+
if (style === "hatch") {
|
|
2230
|
+
ctx.clip();
|
|
2231
|
+
const span = ctx.canvas.width + ctx.canvas.height;
|
|
2232
|
+
ctx.strokeStyle = color;
|
|
2233
|
+
ctx.lineWidth = theme.strokes.thin;
|
|
2234
|
+
ctx.setLineDash([]);
|
|
2235
|
+
ctx.beginPath();
|
|
2236
|
+
for (let d = -span; d < span; d += 6) {
|
|
2237
|
+
ctx.moveTo(d, 0);
|
|
2238
|
+
ctx.lineTo(d + span, span);
|
|
2239
|
+
}
|
|
2240
|
+
ctx.stroke();
|
|
2241
|
+
} else {
|
|
2242
|
+
ctx.globalAlpha *= style === "tint" ? theme.fillOpacity : 1;
|
|
2243
|
+
ctx.fillStyle = color;
|
|
2244
|
+
ctx.fill();
|
|
2245
|
+
}
|
|
2246
|
+
ctx.restore();
|
|
2299
2247
|
}
|
|
2300
|
-
function
|
|
2301
|
-
|
|
2248
|
+
function drawMarker(ctx, theme, x1, y1, x2, y2, color, width) {
|
|
2249
|
+
const size = Math.max(8, width * 4);
|
|
2250
|
+
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
2251
|
+
const left = { x: x2 - size * Math.cos(angle - Math.PI / 6), y: y2 - size * Math.sin(angle - Math.PI / 6) };
|
|
2252
|
+
const right = { x: x2 - size * Math.cos(angle + Math.PI / 6), y: y2 - size * Math.sin(angle + Math.PI / 6) };
|
|
2253
|
+
ctx.save();
|
|
2254
|
+
ctx.strokeStyle = color;
|
|
2255
|
+
ctx.fillStyle = color;
|
|
2256
|
+
ctx.lineWidth = width;
|
|
2257
|
+
ctx.setLineDash([]);
|
|
2258
|
+
switch (theme.marker) {
|
|
2259
|
+
case "triangle": {
|
|
2260
|
+
ctx.beginPath();
|
|
2261
|
+
ctx.moveTo(x2, y2);
|
|
2262
|
+
ctx.lineTo(left.x, left.y);
|
|
2263
|
+
ctx.lineTo(right.x, right.y);
|
|
2264
|
+
ctx.closePath();
|
|
2265
|
+
ctx.fill();
|
|
2266
|
+
break;
|
|
2267
|
+
}
|
|
2268
|
+
case "open":
|
|
2269
|
+
case "line": {
|
|
2270
|
+
ctx.beginPath();
|
|
2271
|
+
ctx.moveTo(left.x, left.y);
|
|
2272
|
+
ctx.lineTo(x2, y2);
|
|
2273
|
+
ctx.lineTo(right.x, right.y);
|
|
2274
|
+
if (theme.marker === "open") ctx.stroke();
|
|
2275
|
+
else {
|
|
2276
|
+
ctx.moveTo(x2 - size * 0.5 * Math.cos(angle + Math.PI / 2), y2 - size * 0.5 * Math.sin(angle + Math.PI / 2));
|
|
2277
|
+
ctx.lineTo(x2 + size * 0.5 * Math.cos(angle + Math.PI / 2), y2 + size * 0.5 * Math.sin(angle + Math.PI / 2));
|
|
2278
|
+
ctx.stroke();
|
|
2279
|
+
}
|
|
2280
|
+
break;
|
|
2281
|
+
}
|
|
2282
|
+
case "dot": {
|
|
2283
|
+
ctx.beginPath();
|
|
2284
|
+
ctx.arc(x2, y2, Math.max(2.5, width * 1.5), 0, Math.PI * 2);
|
|
2285
|
+
ctx.fill();
|
|
2286
|
+
break;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
ctx.restore();
|
|
2290
|
+
}
|
|
2291
|
+
function useCanvasTheme(scopeRef) {
|
|
2292
|
+
const { appliedTheme } = useTheme();
|
|
2293
|
+
const scopeKey = JSON.stringify(useThemeScope());
|
|
2294
|
+
const [state, setState] = useState({ theme: null, version: 0 });
|
|
2295
|
+
useLayoutEffect(() => {
|
|
2296
|
+
const scope = scopeRef.current;
|
|
2297
|
+
if (!scope) return;
|
|
2298
|
+
const theme = resolveCanvasTheme(scope);
|
|
2299
|
+
setState((prev) => ({ theme, version: prev.version + 1 }));
|
|
2300
|
+
}, [appliedTheme, scopeKey, scopeRef]);
|
|
2301
|
+
return state;
|
|
2302
2302
|
}
|
|
2303
2303
|
|
|
2304
2304
|
// lib/drawable/themeDrawables.ts
|