brilliantsole 0.0.28 → 0.0.30

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 (136) hide show
  1. package/build/brilliantsole.cjs +6078 -444
  2. package/build/brilliantsole.cjs.map +1 -1
  3. package/build/brilliantsole.js +21734 -3031
  4. package/build/brilliantsole.js.map +1 -1
  5. package/build/brilliantsole.ls.js +23228 -5817
  6. package/build/brilliantsole.ls.js.map +1 -1
  7. package/build/brilliantsole.min.js +1 -1
  8. package/build/brilliantsole.min.js.map +1 -1
  9. package/build/brilliantsole.module.d.ts +1216 -72
  10. package/build/brilliantsole.module.js +21697 -3032
  11. package/build/brilliantsole.module.js.map +1 -1
  12. package/build/brilliantsole.module.min.d.ts +1216 -72
  13. package/build/brilliantsole.module.min.js +1 -1
  14. package/build/brilliantsole.module.min.js.map +1 -1
  15. package/build/brilliantsole.node.module.d.ts +927 -68
  16. package/build/brilliantsole.node.module.js +6053 -445
  17. package/build/brilliantsole.node.module.js.map +1 -1
  18. package/build/dts/BS.d.ts +21 -1
  19. package/build/dts/Device.d.ts +152 -12
  20. package/build/dts/DeviceManager.d.ts +3 -3
  21. package/build/dts/DisplayManager.d.ts +320 -0
  22. package/build/dts/FileTransferManager.d.ts +10 -4
  23. package/build/dts/MicrophoneManager.d.ts +88 -0
  24. package/build/dts/connection/BaseConnectionManager.d.ts +2 -2
  25. package/build/dts/connection/bluetooth/BluetoothUUID.d.ts +12 -0
  26. package/build/dts/devicePair/DevicePair.d.ts +5 -5
  27. package/build/dts/sensor/SensorConfigurationManager.d.ts +2 -1
  28. package/build/dts/sensor/SensorDataManager.d.ts +3 -3
  29. package/build/dts/server/BaseClient.d.ts +4 -4
  30. package/build/dts/server/udp/UDPUtils.d.ts +1 -1
  31. package/build/dts/utils/ArrayBufferUtils.d.ts +1 -0
  32. package/build/dts/utils/AudioUtils.d.ts +2 -0
  33. package/build/dts/utils/BitmapUtils.d.ts +17 -0
  34. package/build/dts/utils/ColorUtils.d.ts +5 -0
  35. package/build/dts/utils/DisplayBitmapUtils.d.ts +47 -0
  36. package/build/dts/utils/DisplayCanvasHelper.d.ts +270 -0
  37. package/build/dts/utils/DisplayContextCommand.d.ts +300 -0
  38. package/build/dts/utils/DisplayContextState.d.ts +51 -0
  39. package/build/dts/utils/DisplayContextStateHelper.d.ts +9 -0
  40. package/build/dts/utils/DisplayManagerInterface.d.ts +173 -0
  41. package/build/dts/utils/DisplaySpriteSheetUtils.d.ts +72 -0
  42. package/build/dts/utils/DisplayUtils.d.ts +70 -0
  43. package/build/dts/utils/MathUtils.d.ts +16 -0
  44. package/build/dts/utils/PathUtils.d.ts +4 -0
  45. package/build/dts/utils/RangeHelper.d.ts +7 -0
  46. package/build/dts/utils/SpriteSheetUtils.d.ts +20 -0
  47. package/build/index.d.ts +1216 -72
  48. package/build/index.node.d.ts +927 -68
  49. package/examples/3d-generic/index.html +5 -0
  50. package/examples/3d-generic/script.js +1 -0
  51. package/examples/basic/index.html +443 -53
  52. package/examples/basic/script.js +1551 -24
  53. package/examples/camera/barcode-detector.js +109 -0
  54. package/examples/camera/depth-estimation.js +71 -0
  55. package/examples/camera/face-detector.js +119 -0
  56. package/examples/camera/face-landmark.js +111 -0
  57. package/examples/camera/gesture-recognition.js +97 -0
  58. package/examples/camera/hand-landmark.js +74 -0
  59. package/examples/camera/image-segmentation.js +98 -0
  60. package/examples/camera/image-to-text.js +43 -0
  61. package/examples/camera/image-upscale.js +75 -0
  62. package/examples/camera/index.html +129 -0
  63. package/examples/camera/object-detection.js +98 -0
  64. package/examples/camera/pose-landmark.js +60 -0
  65. package/examples/camera/script.js +316 -0
  66. package/examples/camera/utils.js +165 -0
  67. package/examples/camera/yolo-tiny.js +54 -0
  68. package/examples/camera/yolo.js +119 -0
  69. package/examples/display-3d/index.html +195 -0
  70. package/examples/display-3d/script.js +1235 -0
  71. package/examples/display-canvas/aframe.js +42950 -0
  72. package/examples/display-canvas/index.html +245 -0
  73. package/examples/display-canvas/script.js +2312 -0
  74. package/examples/display-image/index.html +189 -0
  75. package/examples/display-image/script.js +1093 -0
  76. package/examples/display-spritesheet/index.html +960 -0
  77. package/examples/display-spritesheet/script.js +4243 -0
  78. package/examples/display-text/index.html +195 -0
  79. package/examples/display-text/script.js +1418 -0
  80. package/examples/display-wireframe/index.html +204 -0
  81. package/examples/display-wireframe/script.js +1167 -0
  82. package/examples/edge-impulse/script.js +23 -5
  83. package/examples/glasses-gestures/README.md +11 -0
  84. package/examples/glasses-gestures/edge-impulse-standalone.js +7228 -0
  85. package/examples/glasses-gestures/edge-impulse-standalone.wasm +0 -0
  86. package/examples/glasses-gestures/index.html +74 -0
  87. package/examples/glasses-gestures/run-impulse.js +135 -0
  88. package/examples/glasses-gestures/script.js +228 -0
  89. package/examples/microphone/index.html +104 -0
  90. package/examples/microphone/script.js +394 -0
  91. package/examples/microphone/utils.js +45 -0
  92. package/examples/microphone/whisper-realtime.js +166 -0
  93. package/examples/microphone/whisper.js +132 -0
  94. package/examples/punch/index.html +4 -1
  95. package/examples/server/script.js +0 -1
  96. package/examples/ukaton-firmware-update/merged-firmware.bin +0 -0
  97. package/examples/webxr-3/components/bs-camera.js +65 -0
  98. package/examples/webxr-3/index.html +134 -0
  99. package/examples/webxr-3/script.js +432 -0
  100. package/package.json +10 -2
  101. package/src/BS.ts +101 -1
  102. package/src/CameraManager.ts +10 -8
  103. package/src/Device.ts +652 -11
  104. package/src/DisplayManager.ts +2989 -0
  105. package/src/FileTransferManager.ts +79 -26
  106. package/src/InformationManager.ts +8 -7
  107. package/src/MicrophoneManager.ts +606 -0
  108. package/src/TfliteManager.ts +4 -2
  109. package/src/WifiManager.ts +4 -1
  110. package/src/connection/BaseConnectionManager.ts +4 -0
  111. package/src/connection/bluetooth/bluetoothUUIDs.ts +36 -1
  112. package/src/devicePair/DevicePairPressureSensorDataManager.ts +1 -1
  113. package/src/scanner/NobleScanner.ts +1 -1
  114. package/src/sensor/SensorConfigurationManager.ts +16 -8
  115. package/src/sensor/SensorDataManager.ts +5 -0
  116. package/src/server/udp/UDPServer.ts +4 -4
  117. package/src/server/udp/UDPUtils.ts +1 -1
  118. package/src/server/websocket/WebSocketClient.ts +50 -1
  119. package/src/utils/ArrayBufferUtils.ts +23 -5
  120. package/src/utils/AudioUtils.ts +65 -0
  121. package/src/utils/ColorUtils.ts +66 -0
  122. package/src/utils/DisplayBitmapUtils.ts +695 -0
  123. package/src/utils/DisplayCanvasHelper.ts +4222 -0
  124. package/src/utils/DisplayContextCommand.ts +1566 -0
  125. package/src/utils/DisplayContextState.ts +138 -0
  126. package/src/utils/DisplayContextStateHelper.ts +48 -0
  127. package/src/utils/DisplayManagerInterface.ts +1356 -0
  128. package/src/utils/DisplaySpriteSheetUtils.ts +782 -0
  129. package/src/utils/DisplayUtils.ts +529 -0
  130. package/src/utils/EventDispatcher.ts +59 -14
  131. package/src/utils/MathUtils.ts +88 -2
  132. package/src/utils/ObjectUtils.ts +6 -1
  133. package/src/utils/PathUtils.ts +192 -0
  134. package/src/utils/RangeHelper.ts +15 -3
  135. package/src/utils/Timer.ts +1 -1
  136. package/src/utils/environment.ts +15 -6
@@ -0,0 +1,2989 @@
1
+ import Device, { SendMessageCallback } from "./Device.ts";
2
+ import {
3
+ concatenateArrayBuffers,
4
+ UInt8ByteBuffer,
5
+ } from "./utils/ArrayBufferUtils.ts";
6
+ import { createConsole } from "./utils/Console.ts";
7
+ import EventDispatcher from "./utils/EventDispatcher.ts";
8
+ import autoBind from "auto-bind";
9
+ import {
10
+ clamp,
11
+ degToRad,
12
+ normalizeRadians,
13
+ Vector2,
14
+ } from "./utils/MathUtils.ts";
15
+ import { rgbToHex, stringToRGB } from "./utils/ColorUtils.ts";
16
+ import DisplayContextStateHelper from "./utils/DisplayContextStateHelper.ts";
17
+ import {
18
+ assertValidColor,
19
+ assertValidDisplayBrightness,
20
+ assertValidSegmentCap,
21
+ DisplayScaleDirection,
22
+ DisplayBitmapScaleDirectionToCommandType,
23
+ DisplayColorRGB,
24
+ DisplayCropDirection,
25
+ DisplayCropDirections,
26
+ DisplayCropDirectionToCommandType,
27
+ DisplayCropDirectionToStateKey,
28
+ DisplayRotationCropDirectionToCommandType,
29
+ DisplayRotationCropDirectionToStateKey,
30
+ maxDisplayScale,
31
+ roundScale,
32
+ DisplaySpriteScaleDirectionToCommandType,
33
+ minDisplayScale,
34
+ assertValidAlignment,
35
+ DisplayAlignmentDirectionToCommandType,
36
+ DisplayAlignmentDirectionToStateKey,
37
+ assertValidDirection,
38
+ assertValidAlignmentDirection,
39
+ assertValidWireframe,
40
+ trimWireframe,
41
+ assertValidNumberOfControlPoints,
42
+ assertValidPathNumberOfControlPoints,
43
+ assertValidPath,
44
+ } from "./utils/DisplayUtils.ts";
45
+ import {
46
+ assertValidBitmapPixels,
47
+ drawBitmapHeaderLength,
48
+ getBitmapNumberOfBytes,
49
+ imageToBitmap,
50
+ quantizeImage,
51
+ resizeAndQuantizeImage,
52
+ } from "./utils/DisplayBitmapUtils.ts";
53
+ import {
54
+ DefaultDisplayContextState,
55
+ DisplayAlignment,
56
+ DisplayAlignmentDirection,
57
+ DisplayContextState,
58
+ DisplayContextStateKey,
59
+ DisplayDirection,
60
+ DisplaySegmentCap,
61
+ PartialDisplayContextState,
62
+ } from "./utils/DisplayContextState.ts";
63
+ import {
64
+ DisplayContextCommand,
65
+ DisplayContextCommandType,
66
+ DisplayContextCommandTypes,
67
+ serializeContextCommand,
68
+ } from "./utils/DisplayContextCommand.ts";
69
+ import {
70
+ assertAnySelectedSpriteSheet,
71
+ assertLoadedSpriteSheet,
72
+ assertSelectedSpriteSheet,
73
+ assertSprite,
74
+ assertSpritePaletteSwap,
75
+ assertSpriteSheetPalette,
76
+ assertSpriteSheetPaletteSwap,
77
+ DisplayManagerInterface,
78
+ drawSpriteFromSpriteSheet,
79
+ getSprite,
80
+ getSpritePaletteSwap,
81
+ getSpriteSheetPalette,
82
+ getSpriteSheetPaletteSwap,
83
+ runDisplayContextCommand,
84
+ runDisplayContextCommands,
85
+ selectSpritePaletteSwap,
86
+ selectSpriteSheetPalette,
87
+ selectSpriteSheetPaletteSwap,
88
+ } from "./utils/DisplayManagerInterface.ts";
89
+ import { SendFileCallback } from "./FileTransferManager.ts";
90
+ import { textDecoder, textEncoder } from "./utils/Text.ts";
91
+ import {
92
+ DisplaySprite,
93
+ DisplaySpritePaletteSwap,
94
+ DisplaySpriteSheetPalette,
95
+ DisplaySpriteSheetPaletteSwap,
96
+ serializeSpriteSheet,
97
+ DisplaySpriteSheet,
98
+ DisplaySpriteLines,
99
+ stringToSpriteLines,
100
+ DisplaySpriteSerializedSubLine,
101
+ DisplaySpriteSerializedLine,
102
+ DisplaySpriteSerializedLines,
103
+ } from "./utils/DisplaySpriteSheetUtils.ts";
104
+ import { wait } from "./utils/Timer.ts";
105
+
106
+ const _console = createConsole("DisplayManager", { log: true });
107
+
108
+ export const DefaultNumberOfDisplayColors = 16;
109
+
110
+ export const DisplayCommands = ["sleep", "wake"] as const;
111
+ export type DisplayCommand = (typeof DisplayCommands)[number];
112
+
113
+ export const DisplayStatuses = ["awake", "asleep"] as const;
114
+ export type DisplayStatus = (typeof DisplayStatuses)[number];
115
+
116
+ export const DisplayInformationTypes = [
117
+ "type",
118
+ "width",
119
+ "height",
120
+ "pixelDepth",
121
+ ] as const;
122
+ export type DisplayInformationType = (typeof DisplayInformationTypes)[number];
123
+
124
+ export const DisplayTypes = [
125
+ "none",
126
+ "generic",
127
+ "monocularLeft",
128
+ "monocularRight",
129
+ "binocular",
130
+ ] as const;
131
+ export type DisplayType = (typeof DisplayTypes)[number];
132
+
133
+ export const DisplayPixelDepths = ["1", "2", "4"] as const;
134
+ export type DisplayPixelDepth = (typeof DisplayPixelDepths)[number];
135
+
136
+ export const DisplayBrightnesses = [
137
+ "veryLow",
138
+ "low",
139
+ "medium",
140
+ "high",
141
+ "veryHigh",
142
+ ] as const;
143
+ export type DisplayBrightness = (typeof DisplayBrightnesses)[number];
144
+
145
+ export const DisplayMessageTypes = [
146
+ "isDisplayAvailable",
147
+ "displayStatus",
148
+ "displayInformation",
149
+ "displayCommand",
150
+ "getDisplayBrightness",
151
+ "setDisplayBrightness",
152
+ "displayContextCommands",
153
+ "displayReady",
154
+ "getSpriteSheetName",
155
+ "setSpriteSheetName",
156
+ "spriteSheetIndex",
157
+ ] as const;
158
+ export type DisplayMessageType = (typeof DisplayMessageTypes)[number];
159
+
160
+ export type DisplaySize = {
161
+ width: number;
162
+ height: number;
163
+ };
164
+ export type DisplayInformation = {
165
+ type: DisplayType;
166
+ width: number;
167
+ height: number;
168
+ pixelDepth: DisplayPixelDepth;
169
+ };
170
+
171
+ export type DisplayBitmapColorPair = {
172
+ bitmapColorIndex: number;
173
+ colorIndex: number;
174
+ };
175
+
176
+ export type DisplaySpriteColorPair = {
177
+ spriteColorIndex: number;
178
+ colorIndex: number;
179
+ };
180
+
181
+ export type DisplayWireframeEdge = {
182
+ startIndex: number;
183
+ endIndex: number;
184
+ };
185
+ export type DisplaySegment = {
186
+ start: Vector2;
187
+ end: Vector2;
188
+ };
189
+ export type DisplayWireframe = {
190
+ points: Vector2[];
191
+ edges: DisplayWireframeEdge[];
192
+ };
193
+
194
+ export const DisplayBezierCurveTypes = [
195
+ "segment",
196
+ "quadratic",
197
+ "cubic",
198
+ ] as const;
199
+ export type DisplayBezierCurveType = (typeof DisplayBezierCurveTypes)[number];
200
+ export type DisplayBezierCurve = {
201
+ type: DisplayBezierCurveType;
202
+ controlPoints: Vector2[];
203
+ };
204
+
205
+ export const displayCurveTypeBitWidth = 2;
206
+ export const displayCurveTypesPerByte = 8 / displayCurveTypeBitWidth;
207
+
208
+ export const DisplayPointDataTypes = ["int8", "int16", "float"] as const;
209
+ export type DisplayPointDataType = (typeof DisplayPointDataTypes)[number];
210
+ export const displayPointDataTypeToSize: Record<DisplayPointDataType, number> =
211
+ {
212
+ int8: 1 * 2,
213
+ int16: 2 * 2,
214
+ float: 4 * 2,
215
+ };
216
+ export const displayPointDataTypeToRange: Record<
217
+ DisplayPointDataType,
218
+ { min: number; max: number }
219
+ > = {
220
+ int8: { min: -(2 ** 7), max: 2 ** 7 - 1 },
221
+ int16: { min: -(2 ** 15), max: 2 ** 15 - 1 },
222
+ float: { min: -Infinity, max: Infinity },
223
+ };
224
+
225
+ export const DisplayInformationValues = {
226
+ type: DisplayTypes,
227
+ pixelDepth: DisplayPixelDepths,
228
+ };
229
+
230
+ export const RequiredDisplayMessageTypes: DisplayMessageType[] = [
231
+ "isDisplayAvailable",
232
+ "displayInformation",
233
+ "displayStatus",
234
+ "getDisplayBrightness",
235
+ ] as const;
236
+
237
+ export const DisplayEventTypes = [
238
+ ...DisplayMessageTypes,
239
+ "displayContextState",
240
+ "displayColor",
241
+ "displayColorOpacity",
242
+ "displayOpacity",
243
+ "displaySpriteSheetUploadStart",
244
+ "displaySpriteSheetUploadProgress",
245
+ "displaySpriteSheetUploadComplete",
246
+ ] as const;
247
+ export type DisplayEventType = (typeof DisplayEventTypes)[number];
248
+
249
+ export interface DisplayEventMessages {
250
+ isDisplayAvailable: { isDisplayAvailable: boolean };
251
+ displayStatus: {
252
+ displayStatus: DisplayStatus;
253
+ previousDisplayStatus: DisplayStatus;
254
+ };
255
+ displayInformation: {
256
+ displayInformation: DisplayInformation;
257
+ };
258
+ getDisplayBrightness: {
259
+ displayBrightness: DisplayBrightness;
260
+ };
261
+ displayContextState: {
262
+ displayContextState: DisplayContextState;
263
+ differences: DisplayContextStateKey[];
264
+ };
265
+ displayColor: {
266
+ colorIndex: number;
267
+ colorRGB: DisplayColorRGB;
268
+ colorHex: string;
269
+ };
270
+ displayColorOpacity: {
271
+ opacity: number;
272
+ colorIndex: number;
273
+ };
274
+ displayOpacity: {
275
+ opacity: number;
276
+ };
277
+ displayReady: {};
278
+ getSpriteSheetName: {
279
+ spriteSheetName: string;
280
+ };
281
+
282
+ displaySpriteSheetUploadStart: {
283
+ spriteSheetName: string;
284
+ spriteSheet: DisplaySpriteSheet;
285
+ };
286
+ displaySpriteSheetUploadProgress: {
287
+ spriteSheetName: string;
288
+ spriteSheet: DisplaySpriteSheet;
289
+ progress: number;
290
+ };
291
+ displaySpriteSheetUploadComplete: {
292
+ spriteSheetName: string;
293
+ spriteSheet: DisplaySpriteSheet;
294
+ };
295
+ }
296
+
297
+ export type DisplayEventDispatcher = EventDispatcher<
298
+ Device,
299
+ DisplayEventType,
300
+ DisplayEventMessages
301
+ >;
302
+ export type SendDisplayMessageCallback =
303
+ SendMessageCallback<DisplayMessageType>;
304
+
305
+ export const MinSpriteSheetNameLength = 1;
306
+ export const MaxSpriteSheetNameLength = 30;
307
+
308
+ export type DisplayBitmap = {
309
+ width: number;
310
+ height: number;
311
+ numberOfColors: number;
312
+ pixels: number[];
313
+ };
314
+
315
+ class DisplayManager implements DisplayManagerInterface {
316
+ constructor() {
317
+ autoBind(this);
318
+ }
319
+
320
+ sendMessage!: SendDisplayMessageCallback;
321
+
322
+ eventDispatcher!: DisplayEventDispatcher;
323
+ get #dispatchEvent() {
324
+ return this.eventDispatcher.dispatchEvent;
325
+ }
326
+ get waitForEvent() {
327
+ return this.eventDispatcher.waitForEvent;
328
+ }
329
+
330
+ requestRequiredInformation() {
331
+ _console.log("requesting required display information");
332
+ const messages = RequiredDisplayMessageTypes.map((messageType) => ({
333
+ type: messageType,
334
+ }));
335
+ this.sendMessage(messages, false);
336
+ }
337
+
338
+ // IS DISPLAY AVAILABLE
339
+ #isAvailable = false;
340
+ get isAvailable() {
341
+ return this.#isAvailable;
342
+ }
343
+
344
+ #assertDisplayIsAvailable() {
345
+ _console.assertWithError(this.#isAvailable, "display is not available");
346
+ }
347
+
348
+ #parseIsDisplayAvailable(dataView: DataView) {
349
+ const newIsDisplayAvailable = dataView.getUint8(0) == 1;
350
+ this.#isAvailable = newIsDisplayAvailable;
351
+ _console.log({ isDisplayAvailable: this.#isAvailable });
352
+ this.#dispatchEvent("isDisplayAvailable", {
353
+ isDisplayAvailable: this.#isAvailable,
354
+ });
355
+ }
356
+
357
+ // DISPLAY CONTEXT STATE
358
+ #contextStateHelper = new DisplayContextStateHelper();
359
+ get contextState() {
360
+ return this.#contextStateHelper.state;
361
+ }
362
+ #onContextStateUpdate(differences: DisplayContextStateKey[]) {
363
+ this.#dispatchEvent("displayContextState", {
364
+ displayContextState: structuredClone(this.contextState),
365
+ differences,
366
+ });
367
+ }
368
+ async setContextState(
369
+ newState: PartialDisplayContextState,
370
+ sendImmediately?: boolean
371
+ ) {
372
+ const differences = this.#contextStateHelper.diff(newState);
373
+ if (differences.length == 0) {
374
+ return;
375
+ }
376
+ differences.forEach((difference) => {
377
+ switch (difference) {
378
+ case "backgroundColorIndex":
379
+ this.selectBackgroundColor(newState.backgroundColorIndex!);
380
+ break;
381
+ case "fillBackground":
382
+ this.setFillBackground(newState.fillBackground!);
383
+ break;
384
+ case "ignoreFill":
385
+ this.setIgnoreFill(newState.ignoreFill!);
386
+ break;
387
+ case "ignoreLine":
388
+ this.setIgnoreLine(newState.ignoreLine!);
389
+ break;
390
+ case "fillColorIndex":
391
+ this.selectFillColor(newState.fillColorIndex!);
392
+ break;
393
+ case "lineColorIndex":
394
+ this.selectLineColor(newState.lineColorIndex!);
395
+ break;
396
+ case "lineWidth":
397
+ this.setLineWidth(newState.lineWidth!);
398
+ break;
399
+ case "horizontalAlignment":
400
+ this.setHorizontalAlignment(newState.horizontalAlignment!);
401
+ break;
402
+ case "verticalAlignment":
403
+ this.setVerticalAlignment(newState.verticalAlignment!);
404
+ break;
405
+ case "rotation":
406
+ this.setRotation(newState.rotation!, true);
407
+ break;
408
+ case "segmentStartCap":
409
+ this.setSegmentStartCap(newState.segmentStartCap!);
410
+ break;
411
+ case "segmentEndCap":
412
+ this.setSegmentEndCap(newState.segmentEndCap!);
413
+ break;
414
+ case "segmentStartRadius":
415
+ this.setSegmentStartRadius(newState.segmentStartRadius!);
416
+ break;
417
+ case "segmentEndRadius":
418
+ this.setSegmentEndRadius(newState.segmentEndRadius!);
419
+ break;
420
+ case "cropTop":
421
+ this.setCropTop(newState.cropTop!);
422
+ break;
423
+ case "cropRight":
424
+ this.setCropRight(newState.cropRight!);
425
+ break;
426
+ case "cropBottom":
427
+ this.setCropBottom(newState.cropBottom!);
428
+ break;
429
+ case "cropLeft":
430
+ this.setCropLeft(newState.cropLeft!);
431
+ break;
432
+ case "rotationCropTop":
433
+ this.setRotationCropTop(newState.rotationCropTop!);
434
+ break;
435
+ case "rotationCropRight":
436
+ this.setRotationCropRight(newState.rotationCropRight!);
437
+ break;
438
+ case "rotationCropBottom":
439
+ this.setRotationCropBottom(newState.rotationCropBottom!);
440
+ break;
441
+ case "rotationCropLeft":
442
+ this.setRotationCropLeft(newState.rotationCropLeft!);
443
+ break;
444
+ case "bitmapColorIndices":
445
+ const bitmapColors: DisplayBitmapColorPair[] = [];
446
+ newState.bitmapColorIndices!.forEach(
447
+ (colorIndex, bitmapColorIndex) => {
448
+ bitmapColors.push({ bitmapColorIndex, colorIndex });
449
+ }
450
+ );
451
+ this.selectBitmapColors(bitmapColors);
452
+ break;
453
+ case "bitmapScaleX":
454
+ this.setBitmapScaleX(newState.bitmapScaleX!);
455
+ break;
456
+ case "bitmapScaleY":
457
+ this.setBitmapScaleY(newState.bitmapScaleY!);
458
+ break;
459
+ case "spriteColorIndices":
460
+ const spriteColors: DisplaySpriteColorPair[] = [];
461
+ newState.spriteColorIndices!.forEach(
462
+ (colorIndex, spriteColorIndex) => {
463
+ spriteColors.push({ spriteColorIndex, colorIndex });
464
+ }
465
+ );
466
+ this.selectSpriteColors(spriteColors);
467
+ break;
468
+ case "spriteScaleX":
469
+ this.setSpriteScaleX(newState.spriteScaleX!);
470
+ break;
471
+ case "spriteScaleY":
472
+ this.setSpriteScaleY(newState.spriteScaleY!);
473
+ break;
474
+ case "spritesLineHeight":
475
+ this.setSpritesLineHeight(newState.spritesLineHeight!);
476
+ break;
477
+ case "spritesDirection":
478
+ this.setSpritesDirection(newState.spritesDirection!);
479
+ break;
480
+ case "spritesLineDirection":
481
+ this.setSpritesLineDirection(newState.spritesLineDirection!);
482
+ break;
483
+ case "spritesSpacing":
484
+ this.setSpritesSpacing(newState.spritesSpacing!);
485
+ break;
486
+ case "spritesLineSpacing":
487
+ this.setSpritesLineSpacing(newState.spritesLineSpacing!);
488
+ break;
489
+ case "spritesAlignment":
490
+ this.setSpritesAlignment(newState.spritesAlignment!);
491
+ break;
492
+ case "spritesLineAlignment":
493
+ this.setSpritesLineAlignment(newState.spritesLineAlignment!);
494
+ break;
495
+ }
496
+ });
497
+ if (sendImmediately) {
498
+ await this.#sendContextCommands();
499
+ }
500
+ }
501
+
502
+ // DISPLAY STATUS
503
+ #displayStatus!: DisplayStatus;
504
+ get displayStatus() {
505
+ return this.#displayStatus;
506
+ }
507
+ get isDisplayAwake() {
508
+ return this.#displayStatus == "awake";
509
+ }
510
+ #parseDisplayStatus(dataView: DataView) {
511
+ const displayStatusIndex = dataView.getUint8(0);
512
+ const newDisplayStatus = DisplayStatuses[displayStatusIndex];
513
+ this.#updateDisplayStatus(newDisplayStatus);
514
+ }
515
+ #updateDisplayStatus(newDisplayStatus: DisplayStatus) {
516
+ _console.assertEnumWithError(newDisplayStatus, DisplayStatuses);
517
+ if (newDisplayStatus == this.#displayStatus) {
518
+ _console.log(`redundant displayStatus ${newDisplayStatus}`);
519
+ return;
520
+ }
521
+ const previousDisplayStatus = this.#displayStatus;
522
+ this.#displayStatus = newDisplayStatus;
523
+ _console.log(`updated displayStatus to "${this.displayStatus}"`);
524
+ this.#dispatchEvent("displayStatus", {
525
+ displayStatus: this.displayStatus,
526
+ previousDisplayStatus,
527
+ });
528
+ }
529
+
530
+ // DISPLAY COMMAND
531
+ async #sendDisplayCommand(
532
+ command: DisplayCommand,
533
+ sendImmediately?: boolean
534
+ ) {
535
+ _console.assertEnumWithError(command, DisplayCommands);
536
+ _console.log(`sending display command "${command}"`);
537
+
538
+ const promise = this.waitForEvent("displayStatus");
539
+ _console.log(`setting command "${command}"`);
540
+ const commandEnum = DisplayCommands.indexOf(command);
541
+
542
+ this.sendMessage(
543
+ [
544
+ {
545
+ type: "displayCommand",
546
+ data: UInt8ByteBuffer(commandEnum),
547
+ },
548
+ ],
549
+ sendImmediately
550
+ );
551
+
552
+ await promise;
553
+ }
554
+ #assertIsAwake() {
555
+ _console.assertWithError(
556
+ this.#displayStatus == "awake",
557
+ `display is not awake - currently ${this.#displayStatus}`
558
+ );
559
+ }
560
+ #assertIsNotAwake() {
561
+ _console.assertWithError(
562
+ this.#displayStatus != "awake",
563
+ `display is awake`
564
+ );
565
+ }
566
+
567
+ async wake() {
568
+ this.#assertIsNotAwake();
569
+ await this.#sendDisplayCommand("wake");
570
+ }
571
+ async sleep() {
572
+ this.#assertIsAwake();
573
+ await this.#sendDisplayCommand("sleep");
574
+ }
575
+ async toggle() {
576
+ switch (this.displayStatus) {
577
+ case "asleep":
578
+ this.wake();
579
+ break;
580
+ case "awake":
581
+ this.sleep();
582
+ break;
583
+ }
584
+ }
585
+
586
+ get numberOfColors() {
587
+ return 2 ** Number(this.pixelDepth!);
588
+ }
589
+
590
+ // INFORMATION
591
+ #displayInformation?: DisplayInformation;
592
+ get displayInformation() {
593
+ return this.#displayInformation!;
594
+ }
595
+
596
+ get pixelDepth() {
597
+ return this.#displayInformation?.pixelDepth!;
598
+ }
599
+ get width() {
600
+ return this.#displayInformation?.width!;
601
+ }
602
+ get height() {
603
+ return this.#displayInformation?.width!;
604
+ }
605
+ get size() {
606
+ return {
607
+ width: this.width!,
608
+ height: this.height!,
609
+ };
610
+ }
611
+ get type() {
612
+ return this.#displayInformation?.type!;
613
+ }
614
+
615
+ #parseDisplayInformation(dataView: DataView) {
616
+ // @ts-expect-error
617
+ const parsedDisplayInformation: DisplayInformation = {};
618
+
619
+ let byteOffset = 0;
620
+ while (byteOffset < dataView.byteLength) {
621
+ const displayInformationTypeIndex = dataView.getUint8(byteOffset++);
622
+ const displayInformationType =
623
+ DisplayInformationTypes[displayInformationTypeIndex];
624
+ _console.assertWithError(
625
+ displayInformationType,
626
+ `invalid displayInformationTypeIndex ${displayInformationType}`
627
+ );
628
+ _console.log({ displayInformationType });
629
+
630
+ switch (displayInformationType) {
631
+ case "width":
632
+ case "height":
633
+ {
634
+ const value = dataView.getUint16(byteOffset, true);
635
+ parsedDisplayInformation[displayInformationType] = value;
636
+ byteOffset += 2;
637
+ }
638
+ break;
639
+ case "pixelDepth":
640
+ case "type":
641
+ {
642
+ const values = DisplayInformationValues[displayInformationType];
643
+ let rawValue = dataView.getUint8(byteOffset++);
644
+ const value = values[rawValue];
645
+ _console.assertEnumWithError(value, values);
646
+ // @ts-expect-error
647
+ parsedDisplayInformation[displayInformationType] = value;
648
+ }
649
+ break;
650
+ }
651
+ }
652
+
653
+ _console.log({ parsedDisplayInformation });
654
+ const missingDisplayInformationType = DisplayInformationTypes.find(
655
+ (type) => !(type in parsedDisplayInformation)
656
+ );
657
+ _console.assertWithError(
658
+ !missingDisplayInformationType,
659
+ `missingDisplayInformationType ${missingDisplayInformationType}`
660
+ );
661
+ this.#displayInformation = parsedDisplayInformation;
662
+ this.#colors = new Array(this.numberOfColors).fill("#000000");
663
+ this.#opacities = new Array(this.numberOfColors).fill(1);
664
+ this.contextState.bitmapColorIndices = new Array(this.numberOfColors).fill(
665
+ 0
666
+ );
667
+ this.contextState.spriteColorIndices = new Array(this.numberOfColors).fill(
668
+ 0
669
+ );
670
+ this.#dispatchEvent("displayInformation", {
671
+ displayInformation: this.#displayInformation,
672
+ });
673
+ }
674
+
675
+ // DISPLAY BRIGHTNESS
676
+ #brightness!: DisplayBrightness;
677
+ get brightness() {
678
+ return this.#brightness;
679
+ }
680
+
681
+ #parseDisplayBrightness(dataView: DataView) {
682
+ const newDisplayBrightnessEnum = dataView.getUint8(0);
683
+ const newDisplayBrightness = DisplayBrightnesses[newDisplayBrightnessEnum];
684
+ assertValidDisplayBrightness(newDisplayBrightness);
685
+
686
+ this.#brightness = newDisplayBrightness;
687
+ _console.log({ displayBrightness: this.#brightness });
688
+ this.#dispatchEvent("getDisplayBrightness", {
689
+ displayBrightness: this.#brightness,
690
+ });
691
+ }
692
+
693
+ async setBrightness(
694
+ newDisplayBrightness: DisplayBrightness,
695
+ sendImmediately?: boolean
696
+ ) {
697
+ this.#assertDisplayIsAvailable();
698
+ assertValidDisplayBrightness(newDisplayBrightness);
699
+ if (this.brightness == newDisplayBrightness) {
700
+ _console.log(`redundant displayBrightness ${newDisplayBrightness}`);
701
+ return;
702
+ }
703
+ const newDisplayBrightnessEnum =
704
+ DisplayBrightnesses.indexOf(newDisplayBrightness);
705
+ const newDisplayBrightnessData = UInt8ByteBuffer(newDisplayBrightnessEnum);
706
+
707
+ const promise = this.waitForEvent("getDisplayBrightness");
708
+ this.sendMessage(
709
+ [{ type: "setDisplayBrightness", data: newDisplayBrightnessData }],
710
+ sendImmediately
711
+ );
712
+ await promise;
713
+ }
714
+
715
+ // DISPLAY CONTEXT
716
+ #assertValidDisplayContextCommand(
717
+ displayContextCommand: DisplayContextCommandType
718
+ ) {
719
+ _console.assertEnumWithError(
720
+ displayContextCommand,
721
+ DisplayContextCommandTypes
722
+ );
723
+ }
724
+
725
+ get #maxCommandDataLength() {
726
+ return this.mtu - 7;
727
+ }
728
+ #displayContextCommandBuffers: ArrayBuffer[] = [];
729
+ async #sendDisplayContextCommand(
730
+ displayContextCommand: DisplayContextCommandType,
731
+ arrayBuffer?: ArrayBuffer,
732
+ sendImmediately?: boolean
733
+ ) {
734
+ this.#assertValidDisplayContextCommand(displayContextCommand);
735
+ _console.log(
736
+ "sendDisplayContextCommand",
737
+ { displayContextCommand, sendImmediately },
738
+ arrayBuffer
739
+ );
740
+ const displayContextCommandEnum = DisplayContextCommandTypes.indexOf(
741
+ displayContextCommand
742
+ );
743
+ const _arrayBuffer = concatenateArrayBuffers(
744
+ UInt8ByteBuffer(displayContextCommandEnum),
745
+ arrayBuffer
746
+ );
747
+ const newLength = this.#displayContextCommandBuffers.reduce(
748
+ (sum, buffer) => sum + buffer.byteLength,
749
+ _arrayBuffer.byteLength
750
+ );
751
+ if (newLength > this.#maxCommandDataLength) {
752
+ _console.log("displayContextCommandBuffers too full - sending now");
753
+ await this.#sendContextCommands();
754
+ }
755
+ this.#displayContextCommandBuffers.push(_arrayBuffer);
756
+ if (sendImmediately) {
757
+ await this.#sendContextCommands();
758
+ }
759
+ }
760
+ async #sendContextCommands() {
761
+ if (this.#displayContextCommandBuffers.length == 0) {
762
+ return;
763
+ }
764
+ const data = concatenateArrayBuffers(this.#displayContextCommandBuffers);
765
+ _console.log(
766
+ `sending displayContextCommands`,
767
+ this.#displayContextCommandBuffers.slice(),
768
+ data
769
+ );
770
+ this.#displayContextCommandBuffers.length = 0;
771
+ await this.sendMessage([{ type: "displayContextCommands", data }], true);
772
+ }
773
+ async flushContextCommands() {
774
+ await this.#sendContextCommands();
775
+ }
776
+ async show(sendImmediately = true) {
777
+ _console.log("showDisplay");
778
+ this.#isReady = false;
779
+ this.#lastShowRequestTime = Date.now();
780
+ await this.#sendDisplayContextCommand("show", undefined, sendImmediately);
781
+ }
782
+ async clear(sendImmediately = true) {
783
+ _console.log("clearDisplay");
784
+ this.#isReady = false;
785
+ this.#lastShowRequestTime = Date.now();
786
+ await this.#sendDisplayContextCommand("clear", undefined, sendImmediately);
787
+ }
788
+
789
+ assertValidColorIndex(colorIndex: number) {
790
+ _console.assertRangeWithError(
791
+ "colorIndex",
792
+ colorIndex,
793
+ 0,
794
+ this.numberOfColors
795
+ );
796
+ }
797
+ #colors: string[] = [];
798
+ get colors() {
799
+ return this.#colors;
800
+ }
801
+ async setColor(
802
+ colorIndex: number,
803
+ color: DisplayColorRGB | string,
804
+ sendImmediately?: boolean
805
+ ) {
806
+ let colorRGB: DisplayColorRGB;
807
+ if (typeof color == "string") {
808
+ colorRGB = stringToRGB(color);
809
+ } else {
810
+ colorRGB = color;
811
+ }
812
+ const colorHex = rgbToHex(colorRGB);
813
+ if (this.colors[colorIndex] == colorHex) {
814
+ _console.log(`redundant color #${colorIndex} ${colorHex}`);
815
+ return;
816
+ }
817
+
818
+ //_console.log(`setting color #${colorIndex}`, colorRGB);
819
+ this.assertValidColorIndex(colorIndex);
820
+ assertValidColor(colorRGB);
821
+ const dataView = new DataView(new ArrayBuffer(4));
822
+ dataView.setUint8(0, colorIndex);
823
+ dataView.setUint8(1, colorRGB.r);
824
+ dataView.setUint8(2, colorRGB.g);
825
+ dataView.setUint8(3, colorRGB.b);
826
+ await this.#sendDisplayContextCommand(
827
+ "setColor",
828
+ dataView.buffer,
829
+ sendImmediately
830
+ );
831
+ this.colors[colorIndex] = colorHex;
832
+ this.#dispatchEvent("displayColor", {
833
+ colorIndex,
834
+ colorRGB,
835
+ colorHex,
836
+ });
837
+ }
838
+ #opacities: number[] = [];
839
+ get opacities() {
840
+ return this.#opacities;
841
+ }
842
+ async setColorOpacity(
843
+ colorIndex: number,
844
+ opacity: number,
845
+ sendImmediately?: boolean
846
+ ) {
847
+ const commandType: DisplayContextCommandType = "setColorOpacity";
848
+ const dataView = serializeContextCommand(this, {
849
+ type: commandType,
850
+ colorIndex,
851
+ opacity,
852
+ });
853
+ if (!dataView) {
854
+ return;
855
+ }
856
+ await this.#sendDisplayContextCommand(
857
+ commandType,
858
+ dataView.buffer,
859
+ sendImmediately
860
+ );
861
+ this.#opacities[colorIndex] = opacity;
862
+ this.#dispatchEvent("displayColorOpacity", { colorIndex, opacity });
863
+ }
864
+ async setOpacity(opacity: number, sendImmediately?: boolean) {
865
+ const commandType: DisplayContextCommandType = "setOpacity";
866
+ const dataView = serializeContextCommand(this, {
867
+ type: commandType,
868
+ opacity,
869
+ });
870
+ if (!dataView) {
871
+ return;
872
+ }
873
+ await this.#sendDisplayContextCommand(
874
+ commandType,
875
+ dataView.buffer,
876
+ sendImmediately
877
+ );
878
+ this.#opacities.fill(opacity);
879
+ this.#dispatchEvent("displayOpacity", { opacity });
880
+ }
881
+
882
+ async saveContext(sendImmediately?: boolean) {
883
+ const commandType: DisplayContextCommandType = "saveContext";
884
+ const dataView = serializeContextCommand(this, { type: commandType });
885
+ await this.#sendDisplayContextCommand(
886
+ commandType,
887
+ dataView?.buffer,
888
+ sendImmediately
889
+ );
890
+ }
891
+ async restoreContext(sendImmediately?: boolean) {
892
+ const commandType: DisplayContextCommandType = "restoreContext";
893
+ const dataView = serializeContextCommand(this, { type: commandType });
894
+ await this.#sendDisplayContextCommand(
895
+ commandType,
896
+ dataView?.buffer,
897
+ sendImmediately
898
+ );
899
+ }
900
+
901
+ async selectFillColor(fillColorIndex: number, sendImmediately?: boolean) {
902
+ this.assertValidColorIndex(fillColorIndex);
903
+ const differences = this.#contextStateHelper.update({
904
+ fillColorIndex,
905
+ });
906
+ if (differences.length == 0) {
907
+ return;
908
+ }
909
+ const commandType: DisplayContextCommandType = "selectFillColor";
910
+ const dataView = serializeContextCommand(this, {
911
+ type: commandType,
912
+ fillColorIndex,
913
+ });
914
+ if (!dataView) {
915
+ return;
916
+ }
917
+ await this.#sendDisplayContextCommand(
918
+ commandType,
919
+ dataView.buffer,
920
+ sendImmediately
921
+ );
922
+ this.#onContextStateUpdate(differences);
923
+ }
924
+ async selectBackgroundColor(
925
+ backgroundColorIndex: number,
926
+ sendImmediately?: boolean
927
+ ) {
928
+ this.assertValidColorIndex(backgroundColorIndex);
929
+ const differences = this.#contextStateHelper.update({
930
+ backgroundColorIndex,
931
+ });
932
+ if (differences.length == 0) {
933
+ return;
934
+ }
935
+ const commandType: DisplayContextCommandType = "selectBackgroundColor";
936
+ const dataView = serializeContextCommand(this, {
937
+ type: commandType,
938
+ backgroundColorIndex,
939
+ });
940
+ if (!dataView) {
941
+ return;
942
+ }
943
+ await this.#sendDisplayContextCommand(
944
+ commandType,
945
+ dataView.buffer,
946
+ sendImmediately
947
+ );
948
+ this.#onContextStateUpdate(differences);
949
+ }
950
+ async selectLineColor(lineColorIndex: number, sendImmediately?: boolean) {
951
+ this.assertValidColorIndex(lineColorIndex);
952
+ const differences = this.#contextStateHelper.update({
953
+ lineColorIndex,
954
+ });
955
+ if (differences.length == 0) {
956
+ return;
957
+ }
958
+ const commandType: DisplayContextCommandType = "selectLineColor";
959
+ const dataView = serializeContextCommand(this, {
960
+ type: commandType,
961
+ lineColorIndex,
962
+ });
963
+ if (!dataView) {
964
+ return;
965
+ }
966
+ await this.#sendDisplayContextCommand(
967
+ commandType,
968
+ dataView.buffer,
969
+ sendImmediately
970
+ );
971
+ this.#onContextStateUpdate(differences);
972
+ }
973
+ async setIgnoreFill(ignoreFill: boolean, sendImmediately?: boolean) {
974
+ const differences = this.#contextStateHelper.update({
975
+ ignoreFill,
976
+ });
977
+ if (differences.length == 0) {
978
+ return;
979
+ }
980
+ const commandType: DisplayContextCommandType = "setIgnoreFill";
981
+ const dataView = serializeContextCommand(this, {
982
+ type: commandType,
983
+ ignoreFill,
984
+ });
985
+ if (!dataView) {
986
+ return;
987
+ }
988
+ await this.#sendDisplayContextCommand(
989
+ commandType,
990
+ dataView.buffer,
991
+ sendImmediately
992
+ );
993
+ this.#onContextStateUpdate(differences);
994
+ }
995
+ async setIgnoreLine(ignoreLine: boolean, sendImmediately?: boolean) {
996
+ const differences = this.#contextStateHelper.update({
997
+ ignoreLine,
998
+ });
999
+ if (differences.length == 0) {
1000
+ return;
1001
+ }
1002
+ const commandType: DisplayContextCommandType = "setIgnoreLine";
1003
+ const dataView = serializeContextCommand(this, {
1004
+ type: commandType,
1005
+ ignoreLine,
1006
+ });
1007
+ if (!dataView) {
1008
+ return;
1009
+ }
1010
+ await this.#sendDisplayContextCommand(
1011
+ commandType,
1012
+ dataView.buffer,
1013
+ sendImmediately
1014
+ );
1015
+ this.#onContextStateUpdate(differences);
1016
+ }
1017
+ async setFillBackground(fillBackground: boolean, sendImmediately?: boolean) {
1018
+ const differences = this.#contextStateHelper.update({
1019
+ fillBackground,
1020
+ });
1021
+ if (differences.length == 0) {
1022
+ return;
1023
+ }
1024
+ const commandType: DisplayContextCommandType = "setFillBackground";
1025
+ const dataView = serializeContextCommand(this, {
1026
+ type: commandType,
1027
+ fillBackground,
1028
+ });
1029
+ if (!dataView) {
1030
+ return;
1031
+ }
1032
+ await this.#sendDisplayContextCommand(
1033
+ commandType,
1034
+ dataView.buffer,
1035
+ sendImmediately
1036
+ );
1037
+ this.#onContextStateUpdate(differences);
1038
+ }
1039
+ assertValidLineWidth(lineWidth: number) {
1040
+ _console.assertRangeWithError(
1041
+ "lineWidth",
1042
+ lineWidth,
1043
+ 0,
1044
+ Math.max(this.width, this.height)
1045
+ );
1046
+ }
1047
+ async setLineWidth(lineWidth: number, sendImmediately?: boolean) {
1048
+ this.assertValidLineWidth(lineWidth);
1049
+ const differences = this.#contextStateHelper.update({
1050
+ lineWidth,
1051
+ });
1052
+ if (differences.length == 0) {
1053
+ return;
1054
+ }
1055
+ const commandType: DisplayContextCommandType = "setLineWidth";
1056
+ const dataView = serializeContextCommand(this, {
1057
+ type: commandType,
1058
+ lineWidth,
1059
+ });
1060
+ if (!dataView) {
1061
+ return;
1062
+ }
1063
+ await this.#sendDisplayContextCommand(
1064
+ commandType,
1065
+ dataView.buffer,
1066
+ sendImmediately
1067
+ );
1068
+ this.#onContextStateUpdate(differences);
1069
+ }
1070
+
1071
+ async setAlignment(
1072
+ alignmentDirection: DisplayAlignmentDirection,
1073
+ alignment: DisplayAlignment,
1074
+ sendImmediately?: boolean
1075
+ ) {
1076
+ assertValidAlignmentDirection(alignmentDirection);
1077
+ const alignmentCommand =
1078
+ DisplayAlignmentDirectionToCommandType[alignmentDirection];
1079
+ const alignmentKey =
1080
+ DisplayAlignmentDirectionToStateKey[alignmentDirection];
1081
+ const differences = this.#contextStateHelper.update({
1082
+ [alignmentKey]: alignment,
1083
+ });
1084
+ _console.log({ alignmentKey, alignment, differences });
1085
+ if (differences.length == 0) {
1086
+ return;
1087
+ }
1088
+ // @ts-ignore
1089
+ const dataView = serializeContextCommand(this, {
1090
+ type: alignmentCommand,
1091
+ [alignmentKey]: alignment,
1092
+ });
1093
+ if (!dataView) {
1094
+ return;
1095
+ }
1096
+ await this.#sendDisplayContextCommand(
1097
+ alignmentCommand,
1098
+ dataView.buffer,
1099
+ sendImmediately
1100
+ );
1101
+ this.#onContextStateUpdate(differences);
1102
+ }
1103
+ async setHorizontalAlignment(
1104
+ horizontalAlignment: DisplayAlignment,
1105
+ sendImmediately?: boolean
1106
+ ) {
1107
+ await this.setAlignment("horizontal", horizontalAlignment, sendImmediately);
1108
+ }
1109
+ async setVerticalAlignment(
1110
+ verticalAlignment: DisplayAlignment,
1111
+ sendImmediately?: boolean
1112
+ ) {
1113
+ await this.setAlignment("vertical", verticalAlignment, sendImmediately);
1114
+ }
1115
+ async resetAlignment(sendImmediately?: boolean) {
1116
+ const differences = this.#contextStateHelper.update({
1117
+ verticalAlignment: DefaultDisplayContextState.verticalAlignment,
1118
+ horizontalAlignment: DefaultDisplayContextState.horizontalAlignment,
1119
+ });
1120
+ if (differences.length == 0) {
1121
+ return;
1122
+ }
1123
+ const commandType: DisplayContextCommandType = "resetAlignment";
1124
+ const dataView = serializeContextCommand(this, {
1125
+ type: commandType,
1126
+ });
1127
+ if (!dataView) {
1128
+ return;
1129
+ }
1130
+ await this.#sendDisplayContextCommand(
1131
+ commandType,
1132
+ dataView?.buffer,
1133
+ sendImmediately
1134
+ );
1135
+ this.#onContextStateUpdate(differences);
1136
+ }
1137
+
1138
+ async setRotation(
1139
+ rotation: number,
1140
+ isRadians?: boolean,
1141
+ sendImmediately?: boolean
1142
+ ) {
1143
+ rotation = isRadians ? rotation : degToRad(rotation);
1144
+ rotation = normalizeRadians(rotation);
1145
+ isRadians = true;
1146
+ const differences = this.#contextStateHelper.update({
1147
+ rotation,
1148
+ });
1149
+ if (differences.length == 0) {
1150
+ return;
1151
+ }
1152
+ const commandType: DisplayContextCommandType = "setRotation";
1153
+ const dataView = serializeContextCommand(this, {
1154
+ type: commandType,
1155
+ rotation,
1156
+ isRadians,
1157
+ });
1158
+ if (!dataView) {
1159
+ return;
1160
+ }
1161
+ await this.#sendDisplayContextCommand(
1162
+ commandType,
1163
+ dataView.buffer,
1164
+ sendImmediately
1165
+ );
1166
+
1167
+ this.#onContextStateUpdate(differences);
1168
+ }
1169
+ async clearRotation(sendImmediately?: boolean) {
1170
+ const differences = this.#contextStateHelper.update({
1171
+ rotation: 0,
1172
+ });
1173
+ if (differences.length == 0) {
1174
+ return;
1175
+ }
1176
+ const commandType: DisplayContextCommandType = "clearRotation";
1177
+ const dataView = serializeContextCommand(this, { type: commandType });
1178
+ if (!dataView) {
1179
+ return;
1180
+ }
1181
+ await this.#sendDisplayContextCommand(
1182
+ commandType,
1183
+ dataView.buffer,
1184
+ sendImmediately
1185
+ );
1186
+ this.#onContextStateUpdate(differences);
1187
+ }
1188
+
1189
+ async setSegmentStartCap(
1190
+ segmentStartCap: DisplaySegmentCap,
1191
+ sendImmediately?: boolean
1192
+ ) {
1193
+ assertValidSegmentCap(segmentStartCap);
1194
+ const differences = this.#contextStateHelper.update({
1195
+ segmentStartCap,
1196
+ });
1197
+ if (differences.length == 0) {
1198
+ return;
1199
+ }
1200
+ const commandType: DisplayContextCommandType = "setSegmentStartCap";
1201
+ const dataView = serializeContextCommand(this, {
1202
+ type: commandType,
1203
+ segmentStartCap,
1204
+ });
1205
+ if (!dataView) {
1206
+ return;
1207
+ }
1208
+ await this.#sendDisplayContextCommand(
1209
+ commandType,
1210
+ dataView.buffer,
1211
+ sendImmediately
1212
+ );
1213
+ this.#onContextStateUpdate(differences);
1214
+ }
1215
+ async setSegmentEndCap(
1216
+ segmentEndCap: DisplaySegmentCap,
1217
+ sendImmediately?: boolean
1218
+ ) {
1219
+ assertValidSegmentCap(segmentEndCap);
1220
+ const differences = this.#contextStateHelper.update({
1221
+ segmentEndCap,
1222
+ });
1223
+ if (differences.length == 0) {
1224
+ return;
1225
+ }
1226
+ const commandType: DisplayContextCommandType = "setSegmentEndCap";
1227
+ const dataView = serializeContextCommand(this, {
1228
+ type: commandType,
1229
+ segmentEndCap,
1230
+ });
1231
+ if (!dataView) {
1232
+ return;
1233
+ }
1234
+ await this.#sendDisplayContextCommand(
1235
+ commandType,
1236
+ dataView.buffer,
1237
+ sendImmediately
1238
+ );
1239
+ this.#onContextStateUpdate(differences);
1240
+ }
1241
+ async setSegmentCap(
1242
+ segmentCap: DisplaySegmentCap,
1243
+ sendImmediately?: boolean
1244
+ ) {
1245
+ assertValidSegmentCap(segmentCap);
1246
+ const differences = this.#contextStateHelper.update({
1247
+ segmentStartCap: segmentCap,
1248
+ segmentEndCap: segmentCap,
1249
+ });
1250
+ if (differences.length == 0) {
1251
+ return;
1252
+ }
1253
+ const commandType: DisplayContextCommandType = "setSegmentCap";
1254
+ const dataView = serializeContextCommand(this, {
1255
+ type: commandType,
1256
+ segmentCap,
1257
+ });
1258
+ if (!dataView) {
1259
+ return;
1260
+ }
1261
+ await this.#sendDisplayContextCommand(
1262
+ commandType,
1263
+ dataView.buffer,
1264
+ sendImmediately
1265
+ );
1266
+ this.#onContextStateUpdate(differences);
1267
+ }
1268
+
1269
+ async setSegmentStartRadius(
1270
+ segmentStartRadius: number,
1271
+ sendImmediately?: boolean
1272
+ ) {
1273
+ const differences = this.#contextStateHelper.update({
1274
+ segmentStartRadius,
1275
+ });
1276
+ if (differences.length == 0) {
1277
+ return;
1278
+ }
1279
+ const commandType: DisplayContextCommandType = "setSegmentStartRadius";
1280
+ const dataView = serializeContextCommand(this, {
1281
+ type: commandType,
1282
+ segmentStartRadius,
1283
+ });
1284
+ if (!dataView) {
1285
+ return;
1286
+ }
1287
+ await this.#sendDisplayContextCommand(
1288
+ commandType,
1289
+ dataView.buffer,
1290
+ sendImmediately
1291
+ );
1292
+ this.#onContextStateUpdate(differences);
1293
+ }
1294
+ async setSegmentEndRadius(
1295
+ segmentEndRadius: number,
1296
+ sendImmediately?: boolean
1297
+ ) {
1298
+ const differences = this.#contextStateHelper.update({
1299
+ segmentEndRadius,
1300
+ });
1301
+ if (differences.length == 0) {
1302
+ return;
1303
+ }
1304
+ const commandType: DisplayContextCommandType = "setSegmentEndRadius";
1305
+ const dataView = serializeContextCommand(this, {
1306
+ type: commandType,
1307
+ segmentEndRadius,
1308
+ });
1309
+ if (!dataView) {
1310
+ return;
1311
+ }
1312
+ await this.#sendDisplayContextCommand(
1313
+ commandType,
1314
+ dataView.buffer,
1315
+ sendImmediately
1316
+ );
1317
+ this.#onContextStateUpdate(differences);
1318
+ }
1319
+ async setSegmentRadius(segmentRadius: number, sendImmediately?: boolean) {
1320
+ const differences = this.#contextStateHelper.update({
1321
+ segmentStartRadius: segmentRadius,
1322
+ segmentEndRadius: segmentRadius,
1323
+ });
1324
+ if (differences.length == 0) {
1325
+ return;
1326
+ }
1327
+ const commandType: DisplayContextCommandType = "setSegmentRadius";
1328
+ const dataView = serializeContextCommand(this, {
1329
+ type: commandType,
1330
+ segmentRadius,
1331
+ });
1332
+ if (!dataView) {
1333
+ return;
1334
+ }
1335
+ await this.#sendDisplayContextCommand(
1336
+ commandType,
1337
+ dataView.buffer,
1338
+ sendImmediately
1339
+ );
1340
+ this.#onContextStateUpdate(differences);
1341
+ }
1342
+
1343
+ async setCrop(
1344
+ cropDirection: DisplayCropDirection,
1345
+ crop: number,
1346
+ sendImmediately?: boolean
1347
+ ) {
1348
+ _console.assertEnumWithError(cropDirection, DisplayCropDirections);
1349
+ crop = Math.max(0, crop);
1350
+ const cropCommand = DisplayCropDirectionToCommandType[cropDirection];
1351
+ const cropKey = DisplayCropDirectionToStateKey[cropDirection];
1352
+ const differences = this.#contextStateHelper.update({
1353
+ [cropKey]: crop,
1354
+ });
1355
+ if (differences.length == 0) {
1356
+ return;
1357
+ }
1358
+ // @ts-ignore
1359
+ const dataView = serializeContextCommand(this, {
1360
+ type: cropCommand,
1361
+ [cropKey]: crop,
1362
+ });
1363
+ if (!dataView) {
1364
+ return;
1365
+ }
1366
+ await this.#sendDisplayContextCommand(
1367
+ cropCommand,
1368
+ dataView.buffer,
1369
+ sendImmediately
1370
+ );
1371
+ this.#onContextStateUpdate(differences);
1372
+ }
1373
+ async setCropTop(cropTop: number, sendImmediately?: boolean) {
1374
+ await this.setCrop("top", cropTop, sendImmediately);
1375
+ }
1376
+ async setCropRight(cropRight: number, sendImmediately?: boolean) {
1377
+ await this.setCrop("right", cropRight, sendImmediately);
1378
+ }
1379
+ async setCropBottom(cropBottom: number, sendImmediately?: boolean) {
1380
+ await this.setCrop("bottom", cropBottom, sendImmediately);
1381
+ }
1382
+ async setCropLeft(cropLeft: number, sendImmediately?: boolean) {
1383
+ await this.setCrop("left", cropLeft, sendImmediately);
1384
+ }
1385
+ async clearCrop(sendImmediately?: boolean) {
1386
+ const differences = this.#contextStateHelper.update({
1387
+ cropTop: 0,
1388
+ cropRight: 0,
1389
+ cropBottom: 0,
1390
+ cropLeft: 0,
1391
+ });
1392
+ if (differences.length == 0) {
1393
+ return;
1394
+ }
1395
+ const commandType: DisplayContextCommandType = "clearCrop";
1396
+ const dataView = serializeContextCommand(this, { type: commandType });
1397
+ await this.#sendDisplayContextCommand(
1398
+ commandType,
1399
+ dataView?.buffer,
1400
+ sendImmediately
1401
+ );
1402
+ this.#onContextStateUpdate(differences);
1403
+ }
1404
+
1405
+ async setRotationCrop(
1406
+ cropDirection: DisplayCropDirection,
1407
+ crop: number,
1408
+ sendImmediately?: boolean
1409
+ ) {
1410
+ _console.assertEnumWithError(cropDirection, DisplayCropDirections);
1411
+ const cropCommand =
1412
+ DisplayRotationCropDirectionToCommandType[cropDirection];
1413
+ const cropKey = DisplayRotationCropDirectionToStateKey[cropDirection];
1414
+ const differences = this.#contextStateHelper.update({
1415
+ [cropKey]: crop,
1416
+ });
1417
+ if (differences.length == 0) {
1418
+ return;
1419
+ }
1420
+ // @ts-ignore
1421
+ const dataView = serializeContextCommand(this, {
1422
+ type: cropCommand,
1423
+ [cropKey]: crop,
1424
+ });
1425
+ if (!dataView) {
1426
+ return;
1427
+ }
1428
+ await this.#sendDisplayContextCommand(
1429
+ cropCommand,
1430
+ dataView.buffer,
1431
+ sendImmediately
1432
+ );
1433
+ this.#onContextStateUpdate(differences);
1434
+ }
1435
+ async setRotationCropTop(rotationCropTop: number, sendImmediately?: boolean) {
1436
+ await this.setRotationCrop("top", rotationCropTop, sendImmediately);
1437
+ }
1438
+ async setRotationCropRight(
1439
+ rotationCropRight: number,
1440
+ sendImmediately?: boolean
1441
+ ) {
1442
+ await this.setRotationCrop("right", rotationCropRight, sendImmediately);
1443
+ }
1444
+ async setRotationCropBottom(
1445
+ rotationCropBottom: number,
1446
+ sendImmediately?: boolean
1447
+ ) {
1448
+ await this.setRotationCrop("bottom", rotationCropBottom, sendImmediately);
1449
+ }
1450
+ async setRotationCropLeft(
1451
+ rotationCropLeft: number,
1452
+ sendImmediately?: boolean
1453
+ ) {
1454
+ await this.setRotationCrop("left", rotationCropLeft, sendImmediately);
1455
+ }
1456
+ async clearRotationCrop(sendImmediately?: boolean) {
1457
+ const differences = this.#contextStateHelper.update({
1458
+ rotationCropTop: 0,
1459
+ rotationCropRight: 0,
1460
+ rotationCropBottom: 0,
1461
+ rotationCropLeft: 0,
1462
+ });
1463
+ if (differences.length == 0) {
1464
+ return;
1465
+ }
1466
+ const commandType: DisplayContextCommandType = "clearRotationCrop";
1467
+ const dataView = serializeContextCommand(this, {
1468
+ type: commandType,
1469
+ });
1470
+ await this.#sendDisplayContextCommand(
1471
+ commandType,
1472
+ dataView?.buffer,
1473
+ sendImmediately
1474
+ );
1475
+ this.#onContextStateUpdate(differences);
1476
+ }
1477
+
1478
+ async selectBitmapColor(
1479
+ bitmapColorIndex: number,
1480
+ colorIndex: number,
1481
+ sendImmediately?: boolean
1482
+ ) {
1483
+ this.assertValidColorIndex(bitmapColorIndex);
1484
+ this.assertValidColorIndex(colorIndex);
1485
+ const bitmapColorIndices = this.contextState.bitmapColorIndices.slice();
1486
+ bitmapColorIndices[bitmapColorIndex] = colorIndex;
1487
+ const differences = this.#contextStateHelper.update({
1488
+ bitmapColorIndices,
1489
+ });
1490
+ if (differences.length == 0) {
1491
+ return;
1492
+ }
1493
+ const commandType: DisplayContextCommandType = "selectBitmapColor";
1494
+ const dataView = serializeContextCommand(this, {
1495
+ type: commandType,
1496
+ bitmapColorIndex,
1497
+ colorIndex,
1498
+ });
1499
+ if (!dataView) {
1500
+ return;
1501
+ }
1502
+ await this.#sendDisplayContextCommand(
1503
+ commandType,
1504
+ dataView.buffer,
1505
+ sendImmediately
1506
+ );
1507
+ this.#onContextStateUpdate(differences);
1508
+ }
1509
+ get bitmapColorIndices() {
1510
+ return this.contextState.bitmapColorIndices;
1511
+ }
1512
+ get bitmapColors() {
1513
+ return this.bitmapColorIndices.map((colorIndex) => this.colors[colorIndex]);
1514
+ }
1515
+ async selectBitmapColors(
1516
+ bitmapColorPairs: DisplayBitmapColorPair[],
1517
+ sendImmediately?: boolean
1518
+ ) {
1519
+ _console.assertRangeWithError(
1520
+ "bitmapColors",
1521
+ bitmapColorPairs.length,
1522
+ 1,
1523
+ this.numberOfColors
1524
+ );
1525
+ const bitmapColorIndices = this.contextState.bitmapColorIndices.slice();
1526
+ bitmapColorPairs.forEach(({ bitmapColorIndex, colorIndex }) => {
1527
+ this.assertValidColorIndex(bitmapColorIndex);
1528
+ this.assertValidColorIndex(colorIndex);
1529
+ bitmapColorIndices[bitmapColorIndex] = colorIndex;
1530
+ });
1531
+
1532
+ const differences = this.#contextStateHelper.update({
1533
+ bitmapColorIndices,
1534
+ });
1535
+ if (differences.length == 0) {
1536
+ return;
1537
+ }
1538
+ const commandType: DisplayContextCommandType = "selectBitmapColors";
1539
+ const dataView = serializeContextCommand(this, {
1540
+ type: commandType,
1541
+ bitmapColorPairs,
1542
+ });
1543
+ if (!dataView) {
1544
+ return;
1545
+ }
1546
+ await this.#sendDisplayContextCommand(
1547
+ commandType,
1548
+ dataView.buffer,
1549
+ sendImmediately
1550
+ );
1551
+ this.#onContextStateUpdate(differences);
1552
+ }
1553
+ async setBitmapColor(
1554
+ bitmapColorIndex: number,
1555
+ color: DisplayColorRGB | string,
1556
+ sendImmediately?: boolean
1557
+ ) {
1558
+ return this.setColor(
1559
+ this.bitmapColorIndices[bitmapColorIndex],
1560
+ color,
1561
+ sendImmediately
1562
+ );
1563
+ }
1564
+ async setBitmapColorOpacity(
1565
+ bitmapColorIndex: number,
1566
+ opacity: number,
1567
+ sendImmediately?: boolean
1568
+ ) {
1569
+ return this.setColorOpacity(
1570
+ this.bitmapColorIndices[bitmapColorIndex],
1571
+ opacity,
1572
+ sendImmediately
1573
+ );
1574
+ }
1575
+ async setBitmapScaleDirection(
1576
+ direction: DisplayScaleDirection,
1577
+ bitmapScale: number,
1578
+ sendImmediately?: boolean
1579
+ ) {
1580
+ bitmapScale = clamp(bitmapScale, minDisplayScale, maxDisplayScale);
1581
+ bitmapScale = roundScale(bitmapScale);
1582
+ const commandType = DisplayBitmapScaleDirectionToCommandType[direction];
1583
+ _console.log({ [commandType]: bitmapScale });
1584
+ const newState: PartialDisplayContextState = {};
1585
+ let command: DisplayContextCommand;
1586
+ switch (direction) {
1587
+ case "all":
1588
+ newState.bitmapScaleX = bitmapScale;
1589
+ newState.bitmapScaleY = bitmapScale;
1590
+ command = { type: "setBitmapScale", bitmapScale };
1591
+ break;
1592
+ case "x":
1593
+ newState.bitmapScaleX = bitmapScale;
1594
+ command = { type: "setBitmapScaleX", bitmapScaleX: bitmapScale };
1595
+ break;
1596
+ case "y":
1597
+ newState.bitmapScaleY = bitmapScale;
1598
+ command = { type: "setBitmapScaleY", bitmapScaleY: bitmapScale };
1599
+ break;
1600
+ }
1601
+ const differences = this.#contextStateHelper.update(newState);
1602
+ if (differences.length == 0) {
1603
+ return;
1604
+ }
1605
+ const dataView = serializeContextCommand(this, command);
1606
+ if (!dataView) {
1607
+ return;
1608
+ }
1609
+ await this.#sendDisplayContextCommand(
1610
+ commandType,
1611
+ dataView.buffer,
1612
+ sendImmediately
1613
+ );
1614
+
1615
+ this.#onContextStateUpdate(differences);
1616
+ }
1617
+ async setBitmapScaleX(bitmapScaleX: number, sendImmediately?: boolean) {
1618
+ return this.setBitmapScaleDirection("x", bitmapScaleX, sendImmediately);
1619
+ }
1620
+ async setBitmapScaleY(bitmapScaleY: number, sendImmediately?: boolean) {
1621
+ return this.setBitmapScaleDirection("y", bitmapScaleY, sendImmediately);
1622
+ }
1623
+ async setBitmapScale(bitmapScale: number, sendImmediately?: boolean) {
1624
+ return this.setBitmapScaleDirection("all", bitmapScale, sendImmediately);
1625
+ }
1626
+ async resetBitmapScale(sendImmediately?: boolean) {
1627
+ //return this.setBitmapScaleDirection("all", 1, sendImmediately);
1628
+
1629
+ const differences = this.#contextStateHelper.update({
1630
+ bitmapScaleX: 1,
1631
+ bitmapScaleY: 1,
1632
+ });
1633
+ if (differences.length == 0) {
1634
+ return;
1635
+ }
1636
+ const commandType: DisplayContextCommandType = "resetBitmapScale";
1637
+ const dataView = serializeContextCommand(this, {
1638
+ type: commandType,
1639
+ });
1640
+ await this.#sendDisplayContextCommand(
1641
+ commandType,
1642
+ dataView?.buffer,
1643
+ sendImmediately
1644
+ );
1645
+ this.#onContextStateUpdate(differences);
1646
+ }
1647
+
1648
+ async selectSpriteColor(
1649
+ spriteColorIndex: number,
1650
+ colorIndex: number,
1651
+ sendImmediately?: boolean
1652
+ ) {
1653
+ this.assertValidColorIndex(spriteColorIndex);
1654
+ this.assertValidColorIndex(colorIndex);
1655
+ const spriteColorIndices = this.contextState.spriteColorIndices.slice();
1656
+ spriteColorIndices[spriteColorIndex] = colorIndex;
1657
+ const differences = this.#contextStateHelper.update({
1658
+ spriteColorIndices,
1659
+ });
1660
+ if (differences.length == 0) {
1661
+ return;
1662
+ }
1663
+ const commandType: DisplayContextCommandType = "selectSpriteColor";
1664
+ const dataView = serializeContextCommand(this, {
1665
+ type: commandType,
1666
+ spriteColorIndex,
1667
+ colorIndex,
1668
+ });
1669
+ if (!dataView) {
1670
+ return;
1671
+ }
1672
+ await this.#sendDisplayContextCommand(
1673
+ commandType,
1674
+ dataView.buffer,
1675
+ sendImmediately
1676
+ );
1677
+ this.#onContextStateUpdate(differences);
1678
+ }
1679
+ get spriteColorIndices() {
1680
+ return this.contextState.spriteColorIndices;
1681
+ }
1682
+ get spriteColors() {
1683
+ return this.spriteColorIndices.map((colorIndex) => this.colors[colorIndex]);
1684
+ }
1685
+ async selectSpriteColors(
1686
+ spriteColorPairs: DisplaySpriteColorPair[],
1687
+ sendImmediately?: boolean
1688
+ ) {
1689
+ _console.assertRangeWithError(
1690
+ "spriteColors",
1691
+ spriteColorPairs.length,
1692
+ 1,
1693
+ this.numberOfColors
1694
+ );
1695
+ const spriteColorIndices = this.contextState.spriteColorIndices.slice();
1696
+ spriteColorPairs.forEach(({ spriteColorIndex, colorIndex }) => {
1697
+ this.assertValidColorIndex(spriteColorIndex);
1698
+ this.assertValidColorIndex(colorIndex);
1699
+ spriteColorIndices[spriteColorIndex] = colorIndex;
1700
+ });
1701
+
1702
+ const differences = this.#contextStateHelper.update({
1703
+ spriteColorIndices,
1704
+ });
1705
+ if (differences.length == 0) {
1706
+ return;
1707
+ }
1708
+ const commandType: DisplayContextCommandType = "selectSpriteColors";
1709
+ const dataView = serializeContextCommand(this, {
1710
+ type: commandType,
1711
+ spriteColorPairs,
1712
+ });
1713
+ if (!dataView) {
1714
+ return;
1715
+ }
1716
+ await this.#sendDisplayContextCommand(
1717
+ commandType,
1718
+ dataView.buffer,
1719
+ sendImmediately
1720
+ );
1721
+ this.#onContextStateUpdate(differences);
1722
+ }
1723
+ async setSpriteColor(
1724
+ spriteColorIndex: number,
1725
+ color: DisplayColorRGB | string,
1726
+ sendImmediately?: boolean
1727
+ ) {
1728
+ return this.setColor(
1729
+ this.spriteColorIndices[spriteColorIndex],
1730
+ color,
1731
+ sendImmediately
1732
+ );
1733
+ }
1734
+ async setSpriteColorOpacity(
1735
+ spriteColorIndex: number,
1736
+ opacity: number,
1737
+ sendImmediately?: boolean
1738
+ ) {
1739
+ return this.setColorOpacity(
1740
+ this.spriteColorIndices[spriteColorIndex],
1741
+ opacity,
1742
+ sendImmediately
1743
+ );
1744
+ }
1745
+
1746
+ async resetSpriteColors(sendImmediately?: boolean) {
1747
+ const spriteColorIndices = new Array(this.numberOfColors).fill(0);
1748
+ const differences = this.#contextStateHelper.update({
1749
+ spriteColorIndices,
1750
+ });
1751
+ if (differences.length == 0) {
1752
+ return;
1753
+ }
1754
+ const commandType: DisplayContextCommandType = "resetSpriteColors";
1755
+ const dataView = serializeContextCommand(this, {
1756
+ type: commandType,
1757
+ });
1758
+ await this.#sendDisplayContextCommand(
1759
+ commandType,
1760
+ dataView?.buffer,
1761
+ sendImmediately
1762
+ );
1763
+ this.#onContextStateUpdate(differences);
1764
+ }
1765
+
1766
+ async setSpriteScaleDirection(
1767
+ direction: DisplayScaleDirection,
1768
+ spriteScale: number,
1769
+ sendImmediately?: boolean
1770
+ ) {
1771
+ spriteScale = clamp(spriteScale, minDisplayScale, maxDisplayScale);
1772
+ spriteScale = roundScale(spriteScale);
1773
+ const commandType = DisplaySpriteScaleDirectionToCommandType[direction];
1774
+ _console.log({ [commandType]: spriteScale });
1775
+ const newState: PartialDisplayContextState = {};
1776
+ let command: DisplayContextCommand;
1777
+ switch (direction) {
1778
+ case "all":
1779
+ newState.spriteScaleX = spriteScale;
1780
+ newState.spriteScaleY = spriteScale;
1781
+ command = { type: "setSpriteScale", spriteScale };
1782
+ break;
1783
+ case "x":
1784
+ newState.spriteScaleX = spriteScale;
1785
+ command = { type: "setSpriteScaleX", spriteScaleX: spriteScale };
1786
+ break;
1787
+ case "y":
1788
+ newState.spriteScaleY = spriteScale;
1789
+ command = { type: "setSpriteScaleY", spriteScaleY: spriteScale };
1790
+ break;
1791
+ }
1792
+ const differences = this.#contextStateHelper.update(newState);
1793
+ if (differences.length == 0) {
1794
+ return;
1795
+ }
1796
+ const dataView = serializeContextCommand(this, command);
1797
+ if (!dataView) {
1798
+ return;
1799
+ }
1800
+ await this.#sendDisplayContextCommand(
1801
+ commandType,
1802
+ dataView.buffer,
1803
+ sendImmediately
1804
+ );
1805
+
1806
+ this.#onContextStateUpdate(differences);
1807
+ }
1808
+ async setSpriteScaleX(spriteScaleX: number, sendImmediately?: boolean) {
1809
+ return this.setSpriteScaleDirection("x", spriteScaleX, sendImmediately);
1810
+ }
1811
+ async setSpriteScaleY(spriteScaleY: number, sendImmediately?: boolean) {
1812
+ return this.setSpriteScaleDirection("y", spriteScaleY, sendImmediately);
1813
+ }
1814
+ async setSpriteScale(spriteScale: number, sendImmediately?: boolean) {
1815
+ return this.setSpriteScaleDirection("all", spriteScale, sendImmediately);
1816
+ }
1817
+ async resetSpriteScale(sendImmediately?: boolean) {
1818
+ //return this.setSpriteScaleDirection("all", 1, sendImmediately);
1819
+
1820
+ const differences = this.#contextStateHelper.update({
1821
+ spriteScaleX: 1,
1822
+ spriteScaleY: 1,
1823
+ });
1824
+ if (differences.length == 0) {
1825
+ return;
1826
+ }
1827
+ const commandType: DisplayContextCommandType = "resetSpriteScale";
1828
+ const dataView = serializeContextCommand(this, {
1829
+ type: commandType,
1830
+ });
1831
+ await this.#sendDisplayContextCommand(
1832
+ commandType,
1833
+ dataView?.buffer,
1834
+ sendImmediately
1835
+ );
1836
+ this.#onContextStateUpdate(differences);
1837
+ }
1838
+
1839
+ async setSpritesLineHeight(
1840
+ spritesLineHeight: number,
1841
+ sendImmediately?: boolean
1842
+ ) {
1843
+ this.assertValidLineWidth(spritesLineHeight);
1844
+ const differences = this.#contextStateHelper.update({
1845
+ spritesLineHeight,
1846
+ });
1847
+ if (differences.length == 0) {
1848
+ return;
1849
+ }
1850
+ const commandType: DisplayContextCommandType = "setSpritesLineHeight";
1851
+ const dataView = serializeContextCommand(this, {
1852
+ type: commandType,
1853
+ spritesLineHeight,
1854
+ });
1855
+ if (!dataView) {
1856
+ return;
1857
+ }
1858
+ await this.#sendDisplayContextCommand(
1859
+ commandType,
1860
+ dataView.buffer,
1861
+ sendImmediately
1862
+ );
1863
+ this.#onContextStateUpdate(differences);
1864
+ }
1865
+
1866
+ async setSpritesDirectionGeneric(
1867
+ direction: DisplayDirection,
1868
+ isOrthogonal: boolean,
1869
+ sendImmediately?: boolean
1870
+ ) {
1871
+ assertValidDirection(direction);
1872
+ const stateKey: DisplayContextStateKey = isOrthogonal
1873
+ ? "spritesLineDirection"
1874
+ : "spritesDirection";
1875
+ const commandType: DisplayContextCommandType = isOrthogonal
1876
+ ? "setSpritesLineDirection"
1877
+ : "setSpritesDirection";
1878
+
1879
+ const differences = this.#contextStateHelper.update({
1880
+ [stateKey]: direction,
1881
+ });
1882
+ if (differences.length == 0) {
1883
+ return;
1884
+ }
1885
+ // @ts-expect-error
1886
+ const dataView = serializeContextCommand(this, {
1887
+ type: commandType,
1888
+ [stateKey]: direction,
1889
+ });
1890
+ if (!dataView) {
1891
+ return;
1892
+ }
1893
+ await this.#sendDisplayContextCommand(
1894
+ commandType,
1895
+ dataView.buffer,
1896
+ sendImmediately
1897
+ );
1898
+ this.#onContextStateUpdate(differences);
1899
+ }
1900
+ async setSpritesDirection(
1901
+ spritesDirection: DisplayDirection,
1902
+ sendImmediately?: boolean
1903
+ ) {
1904
+ await this.setSpritesDirectionGeneric(
1905
+ spritesDirection,
1906
+ false,
1907
+ sendImmediately
1908
+ );
1909
+ }
1910
+ async setSpritesLineDirection(
1911
+ spritesLineDirection: DisplayDirection,
1912
+ sendImmediately?: boolean
1913
+ ) {
1914
+ await this.setSpritesDirectionGeneric(
1915
+ spritesLineDirection,
1916
+ true,
1917
+ sendImmediately
1918
+ );
1919
+ }
1920
+
1921
+ async setSpritesSpacingGeneric(
1922
+ spacing: number,
1923
+ isOrthogonal: boolean,
1924
+ sendImmediately?: boolean
1925
+ ) {
1926
+ const stateKey: DisplayContextStateKey = isOrthogonal
1927
+ ? "spritesLineSpacing"
1928
+ : "spritesSpacing";
1929
+ const commandType: DisplayContextCommandType = isOrthogonal
1930
+ ? "setSpritesLineSpacing"
1931
+ : "setSpritesSpacing";
1932
+
1933
+ const differences = this.#contextStateHelper.update({
1934
+ [stateKey]: spacing,
1935
+ });
1936
+ if (differences.length == 0) {
1937
+ return;
1938
+ }
1939
+ // @ts-expect-error
1940
+ const dataView = serializeContextCommand(this, {
1941
+ type: commandType,
1942
+ [stateKey]: spacing,
1943
+ });
1944
+ if (!dataView) {
1945
+ return;
1946
+ }
1947
+ await this.#sendDisplayContextCommand(
1948
+ commandType,
1949
+ dataView.buffer,
1950
+ sendImmediately
1951
+ );
1952
+ this.#onContextStateUpdate(differences);
1953
+ }
1954
+ async setSpritesSpacing(spritesSpacing: number, sendImmediately?: boolean) {
1955
+ await this.setSpritesSpacingGeneric(spritesSpacing, false, sendImmediately);
1956
+ }
1957
+ async setSpritesLineSpacing(
1958
+ spritesSpacing: number,
1959
+ sendImmediately?: boolean
1960
+ ) {
1961
+ await this.setSpritesSpacingGeneric(spritesSpacing, true, sendImmediately);
1962
+ }
1963
+
1964
+ async setSpritesAlignmentGeneric(
1965
+ alignment: DisplayAlignment,
1966
+ isOrthogonal: boolean,
1967
+ sendImmediately?: boolean
1968
+ ) {
1969
+ assertValidAlignment(alignment);
1970
+ const stateKey: DisplayContextStateKey = isOrthogonal
1971
+ ? "spritesLineAlignment"
1972
+ : "spritesAlignment";
1973
+ const commandType: DisplayContextCommandType = isOrthogonal
1974
+ ? "setSpritesLineAlignment"
1975
+ : "setSpritesAlignment";
1976
+ const differences = this.#contextStateHelper.update({
1977
+ [stateKey]: alignment,
1978
+ });
1979
+ if (differences.length == 0) {
1980
+ return;
1981
+ }
1982
+ // @ts-expect-error
1983
+ const dataView = serializeContextCommand(this, {
1984
+ type: commandType,
1985
+ [stateKey]: alignment,
1986
+ });
1987
+ if (!dataView) {
1988
+ return;
1989
+ }
1990
+ await this.#sendDisplayContextCommand(
1991
+ commandType,
1992
+ dataView.buffer,
1993
+ sendImmediately
1994
+ );
1995
+ this.#onContextStateUpdate(differences);
1996
+ }
1997
+ async setSpritesAlignment(
1998
+ spritesAlignment: DisplayAlignment,
1999
+ sendImmediately?: boolean
2000
+ ) {
2001
+ await this.setSpritesAlignmentGeneric(
2002
+ spritesAlignment,
2003
+ false,
2004
+ sendImmediately
2005
+ );
2006
+ }
2007
+ async setSpritesLineAlignment(
2008
+ spritesLineAlignment: DisplayAlignment,
2009
+ sendImmediately?: boolean
2010
+ ) {
2011
+ await this.setSpritesAlignmentGeneric(
2012
+ spritesLineAlignment,
2013
+ true,
2014
+ sendImmediately
2015
+ );
2016
+ }
2017
+
2018
+ async clearRect(
2019
+ x: number,
2020
+ y: number,
2021
+ width: number,
2022
+ height: number,
2023
+ sendImmediately?: boolean
2024
+ ) {
2025
+ const commandType: DisplayContextCommandType = "clearRect";
2026
+ const dataView = serializeContextCommand(this, {
2027
+ type: commandType,
2028
+ x,
2029
+ y,
2030
+ width,
2031
+ height,
2032
+ });
2033
+ if (!dataView) {
2034
+ return;
2035
+ }
2036
+ await this.#sendDisplayContextCommand(
2037
+ commandType,
2038
+ dataView.buffer,
2039
+ sendImmediately
2040
+ );
2041
+ }
2042
+ async drawRect(
2043
+ offsetX: number,
2044
+ offsetY: number,
2045
+ width: number,
2046
+ height: number,
2047
+ sendImmediately?: boolean
2048
+ ) {
2049
+ const commandType: DisplayContextCommandType = "drawRect";
2050
+ const dataView = serializeContextCommand(this, {
2051
+ type: commandType,
2052
+ offsetX,
2053
+ offsetY,
2054
+ width,
2055
+ height,
2056
+ });
2057
+ if (!dataView) {
2058
+ return;
2059
+ }
2060
+ await this.#sendDisplayContextCommand(
2061
+ commandType,
2062
+ dataView.buffer,
2063
+ sendImmediately
2064
+ );
2065
+ }
2066
+ async drawRoundRect(
2067
+ offsetX: number,
2068
+ offsetY: number,
2069
+ width: number,
2070
+ height: number,
2071
+ borderRadius: number,
2072
+ sendImmediately?: boolean
2073
+ ) {
2074
+ const commandType: DisplayContextCommandType = "drawRoundRect";
2075
+ const dataView = serializeContextCommand(this, {
2076
+ type: commandType,
2077
+ offsetX,
2078
+ offsetY,
2079
+ width,
2080
+ height,
2081
+ borderRadius,
2082
+ });
2083
+ if (!dataView) {
2084
+ return;
2085
+ }
2086
+ await this.#sendDisplayContextCommand(
2087
+ commandType,
2088
+ dataView.buffer,
2089
+ sendImmediately
2090
+ );
2091
+ }
2092
+ async drawCircle(
2093
+ offsetX: number,
2094
+ offsetY: number,
2095
+ radius: number,
2096
+ sendImmediately?: boolean
2097
+ ) {
2098
+ const commandType: DisplayContextCommandType = "drawCircle";
2099
+ const dataView = serializeContextCommand(this, {
2100
+ type: commandType,
2101
+ offsetX,
2102
+ offsetY,
2103
+ radius,
2104
+ });
2105
+ if (!dataView) {
2106
+ return;
2107
+ }
2108
+ await this.#sendDisplayContextCommand(
2109
+ commandType,
2110
+ dataView.buffer,
2111
+ sendImmediately
2112
+ );
2113
+ }
2114
+ async drawEllipse(
2115
+ offsetX: number,
2116
+ offsetY: number,
2117
+ radiusX: number,
2118
+ radiusY: number,
2119
+ sendImmediately?: boolean
2120
+ ) {
2121
+ const commandType: DisplayContextCommandType = "drawEllipse";
2122
+ const dataView = serializeContextCommand(this, {
2123
+ type: commandType,
2124
+ offsetX,
2125
+ offsetY,
2126
+ radiusX,
2127
+ radiusY,
2128
+ });
2129
+ if (!dataView) {
2130
+ return;
2131
+ }
2132
+ await this.#sendDisplayContextCommand(
2133
+ commandType,
2134
+ dataView.buffer,
2135
+ sendImmediately
2136
+ );
2137
+ }
2138
+ async drawRegularPolygon(
2139
+ offsetX: number,
2140
+ offsetY: number,
2141
+ radius: number,
2142
+ numberOfSides: number,
2143
+ sendImmediately?: boolean
2144
+ ) {
2145
+ const commandType: DisplayContextCommandType = "drawRegularPolygon";
2146
+ const dataView = serializeContextCommand(this, {
2147
+ type: commandType,
2148
+ offsetX,
2149
+ offsetY,
2150
+ radius,
2151
+ numberOfSides,
2152
+ });
2153
+ if (!dataView) {
2154
+ return;
2155
+ }
2156
+ await this.#sendDisplayContextCommand(
2157
+ commandType,
2158
+ dataView.buffer,
2159
+ sendImmediately
2160
+ );
2161
+ }
2162
+ async drawPolygon(points: Vector2[], sendImmediately?: boolean) {
2163
+ _console.assertRangeWithError("numberOfPoints", points.length, 2, 255);
2164
+ const commandType: DisplayContextCommandType = "drawPolygon";
2165
+ const dataView = serializeContextCommand(this, {
2166
+ type: commandType,
2167
+ points,
2168
+ });
2169
+ if (!dataView) {
2170
+ return;
2171
+ }
2172
+ await this.#sendDisplayContextCommand(
2173
+ commandType,
2174
+ dataView.buffer,
2175
+ sendImmediately
2176
+ );
2177
+ }
2178
+
2179
+ async drawWireframe(wireframe: DisplayWireframe, sendImmediately?: boolean) {
2180
+ assertValidWireframe(wireframe);
2181
+ const trimmedWireframe = trimWireframe(wireframe);
2182
+ const commandType: DisplayContextCommandType = "drawWireframe";
2183
+ const dataView = serializeContextCommand(this, {
2184
+ type: commandType,
2185
+ wireframe: trimmedWireframe,
2186
+ });
2187
+ if (!dataView) {
2188
+ return;
2189
+ }
2190
+ if (dataView.byteLength > this.#maxCommandDataLength) {
2191
+ _console.error(
2192
+ `wireframe data ${dataView.byteLength} too large (max ${
2193
+ this.#maxCommandDataLength
2194
+ })`
2195
+ );
2196
+ return;
2197
+ }
2198
+ await this.#sendDisplayContextCommand(
2199
+ commandType,
2200
+ dataView.buffer,
2201
+ sendImmediately
2202
+ );
2203
+ }
2204
+
2205
+ async drawCurve(
2206
+ curveType: DisplayBezierCurveType,
2207
+ controlPoints: Vector2[],
2208
+ sendImmediately?: boolean
2209
+ ) {
2210
+ assertValidNumberOfControlPoints(curveType, controlPoints);
2211
+ const commandType: DisplayContextCommandType =
2212
+ curveType == "cubic"
2213
+ ? "drawCubicBezierCurve"
2214
+ : "drawQuadraticBezierCurve";
2215
+ const dataView = serializeContextCommand(this, {
2216
+ type: commandType,
2217
+ controlPoints,
2218
+ });
2219
+ if (!dataView) {
2220
+ return;
2221
+ }
2222
+ await this.#sendDisplayContextCommand(
2223
+ commandType,
2224
+ dataView.buffer,
2225
+ sendImmediately
2226
+ );
2227
+ }
2228
+ async drawCurves(
2229
+ curveType: DisplayBezierCurveType,
2230
+ controlPoints: Vector2[],
2231
+ sendImmediately?: boolean
2232
+ ) {
2233
+ assertValidPathNumberOfControlPoints(curveType, controlPoints);
2234
+ const commandType: DisplayContextCommandType =
2235
+ curveType == "cubic"
2236
+ ? "drawCubicBezierCurves"
2237
+ : "drawQuadraticBezierCurves";
2238
+ const dataView = serializeContextCommand(this, {
2239
+ type: commandType,
2240
+ controlPoints,
2241
+ });
2242
+ if (!dataView) {
2243
+ return;
2244
+ }
2245
+ if (dataView.byteLength > this.#maxCommandDataLength) {
2246
+ _console.error(
2247
+ `curve data ${dataView.byteLength} too large (max ${
2248
+ this.#maxCommandDataLength
2249
+ })`
2250
+ );
2251
+ // FILL - split into multiple curves
2252
+ return;
2253
+ }
2254
+ await this.#sendDisplayContextCommand(
2255
+ commandType,
2256
+ dataView.buffer,
2257
+ sendImmediately
2258
+ );
2259
+ }
2260
+
2261
+ async drawQuadraticBezierCurve(
2262
+ controlPoints: Vector2[],
2263
+ sendImmediately?: boolean
2264
+ ) {
2265
+ await this.drawCurve("quadratic", controlPoints, sendImmediately);
2266
+ }
2267
+ async drawQuadraticBezierCurves(
2268
+ controlPoints: Vector2[],
2269
+ sendImmediately?: boolean
2270
+ ) {
2271
+ await this.drawCurves("quadratic", controlPoints, sendImmediately);
2272
+ }
2273
+
2274
+ async drawCubicBezierCurve(
2275
+ controlPoints: Vector2[],
2276
+ sendImmediately?: boolean
2277
+ ) {
2278
+ await this.drawCurve("cubic", controlPoints, sendImmediately);
2279
+ }
2280
+ async drawCubicBezierCurves(
2281
+ controlPoints: Vector2[],
2282
+ sendImmediately?: boolean
2283
+ ) {
2284
+ await this.drawCurves("cubic", controlPoints, sendImmediately);
2285
+ }
2286
+
2287
+ async _drawPath(
2288
+ isClosed: boolean,
2289
+ curves: DisplayBezierCurve[],
2290
+ sendImmediately?: boolean
2291
+ ) {
2292
+ assertValidPath(curves);
2293
+
2294
+ const commandType: DisplayContextCommandType = isClosed
2295
+ ? "drawClosedPath"
2296
+ : "drawPath";
2297
+ const dataView = serializeContextCommand(this, {
2298
+ type: commandType,
2299
+ curves,
2300
+ });
2301
+ if (!dataView) {
2302
+ return;
2303
+ }
2304
+ if (dataView.byteLength > this.#maxCommandDataLength) {
2305
+ _console.error(
2306
+ `path data ${dataView.byteLength} too large (max ${
2307
+ this.#maxCommandDataLength
2308
+ })`
2309
+ );
2310
+ // FILL - split into multiple paths
2311
+ return;
2312
+ }
2313
+ await this.#sendDisplayContextCommand(
2314
+ commandType,
2315
+ dataView.buffer,
2316
+ sendImmediately
2317
+ );
2318
+ }
2319
+ async drawPath(curves: DisplayBezierCurve[], sendImmediately?: boolean) {
2320
+ await this._drawPath(false, curves, sendImmediately);
2321
+ }
2322
+ async drawClosedPath(
2323
+ curves: DisplayBezierCurve[],
2324
+ sendImmediately?: boolean
2325
+ ) {
2326
+ await this._drawPath(true, curves, sendImmediately);
2327
+ }
2328
+
2329
+ async drawSegment(
2330
+ startX: number,
2331
+ startY: number,
2332
+ endX: number,
2333
+ endY: number,
2334
+ sendImmediately?: boolean
2335
+ ) {
2336
+ const commandType: DisplayContextCommandType = "drawSegment";
2337
+ const dataView = serializeContextCommand(this, {
2338
+ type: commandType,
2339
+ startX,
2340
+ startY,
2341
+ endX,
2342
+ endY,
2343
+ });
2344
+ if (!dataView) {
2345
+ return;
2346
+ }
2347
+ await this.#sendDisplayContextCommand(
2348
+ commandType,
2349
+ dataView.buffer,
2350
+ sendImmediately
2351
+ );
2352
+ }
2353
+ async drawSegments(points: Vector2[], sendImmediately?: boolean) {
2354
+ _console.assertRangeWithError("numberOfPoints", points.length, 2, 255);
2355
+ const commandType: DisplayContextCommandType = "drawSegments";
2356
+ const dataView = serializeContextCommand(this, {
2357
+ type: commandType,
2358
+ points,
2359
+ });
2360
+ if (!dataView) {
2361
+ return;
2362
+ }
2363
+ if (dataView.byteLength > this.#maxCommandDataLength) {
2364
+ const mid = Math.floor(points.length / 2);
2365
+ const firstHalf = points.slice(0, mid + 1);
2366
+ const secondHalf = points.slice(mid);
2367
+ _console.log({ firstHalf, secondHalf });
2368
+ _console.log("sending first half", firstHalf);
2369
+ await this.drawSegments(firstHalf, false);
2370
+ _console.log("sending second half", secondHalf);
2371
+ await this.drawSegments(secondHalf, sendImmediately);
2372
+ return;
2373
+ }
2374
+ await this.#sendDisplayContextCommand(
2375
+ commandType,
2376
+ dataView.buffer,
2377
+ sendImmediately
2378
+ );
2379
+ }
2380
+
2381
+ async drawArc(
2382
+ offsetX: number,
2383
+ offsetY: number,
2384
+ radius: number,
2385
+ startAngle: number,
2386
+ angleOffset: number,
2387
+ isRadians?: boolean,
2388
+ sendImmediately?: boolean
2389
+ ) {
2390
+ const commandType: DisplayContextCommandType = "drawArc";
2391
+ const dataView = serializeContextCommand(this, {
2392
+ type: commandType,
2393
+ offsetX,
2394
+ offsetY,
2395
+ radius,
2396
+ startAngle,
2397
+ angleOffset,
2398
+ isRadians,
2399
+ });
2400
+ if (!dataView) {
2401
+ return;
2402
+ }
2403
+ await this.#sendDisplayContextCommand(
2404
+ commandType,
2405
+ dataView.buffer,
2406
+ sendImmediately
2407
+ );
2408
+ }
2409
+ async drawArcEllipse(
2410
+ offsetX: number,
2411
+ offsetY: number,
2412
+ radiusX: number,
2413
+ radiusY: number,
2414
+ startAngle: number,
2415
+ angleOffset: number,
2416
+ isRadians?: boolean,
2417
+ sendImmediately?: boolean
2418
+ ) {
2419
+ const commandType: DisplayContextCommandType = "drawArcEllipse";
2420
+ const dataView = serializeContextCommand(this, {
2421
+ type: commandType,
2422
+ offsetX,
2423
+ offsetY,
2424
+ radiusX,
2425
+ radiusY,
2426
+ startAngle,
2427
+ angleOffset,
2428
+ isRadians,
2429
+ });
2430
+ if (!dataView) {
2431
+ return;
2432
+ }
2433
+ await this.#sendDisplayContextCommand(
2434
+ commandType,
2435
+ dataView.buffer,
2436
+ sendImmediately
2437
+ );
2438
+ }
2439
+
2440
+ assertValidNumberOfColors(numberOfColors: number) {
2441
+ _console.assertRangeWithError(
2442
+ "numberOfColors",
2443
+ numberOfColors,
2444
+ 2,
2445
+ this.numberOfColors
2446
+ );
2447
+ }
2448
+
2449
+ assertValidBitmap(bitmap: DisplayBitmap, checkSize?: boolean) {
2450
+ this.assertValidNumberOfColors(bitmap.numberOfColors);
2451
+ assertValidBitmapPixels(bitmap);
2452
+ if (checkSize) {
2453
+ this.#assertValidBitmapSize(bitmap);
2454
+ }
2455
+ }
2456
+ #assertValidBitmapSize(bitmap: DisplayBitmap) {
2457
+ const pixelDataLength = getBitmapNumberOfBytes(bitmap);
2458
+ _console.assertRangeWithError(
2459
+ "bitmap.pixels.length",
2460
+ pixelDataLength,
2461
+ 1,
2462
+ this.#maxCommandDataLength - drawBitmapHeaderLength
2463
+ );
2464
+ }
2465
+ async drawBitmap(
2466
+ offsetX: number,
2467
+ offsetY: number,
2468
+ bitmap: DisplayBitmap,
2469
+ sendImmediately?: boolean
2470
+ ) {
2471
+ this.assertValidBitmap(bitmap, true);
2472
+ const commandType: DisplayContextCommandType = "drawBitmap";
2473
+ const dataView = serializeContextCommand(this, {
2474
+ type: commandType,
2475
+ offsetX,
2476
+ offsetY,
2477
+ bitmap,
2478
+ });
2479
+ if (!dataView) {
2480
+ return;
2481
+ }
2482
+ await this.#sendDisplayContextCommand(
2483
+ commandType,
2484
+ dataView.buffer,
2485
+ sendImmediately
2486
+ );
2487
+ }
2488
+
2489
+ async imageToBitmap(
2490
+ image: HTMLImageElement,
2491
+ width: number,
2492
+ height: number,
2493
+ numberOfColors?: number
2494
+ ) {
2495
+ return imageToBitmap(
2496
+ image,
2497
+ width,
2498
+ height,
2499
+ this.colors,
2500
+ this.bitmapColorIndices,
2501
+ numberOfColors
2502
+ );
2503
+ }
2504
+ async quantizeImage(
2505
+ image: HTMLImageElement,
2506
+ width: number,
2507
+ height: number,
2508
+ numberOfColors: number
2509
+ ) {
2510
+ return quantizeImage(image, width, height, numberOfColors);
2511
+ }
2512
+ async resizeAndQuantizeImage(
2513
+ image: HTMLImageElement,
2514
+ width: number,
2515
+ height: number,
2516
+ numberOfColors: number,
2517
+ colors?: string[]
2518
+ ) {
2519
+ return resizeAndQuantizeImage(image, width, height, numberOfColors, colors);
2520
+ }
2521
+
2522
+ // CONTEXT COMMANDS
2523
+
2524
+ async runContextCommand(
2525
+ command: DisplayContextCommand,
2526
+ sendImmediately?: boolean
2527
+ ) {
2528
+ return runDisplayContextCommand(this, command, sendImmediately);
2529
+ }
2530
+ async runContextCommands(
2531
+ commands: DisplayContextCommand[],
2532
+ sendImmediately?: boolean
2533
+ ) {
2534
+ return runDisplayContextCommands(this, commands, sendImmediately);
2535
+ }
2536
+
2537
+ #isReady = true;
2538
+ get isReady() {
2539
+ return this.isAvailable && this.#isReady;
2540
+ }
2541
+ #lastReadyTime = 0;
2542
+ #lastShowRequestTime = 0;
2543
+ #minReadyInterval = 100; // Forced delay due to Frame's fpga timing...
2544
+ #waitBeforeReady = false;
2545
+ async #parseDisplayReady(dataView: DataView) {
2546
+ const now = Date.now();
2547
+ const timeSinceLastDraw = now - this.#lastShowRequestTime;
2548
+ const timeSinceLastReady = now - this.#lastReadyTime;
2549
+ //_console.log(`${timeSinceLastReady}ms since last render`);
2550
+ _console.log(`${timeSinceLastDraw}ms draw time`);
2551
+ if (this.#waitBeforeReady && timeSinceLastReady < this.#minReadyInterval) {
2552
+ const timeToWait = this.#minReadyInterval - timeSinceLastReady;
2553
+ _console.log(`waiting ${timeToWait}ms`);
2554
+ await wait(timeToWait);
2555
+ }
2556
+ this.#isReady = true;
2557
+ this.#lastReadyTime = Date.now();
2558
+ this.#dispatchEvent("displayReady", {});
2559
+ }
2560
+
2561
+ // SPRITE SHEET
2562
+ #spriteSheets: Record<string, DisplaySpriteSheet> = {};
2563
+ #spriteSheetIndices: Record<string, number> = {};
2564
+ get spriteSheets() {
2565
+ return this.#spriteSheets;
2566
+ }
2567
+ get spriteSheetIndices() {
2568
+ return this.#spriteSheetIndices;
2569
+ }
2570
+ async #setSpriteSheetName(
2571
+ spriteSheetName: string,
2572
+ sendImmediately?: boolean
2573
+ ) {
2574
+ _console.assertTypeWithError(spriteSheetName, "string");
2575
+ _console.assertRangeWithError(
2576
+ "newName",
2577
+ spriteSheetName.length,
2578
+ MinSpriteSheetNameLength,
2579
+ MaxSpriteSheetNameLength
2580
+ );
2581
+ const setSpriteSheetNameData = textEncoder.encode(spriteSheetName);
2582
+ _console.log({ setSpriteSheetNameData });
2583
+
2584
+ const promise = this.waitForEvent("getSpriteSheetName");
2585
+ this.sendMessage(
2586
+ [{ type: "setSpriteSheetName", data: setSpriteSheetNameData.buffer }],
2587
+ sendImmediately
2588
+ );
2589
+ await promise;
2590
+ }
2591
+ #pendingSpriteSheet?: DisplaySpriteSheet;
2592
+ get pendingSpriteSheet() {
2593
+ return this.#pendingSpriteSheet;
2594
+ }
2595
+ #pendingSpriteSheetName?: string;
2596
+ get pendingSpriteSheetName() {
2597
+ return this.#pendingSpriteSheetName;
2598
+ }
2599
+ #updateSpriteSheetName(updatedSpriteSheetName: string) {
2600
+ _console.assertTypeWithError(updatedSpriteSheetName, "string");
2601
+ this.#pendingSpriteSheetName = updatedSpriteSheetName;
2602
+ _console.log({ updatedSpriteSheetName: this.#pendingSpriteSheetName });
2603
+ this.#dispatchEvent("getSpriteSheetName", {
2604
+ spriteSheetName: this.#pendingSpriteSheetName,
2605
+ });
2606
+ }
2607
+ sendFile!: SendFileCallback;
2608
+ serializeSpriteSheet(spriteSheet: DisplaySpriteSheet): ArrayBuffer {
2609
+ return serializeSpriteSheet(this, spriteSheet);
2610
+ }
2611
+ async uploadSpriteSheet(spriteSheet: DisplaySpriteSheet) {
2612
+ spriteSheet = structuredClone(spriteSheet);
2613
+ this.#pendingSpriteSheet = spriteSheet;
2614
+ const buffer = this.serializeSpriteSheet(this.#pendingSpriteSheet);
2615
+ await this.#setSpriteSheetName(this.#pendingSpriteSheet.name);
2616
+ const promise = this.waitForEvent("displaySpriteSheetUploadComplete");
2617
+ this.sendFile("spriteSheet", buffer, true);
2618
+ await promise;
2619
+ }
2620
+ async uploadSpriteSheets(spriteSheets: DisplaySpriteSheet[]) {
2621
+ for (const spriteSheet of spriteSheets) {
2622
+ await this.uploadSpriteSheet(spriteSheet);
2623
+ }
2624
+ }
2625
+ assertLoadedSpriteSheet(spriteSheetName: string) {
2626
+ assertLoadedSpriteSheet(this, spriteSheetName);
2627
+ }
2628
+ assertSelectedSpriteSheet(spriteSheetName: string) {
2629
+ assertSelectedSpriteSheet(this, spriteSheetName);
2630
+ }
2631
+ assertAnySelectedSpriteSheet() {
2632
+ assertAnySelectedSpriteSheet(this);
2633
+ }
2634
+ assertSprite(spriteName: string) {
2635
+ return assertSprite(this, spriteName);
2636
+ }
2637
+ getSprite(spriteName: string): DisplaySprite | undefined {
2638
+ return getSprite(this, spriteName);
2639
+ }
2640
+ getSpriteSheetPalette(
2641
+ paletteName: string
2642
+ ): DisplaySpriteSheetPalette | undefined {
2643
+ return getSpriteSheetPalette(this, paletteName);
2644
+ }
2645
+ getSpriteSheetPaletteSwap(
2646
+ paletteSwapName: string
2647
+ ): DisplaySpriteSheetPaletteSwap | undefined {
2648
+ return getSpriteSheetPaletteSwap(this, paletteSwapName);
2649
+ }
2650
+ getSpritePaletteSwap(
2651
+ spriteName: string,
2652
+ paletteSwapName: string
2653
+ ): DisplaySpritePaletteSwap | undefined {
2654
+ return getSpritePaletteSwap(this, spriteName, paletteSwapName);
2655
+ }
2656
+
2657
+ get selectedSpriteSheet() {
2658
+ if (this.contextState.spriteSheetName) {
2659
+ return this.#spriteSheets[this.contextState.spriteSheetName];
2660
+ }
2661
+ }
2662
+ get selectedSpriteSheetName() {
2663
+ return this.selectedSpriteSheet?.name;
2664
+ }
2665
+ async selectSpriteSheet(spriteSheetName: string, sendImmediately?: boolean) {
2666
+ this.assertLoadedSpriteSheet(spriteSheetName);
2667
+ const differences = this.#contextStateHelper.update({
2668
+ spriteSheetName,
2669
+ });
2670
+ if (differences.length == 0) {
2671
+ return;
2672
+ }
2673
+ const spriteSheetIndex = this.spriteSheetIndices[spriteSheetName];
2674
+ const commandType: DisplayContextCommandType = "selectSpriteSheet";
2675
+ const dataView = serializeContextCommand(this, {
2676
+ type: commandType,
2677
+ spriteSheetIndex,
2678
+ });
2679
+ if (!dataView) {
2680
+ return;
2681
+ }
2682
+ await this.#sendDisplayContextCommand(
2683
+ commandType,
2684
+ dataView.buffer,
2685
+ sendImmediately
2686
+ );
2687
+ this.#onContextStateUpdate(differences);
2688
+ }
2689
+ async drawSprite(
2690
+ offsetX: number,
2691
+ offsetY: number,
2692
+ spriteName: string,
2693
+ sendImmediately?: boolean
2694
+ ) {
2695
+ _console.assertWithError(
2696
+ this.selectedSpriteSheet,
2697
+ "no spriteSheet selected"
2698
+ );
2699
+ let spriteIndex = this.selectedSpriteSheet!.sprites.findIndex(
2700
+ (sprite) => sprite.name == spriteName
2701
+ );
2702
+ _console.assertWithError(
2703
+ spriteIndex != -1,
2704
+ `sprite "${spriteName}" not found`
2705
+ );
2706
+ spriteIndex = spriteIndex!;
2707
+ const commandType: DisplayContextCommandType = "drawSprite";
2708
+ const dataView = serializeContextCommand(this, {
2709
+ type: commandType,
2710
+ offsetX,
2711
+ offsetY,
2712
+ spriteIndex,
2713
+ use2Bytes: this.selectedSpriteSheet!.sprites.length > 255,
2714
+ });
2715
+ if (!dataView) {
2716
+ return;
2717
+ }
2718
+ await this.#sendDisplayContextCommand(
2719
+ commandType,
2720
+ dataView.buffer,
2721
+ sendImmediately
2722
+ );
2723
+ }
2724
+
2725
+ async drawSprites(
2726
+ offsetX: number,
2727
+ offsetY: number,
2728
+ spriteLines: DisplaySpriteLines,
2729
+ sendImmediately?: boolean
2730
+ ) {
2731
+ const spriteSerializedLines: DisplaySpriteSerializedLines = [];
2732
+ spriteLines.forEach((spriteLine) => {
2733
+ const serializedLine: DisplaySpriteSerializedLine = [];
2734
+ spriteLine.forEach((spriteSubLine) => {
2735
+ this.assertLoadedSpriteSheet(spriteSubLine.spriteSheetName);
2736
+ const spriteSheet = this.spriteSheets[spriteSubLine.spriteSheetName];
2737
+ const spriteSheetIndex = this.spriteSheetIndices[spriteSheet.name];
2738
+ const serializedSubLine: DisplaySpriteSerializedSubLine = {
2739
+ spriteSheetIndex,
2740
+ spriteIndices: [],
2741
+ use2Bytes: spriteSheet.sprites.length > 255,
2742
+ };
2743
+ spriteSubLine.spriteNames.forEach((spriteName) => {
2744
+ let spriteIndex = spriteSheet.sprites.findIndex(
2745
+ (sprite) => sprite.name == spriteName
2746
+ );
2747
+ _console.assertWithError(
2748
+ spriteIndex != -1,
2749
+ `sprite "${spriteName}" not found`
2750
+ );
2751
+ spriteIndex = spriteIndex!;
2752
+ serializedSubLine.spriteIndices.push(spriteIndex);
2753
+ });
2754
+ serializedLine.push(serializedSubLine);
2755
+ });
2756
+ spriteSerializedLines.push(serializedLine);
2757
+ });
2758
+ _console.log("spriteSerializedLines", spriteSerializedLines);
2759
+ const commandType: DisplayContextCommandType = "drawSprites";
2760
+ const dataView = serializeContextCommand(this, {
2761
+ type: commandType,
2762
+ offsetX,
2763
+ offsetY,
2764
+ spriteSerializedLines: spriteSerializedLines,
2765
+ });
2766
+ if (!dataView) {
2767
+ return;
2768
+ }
2769
+ await this.#sendDisplayContextCommand(
2770
+ commandType,
2771
+ dataView.buffer,
2772
+ sendImmediately
2773
+ );
2774
+ }
2775
+
2776
+ async drawSpritesString(
2777
+ offsetX: number,
2778
+ offsetY: number,
2779
+ string: string,
2780
+ requireAll?: boolean,
2781
+ maxLineBreadth?: number,
2782
+ separators?: string[],
2783
+ sendImmediately?: boolean
2784
+ ) {
2785
+ const spriteLines = this.stringToSpriteLines(
2786
+ string,
2787
+ requireAll,
2788
+ maxLineBreadth,
2789
+ separators
2790
+ );
2791
+ await this.drawSprites(offsetX, offsetY, spriteLines, sendImmediately);
2792
+ }
2793
+ stringToSpriteLines(
2794
+ string: string,
2795
+ requireAll?: boolean,
2796
+ maxLineBreadth?: number,
2797
+ separators?: string[]
2798
+ ): DisplaySpriteLines {
2799
+ return stringToSpriteLines(
2800
+ string,
2801
+ this.spriteSheets,
2802
+ this.contextState,
2803
+ requireAll,
2804
+ maxLineBreadth,
2805
+ separators
2806
+ );
2807
+ }
2808
+
2809
+ async drawSpriteFromSpriteSheet(
2810
+ offsetX: number,
2811
+ offsetY: number,
2812
+ spriteName: string,
2813
+ spriteSheet: DisplaySpriteSheet,
2814
+ paletteName?: string,
2815
+ sendImmediately?: boolean
2816
+ ) {
2817
+ return drawSpriteFromSpriteSheet(
2818
+ this,
2819
+ offsetX,
2820
+ offsetY,
2821
+ spriteName,
2822
+ spriteSheet,
2823
+ paletteName,
2824
+ sendImmediately
2825
+ );
2826
+ }
2827
+
2828
+ #parseSpriteSheetIndex(dataView: DataView) {
2829
+ const spriteSheetIndex = dataView.getUint8(0);
2830
+ _console.log({
2831
+ pendingSpriteSheet: this.#pendingSpriteSheet,
2832
+ spriteSheetName: this.#pendingSpriteSheetName,
2833
+ spriteSheetIndex,
2834
+ });
2835
+ if (this.isServerSide) {
2836
+ return;
2837
+ }
2838
+ _console.assertWithError(
2839
+ this.#pendingSpriteSheetName != undefined,
2840
+ "expected spriteSheetName when receiving spriteSheetIndex"
2841
+ );
2842
+ _console.assertWithError(
2843
+ this.#pendingSpriteSheet != undefined,
2844
+ "expected pendingSpriteSheet when receiving spriteSheetIndex"
2845
+ );
2846
+ this.#spriteSheets[this.#pendingSpriteSheetName!] =
2847
+ this.#pendingSpriteSheet!;
2848
+ this.#spriteSheetIndices[this.#pendingSpriteSheetName!] = spriteSheetIndex;
2849
+ this.#dispatchEvent("displaySpriteSheetUploadComplete", {
2850
+ spriteSheetName: this.#pendingSpriteSheetName!,
2851
+ spriteSheet: this.#pendingSpriteSheet!,
2852
+ });
2853
+ this.#pendingSpriteSheet = undefined;
2854
+ }
2855
+
2856
+ // MESSAGE
2857
+ parseMessage(messageType: DisplayMessageType, dataView: DataView) {
2858
+ _console.log({ messageType, dataView });
2859
+
2860
+ switch (messageType) {
2861
+ case "isDisplayAvailable":
2862
+ this.#parseIsDisplayAvailable(dataView);
2863
+ break;
2864
+ case "displayStatus":
2865
+ this.#parseDisplayStatus(dataView);
2866
+ break;
2867
+ case "displayInformation":
2868
+ this.#parseDisplayInformation(dataView);
2869
+ break;
2870
+ case "getDisplayBrightness":
2871
+ case "setDisplayBrightness":
2872
+ this.#parseDisplayBrightness(dataView);
2873
+ break;
2874
+ case "displayReady":
2875
+ this.#parseDisplayReady(dataView);
2876
+ break;
2877
+ case "getSpriteSheetName":
2878
+ case "setSpriteSheetName":
2879
+ const spriteSheetName = textDecoder.decode(dataView.buffer);
2880
+ _console.log({ spriteSheetName });
2881
+ this.#updateSpriteSheetName(spriteSheetName);
2882
+ break;
2883
+ case "spriteSheetIndex":
2884
+ this.#parseSpriteSheetIndex(dataView);
2885
+ break;
2886
+ default:
2887
+ throw Error(`uncaught messageType ${messageType}`);
2888
+ }
2889
+ }
2890
+
2891
+ // SPRITE SHEET PALETTES
2892
+
2893
+ assertSpriteSheetPalette(paletteName: string) {
2894
+ assertSpriteSheetPalette(this, paletteName);
2895
+ }
2896
+ assertSpriteSheetPaletteSwap(paletteSwapName: string) {
2897
+ assertSpriteSheetPaletteSwap(this, paletteSwapName);
2898
+ }
2899
+ assertSpritePaletteSwap(spriteName: string, paletteSwapName: string) {
2900
+ assertSpritePaletteSwap(this, spriteName, paletteSwapName);
2901
+ }
2902
+ async selectSpriteSheetPalette(
2903
+ paletteName: string,
2904
+ offset?: number,
2905
+ sendImmediately?: boolean
2906
+ ) {
2907
+ await selectSpriteSheetPalette(this, paletteName, offset, sendImmediately);
2908
+ }
2909
+ async selectSpriteSheetPaletteSwap(
2910
+ paletteSwapName: string,
2911
+ offset?: number,
2912
+ sendImmediately?: boolean
2913
+ ) {
2914
+ await selectSpriteSheetPaletteSwap(
2915
+ this,
2916
+ paletteSwapName,
2917
+ offset,
2918
+ sendImmediately
2919
+ );
2920
+ }
2921
+ async selectSpritePaletteSwap(
2922
+ spriteName: string,
2923
+ paletteSwapName: string,
2924
+ offset?: number,
2925
+ sendImmediately?: boolean
2926
+ ) {
2927
+ await selectSpritePaletteSwap(
2928
+ this,
2929
+ spriteName,
2930
+ paletteSwapName,
2931
+ offset,
2932
+ sendImmediately
2933
+ );
2934
+ }
2935
+
2936
+ reset() {
2937
+ _console.log("clearing displayManager");
2938
+ // @ts-ignore
2939
+ this.#displayStatus = undefined;
2940
+ this.#isAvailable = false;
2941
+ this.#displayInformation = undefined;
2942
+ // @ts-ignore
2943
+ this.#brightness = undefined;
2944
+ this.#displayContextCommandBuffers = [];
2945
+ this.#isAvailable = false;
2946
+
2947
+ this.#contextStateHelper.reset();
2948
+ this.#colors.length = 0;
2949
+ this.#opacities.length = 0;
2950
+
2951
+ this.#isReady = true;
2952
+ this.#pendingSpriteSheet = undefined;
2953
+ this.#pendingSpriteSheetName = undefined;
2954
+
2955
+ this.isServerSide = false;
2956
+
2957
+ Object.keys(this.#spriteSheetIndices).forEach(
2958
+ (spriteSheetName) => delete this.#spriteSheetIndices[spriteSheetName]
2959
+ );
2960
+ Object.keys(this.#spriteSheets).forEach(
2961
+ (spriteSheetName) => delete this.#spriteSheets[spriteSheetName]
2962
+ );
2963
+ }
2964
+
2965
+ // MTU
2966
+ #mtu!: number;
2967
+ get mtu() {
2968
+ return this.#mtu;
2969
+ }
2970
+ set mtu(newMtu: number) {
2971
+ this.#mtu = newMtu;
2972
+ }
2973
+
2974
+ // SERVER SIDE
2975
+ #isServerSide = false;
2976
+ get isServerSide() {
2977
+ return this.#isServerSide;
2978
+ }
2979
+ set isServerSide(newIsServerSide) {
2980
+ if (this.#isServerSide == newIsServerSide) {
2981
+ _console.log("redundant isServerSide assignment");
2982
+ return;
2983
+ }
2984
+ _console.log({ newIsServerSide });
2985
+ this.#isServerSide = newIsServerSide;
2986
+ }
2987
+ }
2988
+
2989
+ export default DisplayManager;