@glassly/miniapp 0.3.0-dev.2 → 0.3.0-dev.462
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 +219 -74
- package/dist/background/index.d.ts +14 -8
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js +4 -0
- package/dist/background/index.js.map +1 -1
- package/dist/background/register.d.ts +1 -1
- package/dist/background/register.js +1 -1
- package/dist/display/index.d.ts +23 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +17 -0
- package/dist/display/index.js.map +1 -0
- package/dist/display/measurer/TextMeasurer.d.ts +160 -0
- package/dist/display/measurer/TextMeasurer.d.ts.map +1 -0
- package/dist/display/measurer/TextMeasurer.js +272 -0
- package/dist/display/measurer/TextMeasurer.js.map +1 -0
- package/dist/display/measurer/index.d.ts +10 -0
- package/dist/display/measurer/index.d.ts.map +1 -0
- package/dist/display/measurer/index.js +11 -0
- package/dist/display/measurer/index.js.map +1 -0
- package/dist/display/measurer/script-detection.d.ts +53 -0
- package/dist/display/measurer/script-detection.d.ts.map +1 -0
- package/dist/display/measurer/script-detection.js +184 -0
- package/dist/display/measurer/script-detection.js.map +1 -0
- package/dist/display/pixelArt.d.ts +22 -0
- package/dist/display/pixelArt.d.ts.map +1 -0
- package/dist/display/pixelArt.js +126 -0
- package/dist/display/pixelArt.js.map +1 -0
- package/dist/display/profiles/g1.d.ts +33 -0
- package/dist/display/profiles/g1.d.ts.map +1 -0
- package/dist/display/profiles/g1.js +214 -0
- package/dist/display/profiles/g1.js.map +1 -0
- package/dist/display/profiles/g2.d.ts +13 -0
- package/dist/display/profiles/g2.d.ts.map +1 -0
- package/dist/display/profiles/g2.js +34 -0
- package/dist/display/profiles/g2.js.map +1 -0
- package/dist/display/profiles/index.d.ts +12 -0
- package/dist/display/profiles/index.d.ts.map +1 -0
- package/dist/display/profiles/index.js +15 -0
- package/dist/display/profiles/index.js.map +1 -0
- package/dist/display/profiles/nex.d.ts +10 -0
- package/dist/display/profiles/nex.d.ts.map +1 -0
- package/dist/display/profiles/nex.js +150 -0
- package/dist/display/profiles/nex.js.map +1 -0
- package/dist/display/profiles/types.d.ts +103 -0
- package/dist/display/profiles/types.d.ts.map +1 -0
- package/dist/display/profiles/types.js +2 -0
- package/dist/display/profiles/types.js.map +1 -0
- package/dist/display/profiles/z100.d.ts +33 -0
- package/dist/display/profiles/z100.d.ts.map +1 -0
- package/dist/display/profiles/z100.js +182 -0
- package/dist/display/profiles/z100.js.map +1 -0
- package/dist/display/scene/types.d.ts +292 -0
- package/dist/display/scene/types.d.ts.map +1 -0
- package/dist/display/scene/types.js +0 -0
- package/dist/display/scene/types.js.map +1 -0
- package/dist/display/wrapper/TextWrapper.d.ts +102 -0
- package/dist/display/wrapper/TextWrapper.d.ts.map +1 -0
- package/dist/display/wrapper/TextWrapper.js +469 -0
- package/dist/display/wrapper/TextWrapper.js.map +1 -0
- package/dist/display/wrapper/index.d.ts +12 -0
- package/dist/display/wrapper/index.d.ts.map +1 -0
- package/dist/display/wrapper/index.js +12 -0
- package/dist/display/wrapper/index.js.map +1 -0
- package/dist/display/wrapper/types.d.ts +75 -0
- package/dist/display/wrapper/types.d.ts.map +1 -0
- package/dist/display/wrapper/types.js +11 -0
- package/dist/display/wrapper/types.js.map +1 -0
- package/dist/globals.d.ts +5 -5
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +1 -1
- package/dist/globals.js.map +1 -1
- package/dist/hardware.d.ts +4 -18
- package/dist/hardware.d.ts.map +1 -1
- package/dist/hardware.js +2 -13
- package/dist/hardware.js.map +1 -1
- package/dist/index.d.ts +16 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/knownApps.d.ts +26 -0
- package/dist/knownApps.d.ts.map +1 -0
- package/dist/knownApps.js +140 -0
- package/dist/knownApps.js.map +1 -0
- package/dist/mirror/canvas.d.ts +22 -0
- package/dist/mirror/canvas.d.ts.map +1 -0
- package/dist/mirror/canvas.js +311 -0
- package/dist/mirror/canvas.js.map +1 -0
- package/dist/mirror/geometry.d.ts +69 -0
- package/dist/mirror/geometry.d.ts.map +1 -0
- package/dist/mirror/geometry.js +75 -0
- package/dist/mirror/geometry.js.map +1 -0
- package/dist/mirror/index.d.ts +3 -0
- package/dist/mirror/index.d.ts.map +1 -0
- package/dist/mirror/index.js +2 -0
- package/dist/mirror/index.js.map +1 -0
- package/dist/mirror/types.d.ts +40 -0
- package/dist/mirror/types.d.ts.map +1 -0
- package/dist/mirror/types.js +2 -0
- package/dist/mirror/types.js.map +1 -0
- package/dist/modules/auth.d.ts +1 -1
- package/dist/modules/auth.d.ts.map +1 -1
- package/dist/modules/auth.js +2 -2
- package/dist/modules/auth.js.map +1 -1
- package/dist/modules/compass.d.ts +47 -0
- package/dist/modules/compass.d.ts.map +1 -0
- package/dist/modules/compass.js +39 -0
- package/dist/modules/compass.js.map +1 -0
- package/dist/modules/display.d.ts +192 -8
- package/dist/modules/display.d.ts.map +1 -1
- package/dist/modules/display.js +40 -3
- package/dist/modules/display.js.map +1 -1
- package/dist/modules/events.d.ts +30 -2
- package/dist/modules/events.d.ts.map +1 -1
- package/dist/modules/events.js.map +1 -1
- package/dist/modules/input.d.ts +40 -1
- package/dist/modules/input.d.ts.map +1 -1
- package/dist/modules/input.js +45 -1
- package/dist/modules/input.js.map +1 -1
- package/dist/modules/latest-render-scheduler.d.ts +19 -0
- package/dist/modules/latest-render-scheduler.d.ts.map +1 -0
- package/dist/modules/latest-render-scheduler.js +54 -0
- package/dist/modules/latest-render-scheduler.js.map +1 -0
- package/dist/modules/lightSensor.d.ts +44 -0
- package/dist/modules/lightSensor.d.ts.map +1 -0
- package/dist/modules/lightSensor.js +33 -0
- package/dist/modules/lightSensor.js.map +1 -0
- package/dist/modules/mic.d.ts +3 -2
- package/dist/modules/mic.d.ts.map +1 -1
- package/dist/modules/mic.js +3 -2
- package/dist/modules/mic.js.map +1 -1
- package/dist/modules/miniapps.d.ts +2 -0
- package/dist/modules/miniapps.d.ts.map +1 -1
- package/dist/modules/miniapps.js.map +1 -1
- package/dist/modules/mirror.d.ts +16 -0
- package/dist/modules/mirror.d.ts.map +1 -0
- package/dist/modules/mirror.js +40 -0
- package/dist/modules/mirror.js.map +1 -0
- package/dist/modules/mono-text-rows.d.ts +20 -0
- package/dist/modules/mono-text-rows.d.ts.map +1 -0
- package/dist/modules/mono-text-rows.js +26 -0
- package/dist/modules/mono-text-rows.js.map +1 -0
- package/dist/modules/navigation.d.ts.map +1 -1
- package/dist/modules/navigation.js +21 -14
- package/dist/modules/navigation.js.map +1 -1
- package/dist/modules/phone.d.ts +26 -1
- package/dist/modules/phone.d.ts.map +1 -1
- package/dist/modules/phone.js +28 -0
- package/dist/modules/phone.js.map +1 -1
- package/dist/modules/settings.d.ts +45 -0
- package/dist/modules/settings.d.ts.map +1 -0
- package/dist/modules/settings.js +54 -0
- package/dist/modules/settings.js.map +1 -0
- package/dist/modules/system.d.ts +21 -0
- package/dist/modules/system.d.ts.map +1 -1
- package/dist/modules/system.js +12 -0
- package/dist/modules/system.js.map +1 -1
- package/dist/modules/transcription.d.ts +3 -2
- package/dist/modules/transcription.d.ts.map +1 -1
- package/dist/modules/transcription.js +3 -2
- package/dist/modules/transcription.js.map +1 -1
- package/dist/protocol.d.ts +65 -5
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +66 -5
- package/dist/protocol.js.map +1 -1
- package/dist/react/MirrorCanvas.d.ts +10 -0
- package/dist/react/MirrorCanvas.d.ts.map +1 -0
- package/dist/react/MirrorCanvas.js +23 -0
- package/dist/react/MirrorCanvas.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/useColorScheme.d.ts +2 -3
- package/dist/react/useColorScheme.d.ts.map +1 -1
- package/dist/react/useColorScheme.js +15 -8
- package/dist/react/useColorScheme.js.map +1 -1
- package/dist/react/useRpc.js +2 -2
- package/dist/react/useRpc.js.map +1 -1
- package/dist/react/useSafeArea.d.ts.map +1 -1
- package/dist/react/useSafeArea.js +2 -2
- package/dist/react/useSafeArea.js.map +1 -1
- package/dist/session.d.ts +50 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +35 -6
- package/dist/session.js.map +1 -1
- package/dist/transport/auto.d.ts +2 -2
- package/dist/transport/auto.js +2 -2
- package/dist/transport/dispatch.d.ts +1 -1
- package/dist/transport/dispatch.js +1 -1
- package/dist/transport/mock.d.ts +4 -4
- package/dist/transport/mock.d.ts.map +1 -1
- package/dist/transport/mock.js +31 -7
- package/dist/transport/mock.js.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/package.json +19 -3
- package/src/background/index.ts +26 -4
- package/src/background/register.ts +1 -1
- package/src/display/index.ts +83 -0
- package/src/display/measurer/TextMeasurer.ts +346 -0
- package/src/display/measurer/index.ts +21 -0
- package/src/display/measurer/script-detection.ts +218 -0
- package/src/display/pixelArt.ts +141 -0
- package/src/display/profiles/g1.ts +237 -0
- package/src/display/profiles/g2.ts +39 -0
- package/src/display/profiles/index.ts +28 -0
- package/src/display/profiles/nex.ts +166 -0
- package/src/display/profiles/types.ts +132 -0
- package/src/display/profiles/z100.ts +198 -0
- package/src/display/scene/types.ts +335 -0
- package/src/display/wrapper/TextWrapper.ts +546 -0
- package/src/display/wrapper/index.ts +15 -0
- package/src/display/wrapper/types.ts +102 -0
- package/src/globals.ts +5 -5
- package/src/hardware.ts +6 -21
- package/src/index.ts +34 -6
- package/src/knownApps.ts +170 -0
- package/src/mirror/canvas.ts +322 -0
- package/src/mirror/geometry.ts +128 -0
- package/src/mirror/index.ts +2 -0
- package/src/mirror/types.ts +38 -0
- package/src/modules/auth.ts +2 -2
- package/src/modules/compass.ts +69 -0
- package/src/modules/display.ts +222 -12
- package/src/modules/events.ts +27 -1
- package/src/modules/input.ts +55 -2
- package/src/modules/latest-render-scheduler.ts +55 -0
- package/src/modules/lightSensor.ts +61 -0
- package/src/modules/mic.ts +3 -2
- package/src/modules/miniapps.ts +2 -0
- package/src/modules/mirror.ts +42 -0
- package/src/modules/mono-text-rows.ts +30 -0
- package/src/modules/navigation.ts +20 -14
- package/src/modules/phone.ts +40 -1
- package/src/modules/settings.ts +64 -0
- package/src/modules/system.ts +30 -1
- package/src/modules/transcription.ts +3 -2
- package/src/protocol.ts +72 -5
- package/src/react/MirrorCanvas.tsx +35 -0
- package/src/react/index.ts +3 -0
- package/src/react/useColorScheme.ts +17 -8
- package/src/react/useRpc.ts +2 -2
- package/src/react/useSafeArea.ts +2 -2
- package/src/session.ts +78 -8
- package/src/transport/auto.ts +2 -2
- package/src/transport/dispatch.ts +1 -1
- package/src/transport/mock.ts +42 -11
- package/src/ui/index.ts +3 -0
- package/dist/modules/heading.d.ts +0 -16
- package/dist/modules/heading.d.ts.map +0 -1
- package/dist/modules/heading.js +0 -20
- package/dist/modules/heading.js.map +0 -1
- package/src/modules/heading.ts +0 -23
package/src/hardware.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hardware requirement types — canonical home.
|
|
3
|
-
*
|
|
4
|
-
* Moved verbatim from the legacy `@glassly/types` package so miniapp authors
|
|
5
|
-
* can type their miniapp.json manifest without any dependency on legacy cloud.
|
|
6
|
-
* `@glassly/engine` re-exports these; do not fork copies elsewhere.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Hardware component types that apps can require
|
|
11
|
-
*/
|
|
1
|
+
/** Hardware components that a miniapp can declare in its manifest. */
|
|
12
2
|
export enum HardwareType {
|
|
13
3
|
CAMERA = "CAMERA",
|
|
14
4
|
DISPLAY = "DISPLAY",
|
|
@@ -21,20 +11,15 @@ export enum HardwareType {
|
|
|
21
11
|
EXIST = "EXIST",
|
|
22
12
|
}
|
|
23
13
|
|
|
24
|
-
/**
|
|
25
|
-
* Levels of hardware requirements
|
|
26
|
-
*/
|
|
14
|
+
/** Whether a missing component prevents the miniapp from running. */
|
|
27
15
|
export enum HardwareRequirementLevel {
|
|
28
|
-
REQUIRED = "REQUIRED",
|
|
29
|
-
OPTIONAL = "OPTIONAL",
|
|
16
|
+
REQUIRED = "REQUIRED",
|
|
17
|
+
OPTIONAL = "OPTIONAL",
|
|
30
18
|
}
|
|
31
19
|
|
|
32
|
-
/**
|
|
33
|
-
* Hardware requirement for an app
|
|
34
|
-
* Specifies what hardware components an app needs
|
|
35
|
-
*/
|
|
20
|
+
/** One hardware requirement from a miniapp manifest. */
|
|
36
21
|
export interface HardwareRequirement {
|
|
37
22
|
type: HardwareType
|
|
38
23
|
level: HardwareRequirementLevel
|
|
39
|
-
description?: string
|
|
24
|
+
description?: string
|
|
40
25
|
}
|
package/src/index.ts
CHANGED
|
@@ -16,14 +16,19 @@ import {installDevReloadListenerIfDevMode} from "./dev-reload"
|
|
|
16
16
|
installDevReloadListenerIfDevMode()
|
|
17
17
|
|
|
18
18
|
export {MiniappSession, NotConnectedError} from "./session"
|
|
19
|
+
export {monoTextRows} from "./modules/mono-text-rows"
|
|
20
|
+
export {LatestRenderScheduler} from "./modules/latest-render-scheduler"
|
|
19
21
|
export type {
|
|
20
22
|
AuthUpdatePayload,
|
|
21
23
|
ConnectAckPayload,
|
|
22
24
|
DisplayCapabilities,
|
|
23
25
|
GlassesCapabilities,
|
|
24
26
|
MiniappAuthState,
|
|
27
|
+
MiniappLaunchSource,
|
|
28
|
+
MiniappPreferences,
|
|
25
29
|
MiniappRequestError,
|
|
26
30
|
MiniappSessionOptions,
|
|
31
|
+
MiniappUnitSystem,
|
|
27
32
|
MiniappVisibility,
|
|
28
33
|
} from "./session"
|
|
29
34
|
export type {AuthFetchOptions, AuthModule} from "./modules/auth"
|
|
@@ -31,8 +36,8 @@ export type {AuthFetchOptions, AuthModule} from "./modules/auth"
|
|
|
31
36
|
export {makeRequestId, parseEnvelope, serializeEnvelope} from "./envelope"
|
|
32
37
|
export type {MiniappEnvelope} from "./envelope"
|
|
33
38
|
|
|
34
|
-
export {
|
|
35
|
-
export type {
|
|
39
|
+
export {getHostGlobals} from "./globals"
|
|
40
|
+
export type {HostGlobals, MiniappCapsuleMenuRect, MiniappColorScheme, MiniappSafeAreaInsets} from "./globals"
|
|
36
41
|
|
|
37
42
|
export {MiniappErrorCode, MiniappRequestType, MiniappResponseType, MiniappStreamType} from "./protocol"
|
|
38
43
|
export {CLOUD_STATUS_STREAM} from "./modules/cloud"
|
|
@@ -73,10 +78,18 @@ export type {
|
|
|
73
78
|
RenderBox,
|
|
74
79
|
RenderElement,
|
|
75
80
|
RenderOptions,
|
|
81
|
+
DisplayViewChange,
|
|
82
|
+
DisplayViewChangeReason,
|
|
83
|
+
ShowViewResult,
|
|
76
84
|
RenderRectStyle,
|
|
77
85
|
RenderResult,
|
|
78
86
|
RenderTextStyle,
|
|
79
87
|
ViewType,
|
|
88
|
+
RenderPoint,
|
|
89
|
+
RenderShapeStyle,
|
|
90
|
+
RenderStrokeStyle,
|
|
91
|
+
RenderEasing,
|
|
92
|
+
RenderTransition,
|
|
80
93
|
} from "./modules/display"
|
|
81
94
|
export type {
|
|
82
95
|
AccelData,
|
|
@@ -86,6 +99,7 @@ export type {
|
|
|
86
99
|
ConnectionData,
|
|
87
100
|
HeadingData,
|
|
88
101
|
HeadPositionData,
|
|
102
|
+
KeyboardEventData,
|
|
89
103
|
LocationData,
|
|
90
104
|
NotificationDismissedData,
|
|
91
105
|
PhoneNotificationData,
|
|
@@ -96,7 +110,7 @@ export type {
|
|
|
96
110
|
VadData,
|
|
97
111
|
WifiData,
|
|
98
112
|
} from "./modules/events"
|
|
99
|
-
export type {CalendarEvent, CalendarListOptions, CalendarListResult} from "./modules/phone"
|
|
113
|
+
export type {CalendarEvent, CalendarListOptions, CalendarListResult, NotificationAppInfo} from "./modules/phone"
|
|
100
114
|
export type {
|
|
101
115
|
PlayAudioOptions,
|
|
102
116
|
SpeakOptions,
|
|
@@ -139,6 +153,8 @@ export type {
|
|
|
139
153
|
DownloadResult,
|
|
140
154
|
ScanQrOptions,
|
|
141
155
|
ScanQrResult,
|
|
156
|
+
DisplayDiagnostic,
|
|
157
|
+
DisplayDiagnosticsResult,
|
|
142
158
|
} from "./modules/system"
|
|
143
159
|
export type {MiniappInfo, MiniappActionInfo, MiniappCompatibility, ListMiniappsOptions} from "./modules/miniapps"
|
|
144
160
|
export type {ActionContext, ActionDescriptor, ActionHandler, InvokeOptions} from "./modules/actions"
|
|
@@ -149,7 +165,6 @@ export type {DisplayManager} from "./modules/display"
|
|
|
149
165
|
export type {MiniappsModule} from "./modules/miniapps"
|
|
150
166
|
export type {ActionsModule} from "./modules/actions"
|
|
151
167
|
export type {GlassesModule} from "./modules/glasses"
|
|
152
|
-
export type {HeadingModule} from "./modules/heading"
|
|
153
168
|
export type {
|
|
154
169
|
AiModule,
|
|
155
170
|
AiProvider,
|
|
@@ -164,7 +179,7 @@ export type {
|
|
|
164
179
|
AiUsage,
|
|
165
180
|
} from "./modules/ai"
|
|
166
181
|
export type {ImuModule} from "./modules/imu"
|
|
167
|
-
export type {InputModule} from "./modules/input"
|
|
182
|
+
export type {InputModule, GestureClaimChange} from "./modules/input"
|
|
168
183
|
export type {KeysModule, ApiKeyProvider} from "./modules/keys"
|
|
169
184
|
export type {LocationModule} from "./modules/location"
|
|
170
185
|
export type {
|
|
@@ -207,10 +222,23 @@ export type {
|
|
|
207
222
|
TravelMode,
|
|
208
223
|
} from "./modules/navigation"
|
|
209
224
|
export type {PermissionsModule, PermissionErrorEvent} from "./modules/permissions"
|
|
210
|
-
export type {PhoneModule, PhoneNotificationsModule, PhoneCalendarModule} from "./modules/phone"
|
|
225
|
+
export type {PhoneModule, PhoneNotificationsModule, PhoneCalendarModule, PhoneHeadingModule} from "./modules/phone"
|
|
226
|
+
export type {SettingsModule, SettingsChange} from "./modules/settings"
|
|
211
227
|
export type {TranscriptionModule, TranscriptionConfig, TranscriptionOptions} from "./modules/transcription"
|
|
212
228
|
export type {TranslationModule} from "./modules/translation"
|
|
213
229
|
export type {SpeakerModule} from "./modules/speaker"
|
|
214
230
|
|
|
215
231
|
// Permission types
|
|
216
232
|
export type {PermissionType, PermissionRecord} from "./session"
|
|
233
|
+
|
|
234
|
+
export type {LightSensorModule, LightSensorOptions, LightSensorData} from "./modules/lightSensor"
|
|
235
|
+
export type {
|
|
236
|
+
CompassModule,
|
|
237
|
+
CompassOptions,
|
|
238
|
+
CompassData,
|
|
239
|
+
CompassDiagnostics,
|
|
240
|
+
CompassCalibrationData,
|
|
241
|
+
} from "./modules/compass"
|
|
242
|
+
|
|
243
|
+
export type {MirrorModule} from "./modules/mirror"
|
|
244
|
+
export type {MirrorSnapshot, MirrorContent, MirrorElement} from "./mirror/types"
|
package/src/knownApps.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The apps the notification surfaces know by id: a display name for a bare
|
|
3
|
+
* bundle id or package name and, for the glasses card, which glyph stands in
|
|
4
|
+
* for the app's icon (iOS never exposes another app's icon, and one would not
|
|
5
|
+
* survive 24px of 4-bit gray anyway). The host seeds its notification app
|
|
6
|
+
* catalog from the iOS entries and fills in names on bare ids; miniapps map
|
|
7
|
+
* the glyph names onto their own pixel art.
|
|
8
|
+
*
|
|
9
|
+
* Ids keep their real case so the catalog can hand them back; lookups are
|
|
10
|
+
* case-insensitive.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type AppGlyph =
|
|
14
|
+
| "bubble"
|
|
15
|
+
| "envelope"
|
|
16
|
+
| "calendar"
|
|
17
|
+
| "phone"
|
|
18
|
+
| "camera"
|
|
19
|
+
| "note"
|
|
20
|
+
| "pin"
|
|
21
|
+
| "clock"
|
|
22
|
+
| "play"
|
|
23
|
+
| "cart"
|
|
24
|
+
| "car"
|
|
25
|
+
| "check"
|
|
26
|
+
| "photo"
|
|
27
|
+
| "heart"
|
|
28
|
+
| "dollar"
|
|
29
|
+
|
|
30
|
+
export interface KnownApp {
|
|
31
|
+
/** iOS bundle id or Android package name, real case. */
|
|
32
|
+
id: string
|
|
33
|
+
name: string
|
|
34
|
+
/** Absent: the generic message bubble. */
|
|
35
|
+
glyph?: AppGlyph
|
|
36
|
+
platform: "ios" | "android" | "both"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ios = (id: string, name: string, glyph?: AppGlyph): KnownApp => ({id, name, glyph, platform: "ios"})
|
|
40
|
+
const android = (id: string, name: string, glyph?: AppGlyph): KnownApp => ({id, name, glyph, platform: "android"})
|
|
41
|
+
const both = (id: string, name: string, glyph?: AppGlyph): KnownApp => ({id, name, glyph, platform: "both"})
|
|
42
|
+
|
|
43
|
+
export const KNOWN_APPS: KnownApp[] = [
|
|
44
|
+
// Messaging
|
|
45
|
+
ios("com.apple.MobileSMS", "Messages", "bubble"),
|
|
46
|
+
android("com.google.android.apps.messaging", "Messages", "bubble"),
|
|
47
|
+
android("com.samsung.android.messaging", "Messages", "bubble"),
|
|
48
|
+
ios("net.whatsapp.WhatsApp", "WhatsApp", "bubble"),
|
|
49
|
+
android("com.whatsapp", "WhatsApp", "bubble"),
|
|
50
|
+
ios("ph.telegra.Telegraph", "Telegram", "bubble"),
|
|
51
|
+
android("org.telegram.messenger", "Telegram", "bubble"),
|
|
52
|
+
ios("org.whispersystems.signal", "Signal", "bubble"),
|
|
53
|
+
android("org.thoughtcrime.securesms", "Signal", "bubble"),
|
|
54
|
+
ios("com.facebook.Messenger", "Messenger", "bubble"),
|
|
55
|
+
android("com.facebook.orca", "Messenger", "bubble"),
|
|
56
|
+
ios("com.tinyspeck.chatlyio", "Slack", "bubble"),
|
|
57
|
+
android("com.Slack", "Slack", "bubble"),
|
|
58
|
+
ios("com.hammerandchisel.discord", "Discord", "bubble"),
|
|
59
|
+
android("com.discord", "Discord", "bubble"),
|
|
60
|
+
ios("com.microsoft.skype.teams", "Teams", "bubble"),
|
|
61
|
+
android("com.microsoft.teams", "Teams", "bubble"),
|
|
62
|
+
ios("com.tencent.xin", "WeChat", "bubble"),
|
|
63
|
+
android("com.tencent.mm", "WeChat", "bubble"),
|
|
64
|
+
ios("jp.naver.line", "LINE", "bubble"),
|
|
65
|
+
android("jp.naver.line.android", "LINE", "bubble"),
|
|
66
|
+
ios("com.toyopagroup.picaboo", "Snapchat", "bubble"),
|
|
67
|
+
android("com.snapchat.android", "Snapchat", "bubble"),
|
|
68
|
+
// Social
|
|
69
|
+
ios("com.facebook.Facebook", "Facebook"),
|
|
70
|
+
android("com.facebook.katana", "Facebook"),
|
|
71
|
+
ios("com.atebits.Tweetie2", "X"),
|
|
72
|
+
android("com.twitter.android", "X"),
|
|
73
|
+
ios("com.linkedin.LinkedIn", "LinkedIn"),
|
|
74
|
+
android("com.linkedin.android", "LinkedIn"),
|
|
75
|
+
// Mail
|
|
76
|
+
ios("com.apple.mobilemail", "Mail", "envelope"),
|
|
77
|
+
ios("com.google.Gmail", "Gmail", "envelope"),
|
|
78
|
+
android("com.google.android.gm", "Gmail", "envelope"),
|
|
79
|
+
both("com.microsoft.Office.Outlook", "Outlook", "envelope"),
|
|
80
|
+
ios("com.readdle.smartemail", "Spark", "envelope"),
|
|
81
|
+
android("com.readdle.spark", "Spark", "envelope"),
|
|
82
|
+
ios("ch.protonmail.protonmail", "Proton Mail", "envelope"),
|
|
83
|
+
android("ch.protonmail.android", "Proton Mail", "envelope"),
|
|
84
|
+
// Calendar
|
|
85
|
+
ios("com.apple.mobilecal", "Calendar", "calendar"),
|
|
86
|
+
ios("com.google.calendar", "Google Calendar", "calendar"),
|
|
87
|
+
android("com.google.android.calendar", "Google Calendar", "calendar"),
|
|
88
|
+
android("com.samsung.android.calendar", "Calendar", "calendar"),
|
|
89
|
+
// Phone
|
|
90
|
+
ios("com.apple.mobilephone", "Phone", "phone"),
|
|
91
|
+
ios("com.apple.facetime", "FaceTime", "phone"),
|
|
92
|
+
android("com.google.android.dialer", "Phone", "phone"),
|
|
93
|
+
android("com.samsung.android.dialer", "Phone", "phone"),
|
|
94
|
+
android("com.samsung.android.incallui", "Phone", "phone"),
|
|
95
|
+
// Tasks and reminders
|
|
96
|
+
ios("com.apple.reminders", "Reminders", "check"),
|
|
97
|
+
android("com.google.android.apps.tasks", "Google Tasks", "check"),
|
|
98
|
+
android("com.todoist", "Todoist", "check"),
|
|
99
|
+
ios("com.culturedcode.ThingsiPhone", "Things", "check"),
|
|
100
|
+
// Maps and rides
|
|
101
|
+
ios("com.apple.Maps", "Maps", "pin"),
|
|
102
|
+
ios("com.google.Maps", "Google Maps", "pin"),
|
|
103
|
+
android("com.google.android.apps.maps", "Google Maps", "pin"),
|
|
104
|
+
ios("com.waze.iphone", "Waze", "pin"),
|
|
105
|
+
android("com.waze", "Waze", "pin"),
|
|
106
|
+
ios("com.ubercab.UberClient", "Uber", "car"),
|
|
107
|
+
android("com.ubercab", "Uber", "car"),
|
|
108
|
+
ios("com.zimride.instant", "Lyft", "car"),
|
|
109
|
+
android("me.lyft.android", "Lyft", "car"),
|
|
110
|
+
// Shopping and delivery
|
|
111
|
+
ios("com.amazon.Amazon", "Amazon", "cart"),
|
|
112
|
+
android("com.amazon.mShop.android.shopping", "Amazon", "cart"),
|
|
113
|
+
ios("doordash.DoorDashConsumer", "DoorDash", "cart"),
|
|
114
|
+
android("com.dd.doordash", "DoorDash", "cart"),
|
|
115
|
+
ios("com.ubercab.UberEats", "Uber Eats", "cart"),
|
|
116
|
+
android("com.ubercab.eats", "Uber Eats", "cart"),
|
|
117
|
+
// Music and video
|
|
118
|
+
ios("com.apple.Music", "Music", "note"),
|
|
119
|
+
ios("com.spotify.client", "Spotify", "note"),
|
|
120
|
+
android("com.spotify.music", "Spotify", "note"),
|
|
121
|
+
ios("com.google.ios.youtubemusic", "YouTube Music", "note"),
|
|
122
|
+
android("com.google.android.apps.youtube.music", "YouTube Music", "note"),
|
|
123
|
+
ios("com.google.ios.youtube", "YouTube", "play"),
|
|
124
|
+
android("com.google.android.youtube", "YouTube", "play"),
|
|
125
|
+
ios("com.netflix.Netflix", "Netflix", "play"),
|
|
126
|
+
android("com.netflix.mediaclient", "Netflix", "play"),
|
|
127
|
+
ios("com.zhiliaoapp.musically", "TikTok", "play"),
|
|
128
|
+
android("com.ss.android.ugc.trill", "TikTok", "play"),
|
|
129
|
+
// Photos and camera
|
|
130
|
+
ios("com.apple.mobileslideshow", "Photos", "photo"),
|
|
131
|
+
android("com.google.android.apps.photos", "Google Photos", "photo"),
|
|
132
|
+
ios("com.apple.camera", "Camera", "camera"),
|
|
133
|
+
ios("com.burbn.instagram", "Instagram", "camera"),
|
|
134
|
+
android("com.instagram.android", "Instagram", "camera"),
|
|
135
|
+
// Clock and alarms
|
|
136
|
+
ios("com.apple.mobiletimer", "Clock", "clock"),
|
|
137
|
+
android("com.google.android.deskclock", "Clock", "clock"),
|
|
138
|
+
android("com.sec.android.app.clockpackage", "Clock", "clock"),
|
|
139
|
+
// Health and fitness
|
|
140
|
+
ios("com.apple.Health", "Health", "heart"),
|
|
141
|
+
ios("com.strava.stravaride", "Strava", "heart"),
|
|
142
|
+
android("com.strava", "Strava", "heart"),
|
|
143
|
+
android("com.google.android.apps.fitness", "Google Fit", "heart"),
|
|
144
|
+
ios("com.fitbit.FitbitMobile", "Fitbit", "heart"),
|
|
145
|
+
// Payments
|
|
146
|
+
ios("com.apple.Passbook", "Wallet", "dollar"),
|
|
147
|
+
ios("net.kortina.labs.Venmo", "Venmo", "dollar"),
|
|
148
|
+
ios("com.venmo.TouchFree", "Venmo", "dollar"),
|
|
149
|
+
android("com.venmo", "Venmo", "dollar"),
|
|
150
|
+
ios("com.yourcompany.PPClient", "PayPal", "dollar"),
|
|
151
|
+
android("com.paypal.android.p2pmobile", "PayPal", "dollar"),
|
|
152
|
+
both("com.squareup.cash", "Cash App", "dollar"),
|
|
153
|
+
both("io.heckel.ntfy", "Ntfy", "bubble"),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
const BY_ID: ReadonlyMap<string, KnownApp> = new Map(KNOWN_APPS.map((app) => [app.id.toLowerCase(), app]))
|
|
157
|
+
|
|
158
|
+
export function knownApp(packageName: string | undefined): KnownApp | null {
|
|
159
|
+
if (!packageName) return null
|
|
160
|
+
return BY_ID.get(packageName.trim().toLowerCase()) ?? null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function appGlyph(packageName: string | undefined): AppGlyph | null {
|
|
164
|
+
return knownApp(packageName)?.glyph ?? null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The curated display name for a bare id, when the phone did not report one. */
|
|
168
|
+
export function knownAppName(packageName: string | undefined): string | null {
|
|
169
|
+
return knownApp(packageName)?.name ?? null
|
|
170
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {sceneGeometry, geometryEqual, tweenAt, type SceneGeometry} from "./geometry"
|
|
2
|
+
import type {MirrorElement, MirrorSnapshot} from "./types"
|
|
3
|
+
|
|
4
|
+
interface Pose {
|
|
5
|
+
element: MirrorElement
|
|
6
|
+
from: SceneGeometry
|
|
7
|
+
to: SceneGeometry
|
|
8
|
+
start: number
|
|
9
|
+
duration: number
|
|
10
|
+
rotationFrom: number
|
|
11
|
+
rotationTo: number
|
|
12
|
+
rotationStart: number
|
|
13
|
+
rotationDuration: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** CSS cubic-bezier timing, matching the glasses mirror's easing curves. */
|
|
17
|
+
export function mirrorEase(t: number, name?: string): number {
|
|
18
|
+
if (name === "linear" || t <= 0 || t >= 1) return t
|
|
19
|
+
const [x1, y1, x2, y2] =
|
|
20
|
+
name === "ease-in"
|
|
21
|
+
? [0.42, 0, 1, 1]
|
|
22
|
+
: name === "ease-out"
|
|
23
|
+
? [0, 0, 0.58, 1]
|
|
24
|
+
: name === "ease-in-out"
|
|
25
|
+
? [0.42, 0, 0.58, 1]
|
|
26
|
+
: [0.25, 0.1, 0.25, 1]
|
|
27
|
+
const curve = (u: number, a: number, b: number) => 3 * (1 - u) ** 2 * u * a + 3 * (1 - u) * u ** 2 * b + u ** 3
|
|
28
|
+
let low = 0,
|
|
29
|
+
high = 1
|
|
30
|
+
for (let i = 0; i < 16; i++) {
|
|
31
|
+
const mid = (low + high) / 2
|
|
32
|
+
if (curve(mid, x1, x2) < t) low = mid
|
|
33
|
+
else high = mid
|
|
34
|
+
}
|
|
35
|
+
return curve((low + high) / 2, y1, y2)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function poseAt(pose: Pose, now: number): SceneGeometry {
|
|
39
|
+
const geometry = tweenAt(
|
|
40
|
+
pose.from,
|
|
41
|
+
pose.to,
|
|
42
|
+
mirrorEase(pose.duration ? Math.min(1, (now - pose.start) / pose.duration) : 1, pose.element.transition?.easing),
|
|
43
|
+
)
|
|
44
|
+
const rotationT = mirrorEase(
|
|
45
|
+
pose.rotationDuration ? Math.min(1, (now - pose.rotationStart) / pose.rotationDuration) : 1,
|
|
46
|
+
pose.element.transition?.easing,
|
|
47
|
+
)
|
|
48
|
+
return {...geometry, rotation: pose.rotationFrom + (pose.rotationTo - pose.rotationFrom) * rotationT}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function canTween(previous: MirrorElement, next: MirrorElement): boolean {
|
|
52
|
+
return (
|
|
53
|
+
next.change !== "created" &&
|
|
54
|
+
previous.type === next.type &&
|
|
55
|
+
previous.text === next.text &&
|
|
56
|
+
previous.data === next.data &&
|
|
57
|
+
previous.d === next.d &&
|
|
58
|
+
previous.fillRule === next.fillRule &&
|
|
59
|
+
previous.style?.fill === next.style?.fill &&
|
|
60
|
+
previous.points?.length === next.points?.length &&
|
|
61
|
+
!(next.type === "image" && (previous.box.w !== next.box.w || previous.box.h !== next.box.h))
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Browser-only renderer. Feed snapshots from session.mirror through your UI channel. */
|
|
66
|
+
export class MirrorCanvasRenderer {
|
|
67
|
+
private frame: MirrorSnapshot | null = null
|
|
68
|
+
private poses = new Map<string, Pose>()
|
|
69
|
+
private images = new Map<string, HTMLCanvasElement | null>()
|
|
70
|
+
private pending = new Set<string>()
|
|
71
|
+
private raf: number | null = null
|
|
72
|
+
private disposed = false
|
|
73
|
+
private readonly ctx: CanvasRenderingContext2D
|
|
74
|
+
|
|
75
|
+
constructor(private readonly canvas: HTMLCanvasElement) {
|
|
76
|
+
const ctx = canvas.getContext("2d")
|
|
77
|
+
if (!ctx) throw new Error("Canvas 2D is unavailable")
|
|
78
|
+
this.ctx = ctx
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
update(frame: MirrorSnapshot): void {
|
|
82
|
+
if (this.disposed || (this.frame && frame.revision < this.frame.revision)) return
|
|
83
|
+
const previous = this.frame
|
|
84
|
+
const scene = frame.content.kind === "scene" ? frame.content : null
|
|
85
|
+
const prior = previous?.content.kind === "scene" ? previous.content : null
|
|
86
|
+
const reset =
|
|
87
|
+
!prior ||
|
|
88
|
+
!scene ||
|
|
89
|
+
previous?.model !== frame.model ||
|
|
90
|
+
previous.appId !== frame.appId ||
|
|
91
|
+
previous.view !== frame.view ||
|
|
92
|
+
prior.epoch !== scene.epoch ||
|
|
93
|
+
frame.revision > previous.revision + 1
|
|
94
|
+
const now = Date.now()
|
|
95
|
+
const poses = new Map<string, Pose>()
|
|
96
|
+
for (const element of scene?.elements ?? []) {
|
|
97
|
+
const to = sceneGeometry(element)
|
|
98
|
+
const prev = reset ? undefined : this.poses.get(element.id)
|
|
99
|
+
const animate = prev && canTween(prev.element, element)
|
|
100
|
+
const duration = element.transition?.durationMs ?? 0
|
|
101
|
+
const current = prev ? poseAt(prev, now) : to
|
|
102
|
+
const sameGeometry = animate && geometryEqual(prev.to, to)
|
|
103
|
+
const sameRotation =
|
|
104
|
+
animate && prev.to.rotation === to.rotation && prev.to.pivotX === to.pivotX && prev.to.pivotY === to.pivotY
|
|
105
|
+
poses.set(element.id, {
|
|
106
|
+
element,
|
|
107
|
+
to,
|
|
108
|
+
from: sameGeometry ? prev.from : animate ? current : to,
|
|
109
|
+
start: sameGeometry ? prev.start : now,
|
|
110
|
+
duration: sameGeometry ? prev.duration : animate ? Math.min(duration, 255 * 33) : 0,
|
|
111
|
+
rotationFrom: sameRotation ? prev.rotationFrom : animate ? current.rotation : to.rotation,
|
|
112
|
+
rotationTo: to.rotation,
|
|
113
|
+
rotationStart: sameRotation ? prev.rotationStart : now,
|
|
114
|
+
rotationDuration: sameRotation ? prev.rotationDuration : animate ? Math.min(duration, 65535) : 0,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
this.poses = poses
|
|
118
|
+
this.frame = frame
|
|
119
|
+
const used = new Set(scene?.elements.flatMap((e) => (e.data ? [e.data] : [])) ?? [])
|
|
120
|
+
if (frame.content.kind === "bitmap") used.add(frame.content.data)
|
|
121
|
+
for (const key of this.images.keys()) if (!used.has(key)) this.images.delete(key)
|
|
122
|
+
this.draw()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
dispose(): void {
|
|
126
|
+
this.disposed = true
|
|
127
|
+
if (this.raf !== null) cancelAnimationFrame(this.raf)
|
|
128
|
+
this.raf = null
|
|
129
|
+
this.poses.clear()
|
|
130
|
+
this.images.clear()
|
|
131
|
+
this.pending.clear()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private image(data: string): HTMLCanvasElement | null {
|
|
135
|
+
if (this.images.has(data)) return this.images.get(data) ?? null
|
|
136
|
+
if (this.pending.has(data)) return null
|
|
137
|
+
this.pending.add(data)
|
|
138
|
+
const image = new Image()
|
|
139
|
+
image.onload = () => {
|
|
140
|
+
this.pending.delete(data)
|
|
141
|
+
if (this.disposed) return
|
|
142
|
+
const content = this.frame?.content
|
|
143
|
+
if (
|
|
144
|
+
!(content?.kind === "bitmap" && content.data === data) &&
|
|
145
|
+
!(content?.kind === "scene" && content.elements.some((e) => e.data === data))
|
|
146
|
+
)
|
|
147
|
+
return
|
|
148
|
+
const surface = document.createElement("canvas")
|
|
149
|
+
surface.width = image.naturalWidth
|
|
150
|
+
surface.height = image.naturalHeight
|
|
151
|
+
const ctx = surface.getContext("2d", {willReadFrequently: true})
|
|
152
|
+
if (!ctx) return
|
|
153
|
+
ctx.drawImage(image, 0, 0)
|
|
154
|
+
const pixels = ctx.getImageData(0, 0, surface.width, surface.height)
|
|
155
|
+
for (let i = 0; i < pixels.data.length; i += 4) {
|
|
156
|
+
const alpha =
|
|
157
|
+
((0.2126 * pixels.data[i] + 0.7152 * pixels.data[i + 1] + 0.0722 * pixels.data[i + 2]) * pixels.data[i + 3]) /
|
|
158
|
+
255
|
|
159
|
+
pixels.data[i] = 0
|
|
160
|
+
pixels.data[i + 1] = 255
|
|
161
|
+
pixels.data[i + 2] = 136
|
|
162
|
+
pixels.data[i + 3] = alpha
|
|
163
|
+
}
|
|
164
|
+
ctx.putImageData(pixels, 0, 0)
|
|
165
|
+
this.images.set(data, surface)
|
|
166
|
+
this.draw()
|
|
167
|
+
}
|
|
168
|
+
image.onerror = () => {
|
|
169
|
+
this.pending.delete(data)
|
|
170
|
+
if (!this.disposed) this.images.set(data, null)
|
|
171
|
+
}
|
|
172
|
+
// Encoded images only: never fetch arbitrary URLs from scene data.
|
|
173
|
+
image.src = data.startsWith("data:image/")
|
|
174
|
+
? data
|
|
175
|
+
: `data:image/${data.startsWith("Qk") ? "bmp" : "png"};base64,${data}`
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private draw(): void {
|
|
180
|
+
if (this.disposed || !this.frame) return
|
|
181
|
+
if (this.raf !== null) cancelAnimationFrame(this.raf)
|
|
182
|
+
this.raf = null
|
|
183
|
+
const {canvas, content} = this.frame
|
|
184
|
+
const w = Math.max(1, Math.round(canvas?.width ?? 1)),
|
|
185
|
+
h = Math.max(1, Math.round(canvas?.height ?? 1))
|
|
186
|
+
if (this.canvas.width !== w) this.canvas.width = w
|
|
187
|
+
if (this.canvas.height !== h) this.canvas.height = h
|
|
188
|
+
const ctx = this.ctx
|
|
189
|
+
ctx.clearRect(0, 0, w, h)
|
|
190
|
+
const now = Date.now()
|
|
191
|
+
let animating = false
|
|
192
|
+
if (content.kind === "bitmap") {
|
|
193
|
+
const image = this.image(content.data)
|
|
194
|
+
if (image) {
|
|
195
|
+
const g1 = image.width === 576 && image.height === 135
|
|
196
|
+
const x = g1 ? 29 : 0,
|
|
197
|
+
y = g1 ? 21 : 0
|
|
198
|
+
ctx.drawImage(image, x, y, image.width - x, image.height - y, 0, 0, w, h)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
for (const pose of this.poses.values()) {
|
|
202
|
+
animating ||= now < pose.start + pose.duration || now < pose.rotationStart + pose.rotationDuration
|
|
203
|
+
this.paint(pose.element, poseAt(pose, now))
|
|
204
|
+
}
|
|
205
|
+
if (animating) this.raf = requestAnimationFrame(() => this.draw())
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private paint(element: MirrorElement, g: SceneGeometry): void {
|
|
209
|
+
const ctx = this.ctx
|
|
210
|
+
ctx.save()
|
|
211
|
+
try {
|
|
212
|
+
if (g.rotation) {
|
|
213
|
+
ctx.translate(g.pivotX, g.pivotY)
|
|
214
|
+
ctx.rotate((g.rotation * Math.PI) / 180)
|
|
215
|
+
ctx.translate(-g.pivotX, -g.pivotY)
|
|
216
|
+
}
|
|
217
|
+
if (element.type === "image") {
|
|
218
|
+
const image = element.data ? this.image(element.data) : null
|
|
219
|
+
ctx.globalAlpha = g.level
|
|
220
|
+
if (image) ctx.drawImage(image, g.x, g.y, g.w, g.h)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
if (element.type === "text") {
|
|
224
|
+
if (g.stroke > 0) {
|
|
225
|
+
const border = new Path2D()
|
|
226
|
+
border.roundRect(g.x, g.y, g.w, g.h, Math.max(0, Math.min(g.radius, g.w / 2, g.h / 2)))
|
|
227
|
+
this.path(border, false, g)
|
|
228
|
+
}
|
|
229
|
+
ctx.beginPath()
|
|
230
|
+
ctx.rect(g.x, g.y, g.w, g.h)
|
|
231
|
+
ctx.clip()
|
|
232
|
+
const mono = element.style?.font === "mono",
|
|
233
|
+
size = mono ? element.style?.fontSize || 20 : 20
|
|
234
|
+
const pitch = mono ? Math.round(size * 1.2) : 27
|
|
235
|
+
ctx.font = `${size}px ${mono ? "monospace" : "Arial, sans-serif"}`
|
|
236
|
+
const metrics = ctx.measureText("0"),
|
|
237
|
+
cell = Math.max(1, Math.round(metrics.width))
|
|
238
|
+
const ascent = metrics.fontBoundingBoxAscent || size * 0.8,
|
|
239
|
+
descent = metrics.fontBoundingBoxDescent || size * 0.2
|
|
240
|
+
const baseline = mono ? Math.round((pitch - ascent - descent) / 2 + ascent) : 22
|
|
241
|
+
ctx.fillStyle = `rgba(0,255,136,${g.level})`
|
|
242
|
+
;(element.text ?? "").split("\n").forEach((line, row) => {
|
|
243
|
+
if (row > 0 && (mono ? 2 + row * pitch >= g.h - 2 : (row + 1) * pitch > g.h)) return
|
|
244
|
+
let x = g.x + 2
|
|
245
|
+
for (const char of line) {
|
|
246
|
+
ctx.fillText(char, x, g.y + 2 + row * pitch + baseline)
|
|
247
|
+
x += mono ? cell : this.frame?.glyphWidths[char] ?? ctx.measureText(char).width
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
let path = new Path2D()
|
|
253
|
+
const pts = g.points,
|
|
254
|
+
cx = g.x + g.w / 2,
|
|
255
|
+
cy = g.y + g.h / 2,
|
|
256
|
+
r = Math.min(g.w, g.h) / 2
|
|
257
|
+
switch (element.type) {
|
|
258
|
+
case "rect":
|
|
259
|
+
path.roundRect(g.x, g.y, g.w, g.h, Math.max(0, Math.min(g.radius, g.w / 2, g.h / 2)))
|
|
260
|
+
break
|
|
261
|
+
case "circle":
|
|
262
|
+
path.arc(cx, cy, r, 0, Math.PI * 2)
|
|
263
|
+
break
|
|
264
|
+
case "arc":
|
|
265
|
+
case "pie": {
|
|
266
|
+
const sweep = g.endDeg - g.startDeg
|
|
267
|
+
if (!sweep) return
|
|
268
|
+
if (Math.abs(sweep) >= 360) path.arc(cx, cy, r, 0, Math.PI * 2)
|
|
269
|
+
else {
|
|
270
|
+
if (element.type === "pie") path.moveTo(cx, cy)
|
|
271
|
+
path.arc(cx, cy, r, (g.startDeg * Math.PI) / 180, (g.endDeg * Math.PI) / 180, sweep < 0)
|
|
272
|
+
if (element.type === "pie") path.closePath()
|
|
273
|
+
}
|
|
274
|
+
break
|
|
275
|
+
}
|
|
276
|
+
case "line":
|
|
277
|
+
case "triangle":
|
|
278
|
+
case "quad":
|
|
279
|
+
if (pts.length < 4) return
|
|
280
|
+
path.moveTo(pts[0], pts[1])
|
|
281
|
+
for (let i = 2; i < pts.length; i += 2) path.lineTo(pts[i], pts[i + 1])
|
|
282
|
+
if (element.type !== "line") path.closePath()
|
|
283
|
+
break
|
|
284
|
+
case "bezier":
|
|
285
|
+
path.moveTo(pts[0], pts[1])
|
|
286
|
+
if (pts.length === 6) path.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5])
|
|
287
|
+
else if (pts.length === 8) path.bezierCurveTo(pts[2], pts[3], pts[4], pts[5], pts[6], pts[7])
|
|
288
|
+
else return
|
|
289
|
+
break
|
|
290
|
+
case "svg":
|
|
291
|
+
if (!element.d) return
|
|
292
|
+
path = new Path2D(element.d)
|
|
293
|
+
ctx.translate(g.x, g.y)
|
|
294
|
+
ctx.scale(g.pathScale, g.pathScale)
|
|
295
|
+
break
|
|
296
|
+
default:
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
const filled =
|
|
300
|
+
element.type === "svg" ||
|
|
301
|
+
element.type === "pie" ||
|
|
302
|
+
(["rect", "circle", "triangle", "quad"].includes(element.type) && element.style?.fill === true)
|
|
303
|
+
this.path(path, filled, g, element.fillRule === "evenodd" ? "evenodd" : "nonzero")
|
|
304
|
+
} finally {
|
|
305
|
+
ctx.restore()
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private path(path: Path2D, fill: boolean, g: SceneGeometry, rule: CanvasFillRule = "nonzero"): void {
|
|
310
|
+
const ctx = this.ctx
|
|
311
|
+
ctx.lineWidth = Math.max(1, g.stroke)
|
|
312
|
+
// Replace the shape's covered pixels, including level-zero erasure.
|
|
313
|
+
ctx.globalCompositeOperation = "destination-out"
|
|
314
|
+
ctx.fillStyle = ctx.strokeStyle = "black"
|
|
315
|
+
if (fill) ctx.fill(path, rule)
|
|
316
|
+
else ctx.stroke(path)
|
|
317
|
+
ctx.globalCompositeOperation = "source-over"
|
|
318
|
+
ctx.fillStyle = ctx.strokeStyle = `rgba(0,255,136,${g.level})`
|
|
319
|
+
if (fill) ctx.fill(path, rule)
|
|
320
|
+
else ctx.stroke(path)
|
|
321
|
+
}
|
|
322
|
+
}
|