@glassly/engine 0.3.5-dev.750 → 0.3.5-dev.753
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/GestureDotsService.d.ts.map +1 -1
- package/build/services/GestureDotsService.js +2 -1
- package/build/services/GestureDotsService.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts +2 -1
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +48 -24
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +3 -1
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/utils/display/scene/index.d.ts +1 -1
- package/build/utils/display/scene/index.d.ts.map +1 -1
- package/build/utils/display/scene/index.js.map +1 -1
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +20 -1
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/display/scene/types.d.ts +1 -1
- package/build/utils/display/scene/types.d.ts.map +1 -1
- 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/GestureDotsService.ts +2 -1
- package/src/services/GlassesTouchInput.ts +53 -21
- package/src/services/SceneRenderer.ts +3 -1
- package/src/utils/display/scene/index.ts +1 -0
- package/src/utils/display/scene/process.ts +17 -1
- package/src/utils/display/scene/types.ts +1 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const ENGINE_RELEASE_METADATA = Object.freeze({
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"familyBaseVersion": "0.3.5",
|
|
4
|
-
"releaseIdentity": "0.3.5-dev.
|
|
5
|
-
"releaseSetId": "glassly-0.3.5-dev.
|
|
6
|
-
"sourceCommit": "
|
|
4
|
+
"releaseIdentity": "0.3.5-dev.753",
|
|
5
|
+
"releaseSetId": "glassly-0.3.5-dev.753",
|
|
6
|
+
"sourceCommit": "7cbf7d579ef35c5c469189177b3cd8b47baf9dd8",
|
|
7
7
|
"otaManifestUrl": null,
|
|
8
8
|
"otaManifestSha256": null
|
|
9
9
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.3.5\",\n \"releaseIdentity\": \"0.3.5-dev.
|
|
1
|
+
{"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,uBAAuB,GAAoC,MAAM,CAAC,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,uBAAuB;IACvC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface EngineReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"0.3.5\",\n \"releaseIdentity\": \"0.3.5-dev.753\",\n \"releaseSetId\": \"glassly-0.3.5-dev.753\",\n \"sourceCommit\": \"7cbf7d579ef35c5c469189177b3cd8b47baf9dd8\",\n \"otaManifestUrl\": null,\n \"otaManifestSha256\": null\n})\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GestureDotsService.d.ts","sourceRoot":"","sources":["../../src/services/GestureDotsService.ts"],"names":[],"mappings":"AA+DA,cAAM,kBAAkB;IACtB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,UAAU,CAAsB;IACxC,6GAA6G;IAC7G,OAAO,CAAC,OAAO,CAA4D;IAC3E,kFAAkF;IAClF,OAAO,CAAC,UAAU,CAA4B;IAC9C,8EAA8E;IAC9E,OAAO,CAAC,aAAa,CAA+B;IACpD,oGAAoG;IACpG,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,MAAM,CAAwB;IAEtC,KAAK,IAAI,IAAI;IAqBb,IAAI,IAAI,IAAI;IAQZ,OAAO,CAAC,YAAY;IAOpB,uFAAuF;IACvF,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,OAAO;IAIf;;;;;;OAMG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,UAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"GestureDotsService.d.ts","sourceRoot":"","sources":["../../src/services/GestureDotsService.ts"],"names":[],"mappings":"AA+DA,cAAM,kBAAkB;IACtB,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,UAAU,CAAsB;IACxC,6GAA6G;IAC7G,OAAO,CAAC,OAAO,CAA4D;IAC3E,kFAAkF;IAClF,OAAO,CAAC,UAAU,CAA4B;IAC9C,8EAA8E;IAC9E,OAAO,CAAC,aAAa,CAA+B;IACpD,oGAAoG;IACpG,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,MAAM,CAAwB;IAEtC,KAAK,IAAI,IAAI;IAqBb,IAAI,IAAI,IAAI;IAQZ,OAAO,CAAC,YAAY;IAOpB,uFAAuF;IACvF,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,OAAO;IAIf;;;;;;OAMG;IACH,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,UAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI;IAOpG,OAAO,CAAC,YAAY;IAuDpB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ;IAWhB,2EAA2E;IAC3E,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,UAAU;IAWlB,sFAAsF;IACtF,OAAO,CAAC,KAAK;IAgBb;;;;;;;;;;OAUG;IACH,OAAO,CAAC,MAAM;CAqCf;AAED,QAAA,MAAM,kBAAkB,oBAA2B,CAAA;AACnD,eAAe,kBAAkB,CAAA;AACjC,OAAO,EAAC,kBAAkB,EAAC,CAAA"}
|
|
@@ -122,7 +122,8 @@ class GestureDotsService {
|
|
|
122
122
|
* `controllerId` keeps two controllers' fingers apart.
|
|
123
123
|
*/
|
|
124
124
|
noteGesture(gestureName, claimed = false, fingerIndex, controllerId) {
|
|
125
|
-
|
|
125
|
+
// A ring's scroll steps ride beside their stroke's one swipe, which already flashed.
|
|
126
|
+
if (!this.started || !this.enabled() || gestureName === "scroll_up" || gestureName === "scroll_down")
|
|
126
127
|
return;
|
|
127
128
|
this.applyGesture(gestureName, claimed, fingerIndex, controllerId);
|
|
128
129
|
this.trace("input", { gestureName, claimed });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GestureDotsService.js","sourceRoot":"","sources":["../../src/services/GestureDotsService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Gesture dots — a debug indicator that flashes a small glyph top-right on the\n * glasses when a touch gesture arrives, while nothing else is on the display:\n * a filled circle for a single tap, two for a double tap, an arrow for a swipe\n * (cleared after a short pulse), a bullseye for a recognized long press.\n * Controllers that report press edges (touch_down … tap_end) show the press\n * itself instead: a dot while pressed, the bullseye once held, cleared at\n * release. The Neural Band's middle finger (index 1) draws the same shapes\n * hollow: a ring for its tap, the bullseye without its center for its hold. Extracted from\n * the CFW dashboard service; it renders through\n * the ordinary scene pipeline, so it works on any positioning device (stock\n * G2 included), and is gated by the gesture_dots debug setting.\n *\n * The glyphs are pre-baked images (not text): the stock firmware's builtin\n * font has no ●/↑/↓, and a single image element with a stable id and box maps\n * to ONE re-used firmware image container — flash and idle swap its bytes\n * (data-only update, no page rebuild) instead of creating/removing containers.\n */\nimport {useDisplayStore} from \"../stores/display\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport type {SceneElementInput} from \"../utils/display/scene\"\nimport {BgTimer} from \"../utils/timers\"\nimport {GESTURE_DOT_GLYPHS, GESTURE_DOT_GLYPH_SIZE, type GestureDotGlyph} from \"./gestureDotGlyphs\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport sceneRenderer from \"./SceneRenderer\"\n\nconst DOTS_APP_ID = \"system.gesture-dots\"\n/** How long a gesture stays on the indicator. */\nconst GESTURE_FLASH_MS = 700\n/** A press-edge device's tap and hold reports trail its last edge by its double-tap window. */\nconst EDGE_REPORT_GRACE_MS = 1000\nconst MARGIN_X = 16\nconst MARGIN_TOP = 10\n/** Gestures that flash a glyph. */\nconst FLASH_GLYPHS: Record<string, GestureDotGlyph> = {\n single_tap: \"dot\",\n double_tap: \"double\",\n long_press: \"hold\",\n swipe_up: \"up\",\n swipe_down: \"down\",\n swipe_left: \"left\",\n swipe_right: \"right\",\n}\n/** The band's middle finger draws the filled shapes hollow. */\nconst MIDDLE_FINGER_GLYPHS: Partial<Record<GestureDotGlyph, GestureDotGlyph>> = {\n dot: \"ring\",\n double: \"doubleRing\",\n hold: \"holdRing\",\n}\n\n/** `glyph` as `finger` draws it: the middle finger's shapes are hollow. */\nfunction fingerGlyph(glyph: GestureDotGlyph, finger: number): GestureDotGlyph {\n return finger === 1 ? MIDDLE_FINGER_GLYPHS[glyph] ?? glyph : glyph\n}\n\nfunction fingerKey(controllerId: string | undefined, finger: number): string {\n return `${controllerId ?? \"\"}:${finger}`\n}\n\nfunction fingerOf(key: string): number {\n return Number(key.slice(key.lastIndexOf(\":\") + 1))\n}\n\nclass GestureDotsService {\n private started = false\n private flash: GestureDotGlyph | null = null\n private flashTimer: number | null = null\n /** Raw controller holds survive hold recognition/dial events until their own release. Keyed by fingerKey. */\n private presses = new Map<string, {raw: boolean; glyph: GestureDotGlyph}>()\n /** When each finger last sent a press edge (touch_down, or tap_end after one). */\n private lastEdgeAt = new Map<string, number>()\n /** Avoid sending identical images for successive phases of the same press. */\n private renderedGlyph: GestureDotGlyph | null = null\n /** Whether the indicator scene currently has anything on it (needs a clear when the flash ends). */\n private visible = false\n private unsubs: Array<() => void> = []\n\n start(): void {\n if (this.started) return\n this.started = true\n this.unsubs.push(\n // The indicator owns the screen only while it is empty: drop our\n // retained state when something else takes over, and clear any\n // leftover flash when the display empties again.\n localDisplayManager.onActiveDisplayChange((active) => {\n if (active) this.relinquish()\n else this.render()\n }),\n // Overlays do not change the retained main display's occupancy.\n useDisplayStore.subscribe(\n (state) => state.overlayOwner,\n (owner) => {\n if (owner !== null) this.relinquish()\n },\n ),\n )\n }\n\n stop(): void {\n for (const unsub of this.unsubs) unsub()\n this.unsubs = []\n this.relinquish()\n this.lastEdgeAt.clear()\n this.started = false\n }\n\n private resetGesture(): void {\n if (this.flashTimer !== null) BgTimer.clearTimeout(this.flashTimer)\n this.flashTimer = null\n this.flash = null\n this.presses.clear()\n }\n\n /** Another surface owns the display; forget the indicator without painting over it. */\n private relinquish(): void {\n this.resetGesture()\n this.visible = false\n this.renderedGlyph = null\n sceneRenderer.clearApp(DOTS_APP_ID)\n this.trace(\"relinquish\")\n }\n\n private enabled(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.gesture_dots.key) === true\n }\n\n /**\n * Flash a gesture on the indicator. Other gesture names are ignored.\n * Claimed gestures cancel any earlier press/flash without starting a new\n * one: the claimant may also consume the release edge. `fingerIndex` is the\n * Neural Band's finger on its pinches and press edges (1 = middle);\n * `controllerId` keeps two controllers' fingers apart.\n */\n noteGesture(gestureName: string, claimed = false, fingerIndex?: number, controllerId?: string): void {\n if (!this.started || !this.enabled()) return\n this.applyGesture(gestureName, claimed, fingerIndex, controllerId)\n this.trace(\"input\", {gestureName, claimed})\n }\n\n private applyGesture(gestureName: string, claimed: boolean, fingerIndex?: number, controllerId?: string): void {\n if (localDisplayManager.hasActiveDisplay() || useDisplayStore.getState().overlayOwner !== null) {\n this.relinquish()\n return\n }\n if (claimed) {\n this.resetGesture()\n this.render()\n return\n }\n const finger = fingerIndex ?? 0\n const key = this.pressKey(controllerId, finger)\n const press = this.presses.get(key)\n if (gestureName === \"touch_down\" || gestureName === \"tap_start\") {\n if (gestureName === \"touch_down\") this.lastEdgeAt.set(key, Date.now())\n if (press) {\n // Both reports describe one press: after touch_down, tap_start means it is held.\n if (gestureName === \"touch_down\") press.raw = true\n else if (press.raw) press.glyph = fingerGlyph(\"hold\", finger)\n this.render()\n return\n }\n this.clearFlash()\n this.presses.set(key, {raw: gestureName === \"touch_down\", glyph: fingerGlyph(\"dot\", finger)})\n this.render()\n return\n }\n if (gestureName === \"tap_end\") {\n if (!press) return\n this.presses.delete(key)\n if (press.raw) this.lastEdgeAt.set(key, Date.now())\n this.render()\n return\n }\n const shape = FLASH_GLYPHS[gestureName]\n const glyph = shape ? fingerGlyph(shape, finger) : shape\n // The press already showed its own tap and hold; the device's report of\n // them follows after its double-tap window, often after release.\n const shownByPress = gestureName === \"single_tap\" || gestureName === \"long_press\"\n if (press?.raw && (!glyph || shownByPress)) {\n // Pinch-and-turn reports do not release a finger. A recognized hold\n // stays visible until tap_end instead of expiring halfway through it.\n if (gestureName === \"long_press\") press.glyph = fingerGlyph(\"hold\", finger)\n this.render()\n return\n }\n const lastEdgeAt = this.lastEdgeFor(finger)\n if (!press && shownByPress && lastEdgeAt !== undefined && Date.now() - lastEdgeAt < EDGE_REPORT_GRACE_MS) return\n // Completed taps/swipes become one continuous pulse, even if the device\n // omits tap_end. Older tap_start-only devices retain the same fallback.\n this.presses.delete(key)\n if (glyph) this.startFlash(glyph)\n this.render()\n }\n\n /**\n * The press this edge belongs to. Glasses relaying a ring tag its edges\n * unevenly (a touch_down carries the ring's id while the firmware leaves the\n * release's source unspecified, or the reverse), so an edge that misses its\n * exact key joins the one outstanding press of that finger instead of\n * leaving it stuck.\n */\n private pressKey(controllerId: string | undefined, finger: number): string {\n const exact = fingerKey(controllerId, finger)\n if (this.presses.has(exact)) return exact\n if (controllerId !== undefined) {\n const unattributed = fingerKey(undefined, finger)\n return this.presses.has(unattributed) ? unattributed : exact\n }\n const candidates = [...this.presses.keys()].filter((key) => fingerOf(key) === finger)\n return candidates.length === 1 ? candidates[0] : exact\n }\n\n /** The finger's latest press edge under any attribution (see pressKey). */\n private lastEdgeFor(finger: number): number | undefined {\n let latest: number | undefined\n for (const [key, at] of this.lastEdgeAt) {\n if (fingerOf(key) === finger && (latest === undefined || at > latest)) latest = at\n }\n return latest\n }\n\n private clearFlash(): void {\n if (this.flashTimer !== null) BgTimer.clearTimeout(this.flashTimer)\n this.flashTimer = null\n this.flash = null\n }\n\n private startFlash(glyph: GestureDotGlyph): void {\n this.clearFlash()\n this.flash = glyph\n this.flashTimer = BgTimer.setTimeout(() => {\n this.flashTimer = null\n this.flash = null\n this.render()\n this.trace(\"flash-ended\")\n }, GESTURE_FLASH_MS)\n }\n\n /** Debug setting only; no payloads or retained history. Correlate seq with G2PERF. */\n private trace(reason: string, details: Record<string, unknown> = {}): void {\n if (!this.enabled()) return\n console.log(\n \"GESTUREDOTS \" +\n JSON.stringify({\n t: Date.now(),\n reason,\n held: this.presses.size > 0,\n flash: this.flash,\n overlay: useDisplayStore.getState().overlayOwner,\n active: localDisplayManager.hasActiveDisplay(),\n ...details,\n }),\n )\n }\n\n /**\n * Paint or clear the indicator. Nothing is drawn while anything else is on\n * the glasses (miniapp frame or system screen) or when no gesture was just\n * received. The top-right anchor resolves against the device's real canvas,\n * so no size math here.\n *\n * Between flashes the element STAYS in the scene with the blank glyph:\n * removing it would tear down the firmware image container and rebuild the\n * page on every flash (a rebuild storm on stock firmware). Only disabling\n * the setting removes the element outright.\n */\n private render(): void {\n if (!this.started) return\n if (localDisplayManager.hasActiveDisplay() || useDisplayStore.getState().overlayOwner !== null) return\n const shown = Array.from(this.presses.values()).at(-1)?.glyph ?? this.flash\n if (!this.enabled()) {\n if (this.visible) {\n this.visible = false\n this.renderedGlyph = null\n sceneRenderer.emitScene(DOTS_APP_ID, \"main\", [])\n }\n return\n }\n if (!shown && !this.visible) return\n const glyph = shown ?? \"blank\"\n if (glyph === this.renderedGlyph) return\n const elements: SceneElementInput[] = [\n {\n type: \"image\",\n id: \"gesture-dot\",\n box: {\n x: MARGIN_X,\n y: MARGIN_TOP,\n w: GESTURE_DOT_GLYPH_SIZE,\n h: GESTURE_DOT_GLYPH_SIZE,\n anchor: \"top-right\",\n },\n data: GESTURE_DOT_GLYPHS[glyph],\n },\n ]\n this.visible = true\n this.renderedGlyph = glyph\n const result = sceneRenderer.emitScene(DOTS_APP_ID, \"main\", elements)\n this.trace(\"render\", {\n glyph,\n ...(result?.kind === \"scene\" ? {seq: result.seq, sent: result.sent} : {}),\n })\n }\n}\n\nconst gestureDotsService = new GestureDotsService()\nexport default gestureDotsService\nexport {GestureDotsService}\n"]}
|
|
1
|
+
{"version":3,"file":"GestureDotsService.js","sourceRoot":"","sources":["../../src/services/GestureDotsService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAE7D,OAAO,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAC,kBAAkB,EAAE,sBAAsB,EAAuB,MAAM,oBAAoB,CAAA;AACnG,OAAO,mBAAmB,MAAM,uBAAuB,CAAA;AACvD,OAAO,aAAa,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,GAAG,qBAAqB,CAAA;AACzC,iDAAiD;AACjD,MAAM,gBAAgB,GAAG,GAAG,CAAA;AAC5B,+FAA+F;AAC/F,MAAM,oBAAoB,GAAG,IAAI,CAAA;AACjC,MAAM,QAAQ,GAAG,EAAE,CAAA;AACnB,MAAM,UAAU,GAAG,EAAE,CAAA;AACrB,mCAAmC;AACnC,MAAM,YAAY,GAAoC;IACpD,UAAU,EAAE,KAAK;IACjB,UAAU,EAAE,QAAQ;IACpB,UAAU,EAAE,MAAM;IAClB,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,MAAM;IAClB,UAAU,EAAE,MAAM;IAClB,WAAW,EAAE,OAAO;CACrB,CAAA;AACD,+DAA+D;AAC/D,MAAM,oBAAoB,GAAsD;IAC9E,GAAG,EAAE,MAAM;IACX,MAAM,EAAE,YAAY;IACpB,IAAI,EAAE,UAAU;CACjB,CAAA;AAED,2EAA2E;AAC3E,SAAS,WAAW,CAAC,KAAsB,EAAE,MAAc;IACzD,OAAO,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAA;AACpE,CAAC;AAED,SAAS,SAAS,CAAC,YAAgC,EAAE,MAAc;IACjE,OAAO,GAAG,YAAY,IAAI,EAAE,IAAI,MAAM,EAAE,CAAA;AAC1C,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AACpD,CAAC;AAED,MAAM,kBAAkB;IACd,OAAO,GAAG,KAAK,CAAA;IACf,KAAK,GAA2B,IAAI,CAAA;IACpC,UAAU,GAAkB,IAAI,CAAA;IACxC,6GAA6G;IACrG,OAAO,GAAG,IAAI,GAAG,EAAkD,CAAA;IAC3E,kFAAkF;IAC1E,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC9C,8EAA8E;IACtE,aAAa,GAA2B,IAAI,CAAA;IACpD,oGAAoG;IAC5F,OAAO,GAAG,KAAK,CAAA;IACf,MAAM,GAAsB,EAAE,CAAA;IAEtC,KAAK;QACH,IAAI,IAAI,CAAC,OAAO;YAAE,OAAM;QACxB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,MAAM,CAAC,IAAI;QACd,iEAAiE;QACjE,+DAA+D;QAC/D,iDAAiD;QACjD,mBAAmB,CAAC,qBAAqB,CAAC,CAAC,MAAM,EAAE,EAAE;YACnD,IAAI,MAAM;gBAAE,IAAI,CAAC,UAAU,EAAE,CAAA;;gBACxB,IAAI,CAAC,MAAM,EAAE,CAAA;QACpB,CAAC,CAAC;QACF,gEAAgE;QAChE,eAAe,CAAC,SAAS,CACvB,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,EAC7B,CAAC,KAAK,EAAE,EAAE;YACR,IAAI,KAAK,KAAK,IAAI;gBAAE,IAAI,CAAC,UAAU,EAAE,CAAA;QACvC,CAAC,CACF,CACF,CAAA;IACH,CAAC;IAED,IAAI;QACF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM;YAAE,KAAK,EAAE,CAAA;QACxC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;QAChB,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAA;QACvB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;IACtB,CAAC;IAEO,YAAY;QAClB,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI;YAAE,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACnE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACtB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;QACjB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;IACtB,CAAC;IAED,uFAAuF;IAC/E,UAAU;QAChB,IAAI,CAAC,YAAY,EAAE,CAAA;QACnB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACpB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;QACzB,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;QACnC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAA;IAC1B,CAAC;IAEO,OAAO;QACb,OAAO,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,IAAI,CAAA;IACnF,CAAC;IAED;;;;;;OAMG;IACH,WAAW,CAAC,WAAmB,EAAE,OAAO,GAAG,KAAK,EAAE,WAAoB,EAAE,YAAqB;QAC3F,qFAAqF;QACrF,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,WAAW,KAAK,WAAW,IAAI,WAAW,KAAK,aAAa;YAAE,OAAM;QAC5G,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,CAAC,CAAA;QAClE,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,EAAC,WAAW,EAAE,OAAO,EAAC,CAAC,CAAA;IAC7C,CAAC;IAEO,YAAY,CAAC,WAAmB,EAAE,OAAgB,EAAE,WAAoB,EAAE,YAAqB;QACrG,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,IAAI,eAAe,CAAC,QAAQ,EAAE,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;YAC/F,IAAI,CAAC,UAAU,EAAE,CAAA;YACjB,OAAM;QACR,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC,YAAY,EAAE,CAAA;YACnB,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,OAAM;QACR,CAAC;QACD,MAAM,MAAM,GAAG,WAAW,IAAI,CAAC,CAAA;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACnC,IAAI,WAAW,KAAK,YAAY,IAAI,WAAW,KAAK,WAAW,EAAE,CAAC;YAChE,IAAI,WAAW,KAAK,YAAY;gBAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;YACtE,IAAI,KAAK,EAAE,CAAC;gBACV,iFAAiF;gBACjF,IAAI,WAAW,KAAK,YAAY;oBAAE,KAAK,CAAC,GAAG,GAAG,IAAI,CAAA;qBAC7C,IAAI,KAAK,CAAC,GAAG;oBAAE,KAAK,CAAC,KAAK,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBAC7D,IAAI,CAAC,MAAM,EAAE,CAAA;gBACb,OAAM;YACR,CAAC;YACD,IAAI,CAAC,UAAU,EAAE,CAAA;YACjB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAC,GAAG,EAAE,WAAW,KAAK,YAAY,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,EAAC,CAAC,CAAA;YAC7F,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,OAAM;QACR,CAAC;QACD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,IAAI,CAAC,KAAK;gBAAE,OAAM;YAClB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACxB,IAAI,KAAK,CAAC,GAAG;gBAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;YACnD,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,OAAM;QACR,CAAC;QACD,MAAM,KAAK,GAAG,YAAY,CAAC,WAAW,CAAC,CAAA;QACvC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;QACxD,wEAAwE;QACxE,iEAAiE;QACjE,MAAM,YAAY,GAAG,WAAW,KAAK,YAAY,IAAI,WAAW,KAAK,YAAY,CAAA;QACjF,IAAI,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC,KAAK,IAAI,YAAY,CAAC,EAAE,CAAC;YAC3C,oEAAoE;YACpE,sEAAsE;YACtE,IAAI,WAAW,KAAK,YAAY;gBAAE,KAAK,CAAC,KAAK,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;YAC3E,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,OAAM;QACR,CAAC;QACD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAC3C,IAAI,CAAC,KAAK,IAAI,YAAY,IAAI,UAAU,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,oBAAoB;YAAE,OAAM;QAChH,wEAAwE;QACxE,wEAAwE;QACxE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACxB,IAAI,KAAK;YAAE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QACjC,IAAI,CAAC,MAAM,EAAE,CAAA;IACf,CAAC;IAED;;;;;;OAMG;IACK,QAAQ,CAAC,YAAgC,EAAE,MAAc;QAC/D,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;QAC7C,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAA;QACzC,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,YAAY,GAAG,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;YACjD,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAA;QAC9D,CAAC;QACD,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,CAAA;QACrF,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACxD,CAAC;IAED,2EAA2E;IACnE,WAAW,CAAC,MAAc;QAChC,IAAI,MAA0B,CAAA;QAC9B,KAAK,MAAM,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACxC,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,MAAM,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,GAAG,MAAM,CAAC;gBAAE,MAAM,GAAG,EAAE,CAAA;QACpF,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IAEO,UAAU;QAChB,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI;YAAE,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACnE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACtB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;IAEO,UAAU,CAAC,KAAsB;QACvC,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE;YACxC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;YACtB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;YACjB,IAAI,CAAC,MAAM,EAAE,CAAA;YACb,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;QAC3B,CAAC,EAAE,gBAAgB,CAAC,CAAA;IACtB,CAAC;IAED,sFAAsF;IAC9E,KAAK,CAAC,MAAc,EAAE,UAAmC,EAAE;QACjE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;YAAE,OAAM;QAC3B,OAAO,CAAC,GAAG,CACT,cAAc;YACZ,IAAI,CAAC,SAAS,CAAC;gBACb,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE;gBACb,MAAM;gBACN,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC;gBAC3B,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,OAAO,EAAE,eAAe,CAAC,QAAQ,EAAE,CAAC,YAAY;gBAChD,MAAM,EAAE,mBAAmB,CAAC,gBAAgB,EAAE;gBAC9C,GAAG,OAAO;aACX,CAAC,CACL,CAAA;IACH,CAAC;IAED;;;;;;;;;;OAUG;IACK,MAAM;QACZ,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAM;QACzB,IAAI,mBAAmB,CAAC,gBAAgB,EAAE,IAAI,eAAe,CAAC,QAAQ,EAAE,CAAC,YAAY,KAAK,IAAI;YAAE,OAAM;QACtG,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC,KAAK,CAAA;QAC3E,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YACpB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;gBACpB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;gBACzB,aAAa,CAAC,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;YAClD,CAAC;YACD,OAAM;QACR,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAM;QACnC,MAAM,KAAK,GAAG,KAAK,IAAI,OAAO,CAAA;QAC9B,IAAI,KAAK,KAAK,IAAI,CAAC,aAAa;YAAE,OAAM;QACxC,MAAM,QAAQ,GAAwB;YACpC;gBACE,IAAI,EAAE,OAAO;gBACb,EAAE,EAAE,aAAa;gBACjB,GAAG,EAAE;oBACH,CAAC,EAAE,QAAQ;oBACX,CAAC,EAAE,UAAU;oBACb,CAAC,EAAE,sBAAsB;oBACzB,CAAC,EAAE,sBAAsB;oBACzB,MAAM,EAAE,WAAW;iBACpB;gBACD,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC;aAChC;SACF,CAAA;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QACnB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAA;QAC1B,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAA;QACrE,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;YACnB,KAAK;YACL,GAAG,CAAC,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAC,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1E,CAAC,CAAA;IACJ,CAAC;CACF;AAED,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,EAAE,CAAA;AACnD,eAAe,kBAAkB,CAAA;AACjC,OAAO,EAAC,kBAAkB,EAAC,CAAA","sourcesContent":["/**\n * Gesture dots — a debug indicator that flashes a small glyph top-right on the\n * glasses when a touch gesture arrives, while nothing else is on the display:\n * a filled circle for a single tap, two for a double tap, an arrow for a swipe\n * (cleared after a short pulse), a bullseye for a recognized long press.\n * Controllers that report press edges (touch_down … tap_end) show the press\n * itself instead: a dot while pressed, the bullseye once held, cleared at\n * release. The Neural Band's middle finger (index 1) draws the same shapes\n * hollow: a ring for its tap, the bullseye without its center for its hold. Extracted from\n * the CFW dashboard service; it renders through\n * the ordinary scene pipeline, so it works on any positioning device (stock\n * G2 included), and is gated by the gesture_dots debug setting.\n *\n * The glyphs are pre-baked images (not text): the stock firmware's builtin\n * font has no ●/↑/↓, and a single image element with a stable id and box maps\n * to ONE re-used firmware image container — flash and idle swap its bytes\n * (data-only update, no page rebuild) instead of creating/removing containers.\n */\nimport {useDisplayStore} from \"../stores/display\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport type {SceneElementInput} from \"../utils/display/scene\"\nimport {BgTimer} from \"../utils/timers\"\nimport {GESTURE_DOT_GLYPHS, GESTURE_DOT_GLYPH_SIZE, type GestureDotGlyph} from \"./gestureDotGlyphs\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport sceneRenderer from \"./SceneRenderer\"\n\nconst DOTS_APP_ID = \"system.gesture-dots\"\n/** How long a gesture stays on the indicator. */\nconst GESTURE_FLASH_MS = 700\n/** A press-edge device's tap and hold reports trail its last edge by its double-tap window. */\nconst EDGE_REPORT_GRACE_MS = 1000\nconst MARGIN_X = 16\nconst MARGIN_TOP = 10\n/** Gestures that flash a glyph. */\nconst FLASH_GLYPHS: Record<string, GestureDotGlyph> = {\n single_tap: \"dot\",\n double_tap: \"double\",\n long_press: \"hold\",\n swipe_up: \"up\",\n swipe_down: \"down\",\n swipe_left: \"left\",\n swipe_right: \"right\",\n}\n/** The band's middle finger draws the filled shapes hollow. */\nconst MIDDLE_FINGER_GLYPHS: Partial<Record<GestureDotGlyph, GestureDotGlyph>> = {\n dot: \"ring\",\n double: \"doubleRing\",\n hold: \"holdRing\",\n}\n\n/** `glyph` as `finger` draws it: the middle finger's shapes are hollow. */\nfunction fingerGlyph(glyph: GestureDotGlyph, finger: number): GestureDotGlyph {\n return finger === 1 ? MIDDLE_FINGER_GLYPHS[glyph] ?? glyph : glyph\n}\n\nfunction fingerKey(controllerId: string | undefined, finger: number): string {\n return `${controllerId ?? \"\"}:${finger}`\n}\n\nfunction fingerOf(key: string): number {\n return Number(key.slice(key.lastIndexOf(\":\") + 1))\n}\n\nclass GestureDotsService {\n private started = false\n private flash: GestureDotGlyph | null = null\n private flashTimer: number | null = null\n /** Raw controller holds survive hold recognition/dial events until their own release. Keyed by fingerKey. */\n private presses = new Map<string, {raw: boolean; glyph: GestureDotGlyph}>()\n /** When each finger last sent a press edge (touch_down, or tap_end after one). */\n private lastEdgeAt = new Map<string, number>()\n /** Avoid sending identical images for successive phases of the same press. */\n private renderedGlyph: GestureDotGlyph | null = null\n /** Whether the indicator scene currently has anything on it (needs a clear when the flash ends). */\n private visible = false\n private unsubs: Array<() => void> = []\n\n start(): void {\n if (this.started) return\n this.started = true\n this.unsubs.push(\n // The indicator owns the screen only while it is empty: drop our\n // retained state when something else takes over, and clear any\n // leftover flash when the display empties again.\n localDisplayManager.onActiveDisplayChange((active) => {\n if (active) this.relinquish()\n else this.render()\n }),\n // Overlays do not change the retained main display's occupancy.\n useDisplayStore.subscribe(\n (state) => state.overlayOwner,\n (owner) => {\n if (owner !== null) this.relinquish()\n },\n ),\n )\n }\n\n stop(): void {\n for (const unsub of this.unsubs) unsub()\n this.unsubs = []\n this.relinquish()\n this.lastEdgeAt.clear()\n this.started = false\n }\n\n private resetGesture(): void {\n if (this.flashTimer !== null) BgTimer.clearTimeout(this.flashTimer)\n this.flashTimer = null\n this.flash = null\n this.presses.clear()\n }\n\n /** Another surface owns the display; forget the indicator without painting over it. */\n private relinquish(): void {\n this.resetGesture()\n this.visible = false\n this.renderedGlyph = null\n sceneRenderer.clearApp(DOTS_APP_ID)\n this.trace(\"relinquish\")\n }\n\n private enabled(): boolean {\n return useSettingsStore.getState().getSetting(SETTINGS.gesture_dots.key) === true\n }\n\n /**\n * Flash a gesture on the indicator. Other gesture names are ignored.\n * Claimed gestures cancel any earlier press/flash without starting a new\n * one: the claimant may also consume the release edge. `fingerIndex` is the\n * Neural Band's finger on its pinches and press edges (1 = middle);\n * `controllerId` keeps two controllers' fingers apart.\n */\n noteGesture(gestureName: string, claimed = false, fingerIndex?: number, controllerId?: string): void {\n // A ring's scroll steps ride beside their stroke's one swipe, which already flashed.\n if (!this.started || !this.enabled() || gestureName === \"scroll_up\" || gestureName === \"scroll_down\") return\n this.applyGesture(gestureName, claimed, fingerIndex, controllerId)\n this.trace(\"input\", {gestureName, claimed})\n }\n\n private applyGesture(gestureName: string, claimed: boolean, fingerIndex?: number, controllerId?: string): void {\n if (localDisplayManager.hasActiveDisplay() || useDisplayStore.getState().overlayOwner !== null) {\n this.relinquish()\n return\n }\n if (claimed) {\n this.resetGesture()\n this.render()\n return\n }\n const finger = fingerIndex ?? 0\n const key = this.pressKey(controllerId, finger)\n const press = this.presses.get(key)\n if (gestureName === \"touch_down\" || gestureName === \"tap_start\") {\n if (gestureName === \"touch_down\") this.lastEdgeAt.set(key, Date.now())\n if (press) {\n // Both reports describe one press: after touch_down, tap_start means it is held.\n if (gestureName === \"touch_down\") press.raw = true\n else if (press.raw) press.glyph = fingerGlyph(\"hold\", finger)\n this.render()\n return\n }\n this.clearFlash()\n this.presses.set(key, {raw: gestureName === \"touch_down\", glyph: fingerGlyph(\"dot\", finger)})\n this.render()\n return\n }\n if (gestureName === \"tap_end\") {\n if (!press) return\n this.presses.delete(key)\n if (press.raw) this.lastEdgeAt.set(key, Date.now())\n this.render()\n return\n }\n const shape = FLASH_GLYPHS[gestureName]\n const glyph = shape ? fingerGlyph(shape, finger) : shape\n // The press already showed its own tap and hold; the device's report of\n // them follows after its double-tap window, often after release.\n const shownByPress = gestureName === \"single_tap\" || gestureName === \"long_press\"\n if (press?.raw && (!glyph || shownByPress)) {\n // Pinch-and-turn reports do not release a finger. A recognized hold\n // stays visible until tap_end instead of expiring halfway through it.\n if (gestureName === \"long_press\") press.glyph = fingerGlyph(\"hold\", finger)\n this.render()\n return\n }\n const lastEdgeAt = this.lastEdgeFor(finger)\n if (!press && shownByPress && lastEdgeAt !== undefined && Date.now() - lastEdgeAt < EDGE_REPORT_GRACE_MS) return\n // Completed taps/swipes become one continuous pulse, even if the device\n // omits tap_end. Older tap_start-only devices retain the same fallback.\n this.presses.delete(key)\n if (glyph) this.startFlash(glyph)\n this.render()\n }\n\n /**\n * The press this edge belongs to. Glasses relaying a ring tag its edges\n * unevenly (a touch_down carries the ring's id while the firmware leaves the\n * release's source unspecified, or the reverse), so an edge that misses its\n * exact key joins the one outstanding press of that finger instead of\n * leaving it stuck.\n */\n private pressKey(controllerId: string | undefined, finger: number): string {\n const exact = fingerKey(controllerId, finger)\n if (this.presses.has(exact)) return exact\n if (controllerId !== undefined) {\n const unattributed = fingerKey(undefined, finger)\n return this.presses.has(unattributed) ? unattributed : exact\n }\n const candidates = [...this.presses.keys()].filter((key) => fingerOf(key) === finger)\n return candidates.length === 1 ? candidates[0] : exact\n }\n\n /** The finger's latest press edge under any attribution (see pressKey). */\n private lastEdgeFor(finger: number): number | undefined {\n let latest: number | undefined\n for (const [key, at] of this.lastEdgeAt) {\n if (fingerOf(key) === finger && (latest === undefined || at > latest)) latest = at\n }\n return latest\n }\n\n private clearFlash(): void {\n if (this.flashTimer !== null) BgTimer.clearTimeout(this.flashTimer)\n this.flashTimer = null\n this.flash = null\n }\n\n private startFlash(glyph: GestureDotGlyph): void {\n this.clearFlash()\n this.flash = glyph\n this.flashTimer = BgTimer.setTimeout(() => {\n this.flashTimer = null\n this.flash = null\n this.render()\n this.trace(\"flash-ended\")\n }, GESTURE_FLASH_MS)\n }\n\n /** Debug setting only; no payloads or retained history. Correlate seq with G2PERF. */\n private trace(reason: string, details: Record<string, unknown> = {}): void {\n if (!this.enabled()) return\n console.log(\n \"GESTUREDOTS \" +\n JSON.stringify({\n t: Date.now(),\n reason,\n held: this.presses.size > 0,\n flash: this.flash,\n overlay: useDisplayStore.getState().overlayOwner,\n active: localDisplayManager.hasActiveDisplay(),\n ...details,\n }),\n )\n }\n\n /**\n * Paint or clear the indicator. Nothing is drawn while anything else is on\n * the glasses (miniapp frame or system screen) or when no gesture was just\n * received. The top-right anchor resolves against the device's real canvas,\n * so no size math here.\n *\n * Between flashes the element STAYS in the scene with the blank glyph:\n * removing it would tear down the firmware image container and rebuild the\n * page on every flash (a rebuild storm on stock firmware). Only disabling\n * the setting removes the element outright.\n */\n private render(): void {\n if (!this.started) return\n if (localDisplayManager.hasActiveDisplay() || useDisplayStore.getState().overlayOwner !== null) return\n const shown = Array.from(this.presses.values()).at(-1)?.glyph ?? this.flash\n if (!this.enabled()) {\n if (this.visible) {\n this.visible = false\n this.renderedGlyph = null\n sceneRenderer.emitScene(DOTS_APP_ID, \"main\", [])\n }\n return\n }\n if (!shown && !this.visible) return\n const glyph = shown ?? \"blank\"\n if (glyph === this.renderedGlyph) return\n const elements: SceneElementInput[] = [\n {\n type: \"image\",\n id: \"gesture-dot\",\n box: {\n x: MARGIN_X,\n y: MARGIN_TOP,\n w: GESTURE_DOT_GLYPH_SIZE,\n h: GESTURE_DOT_GLYPH_SIZE,\n anchor: \"top-right\",\n },\n data: GESTURE_DOT_GLYPHS[glyph],\n },\n ]\n this.visible = true\n this.renderedGlyph = glyph\n const result = sceneRenderer.emitScene(DOTS_APP_ID, \"main\", elements)\n this.trace(\"render\", {\n glyph,\n ...(result?.kind === \"scene\" ? {seq: result.seq, sent: result.sent} : {}),\n })\n }\n}\n\nconst gestureDotsService = new GestureDotsService()\nexport default gestureDotsService\nexport {GestureDotsService}\n"]}
|
|
@@ -9,7 +9,8 @@ import { type TouchEvent } from "@glassly/bluetooth-sdk";
|
|
|
9
9
|
* dashboard's claim is for the screen with no app in it. Head position
|
|
10
10
|
* (head_up / head_down) is a gesture like any other: the dashboard claims it
|
|
11
11
|
* while it runs, and an unclaimed head_up on glasses that defer the dashboard
|
|
12
|
-
* to the phone opens the native one, as it would with no app at all.
|
|
12
|
+
* to the phone opens the native one, as it would with no app at all. A ring
|
|
13
|
+
* slide step becomes one swipe per stroke plus a scroll step (see above).
|
|
13
14
|
*/
|
|
14
15
|
export declare function dispatchTouchEvent(event: TouchEvent): void;
|
|
15
16
|
export declare function resetTouchInput(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GlassesTouchInput.d.ts","sourceRoot":"","sources":["../../src/services/GlassesTouchInput.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,UAAU,EAAC,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"GlassesTouchInput.d.ts","sourceRoot":"","sources":["../../src/services/GlassesTouchInput.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,UAAU,EAAC,MAAM,wBAAwB,CAAA;AAkFpE;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAQ1D;AA6DD,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CACzB,WAAW,EACP,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,SAAS,GACT,eAAe,GACf,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,YAAY,GACZ,SAAS,GACT,WAAW,GACX,SAAS,GACT,WAAW,GACd,IAAI,CAQN"}
|
|
@@ -23,23 +23,41 @@ const miniappExitGesture = new MiniappExitGesture({
|
|
|
23
23
|
setTimeout: (callback, delay) => BgTimer.setTimeout(callback, delay),
|
|
24
24
|
clearTimeout: (timerId) => BgTimer.clearTimeout(timerId),
|
|
25
25
|
});
|
|
26
|
-
//
|
|
27
|
-
// (deviceModel G2) or the ring sends
|
|
28
|
-
// both tag source 2.
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
// The ring reports a slide once per step of travel, whether the CFW forwards
|
|
27
|
+
// it (deviceModel G2) or the ring sends it to the phone itself (deviceModel
|
|
28
|
+
// R1); both tag source 2. One press is one swipe: the stroke's first step goes
|
|
29
|
+
// out as swipe_up / swipe_down, and every step, the first included, as
|
|
30
|
+
// scroll_up / scroll_down with its distance and speed for smooth scrolling. A
|
|
31
|
+
// stroke ends at the ring's next touch_down or tap_end, or after
|
|
32
|
+
// RING_STROKE_IDLE_MS without a step when a report was lost. Reversing within
|
|
33
|
+
// a stroke fires no swipe (a finger rolling back at lift-off). Native receipt
|
|
34
|
+
// timestamps keep strokes apart when JS drains a backlog.
|
|
35
|
+
const RING_STROKE_IDLE_MS = 1000;
|
|
36
|
+
/** Per ring, when the open stroke's last step arrived. */
|
|
37
|
+
const ringStrokes = new Map();
|
|
38
|
+
function ringKey(event) {
|
|
39
|
+
return event.controllerId ?? `source:${event.source}`;
|
|
40
|
+
}
|
|
41
|
+
function isRingSlideStep(event) {
|
|
42
|
+
return event.source === 2 && (event.gestureName === "swipe_up" || event.gestureName === "swipe_down");
|
|
43
|
+
}
|
|
44
|
+
function opensRingStroke(event) {
|
|
45
|
+
const last = ringStrokes.get(ringKey(event));
|
|
46
|
+
ringStrokes.set(ringKey(event), event.timestamp);
|
|
38
47
|
const elapsed = last === undefined ? null : event.timestamp - last;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
return elapsed === null || elapsed < 0 || elapsed >= RING_STROKE_IDLE_MS;
|
|
49
|
+
}
|
|
50
|
+
function endRingStroke(event) {
|
|
51
|
+
if (event.source === 2 && (event.gestureName === "touch_down" || event.gestureName === "tap_end")) {
|
|
52
|
+
ringStrokes.delete(ringKey(event));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** The discrete swipe a stroke opens with: the step's travel belongs to scroll_*. */
|
|
56
|
+
function strokeSwipe(step) {
|
|
57
|
+
const swipe = { ...step };
|
|
58
|
+
delete swipe.distance;
|
|
59
|
+
delete swipe.speed;
|
|
60
|
+
return swipe;
|
|
43
61
|
}
|
|
44
62
|
/**
|
|
45
63
|
* Miniapps' copy of the touch: `input2d: true` when it came from a controller
|
|
@@ -64,9 +82,20 @@ function toMiniapps(event) {
|
|
|
64
82
|
* dashboard's claim is for the screen with no app in it. Head position
|
|
65
83
|
* (head_up / head_down) is a gesture like any other: the dashboard claims it
|
|
66
84
|
* while it runs, and an unclaimed head_up on glasses that defer the dashboard
|
|
67
|
-
* to the phone opens the native one, as it would with no app at all.
|
|
85
|
+
* to the phone opens the native one, as it would with no app at all. A ring
|
|
86
|
+
* slide step becomes one swipe per stroke plus a scroll step (see above).
|
|
68
87
|
*/
|
|
69
88
|
export function dispatchTouchEvent(event) {
|
|
89
|
+
if (isRingSlideStep(event)) {
|
|
90
|
+
if (opensRingStroke(event))
|
|
91
|
+
routeTouchEvent(strokeSwipe(event));
|
|
92
|
+
routeTouchEvent({ ...event, gestureName: event.gestureName === "swipe_up" ? "scroll_up" : "scroll_down" });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
endRingStroke(event);
|
|
96
|
+
routeTouchEvent(event);
|
|
97
|
+
}
|
|
98
|
+
function routeTouchEvent(event) {
|
|
70
99
|
const gesture = event.gestureName;
|
|
71
100
|
const claimant = localMiniappRuntime.gestureClaimant(gesture);
|
|
72
101
|
// The Neural Band's middle finger (index 1) shares the tap names but is left
|
|
@@ -94,17 +123,12 @@ export function dispatchTouchEvent(event) {
|
|
|
94
123
|
}
|
|
95
124
|
// The press may have started before this app claimed its release edge.
|
|
96
125
|
gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId);
|
|
97
|
-
if (!shouldForwardScroll(event))
|
|
98
|
-
return;
|
|
99
126
|
localMiniappRuntime.forwardEvent("touch_event", toMiniapps(event), undefined, claimant);
|
|
100
127
|
return;
|
|
101
128
|
}
|
|
102
129
|
// Gesture-dots debug indicator (any device, gated by its setting). It
|
|
103
|
-
// only paints while the screen is otherwise empty.
|
|
104
|
-
// scroll repeats here so their completion still clears a held dot.
|
|
130
|
+
// only paints while the screen is otherwise empty.
|
|
105
131
|
gestureDotsService.noteGesture(gesture, false, event.index, event.controllerId);
|
|
106
|
-
if (!shouldForwardScroll(event))
|
|
107
|
-
return;
|
|
108
132
|
localMiniappRuntime.forwardEvent("touch_event", toMiniapps(event));
|
|
109
133
|
// Nobody claimed a double tap or a head-up: the glasses' native dashboard.
|
|
110
134
|
// On eligible Glassly CFW glasses the dashboard miniapp claims both while it
|
|
@@ -124,7 +148,7 @@ export function dispatchTouchEvent(event) {
|
|
|
124
148
|
}
|
|
125
149
|
export function resetTouchInput() {
|
|
126
150
|
miniappExitGesture.reset();
|
|
127
|
-
|
|
151
|
+
ringStrokes.clear();
|
|
128
152
|
}
|
|
129
153
|
/** Phone mirror input uses the same dashboard/miniapp policy as real glasses. */
|
|
130
154
|
export function sendGesture(gestureName) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GlassesTouchInput.js","sourceRoot":"","sources":["../../src/services/GlassesTouchInput.ts"],"names":[],"mappings":"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","sourcesContent":["import {useDisplayStore} from \"../stores/display\"\nimport {DeviceModels, type TouchEvent} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\n\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {BgTimer} from \"../utils/timers\"\nimport {hasInput2d} from \"./controllers\"\nimport dashboardOpenTrace from \"./debug/DashboardOpenTrace\"\nimport gestureDotsService from \"./GestureDotsService\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport {MiniappExitGesture} from \"./MiniappExitGesture\"\nimport nativeNavigationService from \"./NativeNavigationService\"\nimport {isOverlayApp} from \"./pages/overlayApps\"\nimport {pagesEligible} from \"./pages/pageEligibility\"\n\nconst miniappExitGesture = new MiniappExitGesture({\n getState: () => useAppStatusStore.getState(),\n getTargetPackageName: () => localDisplayManager.getMiniappToLeave(),\n beginExitNotice: (packageName, text) => localDisplayManager.beginMiniappExit(packageName, text),\n finishExitNotice: (packageName) => localDisplayManager.finishMiniappExit(packageName),\n cancelExitNotice: (packageName) => localDisplayManager.cancelMiniappExit(packageName),\n setTimeout: (callback, delay) => BgTimer.setTimeout(callback, delay),\n clearTimeout: (timerId) => BgTimer.clearTimeout(timerId),\n})\n\n// Raw ring reports include every scroll step, whether the CFW forwards them\n// (deviceModel G2) or the ring sends them to the phone itself (deviceModel R1);\n// both tag source 2. Rate-limit scroll actions separately from press/release\n// recognition, per ring, using native receipt timestamps so a JS backlog does\n// not collapse otherwise separate gestures.\nconst RING_SCROLL_INTERVAL_MS = 100\nconst lastRingScrollAt = new Map<string, number>()\n\nfunction shouldForwardScroll(event: TouchEvent): boolean {\n if (event.source !== 2 || (event.gestureName !== \"swipe_up\" && event.gestureName !== \"swipe_down\")) return true\n const ring = event.controllerId ?? `source:${event.source}`\n const last = lastRingScrollAt.get(ring)\n const elapsed = last === undefined ? null : event.timestamp - last\n if (elapsed !== null && elapsed >= 0 && elapsed < RING_SCROLL_INTERVAL_MS) return false\n lastRingScrollAt.set(ring, event.timestamp)\n return true\n}\n\n/**\n * Miniapps' copy of the touch: `input2d: true` when it came from a controller\n * that swipes in two dimensions (the Neural Band), the field absent otherwise,\n * so a 1D swipe from the temples or the ring stays tellable while a band is\n * connected under the same gesture names.\n */\nfunction toMiniapps(event: TouchEvent): TouchEvent & {input2d?: true} {\n const id = event.controllerId\n if (id === undefined) return event\n const model = useGlassesStore.getState().controllers?.[id]?.model\n return model !== undefined && hasInput2d(model) ? {...event, input2d: true} : event\n}\n\n/**\n * Route both real glasses input and phone mirror controls through the same\n * policy. A claimed gesture goes to the app holding the claim, alone; an\n * unclaimed one goes to every subscriber and then the engine's default. The\n * one rule ahead of the claims: an unclaimed double tap leaves the miniapp\n * the wearer is in — the one visibly on the glasses, else the running\n * standard app on an empty screen — whoever else claimed the gesture; the\n * dashboard's claim is for the screen with no app in it. Head position\n * (head_up / head_down) is a gesture like any other: the dashboard claims it\n * while it runs, and an unclaimed head_up on glasses that defer the dashboard\n * to the phone opens the native one, as it would with no app at all.\n */\nexport function dispatchTouchEvent(event: TouchEvent): void {\n const gesture = event.gestureName\n const claimant = localMiniappRuntime.gestureClaimant(gesture)\n // The Neural Band's middle finger (index 1) shares the tap names but is left\n // to miniapps: the engine's own double-tap behaviors are the index finger's.\n const osDoubleTap = gesture === \"double_tap\" && event.index !== 1\n const osHeadUp = gesture === \"head_up\"\n\n if (osDoubleTap) {\n // The app being left opts out of OS exit by registering double tap (the\n // terminal's back-to-menu). The latest claimant still receives it. Otherwise\n // the tap means \"leave the app\". The controller shows the bounded OS\n // notice first, then uses the normal store stop path (and closes its\n // phone overlay).\n const leaving = localDisplayManager.getMiniappToLeave()\n if (\n leaving !== null &&\n !localMiniappRuntime.hasGestureClaim(leaving, gesture) &&\n miniappExitGesture.tryExitMiniapp()\n ) {\n gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)\n return\n }\n }\n\n if (claimant !== null) {\n // An overlay app claims the double tap and head-up on an empty screen to open the dashboard.\n if ((gesture === \"double_tap\" || osHeadUp) && isOverlayApp(claimant)) {\n dashboardOpenTrace.trigger(osHeadUp ? \"head-up\" : \"double-tap\", event.timestamp)\n }\n // The press may have started before this app claimed its release edge.\n gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)\n if (!shouldForwardScroll(event)) return\n localMiniappRuntime.forwardEvent(\"touch_event\", toMiniapps(event), undefined, claimant)\n return\n }\n\n // Gesture-dots debug indicator (any device, gated by its setting). It\n // only paints while the screen is otherwise empty. Process even suppressed\n // scroll repeats here so their completion still clears a held dot.\n gestureDotsService.noteGesture(gesture, false, event.index, event.controllerId)\n\n if (!shouldForwardScroll(event)) return\n localMiniappRuntime.forwardEvent(\"touch_event\", toMiniapps(event))\n\n // Nobody claimed a double tap or a head-up: the glasses' native dashboard.\n // On eligible Glassly CFW glasses the dashboard miniapp claims both while it\n // runs (ADR-0006); stock firmware keeps its native dashboard (ADR-0002) and\n // opens it on a head-up by itself, so only the double tap is relayed there.\n // The modal nav UI owns the screen; showDashboard would fight it. Without\n // glasses there is no dashboard to open, and the mirror keeps its frame.\n // pagesEligible: connected Glassly CFW glasses, whose firmware leaves the\n // dashboard to the phone; other firmware opens its own on a lift.\n const opensNativeDashboard = osDoubleTap || (osHeadUp && pagesEligible())\n if (opensNativeDashboard && !nativeNavigationService.isActive && isGlassesConnected(useGlassesStore.getState().connection)) {\n useDisplayStore.getState().setDisplayEvent({layout: {layoutType: \"unavailable\", reason: \"firmware-dashboard\"}})\n void Promise.resolve(BluetoothSdk.showDashboard()).catch((err) => {\n console.error(\"DeviceEventRouter: showDashboard failed:\", err)\n })\n }\n}\n\nexport function resetTouchInput(): void {\n miniappExitGesture.reset()\n lastRingScrollAt.clear()\n}\n\n/** Phone mirror input uses the same dashboard/miniapp policy as real glasses. */\nexport function sendGesture(\n gestureName:\n | \"single_tap\"\n | \"tap_start\"\n | \"double_tap\"\n | \"tap_end\"\n | \"tap_then_hold\"\n | \"touch_down\"\n | \"swipe_up\"\n | \"swipe_down\"\n | \"swipe_left\"\n | \"swipe_right\"\n | \"long_press\"\n | \"dial_up\"\n | \"dial_down\"\n | \"head_up\"\n | \"head_down\",\n): void {\n const model = useGlassesStore.getState().deviceModel\n dispatchTouchEvent({\n type: \"touch_event\",\n gestureName,\n deviceModel: Object.values(DeviceModels).find((candidate) => candidate === model) ?? DeviceModels.Simulated,\n timestamp: Date.now(),\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"GlassesTouchInput.js","sourceRoot":"","sources":["../../src/services/GlassesTouchInput.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,YAAY,EAAkB,MAAM,wBAAwB,CAAA;AACpE,OAAO,YAAY,MAAM,iCAAiC,CAAA;AAE1D,OAAO,EAAC,iBAAiB,EAAC,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAC,UAAU,EAAC,MAAM,eAAe,CAAA;AACxC,OAAO,kBAAkB,MAAM,4BAA4B,CAAA;AAC3D,OAAO,kBAAkB,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAC,kBAAkB,EAAC,MAAM,oBAAoB,CAAA;AACrD,OAAO,mBAAmB,MAAM,uBAAuB,CAAA;AACvD,OAAO,mBAAmB,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAA;AACvD,OAAO,uBAAuB,MAAM,2BAA2B,CAAA;AAC/D,OAAO,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAC,aAAa,EAAC,MAAM,yBAAyB,CAAA;AAErD,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,CAAC;IAChD,QAAQ,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,QAAQ,EAAE;IAC5C,oBAAoB,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,iBAAiB,EAAE;IACnE,eAAe,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC;IAC/F,gBAAgB,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,WAAW,CAAC;IACrF,gBAAgB,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,WAAW,CAAC;IACrF,UAAU,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC;IACpE,YAAY,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC;CACzD,CAAC,CAAA;AAEF,6EAA6E;AAC7E,4EAA4E;AAC5E,+EAA+E;AAC/E,uEAAuE;AACvE,8EAA8E;AAC9E,iEAAiE;AACjE,8EAA8E;AAC9E,8EAA8E;AAC9E,0DAA0D;AAC1D,MAAM,mBAAmB,GAAG,IAAI,CAAA;AAChC,0DAA0D;AAC1D,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAA;AAE7C,SAAS,OAAO,CAAC,KAAiB;IAChC,OAAO,KAAK,CAAC,YAAY,IAAI,UAAU,KAAK,CAAC,MAAM,EAAE,CAAA;AACvD,CAAC;AAED,SAAS,eAAe,CAAC,KAAiB;IACxC,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,KAAK,UAAU,IAAI,KAAK,CAAC,WAAW,KAAK,YAAY,CAAC,CAAA;AACvG,CAAC;AAED,SAAS,eAAe,CAAC,KAAiB;IACxC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;IAC5C,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;IAChD,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAA;IAClE,OAAO,OAAO,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,IAAI,mBAAmB,CAAA;AAC1E,CAAC;AAED,SAAS,aAAa,CAAC,KAAiB;IACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,KAAK,YAAY,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,EAAE,CAAC;QAClG,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;IACpC,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,WAAW,CAAC,IAAgB;IACnC,MAAM,KAAK,GAAG,EAAC,GAAG,IAAI,EAAC,CAAA;IACvB,OAAO,KAAK,CAAC,QAAQ,CAAA;IACrB,OAAO,KAAK,CAAC,KAAK,CAAA;IAClB,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,KAAiB;IACnC,MAAM,EAAE,GAAG,KAAK,CAAC,YAAY,CAAA;IAC7B,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IAClC,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,CAAA;IACjE,OAAO,KAAK,KAAK,SAAS,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAC,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,EAAC,CAAC,CAAC,CAAC,KAAK,CAAA;AACrF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAiB;IAClD,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,IAAI,eAAe,CAAC,KAAK,CAAC;YAAE,eAAe,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;QAC/D,eAAe,CAAC,EAAC,GAAG,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,EAAC,CAAC,CAAA;QACxG,OAAM;IACR,CAAC;IACD,aAAa,CAAC,KAAK,CAAC,CAAA;IACpB,eAAe,CAAC,KAAK,CAAC,CAAA;AACxB,CAAC;AAED,SAAS,eAAe,CAAC,KAAiB;IACxC,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAAA;IACjC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;IAC7D,6EAA6E;IAC7E,6EAA6E;IAC7E,MAAM,WAAW,GAAG,OAAO,KAAK,YAAY,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC,CAAA;IACjE,MAAM,QAAQ,GAAG,OAAO,KAAK,SAAS,CAAA;IAEtC,IAAI,WAAW,EAAE,CAAC;QAChB,wEAAwE;QACxE,6EAA6E;QAC7E,qEAAqE;QACrE,qEAAqE;QACrE,kBAAkB;QAClB,MAAM,OAAO,GAAG,mBAAmB,CAAC,iBAAiB,EAAE,CAAA;QACvD,IACE,OAAO,KAAK,IAAI;YAChB,CAAC,mBAAmB,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC;YACtD,kBAAkB,CAAC,cAAc,EAAE,EACnC,CAAC;YACD,kBAAkB,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA;YAC9E,OAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,6FAA6F;QAC7F,IAAI,CAAC,OAAO,KAAK,YAAY,IAAI,QAAQ,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrE,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAA;QAClF,CAAC;QACD,uEAAuE;QACvE,kBAAkB,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA;QAC9E,mBAAmB,CAAC,YAAY,CAAC,aAAa,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAA;QACvF,OAAM;IACR,CAAC;IAED,sEAAsE;IACtE,mDAAmD;IACnD,kBAAkB,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA;IAE/E,mBAAmB,CAAC,YAAY,CAAC,aAAa,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;IAElE,2EAA2E;IAC3E,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,kEAAkE;IAClE,MAAM,oBAAoB,GAAG,WAAW,IAAI,CAAC,QAAQ,IAAI,aAAa,EAAE,CAAC,CAAA;IACzE,IAAI,oBAAoB,IAAI,CAAC,uBAAuB,CAAC,QAAQ,IAAI,kBAAkB,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3H,eAAe,CAAC,QAAQ,EAAE,CAAC,eAAe,CAAC,EAAC,MAAM,EAAE,EAAC,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,oBAAoB,EAAC,EAAC,CAAC,CAAA;QAC/G,KAAK,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YAC/D,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,GAAG,CAAC,CAAA;QAChE,CAAC,CAAC,CAAA;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,kBAAkB,CAAC,KAAK,EAAE,CAAA;IAC1B,WAAW,CAAC,KAAK,EAAE,CAAA;AACrB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,WAAW,CACzB,WAee;IAEf,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAA;IACpD,kBAAkB,CAAC;QACjB,IAAI,EAAE,aAAa;QACnB,WAAW;QACX,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,KAAK,CAAC,IAAI,YAAY,CAAC,SAAS;QAC3G,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;KACtB,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import {useDisplayStore} from \"../stores/display\"\nimport {DeviceModels, type TouchEvent} from \"@glassly/bluetooth-sdk\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\n\nimport {useAppStatusStore} from \"../stores/apps\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {BgTimer} from \"../utils/timers\"\nimport {hasInput2d} from \"./controllers\"\nimport dashboardOpenTrace from \"./debug/DashboardOpenTrace\"\nimport gestureDotsService from \"./GestureDotsService\"\nimport {isGlassesConnected} from \"./GlassesReadiness\"\nimport localDisplayManager from \"./LocalDisplayManager\"\nimport localMiniappRuntime from \"./LocalMiniappRuntime\"\nimport {MiniappExitGesture} from \"./MiniappExitGesture\"\nimport nativeNavigationService from \"./NativeNavigationService\"\nimport {isOverlayApp} from \"./pages/overlayApps\"\nimport {pagesEligible} from \"./pages/pageEligibility\"\n\nconst miniappExitGesture = new MiniappExitGesture({\n getState: () => useAppStatusStore.getState(),\n getTargetPackageName: () => localDisplayManager.getMiniappToLeave(),\n beginExitNotice: (packageName, text) => localDisplayManager.beginMiniappExit(packageName, text),\n finishExitNotice: (packageName) => localDisplayManager.finishMiniappExit(packageName),\n cancelExitNotice: (packageName) => localDisplayManager.cancelMiniappExit(packageName),\n setTimeout: (callback, delay) => BgTimer.setTimeout(callback, delay),\n clearTimeout: (timerId) => BgTimer.clearTimeout(timerId),\n})\n\n// The ring reports a slide once per step of travel, whether the CFW forwards\n// it (deviceModel G2) or the ring sends it to the phone itself (deviceModel\n// R1); both tag source 2. One press is one swipe: the stroke's first step goes\n// out as swipe_up / swipe_down, and every step, the first included, as\n// scroll_up / scroll_down with its distance and speed for smooth scrolling. A\n// stroke ends at the ring's next touch_down or tap_end, or after\n// RING_STROKE_IDLE_MS without a step when a report was lost. Reversing within\n// a stroke fires no swipe (a finger rolling back at lift-off). Native receipt\n// timestamps keep strokes apart when JS drains a backlog.\nconst RING_STROKE_IDLE_MS = 1000\n/** Per ring, when the open stroke's last step arrived. */\nconst ringStrokes = new Map<string, number>()\n\nfunction ringKey(event: TouchEvent): string {\n return event.controllerId ?? `source:${event.source}`\n}\n\nfunction isRingSlideStep(event: TouchEvent): boolean {\n return event.source === 2 && (event.gestureName === \"swipe_up\" || event.gestureName === \"swipe_down\")\n}\n\nfunction opensRingStroke(event: TouchEvent): boolean {\n const last = ringStrokes.get(ringKey(event))\n ringStrokes.set(ringKey(event), event.timestamp)\n const elapsed = last === undefined ? null : event.timestamp - last\n return elapsed === null || elapsed < 0 || elapsed >= RING_STROKE_IDLE_MS\n}\n\nfunction endRingStroke(event: TouchEvent): void {\n if (event.source === 2 && (event.gestureName === \"touch_down\" || event.gestureName === \"tap_end\")) {\n ringStrokes.delete(ringKey(event))\n }\n}\n\n/** The discrete swipe a stroke opens with: the step's travel belongs to scroll_*. */\nfunction strokeSwipe(step: TouchEvent): TouchEvent {\n const swipe = {...step}\n delete swipe.distance\n delete swipe.speed\n return swipe\n}\n\n/**\n * Miniapps' copy of the touch: `input2d: true` when it came from a controller\n * that swipes in two dimensions (the Neural Band), the field absent otherwise,\n * so a 1D swipe from the temples or the ring stays tellable while a band is\n * connected under the same gesture names.\n */\nfunction toMiniapps(event: TouchEvent): TouchEvent & {input2d?: true} {\n const id = event.controllerId\n if (id === undefined) return event\n const model = useGlassesStore.getState().controllers?.[id]?.model\n return model !== undefined && hasInput2d(model) ? {...event, input2d: true} : event\n}\n\n/**\n * Route both real glasses input and phone mirror controls through the same\n * policy. A claimed gesture goes to the app holding the claim, alone; an\n * unclaimed one goes to every subscriber and then the engine's default. The\n * one rule ahead of the claims: an unclaimed double tap leaves the miniapp\n * the wearer is in — the one visibly on the glasses, else the running\n * standard app on an empty screen — whoever else claimed the gesture; the\n * dashboard's claim is for the screen with no app in it. Head position\n * (head_up / head_down) is a gesture like any other: the dashboard claims it\n * while it runs, and an unclaimed head_up on glasses that defer the dashboard\n * to the phone opens the native one, as it would with no app at all. A ring\n * slide step becomes one swipe per stroke plus a scroll step (see above).\n */\nexport function dispatchTouchEvent(event: TouchEvent): void {\n if (isRingSlideStep(event)) {\n if (opensRingStroke(event)) routeTouchEvent(strokeSwipe(event))\n routeTouchEvent({...event, gestureName: event.gestureName === \"swipe_up\" ? \"scroll_up\" : \"scroll_down\"})\n return\n }\n endRingStroke(event)\n routeTouchEvent(event)\n}\n\nfunction routeTouchEvent(event: TouchEvent): void {\n const gesture = event.gestureName\n const claimant = localMiniappRuntime.gestureClaimant(gesture)\n // The Neural Band's middle finger (index 1) shares the tap names but is left\n // to miniapps: the engine's own double-tap behaviors are the index finger's.\n const osDoubleTap = gesture === \"double_tap\" && event.index !== 1\n const osHeadUp = gesture === \"head_up\"\n\n if (osDoubleTap) {\n // The app being left opts out of OS exit by registering double tap (the\n // terminal's back-to-menu). The latest claimant still receives it. Otherwise\n // the tap means \"leave the app\". The controller shows the bounded OS\n // notice first, then uses the normal store stop path (and closes its\n // phone overlay).\n const leaving = localDisplayManager.getMiniappToLeave()\n if (\n leaving !== null &&\n !localMiniappRuntime.hasGestureClaim(leaving, gesture) &&\n miniappExitGesture.tryExitMiniapp()\n ) {\n gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)\n return\n }\n }\n\n if (claimant !== null) {\n // An overlay app claims the double tap and head-up on an empty screen to open the dashboard.\n if ((gesture === \"double_tap\" || osHeadUp) && isOverlayApp(claimant)) {\n dashboardOpenTrace.trigger(osHeadUp ? \"head-up\" : \"double-tap\", event.timestamp)\n }\n // The press may have started before this app claimed its release edge.\n gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)\n localMiniappRuntime.forwardEvent(\"touch_event\", toMiniapps(event), undefined, claimant)\n return\n }\n\n // Gesture-dots debug indicator (any device, gated by its setting). It\n // only paints while the screen is otherwise empty.\n gestureDotsService.noteGesture(gesture, false, event.index, event.controllerId)\n\n localMiniappRuntime.forwardEvent(\"touch_event\", toMiniapps(event))\n\n // Nobody claimed a double tap or a head-up: the glasses' native dashboard.\n // On eligible Glassly CFW glasses the dashboard miniapp claims both while it\n // runs (ADR-0006); stock firmware keeps its native dashboard (ADR-0002) and\n // opens it on a head-up by itself, so only the double tap is relayed there.\n // The modal nav UI owns the screen; showDashboard would fight it. Without\n // glasses there is no dashboard to open, and the mirror keeps its frame.\n // pagesEligible: connected Glassly CFW glasses, whose firmware leaves the\n // dashboard to the phone; other firmware opens its own on a lift.\n const opensNativeDashboard = osDoubleTap || (osHeadUp && pagesEligible())\n if (opensNativeDashboard && !nativeNavigationService.isActive && isGlassesConnected(useGlassesStore.getState().connection)) {\n useDisplayStore.getState().setDisplayEvent({layout: {layoutType: \"unavailable\", reason: \"firmware-dashboard\"}})\n void Promise.resolve(BluetoothSdk.showDashboard()).catch((err) => {\n console.error(\"DeviceEventRouter: showDashboard failed:\", err)\n })\n }\n}\n\nexport function resetTouchInput(): void {\n miniappExitGesture.reset()\n ringStrokes.clear()\n}\n\n/** Phone mirror input uses the same dashboard/miniapp policy as real glasses. */\nexport function sendGesture(\n gestureName:\n | \"single_tap\"\n | \"tap_start\"\n | \"double_tap\"\n | \"tap_end\"\n | \"tap_then_hold\"\n | \"touch_down\"\n | \"swipe_up\"\n | \"swipe_down\"\n | \"swipe_left\"\n | \"swipe_right\"\n | \"long_press\"\n | \"dial_up\"\n | \"dial_down\"\n | \"head_up\"\n | \"head_down\",\n): void {\n const model = useGlassesStore.getState().deviceModel\n dispatchTouchEvent({\n type: \"touch_event\",\n gestureName,\n deviceModel: Object.values(DeviceModels).find((candidate) => candidate === model) ?? DeviceModels.Simulated,\n timestamp: Date.now(),\n })\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,OAAO,EAML,KAAK,YAAY,EAEjB,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EAEvB,MAAM,wBAAwB,CAAA;AAI/B,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAC,CAAA;AAEvE,MAAM,MAAM,eAAe;AACzB;;;;;GAKG;AACD;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC;AACrG,0GAA0G;GACxG;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAC;AACrF,uDAAuD;GACrD;IAAC,IAAI,EAAE,YAAY,CAAA;CAAC,CAAA;AAExB,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,wFAAwF;IACxF,SAAS,EAAE,OAAO,CAAA;IAClB,+CAA+C;IAC/C,UAAU,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;
|
|
1
|
+
{"version":3,"file":"SceneRenderer.d.ts","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,7 +26,9 @@ const MAX_SETTLE_MS = 255 * 33;
|
|
|
26
26
|
function settleTimeOf(elements) {
|
|
27
27
|
let ms = 0;
|
|
28
28
|
for (const el of elements) {
|
|
29
|
-
|
|
29
|
+
// Entering and leaving elements tween too, from or to their offset.
|
|
30
|
+
const edge = el.transition?.exit !== undefined || (el.change === "created" && el.transition?.enter !== undefined);
|
|
31
|
+
if (el.change !== "updated" && el.change !== "moved" && !edge)
|
|
30
32
|
continue;
|
|
31
33
|
const t = el.transition?.durationMs ?? 0;
|
|
32
34
|
// Rotation has its own millisecond clock, beyond the geometry frame limit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SceneRenderer.js","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,gBAAgB,MAAM,oBAAoB,CAAA;AACjD,OAAO,YAAY,MAAM,iCAAiC,CAAA;AAC1D,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,QAAQ,EAAE,gBAAgB,EAAC,MAAM,oBAAoB,CAAA;AAC7D,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,WAAW,EAAC,MAAM,UAAU,CAAA;AACpC,OAAO,EAAC,wBAAwB,EAAC,MAAM,yBAAyB,CAAA;AAChE,OAAO,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAA;AACvC,OAAO,EACL,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,cAAc,EACd,UAAU,GAMX,MAAM,wBAAwB,CAAA;AAE/B,MAAM,OAAO,GAAG,gBAAgB,CAAA;AA2BhC,kGAAkG;AAClG,MAAM,aAAa,GAAG,GAAG,GAAG,EAAE,CAAA;AAE9B,2FAA2F;AAC3F,SAAS,YAAY,CAAC,QAAwB;IAC5C,IAAI,EAAE,GAAG,CAAC,CAAA;IACV,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,IAAI,EAAE,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,CAAC,MAAM,KAAK,OAAO;YAAE,SAAQ;QAC9D,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,IAAI,CAAC,CAAA;QACxC,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAA;QAC/E,IAAI,QAAQ,GAAG,EAAE;YAAE,EAAE,GAAG,QAAQ,CAAA;IAClC,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED,MAAM,aAAa;IACT,MAAM,CAAC,QAAQ,GAAyB,IAAI,CAAA;IAC5C,KAAK,GAAG,IAAI,UAAU,EAAE,CAAA;IACxB,UAAU,GAAG,CAAC,CAAA;IACd,OAAO,GAAG,IAAI,GAAG,EAAoE,CAAA;IACrF,YAAY,GAAgC,IAAI,CAAA;IAChD,UAAU,GAAgC,IAAI,CAAA;IAC9C,gBAAgB,GAAG,IAAI,GAAG,EAAyB,CAAA;IAEpD,MAAM,CAAC,WAAW;QACvB,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC;YAC5B,aAAa,CAAC,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAA;QAC9C,CAAC;QACD,OAAO,aAAa,CAAC,QAAQ,CAAA;IAC/B,CAAC;IAED,8EAA8E;IAC9E,eAAe;IACf,8EAA8E;IAE9E;;;OAGG;IACI,mBAAmB;QACxB,wEAAwE;QACxE,+CAA+C;QAC/C,MAAM,KAAK,GACR,gBAAgB,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAwB;YAC5F,eAAe,CAAC,QAAQ,EAAE,CAAC,WAAkC,CAAA;QAChE,MAAM,OAAO,GAAG,wBAAwB,CAAC,CAAC,KAAK,IAAI,WAAW,CAAC,IAAI,CAAgB,CAAC,EAAE,OAAO,CAAA;QAC7F,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QACzB,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,UAAU,EAAE,KAAK,IAAI,gBAAgB,CAAC,UAAU,EAAE,CAAC,cAAc;YACjG,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,UAAU,EAAE,MAAM,IAAI,gBAAgB,CAAC,UAAU,EAAE,CAAC,eAAe,IAAI,GAAG;YAC5G,WAAW,EAAE,OAAO,CAAC,WAAW,KAAK,IAAI;YACzC,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,CAAC;YAC7C,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,CAAC;YAC/C,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;YAC5B,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,KAAK;YACrC,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,KAAK;YACnC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,KAAK;YAC7B,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,CAAC;YAE7C,aAAa,EAAE,OAAO,CAAC,aAAa,IAAI,KAAK;SAC9C,CAAA;IACH,CAAC;IAED,8EAA8E;IAC9E,2EAA2E;IAC3E,8EAA8E;IAE9E;;;;OAIG;IACI,mBAAmB,CAAC,MAAoB,EAAE,IAA8B;QAC7E,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAA;QACpB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAA;QACrB,MAAM,SAAS,GAAG,MAAM,CAAC,SAAuC,CAAA;QAEhE,QAAQ,MAAM,CAAC,UAAU,EAAE,CAAC;YAC1B,KAAK,WAAW;gBACd,OAAO;oBACL;wBACE,IAAI,EAAE,MAAM;wBACZ,EAAE,EAAE,YAAY;wBAChB,GAAG,EAAE,EAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAC;wBAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;wBAC/B,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,EAAC,SAAS,EAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC3C;iBACF,CAAA;YACH,KAAK,kBAAkB,CAAC,CAAC,CAAC;gBACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;gBAC9B,OAAO;oBACL;wBACE,IAAI,EAAE,MAAM;wBACZ,EAAE,EAAE,YAAY;wBAChB,GAAG,EAAE,EAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAC;wBAChC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;wBAClC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,EAAC,SAAS,EAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC3C;oBACD;wBACE,IAAI,EAAE,MAAM;wBACZ,EAAE,EAAE,aAAa;wBACjB,GAAG,EAAE,EAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,CAAC,EAAC;wBACvC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;wBACrC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,EAAC,SAAS,EAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC3C;iBACF,CAAA;YACH,CAAC;YACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACtB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;gBAC9C,OAAO;oBACL,EAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,EAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,EAAC;oBACvG;wBACE,IAAI,EAAE,MAAM;wBACZ,EAAE,EAAE,YAAY;wBAChB,GAAG,EAAE,EAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,MAAM,EAAC;wBAC3C,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;wBAC/B,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,EAAC,SAAS,EAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC3C;iBACF,CAAA;YACH,CAAC;YACD,KAAK,aAAa,CAAC,CAAC,CAAC;gBACnB,mEAAmE;gBACnE,qEAAqE;gBACrE,sEAAsE;gBACtE,MAAM,CAAC,GAAI,MAAM,CAAC,KAA4B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;gBAClE,MAAM,CAAC,GAAI,MAAM,CAAC,MAA6B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;gBACnE,OAAO;oBACL;wBACE,IAAI,EAAE,OAAO;wBACb,EAAE,EAAE,WAAW;wBACf,GAAG,EAAE;4BACH,CAAC,EAAG,MAAM,CAAC,CAAwB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;4BAC3E,CAAC,EAAG,MAAM,CAAC,CAAwB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;4BAC3E,CAAC;4BACD,CAAC;yBACF;wBACD,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;qBAChC;iBACF,CAAA;YACH,CAAC;YACD,KAAK,iBAAiB,CAAC,CAAC,CAAC;gBACvB,MAAM,MAAM,GAAI,MAAM,CAAC,WAAkC,IAAI,CAAC,CAAA;gBAC9D,MAAM,MAAM,GAAI,MAAM,CAAC,YAAmC,IAAI,CAAC,CAAA;gBAC/D,OAAO;oBACL;wBACE,IAAI,EAAE,MAAM;wBACZ,EAAE,EAAE,WAAW;wBACf,GAAG,EAAE;4BACH,CAAC,EAAG,MAAM,CAAC,CAAwB,IAAI,CAAC;4BACxC,CAAC,EAAG,MAAM,CAAC,CAAwB,IAAI,CAAC;4BACxC,CAAC,EAAG,MAAM,CAAC,KAA4B,IAAI,CAAC;4BAC5C,CAAC,EAAG,MAAM,CAAC,MAA6B,IAAI,CAAC;yBAC9C;wBACD,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;wBAC/B,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,EAAC,KAAK,EAAE,EAAC,MAAM,EAAE,MAAM,EAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC;qBACvD;iBACF,CAAA;YACH,CAAC;YACD;gBACE,OAAO,IAAI,CAAA;QACf,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,OAAO;IACP,8EAA8E;IAE9E;;;;;OAKG;IACI,SAAS,CAAC,KAAa,EAAE,IAA0B,EAAE,QAA6B;QACvF,MAAM,IAAI,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAA;QACvC,IAAI,CAAC,IAAI;YAAE,OAAO,EAAC,IAAI,EAAE,YAAY,EAAC,CAAA;QAEtC,sEAAsE;QACtE,0EAA0E;QAC1E,yEAAyE;QACzE,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QAE5D,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,EAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAA;YAC1D,OAAO,EAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAC,CAAA;QACpD,CAAC;QAED,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,gBAAgB,CAAC,UAAU,EAAE,CAAC,CAAA;QAC7E,0EAA0E;QAC1E,yEAAyE;QACzE,8BAA8B;QAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAClG,MAAM,EAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAC,GAAG,SAAS,CAC3C,SAAS,EACT,SAAS,CAAC,QAAQ,EAClB,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CACxC,CAAA;QACD,MAAM,MAAM,GAAG,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAC,CAAA;QACvD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QAE9C,MAAM,KAAK,GAAe;YACxB,KAAK;YACL,IAAI;YACJ,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC;YAChD,GAAG,EAAE,EAAE,IAAI,CAAC,UAAU;YACtB,QAAQ,EAAE,MAAM;YAChB,OAAO;YACP,MAAM;SACP,CAAA;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QAClC,OAAO;YACL,IAAI,EAAE,OAAO;YACb,QAAQ,EAAE,SAAS,CAAC,QAAQ;YAC5B,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI;YACJ,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;SACpD,CAAA;IACH,CAAC;IAED;;;;OAIG;IACI,SAAS,CAAC,KAAa,EAAE,IAA0B;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;QACzE,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAA;QACxB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QACrB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,0FAA0F;IACnF,QAAQ,CAAC,KAAa;QAC3B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACvB,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,EAAE,EAAC,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAC,CAAC,CAAA;IACvG,CAAC;IAED,8EAA8E;IAC9E,4DAA4D;IAC5D,8EAA8E;IAE9E;;;;OAIG;IACI,aAAa,CAAC,GAAW,EAAE,SAAiB,EAAE,KAAa;QAChE,IAAI,CAAC,uBAAuB,EAAE,CAAA;QAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE;gBACpC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC;oBAAE,OAAO,CAAC,EAAC,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,wBAAwB,EAAC,CAAC,CAAA;YAChH,CAAC,EAAE,SAAS,CAAC,CAAA;YACb,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE;gBACpB,KAAK;gBACL,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;oBACb,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAA;oBAC3B,OAAO,CAAC,CAAC,CAAC,CAAA;gBACZ,CAAC;aACF,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;IAEO,uBAAuB;QAC7B,IAAI,IAAI,CAAC,YAAY;YAAE,OAAM;QAC7B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC,WAAW,CAAC,uBAAuB,EAAE,CAAC,KAAK,EAAE,EAAE;YAC9E,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;QAC9E,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;;;;OAKG;IACI,SAAS,CAAC,QAA+B;QAC9C,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QACnC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,IAAI,CAAC,UAAU,GAAG,YAAY,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE;gBACpE,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAA;gBACtE,IAAI,GAAG,IAAI,CAAC;oBAAE,OAAM;gBACpB,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC;oBAAE,OAAO,CAAC,GAAG,CAAC,CAAA;YAChE,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,EAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAC,CAAA;IAC/D,CAAC;IAED,6FAA6F;IACrF,gBAAgB,CAAC,GAAW,EAAE,KAAa,EAAE,SAAkB,EAAE,MAAe;QACtF,IAAI,CAAC,aAAa,CAChB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,EAClB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACP,CAAC,KAAK,GAAG;YACP,CAAC,CAAC,EAAC,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAC;YACxC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK;gBACjB,CAAC,CAAC,EAAC,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAC;gBACvC,CAAC,CAAC,EAAC,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,KAAK,EAAE,EAAC,CAC/E,CAAA;IACH,CAAC;IAEO,aAAa,CACnB,KAAmD,EACnD,OAAqF;QAErF,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;gBAAE,SAAQ;YAC5B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACxB,CAAC,CAAC,OAAO,CAAC,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;QACtE,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACI,aAAa,CAAC,KAAa,EAAE,IAA0B;QAC5D,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IACpC,CAAC;IAED;;;;;OAKG;IACI,aAAa,CAAC,IAA0B;QAC7C,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;QAC9B,yDAAyD;QACzD,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,EAAC,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAC,CAAC,CAAA;IACzF,CAAC;IAED,2FAA2F;IACnF,SAAS,CAAC,KAAiB;QACjC,2EAA2E;QAC3E,kEAAkE;QAClE,MAAM,WAAW,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC,YAAY,KAAK,IAAI,CAAA;QACpE,IAAI,WAAW,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC;YAAE,OAAO,KAAK,CAAA;QAC9D,IAAI,CAAC;YACH,wEAAwE;YACxE,uEAAuE;YACvE,MAAM,IAAI,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAA;YACvC,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI;gBAAE,KAAK,GAAG,EAAC,GAAG,KAAK,EAAE,MAAM,EAAE,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAC,EAAC,CAAA;YAC/F,uEAAuE;YACvE,mDAAmD;YACnD,KAAK,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,EAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC9F,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,6BAA6B,EAAE,GAAG,CAAC,CAAA;YAC7D,CAAC,CAAC,CAAA;YAEF,yEAAyE;YACzE,uEAAuE;YACvE,gEAAgE;YAChE,mEAAmE;YACnE,eAAe,CAAC,QAAQ,EAAE,CAAC,eAAe,CAAC;gBACzC,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE;oBACN,UAAU,EAAE,OAAO;oBACnB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK;oBAC1B,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM;oBAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;iBACzB;aACF,CAAC,CAAA;YACF,OAAO,IAAI,CAAA;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,6BAA6B,EAAE,GAAG,CAAC,CAAA;YAC3D,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,aAAa;IACb,8EAA8E;IAEvE,aAAa;QAClB,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAA;QACrB,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,EAAC,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAC,CAAC,CAAA;QACtF,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,CAAA;QAC3B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAA;QACxB,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,CAAA;QACzB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACtB,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAA;QAC7B,IAAI,CAAC,UAAU,GAAG,CAAC,CAAA;IACrB,CAAC;IAEM,iBAAiB,CAAC,KAAa,EAAE,IAAY;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IAC1C,CAAC;;AAGH,MAAM,aAAa,GAAG,aAAa,CAAC,WAAW,EAAE,CAAA;AACjD,eAAe,aAAa,CAAA;AAC5B,OAAO,EAAC,aAAa,EAAC,CAAA","sourcesContent":["/**\n * SceneRenderer — the device-aware boundary of the scene display pipeline.\n *\n * LocalDisplayManager owns WHO may draw (arbitration/boot/expiry); this service\n * owns WHAT gets sent for a scene: capabilities lookup, legacy-layout→scene\n * conversion (sugar and old bundles ride the same pipeline), process → diff →\n * SceneFrame emission, retained-scene replay, and the degrade branch for\n * devices that can't position elements.\n *\n * Everything device-specific here is DATA (capability fields, display profile);\n * the code paths are generic. Spec: notes/miniapp-display-render-implementation-spec.md\n */\n\nimport displayProcessor from \"./DisplayProcessor\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {useDisplayStore} from \"../stores/display\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {DeviceTypes} from \"../types\"\nimport {getEffectiveCapabilities} from \"./effectiveCapabilities\"\nimport {BgTimer} from \"../utils/timers\"\nimport {\n degradeScene,\n diffScene,\n processScene,\n resolveAnchors,\n SceneStore,\n type FrameElement,\n type SceneBreakMode,\n type SceneDisplayCapabilities,\n type SceneElementInput,\n type SceneFrame,\n} from \"../utils/display/scene\"\n\nconst LOG_TAG = \"SCENE_RENDERER\"\n\n/** Legacy layout shape (matches DisplayPayload.layout). */\nexport type LegacyLayout = {layoutType: string; [key: string]: unknown}\n\nexport type SceneEmitResult =\n /**\n * A SceneFrame was committed. `sent` is false when the frame stayed\n * phone-side (dashboard covering main). `settleMs` is how long the device\n * keeps animating after it has the frame: the longest transition among the\n * elements this frame changed, 0 on devices that jump.\n */\n | {kind: \"scene\"; degraded: boolean; dropped: string[]; seq: number; sent: boolean; settleMs: number}\n /** Degrade path: caller routes this legacy layout through the existing pipeline (null layout ⇒ clear). */\n | {kind: \"legacy\"; layout: LegacyLayout | null; degraded: boolean; dropped: string[]}\n /** No display capabilities on the connected device. */\n | {kind: \"no-display\"}\n\n/** How a frame ended up, as reported by native (or inferred from a later frame's report). */\nexport interface ScenePresentation {\n /** The device had the frame's content (this frame or a later one that folded it in). */\n presented: boolean\n /** A later frame's report settled this one. */\n superseded: boolean\n reason?: string\n}\n\n/** Longest geometry tween (255 frames at ~30 fps); rotation uses a separate millisecond clock. */\nconst MAX_SETTLE_MS = 255 * 33\n\n/** Longest transition among the elements that actually change on the device this frame. */\nfunction settleTimeOf(elements: FrameElement[]): number {\n let ms = 0\n for (const el of elements) {\n if (el.change !== \"updated\" && el.change !== \"moved\") continue\n const t = el.transition?.durationMs ?? 0\n // Rotation has its own millisecond clock, beyond the geometry frame limit.\n const duration = Math.min(t, el.rotation !== undefined ? 65535 : MAX_SETTLE_MS)\n if (duration > ms) ms = duration\n }\n return ms\n}\n\nclass SceneRenderer {\n private static instance: SceneRenderer | null = null\n private store = new SceneStore()\n private seqCounter = 0\n private pending = new Map<number, {appId: string; resolve: (p: ScenePresentation) => void}>()\n private presentedSub: {remove: () => void} | null = null\n private settledSub: {remove: () => void} | null = null\n private settledListeners = new Set<(seq: number) => void>()\n\n public static getInstance(): SceneRenderer {\n if (!SceneRenderer.instance) {\n SceneRenderer.instance = new SceneRenderer()\n }\n return SceneRenderer.instance\n }\n\n // ===========================================================================\n // Capabilities\n // ===========================================================================\n\n /**\n * Scene capabilities of the currently-selected device, or null when it has no\n * display block. Read on demand — same model source the CONNECT_ACK uses.\n */\n public currentCapabilities(): SceneDisplayCapabilities | null {\n // dev read these via the (since-removed) settings/glassesStatus runtime\n // hooks; engine reads its own stores directly.\n const model =\n (useSettingsStore.getState().getSetting(SETTINGS.default_wearable.key) as string | undefined) ??\n (useGlassesStore.getState().deviceModel as string | undefined)\n const display = getEffectiveCapabilities((model ?? DeviceTypes.NONE) as DeviceTypes)?.display\n if (!display) return null\n return {\n width: display.width ?? display.resolution?.width ?? displayProcessor.getProfile().displayWidthPx,\n height: display.height ?? display.resolution?.height ?? displayProcessor.getProfile().displayHeightPx ?? 288,\n canPosition: display.canPosition === true,\n maxTextElements: display.maxTextElements ?? 6,\n maxImageElements: display.maxImageElements ?? 0,\n maxImagePx: display.maxImagePx,\n shapes: display.shapes ?? [],\n animation: display.animation ?? false,\n rotation: display.rotation ?? false,\n depth: display.depth ?? false,\n intensityLevels: display.intensityLevels ?? 2,\n\n partialUpdate: display.partialUpdate ?? false,\n }\n }\n\n // ===========================================================================\n // Legacy layout → scene conversion (sugar + old bundles on the scene path)\n // ===========================================================================\n\n /**\n * Convert a legacy layout into scene elements for a positioning device.\n * Returns null for layouts that must stay on the legacy path (dashboard_card,\n * clear_view, unknown types). Sugar semantics: full-canvas / split boxes.\n */\n public convertLegacyLayout(layout: LegacyLayout, caps: SceneDisplayCapabilities): SceneElementInput[] | null {\n const W = caps.width\n const H = caps.height\n const breakMode = layout.breakMode as SceneBreakMode | undefined\n\n switch (layout.layoutType) {\n case \"text_wall\":\n return [\n {\n type: \"text\",\n id: \"sugar:wall\",\n box: {x: 0, y: 0, w: W, h: H},\n text: String(layout.text ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n case \"double_text_wall\": {\n const half = Math.floor(W / 2)\n return [\n {\n type: \"text\",\n id: \"sugar:left\",\n box: {x: 0, y: 0, w: half, h: H},\n text: String(layout.topText ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n {\n type: \"text\",\n id: \"sugar:right\",\n box: {x: half, y: 0, w: W - half, h: H},\n text: String(layout.bottomText ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n }\n case \"reference_card\": {\n const titleH = Math.max(24, Math.floor(H / 5))\n return [\n {type: \"text\", id: \"sugar:title\", box: {x: 0, y: 0, w: W, h: titleH}, text: String(layout.title ?? \"\")},\n {\n type: \"text\",\n id: \"sugar:body\",\n box: {x: 0, y: titleH, w: W, h: H - titleH},\n text: String(layout.text ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n }\n case \"bitmap_view\": {\n // Dimensionless legacy bitmaps historically landed in the device's\n // default ~200×100 container (G2 seeded 200×100 at 188,44) — NOT the\n // full canvas, which would tile a small image across the whole frame.\n const w = (layout.width as number | undefined) ?? Math.min(200, W)\n const h = (layout.height as number | undefined) ?? Math.min(100, H)\n return [\n {\n type: \"image\",\n id: \"sugar:bmp\",\n box: {\n x: (layout.x as number | undefined) ?? Math.max(0, Math.round((W - w) / 2)),\n y: (layout.y as number | undefined) ?? Math.max(0, Math.round((H - h) / 4)),\n w,\n h,\n },\n data: String(layout.data ?? \"\"),\n },\n ]\n }\n case \"positioned_text\": {\n const border = (layout.borderWidth as number | undefined) ?? 0\n const radius = (layout.borderRadius as number | undefined) ?? 0\n return [\n {\n type: \"text\",\n id: \"sugar:pos\",\n box: {\n x: (layout.x as number | undefined) ?? 0,\n y: (layout.y as number | undefined) ?? 0,\n w: (layout.width as number | undefined) ?? W,\n h: (layout.height as number | undefined) ?? H,\n },\n text: String(layout.text ?? \"\"),\n ...(border || radius ? {style: {border, radius}} : {}),\n },\n ]\n }\n default:\n return null\n }\n }\n\n // ===========================================================================\n // Emit\n // ===========================================================================\n\n /**\n * Process + diff + send a scene for (app, view). On non-positioning devices\n * this returns the degraded legacy layout instead — the caller routes it\n * through the existing DisplayProcessor path (which wraps it, keeping G1/Z100\n * output byte-identical to today).\n */\n public emitScene(appId: string, view: \"main\" | \"dashboard\", elements: SceneElementInput[]): SceneEmitResult {\n const caps = this.currentCapabilities()\n if (!caps) return {kind: \"no-display\"}\n\n // Corner-anchored boxes resolve against the REAL canvas before either\n // branch: processScene clamps concrete coords, and degradeScene's reading\n // order sorts by resolved y (a bottom-anchored y:0 must not sort first).\n elements = resolveAnchors(elements, caps.width, caps.height)\n\n if (!caps.canPosition) {\n const {layout, degraded, dropped} = degradeScene(elements)\n return {kind: \"legacy\", layout, degraded, dropped}\n }\n\n const processed = processScene(elements, caps, displayProcessor.getProfile())\n // A stale baseline means the glasses were cleared since the last commit —\n // diff from empty so everything repaints (the retained frame stays valid\n // as the replay source only).\n const prevFrame = this.store.isBaselineStale(appId, view) ? [] : this.store.lastFrame(appId, view)\n const {elements: diffed, removed} = diffScene(\n prevFrame,\n processed.elements,\n this.store.nextSyntheticId(appId, view),\n )\n const canvas = {width: caps.width, height: caps.height}\n this.store.commit(appId, view, diffed, canvas)\n\n const frame: SceneFrame = {\n appId,\n view,\n sceneEpoch: this.store.currentEpoch(appId, view),\n seq: ++this.seqCounter,\n elements: diffed,\n removed,\n canvas,\n }\n const sent = this.sendFrame(frame)\n return {\n kind: \"scene\",\n degraded: processed.degraded,\n dropped: processed.dropped,\n seq: frame.seq,\n sent,\n settleMs: caps.animation ? settleTimeOf(diffed) : 0,\n }\n }\n\n /**\n * Re-emit an app's retained scene (reconnect / arbitration restore). Replay\n * is always create-based — the device rebuilds from scratch (spec §4).\n * Returns false when nothing is retained.\n */\n public replayApp(appId: string, view: \"main\" | \"dashboard\"): boolean {\n const frame = this.store.buildReplayFrame(appId, view, ++this.seqCounter)\n if (!frame) return false\n this.sendFrame(frame)\n return true\n }\n\n /** Forget an app's retained scenes (app stop). Frames still in flight will never show. */\n public clearApp(appId: string): void {\n this.store.clear(appId)\n this.settlePending((p) => p.appId === appId, {presented: false, superseded: true, reason: \"cleared\"})\n }\n\n // ===========================================================================\n // Presentation — native reports when the device has a frame\n // ===========================================================================\n\n /**\n * Resolves once native reports frame `seq` on the device, or a later frame\n * that folded it in. Never rejects; `timeoutMs` bounds a frame the device\n * never acknowledges (link stalls, page rebuilding).\n */\n public whenPresented(seq: number, timeoutMs: number, appId: string): Promise<ScenePresentation> {\n this.ensurePresentedListener()\n return new Promise((resolve) => {\n const timer = BgTimer.setTimeout(() => {\n if (this.pending.delete(seq)) resolve({presented: false, superseded: false, reason: \"no presentation report\"})\n }, timeoutMs)\n this.pending.set(seq, {\n appId,\n resolve: (p) => {\n BgTimer.clearTimeout(timer)\n resolve(p)\n },\n })\n })\n }\n\n private ensurePresentedListener(): void {\n if (this.presentedSub) return\n this.presentedSub = BluetoothSdk.addListener(\"scene_frame_presented\", (event) => {\n this.onFramePresented(event.seq, event.appId, event.presented, event.reason)\n })\n }\n\n /**\n * Fires with the host frame number once the glasses report that every\n * animation the frame started has ended (Glassly CFW revision 27+). The\n * device echoes the low 16 bits of the seq; the full number is recovered\n * against the latest frame sent.\n */\n public onSettled(listener: (seq: number) => void): {remove: () => void} {\n this.settledListeners.add(listener)\n if (!this.settledSub) {\n this.settledSub = BluetoothSdk.addListener(\"scene_settled\", (event) => {\n const seq = this.seqCounter - ((this.seqCounter - event.tag) & 0xffff)\n if (seq <= 0) return\n for (const settled of [...this.settledListeners]) settled(seq)\n })\n }\n return {remove: () => this.settledListeners.delete(listener)}\n }\n\n /** A report for `seq` settles it and every earlier frame (folded in by the native pacer). */\n private onFramePresented(seq: number, appId: string, presented: boolean, reason?: string): void {\n this.settlePending(\n (p, s) => s <= seq,\n (p, s) =>\n s === seq\n ? {presented, superseded: false, reason}\n : p.appId === appId\n ? {presented, superseded: true, reason}\n : {presented: false, superseded: true, reason: `superseded by ${appId}`},\n )\n }\n\n private settlePending(\n match: (p: {appId: string}, seq: number) => boolean,\n outcome: ScenePresentation | ((p: {appId: string}, seq: number) => ScenePresentation),\n ): void {\n for (const [seq, p] of [...this.pending]) {\n if (!match(p, seq)) continue\n this.pending.delete(seq)\n p.resolve(typeof outcome === \"function\" ? outcome(p, seq) : outcome)\n }\n }\n\n /**\n * Reset the diff baseline for (app, view) without touching the device.\n * Used when a legacy layout bypasses the scene path (clear_view /\n * dashboard_card / unknown layoutTypes go to native raw): after that, the\n * retained frame no longer matches the glasses, so the next render() must\n * diff from empty — otherwise \"unchanged\" elements would be skipped and\n * never repainted.\n */\n public resetBaseline(appId: string, view: \"main\" | \"dashboard\"): void {\n this.store.commit(appId, view, [])\n }\n\n /**\n * A system-level clear wiped the view (durationMs expiry, unmount fallback,\n * boot-window expiry): mark every app's baseline for that view stale. Unlike\n * resetBaseline this KEEPS the retained frames — they remain the\n * restore-as-replay source; only incremental diffing restarts from empty.\n */\n public markViewStale(view: \"main\" | \"dashboard\"): void {\n this.store.markViewStale(view)\n // Whatever was in flight for the view got wiped with it.\n this.settlePending(() => true, {presented: false, superseded: true, reason: \"cleared\"})\n }\n\n /** Returns false when the frame stayed phone-side (no presentation report will follow). */\n private sendFrame(frame: SceneFrame): boolean {\n // Single native view: only the visible surface's frames go out. Suppressed\n // main frames stay committed; setOverlayOwner(null) replays them.\n const overlayHeld = useDisplayStore.getState().overlayOwner !== null\n if (overlayHeld !== (frame.view === \"dashboard\")) return false\n try {\n // Frames carry the canvas they were laid out against; only a frame from\n // a path that predates the tag falls back to the current capabilities.\n const caps = this.currentCapabilities()\n if (!frame.canvas && caps) frame = {...frame, canvas: {width: caps.width, height: caps.height}}\n // sendDisplayEvent hook -> direct BluetoothSdk.displayEvent (the exact\n // wiring the deleted MantleManager hook provided).\n void Promise.resolve(BluetoothSdk.displayEvent({view: frame.view, scene: frame})).catch((err) => {\n console.error(`${LOG_TAG}: native scene send failed:`, err)\n })\n\n // Keep the phone-side glasses mirror on the same frame as native. Legacy\n // layouts update this store in LocalDisplayManager.sendToNative; scene\n // frames bypass that method, so without an explicit write every\n // positioning device (including G2) leaves the mirror blank/stale.\n useDisplayStore.getState().setDisplayEvent({\n view: frame.view,\n layout: {\n layoutType: \"scene\",\n appId: frame.appId,\n view: frame.view,\n sceneEpoch: frame.sceneEpoch,\n width: frame.canvas?.width,\n height: frame.canvas?.height,\n elements: frame.elements,\n },\n })\n return true\n } catch (err) {\n console.error(`${LOG_TAG}: native scene send failed:`, err)\n return false\n }\n }\n\n // ===========================================================================\n // Test hooks\n // ===========================================================================\n\n public _resetForTest(): void {\n this.store.clearAll()\n this.settlePending(() => true, {presented: false, superseded: false, reason: \"reset\"})\n this.presentedSub?.remove()\n this.presentedSub = null\n this.settledSub?.remove()\n this.settledSub = null\n this.settledListeners.clear()\n this.seqCounter = 0\n }\n\n public _lastFrameForTest(appId: string, view: string): FrameElement[] {\n return this.store.lastFrame(appId, view)\n }\n}\n\nconst sceneRenderer = SceneRenderer.getInstance()\nexport default sceneRenderer\nexport {SceneRenderer}\n"]}
|
|
1
|
+
{"version":3,"file":"SceneRenderer.js","sourceRoot":"","sources":["../../src/services/SceneRenderer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SceneRenderer — the device-aware boundary of the scene display pipeline.\n *\n * LocalDisplayManager owns WHO may draw (arbitration/boot/expiry); this service\n * owns WHAT gets sent for a scene: capabilities lookup, legacy-layout→scene\n * conversion (sugar and old bundles ride the same pipeline), process → diff →\n * SceneFrame emission, retained-scene replay, and the degrade branch for\n * devices that can't position elements.\n *\n * Everything device-specific here is DATA (capability fields, display profile);\n * the code paths are generic. Spec: notes/miniapp-display-render-implementation-spec.md\n */\n\nimport displayProcessor from \"./DisplayProcessor\"\nimport BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {useDisplayStore} from \"../stores/display\"\nimport {SETTINGS, useSettingsStore} from \"../stores/settings\"\nimport {useGlassesStore} from \"../stores/glasses\"\nimport {DeviceTypes} from \"../types\"\nimport {getEffectiveCapabilities} from \"./effectiveCapabilities\"\nimport {BgTimer} from \"../utils/timers\"\nimport {\n degradeScene,\n diffScene,\n processScene,\n resolveAnchors,\n SceneStore,\n type FrameElement,\n type SceneBreakMode,\n type SceneDisplayCapabilities,\n type SceneElementInput,\n type SceneFrame,\n} from \"../utils/display/scene\"\n\nconst LOG_TAG = \"SCENE_RENDERER\"\n\n/** Legacy layout shape (matches DisplayPayload.layout). */\nexport type LegacyLayout = {layoutType: string; [key: string]: unknown}\n\nexport type SceneEmitResult =\n /**\n * A SceneFrame was committed. `sent` is false when the frame stayed\n * phone-side (dashboard covering main). `settleMs` is how long the device\n * keeps animating after it has the frame: the longest transition among the\n * elements this frame changed, 0 on devices that jump.\n */\n | {kind: \"scene\"; degraded: boolean; dropped: string[]; seq: number; sent: boolean; settleMs: number}\n /** Degrade path: caller routes this legacy layout through the existing pipeline (null layout ⇒ clear). */\n | {kind: \"legacy\"; layout: LegacyLayout | null; degraded: boolean; dropped: string[]}\n /** No display capabilities on the connected device. */\n | {kind: \"no-display\"}\n\n/** How a frame ended up, as reported by native (or inferred from a later frame's report). */\nexport interface ScenePresentation {\n /** The device had the frame's content (this frame or a later one that folded it in). */\n presented: boolean\n /** A later frame's report settled this one. */\n superseded: boolean\n reason?: string\n}\n\n/** Longest geometry tween (255 frames at ~30 fps); rotation uses a separate millisecond clock. */\nconst MAX_SETTLE_MS = 255 * 33\n\n/** Longest transition among the elements that actually change on the device this frame. */\nfunction settleTimeOf(elements: FrameElement[]): number {\n let ms = 0\n for (const el of elements) {\n // Entering and leaving elements tween too, from or to their offset.\n const edge = el.transition?.exit !== undefined || (el.change === \"created\" && el.transition?.enter !== undefined)\n if (el.change !== \"updated\" && el.change !== \"moved\" && !edge) continue\n const t = el.transition?.durationMs ?? 0\n // Rotation has its own millisecond clock, beyond the geometry frame limit.\n const duration = Math.min(t, el.rotation !== undefined ? 65535 : MAX_SETTLE_MS)\n if (duration > ms) ms = duration\n }\n return ms\n}\n\nclass SceneRenderer {\n private static instance: SceneRenderer | null = null\n private store = new SceneStore()\n private seqCounter = 0\n private pending = new Map<number, {appId: string; resolve: (p: ScenePresentation) => void}>()\n private presentedSub: {remove: () => void} | null = null\n private settledSub: {remove: () => void} | null = null\n private settledListeners = new Set<(seq: number) => void>()\n\n public static getInstance(): SceneRenderer {\n if (!SceneRenderer.instance) {\n SceneRenderer.instance = new SceneRenderer()\n }\n return SceneRenderer.instance\n }\n\n // ===========================================================================\n // Capabilities\n // ===========================================================================\n\n /**\n * Scene capabilities of the currently-selected device, or null when it has no\n * display block. Read on demand — same model source the CONNECT_ACK uses.\n */\n public currentCapabilities(): SceneDisplayCapabilities | null {\n // dev read these via the (since-removed) settings/glassesStatus runtime\n // hooks; engine reads its own stores directly.\n const model =\n (useSettingsStore.getState().getSetting(SETTINGS.default_wearable.key) as string | undefined) ??\n (useGlassesStore.getState().deviceModel as string | undefined)\n const display = getEffectiveCapabilities((model ?? DeviceTypes.NONE) as DeviceTypes)?.display\n if (!display) return null\n return {\n width: display.width ?? display.resolution?.width ?? displayProcessor.getProfile().displayWidthPx,\n height: display.height ?? display.resolution?.height ?? displayProcessor.getProfile().displayHeightPx ?? 288,\n canPosition: display.canPosition === true,\n maxTextElements: display.maxTextElements ?? 6,\n maxImageElements: display.maxImageElements ?? 0,\n maxImagePx: display.maxImagePx,\n shapes: display.shapes ?? [],\n animation: display.animation ?? false,\n rotation: display.rotation ?? false,\n depth: display.depth ?? false,\n intensityLevels: display.intensityLevels ?? 2,\n\n partialUpdate: display.partialUpdate ?? false,\n }\n }\n\n // ===========================================================================\n // Legacy layout → scene conversion (sugar + old bundles on the scene path)\n // ===========================================================================\n\n /**\n * Convert a legacy layout into scene elements for a positioning device.\n * Returns null for layouts that must stay on the legacy path (dashboard_card,\n * clear_view, unknown types). Sugar semantics: full-canvas / split boxes.\n */\n public convertLegacyLayout(layout: LegacyLayout, caps: SceneDisplayCapabilities): SceneElementInput[] | null {\n const W = caps.width\n const H = caps.height\n const breakMode = layout.breakMode as SceneBreakMode | undefined\n\n switch (layout.layoutType) {\n case \"text_wall\":\n return [\n {\n type: \"text\",\n id: \"sugar:wall\",\n box: {x: 0, y: 0, w: W, h: H},\n text: String(layout.text ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n case \"double_text_wall\": {\n const half = Math.floor(W / 2)\n return [\n {\n type: \"text\",\n id: \"sugar:left\",\n box: {x: 0, y: 0, w: half, h: H},\n text: String(layout.topText ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n {\n type: \"text\",\n id: \"sugar:right\",\n box: {x: half, y: 0, w: W - half, h: H},\n text: String(layout.bottomText ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n }\n case \"reference_card\": {\n const titleH = Math.max(24, Math.floor(H / 5))\n return [\n {type: \"text\", id: \"sugar:title\", box: {x: 0, y: 0, w: W, h: titleH}, text: String(layout.title ?? \"\")},\n {\n type: \"text\",\n id: \"sugar:body\",\n box: {x: 0, y: titleH, w: W, h: H - titleH},\n text: String(layout.text ?? \"\"),\n ...(breakMode ? {style: {breakMode}} : {}),\n },\n ]\n }\n case \"bitmap_view\": {\n // Dimensionless legacy bitmaps historically landed in the device's\n // default ~200×100 container (G2 seeded 200×100 at 188,44) — NOT the\n // full canvas, which would tile a small image across the whole frame.\n const w = (layout.width as number | undefined) ?? Math.min(200, W)\n const h = (layout.height as number | undefined) ?? Math.min(100, H)\n return [\n {\n type: \"image\",\n id: \"sugar:bmp\",\n box: {\n x: (layout.x as number | undefined) ?? Math.max(0, Math.round((W - w) / 2)),\n y: (layout.y as number | undefined) ?? Math.max(0, Math.round((H - h) / 4)),\n w,\n h,\n },\n data: String(layout.data ?? \"\"),\n },\n ]\n }\n case \"positioned_text\": {\n const border = (layout.borderWidth as number | undefined) ?? 0\n const radius = (layout.borderRadius as number | undefined) ?? 0\n return [\n {\n type: \"text\",\n id: \"sugar:pos\",\n box: {\n x: (layout.x as number | undefined) ?? 0,\n y: (layout.y as number | undefined) ?? 0,\n w: (layout.width as number | undefined) ?? W,\n h: (layout.height as number | undefined) ?? H,\n },\n text: String(layout.text ?? \"\"),\n ...(border || radius ? {style: {border, radius}} : {}),\n },\n ]\n }\n default:\n return null\n }\n }\n\n // ===========================================================================\n // Emit\n // ===========================================================================\n\n /**\n * Process + diff + send a scene for (app, view). On non-positioning devices\n * this returns the degraded legacy layout instead — the caller routes it\n * through the existing DisplayProcessor path (which wraps it, keeping G1/Z100\n * output byte-identical to today).\n */\n public emitScene(appId: string, view: \"main\" | \"dashboard\", elements: SceneElementInput[]): SceneEmitResult {\n const caps = this.currentCapabilities()\n if (!caps) return {kind: \"no-display\"}\n\n // Corner-anchored boxes resolve against the REAL canvas before either\n // branch: processScene clamps concrete coords, and degradeScene's reading\n // order sorts by resolved y (a bottom-anchored y:0 must not sort first).\n elements = resolveAnchors(elements, caps.width, caps.height)\n\n if (!caps.canPosition) {\n const {layout, degraded, dropped} = degradeScene(elements)\n return {kind: \"legacy\", layout, degraded, dropped}\n }\n\n const processed = processScene(elements, caps, displayProcessor.getProfile())\n // A stale baseline means the glasses were cleared since the last commit —\n // diff from empty so everything repaints (the retained frame stays valid\n // as the replay source only).\n const prevFrame = this.store.isBaselineStale(appId, view) ? [] : this.store.lastFrame(appId, view)\n const {elements: diffed, removed} = diffScene(\n prevFrame,\n processed.elements,\n this.store.nextSyntheticId(appId, view),\n )\n const canvas = {width: caps.width, height: caps.height}\n this.store.commit(appId, view, diffed, canvas)\n\n const frame: SceneFrame = {\n appId,\n view,\n sceneEpoch: this.store.currentEpoch(appId, view),\n seq: ++this.seqCounter,\n elements: diffed,\n removed,\n canvas,\n }\n const sent = this.sendFrame(frame)\n return {\n kind: \"scene\",\n degraded: processed.degraded,\n dropped: processed.dropped,\n seq: frame.seq,\n sent,\n settleMs: caps.animation ? settleTimeOf(diffed) : 0,\n }\n }\n\n /**\n * Re-emit an app's retained scene (reconnect / arbitration restore). Replay\n * is always create-based — the device rebuilds from scratch (spec §4).\n * Returns false when nothing is retained.\n */\n public replayApp(appId: string, view: \"main\" | \"dashboard\"): boolean {\n const frame = this.store.buildReplayFrame(appId, view, ++this.seqCounter)\n if (!frame) return false\n this.sendFrame(frame)\n return true\n }\n\n /** Forget an app's retained scenes (app stop). Frames still in flight will never show. */\n public clearApp(appId: string): void {\n this.store.clear(appId)\n this.settlePending((p) => p.appId === appId, {presented: false, superseded: true, reason: \"cleared\"})\n }\n\n // ===========================================================================\n // Presentation — native reports when the device has a frame\n // ===========================================================================\n\n /**\n * Resolves once native reports frame `seq` on the device, or a later frame\n * that folded it in. Never rejects; `timeoutMs` bounds a frame the device\n * never acknowledges (link stalls, page rebuilding).\n */\n public whenPresented(seq: number, timeoutMs: number, appId: string): Promise<ScenePresentation> {\n this.ensurePresentedListener()\n return new Promise((resolve) => {\n const timer = BgTimer.setTimeout(() => {\n if (this.pending.delete(seq)) resolve({presented: false, superseded: false, reason: \"no presentation report\"})\n }, timeoutMs)\n this.pending.set(seq, {\n appId,\n resolve: (p) => {\n BgTimer.clearTimeout(timer)\n resolve(p)\n },\n })\n })\n }\n\n private ensurePresentedListener(): void {\n if (this.presentedSub) return\n this.presentedSub = BluetoothSdk.addListener(\"scene_frame_presented\", (event) => {\n this.onFramePresented(event.seq, event.appId, event.presented, event.reason)\n })\n }\n\n /**\n * Fires with the host frame number once the glasses report that every\n * animation the frame started has ended (Glassly CFW revision 27+). The\n * device echoes the low 16 bits of the seq; the full number is recovered\n * against the latest frame sent.\n */\n public onSettled(listener: (seq: number) => void): {remove: () => void} {\n this.settledListeners.add(listener)\n if (!this.settledSub) {\n this.settledSub = BluetoothSdk.addListener(\"scene_settled\", (event) => {\n const seq = this.seqCounter - ((this.seqCounter - event.tag) & 0xffff)\n if (seq <= 0) return\n for (const settled of [...this.settledListeners]) settled(seq)\n })\n }\n return {remove: () => this.settledListeners.delete(listener)}\n }\n\n /** A report for `seq` settles it and every earlier frame (folded in by the native pacer). */\n private onFramePresented(seq: number, appId: string, presented: boolean, reason?: string): void {\n this.settlePending(\n (p, s) => s <= seq,\n (p, s) =>\n s === seq\n ? {presented, superseded: false, reason}\n : p.appId === appId\n ? {presented, superseded: true, reason}\n : {presented: false, superseded: true, reason: `superseded by ${appId}`},\n )\n }\n\n private settlePending(\n match: (p: {appId: string}, seq: number) => boolean,\n outcome: ScenePresentation | ((p: {appId: string}, seq: number) => ScenePresentation),\n ): void {\n for (const [seq, p] of [...this.pending]) {\n if (!match(p, seq)) continue\n this.pending.delete(seq)\n p.resolve(typeof outcome === \"function\" ? outcome(p, seq) : outcome)\n }\n }\n\n /**\n * Reset the diff baseline for (app, view) without touching the device.\n * Used when a legacy layout bypasses the scene path (clear_view /\n * dashboard_card / unknown layoutTypes go to native raw): after that, the\n * retained frame no longer matches the glasses, so the next render() must\n * diff from empty — otherwise \"unchanged\" elements would be skipped and\n * never repainted.\n */\n public resetBaseline(appId: string, view: \"main\" | \"dashboard\"): void {\n this.store.commit(appId, view, [])\n }\n\n /**\n * A system-level clear wiped the view (durationMs expiry, unmount fallback,\n * boot-window expiry): mark every app's baseline for that view stale. Unlike\n * resetBaseline this KEEPS the retained frames — they remain the\n * restore-as-replay source; only incremental diffing restarts from empty.\n */\n public markViewStale(view: \"main\" | \"dashboard\"): void {\n this.store.markViewStale(view)\n // Whatever was in flight for the view got wiped with it.\n this.settlePending(() => true, {presented: false, superseded: true, reason: \"cleared\"})\n }\n\n /** Returns false when the frame stayed phone-side (no presentation report will follow). */\n private sendFrame(frame: SceneFrame): boolean {\n // Single native view: only the visible surface's frames go out. Suppressed\n // main frames stay committed; setOverlayOwner(null) replays them.\n const overlayHeld = useDisplayStore.getState().overlayOwner !== null\n if (overlayHeld !== (frame.view === \"dashboard\")) return false\n try {\n // Frames carry the canvas they were laid out against; only a frame from\n // a path that predates the tag falls back to the current capabilities.\n const caps = this.currentCapabilities()\n if (!frame.canvas && caps) frame = {...frame, canvas: {width: caps.width, height: caps.height}}\n // sendDisplayEvent hook -> direct BluetoothSdk.displayEvent (the exact\n // wiring the deleted MantleManager hook provided).\n void Promise.resolve(BluetoothSdk.displayEvent({view: frame.view, scene: frame})).catch((err) => {\n console.error(`${LOG_TAG}: native scene send failed:`, err)\n })\n\n // Keep the phone-side glasses mirror on the same frame as native. Legacy\n // layouts update this store in LocalDisplayManager.sendToNative; scene\n // frames bypass that method, so without an explicit write every\n // positioning device (including G2) leaves the mirror blank/stale.\n useDisplayStore.getState().setDisplayEvent({\n view: frame.view,\n layout: {\n layoutType: \"scene\",\n appId: frame.appId,\n view: frame.view,\n sceneEpoch: frame.sceneEpoch,\n width: frame.canvas?.width,\n height: frame.canvas?.height,\n elements: frame.elements,\n },\n })\n return true\n } catch (err) {\n console.error(`${LOG_TAG}: native scene send failed:`, err)\n return false\n }\n }\n\n // ===========================================================================\n // Test hooks\n // ===========================================================================\n\n public _resetForTest(): void {\n this.store.clearAll()\n this.settlePending(() => true, {presented: false, superseded: false, reason: \"reset\"})\n this.presentedSub?.remove()\n this.presentedSub = null\n this.settledSub?.remove()\n this.settledSub = null\n this.settledListeners.clear()\n this.seqCounter = 0\n }\n\n public _lastFrameForTest(appId: string, view: string): FrameElement[] {\n return this.store.lastFrame(appId, view)\n }\n}\n\nconst sceneRenderer = SceneRenderer.getInstance()\nexport default sceneRenderer\nexport {SceneRenderer}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { FrameElement, SceneAnchor, SceneBox, SceneBoxInput, SceneBreakMode, SceneChange, SceneDisplayCapabilities, SceneElementInput, SceneElementType, SceneFrame, SceneImageStyle, ScenePoint, SceneProcessResult, SceneRectStyle, SceneShapeStyle, SceneStrokeStyle, SceneTextStyle, SceneTransition, } from "./types";
|
|
1
|
+
export type { FrameElement, SceneAnchor, SceneBox, SceneBoxInput, SceneBreakMode, SceneChange, SceneDisplayCapabilities, SceneElementInput, SceneElementType, SceneFrame, SceneImageStyle, ScenePoint, SceneProcessResult, SceneRectStyle, SceneShapeStyle, SceneStrokeStyle, SceneTextStyle, SceneTransition, SceneTransitionEdge, } from "./types";
|
|
2
2
|
export { boxesEqual, contentHash, elementContentHash } from "./types";
|
|
3
3
|
export { resolveAnchors } from "./anchor";
|
|
4
4
|
export type { DiffResult, DiffableElement } from "./differ";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,aAAa,EACb,cAAc,EACd,WAAW,EACX,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,aAAa,EACb,cAAc,EACd,WAAW,EACX,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,GACpB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAC,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAA;AACnE,OAAO,EAAC,cAAc,EAAC,MAAM,UAAU,CAAA;AACvC,YAAY,EAAC,UAAU,EAAE,eAAe,EAAC,MAAM,UAAU,CAAA;AACzD,OAAO,EAAC,SAAS,EAAC,MAAM,UAAU,CAAA;AAClC,YAAY,EAAC,cAAc,EAAC,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAC,YAAY,EAAE,mBAAmB,EAAC,MAAM,WAAW,CAAA;AAC3D,YAAY,EAAC,aAAa,EAAC,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAC,YAAY,EAAC,MAAM,WAAW,CAAA;AACtC,OAAO,EAAC,UAAU,EAAC,MAAM,SAAS,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAC,UAAU,EAAE,WAAW,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAA;AACnE,OAAO,EAAC,cAAc,EAAC,MAAM,UAAU,CAAA;AAEvC,OAAO,EAAC,SAAS,EAAC,MAAM,UAAU,CAAA;AAElC,OAAO,EAAC,YAAY,EAAE,mBAAmB,EAAC,MAAM,WAAW,CAAA;AAE3D,OAAO,EAAC,YAAY,EAAC,MAAM,WAAW,CAAA;AACtC,OAAO,EAAC,UAAU,EAAC,MAAM,SAAS,CAAA","sourcesContent":["export type {\n FrameElement,\n SceneAnchor,\n SceneBox,\n SceneBoxInput,\n SceneBreakMode,\n SceneChange,\n SceneDisplayCapabilities,\n SceneElementInput,\n SceneElementType,\n SceneFrame,\n SceneImageStyle,\n ScenePoint,\n SceneProcessResult,\n SceneRectStyle,\n SceneShapeStyle,\n SceneStrokeStyle,\n SceneTextStyle,\n SceneTransition,\n SceneTransitionEdge,\n} from \"./types\"\nexport {boxesEqual, contentHash, elementContentHash} from \"./types\"\nexport {resolveAnchors} from \"./anchor\"\nexport type {DiffResult, DiffableElement} from \"./differ\"\nexport {diffScene} from \"./differ\"\nexport type {ProcessedScene} from \"./process\"\nexport {processScene, profileLineHeightPx} from \"./process\"\nexport type {DegradedScene} from \"./degrade\"\nexport {degradeScene} from \"./degrade\"\nexport {SceneStore} from \"./store\"\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAGV,wBAAwB,EACxB,iBAAiB,
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/process.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAGV,wBAAwB,EACxB,iBAAiB,EAKlB,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;AAmED;;;;;;;;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,CAkThB"}
|
|
@@ -45,13 +45,29 @@ function pointsBox(points) {
|
|
|
45
45
|
const y = Math.min(...ys);
|
|
46
46
|
return { x, y, w: Math.max(...xs) - x + 1, h: Math.max(...ys) - y + 1 };
|
|
47
47
|
}
|
|
48
|
+
function validEdge(e) {
|
|
49
|
+
if (!e || typeof e !== "object")
|
|
50
|
+
return undefined;
|
|
51
|
+
const dx = Math.round(Number(e.dx ?? 0));
|
|
52
|
+
const dy = Math.round(Number(e.dy ?? 0));
|
|
53
|
+
if (!isFinite(dx) || !isFinite(dy) || Math.abs(dx) > 1024 || Math.abs(dy) > 1024)
|
|
54
|
+
return undefined;
|
|
55
|
+
return { dx, dy };
|
|
56
|
+
}
|
|
48
57
|
function validTransition(t) {
|
|
49
58
|
if (!t || typeof t !== "object")
|
|
50
59
|
return undefined;
|
|
51
60
|
const { durationMs, easing } = t;
|
|
52
61
|
if (!isFinite(durationMs) || durationMs <= 0)
|
|
53
62
|
return undefined;
|
|
54
|
-
|
|
63
|
+
const out = easing ? { durationMs, easing } : { durationMs };
|
|
64
|
+
const enter = validEdge(t.enter);
|
|
65
|
+
const exit = validEdge(t.exit);
|
|
66
|
+
if (enter)
|
|
67
|
+
out.enter = enter;
|
|
68
|
+
if (exit)
|
|
69
|
+
out.exit = exit;
|
|
70
|
+
return out;
|
|
55
71
|
}
|
|
56
72
|
function boxShrunk(orig, clamped) {
|
|
57
73
|
return (clamped.x !== Math.floor(orig.x) ||
|
|
@@ -143,6 +159,9 @@ export function processScene(input, caps, profile) {
|
|
|
143
159
|
let pathEdges = 0;
|
|
144
160
|
for (const { el, index, box, points } of valid) {
|
|
145
161
|
const transition = validTransition(el.transition);
|
|
162
|
+
// Leaving elements only exist to tween away; a device that jumps never shows them.
|
|
163
|
+
if (transition?.exit && !caps.animation)
|
|
164
|
+
continue;
|
|
146
165
|
const transform = {};
|
|
147
166
|
if (el.rotation !== undefined || el.pivot !== undefined) {
|
|
148
167
|
const angle = el.rotation ?? 0;
|
|
@@ -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 {compileSvgPath} from \"./svgPath\"\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 {MAX_SCENE_DEPTH, 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 let pathEdges = 0\n\n for (const {el, index, box, points} of valid) {\n const transition = validTransition((el as {transition?: unknown}).transition)\n const transform: {rotation?: number; pivot?: ScenePoint} = {}\n if (el.rotation !== undefined || el.pivot !== undefined) {\n const angle = el.rotation ?? 0\n const pivot = el.pivot ?? {x: box.x + box.w / 2, y: box.y + box.h / 2}\n if (\n !isFinite(angle) ||\n Math.abs(angle) > 36000 ||\n !isFinite(pivot.x) ||\n !isFinite(pivot.y) ||\n Math.abs(pivot.x) > 32767 ||\n Math.abs(pivot.y) > 32767 ||\n !caps.rotation ||\n el.type === \"image\" ||\n el.type === \"text\"\n ) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n transform.rotation = Math.round(angle * 256) / 256\n transform.pivot = {x: Math.round(pivot.x), y: Math.round(pivot.y)}\n if (transition) transition.durationMs = Math.min(65535, transition.durationMs)\n }\n const transformHash =\n transform.rotation === undefined ? undefined : `${transform.rotation}:${transform.pivot?.x}:${transform.pivot?.y}`\n // Depth is a lens disparity, not content: devices without it draw the\n // element flat instead of dropping it. A non-number is still a dev error.\n let depth: number | undefined\n if (el.depth !== undefined) {\n if (!isFinite(el.depth)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const d = Math.max(-MAX_SCENE_DEPTH, Math.min(MAX_SCENE_DEPTH, Math.round(el.depth)))\n if (caps.depth && d !== 0) depth = d\n }\n const plane = depth === undefined ? {} : {depth}\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 === \"svg\") {\n try {\n if (!caps.shapes.includes(\"svg\") || textBudget <= 0 || shrunk)\n throw new Error(\"SVG unavailable or out of bounds\")\n const scale = el.scale ?? 1\n if (\n !isFinite(scale) ||\n scale < 0 ||\n scale > 8 ||\n (el.fillRule !== undefined && el.fillRule !== \"evenodd\" && el.fillRule !== \"nonzero\")\n )\n throw new Error(\"Invalid SVG scale or fill rule\")\n const compiled = compileSvgPath(el.d)\n if (pathEdges + compiled.edges > 4096) throw new Error(\"SVG scene exceeds the edge budget\")\n pathEdges += compiled.edges\n textBudget--\n out.push({\n id: el.id,\n type: \"svg\",\n box: clamped,\n d: el.d,\n path: compiled.commands,\n pathScale: Math.round(scale * 256),\n fillRule: el.fillRule ?? \"nonzero\",\n style: el.style,\n ...transform,\n ...plane,\n ...(transition ? {transition} : {}),\n contentHash: elementContentHash({\n type: \"svg\",\n data: `${el.d}:${scale}:${el.fillRule}:${transformHash}`,\n style: el.style,\n depth,\n }),\n })\n } catch {\n dropped.push(reportId(el, index))\n degraded = true\n }\n continue\n }\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 ...transform,\n ...plane,\n contentHash: elementContentHash({\n type: el.type,\n style: el.style,\n points,\n ...angles,\n data: transformHash,\n depth,\n }),\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 ...(el.style ? {style: el.style} : {}),\n ...(transition ? {transition} : {}),\n ...plane,\n contentHash: elementContentHash({type: \"image\", data: el.data, style: el.style, depth}),\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 ...transform,\n ...plane,\n contentHash: elementContentHash({type: \"rect\", style: el.style, data: transformHash, depth}),\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 ...plane,\n contentHash: elementContentHash({type: \"text\", text: monoText, style: el.style, depth}),\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 ...plane,\n contentHash: elementContentHash({type: \"text\", text: wrappedText, style: el.style, depth}),\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 {compileSvgPath} from \"./svgPath\"\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 SceneTransitionEdge,\n} from \"./types\"\nimport {MAX_SCENE_DEPTH, 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 validEdge(e: unknown): SceneTransitionEdge | undefined {\n if (!e || typeof e !== \"object\") return undefined\n const dx = Math.round(Number((e as SceneTransitionEdge).dx ?? 0))\n const dy = Math.round(Number((e as SceneTransitionEdge).dy ?? 0))\n if (!isFinite(dx) || !isFinite(dy) || Math.abs(dx) > 1024 || Math.abs(dy) > 1024) return undefined\n return {dx, dy}\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 const out: SceneTransition = easing ? {durationMs, easing} : {durationMs}\n const enter = validEdge((t as SceneTransition).enter)\n const exit = validEdge((t as SceneTransition).exit)\n if (enter) out.enter = enter\n if (exit) out.exit = exit\n return out\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 let pathEdges = 0\n\n for (const {el, index, box, points} of valid) {\n const transition = validTransition((el as {transition?: unknown}).transition)\n // Leaving elements only exist to tween away; a device that jumps never shows them.\n if (transition?.exit && !caps.animation) continue\n const transform: {rotation?: number; pivot?: ScenePoint} = {}\n if (el.rotation !== undefined || el.pivot !== undefined) {\n const angle = el.rotation ?? 0\n const pivot = el.pivot ?? {x: box.x + box.w / 2, y: box.y + box.h / 2}\n if (\n !isFinite(angle) ||\n Math.abs(angle) > 36000 ||\n !isFinite(pivot.x) ||\n !isFinite(pivot.y) ||\n Math.abs(pivot.x) > 32767 ||\n Math.abs(pivot.y) > 32767 ||\n !caps.rotation ||\n el.type === \"image\" ||\n el.type === \"text\"\n ) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n transform.rotation = Math.round(angle * 256) / 256\n transform.pivot = {x: Math.round(pivot.x), y: Math.round(pivot.y)}\n if (transition) transition.durationMs = Math.min(65535, transition.durationMs)\n }\n const transformHash =\n transform.rotation === undefined ? undefined : `${transform.rotation}:${transform.pivot?.x}:${transform.pivot?.y}`\n // Depth is a lens disparity, not content: devices without it draw the\n // element flat instead of dropping it. A non-number is still a dev error.\n let depth: number | undefined\n if (el.depth !== undefined) {\n if (!isFinite(el.depth)) {\n dropped.push(reportId(el, index))\n degraded = true\n continue\n }\n const d = Math.max(-MAX_SCENE_DEPTH, Math.min(MAX_SCENE_DEPTH, Math.round(el.depth)))\n if (caps.depth && d !== 0) depth = d\n }\n const plane = depth === undefined ? {} : {depth}\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 === \"svg\") {\n try {\n if (!caps.shapes.includes(\"svg\") || textBudget <= 0 || shrunk)\n throw new Error(\"SVG unavailable or out of bounds\")\n const scale = el.scale ?? 1\n if (\n !isFinite(scale) ||\n scale < 0 ||\n scale > 8 ||\n (el.fillRule !== undefined && el.fillRule !== \"evenodd\" && el.fillRule !== \"nonzero\")\n )\n throw new Error(\"Invalid SVG scale or fill rule\")\n const compiled = compileSvgPath(el.d)\n if (pathEdges + compiled.edges > 4096) throw new Error(\"SVG scene exceeds the edge budget\")\n pathEdges += compiled.edges\n textBudget--\n out.push({\n id: el.id,\n type: \"svg\",\n box: clamped,\n d: el.d,\n path: compiled.commands,\n pathScale: Math.round(scale * 256),\n fillRule: el.fillRule ?? \"nonzero\",\n style: el.style,\n ...transform,\n ...plane,\n ...(transition ? {transition} : {}),\n contentHash: elementContentHash({\n type: \"svg\",\n data: `${el.d}:${scale}:${el.fillRule}:${transformHash}`,\n style: el.style,\n depth,\n }),\n })\n } catch {\n dropped.push(reportId(el, index))\n degraded = true\n }\n continue\n }\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 ...transform,\n ...plane,\n contentHash: elementContentHash({\n type: el.type,\n style: el.style,\n points,\n ...angles,\n data: transformHash,\n depth,\n }),\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 ...(el.style ? {style: el.style} : {}),\n ...(transition ? {transition} : {}),\n ...plane,\n contentHash: elementContentHash({type: \"image\", data: el.data, style: el.style, depth}),\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 ...transform,\n ...plane,\n contentHash: elementContentHash({type: \"rect\", style: el.style, data: transformHash, depth}),\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 ...plane,\n contentHash: elementContentHash({type: \"text\", text: monoText, style: el.style, depth}),\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 ...plane,\n contentHash: elementContentHash({type: \"text\", text: wrappedText, style: el.style, depth}),\n })\n }\n\n return {elements: out, degraded, dropped}\n}\n"]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { FrameElement, SceneAnchor, SceneBox, SceneBoxInput, SceneBreakMode, SceneChange, SceneDisplayCapabilities, SceneElementInput, SceneElementType, SceneFrame, SceneImageStyle, ScenePoint, SceneProcessResult, SceneRectStyle, SceneShapeStyle, SceneStrokeStyle, SceneTextStyle, SceneTransition, } from "@glassly/miniapp/display";
|
|
1
|
+
export type { FrameElement, SceneAnchor, SceneBox, SceneBoxInput, SceneBreakMode, SceneChange, SceneDisplayCapabilities, SceneElementInput, SceneElementType, SceneFrame, SceneImageStyle, ScenePoint, SceneProcessResult, SceneRectStyle, SceneShapeStyle, SceneStrokeStyle, SceneTextStyle, SceneTransition, SceneTransitionEdge, } from "@glassly/miniapp/display";
|
|
2
2
|
export { boxesEqual, contentHash, elementContentHash, MAX_SCENE_DEPTH, POINT_SHAPE_ARITY, POINT_SHAPE_TYPES, } from "@glassly/miniapp/display";
|
|
3
3
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"AACA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,aAAa,EACb,cAAc,EACd,WAAW,EACX,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"AACA,YAAY,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,aAAa,EACb,cAAc,EACd,WAAW,EACX,wBAAwB,EACxB,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,mBAAmB,GACpB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,0BAA0B,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/utils/display/scene/types.ts"],"names":[],"mappings":"AAsBA,OAAO,EACL,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,0BAA0B,CAAA","sourcesContent":["// Moved into the SDK (@glassly/miniapp/display) so miniapps lay text out the way the host measures it; this path re-exports it for the engine.\nexport type {\n FrameElement,\n SceneAnchor,\n SceneBox,\n SceneBoxInput,\n SceneBreakMode,\n SceneChange,\n SceneDisplayCapabilities,\n SceneElementInput,\n SceneElementType,\n SceneFrame,\n SceneImageStyle,\n ScenePoint,\n SceneProcessResult,\n SceneRectStyle,\n SceneShapeStyle,\n SceneStrokeStyle,\n SceneTextStyle,\n SceneTransition,\n SceneTransitionEdge,\n} from \"@glassly/miniapp/display\"\nexport {\n boxesEqual,\n contentHash,\n elementContentHash,\n MAX_SCENE_DEPTH,\n POINT_SHAPE_ARITY,\n POINT_SHAPE_TYPES,\n} from \"@glassly/miniapp/display\"\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.3.5-dev.
|
|
3
|
+
"version": "0.3.5-dev.753",
|
|
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.3.5-dev.
|
|
101
|
-
"@glassly/cloud-client": "0.3.5-dev.
|
|
102
|
-
"@glassly/cloud-protocol": "0.3.5-dev.
|
|
103
|
-
"@glassly/crust": "0.3.5-dev.
|
|
104
|
-
"@glassly/miniapp": "0.3.5-dev.
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.3.5-dev.753",
|
|
101
|
+
"@glassly/cloud-client": "0.3.5-dev.753",
|
|
102
|
+
"@glassly/cloud-protocol": "0.3.5-dev.753",
|
|
103
|
+
"@glassly/crust": "0.3.5-dev.753",
|
|
104
|
+
"@glassly/miniapp": "0.3.5-dev.753",
|
|
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.3.5-dev.
|
|
110
|
+
"@glassly/llm-providers": "0.3.5-dev.753",
|
|
111
111
|
"@huggingface/jinja": "^0.5.9",
|
|
112
112
|
"jsonrepair": "^3.12.0"
|
|
113
113
|
},
|
|
@@ -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.3.5",
|
|
15
|
-
"releaseIdentity": "0.3.5-dev.
|
|
16
|
-
"releaseSetId": "glassly-0.3.5-dev.
|
|
17
|
-
"sourceCommit": "
|
|
15
|
+
"releaseIdentity": "0.3.5-dev.753",
|
|
16
|
+
"releaseSetId": "glassly-0.3.5-dev.753",
|
|
17
|
+
"sourceCommit": "7cbf7d579ef35c5c469189177b3cd8b47baf9dd8",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
|
@@ -132,7 +132,8 @@ class GestureDotsService {
|
|
|
132
132
|
* `controllerId` keeps two controllers' fingers apart.
|
|
133
133
|
*/
|
|
134
134
|
noteGesture(gestureName: string, claimed = false, fingerIndex?: number, controllerId?: string): void {
|
|
135
|
-
|
|
135
|
+
// A ring's scroll steps ride beside their stroke's one swipe, which already flashed.
|
|
136
|
+
if (!this.started || !this.enabled() || gestureName === "scroll_up" || gestureName === "scroll_down") return
|
|
136
137
|
this.applyGesture(gestureName, claimed, fingerIndex, controllerId)
|
|
137
138
|
this.trace("input", {gestureName, claimed})
|
|
138
139
|
}
|
|
@@ -26,22 +26,46 @@ const miniappExitGesture = new MiniappExitGesture({
|
|
|
26
26
|
clearTimeout: (timerId) => BgTimer.clearTimeout(timerId),
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
// (deviceModel G2) or the ring sends
|
|
31
|
-
// both tag source 2.
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
// The ring reports a slide once per step of travel, whether the CFW forwards
|
|
30
|
+
// it (deviceModel G2) or the ring sends it to the phone itself (deviceModel
|
|
31
|
+
// R1); both tag source 2. One press is one swipe: the stroke's first step goes
|
|
32
|
+
// out as swipe_up / swipe_down, and every step, the first included, as
|
|
33
|
+
// scroll_up / scroll_down with its distance and speed for smooth scrolling. A
|
|
34
|
+
// stroke ends at the ring's next touch_down or tap_end, or after
|
|
35
|
+
// RING_STROKE_IDLE_MS without a step when a report was lost. Reversing within
|
|
36
|
+
// a stroke fires no swipe (a finger rolling back at lift-off). Native receipt
|
|
37
|
+
// timestamps keep strokes apart when JS drains a backlog.
|
|
38
|
+
const RING_STROKE_IDLE_MS = 1000
|
|
39
|
+
/** Per ring, when the open stroke's last step arrived. */
|
|
40
|
+
const ringStrokes = new Map<string, number>()
|
|
41
|
+
|
|
42
|
+
function ringKey(event: TouchEvent): string {
|
|
43
|
+
return event.controllerId ?? `source:${event.source}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isRingSlideStep(event: TouchEvent): boolean {
|
|
47
|
+
return event.source === 2 && (event.gestureName === "swipe_up" || event.gestureName === "swipe_down")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function opensRingStroke(event: TouchEvent): boolean {
|
|
51
|
+
const last = ringStrokes.get(ringKey(event))
|
|
52
|
+
ringStrokes.set(ringKey(event), event.timestamp)
|
|
41
53
|
const elapsed = last === undefined ? null : event.timestamp - last
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
return elapsed === null || elapsed < 0 || elapsed >= RING_STROKE_IDLE_MS
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function endRingStroke(event: TouchEvent): void {
|
|
58
|
+
if (event.source === 2 && (event.gestureName === "touch_down" || event.gestureName === "tap_end")) {
|
|
59
|
+
ringStrokes.delete(ringKey(event))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The discrete swipe a stroke opens with: the step's travel belongs to scroll_*. */
|
|
64
|
+
function strokeSwipe(step: TouchEvent): TouchEvent {
|
|
65
|
+
const swipe = {...step}
|
|
66
|
+
delete swipe.distance
|
|
67
|
+
delete swipe.speed
|
|
68
|
+
return swipe
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
/**
|
|
@@ -67,9 +91,20 @@ function toMiniapps(event: TouchEvent): TouchEvent & {input2d?: true} {
|
|
|
67
91
|
* dashboard's claim is for the screen with no app in it. Head position
|
|
68
92
|
* (head_up / head_down) is a gesture like any other: the dashboard claims it
|
|
69
93
|
* while it runs, and an unclaimed head_up on glasses that defer the dashboard
|
|
70
|
-
* to the phone opens the native one, as it would with no app at all.
|
|
94
|
+
* to the phone opens the native one, as it would with no app at all. A ring
|
|
95
|
+
* slide step becomes one swipe per stroke plus a scroll step (see above).
|
|
71
96
|
*/
|
|
72
97
|
export function dispatchTouchEvent(event: TouchEvent): void {
|
|
98
|
+
if (isRingSlideStep(event)) {
|
|
99
|
+
if (opensRingStroke(event)) routeTouchEvent(strokeSwipe(event))
|
|
100
|
+
routeTouchEvent({...event, gestureName: event.gestureName === "swipe_up" ? "scroll_up" : "scroll_down"})
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
endRingStroke(event)
|
|
104
|
+
routeTouchEvent(event)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function routeTouchEvent(event: TouchEvent): void {
|
|
73
108
|
const gesture = event.gestureName
|
|
74
109
|
const claimant = localMiniappRuntime.gestureClaimant(gesture)
|
|
75
110
|
// The Neural Band's middle finger (index 1) shares the tap names but is left
|
|
@@ -101,17 +136,14 @@ export function dispatchTouchEvent(event: TouchEvent): void {
|
|
|
101
136
|
}
|
|
102
137
|
// The press may have started before this app claimed its release edge.
|
|
103
138
|
gestureDotsService.noteGesture(gesture, true, event.index, event.controllerId)
|
|
104
|
-
if (!shouldForwardScroll(event)) return
|
|
105
139
|
localMiniappRuntime.forwardEvent("touch_event", toMiniapps(event), undefined, claimant)
|
|
106
140
|
return
|
|
107
141
|
}
|
|
108
142
|
|
|
109
143
|
// Gesture-dots debug indicator (any device, gated by its setting). It
|
|
110
|
-
// only paints while the screen is otherwise empty.
|
|
111
|
-
// scroll repeats here so their completion still clears a held dot.
|
|
144
|
+
// only paints while the screen is otherwise empty.
|
|
112
145
|
gestureDotsService.noteGesture(gesture, false, event.index, event.controllerId)
|
|
113
146
|
|
|
114
|
-
if (!shouldForwardScroll(event)) return
|
|
115
147
|
localMiniappRuntime.forwardEvent("touch_event", toMiniapps(event))
|
|
116
148
|
|
|
117
149
|
// Nobody claimed a double tap or a head-up: the glasses' native dashboard.
|
|
@@ -133,7 +165,7 @@ export function dispatchTouchEvent(event: TouchEvent): void {
|
|
|
133
165
|
|
|
134
166
|
export function resetTouchInput(): void {
|
|
135
167
|
miniappExitGesture.reset()
|
|
136
|
-
|
|
168
|
+
ringStrokes.clear()
|
|
137
169
|
}
|
|
138
170
|
|
|
139
171
|
/** Phone mirror input uses the same dashboard/miniapp policy as real glasses. */
|
|
@@ -66,7 +66,9 @@ const MAX_SETTLE_MS = 255 * 33
|
|
|
66
66
|
function settleTimeOf(elements: FrameElement[]): number {
|
|
67
67
|
let ms = 0
|
|
68
68
|
for (const el of elements) {
|
|
69
|
-
|
|
69
|
+
// Entering and leaving elements tween too, from or to their offset.
|
|
70
|
+
const edge = el.transition?.exit !== undefined || (el.change === "created" && el.transition?.enter !== undefined)
|
|
71
|
+
if (el.change !== "updated" && el.change !== "moved" && !edge) continue
|
|
70
72
|
const t = el.transition?.durationMs ?? 0
|
|
71
73
|
// Rotation has its own millisecond clock, beyond the geometry frame limit.
|
|
72
74
|
const duration = Math.min(t, el.rotation !== undefined ? 65535 : MAX_SETTLE_MS)
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
ScenePoint,
|
|
22
22
|
SceneTextStyle,
|
|
23
23
|
SceneTransition,
|
|
24
|
+
SceneTransitionEdge,
|
|
24
25
|
} from "./types"
|
|
25
26
|
import {MAX_SCENE_DEPTH, POINT_SHAPE_ARITY, POINT_SHAPE_TYPES, elementContentHash} from "./types"
|
|
26
27
|
|
|
@@ -66,11 +67,24 @@ function pointsBox(points: ScenePoint[]): SceneBox {
|
|
|
66
67
|
return {x, y, w: Math.max(...xs) - x + 1, h: Math.max(...ys) - y + 1}
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
function validEdge(e: unknown): SceneTransitionEdge | undefined {
|
|
71
|
+
if (!e || typeof e !== "object") return undefined
|
|
72
|
+
const dx = Math.round(Number((e as SceneTransitionEdge).dx ?? 0))
|
|
73
|
+
const dy = Math.round(Number((e as SceneTransitionEdge).dy ?? 0))
|
|
74
|
+
if (!isFinite(dx) || !isFinite(dy) || Math.abs(dx) > 1024 || Math.abs(dy) > 1024) return undefined
|
|
75
|
+
return {dx, dy}
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
function validTransition(t: unknown): SceneTransition | undefined {
|
|
70
79
|
if (!t || typeof t !== "object") return undefined
|
|
71
80
|
const {durationMs, easing} = t as SceneTransition
|
|
72
81
|
if (!isFinite(durationMs) || durationMs <= 0) return undefined
|
|
73
|
-
|
|
82
|
+
const out: SceneTransition = easing ? {durationMs, easing} : {durationMs}
|
|
83
|
+
const enter = validEdge((t as SceneTransition).enter)
|
|
84
|
+
const exit = validEdge((t as SceneTransition).exit)
|
|
85
|
+
if (enter) out.enter = enter
|
|
86
|
+
if (exit) out.exit = exit
|
|
87
|
+
return out
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
function boxShrunk(orig: SceneBox, clamped: SceneBox): boolean {
|
|
@@ -175,6 +189,8 @@ export function processScene(
|
|
|
175
189
|
|
|
176
190
|
for (const {el, index, box, points} of valid) {
|
|
177
191
|
const transition = validTransition((el as {transition?: unknown}).transition)
|
|
192
|
+
// Leaving elements only exist to tween away; a device that jumps never shows them.
|
|
193
|
+
if (transition?.exit && !caps.animation) continue
|
|
178
194
|
const transform: {rotation?: number; pivot?: ScenePoint} = {}
|
|
179
195
|
if (el.rotation !== undefined || el.pivot !== undefined) {
|
|
180
196
|
const angle = el.rotation ?? 0
|