@glassly/engine 0.1.0-dev.105 → 0.1.0-dev.109
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/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +1 -0
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/effectiveCapabilities.d.ts.map +1 -1
- package/build/services/effectiveCapabilities.js +5 -1
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +1 -0
- package/build/stores/glasses.js.map +1 -1
- package/build/types/hardware.d.ts +6 -1
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +2 -0
- package/build/types/hardware.js.map +1 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +1 -0
- package/build/types/index.js.map +1 -1
- package/build/utils/display/scene/degrade.d.ts.map +1 -1
- package/build/utils/display/scene/degrade.js +4 -1
- package/build/utils/display/scene/degrade.js.map +1 -1
- package/build/utils/display/scene/differ.d.ts +5 -1
- package/build/utils/display/scene/differ.d.ts.map +1 -1
- package/build/utils/display/scene/differ.js +8 -0
- package/build/utils/display/scene/differ.js.map +1 -1
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +84 -7
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/display/scene/types.d.ts +102 -4
- package/build/utils/display/scene/types.d.ts.map +1 -1
- package/build/utils/display/scene/types.js +0 -0
- package/build/utils/display/scene/types.js.map +1 -1
- package/package.json +7 -7
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/SceneRenderer.ts +1 -0
- package/src/services/effectiveCapabilities.ts +5 -1
- package/src/stores/glasses.ts +1 -0
- package/src/types/hardware.ts +8 -1
- package/src/types/index.ts +1 -0
- package/src/utils/display/scene/degrade.ts +4 -1
- package/src/utils/display/scene/differ.ts +9 -1
- package/src/utils/display/scene/process.ts +95 -9
- package/src/utils/display/scene/types.ts +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"differ.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/differ.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAC,UAAU,EAAC,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"differ.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/differ.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene differ — matches elements between the last frame sent to the device and\n * the next scene, and classifies each element's change. Pure set arithmetic:\n * nothing in here is device-specific (spec §1).\n *\n * Matching rules (design doc §3.4.1, in order):\n * 1. same explicit `id` + same `type` ⇒ same element\n * 2. no id: same `type` + identical box ⇒ same element\n * 3. no id: same `type`, by order among the still-unmatched\n * 4. anything unmatched in prev ⇒ removed; unmatched in next ⇒ created\n *\n * Matched no-id elements INHERIT the previous element's assigned id so the\n * device-side id⇒firmware-component mapping stays stable; fresh elements get a\n * synthesized id from the caller (SceneStore counter). Synthesized ids are\n * prefixed \"~\" so they can never collide with app-supplied ids.\n */\n\nimport type {FrameElement, SceneBox, SceneChange, SceneElementType, ScenePoint, SceneTransition} from \"./types\"\nimport {boxesEqual} from \"./types\"\n\n/** A processed (clamped/wrapped) element, pre-diff: id optional, no annotation yet. */\nexport interface DiffableElement {\n id?: string\n type: SceneElementType\n box: SceneBox\n text?: string\n data?: string\n style?: FrameElement[\"style\"]\n points?: ScenePoint[]\n startDeg?: number\n endDeg?: number\n transition?: SceneTransition\n contentHash: string\n}\n\nexport interface DiffResult {\n elements: FrameElement[]\n removed: string[]\n}\n\nfunction classify(prev: FrameElement, next: DiffableElement): SceneChange {\n if (!boxesEqual(prev.box, next.box)) return \"moved\"\n if (prev.contentHash !== next.contentHash) return \"updated\"\n return \"unchanged\"\n}\n\n/**\n * Assemble the annotated FrameElement, OMITTING optional fields that are\n * undefined. A present key with an `undefined` value cannot cross the Expo\n * Android bridge — its `Map<String, Any>` converter rejects it (\"Value is\n * undefined, expected an Object\"), which surfaces as ERR_UNEXPECTED from\n * displayEvent. (iOS's `[String: Any]` bridge silently drops nils, so the\n * defect was invisible there.) Keeping the keys absent — not undefined —\n * matches the optional-field contract in types.ts.\n */\nfunction buildFrameElement(el: DiffableElement, id: string, change: SceneChange): FrameElement {\n const out: FrameElement = {id, type: el.type, box: el.box, contentHash: el.contentHash, change}\n if (el.text !== undefined) out.text = el.text\n if (el.data !== undefined) out.data = el.data\n if (el.style !== undefined) out.style = el.style\n if (el.points !== undefined) out.points = el.points\n if (el.startDeg !== undefined) out.startDeg = el.startDeg\n if (el.endDeg !== undefined) out.endDeg = el.endDeg\n if (el.transition !== undefined) out.transition = el.transition\n return out\n}\n\n/**\n * Diff `next` against `prev` (the last frame sent for this app/view/device;\n * empty array for a fresh scene). `nextSyntheticId` mints ids for elements the\n * app didn't name — must be stable per (app, view) across calls (SceneStore).\n */\nexport function diffScene(\n prev: readonly FrameElement[],\n next: readonly DiffableElement[],\n nextSyntheticId: () => string,\n): DiffResult {\n const prevUnmatched = new Set(prev.map((_, i) => i))\n const prevByExplicitId = new Map<string, number>()\n prev.forEach((el, i) => {\n if (!el.id.startsWith(\"~\")) prevByExplicitId.set(`${el.type}:${el.id}`, i)\n })\n\n // matches[nextIndex] = prevIndex | undefined\n const matches: (number | undefined)[] = new Array(next.length).fill(undefined)\n\n // Pass 1: explicit id + type.\n next.forEach((el, i) => {\n if (!el.id) return\n const j = prevByExplicitId.get(`${el.type}:${el.id}`)\n if (j !== undefined && prevUnmatched.has(j)) {\n matches[i] = j\n prevUnmatched.delete(j)\n }\n })\n\n // Pass 2: no-id elements match a no-id prev element of the same type with an\n // identical box.\n next.forEach((el, i) => {\n if (el.id || matches[i] !== undefined) return\n for (const j of prevUnmatched) {\n const p = prev[j]\n if (p.id.startsWith(\"~\") && p.type === el.type && boxesEqual(p.box, el.box)) {\n matches[i] = j\n prevUnmatched.delete(j)\n break\n }\n }\n })\n\n // Pass 3: remaining no-id elements match remaining no-id prev of the same\n // type in order (k-th unmatched text ↔ k-th unmatched text).\n next.forEach((el, i) => {\n if (el.id || matches[i] !== undefined) return\n for (const j of prevUnmatched) {\n const p = prev[j]\n if (p.id.startsWith(\"~\") && p.type === el.type) {\n matches[i] = j\n prevUnmatched.delete(j)\n break\n }\n }\n })\n\n const elements: FrameElement[] = next.map((el, i) => {\n const j = matches[i]\n if (j !== undefined) {\n const p = prev[j]\n return buildFrameElement(el, el.id ?? p.id, classify(p, el))\n }\n return buildFrameElement(el, el.id ?? nextSyntheticId(), \"created\")\n })\n\n const removed = [...prevUnmatched].map((j) => prev[j].id)\n return {elements, removed}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAGV,wBAAwB,EACxB,iBAAiB,EAIlB,MAAM,SAAS,CAAA;AAGhB,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAsDD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,IAAI,EAAE,wBAAwB,EAC9B,OAAO,EAAE,cAAc,GACtB,cAAc,CAgNhB"}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { TextMeasurer } from "../measurer/TextMeasurer";
|
|
11
11
|
import { TextWrapper } from "../wrapper/TextWrapper";
|
|
12
|
-
import { elementContentHash } from "./types";
|
|
12
|
+
import { POINT_SHAPE_ARITY, POINT_SHAPE_TYPES, elementContentHash } from "./types";
|
|
13
13
|
/** Reporting id for an element the app may not have named. */
|
|
14
14
|
function reportId(el, index) {
|
|
15
15
|
return el.id ?? `${el.type}[${index}]`;
|
|
@@ -23,6 +23,35 @@ function clampBox(box, width, height) {
|
|
|
23
23
|
return null;
|
|
24
24
|
return { x: x1, y: y1, w: x2 - x1, h: y2 - y1 };
|
|
25
25
|
}
|
|
26
|
+
const isFinite = (n) => typeof n === "number" && Number.isFinite(n);
|
|
27
|
+
/** Integer points, or null when the count/values are invalid for the type. */
|
|
28
|
+
function validPoints(el) {
|
|
29
|
+
const raw = el.points;
|
|
30
|
+
if (!Array.isArray(raw) || !(POINT_SHAPE_ARITY[el.type] ?? []).includes(raw.length))
|
|
31
|
+
return null;
|
|
32
|
+
const points = [];
|
|
33
|
+
for (const p of raw) {
|
|
34
|
+
if (!p || typeof p !== "object" || !isFinite(p.x) || !isFinite(p.y))
|
|
35
|
+
return null;
|
|
36
|
+
points.push({ x: Math.floor(p.x), y: Math.floor(p.y) });
|
|
37
|
+
}
|
|
38
|
+
return points;
|
|
39
|
+
}
|
|
40
|
+
function pointsBox(points) {
|
|
41
|
+
const xs = points.map((p) => p.x);
|
|
42
|
+
const ys = points.map((p) => p.y);
|
|
43
|
+
const x = Math.min(...xs);
|
|
44
|
+
const y = Math.min(...ys);
|
|
45
|
+
return { x, y, w: Math.max(...xs) - x + 1, h: Math.max(...ys) - y + 1 };
|
|
46
|
+
}
|
|
47
|
+
function validTransition(t) {
|
|
48
|
+
if (!t || typeof t !== "object")
|
|
49
|
+
return undefined;
|
|
50
|
+
const { durationMs, easing } = t;
|
|
51
|
+
if (!isFinite(durationMs) || durationMs <= 0)
|
|
52
|
+
return undefined;
|
|
53
|
+
return easing ? { durationMs, easing } : { durationMs };
|
|
54
|
+
}
|
|
26
55
|
function boxShrunk(orig, clamped) {
|
|
27
56
|
return (clamped.x !== Math.floor(orig.x) ||
|
|
28
57
|
clamped.y !== Math.floor(orig.y) ||
|
|
@@ -57,15 +86,36 @@ export function processScene(input, caps, profile) {
|
|
|
57
86
|
const seenIds = new Set();
|
|
58
87
|
const valid = [];
|
|
59
88
|
input.forEach((raw, index) => {
|
|
60
|
-
if (!raw || typeof raw !== "object"
|
|
89
|
+
if (!raw || typeof raw !== "object") {
|
|
61
90
|
dropped.push(reportId(raw ?? { type: "text" }, index));
|
|
62
91
|
degraded = true;
|
|
63
92
|
return;
|
|
64
93
|
}
|
|
94
|
+
let points;
|
|
95
|
+
if (POINT_SHAPE_TYPES.includes(raw.type)) {
|
|
96
|
+
const valid = validPoints(raw);
|
|
97
|
+
if (!valid) {
|
|
98
|
+
dropped.push(reportId(raw, index));
|
|
99
|
+
degraded = true;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
points = valid;
|
|
103
|
+
raw = { ...raw, box: pointsBox(valid) };
|
|
104
|
+
}
|
|
105
|
+
if (!raw.box) {
|
|
106
|
+
dropped.push(reportId(raw, index));
|
|
107
|
+
degraded = true;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
65
110
|
// Non-finite box numbers (NaN/±Infinity/non-number) would survive the
|
|
66
111
|
// Math.* clamp below and poison the frame all the way to native ints.
|
|
67
112
|
const b = raw.box;
|
|
68
|
-
if (![b.x, b.y, b.w, b.h].every(
|
|
113
|
+
if (![b.x, b.y, b.w, b.h].every(isFinite)) {
|
|
114
|
+
dropped.push(reportId(raw, index));
|
|
115
|
+
degraded = true;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if ((raw.type === "arc" || raw.type === "pie") && !(isFinite(raw.startDeg) && isFinite(raw.endDeg))) {
|
|
69
119
|
dropped.push(reportId(raw, index));
|
|
70
120
|
degraded = true;
|
|
71
121
|
return;
|
|
@@ -83,22 +133,45 @@ export function processScene(input, caps, profile) {
|
|
|
83
133
|
}
|
|
84
134
|
seenIds.add(key);
|
|
85
135
|
}
|
|
86
|
-
valid.push({ el, index });
|
|
136
|
+
valid.push({ el, index, box: el.box, points });
|
|
87
137
|
});
|
|
88
138
|
// Clamp + per-type limits, then budget in array order.
|
|
89
139
|
let textBudget = caps.maxTextElements;
|
|
90
140
|
let imageBudget = caps.maxImageElements;
|
|
91
141
|
const out = [];
|
|
92
|
-
for (const { el, index } of valid) {
|
|
93
|
-
const
|
|
142
|
+
for (const { el, index, box, points } of valid) {
|
|
143
|
+
const transition = validTransition(el.transition);
|
|
144
|
+
const clamped = clampBox(box, caps.width, caps.height);
|
|
94
145
|
if (!clamped) {
|
|
95
146
|
dropped.push(reportId(el, index));
|
|
96
147
|
degraded = true;
|
|
97
148
|
continue;
|
|
98
149
|
}
|
|
99
|
-
const shrunk = boxShrunk(
|
|
150
|
+
const shrunk = boxShrunk(box, clamped);
|
|
100
151
|
if (shrunk)
|
|
101
152
|
degraded = true;
|
|
153
|
+
if (el.type !== "text" && el.type !== "image" && el.type !== "rect") {
|
|
154
|
+
// Vector shapes: only devices that draw them get them; the device clips
|
|
155
|
+
// point shapes itself, so an off-canvas point is not a drop.
|
|
156
|
+
if (!caps.shapes.includes(el.type) || textBudget <= 0) {
|
|
157
|
+
dropped.push(reportId(el, index));
|
|
158
|
+
degraded = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
textBudget--;
|
|
162
|
+
const angles = el.type === "arc" || el.type === "pie" ? { startDeg: el.startDeg, endDeg: el.endDeg } : {};
|
|
163
|
+
out.push({
|
|
164
|
+
id: el.id,
|
|
165
|
+
type: el.type,
|
|
166
|
+
box: clamped,
|
|
167
|
+
style: el.style,
|
|
168
|
+
...(points ? { points } : {}),
|
|
169
|
+
...angles,
|
|
170
|
+
...(transition ? { transition } : {}),
|
|
171
|
+
contentHash: elementContentHash({ type: el.type, style: el.style, points, ...angles }),
|
|
172
|
+
});
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
102
175
|
if (el.type === "image") {
|
|
103
176
|
// Images: a shrunk box can't be honored (we don't crop pixels host-side)
|
|
104
177
|
// and per-image device limits are box-level (the firmware allocates the
|
|
@@ -124,6 +197,7 @@ export function processScene(input, caps, profile) {
|
|
|
124
197
|
type: "image",
|
|
125
198
|
box: clamped,
|
|
126
199
|
data: el.data,
|
|
200
|
+
...(transition ? { transition } : {}),
|
|
127
201
|
contentHash: elementContentHash({ type: "image", data: el.data }),
|
|
128
202
|
});
|
|
129
203
|
continue;
|
|
@@ -141,6 +215,7 @@ export function processScene(input, caps, profile) {
|
|
|
141
215
|
type: "rect",
|
|
142
216
|
box: clamped,
|
|
143
217
|
style: el.style,
|
|
218
|
+
...(transition ? { transition } : {}),
|
|
144
219
|
contentHash: elementContentHash({ type: "rect", style: el.style }),
|
|
145
220
|
});
|
|
146
221
|
continue;
|
|
@@ -161,6 +236,7 @@ export function processScene(input, caps, profile) {
|
|
|
161
236
|
box: clamped,
|
|
162
237
|
text: monoText,
|
|
163
238
|
style: el.style,
|
|
239
|
+
...(transition ? { transition } : {}),
|
|
164
240
|
contentHash: elementContentHash({ type: "text", text: monoText, style: el.style }),
|
|
165
241
|
});
|
|
166
242
|
continue;
|
|
@@ -194,6 +270,7 @@ export function processScene(input, caps, profile) {
|
|
|
194
270
|
box: clamped,
|
|
195
271
|
text: wrappedText,
|
|
196
272
|
style: el.style,
|
|
273
|
+
...(transition ? { transition } : {}),
|
|
197
274
|
contentHash: elementContentHash({ type: "text", text: wrappedText, style: el.style }),
|
|
198
275
|
});
|
|
199
276
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene processing — validate → clamp → budget → wrap. Everything here is\n * generic; device variation enters only through the capabilities block and the\n * display profile (data, not code). Spec §5.\n *\n * Never rejects a frame: offending elements are dropped per-element and\n * reported via `dropped` / `degraded` (design doc §3.4.6; spec §4 refines the\n * design doc's image \"reject\" to per-element drop).\n */\n\nimport {TextMeasurer} from \"../measurer/TextMeasurer\"\nimport {TextWrapper} from \"../wrapper/TextWrapper\"\nimport type {DisplayProfile} from \"../profiles/types\"\nimport type {DiffableElement} from \"./differ\"\nimport type {SceneBox, SceneDisplayCapabilities, SceneElementInput, SceneTextStyle} from \"./types\"\nimport {elementContentHash} from \"./types\"\n\nexport interface ProcessedScene {\n elements: DiffableElement[]\n degraded: boolean\n dropped: string[]\n}\n\n/** Reporting id for an element the app may not have named. */\nfunction reportId(el: SceneElementInput, index: number): string {\n return el.id ?? `${el.type}[${index}]`\n}\n\nfunction clampBox(box: SceneBox, width: number, height: number): SceneBox | null {\n const x1 = Math.max(0, Math.floor(box.x))\n const y1 = Math.max(0, Math.floor(box.y))\n const x2 = Math.min(width, Math.floor(box.x) + Math.max(0, Math.floor(box.w)))\n const y2 = Math.min(height, Math.floor(box.y) + Math.max(0, Math.floor(box.h)))\n if (x2 <= x1 || y2 <= y1) return null\n return {x: x1, y: y1, w: x2 - x1, h: y2 - y1}\n}\n\nfunction boxShrunk(orig: SceneBox, clamped: SceneBox): boolean {\n return (\n clamped.x !== Math.floor(orig.x) ||\n clamped.y !== Math.floor(orig.y) ||\n clamped.w !== Math.floor(orig.w) ||\n clamped.h !== Math.floor(orig.h)\n )\n}\n\n/**\n * Line height for box-height→line-count math — ONLY when the profile declares\n * a calibrated `lineHeightPx`. Deriving one from full-canvas numbers proved\n * wrong on hardware (288/8=36px clipped the G2 nav instruction to one line;\n * the real container line height is smaller). Without calibration we return\n * null and the wrap step skips host-side height clipping entirely — text wraps\n * to the box WIDTH and the firmware clips vertically in-box, which is exactly\n * the legacy behavior.\n */\nexport function profileLineHeightPx(profile: DisplayProfile, _canvasHeight: number): number | null {\n return profile.lineHeightPx ?? null\n}\n\n/**\n * Process a raw scene against a device's capabilities + profile. Output is\n * diff-ready (text pre-wrapped, boxes clamped, content hashed) and reflects\n * exactly what will be sent — the diff baseline is post-processed by design\n * (spec §4).\n */\nexport function processScene(\n input: readonly SceneElementInput[],\n caps: SceneDisplayCapabilities,\n profile: DisplayProfile,\n): ProcessedScene {\n const dropped: string[] = []\n let degraded = false\n\n const measurer = new TextMeasurer(profile)\n const wrapper = new TextWrapper(measurer)\n const lineHeight = profileLineHeightPx(profile, caps.height)\n\n // Validate + dedupe explicit ids (first occurrence wins; dupes are dev error).\n const seenIds = new Set<string>()\n const valid: {el: SceneElementInput; index: number}[] = []\n input.forEach((raw, index) => {\n if (!raw || typeof raw !== \"object\" || !raw.box) {\n dropped.push(reportId(raw ?? ({type: \"text\"} as SceneElementInput), index))\n degraded = true\n return\n }\n // Non-finite box numbers (NaN/±Infinity/non-number) would survive the\n // Math.* clamp below and poison the frame all the way to native ints.\n const b = raw.box\n if (![b.x, b.y, b.w, b.h].every((n) => typeof n === \"number\" && Number.isFinite(n))) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n // Leading \"~\" is the differ's synthetic-id namespace — an app id there\n // would never match across frames (re-created every push). Normalize by\n // stripping; the rewrite is deterministic, so diffing stays stable.\n const el = raw.id?.startsWith(\"~\") ? {...raw, id: raw.id.replace(/^~+/, \"\") || undefined} : raw\n if (el.id) {\n const key = `${el.type}:${el.id}`\n if (seenIds.has(key)) {\n dropped.push(reportId(el, index))\n degraded = true\n return\n }\n seenIds.add(key)\n }\n valid.push({el, index})\n })\n\n // Clamp + per-type limits, then budget in array order.\n let textBudget = caps.maxTextElements\n let imageBudget = caps.maxImageElements\n const out: DiffableElement[] = []\n\n for (const {el, index} of valid) {\n const clamped = clampBox(el.box, caps.width, caps.height)\n if (!clamped) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const shrunk = boxShrunk(el.box, clamped)\n if (shrunk) degraded = true\n\n if (el.type === \"image\") {\n // Images: a shrunk box can't be honored (we don't crop pixels host-side)\n // and per-image device limits are box-level (the firmware allocates the\n // component from the box). SGCs scale pixels to the box (phone-side\n // scaling — never on glasses).\n if (shrunk) {\n dropped.push(reportId(el, index))\n continue\n }\n if (caps.maxImagePx && (clamped.w > caps.maxImagePx.width || clamped.h > caps.maxImagePx.height)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n if (imageBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n imageBudget--\n out.push({\n id: el.id,\n type: \"image\",\n box: clamped,\n data: el.data,\n contentHash: elementContentHash({type: \"image\", data: el.data}),\n })\n continue\n }\n\n // text + rect share the text-container budget (design doc §3.4.6).\n if (textBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n textBudget--\n\n if (el.type === \"rect\") {\n out.push({\n id: el.id,\n type: \"rect\",\n box: clamped,\n style: el.style,\n contentHash: elementContentHash({type: \"rect\", style: el.style}),\n })\n continue\n }\n\n // Text: wrap on the phone into the (clamped) box. The box then carries\n // pre-wrapped text; firmware in-box wrap is a fallback, not the mechanism.\n // Height clipping only applies with a CALIBRATED line height — otherwise\n // the firmware clips vertically in-box (legacy behavior).\n const style: SceneTextStyle = el.style ?? {}\n if (style.font === \"mono\" || style.font === \"builtin\") {\n // Grid text is laid out by the app (cols × rows) and builtin text by the\n // glasses' own font engine; the host's proportional wrap metrics don't\n // apply to either. Pass lines through and let the device clip.\n const monoText = el.text ?? \"\"\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: monoText,\n style: el.style,\n contentHash: elementContentHash({type: \"text\", text: monoText, style: el.style}),\n })\n continue\n }\n const maxLines = lineHeight ? Math.max(1, Math.floor(clamped.h / lineHeight)) : profile.maxLines\n const result = wrapper.wrap(el.text ?? \"\", {\n maxWidthPx: clamped.w,\n maxLines,\n ...(style.breakMode ? {breakMode: style.breakMode} : {}),\n })\n let lines = result.lines\n if (result.truncated) {\n degraded = true\n if (style.overflow === \"ellipsis\" && lines.length > 0) {\n // Trim the last line until it fits WITH the ellipsis appended —\n // otherwise the extra glyph can overflow the clamped box width.\n let last = lines[lines.length - 1]\n while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, clamped.w)) {\n last = last.slice(0, -1)\n }\n // A box narrower than the ellipsis glyph itself gets an empty line —\n // never emit a line wider than the clamped box.\n const ellipsized = last.length > 0 || measurer.fitsInWidth(\"…\", clamped.w) ? `${last}…` : \"\"\n lines = [...lines.slice(0, -1), ellipsized]\n }\n }\n const wrappedText = lines.join(\"\\n\")\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: wrappedText,\n style: el.style,\n contentHash: elementContentHash({type: \"text\", text: wrappedText, style: el.style}),\n })\n }\n\n return {elements: out, degraded, dropped}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Scene processing — validate → clamp → budget → wrap. Everything here is\n * generic; device variation enters only through the capabilities block and the\n * display profile (data, not code). Spec §5.\n *\n * Never rejects a frame: offending elements are dropped per-element and\n * reported via `dropped` / `degraded` (design doc §3.4.6; spec §4 refines the\n * design doc's image \"reject\" to per-element drop).\n */\n\nimport {TextMeasurer} from \"../measurer/TextMeasurer\"\nimport {TextWrapper} from \"../wrapper/TextWrapper\"\nimport type {DisplayProfile} from \"../profiles/types\"\nimport type {DiffableElement} from \"./differ\"\nimport type {\n SceneBox,\n SceneBoxInput,\n SceneDisplayCapabilities,\n SceneElementInput,\n ScenePoint,\n SceneTextStyle,\n SceneTransition,\n} from \"./types\"\nimport {POINT_SHAPE_ARITY, POINT_SHAPE_TYPES, elementContentHash} from \"./types\"\n\nexport interface ProcessedScene {\n elements: DiffableElement[]\n degraded: boolean\n dropped: string[]\n}\n\n/** Reporting id for an element the app may not have named. */\nfunction reportId(el: SceneElementInput, index: number): string {\n return el.id ?? `${el.type}[${index}]`\n}\n\nfunction clampBox(box: SceneBox, width: number, height: number): SceneBox | null {\n const x1 = Math.max(0, Math.floor(box.x))\n const y1 = Math.max(0, Math.floor(box.y))\n const x2 = Math.min(width, Math.floor(box.x) + Math.max(0, Math.floor(box.w)))\n const y2 = Math.min(height, Math.floor(box.y) + Math.max(0, Math.floor(box.h)))\n if (x2 <= x1 || y2 <= y1) return null\n return {x: x1, y: y1, w: x2 - x1, h: y2 - y1}\n}\n\nconst isFinite = (n: unknown): n is number => typeof n === \"number\" && Number.isFinite(n)\n\n/** Integer points, or null when the count/values are invalid for the type. */\nfunction validPoints(el: SceneElementInput): ScenePoint[] | null {\n const raw = (el as {points?: unknown}).points\n if (!Array.isArray(raw) || !(POINT_SHAPE_ARITY[el.type] ?? []).includes(raw.length)) return null\n const points: ScenePoint[] = []\n for (const p of raw) {\n if (!p || typeof p !== \"object\" || !isFinite((p as ScenePoint).x) || !isFinite((p as ScenePoint).y)) return null\n points.push({x: Math.floor((p as ScenePoint).x), y: Math.floor((p as ScenePoint).y)})\n }\n return points\n}\n\nfunction pointsBox(points: ScenePoint[]): SceneBox {\n const xs = points.map((p) => p.x)\n const ys = points.map((p) => p.y)\n const x = Math.min(...xs)\n const y = Math.min(...ys)\n return {x, y, w: Math.max(...xs) - x + 1, h: Math.max(...ys) - y + 1}\n}\n\nfunction validTransition(t: unknown): SceneTransition | undefined {\n if (!t || typeof t !== \"object\") return undefined\n const {durationMs, easing} = t as SceneTransition\n if (!isFinite(durationMs) || durationMs <= 0) return undefined\n return easing ? {durationMs, easing} : {durationMs}\n}\n\nfunction boxShrunk(orig: SceneBox, clamped: SceneBox): boolean {\n return (\n clamped.x !== Math.floor(orig.x) ||\n clamped.y !== Math.floor(orig.y) ||\n clamped.w !== Math.floor(orig.w) ||\n clamped.h !== Math.floor(orig.h)\n )\n}\n\n/**\n * Line height for box-height→line-count math — ONLY when the profile declares\n * a calibrated `lineHeightPx`. Deriving one from full-canvas numbers proved\n * wrong on hardware (288/8=36px clipped the G2 nav instruction to one line;\n * the real container line height is smaller). Without calibration we return\n * null and the wrap step skips host-side height clipping entirely — text wraps\n * to the box WIDTH and the firmware clips vertically in-box, which is exactly\n * the legacy behavior.\n */\nexport function profileLineHeightPx(profile: DisplayProfile, _canvasHeight: number): number | null {\n return profile.lineHeightPx ?? null\n}\n\n/**\n * Process a raw scene against a device's capabilities + profile. Output is\n * diff-ready (text pre-wrapped, boxes clamped, content hashed) and reflects\n * exactly what will be sent — the diff baseline is post-processed by design\n * (spec §4).\n */\nexport function processScene(\n input: readonly SceneElementInput[],\n caps: SceneDisplayCapabilities,\n profile: DisplayProfile,\n): ProcessedScene {\n const dropped: string[] = []\n let degraded = false\n\n const measurer = new TextMeasurer(profile)\n const wrapper = new TextWrapper(measurer)\n const lineHeight = profileLineHeightPx(profile, caps.height)\n\n // Validate + dedupe explicit ids (first occurrence wins; dupes are dev error).\n const seenIds = new Set<string>()\n const valid: {el: SceneElementInput; index: number; box: SceneBoxInput; points?: ScenePoint[]}[] = []\n input.forEach((raw, index) => {\n if (!raw || typeof raw !== \"object\") {\n dropped.push(reportId(raw ?? ({type: \"text\"} as SceneElementInput), index))\n degraded = true\n return\n }\n let points: ScenePoint[] | undefined\n if (POINT_SHAPE_TYPES.includes(raw.type)) {\n const valid = validPoints(raw)\n if (!valid) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n points = valid\n raw = {...raw, box: pointsBox(valid)} as SceneElementInput\n }\n if (!raw.box) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n // Non-finite box numbers (NaN/±Infinity/non-number) would survive the\n // Math.* clamp below and poison the frame all the way to native ints.\n const b = raw.box\n if (![b.x, b.y, b.w, b.h].every(isFinite)) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n if ((raw.type === \"arc\" || raw.type === \"pie\") && !(isFinite(raw.startDeg) && isFinite(raw.endDeg))) {\n dropped.push(reportId(raw, index))\n degraded = true\n return\n }\n // Leading \"~\" is the differ's synthetic-id namespace — an app id there\n // would never match across frames (re-created every push). Normalize by\n // stripping; the rewrite is deterministic, so diffing stays stable.\n const el = raw.id?.startsWith(\"~\") ? {...raw, id: raw.id.replace(/^~+/, \"\") || undefined} : raw\n if (el.id) {\n const key = `${el.type}:${el.id}`\n if (seenIds.has(key)) {\n dropped.push(reportId(el, index))\n degraded = true\n return\n }\n seenIds.add(key)\n }\n valid.push({el, index, box: el.box as SceneBoxInput, points})\n })\n\n // Clamp + per-type limits, then budget in array order.\n let textBudget = caps.maxTextElements\n let imageBudget = caps.maxImageElements\n const out: DiffableElement[] = []\n\n for (const {el, index, box, points} of valid) {\n const transition = validTransition((el as {transition?: unknown}).transition)\n const clamped = clampBox(box, caps.width, caps.height)\n if (!clamped) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const shrunk = boxShrunk(box, clamped)\n if (shrunk) degraded = true\n\n if (el.type !== \"text\" && el.type !== \"image\" && el.type !== \"rect\") {\n // Vector shapes: only devices that draw them get them; the device clips\n // point shapes itself, so an off-canvas point is not a drop.\n if (!caps.shapes.includes(el.type) || textBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n textBudget--\n const angles = el.type === \"arc\" || el.type === \"pie\" ? {startDeg: el.startDeg, endDeg: el.endDeg} : {}\n out.push({\n id: el.id,\n type: el.type,\n box: clamped,\n style: el.style,\n ...(points ? {points} : {}),\n ...angles,\n ...(transition ? {transition} : {}),\n contentHash: elementContentHash({type: el.type, style: el.style, points, ...angles}),\n })\n continue\n }\n\n if (el.type === \"image\") {\n // Images: a shrunk box can't be honored (we don't crop pixels host-side)\n // and per-image device limits are box-level (the firmware allocates the\n // component from the box). SGCs scale pixels to the box (phone-side\n // scaling — never on glasses).\n if (shrunk) {\n dropped.push(reportId(el, index))\n continue\n }\n if (caps.maxImagePx && (clamped.w > caps.maxImagePx.width || clamped.h > caps.maxImagePx.height)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n if (imageBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n imageBudget--\n out.push({\n id: el.id,\n type: \"image\",\n box: clamped,\n data: el.data,\n ...(transition ? {transition} : {}),\n contentHash: elementContentHash({type: \"image\", data: el.data}),\n })\n continue\n }\n\n // text + rect share the text-container budget (design doc §3.4.6).\n if (textBudget <= 0) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n textBudget--\n\n if (el.type === \"rect\") {\n out.push({\n id: el.id,\n type: \"rect\",\n box: clamped,\n style: el.style,\n ...(transition ? {transition} : {}),\n contentHash: elementContentHash({type: \"rect\", style: el.style}),\n })\n continue\n }\n\n // Text: wrap on the phone into the (clamped) box. The box then carries\n // pre-wrapped text; firmware in-box wrap is a fallback, not the mechanism.\n // Height clipping only applies with a CALIBRATED line height — otherwise\n // the firmware clips vertically in-box (legacy behavior).\n const style: SceneTextStyle = el.style ?? {}\n if (style.font === \"mono\" || style.font === \"builtin\") {\n // Grid text is laid out by the app (cols × rows) and builtin text by the\n // glasses' own font engine; the host's proportional wrap metrics don't\n // apply to either. Pass lines through and let the device clip.\n const monoText = el.text ?? \"\"\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: monoText,\n style: el.style,\n ...(transition ? {transition} : {}),\n contentHash: elementContentHash({type: \"text\", text: monoText, style: el.style}),\n })\n continue\n }\n const maxLines = lineHeight ? Math.max(1, Math.floor(clamped.h / lineHeight)) : profile.maxLines\n const result = wrapper.wrap(el.text ?? \"\", {\n maxWidthPx: clamped.w,\n maxLines,\n ...(style.breakMode ? {breakMode: style.breakMode} : {}),\n })\n let lines = result.lines\n if (result.truncated) {\n degraded = true\n if (style.overflow === \"ellipsis\" && lines.length > 0) {\n // Trim the last line until it fits WITH the ellipsis appended —\n // otherwise the extra glyph can overflow the clamped box width.\n let last = lines[lines.length - 1]\n while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, clamped.w)) {\n last = last.slice(0, -1)\n }\n // A box narrower than the ellipsis glyph itself gets an empty line —\n // never emit a line wider than the clamped box.\n const ellipsized = last.length > 0 || measurer.fitsInWidth(\"…\", clamped.w) ? `${last}…` : \"\"\n lines = [...lines.slice(0, -1), ellipsized]\n }\n }\n const wrappedText = lines.join(\"\\n\")\n out.push({\n id: el.id,\n type: \"text\",\n box: clamped,\n text: wrappedText,\n style: el.style,\n ...(transition ? {transition} : {}),\n contentHash: elementContentHash({type: \"text\", text: wrappedText, style: el.style}),\n })\n }\n\n return {elements: out, degraded, dropped}\n}\n"]}
|
|
@@ -45,7 +45,33 @@ export interface SceneTextStyle {
|
|
|
45
45
|
export interface SceneRectStyle {
|
|
46
46
|
border?: number;
|
|
47
47
|
radius?: number;
|
|
48
|
+
/** Solid fill instead of an outline. */
|
|
49
|
+
fill?: boolean;
|
|
50
|
+
/** Gray level 0..15 (15 = brightest). Default 15. */
|
|
51
|
+
color?: number;
|
|
48
52
|
}
|
|
53
|
+
/** Closed shapes (circle, triangle, quad): outline of `border` px, or filled. */
|
|
54
|
+
export interface SceneShapeStyle {
|
|
55
|
+
border?: number;
|
|
56
|
+
fill?: boolean;
|
|
57
|
+
color?: number;
|
|
58
|
+
}
|
|
59
|
+
/** Open strokes (line, bezier, arc). */
|
|
60
|
+
export interface SceneStrokeStyle {
|
|
61
|
+
width?: number;
|
|
62
|
+
color?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface ScenePoint {
|
|
65
|
+
x: number;
|
|
66
|
+
y: number;
|
|
67
|
+
}
|
|
68
|
+
export type SceneEasing = "linear" | "ease" | "ease-in" | "ease-out" | "ease-in-out";
|
|
69
|
+
/** Animate geometry changes of an element with a stable id (devices with `animation`). */
|
|
70
|
+
export interface SceneTransition {
|
|
71
|
+
durationMs: number;
|
|
72
|
+
easing?: SceneEasing;
|
|
73
|
+
}
|
|
74
|
+
export type SceneShape = "rect" | "circle" | "arc" | "pie" | "line" | "triangle" | "quad" | "bezier";
|
|
49
75
|
/** A scene element as it arrives from the miniapp SDK (pre-processing). */
|
|
50
76
|
export type SceneElementInput = {
|
|
51
77
|
type: "text";
|
|
@@ -53,18 +79,80 @@ export type SceneElementInput = {
|
|
|
53
79
|
box: SceneBoxInput;
|
|
54
80
|
text: string;
|
|
55
81
|
style?: SceneTextStyle;
|
|
82
|
+
transition?: SceneTransition;
|
|
56
83
|
} | {
|
|
57
84
|
type: "image";
|
|
58
85
|
id?: string;
|
|
59
86
|
box: SceneBoxInput;
|
|
60
87
|
data: string;
|
|
88
|
+
transition?: SceneTransition;
|
|
61
89
|
} | {
|
|
62
90
|
type: "rect";
|
|
63
91
|
id?: string;
|
|
64
92
|
box: SceneBoxInput;
|
|
65
93
|
style?: SceneRectStyle;
|
|
94
|
+
transition?: SceneTransition;
|
|
95
|
+
} | {
|
|
96
|
+
type: "circle";
|
|
97
|
+
id?: string;
|
|
98
|
+
box: SceneBoxInput;
|
|
99
|
+
style?: SceneShapeStyle;
|
|
100
|
+
transition?: SceneTransition;
|
|
101
|
+
} | {
|
|
102
|
+
type: "arc";
|
|
103
|
+
id?: string;
|
|
104
|
+
box: SceneBoxInput;
|
|
105
|
+
startDeg: number;
|
|
106
|
+
endDeg: number;
|
|
107
|
+
style?: SceneStrokeStyle;
|
|
108
|
+
transition?: SceneTransition;
|
|
109
|
+
} | {
|
|
110
|
+
type: "pie";
|
|
111
|
+
id?: string;
|
|
112
|
+
box: SceneBoxInput;
|
|
113
|
+
startDeg: number;
|
|
114
|
+
endDeg: number;
|
|
115
|
+
style?: {
|
|
116
|
+
color?: number;
|
|
117
|
+
};
|
|
118
|
+
transition?: SceneTransition;
|
|
119
|
+
} | {
|
|
120
|
+
type: "line";
|
|
121
|
+
id?: string;
|
|
122
|
+
points: ScenePoint[];
|
|
123
|
+
box?: SceneBoxInput;
|
|
124
|
+
style?: SceneStrokeStyle;
|
|
125
|
+
transition?: SceneTransition;
|
|
126
|
+
} | {
|
|
127
|
+
type: "triangle";
|
|
128
|
+
id?: string;
|
|
129
|
+
points: ScenePoint[];
|
|
130
|
+
box?: SceneBoxInput;
|
|
131
|
+
style?: SceneShapeStyle;
|
|
132
|
+
transition?: SceneTransition;
|
|
133
|
+
} | {
|
|
134
|
+
type: "quad";
|
|
135
|
+
id?: string;
|
|
136
|
+
points: ScenePoint[];
|
|
137
|
+
box?: SceneBoxInput;
|
|
138
|
+
style?: SceneShapeStyle;
|
|
139
|
+
transition?: SceneTransition;
|
|
140
|
+
} | {
|
|
141
|
+
type: "bezier";
|
|
142
|
+
id?: string;
|
|
143
|
+
points: ScenePoint[];
|
|
144
|
+
box?: SceneBoxInput;
|
|
145
|
+
style?: SceneStrokeStyle;
|
|
146
|
+
transition?: SceneTransition;
|
|
66
147
|
};
|
|
67
148
|
export type SceneElementType = SceneElementInput["type"];
|
|
149
|
+
export type SceneElementStyle = SceneTextStyle | SceneRectStyle | SceneShapeStyle | SceneStrokeStyle | {
|
|
150
|
+
color?: number;
|
|
151
|
+
};
|
|
152
|
+
/** Elements positioned by a point list; the host derives `box` (their bounding box) before processing. */
|
|
153
|
+
export declare const POINT_SHAPE_TYPES: readonly SceneElementType[];
|
|
154
|
+
/** Point counts each point shape accepts. */
|
|
155
|
+
export declare const POINT_SHAPE_ARITY: Record<string, readonly number[]>;
|
|
68
156
|
export type SceneChange = "created" | "updated" | "moved" | "unchanged";
|
|
69
157
|
/**
|
|
70
158
|
* A processed element inside a SceneFrame: id definitely assigned, box clamped,
|
|
@@ -78,7 +166,12 @@ export interface FrameElement {
|
|
|
78
166
|
text?: string;
|
|
79
167
|
/** Image pixels, base64 (PNG documented; SGCs decode → re-encode to wire format and scale to box). */
|
|
80
168
|
data?: string;
|
|
81
|
-
style?:
|
|
169
|
+
style?: SceneElementStyle;
|
|
170
|
+
/** Point shapes: the points, in canvas pixels; `box` is their bounding box. */
|
|
171
|
+
points?: ScenePoint[];
|
|
172
|
+
startDeg?: number;
|
|
173
|
+
endDeg?: number;
|
|
174
|
+
transition?: SceneTransition;
|
|
82
175
|
change: SceneChange;
|
|
83
176
|
/** Hash of the element's content (text/data + style) — lets SGCs skip BLE re-upload without retaining pixels. */
|
|
84
177
|
contentHash: string;
|
|
@@ -119,7 +212,9 @@ export interface SceneDisplayCapabilities {
|
|
|
119
212
|
width: number;
|
|
120
213
|
height: number;
|
|
121
214
|
};
|
|
122
|
-
shapes:
|
|
215
|
+
shapes: SceneShape[];
|
|
216
|
+
/** True ⇒ element transitions animate on the device. */
|
|
217
|
+
animation: boolean;
|
|
123
218
|
intensityLevels: number;
|
|
124
219
|
partialUpdate: boolean;
|
|
125
220
|
}
|
|
@@ -133,11 +228,14 @@ export interface SceneProcessResult {
|
|
|
133
228
|
/** Cheap stable string hash (FNV-1a, 32-bit) — content identity for diff + native dedup. */
|
|
134
229
|
export declare function contentHash(...parts: (string | number | undefined)[]): string;
|
|
135
230
|
export declare function boxesEqual(a: SceneBox, b: SceneBox): boolean;
|
|
136
|
-
/** Content identity of an element (text/data + style), excluding
|
|
231
|
+
/** Content identity of an element (text/data/points/angles + style), excluding its box. */
|
|
137
232
|
export declare function elementContentHash(el: {
|
|
138
233
|
type: SceneElementType;
|
|
139
234
|
text?: string;
|
|
140
235
|
data?: string;
|
|
141
|
-
style?:
|
|
236
|
+
style?: SceneElementStyle;
|
|
237
|
+
points?: ScenePoint[];
|
|
238
|
+
startDeg?: number;
|
|
239
|
+
endDeg?: number;
|
|
142
240
|
}): string;
|
|
143
241
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAA;AAEnF;;;;;GAKG;AACH,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,qBAAqB,GAAG,MAAM,GAAG,aAAa,CAAA;AAEzF,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;IAC9B,sCAAsC;IACtC,SAAS,CAAC,EAAE,cAAc,CAAA;IAC1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuHH,4FAA4F;AAC5F,MAAM,UAAU,WAAW,CAAC,GAAG,KAAsC;IACnE,IAAI,CAAC,GAAG,UAAU,CAAA;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;YACpB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;QAC9B,CAAC;QACD,CAAC,IAAI,IAAI,CAAA;QACT,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;IAC9B,CAAC;IACD,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAW,EAAE,CAAW;IACjD,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACjE,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB,CAAC,EAKlC;IACC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAA;IAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;SAChC,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAK,KAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3D,IAAI,CAAC,GAAG,CAAC,CAAA;IACZ,OAAO,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;AACzD,CAAC","sourcesContent":["/**\n * Scene pipeline types — the host half of the miniapp `render()` display API.\n *\n * A miniapp describes a full frame (\"scene\") of positioned elements; the host\n * clamps/budgets/wraps it, diffs it against the last frame sent to the device,\n * and ships ONE annotated SceneFrame over the bridge. SGCs either walk the\n * per-element annotations (G2, Mentra Display) or serialize the whole frame\n * (full-frame devices). Spec: notes/miniapp-display-render-implementation-spec.md\n */\n\nexport interface SceneBox {\n x: number\n y: number\n w: number\n h: number\n}\n\n/** Canvas corner a box's x/y are measured from (insets grow inward). */\nexport type SceneAnchor = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\"\n\n/**\n * A box as it arrives from the miniapp SDK: optionally corner-anchored.\n * `resolveAnchors` turns these into plain top-left SceneBoxes against the\n * device's real canvas before any processing/degrading — anchors never reach\n * FrameElement, the differ, or the wire.\n */\nexport interface SceneBoxInput extends SceneBox {\n anchor?: SceneAnchor\n}\n\nexport type SceneBreakMode = \"character\" | \"character-no-hyphen\" | \"word\" | \"strict-word\"\n\nexport interface SceneTextStyle {\n /** Border width in px (0/absent = none). */\n border?: number\n /** Border corner radius in px. */\n radius?: number\n /** What happens to text that doesn't fit the box after wrapping. Default \"clip\". */\n overflow?: \"clip\" | \"ellipsis\"\n /** Line-break policy for wrapping. */\n breakMode?: SceneBreakMode\n /**\n * \"mono\": fixed-pitch at `fontSize` px, lines taken as given (no host wrap).\n * \"builtin\": drawn by the glasses' own 20 px font engine (Glassly CFW G2);\n * lines taken as given, other devices fall back to the default face.\n */\n font?: \"default\" | \"mono\" | \"builtin\"\n fontSize?: number\n}\n\nexport interface SceneRectStyle {\n border?: number\n radius?: number\n}\n\n/** A scene element as it arrives from the miniapp SDK (pre-processing). */\nexport type SceneElementInput =\n | {type: \"text\"; id?: string; box: SceneBoxInput; text: string; style?: SceneTextStyle}\n | {type: \"image\"; id?: string; box: SceneBoxInput; data: string}\n | {type: \"rect\"; id?: string; box: SceneBoxInput; style?: SceneRectStyle}\n\nexport type SceneElementType = SceneElementInput[\"type\"]\n\nexport type SceneChange = \"created\" | \"updated\" | \"moved\" | \"unchanged\"\n\n/**\n * A processed element inside a SceneFrame: id definitely assigned, box clamped,\n * text pre-wrapped (newlines inserted), annotated with what changed vs the last\n * frame sent to this device.\n */\nexport interface FrameElement {\n id: string\n type: SceneElementType\n box: SceneBox\n text?: string\n /** Image pixels, base64 (PNG documented; SGCs decode → re-encode to wire format and scale to box). */\n data?: string\n style?: SceneTextStyle | SceneRectStyle\n change: SceneChange\n /** Hash of the element's content (text/data + style) — lets SGCs skip BLE re-upload without retaining pixels. */\n contentHash: string\n}\n\n/** The unit that crosses the JS→native bridge: one whole frame per render(). */\nexport interface SceneFrame {\n appId: string\n view: \"main\" | \"dashboard\"\n /** Bumps on replay / app switch / reconnect; a bumped epoch bypasses native dedup. */\n sceneEpoch: number\n /** True when the device is expected to rebuild from scratch (all elements arrive \"created\"). */\n replay?: boolean\n elements: FrameElement[]\n /** Element ids present in the previous frame but absent now — host-computed; apps never send removes. */\n removed: string[]\n /** Canvas the boxes were clamped against — lets a full-frame SGC place a legacy-sized frame correctly. */\n canvas?: {width: number; height: number}\n}\n\n/**\n * Typed display capabilities block (host-side mirror of the SDK's\n * DisplayCapabilities). All limits are DATA — the pipeline acts on them\n * generically; no device names appear in pipeline code.\n */\nexport interface SceneDisplayCapabilities {\n /** Public drawable canvas in px (the device's safe area, not the physical panel). */\n width: number\n height: number\n /** False ⇒ scenes degrade to legacy text layouts host-side; only sugar-shaped content renders. */\n canPosition: boolean\n /** Text-element budget. Rects share this pool on container-based devices. */\n maxTextElements: number\n maxImageElements: number\n /** Max per-image dimensions the device accepts (box-level check), if limited. */\n maxImagePx?: {width: number; height: number}\n shapes: \"rect\"[]\n intensityLevels: number\n partialUpdate: boolean\n}\n\n/** Result of processing a scene — feeds the awaitable render() result. */\nexport interface SceneProcessResult {\n frame: SceneFrame\n degraded: boolean\n /** Ids (or synthesized ids) of elements dropped by budget/bounds/image limits. */\n dropped: string[]\n}\n\n/** Cheap stable string hash (FNV-1a, 32-bit) — content identity for diff + native dedup. */\nexport function contentHash(...parts: (string | number | undefined)[]): string {\n let h = 0x811c9dc5\n for (const part of parts) {\n const s = part === undefined ? \"\u0000\" : String(part)\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n h ^= 0x1f\n h = Math.imul(h, 0x01000193)\n }\n return (h >>> 0).toString(36)\n}\n\nexport function boxesEqual(a: SceneBox, b: SceneBox): boolean {\n return a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h\n}\n\n/** Content identity of an element (text/data + style), excluding geometry. */\nexport function elementContentHash(el: {\n type: SceneElementType\n text?: string\n data?: string\n style?: SceneTextStyle | SceneRectStyle\n}): string {\n const style = el.style ?? {}\n const styleKey = Object.keys(style)\n .sort()\n .map((k) => `${k}=${(style as Record<string, unknown>)[k]}`)\n .join(\",\")\n return contentHash(el.type, el.text, el.data, styleKey)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA4IH,0GAA0G;AAC1G,MAAM,CAAC,MAAM,iBAAiB,GAAgC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;AAEpG,6CAA6C;AAC7C,MAAM,CAAC,MAAM,iBAAiB,GAAsC;IAClE,IAAI,EAAE,CAAC,CAAC,CAAC;IACT,QAAQ,EAAE,CAAC,CAAC,CAAC;IACb,IAAI,EAAE,CAAC,CAAC,CAAC;IACT,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;CACf,CAAA;AAyED,4FAA4F;AAC5F,MAAM,UAAU,WAAW,CAAC,GAAG,KAAsC;IACnE,IAAI,CAAC,GAAG,UAAU,CAAA;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;YACpB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;QAC9B,CAAC;QACD,CAAC,IAAI,IAAI,CAAA;QACT,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;IAC9B,CAAC;IACD,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAW,EAAE,CAAW;IACjD,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACjE,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,kBAAkB,CAAC,EAQlC;IACC,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAA;IAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;SAChC,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAK,KAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC3D,IAAI,CAAC,GAAG,CAAC,CAAA;IACZ,MAAM,SAAS,GAAG,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAClE,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IACjH,OAAO,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAA;AAC9E,CAAC","sourcesContent":["/**\n * Scene pipeline types — the host half of the miniapp `render()` display API.\n *\n * A miniapp describes a full frame (\"scene\") of positioned elements; the host\n * clamps/budgets/wraps it, diffs it against the last frame sent to the device,\n * and ships ONE annotated SceneFrame over the bridge. SGCs either walk the\n * per-element annotations (G2, Mentra Display) or serialize the whole frame\n * (full-frame devices). Spec: notes/miniapp-display-render-implementation-spec.md\n */\n\nexport interface SceneBox {\n x: number\n y: number\n w: number\n h: number\n}\n\n/** Canvas corner a box's x/y are measured from (insets grow inward). */\nexport type SceneAnchor = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\"\n\n/**\n * A box as it arrives from the miniapp SDK: optionally corner-anchored.\n * `resolveAnchors` turns these into plain top-left SceneBoxes against the\n * device's real canvas before any processing/degrading — anchors never reach\n * FrameElement, the differ, or the wire.\n */\nexport interface SceneBoxInput extends SceneBox {\n anchor?: SceneAnchor\n}\n\nexport type SceneBreakMode = \"character\" | \"character-no-hyphen\" | \"word\" | \"strict-word\"\n\nexport interface SceneTextStyle {\n /** Border width in px (0/absent = none). */\n border?: number\n /** Border corner radius in px. */\n radius?: number\n /** What happens to text that doesn't fit the box after wrapping. Default \"clip\". */\n overflow?: \"clip\" | \"ellipsis\"\n /** Line-break policy for wrapping. */\n breakMode?: SceneBreakMode\n /**\n * \"mono\": fixed-pitch at `fontSize` px, lines taken as given (no host wrap).\n * \"builtin\": drawn by the glasses' own 20 px font engine (Glassly CFW G2);\n * lines taken as given, other devices fall back to the default face.\n */\n font?: \"default\" | \"mono\" | \"builtin\"\n fontSize?: number\n}\n\nexport interface SceneRectStyle {\n border?: number\n radius?: number\n /** Solid fill instead of an outline. */\n fill?: boolean\n /** Gray level 0..15 (15 = brightest). Default 15. */\n color?: number\n}\n\n/** Closed shapes (circle, triangle, quad): outline of `border` px, or filled. */\nexport interface SceneShapeStyle {\n border?: number\n fill?: boolean\n color?: number\n}\n\n/** Open strokes (line, bezier, arc). */\nexport interface SceneStrokeStyle {\n width?: number\n color?: number\n}\n\nexport interface ScenePoint {\n x: number\n y: number\n}\n\nexport type SceneEasing = \"linear\" | \"ease\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\"\n\n/** Animate geometry changes of an element with a stable id (devices with `animation`). */\nexport interface SceneTransition {\n durationMs: number\n easing?: SceneEasing\n}\n\nexport type SceneShape = \"rect\" | \"circle\" | \"arc\" | \"pie\" | \"line\" | \"triangle\" | \"quad\" | \"bezier\"\n\n/** A scene element as it arrives from the miniapp SDK (pre-processing). */\nexport type SceneElementInput =\n | {type: \"text\"; id?: string; box: SceneBoxInput; text: string; style?: SceneTextStyle; transition?: SceneTransition}\n | {type: \"image\"; id?: string; box: SceneBoxInput; data: string; transition?: SceneTransition}\n | {type: \"rect\"; id?: string; box: SceneBoxInput; style?: SceneRectStyle; transition?: SceneTransition}\n | {type: \"circle\"; id?: string; box: SceneBoxInput; style?: SceneShapeStyle; transition?: SceneTransition}\n | {\n type: \"arc\"\n id?: string\n box: SceneBoxInput\n startDeg: number\n endDeg: number\n style?: SceneStrokeStyle\n transition?: SceneTransition\n }\n | {\n type: \"pie\"\n id?: string\n box: SceneBoxInput\n startDeg: number\n endDeg: number\n style?: {color?: number}\n transition?: SceneTransition\n }\n | {\n type: \"line\"\n id?: string\n points: ScenePoint[]\n box?: SceneBoxInput\n style?: SceneStrokeStyle\n transition?: SceneTransition\n }\n | {\n type: \"triangle\"\n id?: string\n points: ScenePoint[]\n box?: SceneBoxInput\n style?: SceneShapeStyle\n transition?: SceneTransition\n }\n | {\n type: \"quad\"\n id?: string\n points: ScenePoint[]\n box?: SceneBoxInput\n style?: SceneShapeStyle\n transition?: SceneTransition\n }\n | {\n type: \"bezier\"\n id?: string\n points: ScenePoint[]\n box?: SceneBoxInput\n style?: SceneStrokeStyle\n transition?: SceneTransition\n }\n\nexport type SceneElementType = SceneElementInput[\"type\"]\n\nexport type SceneElementStyle = SceneTextStyle | SceneRectStyle | SceneShapeStyle | SceneStrokeStyle | {color?: number}\n\n/** Elements positioned by a point list; the host derives `box` (their bounding box) before processing. */\nexport const POINT_SHAPE_TYPES: readonly SceneElementType[] = [\"line\", \"triangle\", \"quad\", \"bezier\"]\n\n/** Point counts each point shape accepts. */\nexport const POINT_SHAPE_ARITY: Record<string, readonly number[]> = {\n line: [2],\n triangle: [3],\n quad: [4],\n bezier: [3, 4],\n}\n\nexport type SceneChange = \"created\" | \"updated\" | \"moved\" | \"unchanged\"\n\n/**\n * A processed element inside a SceneFrame: id definitely assigned, box clamped,\n * text pre-wrapped (newlines inserted), annotated with what changed vs the last\n * frame sent to this device.\n */\nexport interface FrameElement {\n id: string\n type: SceneElementType\n box: SceneBox\n text?: string\n /** Image pixels, base64 (PNG documented; SGCs decode → re-encode to wire format and scale to box). */\n data?: string\n style?: SceneElementStyle\n /** Point shapes: the points, in canvas pixels; `box` is their bounding box. */\n points?: ScenePoint[]\n startDeg?: number\n endDeg?: number\n transition?: SceneTransition\n change: SceneChange\n /** Hash of the element's content (text/data + style) — lets SGCs skip BLE re-upload without retaining pixels. */\n contentHash: string\n}\n\n/** The unit that crosses the JS→native bridge: one whole frame per render(). */\nexport interface SceneFrame {\n appId: string\n view: \"main\" | \"dashboard\"\n /** Bumps on replay / app switch / reconnect; a bumped epoch bypasses native dedup. */\n sceneEpoch: number\n /** True when the device is expected to rebuild from scratch (all elements arrive \"created\"). */\n replay?: boolean\n elements: FrameElement[]\n /** Element ids present in the previous frame but absent now — host-computed; apps never send removes. */\n removed: string[]\n /** Canvas the boxes were clamped against — lets a full-frame SGC place a legacy-sized frame correctly. */\n canvas?: {width: number; height: number}\n}\n\n/**\n * Typed display capabilities block (host-side mirror of the SDK's\n * DisplayCapabilities). All limits are DATA — the pipeline acts on them\n * generically; no device names appear in pipeline code.\n */\nexport interface SceneDisplayCapabilities {\n /** Public drawable canvas in px (the device's safe area, not the physical panel). */\n width: number\n height: number\n /** False ⇒ scenes degrade to legacy text layouts host-side; only sugar-shaped content renders. */\n canPosition: boolean\n /** Text-element budget. Rects share this pool on container-based devices. */\n maxTextElements: number\n maxImageElements: number\n /** Max per-image dimensions the device accepts (box-level check), if limited. */\n maxImagePx?: {width: number; height: number}\n shapes: SceneShape[]\n /** True ⇒ element transitions animate on the device. */\n animation: boolean\n intensityLevels: number\n partialUpdate: boolean\n}\n\n/** Result of processing a scene — feeds the awaitable render() result. */\nexport interface SceneProcessResult {\n frame: SceneFrame\n degraded: boolean\n /** Ids (or synthesized ids) of elements dropped by budget/bounds/image limits. */\n dropped: string[]\n}\n\n/** Cheap stable string hash (FNV-1a, 32-bit) — content identity for diff + native dedup. */\nexport function contentHash(...parts: (string | number | undefined)[]): string {\n let h = 0x811c9dc5\n for (const part of parts) {\n const s = part === undefined ? \"\u0000\" : String(part)\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n h ^= 0x1f\n h = Math.imul(h, 0x01000193)\n }\n return (h >>> 0).toString(36)\n}\n\nexport function boxesEqual(a: SceneBox, b: SceneBox): boolean {\n return a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h\n}\n\n/** Content identity of an element (text/data/points/angles + style), excluding its box. */\nexport function elementContentHash(el: {\n type: SceneElementType\n text?: string\n data?: string\n style?: SceneElementStyle\n points?: ScenePoint[]\n startDeg?: number\n endDeg?: number\n}): string {\n const style = el.style ?? {}\n const styleKey = Object.keys(style)\n .sort()\n .map((k) => `${k}=${(style as Record<string, unknown>)[k]}`)\n .join(\",\")\n const pointsKey = el.points?.map((p) => `${p.x},${p.y}`).join(\";\")\n const angleKey = el.startDeg !== undefined || el.endDeg !== undefined ? `${el.startDeg}-${el.endDeg}` : undefined\n return contentHash(el.type, el.text, el.data, styleKey, pointsKey, angleKey)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.109",
|
|
4
4
|
"description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -97,17 +97,17 @@
|
|
|
97
97
|
"registry": "https://registry.npmjs.org/"
|
|
98
98
|
},
|
|
99
99
|
"dependencies": {
|
|
100
|
-
"@glassly/bluetooth-sdk": "0.1.0-dev.
|
|
101
|
-
"@glassly/cloud-client": "0.1.0-dev.
|
|
102
|
-
"@glassly/cloud-protocol": "0.1.0-dev.
|
|
103
|
-
"@glassly/crust": "0.1.0-dev.
|
|
104
|
-
"@glassly/miniapp": "0.1.0-dev.
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.1.0-dev.109",
|
|
101
|
+
"@glassly/cloud-client": "0.1.0-dev.109",
|
|
102
|
+
"@glassly/cloud-protocol": "0.1.0-dev.109",
|
|
103
|
+
"@glassly/crust": "0.1.0-dev.109",
|
|
104
|
+
"@glassly/miniapp": "0.1.0-dev.109",
|
|
105
105
|
"buffer": "^6.0.3",
|
|
106
106
|
"events": "^3.3.0",
|
|
107
107
|
"react-native-marked": "8.1.1",
|
|
108
108
|
"semver": "^7.7.4",
|
|
109
109
|
"typesafe-ts": "^1.7.1",
|
|
110
|
-
"@glassly/llm-providers": "0.1.0-dev.
|
|
110
|
+
"@glassly/llm-providers": "0.1.0-dev.109"
|
|
111
111
|
},
|
|
112
112
|
"devDependencies": {
|
|
113
113
|
"@glassly/llm-providers": "workspace:*",
|
|
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
|
|
|
12
12
|
export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "0.1.0",
|
|
15
|
-
"releaseIdentity": "0.1.0-dev.
|
|
16
|
-
"releaseSetId": "glassly-0.1.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.1.0-dev.109",
|
|
16
|
+
"releaseSetId": "glassly-0.1.0-dev.109",
|
|
17
|
+
"sourceCommit": "509f3299454a2e3eb9b5862aa4caba01b8de9b09",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
|
@@ -79,6 +79,7 @@ class SceneRenderer {
|
|
|
79
79
|
maxImageElements: display.maxImageElements ?? 0,
|
|
80
80
|
maxImagePx: display.maxImagePx,
|
|
81
81
|
shapes: display.shapes ?? [],
|
|
82
|
+
animation: display.animation ?? false,
|
|
82
83
|
intensityLevels: display.intensityLevels ?? 2,
|
|
83
84
|
partialUpdate: display.partialUpdate ?? false,
|
|
84
85
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* lifted alongside the canvas, bounded only to keep frames sane.
|
|
10
10
|
*/
|
|
11
11
|
import {useGlassesStore} from "../stores/glasses"
|
|
12
|
-
import {DeviceTypes, getModelCapabilities, type Capabilities} from "../types"
|
|
12
|
+
import {ALL_DISPLAY_SHAPES, DeviceTypes, getModelCapabilities, type Capabilities} from "../types"
|
|
13
13
|
import {isGlasslyCfwFirmwareVersion} from "./CustomDashboardModel"
|
|
14
14
|
|
|
15
15
|
const CFW_MAX_TEXT_ELEMENTS = 24
|
|
@@ -35,6 +35,9 @@ export function getEffectiveCapabilities(model: DeviceTypes): Capabilities {
|
|
|
35
35
|
// always rasterizes phone-side regardless of whether native reported dims.
|
|
36
36
|
const cfw = model === DeviceTypes.G2 && isGlasslyCfwFirmwareVersion(useGlassesStore.getState().firmwareVersion)
|
|
37
37
|
if (!base.display || (!canvas && !cfw)) return base
|
|
38
|
+
// Native reports whether the connected CFW draws vector shapes and animates
|
|
39
|
+
// transitions on the glasses (firmware modes 16-18).
|
|
40
|
+
const vector = model === DeviceTypes.G2 && useGlassesStore.getState().sceneVector === true
|
|
38
41
|
return {
|
|
39
42
|
...base,
|
|
40
43
|
display: {
|
|
@@ -43,6 +46,7 @@ export function getEffectiveCapabilities(model: DeviceTypes): Capabilities {
|
|
|
43
46
|
height: canvas?.height ?? base.display.height,
|
|
44
47
|
maxTextElements: Math.max(base.display.maxTextElements ?? 0, CFW_MAX_TEXT_ELEMENTS),
|
|
45
48
|
maxImageElements: Math.max(base.display.maxImageElements ?? 0, CFW_MAX_IMAGE_ELEMENTS),
|
|
49
|
+
...(vector ? {shapes: ALL_DISPLAY_SHAPES, animation: true} : {}),
|
|
46
50
|
},
|
|
47
51
|
}
|
|
48
52
|
}
|
package/src/stores/glasses.ts
CHANGED