@glassly/engine 0.1.0-dev.105 → 0.1.0-dev.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +1 -0
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/effectiveCapabilities.d.ts.map +1 -1
- package/build/services/effectiveCapabilities.js +5 -1
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +1 -0
- package/build/stores/glasses.js.map +1 -1
- package/build/types/hardware.d.ts +6 -1
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +2 -0
- package/build/types/hardware.js.map +1 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/index.js +1 -0
- package/build/types/index.js.map +1 -1
- package/build/utils/display/scene/degrade.d.ts.map +1 -1
- package/build/utils/display/scene/degrade.js +4 -1
- package/build/utils/display/scene/degrade.js.map +1 -1
- package/build/utils/display/scene/differ.d.ts +5 -1
- package/build/utils/display/scene/differ.d.ts.map +1 -1
- package/build/utils/display/scene/differ.js +8 -0
- package/build/utils/display/scene/differ.js.map +1 -1
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +84 -7
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/display/scene/types.d.ts +102 -4
- package/build/utils/display/scene/types.d.ts.map +1 -1
- package/build/utils/display/scene/types.js +0 -0
- package/build/utils/display/scene/types.js.map +1 -1
- package/package.json +7 -7
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/services/SceneRenderer.ts +1 -0
- package/src/services/effectiveCapabilities.ts +5 -1
- package/src/stores/glasses.ts +1 -0
- package/src/types/hardware.ts +8 -1
- package/src/types/index.ts +1 -0
- package/src/utils/display/scene/degrade.ts +4 -1
- package/src/utils/display/scene/differ.ts +9 -1
- package/src/utils/display/scene/process.ts +95 -9
- package/src/utils/display/scene/types.ts +0 -0
package/src/types/hardware.ts
CHANGED
|
@@ -56,11 +56,18 @@ export interface DisplayCapabilities {
|
|
|
56
56
|
maxImageElements?: number
|
|
57
57
|
/** Per-image dimension cap (box-level), when the firmware has one. */
|
|
58
58
|
maxImagePx?: {width: number; height: number}
|
|
59
|
-
shapes?:
|
|
59
|
+
shapes?: DisplayShape[]
|
|
60
|
+
/** True ⇒ the device animates element transitions itself. */
|
|
61
|
+
animation?: boolean
|
|
60
62
|
intensityLevels?: number
|
|
61
63
|
partialUpdate?: boolean
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
export type DisplayShape = "rect" | "circle" | "arc" | "pie" | "line" | "triangle" | "quad" | "bezier"
|
|
67
|
+
|
|
68
|
+
/** Every vector shape the Glassly CFW rasterizes on the glasses. */
|
|
69
|
+
export const ALL_DISPLAY_SHAPES: DisplayShape[] = ["rect", "circle", "arc", "pie", "line", "triangle", "quad", "bezier"]
|
|
70
|
+
|
|
64
71
|
/**
|
|
65
72
|
* Microphone capabilities
|
|
66
73
|
*/
|
package/src/types/index.ts
CHANGED
|
@@ -37,6 +37,9 @@ export function degradeScene(input: readonly SceneElementInput[]): DegradedScene
|
|
|
37
37
|
|
|
38
38
|
const texts: TextEl[] = []
|
|
39
39
|
input.forEach((el, index) => {
|
|
40
|
+
if (el && typeof el === "object" && Array.isArray((el as {points?: unknown}).points)) {
|
|
41
|
+
return // vector shapes: decoration on a text wall
|
|
42
|
+
}
|
|
40
43
|
if (!el || typeof el !== "object" || !el.box) {
|
|
41
44
|
// Malformed element — report it like the positioning pipeline does
|
|
42
45
|
// instead of silently pretending the render was clean.
|
|
@@ -50,7 +53,7 @@ export function degradeScene(input: readonly SceneElementInput[]): DegradedScene
|
|
|
50
53
|
dropped.push(el.id ?? `image[${index}]`)
|
|
51
54
|
degraded = true
|
|
52
55
|
}
|
|
53
|
-
// rects: silent drop — pure decoration on a text wall.
|
|
56
|
+
// rects and other shapes: silent drop — pure decoration on a text wall.
|
|
54
57
|
})
|
|
55
58
|
|
|
56
59
|
if (texts.length === 0) {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* prefixed "~" so they can never collide with app-supplied ids.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type {FrameElement, SceneBox, SceneChange, SceneElementType} from "./types"
|
|
18
|
+
import type {FrameElement, SceneBox, SceneChange, SceneElementType, ScenePoint, SceneTransition} from "./types"
|
|
19
19
|
import {boxesEqual} from "./types"
|
|
20
20
|
|
|
21
21
|
/** A processed (clamped/wrapped) element, pre-diff: id optional, no annotation yet. */
|
|
@@ -26,6 +26,10 @@ export interface DiffableElement {
|
|
|
26
26
|
text?: string
|
|
27
27
|
data?: string
|
|
28
28
|
style?: FrameElement["style"]
|
|
29
|
+
points?: ScenePoint[]
|
|
30
|
+
startDeg?: number
|
|
31
|
+
endDeg?: number
|
|
32
|
+
transition?: SceneTransition
|
|
29
33
|
contentHash: string
|
|
30
34
|
}
|
|
31
35
|
|
|
@@ -54,6 +58,10 @@ function buildFrameElement(el: DiffableElement, id: string, change: SceneChange)
|
|
|
54
58
|
if (el.text !== undefined) out.text = el.text
|
|
55
59
|
if (el.data !== undefined) out.data = el.data
|
|
56
60
|
if (el.style !== undefined) out.style = el.style
|
|
61
|
+
if (el.points !== undefined) out.points = el.points
|
|
62
|
+
if (el.startDeg !== undefined) out.startDeg = el.startDeg
|
|
63
|
+
if (el.endDeg !== undefined) out.endDeg = el.endDeg
|
|
64
|
+
if (el.transition !== undefined) out.transition = el.transition
|
|
57
65
|
return out
|
|
58
66
|
}
|
|
59
67
|
|
|
@@ -12,8 +12,16 @@ import {TextMeasurer} from "../measurer/TextMeasurer"
|
|
|
12
12
|
import {TextWrapper} from "../wrapper/TextWrapper"
|
|
13
13
|
import type {DisplayProfile} from "../profiles/types"
|
|
14
14
|
import type {DiffableElement} from "./differ"
|
|
15
|
-
import type {
|
|
16
|
-
|
|
15
|
+
import type {
|
|
16
|
+
SceneBox,
|
|
17
|
+
SceneBoxInput,
|
|
18
|
+
SceneDisplayCapabilities,
|
|
19
|
+
SceneElementInput,
|
|
20
|
+
ScenePoint,
|
|
21
|
+
SceneTextStyle,
|
|
22
|
+
SceneTransition,
|
|
23
|
+
} from "./types"
|
|
24
|
+
import {POINT_SHAPE_ARITY, POINT_SHAPE_TYPES, elementContentHash} from "./types"
|
|
17
25
|
|
|
18
26
|
export interface ProcessedScene {
|
|
19
27
|
elements: DiffableElement[]
|
|
@@ -35,6 +43,35 @@ function clampBox(box: SceneBox, width: number, height: number): SceneBox | null
|
|
|
35
43
|
return {x: x1, y: y1, w: x2 - x1, h: y2 - y1}
|
|
36
44
|
}
|
|
37
45
|
|
|
46
|
+
const isFinite = (n: unknown): n is number => typeof n === "number" && Number.isFinite(n)
|
|
47
|
+
|
|
48
|
+
/** Integer points, or null when the count/values are invalid for the type. */
|
|
49
|
+
function validPoints(el: SceneElementInput): ScenePoint[] | null {
|
|
50
|
+
const raw = (el as {points?: unknown}).points
|
|
51
|
+
if (!Array.isArray(raw) || !(POINT_SHAPE_ARITY[el.type] ?? []).includes(raw.length)) return null
|
|
52
|
+
const points: ScenePoint[] = []
|
|
53
|
+
for (const p of raw) {
|
|
54
|
+
if (!p || typeof p !== "object" || !isFinite((p as ScenePoint).x) || !isFinite((p as ScenePoint).y)) return null
|
|
55
|
+
points.push({x: Math.floor((p as ScenePoint).x), y: Math.floor((p as ScenePoint).y)})
|
|
56
|
+
}
|
|
57
|
+
return points
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pointsBox(points: ScenePoint[]): SceneBox {
|
|
61
|
+
const xs = points.map((p) => p.x)
|
|
62
|
+
const ys = points.map((p) => p.y)
|
|
63
|
+
const x = Math.min(...xs)
|
|
64
|
+
const y = Math.min(...ys)
|
|
65
|
+
return {x, y, w: Math.max(...xs) - x + 1, h: Math.max(...ys) - y + 1}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validTransition(t: unknown): SceneTransition | undefined {
|
|
69
|
+
if (!t || typeof t !== "object") return undefined
|
|
70
|
+
const {durationMs, easing} = t as SceneTransition
|
|
71
|
+
if (!isFinite(durationMs) || durationMs <= 0) return undefined
|
|
72
|
+
return easing ? {durationMs, easing} : {durationMs}
|
|
73
|
+
}
|
|
74
|
+
|
|
38
75
|
function boxShrunk(orig: SceneBox, clamped: SceneBox): boolean {
|
|
39
76
|
return (
|
|
40
77
|
clamped.x !== Math.floor(orig.x) ||
|
|
@@ -77,17 +114,38 @@ export function processScene(
|
|
|
77
114
|
|
|
78
115
|
// Validate + dedupe explicit ids (first occurrence wins; dupes are dev error).
|
|
79
116
|
const seenIds = new Set<string>()
|
|
80
|
-
const valid: {el: SceneElementInput; index: number}[] = []
|
|
117
|
+
const valid: {el: SceneElementInput; index: number; box: SceneBoxInput; points?: ScenePoint[]}[] = []
|
|
81
118
|
input.forEach((raw, index) => {
|
|
82
|
-
if (!raw || typeof raw !== "object"
|
|
119
|
+
if (!raw || typeof raw !== "object") {
|
|
83
120
|
dropped.push(reportId(raw ?? ({type: "text"} as SceneElementInput), index))
|
|
84
121
|
degraded = true
|
|
85
122
|
return
|
|
86
123
|
}
|
|
124
|
+
let points: ScenePoint[] | undefined
|
|
125
|
+
if (POINT_SHAPE_TYPES.includes(raw.type)) {
|
|
126
|
+
const valid = validPoints(raw)
|
|
127
|
+
if (!valid) {
|
|
128
|
+
dropped.push(reportId(raw, index))
|
|
129
|
+
degraded = true
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
points = valid
|
|
133
|
+
raw = {...raw, box: pointsBox(valid)} as SceneElementInput
|
|
134
|
+
}
|
|
135
|
+
if (!raw.box) {
|
|
136
|
+
dropped.push(reportId(raw, index))
|
|
137
|
+
degraded = true
|
|
138
|
+
return
|
|
139
|
+
}
|
|
87
140
|
// Non-finite box numbers (NaN/±Infinity/non-number) would survive the
|
|
88
141
|
// Math.* clamp below and poison the frame all the way to native ints.
|
|
89
142
|
const b = raw.box
|
|
90
|
-
if (![b.x, b.y, b.w, b.h].every(
|
|
143
|
+
if (![b.x, b.y, b.w, b.h].every(isFinite)) {
|
|
144
|
+
dropped.push(reportId(raw, index))
|
|
145
|
+
degraded = true
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
if ((raw.type === "arc" || raw.type === "pie") && !(isFinite(raw.startDeg) && isFinite(raw.endDeg))) {
|
|
91
149
|
dropped.push(reportId(raw, index))
|
|
92
150
|
degraded = true
|
|
93
151
|
return
|
|
@@ -105,7 +163,7 @@ export function processScene(
|
|
|
105
163
|
}
|
|
106
164
|
seenIds.add(key)
|
|
107
165
|
}
|
|
108
|
-
valid.push({el, index})
|
|
166
|
+
valid.push({el, index, box: el.box as SceneBoxInput, points})
|
|
109
167
|
})
|
|
110
168
|
|
|
111
169
|
// Clamp + per-type limits, then budget in array order.
|
|
@@ -113,16 +171,40 @@ export function processScene(
|
|
|
113
171
|
let imageBudget = caps.maxImageElements
|
|
114
172
|
const out: DiffableElement[] = []
|
|
115
173
|
|
|
116
|
-
for (const {el, index} of valid) {
|
|
117
|
-
const
|
|
174
|
+
for (const {el, index, box, points} of valid) {
|
|
175
|
+
const transition = validTransition((el as {transition?: unknown}).transition)
|
|
176
|
+
const clamped = clampBox(box, caps.width, caps.height)
|
|
118
177
|
if (!clamped) {
|
|
119
178
|
dropped.push(reportId(el, index))
|
|
120
179
|
degraded = true
|
|
121
180
|
continue
|
|
122
181
|
}
|
|
123
|
-
const shrunk = boxShrunk(
|
|
182
|
+
const shrunk = boxShrunk(box, clamped)
|
|
124
183
|
if (shrunk) degraded = true
|
|
125
184
|
|
|
185
|
+
if (el.type !== "text" && el.type !== "image" && el.type !== "rect") {
|
|
186
|
+
// Vector shapes: only devices that draw them get them; the device clips
|
|
187
|
+
// point shapes itself, so an off-canvas point is not a drop.
|
|
188
|
+
if (!caps.shapes.includes(el.type) || textBudget <= 0) {
|
|
189
|
+
dropped.push(reportId(el, index))
|
|
190
|
+
degraded = true
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
textBudget--
|
|
194
|
+
const angles = el.type === "arc" || el.type === "pie" ? {startDeg: el.startDeg, endDeg: el.endDeg} : {}
|
|
195
|
+
out.push({
|
|
196
|
+
id: el.id,
|
|
197
|
+
type: el.type,
|
|
198
|
+
box: clamped,
|
|
199
|
+
style: el.style,
|
|
200
|
+
...(points ? {points} : {}),
|
|
201
|
+
...angles,
|
|
202
|
+
...(transition ? {transition} : {}),
|
|
203
|
+
contentHash: elementContentHash({type: el.type, style: el.style, points, ...angles}),
|
|
204
|
+
})
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
|
|
126
208
|
if (el.type === "image") {
|
|
127
209
|
// Images: a shrunk box can't be honored (we don't crop pixels host-side)
|
|
128
210
|
// and per-image device limits are box-level (the firmware allocates the
|
|
@@ -148,6 +230,7 @@ export function processScene(
|
|
|
148
230
|
type: "image",
|
|
149
231
|
box: clamped,
|
|
150
232
|
data: el.data,
|
|
233
|
+
...(transition ? {transition} : {}),
|
|
151
234
|
contentHash: elementContentHash({type: "image", data: el.data}),
|
|
152
235
|
})
|
|
153
236
|
continue
|
|
@@ -167,6 +250,7 @@ export function processScene(
|
|
|
167
250
|
type: "rect",
|
|
168
251
|
box: clamped,
|
|
169
252
|
style: el.style,
|
|
253
|
+
...(transition ? {transition} : {}),
|
|
170
254
|
contentHash: elementContentHash({type: "rect", style: el.style}),
|
|
171
255
|
})
|
|
172
256
|
continue
|
|
@@ -188,6 +272,7 @@ export function processScene(
|
|
|
188
272
|
box: clamped,
|
|
189
273
|
text: monoText,
|
|
190
274
|
style: el.style,
|
|
275
|
+
...(transition ? {transition} : {}),
|
|
191
276
|
contentHash: elementContentHash({type: "text", text: monoText, style: el.style}),
|
|
192
277
|
})
|
|
193
278
|
continue
|
|
@@ -221,6 +306,7 @@ export function processScene(
|
|
|
221
306
|
box: clamped,
|
|
222
307
|
text: wrappedText,
|
|
223
308
|
style: el.style,
|
|
309
|
+
...(transition ? {transition} : {}),
|
|
224
310
|
contentHash: elementContentHash({type: "text", text: wrappedText, style: el.style}),
|
|
225
311
|
})
|
|
226
312
|
}
|
|
Binary file
|