@glassly/engine 0.3.7 → 0.3.8-beta.1013
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/build/engine.d.ts +18 -2
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +7 -4
- package/build/engine.js.map +1 -1
- package/build/facades/ai.d.ts +3 -0
- package/build/facades/ai.d.ts.map +1 -1
- package/build/facades/ai.js +3 -0
- package/build/facades/ai.js.map +1 -1
- package/build/facades/backup.d.ts +14 -0
- package/build/facades/backup.d.ts.map +1 -0
- package/build/facades/backup.js +22 -0
- package/build/facades/backup.js.map +1 -0
- package/build/facades/glasses.d.ts +13 -5
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +17 -4
- package/build/facades/glasses.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/services/AppRegistry.d.ts +2 -1
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +38 -8
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/AppStateBackup.d.ts +98 -0
- package/build/services/AppStateBackup.d.ts.map +1 -0
- package/build/services/AppStateBackup.js +224 -0
- package/build/services/AppStateBackup.js.map +1 -0
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +5 -12
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/CloudClientService.d.ts +3 -1
- package/build/services/CloudClientService.d.ts.map +1 -1
- package/build/services/CloudClientService.js +42 -2
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +1 -0
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/DisplayDiagnostics.d.ts.map +1 -1
- package/build/services/DisplayDiagnostics.js +4 -2
- package/build/services/DisplayDiagnostics.js.map +1 -1
- package/build/services/DisplayProcessor.d.ts +2 -0
- package/build/services/DisplayProcessor.d.ts.map +1 -1
- package/build/services/DisplayProcessor.js +15 -1
- package/build/services/DisplayProcessor.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +4 -2
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/JSCrashloopReportService.d.ts.map +1 -1
- package/build/services/JSCrashloopReportService.js +5 -0
- package/build/services/JSCrashloopReportService.js.map +1 -1
- package/build/services/JSRouter.d.ts +11 -3
- package/build/services/JSRouter.d.ts.map +1 -1
- package/build/services/JSRouter.js +36 -6
- package/build/services/JSRouter.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +5 -2
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +13 -0
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +61 -22
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
- package/build/services/LocalSttFallbackCoordinator.js +2 -0
- package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
- package/build/services/LocalTranslationService.d.ts +2 -0
- package/build/services/LocalTranslationService.d.ts.map +1 -1
- package/build/services/LocalTranslationService.js +22 -0
- package/build/services/LocalTranslationService.js.map +1 -1
- package/build/services/MiniappEngine.d.ts +9 -0
- package/build/services/MiniappEngine.d.ts.map +1 -1
- package/build/services/MiniappEngine.js +60 -27
- package/build/services/MiniappEngine.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts +2 -0
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +6 -0
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/NavigationService.d.ts +1 -0
- package/build/services/NavigationService.d.ts.map +1 -1
- package/build/services/NavigationService.js +20 -6
- package/build/services/NavigationService.js.map +1 -1
- package/build/services/OfflineActivity.d.ts +54 -0
- package/build/services/OfflineActivity.d.ts.map +1 -0
- package/build/services/OfflineActivity.js +86 -0
- package/build/services/OfflineActivity.js.map +1 -0
- package/build/services/PhoneCalendarService.d.ts.map +1 -1
- package/build/services/PhoneCalendarService.js +8 -0
- package/build/services/PhoneCalendarService.js.map +1 -1
- package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
- package/build/services/PhoneNotificationsSync.js +7 -3
- package/build/services/PhoneNotificationsSync.js.map +1 -1
- package/build/services/STTModelManager.d.ts +1 -0
- package/build/services/STTModelManager.d.ts.map +1 -1
- package/build/services/STTModelManager.js +6 -0
- package/build/services/STTModelManager.js.map +1 -1
- package/build/services/SceneRenderer.js +1 -1
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/appStateBackupFile.d.ts +32 -0
- package/build/services/appStateBackupFile.d.ts.map +1 -0
- package/build/services/appStateBackupFile.js +135 -0
- package/build/services/appStateBackupFile.js.map +1 -0
- package/build/services/controllers.d.ts.map +1 -1
- package/build/services/controllers.js +13 -3
- package/build/services/controllers.js.map +1 -1
- package/build/services/effectiveCapabilities.js +6 -1
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.d.ts +12 -1
- package/build/services/neuralBand/NeuralBandInput.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.js +27 -0
- package/build/services/neuralBand/NeuralBandInput.js.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.js +6 -0
- package/build/services/neuralBand/NeuralBandSettingsSync.js.map +1 -1
- package/build/services/pages/pageEligibility.d.ts.map +1 -1
- package/build/services/pages/pageEligibility.js +8 -1
- package/build/services/pages/pageEligibility.js.map +1 -1
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +1 -0
- package/build/services/settingsBridge.js.map +1 -1
- package/build/services/{appleWatch/AppleWatchLink.d.ts → watch/WatchLink.d.ts} +8 -7
- package/build/services/watch/WatchLink.d.ts.map +1 -0
- package/build/services/{appleWatch/AppleWatchLink.js → watch/WatchLink.js} +14 -15
- package/build/services/watch/WatchLink.js.map +1 -0
- package/build/stores/appVisibility.d.ts.map +1 -1
- package/build/stores/appVisibility.js +0 -1
- package/build/stores/appVisibility.js.map +1 -1
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +22 -10
- package/build/stores/apps.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +71 -6
- package/build/stores/settings.js.map +1 -1
- package/build/types/capabilities/even-realities-g1.d.ts.map +1 -1
- package/build/types/capabilities/even-realities-g1.js +3 -0
- package/build/types/capabilities/even-realities-g1.js.map +1 -1
- package/build/types/capabilities/memomind-one.d.ts +3 -0
- package/build/types/capabilities/memomind-one.d.ts.map +1 -0
- package/build/types/capabilities/memomind-one.js +42 -0
- package/build/types/capabilities/memomind-one.js.map +1 -0
- package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
- package/build/types/capabilities/rayneo-io.js +3 -2
- package/build/types/capabilities/rayneo-io.js.map +1 -1
- package/build/types/enums.d.ts +8 -2
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +6 -0
- package/build/types/enums.js.map +1 -1
- package/build/types/hardware.d.ts +3 -1
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +3 -1
- package/build/types/hardware.js.map +1 -1
- package/build/utils/display/index.d.ts +2 -1
- package/build/utils/display/index.d.ts.map +1 -1
- package/build/utils/display/index.js +3 -2
- package/build/utils/display/index.js.map +1 -1
- package/build/utils/display/profiles/index.d.ts +2 -1
- package/build/utils/display/profiles/index.d.ts.map +1 -1
- package/build/utils/display/profiles/index.js +3 -2
- package/build/utils/display/profiles/index.js.map +1 -1
- package/build/utils/display/profiles/rayneo-io.d.ts +1 -1
- package/build/utils/display/profiles/rayneo-io.d.ts.map +1 -1
- package/build/utils/display/profiles/rayneo-io.js +1 -1
- package/build/utils/display/profiles/rayneo-io.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 +1 -1
- package/build/utils/display/scene/index.js.map +1 -1
- package/build/utils/display/scene/process.d.ts +6 -0
- package/build/utils/display/scene/process.d.ts.map +1 -1
- package/build/utils/display/scene/process.js +22 -7
- package/build/utils/display/scene/process.js.map +1 -1
- package/build/utils/miniappGlobals.d.ts +13 -17
- package/build/utils/miniappGlobals.d.ts.map +1 -1
- package/build/utils/miniappGlobals.js +24 -56
- package/build/utils/miniappGlobals.js.map +1 -1
- package/package.json +7 -7
- package/src/engine.ts +7 -4
- package/src/facades/ai.ts +4 -0
- package/src/facades/backup.ts +28 -0
- package/src/facades/glasses.ts +22 -5
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +10 -0
- package/src/services/AppRegistry.ts +36 -9
- package/src/services/AppStateBackup.ts +283 -0
- package/src/services/BlobStore.ts +5 -11
- package/src/services/CloudClientService.ts +43 -4
- package/src/services/DeviceEventRouter.ts +1 -0
- package/src/services/DisplayDiagnostics.ts +4 -2
- package/src/services/DisplayProcessor.ts +20 -8
- package/src/services/GlassesTouchInput.ts +6 -2
- package/src/services/JSCrashloopReportService.ts +5 -0
- package/src/services/JSRouter.ts +37 -6
- package/src/services/LocalDisplayManager.ts +5 -2
- package/src/services/LocalMiniappRuntime.ts +61 -22
- package/src/services/LocalSttFallbackCoordinator.ts +4 -0
- package/src/services/LocalTranslationService.ts +20 -0
- package/src/services/MiniappEngine.ts +62 -27
- package/src/services/MiniappLauncher.ts +7 -0
- package/src/services/NavigationService.ts +28 -8
- package/src/services/OfflineActivity.ts +131 -0
- package/src/services/PhoneCalendarService.ts +7 -0
- package/src/services/PhoneNotificationsSync.ts +8 -3
- package/src/services/STTModelManager.ts +6 -0
- package/src/services/SceneRenderer.ts +1 -1
- package/src/services/appStateBackupFile.ts +171 -0
- package/src/services/controllers.ts +14 -3
- package/src/services/effectiveCapabilities.ts +6 -1
- package/src/services/neuralBand/NeuralBandInput.ts +37 -1
- package/src/services/neuralBand/NeuralBandSettingsSync.ts +12 -0
- package/src/services/pages/pageEligibility.ts +9 -1
- package/src/services/settingsBridge.ts +1 -0
- package/src/services/{appleWatch/AppleWatchLink.ts → watch/WatchLink.ts} +20 -20
- package/src/stores/appVisibility.ts +0 -1
- package/src/stores/apps.ts +22 -11
- package/src/stores/settings.ts +78 -10
- package/src/types/capabilities/even-realities-g1.ts +4 -0
- package/src/types/capabilities/memomind-one.ts +43 -0
- package/src/types/capabilities/rayneo-io.ts +3 -2
- package/src/types/enums.ts +6 -0
- package/src/types/hardware.ts +14 -1
- package/src/utils/display/index.ts +4 -2
- package/src/utils/display/profiles/index.ts +9 -2
- package/src/utils/display/profiles/rayneo-io.ts +6 -1
- package/src/utils/display/scene/index.ts +1 -1
- package/src/utils/display/scene/process.ts +22 -7
- package/src/utils/miniappGlobals.ts +32 -64
- package/build/services/appleWatch/AppleWatchLink.d.ts.map +0 -1
- package/build/services/appleWatch/AppleWatchLink.js.map +0 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The watch remote's phone half, for the Apple Watch (iOS, WatchConnectivity) and a Wear OS
|
|
3
|
+
* watch (Android, Wearable Data Layer) alike: the watch app's status (glasses, the miniapp in
|
|
3
4
|
* front, launchable miniapps, whether Assist takes questions, the mirror setting), mirror frames
|
|
4
5
|
* while the setting is on, and the commands the watch sends back. Native adds whether the active
|
|
5
6
|
* watch is paired and keeps the newest frame for when the watch app opens. Gestures ride
|
|
6
7
|
* `touch_event` like any controller's.
|
|
7
8
|
*/
|
|
8
|
-
import type {
|
|
9
|
+
import type {WatchCommandEvent} from "@glassly/bluetooth-sdk"
|
|
9
10
|
import BluetoothSdk from "@glassly/bluetooth-sdk/internal"
|
|
10
11
|
import type {MirrorSnapshot} from "@glassly/miniapp"
|
|
11
12
|
import {MiniappStreamType} from "@glassly/miniapp"
|
|
12
|
-
import {Platform} from "react-native"
|
|
13
13
|
|
|
14
14
|
import {useAppStatusStore} from "../../stores/apps"
|
|
15
15
|
import {useGlassesStore} from "../../stores/glasses"
|
|
@@ -23,7 +23,7 @@ import {keyEventsForTextChange} from "../neuralBand/NeuralBandInput"
|
|
|
23
23
|
|
|
24
24
|
export const ASSIST_PACKAGE = "com.glassly.assistant"
|
|
25
25
|
export const ASSIST_ACTION = "ask"
|
|
26
|
-
/** WatchConnectivity messages top out near 64 KB; frames past this are skipped, not truncated. */
|
|
26
|
+
/** WatchConnectivity messages top out near 64 KB (Data Layer messages at 100 KB); frames past this are skipped, not truncated. */
|
|
27
27
|
const MAX_FRAME_BYTES = 48_000
|
|
28
28
|
const MAX_IMAGE_CHARS = 16_000
|
|
29
29
|
const MAX_APPS = 24
|
|
@@ -142,7 +142,7 @@ export function mirrorFrame(snapshot: MirrorSnapshot): Record<string, unknown> |
|
|
|
142
142
|
return JSON.stringify(frame).length > MAX_FRAME_BYTES ? null : frame
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
export interface
|
|
145
|
+
export interface WatchCommandDeps {
|
|
146
146
|
apps: () => readonly ClientApp[]
|
|
147
147
|
start: (app: ClientApp) => Promise<unknown>
|
|
148
148
|
stop: (packageName: string) => Promise<unknown>
|
|
@@ -151,7 +151,7 @@ export interface AppleWatchCommandDeps {
|
|
|
151
151
|
now: () => number
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
const defaultDeps:
|
|
154
|
+
const defaultDeps: WatchCommandDeps = {
|
|
155
155
|
apps: () => useAppStatusStore.getState().apps,
|
|
156
156
|
start: (app) => useAppStatusStore.getState().start(app),
|
|
157
157
|
stop: (packageName) => useAppStatusStore.getState().stop(packageName),
|
|
@@ -160,9 +160,9 @@ const defaultDeps: AppleWatchCommandDeps = {
|
|
|
160
160
|
now: () => Date.now(),
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
export async function
|
|
164
|
-
event:
|
|
165
|
-
deps:
|
|
163
|
+
export async function handleWatchCommand(
|
|
164
|
+
event: WatchCommandEvent,
|
|
165
|
+
deps: WatchCommandDeps = defaultDeps,
|
|
166
166
|
): Promise<void> {
|
|
167
167
|
switch (event.command) {
|
|
168
168
|
case "launch": {
|
|
@@ -170,7 +170,7 @@ export async function handleAppleWatchCommand(
|
|
|
170
170
|
? deps.apps().find((entry) => entry.packageName === event.packageName)
|
|
171
171
|
: undefined
|
|
172
172
|
if (!app) {
|
|
173
|
-
console.warn(`
|
|
173
|
+
console.warn(`Watch: launch of unknown miniapp ${event.packageName}`)
|
|
174
174
|
return
|
|
175
175
|
}
|
|
176
176
|
await deps.start(app)
|
|
@@ -206,7 +206,7 @@ function currentState(): Record<string, unknown> {
|
|
|
206
206
|
},
|
|
207
207
|
apps: useAppStatusStore.getState().apps,
|
|
208
208
|
assist: localMiniappRuntime.hostActionAvailable(ASSIST_PACKAGE, ASSIST_ACTION),
|
|
209
|
-
mirror: useSettingsStore.getState().getSetting(SETTINGS.
|
|
209
|
+
mirror: useSettingsStore.getState().getSetting(SETTINGS.watch_mirror.key) === true,
|
|
210
210
|
})
|
|
211
211
|
}
|
|
212
212
|
|
|
@@ -226,7 +226,7 @@ function syncMirror(enabled: boolean): void {
|
|
|
226
226
|
const send = (snapshot: MirrorSnapshot) => {
|
|
227
227
|
lastSentAt = Date.now()
|
|
228
228
|
void BluetoothSdk.setWatchMirror(mirrorFrame(snapshot)).catch((err) =>
|
|
229
|
-
console.warn("
|
|
229
|
+
console.warn("Watch: mirror push failed:", err),
|
|
230
230
|
)
|
|
231
231
|
}
|
|
232
232
|
const unsubscribe = mirrorService.subscribe((snapshot) => {
|
|
@@ -252,8 +252,8 @@ function syncMirror(enabled: boolean): void {
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
export function
|
|
256
|
-
if (
|
|
255
|
+
export function startWatchLink(): void {
|
|
256
|
+
if (stops.length > 0) return
|
|
257
257
|
let lastKey = ""
|
|
258
258
|
let timer: ReturnType<typeof BgTimer.setTimeout> | null = null
|
|
259
259
|
const push = () => {
|
|
@@ -262,21 +262,21 @@ export function startAppleWatchLink(): void {
|
|
|
262
262
|
const key = JSON.stringify(state)
|
|
263
263
|
if (key === lastKey) return
|
|
264
264
|
lastKey = key
|
|
265
|
-
void BluetoothSdk.setWatchState(state).catch((err) => console.warn("
|
|
265
|
+
void BluetoothSdk.setWatchState(state).catch((err) => console.warn("Watch: state push failed:", err))
|
|
266
266
|
}
|
|
267
267
|
const schedule = () => {
|
|
268
268
|
timer ??= BgTimer.setTimeout(push, STATE_DEBOUNCE_MS)
|
|
269
269
|
}
|
|
270
|
-
const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.
|
|
271
|
-
const commands = BluetoothSdk.addListener("
|
|
272
|
-
|
|
270
|
+
const mirrorEnabled = () => useSettingsStore.getState().getSetting(SETTINGS.watch_mirror.key) === true
|
|
271
|
+
const commands = BluetoothSdk.addListener("watch_command", (event) => {
|
|
272
|
+
handleWatchCommand(event).catch((err) => console.warn(`Watch: ${event.command} failed:`, err))
|
|
273
273
|
})
|
|
274
274
|
stops = [
|
|
275
275
|
() => commands.remove(),
|
|
276
276
|
useGlassesStore.subscribe(schedule),
|
|
277
277
|
useAppStatusStore.subscribe(schedule),
|
|
278
278
|
useSettingsStore.subscribe(
|
|
279
|
-
(s) => s.getSetting(SETTINGS.
|
|
279
|
+
(s) => s.getSetting(SETTINGS.watch_mirror.key),
|
|
280
280
|
() => {
|
|
281
281
|
syncMirror(mirrorEnabled())
|
|
282
282
|
schedule()
|
|
@@ -291,7 +291,7 @@ export function startAppleWatchLink(): void {
|
|
|
291
291
|
schedule()
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
export function
|
|
294
|
+
export function stopWatchLink(): void {
|
|
295
295
|
stops.splice(0).forEach((stop) => stop())
|
|
296
296
|
syncMirror(false)
|
|
297
297
|
}
|
package/src/stores/apps.ts
CHANGED
|
@@ -217,6 +217,7 @@ function projectApps(previousState: AppStatusState, localApps: ClientApp[]): Cli
|
|
|
217
217
|
DeviceTypes.NONE
|
|
218
218
|
const capabilities = getModelCapabilities(defaultWearable)
|
|
219
219
|
const devToolsEnabled = useSettingsStore.getState().getSetting(SETTINGS.dev_tools.key) === true
|
|
220
|
+
const persistScreenshots = useSettingsStore.getState().getSetting(SETTINGS.persist_miniapp_screenshots.key) !== false
|
|
220
221
|
const hasPairedGlasses = useSettingsStore.getState().getSetting(SETTINGS.has_paired_glasses.key) === true
|
|
221
222
|
|
|
222
223
|
// Single pass: dedupe → screenshot carry-over → compat → hidden, all into
|
|
@@ -246,7 +247,10 @@ function projectApps(previousState: AppStatusState, localApps: ClientApp[]): Cli
|
|
|
246
247
|
.map((app) => {
|
|
247
248
|
const next: ClientApp = {
|
|
248
249
|
...app,
|
|
249
|
-
|
|
250
|
+
// The registry's screenshot is the one restored from disk; only an
|
|
251
|
+
// in-memory capture carries over when persistence is off.
|
|
252
|
+
screenshot:
|
|
253
|
+
previousByPackage.get(app.packageName)?.screenshot ?? (persistScreenshots ? app.screenshot : undefined),
|
|
250
254
|
compatibility: HardwareCompatibility.checkCompatibility(app.hardwareRequirements, capabilities),
|
|
251
255
|
// A registry source can force an app off the primary home screen.
|
|
252
256
|
// Compose that gate with the user's persisted home-screen layout instead
|
|
@@ -384,15 +388,6 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
|
|
|
384
388
|
// owns the settings store).
|
|
385
389
|
useSettingsStore.getState().setSetting(SETTINGS.has_ever_activated_app.key, true)
|
|
386
390
|
|
|
387
|
-
// If the tapped app is already running, this is a re-open: the nav/foreground
|
|
388
|
-
// above already drove the animation, and there's nothing to spawn. Short-
|
|
389
|
-
// circuit before the spawn pipeline. A promoted app keeps its context but
|
|
390
|
-
// still takes the foreground below.
|
|
391
|
-
if (app.running && !app.widgetOnly) {
|
|
392
|
-
saveLastOpenTime(packageName)
|
|
393
|
-
return true
|
|
394
|
-
}
|
|
395
|
-
|
|
396
391
|
// Only-one rules: one standard app in the foreground, one dashboard
|
|
397
392
|
// drawing the overlay. Starting another stops the one running. Widget-only
|
|
398
393
|
// peers hold no foreground, so they stay.
|
|
@@ -405,6 +400,17 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
|
|
|
405
400
|
}
|
|
406
401
|
}
|
|
407
402
|
|
|
403
|
+
// If the tapped app is already running, this is a re-open: the nav/foreground
|
|
404
|
+
// above already drove the animation, and there's nothing to spawn. Short-
|
|
405
|
+
// circuit before the spawn pipeline. A promoted app keeps its context but
|
|
406
|
+
// still takes the foreground below.
|
|
407
|
+
if (app.running && !app.widgetOnly) {
|
|
408
|
+
saveLastOpenTime(packageName)
|
|
409
|
+
// The running background only knows the source of its spawn.
|
|
410
|
+
if (app.local) miniappLauncher.notifyLaunch(packageName, opts?.launchSource ?? "phone")
|
|
411
|
+
return true
|
|
412
|
+
}
|
|
413
|
+
|
|
408
414
|
set((s) => ({
|
|
409
415
|
apps: s.apps.map((a) => (a.packageName === packageName ? {...a, running: true, loading: false} : a)),
|
|
410
416
|
}))
|
|
@@ -543,7 +549,12 @@ export const useAppStatusStore = create<AppStatusState>((set, get) => ({
|
|
|
543
549
|
},
|
|
544
550
|
|
|
545
551
|
saveScreenshot: async (packageName: string, screenshot: string) => {
|
|
546
|
-
|
|
552
|
+
const key = `${packageName}_screenshot`
|
|
553
|
+
if (useSettingsStore.getState().getSetting(SETTINGS.persist_miniapp_screenshots.key) !== false) {
|
|
554
|
+
storage.save(key, screenshot)
|
|
555
|
+
} else {
|
|
556
|
+
storage.remove(key)
|
|
557
|
+
}
|
|
547
558
|
set((s) => ({
|
|
548
559
|
apps: s.apps.map((a) => (a.packageName === packageName ? {...a, screenshot} : a)),
|
|
549
560
|
}))
|
package/src/stores/settings.ts
CHANGED
|
@@ -132,6 +132,13 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
132
132
|
saveOnServer: true,
|
|
133
133
|
persist: true,
|
|
134
134
|
},
|
|
135
|
+
memomind_accessory_picker: {
|
|
136
|
+
key: "memomind_accessory_picker",
|
|
137
|
+
defaultValue: () => false,
|
|
138
|
+
writable: true,
|
|
139
|
+
saveOnServer: false,
|
|
140
|
+
persist: true,
|
|
141
|
+
},
|
|
135
142
|
debug_console: {
|
|
136
143
|
key: "debug_console",
|
|
137
144
|
defaultValue: () => false,
|
|
@@ -803,8 +810,8 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
803
810
|
// OFF by default: they crowd out everything else in the ten-minute buffer.
|
|
804
811
|
// Synced to the Bluetooth SDK, which keeps counting either way and only
|
|
805
812
|
// logs while this is on. Device-local debug knob, so not saved on the server.
|
|
806
|
-
|
|
807
|
-
key: "
|
|
813
|
+
perf_logs: {
|
|
814
|
+
key: "perf_logs",
|
|
808
815
|
defaultValue: () => false,
|
|
809
816
|
writable: true,
|
|
810
817
|
saveOnServer: false,
|
|
@@ -841,12 +848,29 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
841
848
|
saveOnServer: false,
|
|
842
849
|
persist: true,
|
|
843
850
|
},
|
|
844
|
-
//
|
|
845
|
-
|
|
851
|
+
// The band's own buzz on each gesture (its haptics pattern 1; pattern 2 is silent).
|
|
852
|
+
neural_band_vibration: {
|
|
853
|
+
key: "neural_band_vibration",
|
|
854
|
+
defaultValue: () => true,
|
|
855
|
+
writable: true,
|
|
856
|
+
saveOnServer: false,
|
|
857
|
+
persist: true,
|
|
858
|
+
},
|
|
859
|
+
// Watch (Apple Watch, Wear OS): draw the glasses' current frame behind the watch
|
|
860
|
+
// trackpad. Frames cross the watch link while the watch app is up, so it stays off
|
|
861
|
+
// unless asked for.
|
|
862
|
+
watch_mirror: {
|
|
863
|
+
key: "watch_mirror",
|
|
864
|
+
defaultValue: () => false,
|
|
865
|
+
writable: true,
|
|
866
|
+
saveOnServer: false,
|
|
867
|
+
persist: true,
|
|
868
|
+
},
|
|
869
|
+
// Deprecated: the setting above under its Apple-only name; read once to migrate.
|
|
846
870
|
apple_watch_mirror: {
|
|
847
871
|
key: "apple_watch_mirror",
|
|
848
872
|
defaultValue: () => false,
|
|
849
|
-
writable:
|
|
873
|
+
writable: false,
|
|
850
874
|
saveOnServer: false,
|
|
851
875
|
persist: true,
|
|
852
876
|
},
|
|
@@ -904,6 +928,23 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
904
928
|
saveOnServer: false,
|
|
905
929
|
persist: true,
|
|
906
930
|
},
|
|
931
|
+
// Debug: keep each miniapp's switcher-card screenshot on disk so the card
|
|
932
|
+
// comes back after an app restart. Off keeps captures in memory only, for
|
|
933
|
+
// checking the capture path itself. Device-local, default on.
|
|
934
|
+
persist_miniapp_screenshots: {
|
|
935
|
+
key: "persist_miniapp_screenshots",
|
|
936
|
+
defaultValue: () => true,
|
|
937
|
+
writable: true,
|
|
938
|
+
saveOnServer: false,
|
|
939
|
+
persist: true,
|
|
940
|
+
},
|
|
941
|
+
home_offline_card: {
|
|
942
|
+
key: "home_offline_card",
|
|
943
|
+
defaultValue: () => true,
|
|
944
|
+
writable: true,
|
|
945
|
+
saveOnServer: false,
|
|
946
|
+
persist: true,
|
|
947
|
+
},
|
|
907
948
|
// Gesture-dots debug indicator: flash a glyph on the glasses when a touch
|
|
908
949
|
// gesture is received while the display is empty. Works on any
|
|
909
950
|
// scene-capable device, not only CFW. Device-local debug knob, default off.
|
|
@@ -1128,6 +1169,13 @@ export const SETTINGS: Record<string, Setting> = {
|
|
|
1128
1169
|
saveOnServer: true,
|
|
1129
1170
|
persist: true,
|
|
1130
1171
|
},
|
|
1172
|
+
notifications_batch_seconds: {
|
|
1173
|
+
key: "notifications_batch_seconds",
|
|
1174
|
+
defaultValue: () => 0,
|
|
1175
|
+
writable: true,
|
|
1176
|
+
saveOnServer: true,
|
|
1177
|
+
persist: true,
|
|
1178
|
+
},
|
|
1131
1179
|
// Hold popups while another glasses feature (navigation, a miniapp) is on screen.
|
|
1132
1180
|
notifications_hold_during_apps: {
|
|
1133
1181
|
key: "notifications_hold_during_apps",
|
|
@@ -1205,7 +1253,8 @@ export const BLUETOOTH_SETTING_KEYS: string[] = [
|
|
|
1205
1253
|
SETTINGS.calendar_events.key,
|
|
1206
1254
|
SETTINGS.cfw_scene_renderer.key,
|
|
1207
1255
|
SETTINGS.cfw_fast_ble.key,
|
|
1208
|
-
SETTINGS.
|
|
1256
|
+
SETTINGS.perf_logs.key,
|
|
1257
|
+
SETTINGS.memomind_accessory_picker.key,
|
|
1209
1258
|
SETTINGS.ring_via_glasses.key,
|
|
1210
1259
|
SETTINGS.display_max_fps.key,
|
|
1211
1260
|
SETTINGS.twelve_hour_time.key,
|
|
@@ -1276,10 +1325,13 @@ export interface SettingsState {
|
|
|
1276
1325
|
}
|
|
1277
1326
|
|
|
1278
1327
|
const getDefaultSettings = () =>
|
|
1279
|
-
Object.keys(SETTINGS).reduce(
|
|
1280
|
-
acc
|
|
1281
|
-
|
|
1282
|
-
|
|
1328
|
+
Object.keys(SETTINGS).reduce(
|
|
1329
|
+
(acc, key) => {
|
|
1330
|
+
acc[key] = SETTINGS[key].defaultValue()
|
|
1331
|
+
return acc
|
|
1332
|
+
},
|
|
1333
|
+
{} as Record<string, any>,
|
|
1334
|
+
)
|
|
1283
1335
|
|
|
1284
1336
|
// Single-flight for loadAllSettings: the host fires it at module load and
|
|
1285
1337
|
// engine.start()'s device-store hydration awaits it — without the memo the
|
|
@@ -1619,6 +1671,22 @@ export const useSettingsStore = create<SettingsState>()(
|
|
|
1619
1671
|
}
|
|
1620
1672
|
if (menuMigrated) storage.save(MENU_SETTINGS_MIGRATION_KEY, true)
|
|
1621
1673
|
}
|
|
1674
|
+
|
|
1675
|
+
// The watch mirror switch lost its Apple-only name when Wear OS joined: carry a
|
|
1676
|
+
// turned-on `apple_watch_mirror` over to `watch_mirror` once.
|
|
1677
|
+
const WATCH_MIRROR_MIGRATION_KEY = "migration:watch_mirror_v1"
|
|
1678
|
+
const watchMirrorMigrationDone = storage.load<boolean>(WATCH_MIRROR_MIGRATION_KEY)
|
|
1679
|
+
if (watchMirrorMigrationDone.is_error() || !watchMirrorMigrationDone.value) {
|
|
1680
|
+
let watchMirrorMigrated = true
|
|
1681
|
+
if (get().getSetting(SETTINGS.apple_watch_mirror.key) === true) {
|
|
1682
|
+
const result = await get().setSetting(SETTINGS.watch_mirror.key, true, false)
|
|
1683
|
+
if (result.is_error()) {
|
|
1684
|
+
console.log("SETTINGS: watch_mirror migration failed:", result.error)
|
|
1685
|
+
watchMirrorMigrated = false
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
if (watchMirrorMigrated) storage.save(WATCH_MIRROR_MIGRATION_KEY, true)
|
|
1689
|
+
}
|
|
1622
1690
|
})
|
|
1623
1691
|
loadAllSettingsInFlight = inFlight
|
|
1624
1692
|
void Promise.resolve(inFlight).then((result) => {
|
|
@@ -68,4 +68,8 @@ export const evenRealitiesG1: Capabilities = {
|
|
|
68
68
|
|
|
69
69
|
// OTA capabilities - G1 firmware updates are not driven by the ASG OTA flow
|
|
70
70
|
hasOta: false,
|
|
71
|
+
|
|
72
|
+
// The app flashes the bundled Glassly CFW over MCUmgr SMP (see
|
|
73
|
+
// bluetooth-sdk/ios/Source/Resources/G1Firmware/README.md).
|
|
74
|
+
hasCustomFirmware: true,
|
|
71
75
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {Capabilities} from "../hardware"
|
|
2
|
+
|
|
3
|
+
export const memoMindOne: Capabilities = {
|
|
4
|
+
modelName: "MemoMind One",
|
|
5
|
+
hasCamera: false,
|
|
6
|
+
camera: null,
|
|
7
|
+
hasDisplay: true,
|
|
8
|
+
display: {
|
|
9
|
+
// One panel: the vector canvas offers no depth on it.
|
|
10
|
+
count: 1,
|
|
11
|
+
isColor: false,
|
|
12
|
+
canDisplayBitmap: true,
|
|
13
|
+
resolution: {width: 600, height: 350},
|
|
14
|
+
width: 600,
|
|
15
|
+
height: 350,
|
|
16
|
+
canPosition: true,
|
|
17
|
+
intensityLevels: 16,
|
|
18
|
+
partialUpdate: true,
|
|
19
|
+
adjustBrightness: true,
|
|
20
|
+
positionRange: {height: {min: 0, max: 8}, depth: {min: 0, max: 8}},
|
|
21
|
+
},
|
|
22
|
+
hasMicrophone: true,
|
|
23
|
+
microphone: {count: 1, hasVAD: false},
|
|
24
|
+
hasSpeaker: false,
|
|
25
|
+
speaker: null,
|
|
26
|
+
hasIMU: false,
|
|
27
|
+
imu: null,
|
|
28
|
+
hasButton: true,
|
|
29
|
+
button: {
|
|
30
|
+
count: 1,
|
|
31
|
+
buttons: [{type: "press", events: ["TAP", "DOUBLE_TAP", "PRESS_HOLD", "RELEASE"], isCapacitive: false}],
|
|
32
|
+
},
|
|
33
|
+
hasLight: false,
|
|
34
|
+
light: null,
|
|
35
|
+
power: {hasExternalBattery: false},
|
|
36
|
+
hasWifi: false,
|
|
37
|
+
hasOta: false,
|
|
38
|
+
hasCustomFirmware: true,
|
|
39
|
+
hasNativeDashboard: false,
|
|
40
|
+
hasNativeNavigation: false,
|
|
41
|
+
hasNativeNotifications: false,
|
|
42
|
+
hasNotificationRelay: true,
|
|
43
|
+
}
|
|
@@ -19,10 +19,11 @@ export const rayneoIO: Capabilities = {
|
|
|
19
19
|
hasCamera: false,
|
|
20
20
|
camera: null,
|
|
21
21
|
|
|
22
|
-
// Display capabilities -
|
|
22
|
+
// Display capabilities - a monochrome panel per eye; the Glassly CFW keeps
|
|
23
|
+
// a scene per lens, so element depth is a real disparity here
|
|
23
24
|
hasDisplay: true,
|
|
24
25
|
display: {
|
|
25
|
-
count:
|
|
26
|
+
count: 2,
|
|
26
27
|
isColor: false,
|
|
27
28
|
color: "green",
|
|
28
29
|
// Bitmaps and positioned elements need the Glassly CFW; stock firmware
|
package/src/types/enums.ts
CHANGED
|
@@ -13,10 +13,16 @@ export enum DeviceTypes {
|
|
|
13
13
|
NIMO = "NIMO",
|
|
14
14
|
FRAME = "Brilliant Frame",
|
|
15
15
|
RAYNEO_IO = "RayNeo iO",
|
|
16
|
+
MEMOMIND_ONE = "MemoMind One",
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export enum ControllerTypes {
|
|
19
20
|
R1 = "Even Realities R1",
|
|
20
21
|
NEURAL_BAND = "Meta Neural Band",
|
|
21
22
|
APPLE_WATCH = "Apple Watch",
|
|
23
|
+
WEAR_OS = "Wear OS Watch",
|
|
24
|
+
GAMEBRICK = "IINE Gamebrick",
|
|
25
|
+
IINE_ANANKE = "IINE Ananke Gen 2",
|
|
26
|
+
ZERO2 = "8BitDo Zero 2",
|
|
27
|
+
MICRO = "8BitDo Micro",
|
|
22
28
|
}
|
package/src/types/hardware.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {mentraLive} from "./capabilities/mentra-live"
|
|
|
9
9
|
import {simulatedGlasses} from "./capabilities/simulated-glasses"
|
|
10
10
|
import {nimo} from "./capabilities/nimo"
|
|
11
11
|
import {rayneoIO} from "./capabilities/rayneo-io"
|
|
12
|
+
import {memoMindOne} from "./capabilities/memomind-one"
|
|
12
13
|
import {vuzixZ100} from "./capabilities/vuzix-z100"
|
|
13
14
|
import {none} from "./capabilities/none"
|
|
14
15
|
import {DeviceTypes} from "./enums"
|
|
@@ -217,6 +218,7 @@ export interface Capabilities {
|
|
|
217
218
|
// (reading the phone's stream, or an upload from the phone) is the driver's
|
|
218
219
|
// business; the engine only decides who presents.
|
|
219
220
|
hasNativeNotifications?: boolean
|
|
221
|
+
hasNotificationRelay?: boolean
|
|
220
222
|
// Popup durations the firmware accepts, in seconds (0 = firmware default).
|
|
221
223
|
// Drivers snap other values; the settings UI offers exactly these.
|
|
222
224
|
nativeNotificationPopupSeconds?: number[]
|
|
@@ -236,6 +238,7 @@ export const HARDWARE_CAPABILITIES: Record<string, Capabilities> = {
|
|
|
236
238
|
[vuzixZ100.modelName]: vuzixZ100,
|
|
237
239
|
[nimo.modelName]: nimo,
|
|
238
240
|
[rayneoIO.modelName]: rayneoIO,
|
|
241
|
+
[memoMindOne.modelName]: memoMindOne,
|
|
239
242
|
[DeviceTypes.MACH1]: vuzixZ100, // Mach1 uses same Vuzix Ultralite hardware as Z100
|
|
240
243
|
[DeviceTypes.AR99]: {
|
|
241
244
|
...evenRealitiesG1,
|
|
@@ -265,4 +268,14 @@ export const getModelCapabilities = (deviceType: DeviceTypes): Capabilities => {
|
|
|
265
268
|
}
|
|
266
269
|
|
|
267
270
|
// export * from "./capabilities"
|
|
268
|
-
export {
|
|
271
|
+
export {
|
|
272
|
+
simulatedGlasses,
|
|
273
|
+
evenRealitiesG1,
|
|
274
|
+
evenRealitiesG2,
|
|
275
|
+
mentraLive,
|
|
276
|
+
vuzixZ100,
|
|
277
|
+
mentraDisplay,
|
|
278
|
+
nimo,
|
|
279
|
+
rayneoIO,
|
|
280
|
+
memoMindOne,
|
|
281
|
+
}
|
|
@@ -70,8 +70,8 @@ export {Z100_PROFILE, Z100_HYPHEN_WIDTH_PX, Z100_SPACE_WIDTH_PX} from "./profile
|
|
|
70
70
|
// Mentra Nex Profile (a.k.a. Mentra Display)
|
|
71
71
|
export {NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX} from "./profiles"
|
|
72
72
|
|
|
73
|
-
// RayNeo iO
|
|
74
|
-
export {RAYNEO_IO_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from "./profiles"
|
|
73
|
+
// RayNeo iO Profiles (stock subtitle page, Glassly CFW scene page)
|
|
74
|
+
export {RAYNEO_IO_PROFILE, RAYNEO_IO_CFW_PROFILE, RAYNEO_IO_HYPHEN_WIDTH_PX, RAYNEO_IO_SPACE_WIDTH_PX} from "./profiles"
|
|
75
75
|
|
|
76
76
|
// =============================================================================
|
|
77
77
|
// Measurer - Pixel-accurate text measurement
|
|
@@ -274,3 +274,5 @@ export function createNexToolkit(): {
|
|
|
274
274
|
breakMode: "character-no-hyphen",
|
|
275
275
|
})
|
|
276
276
|
}
|
|
277
|
+
|
|
278
|
+
export {MEMOMIND_ONE_PROFILE} from "./profiles"
|
|
@@ -27,5 +27,12 @@ export {G2_PROFILE, G2_HYPHEN_WIDTH_PX, G2_SPACE_WIDTH_PX} from "./g2"
|
|
|
27
27
|
// Mentra Nex Profile (a.k.a. Mentra Display)
|
|
28
28
|
export {NEX_PROFILE, NEX_HYPHEN_WIDTH_PX, NEX_SPACE_WIDTH_PX} from "./nex"
|
|
29
29
|
|
|
30
|
-
// RayNeo iO
|
|
31
|
-
export {
|
|
30
|
+
// RayNeo iO Profiles (stock subtitle page, Glassly CFW scene page)
|
|
31
|
+
export {
|
|
32
|
+
RAYNEO_IO_PROFILE,
|
|
33
|
+
RAYNEO_IO_CFW_PROFILE,
|
|
34
|
+
RAYNEO_IO_HYPHEN_WIDTH_PX,
|
|
35
|
+
RAYNEO_IO_SPACE_WIDTH_PX,
|
|
36
|
+
} from "./rayneo-io"
|
|
37
|
+
|
|
38
|
+
export {MEMOMIND_ONE_PROFILE} from "@glassly/miniapp/display"
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
// 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.
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
RAYNEO_IO_PROFILE,
|
|
4
|
+
RAYNEO_IO_CFW_PROFILE,
|
|
5
|
+
RAYNEO_IO_HYPHEN_WIDTH_PX,
|
|
6
|
+
RAYNEO_IO_SPACE_WIDTH_PX,
|
|
7
|
+
} from "@glassly/miniapp/display"
|
|
@@ -24,7 +24,7 @@ export {resolveAnchors} from "./anchor"
|
|
|
24
24
|
export type {DiffResult, DiffableElement} from "./differ"
|
|
25
25
|
export {diffScene} from "./differ"
|
|
26
26
|
export type {ProcessedScene} from "./process"
|
|
27
|
-
export {processScene, profileLineHeightPx} from "./process"
|
|
27
|
+
export {processScene, profileLineHeightPx, wrapsOnDevice} from "./process"
|
|
28
28
|
export type {DegradedScene} from "./degrade"
|
|
29
29
|
export {degradeScene} from "./degrade"
|
|
30
30
|
export {SceneStore} from "./store"
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import {compileSvgPath} from "./svgPath"
|
|
12
|
+
import {isUnsupportedScript} from "../measurer/script-detection"
|
|
12
13
|
import {TextMeasurer} from "../measurer/TextMeasurer"
|
|
13
14
|
import {TextWrapper} from "../wrapper/TextWrapper"
|
|
14
15
|
import type {DisplayProfile} from "../profiles/types"
|
|
@@ -47,6 +48,18 @@ function clampBox(box: SceneBox, width: number, height: number): SceneBox | null
|
|
|
47
48
|
|
|
48
49
|
const isFinite = (n: unknown): n is number => typeof n === "number" && Number.isFinite(n)
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Text the host cannot wrap: a script the measurer has no widths or break
|
|
53
|
+
* rules for (Thai has no spaces between words, for one). It is passed through
|
|
54
|
+
* unwrapped and the device's text engine lays it out in the box.
|
|
55
|
+
*/
|
|
56
|
+
export function wrapsOnDevice(text: string): boolean {
|
|
57
|
+
for (const char of text) {
|
|
58
|
+
if (isUnsupportedScript(char)) return true
|
|
59
|
+
}
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
50
63
|
/** Integer points, or null when the count/values are invalid for the type. */
|
|
51
64
|
function validPoints(el: SceneElementInput): ScenePoint[] | null {
|
|
52
65
|
const raw = (el as {points?: unknown}).points
|
|
@@ -372,10 +385,11 @@ export function processScene(
|
|
|
372
385
|
// Height clipping only applies with a CALIBRATED line height — otherwise
|
|
373
386
|
// the firmware clips vertically in-box (legacy behavior).
|
|
374
387
|
const style: SceneTextStyle = el.style ?? {}
|
|
375
|
-
if (style.font === "mono" || style.font === "builtin") {
|
|
376
|
-
// Grid text is laid out by the app (cols × rows)
|
|
377
|
-
// glasses' own font engine
|
|
378
|
-
//
|
|
388
|
+
if (style.font === "mono" || style.font === "builtin" || wrapsOnDevice(el.text ?? "")) {
|
|
389
|
+
// Grid text is laid out by the app (cols × rows), builtin text by the
|
|
390
|
+
// glasses' own font engine and unsupported scripts by the device's text
|
|
391
|
+
// engine; the host's proportional wrap metrics don't apply to any of
|
|
392
|
+
// them. Pass lines through and let the device wrap and clip.
|
|
379
393
|
const monoText = el.text ?? ""
|
|
380
394
|
out.push({
|
|
381
395
|
id: el.id,
|
|
@@ -390,8 +404,9 @@ export function processScene(
|
|
|
390
404
|
continue
|
|
391
405
|
}
|
|
392
406
|
const maxLines = lineHeight ? Math.max(1, Math.floor(clamped.h / lineHeight)) : profile.maxLines
|
|
407
|
+
const maxWidthPx = Math.max(1, clamped.w - 2 * (profile.textInsetPx ?? 0))
|
|
393
408
|
const result = wrapper.wrap(el.text ?? "", {
|
|
394
|
-
maxWidthPx
|
|
409
|
+
maxWidthPx,
|
|
395
410
|
maxLines,
|
|
396
411
|
...(style.breakMode ? {breakMode: style.breakMode} : {}),
|
|
397
412
|
})
|
|
@@ -402,12 +417,12 @@ export function processScene(
|
|
|
402
417
|
// Trim the last line until it fits WITH the ellipsis appended —
|
|
403
418
|
// otherwise the extra glyph can overflow the clamped box width.
|
|
404
419
|
let last = lines[lines.length - 1]
|
|
405
|
-
while (last.length > 0 && !measurer.fitsInWidth(`${last}…`,
|
|
420
|
+
while (last.length > 0 && !measurer.fitsInWidth(`${last}…`, maxWidthPx)) {
|
|
406
421
|
last = last.slice(0, -1)
|
|
407
422
|
}
|
|
408
423
|
// A box narrower than the ellipsis glyph itself gets an empty line —
|
|
409
424
|
// never emit a line wider than the clamped box.
|
|
410
|
-
const ellipsized = last.length > 0 || measurer.fitsInWidth("…",
|
|
425
|
+
const ellipsized = last.length > 0 || measurer.fitsInWidth("…", maxWidthPx) ? `${last}…` : ""
|
|
411
426
|
lines = [...lines.slice(0, -1), ellipsized]
|
|
412
427
|
}
|
|
413
428
|
}
|