@vitreajs/vitrea 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -19
- package/dist/{dist-FGJI5LQM.js → dist-RQ4ZMA3D.js} +984 -98
- package/dist/dist-RQ4ZMA3D.js.map +1 -0
- package/dist/index.d.ts +539 -39
- package/dist/index.js +129 -18
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/dist-FGJI5LQM.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -302,9 +302,11 @@ var DIAGNOSTIC_CODES = [
|
|
|
302
302
|
"same-plane-overlap",
|
|
303
303
|
/** `regular` and `clear` nodes share one GlassGroup (§Material variants). */
|
|
304
304
|
"variant-mixing",
|
|
305
|
+
/** Two different author tint seeds share one GlassGroup, which is one optics pass (§Material tint). */
|
|
306
|
+
"tint-mixing",
|
|
305
307
|
/** A group's `mergeDistance` is below its `samplingPadding`, so proxies can double-filter (X1). */
|
|
306
308
|
"merge-distance-below-padding",
|
|
307
|
-
/**
|
|
309
|
+
/** A group's padded proxy samples pixels a neighbouring group paints, so the filter applies twice (X1). */
|
|
308
310
|
"group-proxy-overlap",
|
|
309
311
|
/** A `clear` node has no dimming policy, so it resolved to `regular` instead. */
|
|
310
312
|
"clear-variant-needs-dimming",
|
|
@@ -420,10 +422,24 @@ var FRAME_PHASES = ["collect", "read", "update", "write", "render"];
|
|
|
420
422
|
|
|
421
423
|
// src/material.ts
|
|
422
424
|
var MATERIAL_VARIANTS = ["regular", "clear"];
|
|
425
|
+
var clamp01 = (value) => Math.min(1, Math.max(0, value));
|
|
426
|
+
function glassTint(color, strength = 1) {
|
|
427
|
+
return {
|
|
428
|
+
color: [clamp01(color[0]), clamp01(color[1]), clamp01(color[2])],
|
|
429
|
+
strength: clamp01(strength)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function normaliseTint(tint) {
|
|
433
|
+
if (tint === null || tint === void 0) return void 0;
|
|
434
|
+
const normalised = glassTint(tint.color, tint.strength);
|
|
435
|
+
return normalised.strength === 0 ? void 0 : normalised;
|
|
436
|
+
}
|
|
423
437
|
var DEFAULT_CLEAR_DIMMING = { scrim: 0.28, direction: "darken" };
|
|
424
438
|
function resolveMaterial(request) {
|
|
425
439
|
const { variant, dimming, nodeId, diagnostics } = request;
|
|
426
|
-
|
|
440
|
+
const tint = normaliseTint(request.tint);
|
|
441
|
+
const tinted = tint === void 0 ? {} : { tint };
|
|
442
|
+
if (variant === "regular") return { variant: "regular", adaptation: "adaptive", ...tinted };
|
|
427
443
|
if (dimming === void 0) {
|
|
428
444
|
diagnostics?.report({
|
|
429
445
|
code: "clear-variant-needs-dimming",
|
|
@@ -431,9 +447,31 @@ function resolveMaterial(request) {
|
|
|
431
447
|
subjects: [nodeId ?? "*"],
|
|
432
448
|
message: `The clear variant requires a dimming policy (\xA7Material variants); without one its foreground is not guaranteed legible. This surface rendered as regular instead. Supply one on the group's material profile \u2014 DEFAULT_CLEAR_DIMMING is a usable starting point.`
|
|
433
449
|
});
|
|
434
|
-
return { variant: "regular", adaptation: "adaptive" };
|
|
450
|
+
return { variant: "regular", adaptation: "adaptive", ...tinted };
|
|
435
451
|
}
|
|
436
|
-
return { variant: "clear", adaptation: "constrained", dimming };
|
|
452
|
+
return { variant: "clear", adaptation: "constrained", dimming, ...tinted };
|
|
453
|
+
}
|
|
454
|
+
function sameTint(a, b) {
|
|
455
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
456
|
+
return a.strength === b.strength && a.color[0] === b.color[0] && a.color[1] === b.color[1] && a.color[2] === b.color[2];
|
|
457
|
+
}
|
|
458
|
+
function checkTintMixing(check) {
|
|
459
|
+
const { groupId, members, diagnostics } = check;
|
|
460
|
+
const tinted = members.filter(
|
|
461
|
+
(member) => member.tint !== void 0
|
|
462
|
+
);
|
|
463
|
+
const distinct = tinted.filter(
|
|
464
|
+
(member, index) => tinted.findIndex((other) => sameTint(other.tint, member.tint)) === index
|
|
465
|
+
);
|
|
466
|
+
if (distinct.length < 2) return false;
|
|
467
|
+
const name = (list) => list.map((member) => member.nodeId).join(", ");
|
|
468
|
+
diagnostics?.report({
|
|
469
|
+
code: "tint-mixing",
|
|
470
|
+
severity: "warning",
|
|
471
|
+
subjects: [groupId],
|
|
472
|
+
message: `Group "${groupId}" asks for ${distinct.length} different tint seeds (${name(tinted)}). A group is one optics pass and carries one seed, so the GPU tier paints them all with the first surface's colour while the CSS tier honours each \u2014 the two tiers will not agree. Apple's guidance is to tint one control rather than several; give a second tinted surface its own GlassGroup if it really needs a different colour.`
|
|
473
|
+
});
|
|
474
|
+
return true;
|
|
437
475
|
}
|
|
438
476
|
function checkVariantMixing(check) {
|
|
439
477
|
const { groupId, members, diagnostics } = check;
|
|
@@ -475,6 +513,19 @@ function inflateRect(rect, by) {
|
|
|
475
513
|
height: rect.height + by * 2
|
|
476
514
|
};
|
|
477
515
|
}
|
|
516
|
+
function clipRect(rect, clip) {
|
|
517
|
+
if (clip === void 0 || clip.length === 0) return rect;
|
|
518
|
+
let { x, y } = rect;
|
|
519
|
+
let right = rect.x + rect.width;
|
|
520
|
+
let bottom = rect.y + rect.height;
|
|
521
|
+
for (const window of clip) {
|
|
522
|
+
x = Math.max(x, window.x);
|
|
523
|
+
y = Math.max(y, window.y);
|
|
524
|
+
right = Math.min(right, window.x + window.width);
|
|
525
|
+
bottom = Math.min(bottom, window.y + window.height);
|
|
526
|
+
}
|
|
527
|
+
return { x, y, width: Math.max(0, right - x), height: Math.max(0, bottom - y) };
|
|
528
|
+
}
|
|
478
529
|
function rectsOverlap(a, b) {
|
|
479
530
|
if (a.width <= 0 || a.height <= 0 || b.width <= 0 || b.height <= 0) return false;
|
|
480
531
|
return a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
@@ -553,17 +604,51 @@ function createGlassScene(options) {
|
|
|
553
604
|
};
|
|
554
605
|
const groupsOfSource = (sourceId) => [...groups.values()].filter((group) => group.descriptor.backdropSourceId === sourceId);
|
|
555
606
|
const nodesOfGroup = (groupId) => [...nodes.values()].filter((node) => node.descriptor.groupId === groupId);
|
|
607
|
+
const concentricChildrenOf = (nodeId) => [...nodes.values()].filter((node) => node.descriptor.concentricOf?.nodeId === nodeId);
|
|
608
|
+
function requireConcentricParent(descriptor) {
|
|
609
|
+
const link = descriptor.concentricOf;
|
|
610
|
+
if (link === void 0) return;
|
|
611
|
+
if (link.nodeId === descriptor.id) {
|
|
612
|
+
throw new GlassSceneError(
|
|
613
|
+
"in-use",
|
|
614
|
+
`Glass node "${descriptor.id}" is its own concentric parent. A surface cannot be a level set of its own field.`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
const parent = nodes.get(link.nodeId);
|
|
618
|
+
if (parent === void 0) {
|
|
619
|
+
throw unknown("glass node", link.nodeId);
|
|
620
|
+
}
|
|
621
|
+
if (parent.descriptor.groupId !== descriptor.groupId) {
|
|
622
|
+
throw new GlassSceneError(
|
|
623
|
+
"in-use",
|
|
624
|
+
`Glass node "${descriptor.id}" is concentric on "${link.nodeId}", which is in group "${parent.descriptor.groupId}" rather than "${descriptor.groupId}". A concentric child is drawn as a level set of its parent's field, and fields are resolved per group (X8 rider 2).`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
const seen = /* @__PURE__ */ new Set([descriptor.id]);
|
|
628
|
+
let ancestor = link.nodeId;
|
|
629
|
+
while (ancestor !== void 0) {
|
|
630
|
+
if (seen.has(ancestor)) {
|
|
631
|
+
throw new GlassSceneError(
|
|
632
|
+
"in-use",
|
|
633
|
+
`Concentric parent "${link.nodeId}" would put glass node "${descriptor.id}" in a cycle (${[...seen].join(" \u2192 ")} \u2192 ${ancestor}). A level set has to bottom out in a shape.`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
seen.add(ancestor);
|
|
637
|
+
ancestor = nodes.get(ancestor)?.descriptor.concentricOf?.nodeId;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
556
640
|
const isRebuildable = (record) => record.descriptor.kind === "texture" && record.dirtyEpoch > record.builtEpoch;
|
|
557
641
|
function capabilityInputs(group, hint) {
|
|
558
642
|
const source = requireSource(group.descriptor.backdropSourceId);
|
|
559
643
|
const pressure = group.governor ?? governor;
|
|
644
|
+
const probe = group.platform ?? platform;
|
|
560
645
|
return source.descriptor.kind === "texture" ? {
|
|
561
646
|
configuredSource: "texture",
|
|
562
|
-
platform,
|
|
647
|
+
platform: probe,
|
|
563
648
|
source: source.descriptor.probe,
|
|
564
649
|
governor: pressure,
|
|
565
650
|
hint
|
|
566
|
-
} : { configuredSource: "dom", platform, governor: pressure, hint };
|
|
651
|
+
} : { configuredSource: "dom", platform: probe, governor: pressure, hint };
|
|
567
652
|
}
|
|
568
653
|
const paddingOf = (group) => group.descriptor.samplingPadding ?? DEFAULT_GROUP_SAMPLING.samplingPadding;
|
|
569
654
|
function samplingOf(group) {
|
|
@@ -659,6 +744,7 @@ function createGlassScene(options) {
|
|
|
659
744
|
registerGlassNode(descriptor) {
|
|
660
745
|
if (nodes.has(descriptor.id)) throw duplicate("glass node", descriptor.id);
|
|
661
746
|
requireGroup(descriptor.groupId);
|
|
747
|
+
requireConcentricParent(descriptor);
|
|
662
748
|
guardFrozenScene(descriptor.id);
|
|
663
749
|
nodes.set(descriptor.id, { descriptor });
|
|
664
750
|
},
|
|
@@ -666,12 +752,20 @@ function createGlassScene(options) {
|
|
|
666
752
|
const record = requireNode(id);
|
|
667
753
|
const descriptor = applyPatch(record.descriptor, patch);
|
|
668
754
|
requireGroup(descriptor.groupId);
|
|
755
|
+
requireConcentricParent(descriptor);
|
|
669
756
|
guardFrozenScene(id);
|
|
670
757
|
nodes.set(id, { ...record, descriptor });
|
|
671
758
|
},
|
|
672
759
|
removeGlassNode(id) {
|
|
673
760
|
requireNode(id);
|
|
674
761
|
guardFrozenScene(id);
|
|
762
|
+
const children = concentricChildrenOf(id);
|
|
763
|
+
if (children.length > 0) {
|
|
764
|
+
throw new GlassSceneError(
|
|
765
|
+
"in-use",
|
|
766
|
+
`Glass node "${id}" is the concentric parent of ${children.map((node) => `"${node.descriptor.id}"`).join(", ")}. A child drawn as a level set of this field has no shape of its own once it is gone \u2014 remove the children first, or clear their \`concentricOf\`.`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
675
769
|
nodes.delete(id);
|
|
676
770
|
},
|
|
677
771
|
glassNode(id) {
|
|
@@ -690,8 +784,13 @@ function createGlassScene(options) {
|
|
|
690
784
|
}
|
|
691
785
|
nodes.set(id, { ...record, bounds, ...clip === void 0 ? {} : { clip } });
|
|
692
786
|
},
|
|
693
|
-
setPlatformProbe(probe) {
|
|
694
|
-
|
|
787
|
+
setPlatformProbe(probe, groupId) {
|
|
788
|
+
if (groupId === void 0) {
|
|
789
|
+
platform = probe;
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const record = requireGroup(groupId);
|
|
793
|
+
groups.set(groupId, { ...record, platform: probe });
|
|
695
794
|
},
|
|
696
795
|
setSourceProbe(sourceId, probe) {
|
|
697
796
|
const record = requireSource(sourceId);
|
|
@@ -790,14 +889,21 @@ function createGlassScene(options) {
|
|
|
790
889
|
resolvedGroups.push({ groupId, state, hint, foreground, sampling: samplingOf(group) });
|
|
791
890
|
const profile = group.descriptor.material ?? { variant: "regular" };
|
|
792
891
|
const members = nodesOfGroup(groupId);
|
|
892
|
+
const tinted = [];
|
|
793
893
|
for (const node of members) {
|
|
794
894
|
const nodeId = node.descriptor.id;
|
|
895
|
+
const declaredTint = node.descriptor.tint === void 0 ? profile.tint : node.descriptor.tint;
|
|
795
896
|
const material = resolveMaterial({
|
|
796
897
|
variant: node.descriptor.variant ?? profile.variant,
|
|
797
898
|
...profile.dimming === void 0 ? {} : { dimming: profile.dimming },
|
|
899
|
+
...declaredTint === void 0 || declaredTint === null ? {} : { tint: declaredTint },
|
|
798
900
|
nodeId,
|
|
799
901
|
diagnostics
|
|
800
902
|
});
|
|
903
|
+
tinted.push({
|
|
904
|
+
nodeId,
|
|
905
|
+
...material.tint === void 0 ? {} : { tint: material.tint }
|
|
906
|
+
});
|
|
801
907
|
resolvedNodes.push({
|
|
802
908
|
nodeId,
|
|
803
909
|
groupId,
|
|
@@ -817,6 +923,7 @@ function createGlassScene(options) {
|
|
|
817
923
|
})),
|
|
818
924
|
diagnostics
|
|
819
925
|
});
|
|
926
|
+
checkTintMixing({ groupId, members: tinted, diagnostics });
|
|
820
927
|
}
|
|
821
928
|
}
|
|
822
929
|
for (const group of settled) groups.set(group.descriptor.id, group);
|
|
@@ -831,16 +938,18 @@ function createGlassScene(options) {
|
|
|
831
938
|
if (!devMode) return [];
|
|
832
939
|
const measured = [...nodes.values()].filter(
|
|
833
940
|
(node) => node.bounds !== void 0
|
|
834
|
-
);
|
|
941
|
+
).map((node) => ({ node, visible: clipRect(node.bounds, node.clip) }));
|
|
835
942
|
const overlaps = [];
|
|
836
943
|
for (let i = 0; i < measured.length; i += 1) {
|
|
837
944
|
for (let j = i + 1; j < measured.length; j += 1) {
|
|
838
|
-
const a = measured[i];
|
|
839
|
-
const b = measured[j];
|
|
945
|
+
const a = measured[i]?.node;
|
|
946
|
+
const b = measured[j]?.node;
|
|
840
947
|
if (a === void 0 || b === void 0) continue;
|
|
841
948
|
const plane = a.descriptor.zSlot.plane;
|
|
842
949
|
if (plane !== b.descriptor.zSlot.plane) continue;
|
|
843
|
-
if (!rectsOverlap(a.bounds, b.bounds))
|
|
950
|
+
if (!rectsOverlap(measured[i]?.visible ?? a.bounds, measured[j]?.visible ?? b.bounds)) {
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
844
953
|
const nodeIds = [a.descriptor.id, b.descriptor.id];
|
|
845
954
|
overlaps.push({ plane, nodeIds });
|
|
846
955
|
diagnostics.report({
|
|
@@ -863,12 +972,14 @@ function createGlassScene(options) {
|
|
|
863
972
|
for (const node of nodesOfGroup(groupId)) {
|
|
864
973
|
const { bounds } = node;
|
|
865
974
|
if (bounds === void 0) continue;
|
|
975
|
+
const visible = clipRect(bounds, node.clip);
|
|
976
|
+
if (visible.width <= 0 || visible.height <= 0) continue;
|
|
866
977
|
const plane = node.descriptor.zSlot.plane;
|
|
867
978
|
const grown = byPlane.get(plane);
|
|
868
|
-
byPlane.set(plane, grown === void 0 ?
|
|
979
|
+
byPlane.set(plane, grown === void 0 ? visible : unionRect(grown, visible));
|
|
869
980
|
}
|
|
870
981
|
for (const [plane, union] of byPlane) {
|
|
871
|
-
boxes.push({ groupId, plane, box: inflateRect(union, padding) });
|
|
982
|
+
boxes.push({ groupId, plane, box: inflateRect(union, padding), clipUnion: union });
|
|
872
983
|
}
|
|
873
984
|
}
|
|
874
985
|
const overlaps = [];
|
|
@@ -878,14 +989,14 @@ function createGlassScene(options) {
|
|
|
878
989
|
const b = boxes[j];
|
|
879
990
|
if (a === void 0 || b === void 0) continue;
|
|
880
991
|
if (a.plane !== b.plane || a.groupId === b.groupId) continue;
|
|
881
|
-
if (!rectsOverlap(a.box, b.box)) continue;
|
|
992
|
+
if (!rectsOverlap(a.box, b.clipUnion) && !rectsOverlap(b.box, a.clipUnion)) continue;
|
|
882
993
|
const groupIds = [a.groupId, b.groupId];
|
|
883
994
|
overlaps.push({ plane: a.plane, groupIds });
|
|
884
995
|
diagnostics.report({
|
|
885
996
|
code: "group-proxy-overlap",
|
|
886
997
|
severity: "warning",
|
|
887
998
|
subjects: [...groupIds],
|
|
888
|
-
message: `Groups "${groupIds[0]}" and "${groupIds[1]}" sit close enough in the "${a.plane}" plane that
|
|
999
|
+
message: `Groups "${groupIds[0]}" and "${groupIds[1]}" sit close enough in the "${a.plane}" plane that one group's padded backdrop proxy samples the pixels the other group paints, and X1 says the filter then applies twice over them \u2014 paint-order dependent, and steeply distance-dependent: measured at most 3/255 at a 1.5\u03C3 gap, mean 0.43 / max 4 at 1\u03C3, mean 2.56 / max 15 at 0.25\u03C3, and byte-identical zero once the gap reaches the padding. mergeDistance cannot help: it only unions members inside one group. Either put these surfaces in one group so they share a proxy, or separate them by at least the larger group's samplingPadding.`
|
|
889
1000
|
});
|
|
890
1001
|
}
|
|
891
1002
|
}
|
|
@@ -981,7 +1092,7 @@ function isHealthy(state) {
|
|
|
981
1092
|
|
|
982
1093
|
// src/renderer-seam.ts
|
|
983
1094
|
async function loadWebGPURendererModule() {
|
|
984
|
-
return import('./dist-
|
|
1095
|
+
return import('./dist-RQ4ZMA3D.js');
|
|
985
1096
|
}
|
|
986
1097
|
async function loadWebGPURenderer() {
|
|
987
1098
|
const { createWebGPURenderer } = await loadWebGPURendererModule();
|
|
@@ -996,6 +1107,6 @@ var VITREA_CONTRACTS = {
|
|
|
996
1107
|
};
|
|
997
1108
|
var RENDERER_TIERS = ["webgpu", "css"];
|
|
998
1109
|
|
|
999
|
-
export { ACCESSIBILITY_BEHAVIOR_TABLE, ACCESSIBILITY_FLAGS, ACCESSIBILITY_PRECEDENCE, DEFAULT_BACKDROP_RESOLUTION, DEFAULT_CLEAR_DIMMING, DEFAULT_GROUP_SAMPLING, DEMOTION_REASONS, DEMOTION_RECOVERY, DIAGNOSTIC_CODES, FOREGROUND_MODES, FRAME_PHASES, GLASS_PLANES, GOVERNOR_PRESSURES, GlassSceneError, HINT_AVAILABILITIES, MATERIAL_VARIANTS, NOMINAL_ACCESSIBILITY_POLICY, OVERRIDABLE_ACCESSIBILITY_FLAGS, RENDERER_TIERS, SAMPLED_ASYNC_DEFAULTS, SAMPLED_ASYNC_RATE_LIMITS, VITREA_CONTRACTS, WEBGPU_AVAILABILITIES, checkVariantMixing, classifyStateChange, compareZSlot, createDiagnosticsChannel, createFrameScheduler, createGlassScene, defaultForegroundAdaptation, inflateRect, isHealthy, loadWebGPURenderer, loadWebGPURendererModule, rectsOverlap, resolveAccessibilityPolicy, resolveBackdropHint, resolveForegroundAdaptation, resolveGlassGroupState, resolveMaterial, unionRect };
|
|
1110
|
+
export { ACCESSIBILITY_BEHAVIOR_TABLE, ACCESSIBILITY_FLAGS, ACCESSIBILITY_PRECEDENCE, DEFAULT_BACKDROP_RESOLUTION, DEFAULT_CLEAR_DIMMING, DEFAULT_GROUP_SAMPLING, DEMOTION_REASONS, DEMOTION_RECOVERY, DIAGNOSTIC_CODES, FOREGROUND_MODES, FRAME_PHASES, GLASS_PLANES, GOVERNOR_PRESSURES, GlassSceneError, HINT_AVAILABILITIES, MATERIAL_VARIANTS, NOMINAL_ACCESSIBILITY_POLICY, OVERRIDABLE_ACCESSIBILITY_FLAGS, RENDERER_TIERS, SAMPLED_ASYNC_DEFAULTS, SAMPLED_ASYNC_RATE_LIMITS, VITREA_CONTRACTS, WEBGPU_AVAILABILITIES, checkTintMixing, checkVariantMixing, classifyStateChange, clipRect, compareZSlot, createDiagnosticsChannel, createFrameScheduler, createGlassScene, defaultForegroundAdaptation, glassTint, inflateRect, isHealthy, loadWebGPURenderer, loadWebGPURendererModule, rectsOverlap, resolveAccessibilityPolicy, resolveBackdropHint, resolveForegroundAdaptation, resolveGlassGroupState, resolveMaterial, unionRect };
|
|
1000
1111
|
//# sourceMappingURL=index.js.map
|
|
1001
1112
|
//# sourceMappingURL=index.js.map
|