pixi-watchface-engine 0.1.0

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 (69) hide show
  1. package/API.md +796 -0
  2. package/LICENSE +21 -0
  3. package/README.md +257 -0
  4. package/dist/EventBus.d.ts +73 -0
  5. package/dist/EventBus.d.ts.map +1 -0
  6. package/dist/WatchfaceEngine.d.ts +43 -0
  7. package/dist/WatchfaceEngine.d.ts.map +1 -0
  8. package/dist/elements/BaseElement.d.ts +49 -0
  9. package/dist/elements/BaseElement.d.ts.map +1 -0
  10. package/dist/elements/ElementRegistry.d.ts +12 -0
  11. package/dist/elements/ElementRegistry.d.ts.map +1 -0
  12. package/dist/elements/FillMask.d.ts +10 -0
  13. package/dist/elements/FillMask.d.ts.map +1 -0
  14. package/dist/elements/ImageElement.d.ts +21 -0
  15. package/dist/elements/ImageElement.d.ts.map +1 -0
  16. package/dist/elements/ShapeElement.d.ts +42 -0
  17. package/dist/elements/ShapeElement.d.ts.map +1 -0
  18. package/dist/elements/TextElement.d.ts +43 -0
  19. package/dist/elements/TextElement.d.ts.map +1 -0
  20. package/dist/elements/index.d.ts +10 -0
  21. package/dist/elements/index.d.ts.map +1 -0
  22. package/dist/index.d.ts +26 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +35005 -0
  25. package/dist/managers/ElementManager.d.ts +19 -0
  26. package/dist/managers/ElementManager.d.ts.map +1 -0
  27. package/dist/managers/InteractionManager.d.ts +21 -0
  28. package/dist/managers/InteractionManager.d.ts.map +1 -0
  29. package/dist/managers/SerializationManager.d.ts +20 -0
  30. package/dist/managers/SerializationManager.d.ts.map +1 -0
  31. package/dist/managers/ZOrderManager.d.ts +14 -0
  32. package/dist/managers/ZOrderManager.d.ts.map +1 -0
  33. package/dist/managers/index.d.ts +6 -0
  34. package/dist/managers/index.d.ts.map +1 -0
  35. package/dist/plugins/AutosavePlugin.d.ts +18 -0
  36. package/dist/plugins/AutosavePlugin.d.ts.map +1 -0
  37. package/dist/plugins/GridPlugin.d.ts +28 -0
  38. package/dist/plugins/GridPlugin.d.ts.map +1 -0
  39. package/dist/plugins/KeyboardShortcutsPlugin.d.ts +10 -0
  40. package/dist/plugins/KeyboardShortcutsPlugin.d.ts.map +1 -0
  41. package/dist/plugins/Plugin.d.ts +8 -0
  42. package/dist/plugins/Plugin.d.ts.map +1 -0
  43. package/dist/plugins/PluginManager.d.ts +14 -0
  44. package/dist/plugins/PluginManager.d.ts.map +1 -0
  45. package/dist/plugins/UndoRedoPlugin.d.ts +22 -0
  46. package/dist/plugins/UndoRedoPlugin.d.ts.map +1 -0
  47. package/dist/plugins/index.d.ts +10 -0
  48. package/dist/plugins/index.d.ts.map +1 -0
  49. package/dist/selection/MarqueeSelect.d.ts +15 -0
  50. package/dist/selection/MarqueeSelect.d.ts.map +1 -0
  51. package/dist/selection/SelectionBox.d.ts +23 -0
  52. package/dist/selection/SelectionBox.d.ts.map +1 -0
  53. package/dist/selection/SelectionManager.d.ts +51 -0
  54. package/dist/selection/SelectionManager.d.ts.map +1 -0
  55. package/dist/selection/TransformController.d.ts +24 -0
  56. package/dist/selection/TransformController.d.ts.map +1 -0
  57. package/dist/selection/TransformHandle.d.ts +12 -0
  58. package/dist/selection/TransformHandle.d.ts.map +1 -0
  59. package/dist/selection/index.d.ts +7 -0
  60. package/dist/selection/index.d.ts.map +1 -0
  61. package/dist/types.d.ts +43 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/utils/math.d.ts +5 -0
  64. package/dist/utils/math.d.ts.map +1 -0
  65. package/dist/utils/platform.d.ts +3 -0
  66. package/dist/utils/platform.d.ts.map +1 -0
  67. package/dist/utils/uid.d.ts +2 -0
  68. package/dist/utils/uid.d.ts.map +1 -0
  69. package/package.json +70 -0
package/API.md ADDED
@@ -0,0 +1,796 @@
1
+ # Pixi Watchface Editor Engine — Complete API Reference
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [WatchfaceEngine](#watchfaceengine)
6
+ 2. [Elements](#elements)
7
+ 3. [Selection & Transformation](#selection--transformation)
8
+ 4. [Managers](#managers)
9
+ 5. [Plugins](#plugins)
10
+ 6. [Events](#events)
11
+ 7. [Types](#types)
12
+
13
+ ---
14
+
15
+ ## WatchfaceEngine
16
+
17
+ Main orchestrator for the watchface editor.
18
+
19
+ ### Constructor
20
+
21
+ ```typescript
22
+ const engine = new WatchfaceEngine();
23
+ ```
24
+
25
+ ### Methods
26
+
27
+ #### `async init(container: HTMLDivElement, options: WatchfaceEngineOptions): Promise<void>`
28
+
29
+ Initialize the engine with a canvas container and options.
30
+
31
+ ```typescript
32
+ await engine.init(document.getElementById('canvas')!, {
33
+ width: 400,
34
+ height: 400,
35
+ background: 0x1a1a2e,
36
+ selectionHandleColor: 0x0088ff,
37
+ selectionHandleFillColor: 0xffffff,
38
+ selectionHandleSize: 8,
39
+ });
40
+ ```
41
+
42
+ **Parameters:**
43
+ - `container`: HTML element to mount the canvas to
44
+ - `options`:
45
+ - `width` (number, required): Canvas width
46
+ - `height` (number, required): Canvas height
47
+ - `background` (number | string, optional): Background color (default: 0x1a1a2e)
48
+ - `selectionHandleColor` (number, optional): Handle outline color (default: 0x0088ff)
49
+ - `selectionHandleFillColor` (number, optional): Handle fill color (default: 0xffffff)
50
+ - `selectionHandleSize` (number, optional): Handle size in pixels (default: 8)
51
+
52
+ #### `destroy(): void`
53
+
54
+ Clean up the engine, destroy all plugins and elements.
55
+
56
+ ```typescript
57
+ engine.destroy();
58
+ ```
59
+
60
+ #### `getBackgroundLayer(): Container`
61
+
62
+ Get the background layer for custom overlays (e.g., grid).
63
+
64
+ #### `getElementsLayer(): Container`
65
+
66
+ Get the elements layer (where user elements are added).
67
+
68
+ #### `getSelectionLayer(): Container`
69
+
70
+ Get the selection layer (where selection handles are drawn).
71
+
72
+ #### `getOverlayLayer(): Container`
73
+
74
+ Get the overlay layer for plugin UI.
75
+
76
+ ### Properties
77
+
78
+ #### `app: Application`
79
+
80
+ The underlying PixiJS Application instance.
81
+
82
+ ```typescript
83
+ engine.app.stage;
84
+ engine.app.ticker;
85
+ engine.app.screen;
86
+ ```
87
+
88
+ #### `eventBus: EventBus`
89
+
90
+ Event emitter for engine lifecycle and user interactions.
91
+
92
+ ```typescript
93
+ engine.eventBus.on('element:added', ({ element }) => {});
94
+ ```
95
+
96
+ #### `elements: ElementManager`
97
+
98
+ Manager for adding, removing, and accessing elements.
99
+
100
+ ```typescript
101
+ engine.elements.add(element);
102
+ engine.elements.remove(element);
103
+ engine.elements.getAll();
104
+ ```
105
+
106
+ #### `selection: SelectionManager`
107
+
108
+ Manager for element selection and transformation.
109
+
110
+ ```typescript
111
+ engine.selection.select(element);
112
+ engine.selection.getSelected();
113
+ ```
114
+
115
+ #### `plugins: PluginManager`
116
+
117
+ Manager for registering and accessing plugins.
118
+
119
+ ```typescript
120
+ engine.plugins.register(new GridPlugin());
121
+ engine.plugins.get('grid');
122
+ ```
123
+
124
+ #### `serialization: SerializationManager`
125
+
126
+ Manager for JSON serialization and deserialization.
127
+
128
+ ```typescript
129
+ const json = engine.serialization.serialize();
130
+ await engine.serialization.deserialize(json);
131
+ ```
132
+
133
+ #### `zOrder: ZOrderManager`
134
+
135
+ Manager for element z-ordering (layering).
136
+
137
+ ```typescript
138
+ engine.zOrder.bringToFront(element);
139
+ ```
140
+
141
+ #### `options: Readonly<WatchfaceEngineOptions>`
142
+
143
+ Current engine options (read-only).
144
+
145
+ #### `initialized: boolean`
146
+
147
+ Whether the engine has been successfully initialized.
148
+
149
+ #### `_suppressActions: boolean`
150
+
151
+ **Internal use only.** Used by UndoRedoPlugin to prevent infinite loops.
152
+
153
+ ---
154
+
155
+ ## Elements
156
+
157
+ ### BaseElement (Abstract)
158
+
159
+ Base class for all elements. Not used directly, but defines the common API.
160
+
161
+ #### Reactive Properties
162
+
163
+ These properties trigger change events and record undoable actions when modified:
164
+
165
+ ```typescript
166
+ element.x = 100; // number
167
+ element.y = 200; // number
168
+ element.rotation = Math.PI / 4; // radians
169
+ element.zIndex = 5; // integer
170
+ element.visible = true; // boolean
171
+ element.interactable = true; // boolean (false = not selectable)
172
+ element.opacity = 0.8; // 0-1
173
+ element.width = 100; // number (element-specific)
174
+ element.height = 200; // number (element-specific)
175
+ ```
176
+
177
+ #### Methods
178
+
179
+ ```typescript
180
+ element.getDisplayObject(): Container;
181
+ element.toJSON(): SerializedElement;
182
+ element.applyJSON(data: SerializedElement): void;
183
+ ```
184
+
185
+ ### TextElement
186
+
187
+ Text element with styling support.
188
+
189
+ #### Constructor
190
+
191
+ ```typescript
192
+ const text = new TextElement(
193
+ 'Hello World', // text content
194
+ 100, // x position
195
+ 200, // y position
196
+ { // optional style
197
+ fontFamily: 'Arial',
198
+ fontSize: 24,
199
+ fontWeight: 'bold', // 'normal' | 'bold' | 'bolder' | 'lighter' | '100' | ... | '900'
200
+ align: 'center', // 'left' | 'center' | 'right'
201
+ color: 0xffffff, // hex color
202
+ }
203
+ );
204
+
205
+ engine.elements.add(text);
206
+ ```
207
+
208
+ #### Properties
209
+
210
+ ```typescript
211
+ text.text = 'New text';
212
+ text.fontFamily = 'Georgia';
213
+ text.fontSize = 32;
214
+ text.fontWeight = 'bold';
215
+ text.align = 'center';
216
+ text.color = 0xff0000;
217
+ ```
218
+
219
+ All base element properties are also available:
220
+ ```typescript
221
+ text.x = 150;
222
+ text.y = 250;
223
+ text.rotation = 0;
224
+ text.zIndex = 1;
225
+ text.visible = true;
226
+ text.interactable = true;
227
+ text.opacity = 1;
228
+ text.width; // read-only, determined by text content
229
+ text.height; // read-only, determined by text content
230
+ ```
231
+
232
+ ### ImageElement
233
+
234
+ Image element with async texture loading.
235
+
236
+ #### Constructor
237
+
238
+ ```typescript
239
+ const image = new ImageElement(
240
+ '/path/to/image.png', // image URL or path
241
+ 100, // x position
242
+ 200, // y position
243
+ );
244
+
245
+ engine.elements.add(image);
246
+ ```
247
+
248
+ #### Properties
249
+
250
+ ```typescript
251
+ image.src = '/new/image.png'; // async loads new texture
252
+ image.width = 100;
253
+ image.height = 100;
254
+ image.loaded; // boolean, whether texture is loaded
255
+
256
+ // All base element properties
257
+ image.x = 150;
258
+ image.y = 250;
259
+ image.rotation = 0.5;
260
+ image.zIndex = 2;
261
+ image.visible = true;
262
+ image.interactable = true;
263
+ image.opacity = 0.9;
264
+ ```
265
+
266
+ ### ShapeElement
267
+
268
+ Shape element with configurable fill percentage and direction.
269
+
270
+ #### Static Builders
271
+
272
+ ```typescript
273
+ // Line from (x1, y1) to (x2, y2)
274
+ ShapeElement.line(x1, y1, x2, y2);
275
+
276
+ // Circle at (cx, cy) with radius
277
+ ShapeElement.circle(cx, cy, radius);
278
+
279
+ // Rectangle at (x, y) with width and height
280
+ ShapeElement.rectangle(x, y, width, height);
281
+
282
+ // Arc at (cx, cy) with radius from startAngle to endAngle
283
+ ShapeElement.arc(cx, cy, radius, startAngle, endAngle);
284
+ ```
285
+
286
+ #### Constructor Options
287
+
288
+ ```typescript
289
+ const shape = ShapeElement.circle(200, 200, 100);
290
+ // Customize via properties
291
+ shape.foregroundColor = 0xffffff;
292
+ shape.backgroundColor = 0x333333;
293
+ shape.fillPercentage = 75;
294
+ shape.fillDirection = 'left-to-right';
295
+ shape.strokeWidth = 2;
296
+ ```
297
+
298
+ #### Properties
299
+
300
+ ```typescript
301
+ shape.foregroundColor = 0xffffff; // foreground fill color (hex)
302
+ shape.backgroundColor = 0x333333; // background fill color (hex)
303
+ shape.fillPercentage = 75; // 0-100, percentage of shape filled
304
+ shape.fillDirection = 'left-to-right'; // 'left-to-right' | 'right-to-left' | 'top-to-bottom' | 'bottom-to-top'
305
+ shape.strokeWidth = 2; // for lines and arcs
306
+ shape.shapeType; // read-only: 'line' | 'circle' | 'rectangle' | 'arc'
307
+
308
+ // Fill percentage example:
309
+ // foregroundColor fills the specified percentage in the specified direction
310
+ // backgroundColor fills the remainder
311
+ //
312
+ // Circle at 50% fill (left-to-right):
313
+ // |██████| |
314
+ // |foreground background|
315
+ ```
316
+
317
+ All base element properties are also available:
318
+ ```typescript
319
+ shape.x = 150;
320
+ shape.y = 250;
321
+ shape.rotation = Math.PI / 6;
322
+ shape.zIndex = 3;
323
+ shape.visible = true;
324
+ shape.interactable = true;
325
+ shape.opacity = 0.8;
326
+ shape.width; // derived from shape parameters
327
+ shape.height; // derived from shape parameters
328
+ ```
329
+
330
+ ---
331
+
332
+ ## Selection & Transformation
333
+
334
+ ### SelectionManager
335
+
336
+ Manages element selection, clipboard, and user transformations.
337
+
338
+ #### Selection Methods
339
+
340
+ ```typescript
341
+ // Single select (deselects others)
342
+ engine.selection.select(element);
343
+
344
+ // Add to selection (multi-select)
345
+ engine.selection.addToSelection(element);
346
+
347
+ // Remove from selection
348
+ engine.selection.deselect(element);
349
+
350
+ // Clear all selections
351
+ engine.selection.clearSelection();
352
+
353
+ // Select all interactable elements
354
+ engine.selection.selectAll();
355
+
356
+ // Get currently selected elements
357
+ const selected = engine.selection.getSelected(); // BaseElement[]
358
+
359
+ // Check if element is selected
360
+ if (engine.selection.isSelected(element)) { }
361
+ ```
362
+
363
+ #### Clipboard
364
+
365
+ ```typescript
366
+ // Copy selected elements (as serialized JSON)
367
+ engine.selection.copySelected();
368
+
369
+ // Paste from clipboard (offset by 20px)
370
+ engine.selection.pasteClipboard();
371
+ ```
372
+
373
+ #### Deletion & Movement
374
+
375
+ ```typescript
376
+ // Delete all selected elements
377
+ engine.selection.deleteSelected();
378
+
379
+ // Nudge selected elements by dx, dy
380
+ engine.selection.nudgeSelected(10, 20); // 10px right, 20px down
381
+ ```
382
+
383
+ #### User Interactions (Automatic)
384
+
385
+ - **Click**: Select single element
386
+ - **Shift+Click**: Toggle selection
387
+ - **Drag**: Move selected elements (if already selected)
388
+ - **Drag (on empty canvas)**: Marquee select multiple elements
389
+ - **Drag corner handle**: Resize selected element
390
+ - **Drag rotation handle**: Rotate selected element
391
+ - **Ctrl/Cmd+C**: Copy selected
392
+ - **Ctrl/Cmd+V**: Paste
393
+ - **Delete/Backspace**: Delete selected
394
+ - **Arrow keys**: Nudge selected (Shift for 10px)
395
+
396
+ ---
397
+
398
+ ## Managers
399
+
400
+ ### ElementManager
401
+
402
+ Manages element lifecycle.
403
+
404
+ ```typescript
405
+ engine.elements.add(element); // Add element
406
+ engine.elements.remove(element); // Remove element
407
+ engine.elements.getById(id); // Get by ID
408
+ engine.elements.getAll(); // Get all elements
409
+ engine.elements.clear(); // Remove all elements
410
+ engine.elements.count; // Number of elements
411
+ ```
412
+
413
+ ### ZOrderManager
414
+
415
+ Manages element z-ordering (layering).
416
+
417
+ ```typescript
418
+ engine.zOrder.bringToFront(element); // Move to highest z-index
419
+ engine.zOrder.sendToBack(element); // Move to lowest z-index
420
+ engine.zOrder.moveForward(element); // Increase z-index by 1
421
+ engine.zOrder.moveBackward(element); // Decrease z-index by 1
422
+ ```
423
+
424
+ ### SerializationManager
425
+
426
+ Serializes and deserializes watchface state to/from JSON.
427
+
428
+ ```typescript
429
+ // Export to JSON string
430
+ const json = engine.serialization.serialize();
431
+
432
+ // Import from JSON string
433
+ await engine.serialization.deserialize(json);
434
+
435
+ // Deserialize single element
436
+ const element = engine.serialization.deserializeElement(data);
437
+
438
+ // Get the element registry (for custom element types)
439
+ const registry = engine.serialization.getRegistry();
440
+ ```
441
+
442
+ ### InteractionManager
443
+
444
+ **Internal use.** Routes pointer events from elements to SelectionManager.
445
+
446
+ ---
447
+
448
+ ## Plugins
449
+
450
+ ### Plugin Interface
451
+
452
+ All plugins must implement:
453
+
454
+ ```typescript
455
+ interface Plugin {
456
+ readonly name: string;
457
+ init(engine: WatchfaceEngine): void;
458
+ destroy(): void;
459
+ update?(deltaTime: number): void;
460
+ }
461
+ ```
462
+
463
+ ### PluginManager
464
+
465
+ ```typescript
466
+ // Register plugin
467
+ engine.plugins.register(new GridPlugin());
468
+
469
+ // Unregister plugin
470
+ engine.plugins.unregister('grid');
471
+
472
+ // Get plugin by name
473
+ const grid = engine.plugins.get<GridPlugin>('grid');
474
+
475
+ // Check if plugin is registered
476
+ if (engine.plugins.has('grid')) { }
477
+
478
+ // Destroy all plugins
479
+ engine.plugins.destroyAll();
480
+ ```
481
+
482
+ ### GridPlugin
483
+
484
+ Grid overlay with optional snap-to-grid.
485
+
486
+ ```typescript
487
+ engine.plugins.register(new GridPlugin({
488
+ cellSize: 20, // Cell size in pixels (default: 20)
489
+ color: 0x444444, // Grid line color (default: 0x444444)
490
+ opacity: 0.3, // Grid opacity (default: 0.3)
491
+ snapEnabled: false, // Enable snap-to-grid (default: false)
492
+ visible: true, // Show grid (default: true)
493
+ }));
494
+
495
+ const grid = engine.plugins.get<GridPlugin>('grid');
496
+ grid.cellSize = 10;
497
+ grid.snapEnabled = true;
498
+ grid.visible = false;
499
+ ```
500
+
501
+ ### AutosavePlugin
502
+
503
+ Periodically save watchface to JSON via callback.
504
+
505
+ ```typescript
506
+ engine.plugins.register(new AutosavePlugin({
507
+ intervalMs: 30000, // Save interval in ms (default: 30000)
508
+ onSave: async (json) => {
509
+ await fetch('/api/save', {
510
+ method: 'POST',
511
+ body: json
512
+ });
513
+ },
514
+ }));
515
+
516
+ const autosave = engine.plugins.get<AutosavePlugin>('autosave');
517
+ autosave.saveNow(); // Force save immediately
518
+ ```
519
+
520
+ ### UndoRedoPlugin
521
+
522
+ Undo/redo support for all actions.
523
+
524
+ ```typescript
525
+ engine.plugins.register(new UndoRedoPlugin({
526
+ maxHistory: 100, // Maximum undo steps (default: 100)
527
+ }));
528
+
529
+ const ur = engine.plugins.get<UndoRedoPlugin>('undoredo');
530
+ ur.undo();
531
+ ur.redo();
532
+ ur.canUndo; // boolean
533
+ ur.canRedo; // boolean
534
+ ur.clear(); // Clear history
535
+ ```
536
+
537
+ ### KeyboardShortcutsPlugin
538
+
539
+ Keyboard shortcuts. Automatically detects platform (Cmd on macOS, Ctrl elsewhere).
540
+
541
+ ```typescript
542
+ engine.plugins.register(new KeyboardShortcutsPlugin());
543
+
544
+ // Shortcuts:
545
+ // Ctrl/Cmd+Z → Undo
546
+ // Ctrl/Cmd+Shift+Z → Redo
547
+ // Ctrl/Cmd+C → Copy selected
548
+ // Ctrl/Cmd+V → Paste
549
+ // Ctrl/Cmd+A → Select all
550
+ // Delete/Backspace → Delete selected
551
+ // Arrow keys → Nudge selected (1px)
552
+ // Shift+Arrow keys → Nudge selected (10px)
553
+ ```
554
+
555
+ ### Custom Plugin Example
556
+
557
+ ```typescript
558
+ import type { Plugin } from 'pixi-watchface-engine';
559
+
560
+ class LoggerPlugin implements Plugin {
561
+ readonly name = 'logger';
562
+ private unsub?: () => void;
563
+
564
+ init(engine: WatchfaceEngine): void {
565
+ this.unsub = engine.eventBus.on('element:added', ({ element }) => {
566
+ console.log('Element added:', element.type, element.id);
567
+ });
568
+ }
569
+
570
+ destroy(): void {
571
+ this.unsub?.();
572
+ }
573
+
574
+ update?(deltaTime: number): void {
575
+ // Called once per frame
576
+ }
577
+ }
578
+
579
+ engine.plugins.register(new LoggerPlugin());
580
+ ```
581
+
582
+ ---
583
+
584
+ ## Events
585
+
586
+ ### EventBus
587
+
588
+ All events go through `engine.eventBus`:
589
+
590
+ ```typescript
591
+ engine.eventBus.on(event, handler); // Subscribe
592
+ engine.eventBus.off(event, handler); // Unsubscribe
593
+ engine.eventBus.emit(event, data); // Emit (internal)
594
+
595
+ // Returns unsubscribe function
596
+ const unsub = engine.eventBus.on('element:added', ({ element }) => {});
597
+ unsub(); // Unsubscribe
598
+ ```
599
+
600
+ ### Available Events
601
+
602
+ #### Element Lifecycle
603
+
604
+ ```typescript
605
+ engine.eventBus.on('element:added', ({ element }) => {});
606
+ engine.eventBus.on('element:removed', ({ element }) => {});
607
+ engine.eventBus.on('element:changed', ({ element, property, oldValue, newValue }) => {});
608
+ engine.eventBus.on('element:zChanged', ({ element, oldZ, newZ }) => {});
609
+ ```
610
+
611
+ #### Selection
612
+
613
+ ```typescript
614
+ engine.eventBus.on('selection:changed', ({ selected }) => {
615
+ // selected: BaseElement[]
616
+ });
617
+ engine.eventBus.on('selection:cleared', () => {});
618
+ ```
619
+
620
+ #### Interaction
621
+
622
+ ```typescript
623
+ engine.eventBus.on('interaction:dragStart', ({ element, position }) => {
624
+ // position: { x: number, y: number }
625
+ });
626
+ engine.eventBus.on('interaction:dragMove', ({ elements, dx, dy }) => {
627
+ // elements: BaseElement[]
628
+ // dx, dy: number
629
+ });
630
+ engine.eventBus.on('interaction:dragEnd', ({ elements }) => {});
631
+
632
+ engine.eventBus.on('interaction:resizeStart', ({ element }) => {});
633
+ engine.eventBus.on('interaction:resizeEnd', ({ element }) => {});
634
+
635
+ engine.eventBus.on('interaction:rotateStart', ({ element }) => {});
636
+ engine.eventBus.on('interaction:rotateEnd', ({ element }) => {});
637
+ ```
638
+
639
+ #### Serialization
640
+
641
+ ```typescript
642
+ engine.eventBus.on('state:saved', ({ json }) => {});
643
+ engine.eventBus.on('state:loaded', ({ json }) => {});
644
+ ```
645
+
646
+ #### Actions (for UndoRedo integration)
647
+
648
+ ```typescript
649
+ engine.eventBus.on('action:performed', ({ action }) => {
650
+ // action: { type: string, description: string, undo(), redo() }
651
+ });
652
+ ```
653
+
654
+ #### Engine Lifecycle
655
+
656
+ ```typescript
657
+ engine.eventBus.on('engine:ready', () => {});
658
+ engine.eventBus.on('engine:destroy', () => {});
659
+ ```
660
+
661
+ ---
662
+
663
+ ## Types
664
+
665
+ ### WatchfaceEngineOptions
666
+
667
+ ```typescript
668
+ interface WatchfaceEngineOptions {
669
+ width: number;
670
+ height: number;
671
+ background?: string | number;
672
+ selectionHandleColor?: number;
673
+ selectionHandleFillColor?: number;
674
+ selectionHandleSize?: number;
675
+ }
676
+ ```
677
+
678
+ ### SerializedElement
679
+
680
+ Represents an element in JSON form.
681
+
682
+ ```typescript
683
+ interface SerializedElement {
684
+ type: ElementType;
685
+ id: string;
686
+ x: number;
687
+ y: number;
688
+ width: number;
689
+ height: number;
690
+ rotation: number;
691
+ scaleX: number;
692
+ scaleY: number;
693
+ zIndex: number;
694
+ visible: boolean;
695
+ interactable: boolean;
696
+ opacity: number;
697
+ [key: string]: unknown; // Element-specific fields
698
+ }
699
+ ```
700
+
701
+ ### WatchfaceState
702
+
703
+ Complete watchface state (suitable for export/import).
704
+
705
+ ```typescript
706
+ interface WatchfaceState {
707
+ version: number;
708
+ width: number;
709
+ height: number;
710
+ elements: SerializedElement[];
711
+ }
712
+ ```
713
+
714
+ ### Type Aliases
715
+
716
+ ```typescript
717
+ type FillDirection = 'left-to-right' | 'right-to-left' | 'top-to-bottom' | 'bottom-to-top';
718
+ type ShapeType = 'line' | 'circle' | 'rectangle' | 'arc';
719
+ type ElementType = 'text' | 'image' | 'shape';
720
+
721
+ interface HandleConfig {
722
+ color: number;
723
+ fillColor: number;
724
+ size: number;
725
+ }
726
+
727
+ interface Bounds {
728
+ x: number;
729
+ y: number;
730
+ width: number;
731
+ height: number;
732
+ }
733
+ ```
734
+
735
+ ---
736
+
737
+ ## Complete Example
738
+
739
+ ```typescript
740
+ import {
741
+ WatchfaceEngine,
742
+ TextElement,
743
+ ImageElement,
744
+ ShapeElement,
745
+ GridPlugin,
746
+ AutosavePlugin,
747
+ UndoRedoPlugin,
748
+ KeyboardShortcutsPlugin,
749
+ } from 'pixi-watchface-engine';
750
+
751
+ // Create and initialize engine
752
+ const engine = new WatchfaceEngine();
753
+ await engine.init(document.getElementById('canvas')!, {
754
+ width: 400,
755
+ height: 400,
756
+ background: 0x1a1a2e,
757
+ selectionHandleColor: 0x0088ff,
758
+ });
759
+
760
+ // Register plugins
761
+ engine.plugins.register(new GridPlugin({ snapEnabled: true }));
762
+ engine.plugins.register(new AutosavePlugin({
763
+ onSave: (json) => localStorage.setItem('watchface', json),
764
+ }));
765
+ engine.plugins.register(new UndoRedoPlugin());
766
+ engine.plugins.register(new KeyboardShortcutsPlugin());
767
+
768
+ // Create elements
769
+ const background = ShapeElement.circle(200, 200, 180);
770
+ background.interactable = false;
771
+ background.foregroundColor = 0x2d2d5e;
772
+ engine.elements.add(background);
773
+
774
+ const timeText = new TextElement('12:00', 150, 170, {
775
+ fontSize: 48,
776
+ fontWeight: 'bold',
777
+ color: 0xffffff,
778
+ align: 'center',
779
+ });
780
+ engine.elements.add(timeText);
781
+
782
+ const battery = ShapeElement.rectangle(160, 350, 80, 12);
783
+ battery.foregroundColor = 0x00ff88;
784
+ battery.backgroundColor = 0x333333;
785
+ battery.fillPercentage = 75;
786
+ battery.fillDirection = 'left-to-right';
787
+ engine.elements.add(battery);
788
+
789
+ // Listen to events
790
+ engine.eventBus.on('selection:changed', ({ selected }) => {
791
+ console.log('Selected:', selected.map(el => el.id));
792
+ });
793
+
794
+ // Cleanup
795
+ engine.destroy();
796
+ ```