@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.
Files changed (44) hide show
  1. package/build/generated/releaseMetadata.js +3 -3
  2. package/build/generated/releaseMetadata.js.map +1 -1
  3. package/build/services/SceneRenderer.d.ts.map +1 -1
  4. package/build/services/SceneRenderer.js +1 -0
  5. package/build/services/SceneRenderer.js.map +1 -1
  6. package/build/services/effectiveCapabilities.d.ts.map +1 -1
  7. package/build/services/effectiveCapabilities.js +5 -1
  8. package/build/services/effectiveCapabilities.js.map +1 -1
  9. package/build/stores/glasses.d.ts.map +1 -1
  10. package/build/stores/glasses.js +1 -0
  11. package/build/stores/glasses.js.map +1 -1
  12. package/build/types/hardware.d.ts +6 -1
  13. package/build/types/hardware.d.ts.map +1 -1
  14. package/build/types/hardware.js +2 -0
  15. package/build/types/hardware.js.map +1 -1
  16. package/build/types/index.d.ts +1 -0
  17. package/build/types/index.d.ts.map +1 -1
  18. package/build/types/index.js +1 -0
  19. package/build/types/index.js.map +1 -1
  20. package/build/utils/display/scene/degrade.d.ts.map +1 -1
  21. package/build/utils/display/scene/degrade.js +4 -1
  22. package/build/utils/display/scene/degrade.js.map +1 -1
  23. package/build/utils/display/scene/differ.d.ts +5 -1
  24. package/build/utils/display/scene/differ.d.ts.map +1 -1
  25. package/build/utils/display/scene/differ.js +8 -0
  26. package/build/utils/display/scene/differ.js.map +1 -1
  27. package/build/utils/display/scene/process.d.ts.map +1 -1
  28. package/build/utils/display/scene/process.js +84 -7
  29. package/build/utils/display/scene/process.js.map +1 -1
  30. package/build/utils/display/scene/types.d.ts +102 -4
  31. package/build/utils/display/scene/types.d.ts.map +1 -1
  32. package/build/utils/display/scene/types.js +0 -0
  33. package/build/utils/display/scene/types.js.map +1 -1
  34. package/package.json +7 -7
  35. package/src/generated/releaseMetadata.ts +3 -3
  36. package/src/services/SceneRenderer.ts +1 -0
  37. package/src/services/effectiveCapabilities.ts +5 -1
  38. package/src/stores/glasses.ts +1 -0
  39. package/src/types/hardware.ts +8 -1
  40. package/src/types/index.ts +1 -0
  41. package/src/utils/display/scene/degrade.ts +4 -1
  42. package/src/utils/display/scene/differ.ts +9 -1
  43. package/src/utils/display/scene/process.ts +95 -9
  44. package/src/utils/display/scene/types.ts +0 -0
@@ -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?: "rect"[]
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
  */
@@ -17,6 +17,7 @@ export type {
17
17
  PowerCapabilities,
18
18
  Capabilities,
19
19
  } from "./hardware"
20
+ export {ALL_DISPLAY_SHAPES} from "./hardware"
20
21
 
21
22
  export {
22
23
  HARDWARE_CAPABILITIES,
@@ -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 {SceneBox, SceneDisplayCapabilities, SceneElementInput, SceneTextStyle} from "./types"
16
- import {elementContentHash} from "./types"
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" || !raw.box) {
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((n) => typeof n === "number" && Number.isFinite(n))) {
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 clamped = clampBox(el.box, caps.width, caps.height)
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(el.box, clamped)
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