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.
- package/API.md +796 -0
- package/LICENSE +21 -0
- package/README.md +257 -0
- package/dist/EventBus.d.ts +73 -0
- package/dist/EventBus.d.ts.map +1 -0
- package/dist/WatchfaceEngine.d.ts +43 -0
- package/dist/WatchfaceEngine.d.ts.map +1 -0
- package/dist/elements/BaseElement.d.ts +49 -0
- package/dist/elements/BaseElement.d.ts.map +1 -0
- package/dist/elements/ElementRegistry.d.ts +12 -0
- package/dist/elements/ElementRegistry.d.ts.map +1 -0
- package/dist/elements/FillMask.d.ts +10 -0
- package/dist/elements/FillMask.d.ts.map +1 -0
- package/dist/elements/ImageElement.d.ts +21 -0
- package/dist/elements/ImageElement.d.ts.map +1 -0
- package/dist/elements/ShapeElement.d.ts +42 -0
- package/dist/elements/ShapeElement.d.ts.map +1 -0
- package/dist/elements/TextElement.d.ts +43 -0
- package/dist/elements/TextElement.d.ts.map +1 -0
- package/dist/elements/index.d.ts +10 -0
- package/dist/elements/index.d.ts.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35005 -0
- package/dist/managers/ElementManager.d.ts +19 -0
- package/dist/managers/ElementManager.d.ts.map +1 -0
- package/dist/managers/InteractionManager.d.ts +21 -0
- package/dist/managers/InteractionManager.d.ts.map +1 -0
- package/dist/managers/SerializationManager.d.ts +20 -0
- package/dist/managers/SerializationManager.d.ts.map +1 -0
- package/dist/managers/ZOrderManager.d.ts +14 -0
- package/dist/managers/ZOrderManager.d.ts.map +1 -0
- package/dist/managers/index.d.ts +6 -0
- package/dist/managers/index.d.ts.map +1 -0
- package/dist/plugins/AutosavePlugin.d.ts +18 -0
- package/dist/plugins/AutosavePlugin.d.ts.map +1 -0
- package/dist/plugins/GridPlugin.d.ts +28 -0
- package/dist/plugins/GridPlugin.d.ts.map +1 -0
- package/dist/plugins/KeyboardShortcutsPlugin.d.ts +10 -0
- package/dist/plugins/KeyboardShortcutsPlugin.d.ts.map +1 -0
- package/dist/plugins/Plugin.d.ts +8 -0
- package/dist/plugins/Plugin.d.ts.map +1 -0
- package/dist/plugins/PluginManager.d.ts +14 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -0
- package/dist/plugins/UndoRedoPlugin.d.ts +22 -0
- package/dist/plugins/UndoRedoPlugin.d.ts.map +1 -0
- package/dist/plugins/index.d.ts +10 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/selection/MarqueeSelect.d.ts +15 -0
- package/dist/selection/MarqueeSelect.d.ts.map +1 -0
- package/dist/selection/SelectionBox.d.ts +23 -0
- package/dist/selection/SelectionBox.d.ts.map +1 -0
- package/dist/selection/SelectionManager.d.ts +51 -0
- package/dist/selection/SelectionManager.d.ts.map +1 -0
- package/dist/selection/TransformController.d.ts +24 -0
- package/dist/selection/TransformController.d.ts.map +1 -0
- package/dist/selection/TransformHandle.d.ts +12 -0
- package/dist/selection/TransformHandle.d.ts.map +1 -0
- package/dist/selection/index.d.ts +7 -0
- package/dist/selection/index.d.ts.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/math.d.ts +5 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/platform.d.ts +3 -0
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/uid.d.ts +2 -0
- package/dist/utils/uid.d.ts.map +1 -0
- 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
|
+
```
|