@elizaos/capacitor-canvas 1.0.0 → 2.0.0-beta.1
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/android/build.gradle +1 -1
- package/package.json +7 -7
- package/electrobun/src/index.ts +0 -872
- package/electrobun/tsconfig.json +0 -18
package/android/build.gradle
CHANGED
|
@@ -33,7 +33,7 @@ android {
|
|
|
33
33
|
repositories {
|
|
34
34
|
google()
|
|
35
35
|
maven {
|
|
36
|
-
url = uri(rootProject.ext.mavenCentralMirrorUrl)
|
|
36
|
+
url = uri(rootProject.ext.has('mavenCentralMirrorUrl') ? rootProject.ext.mavenCentralMirrorUrl : 'https://repo.maven.apache.org/maven2')
|
|
37
37
|
}
|
|
38
38
|
mavenCentral()
|
|
39
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/capacitor-canvas",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
4
|
"description": "Creates interactive canvases with layers, drawing primitives, and A2UI integration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"canvas",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"dist/",
|
|
27
27
|
"ios/Sources/",
|
|
28
28
|
"ios/Plugin.xcodeproj/",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
29
|
+
"*.podspec",
|
|
30
|
+
"dist"
|
|
31
31
|
],
|
|
32
32
|
"author": "elizaOS",
|
|
33
33
|
"license": "MIT",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"url": "https://github.com/elizaOS/eliza"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
|
-
"build": "
|
|
40
|
-
"clean": "
|
|
41
|
-
"prepublishOnly": "
|
|
39
|
+
"build": "bun run clean && tsc && bun --bun rollup -c rollup.config.mjs",
|
|
40
|
+
"clean": "node ../../../scripts/rm-path-recursive.mjs dist",
|
|
41
|
+
"prepublishOnly": "bun run build",
|
|
42
42
|
"watch": "tsc --watch"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
49
49
|
"rimraf": "^6.0.0",
|
|
50
50
|
"rollup": "^4.60.2",
|
|
51
|
-
"typescript": "^6.0.
|
|
51
|
+
"typescript": "^6.0.3"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@capacitor/core": "^8.3.1"
|
package/electrobun/src/index.ts
DELETED
|
@@ -1,872 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canvas Plugin for Electrobun
|
|
3
|
-
*
|
|
4
|
-
* Provides HTML5 Canvas rendering capabilities on desktop platforms.
|
|
5
|
-
* This is essentially the same as the web implementation since Canvas
|
|
6
|
-
* is fully supported in the desktop Chromium renderer.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { PluginListenerHandle } from "@capacitor/core";
|
|
10
|
-
import type {
|
|
11
|
-
EventCallback,
|
|
12
|
-
ListenerEntry as BaseListenerEntry,
|
|
13
|
-
} from "../../../shared-types.js";
|
|
14
|
-
import type {
|
|
15
|
-
CanvasColor,
|
|
16
|
-
CanvasDrawBatchCommand,
|
|
17
|
-
CanvasDrawOptions,
|
|
18
|
-
CanvasFillStyle,
|
|
19
|
-
CanvasGradient,
|
|
20
|
-
CanvasImageData,
|
|
21
|
-
CanvasLayer,
|
|
22
|
-
CanvasPath,
|
|
23
|
-
CanvasPlugin,
|
|
24
|
-
CanvasPoint,
|
|
25
|
-
CanvasRect,
|
|
26
|
-
CanvasRenderEvent,
|
|
27
|
-
CanvasSize,
|
|
28
|
-
CanvasStrokeStyle,
|
|
29
|
-
CanvasTextStyle,
|
|
30
|
-
CanvasTouchEvent,
|
|
31
|
-
CanvasTransform,
|
|
32
|
-
} from "../../src/definitions";
|
|
33
|
-
|
|
34
|
-
type CanvasEvent = CanvasTouchEvent | CanvasRenderEvent;
|
|
35
|
-
|
|
36
|
-
type ListenerEntry = BaseListenerEntry<string, CanvasEvent>;
|
|
37
|
-
|
|
38
|
-
interface CanvasInstance {
|
|
39
|
-
element: HTMLCanvasElement;
|
|
40
|
-
context: CanvasRenderingContext2D;
|
|
41
|
-
layers: Map<string, CanvasLayer>;
|
|
42
|
-
attachedElement: HTMLElement | null;
|
|
43
|
-
touchEnabled: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function colorToString(color: CanvasColor | string): string {
|
|
47
|
-
if (typeof color === "string") return color;
|
|
48
|
-
const { r, g, b, a = 1 } = color;
|
|
49
|
-
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function createGradient(
|
|
53
|
-
ctx: CanvasRenderingContext2D,
|
|
54
|
-
gradient: CanvasGradient,
|
|
55
|
-
): CanvasGradient2D {
|
|
56
|
-
let canvasGradient: CanvasGradient2D;
|
|
57
|
-
if (gradient.type === "linear") {
|
|
58
|
-
canvasGradient = ctx.createLinearGradient(
|
|
59
|
-
gradient.x0,
|
|
60
|
-
gradient.y0,
|
|
61
|
-
gradient.x1,
|
|
62
|
-
gradient.y1,
|
|
63
|
-
);
|
|
64
|
-
} else {
|
|
65
|
-
canvasGradient = ctx.createRadialGradient(
|
|
66
|
-
gradient.x0,
|
|
67
|
-
gradient.y0,
|
|
68
|
-
gradient.r0,
|
|
69
|
-
gradient.x1,
|
|
70
|
-
gradient.y1,
|
|
71
|
-
gradient.r1,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
for (const stop of gradient.stops) {
|
|
75
|
-
canvasGradient.addColorStop(stop.offset, colorToString(stop.color));
|
|
76
|
-
}
|
|
77
|
-
return canvasGradient;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
type CanvasGradient2D = ReturnType<
|
|
81
|
-
CanvasRenderingContext2D["createLinearGradient"]
|
|
82
|
-
>;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Canvas Plugin implementation for Electrobun
|
|
86
|
-
*/
|
|
87
|
-
export class CanvasElectrobun implements CanvasPlugin {
|
|
88
|
-
private canvases: Map<string, CanvasInstance> = new Map();
|
|
89
|
-
private listeners: ListenerEntry[] = [];
|
|
90
|
-
private canvasIdCounter = 0;
|
|
91
|
-
private layerIdCounter = 0;
|
|
92
|
-
|
|
93
|
-
// MARK: - Canvas Lifecycle
|
|
94
|
-
|
|
95
|
-
async create(options: {
|
|
96
|
-
size: CanvasSize;
|
|
97
|
-
backgroundColor?: CanvasColor | string;
|
|
98
|
-
}): Promise<{ canvasId: string }> {
|
|
99
|
-
const canvasId = `canvas_${++this.canvasIdCounter}`;
|
|
100
|
-
|
|
101
|
-
const canvas = document.createElement("canvas");
|
|
102
|
-
canvas.id = canvasId;
|
|
103
|
-
canvas.width = options.size.width;
|
|
104
|
-
canvas.height = options.size.height;
|
|
105
|
-
|
|
106
|
-
const ctx = canvas.getContext("2d");
|
|
107
|
-
if (!ctx) {
|
|
108
|
-
throw new Error("Failed to get 2D context");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (options.backgroundColor) {
|
|
112
|
-
ctx.fillStyle = colorToString(options.backgroundColor);
|
|
113
|
-
ctx.fillRect(0, 0, options.size.width, options.size.height);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const instance: CanvasInstance = {
|
|
117
|
-
element: canvas,
|
|
118
|
-
context: ctx,
|
|
119
|
-
layers: new Map(),
|
|
120
|
-
attachedElement: null,
|
|
121
|
-
touchEnabled: false,
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
this.canvases.set(canvasId, instance);
|
|
125
|
-
return { canvasId };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async destroy(options: { canvasId: string }): Promise<void> {
|
|
129
|
-
const instance = this.canvases.get(options.canvasId);
|
|
130
|
-
if (instance) {
|
|
131
|
-
if (instance.attachedElement) {
|
|
132
|
-
instance.attachedElement.removeChild(instance.element);
|
|
133
|
-
}
|
|
134
|
-
this.canvases.delete(options.canvasId);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
async attach(options: {
|
|
139
|
-
canvasId: string;
|
|
140
|
-
element: HTMLElement;
|
|
141
|
-
}): Promise<void> {
|
|
142
|
-
const instance = this.canvases.get(options.canvasId);
|
|
143
|
-
if (!instance) {
|
|
144
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (instance.attachedElement) {
|
|
148
|
-
instance.attachedElement.removeChild(instance.element);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
options.element.appendChild(instance.element);
|
|
152
|
-
instance.attachedElement = options.element;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async detach(options: { canvasId: string }): Promise<void> {
|
|
156
|
-
const instance = this.canvases.get(options.canvasId);
|
|
157
|
-
if (!instance) {
|
|
158
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (instance.attachedElement) {
|
|
162
|
-
instance.attachedElement.removeChild(instance.element);
|
|
163
|
-
instance.attachedElement = null;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async resize(options: { canvasId: string; size: CanvasSize }): Promise<void> {
|
|
168
|
-
const instance = this.canvases.get(options.canvasId);
|
|
169
|
-
if (!instance) {
|
|
170
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
instance.element.width = options.size.width;
|
|
174
|
-
instance.element.height = options.size.height;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async clear(options: {
|
|
178
|
-
canvasId: string;
|
|
179
|
-
rect?: CanvasRect;
|
|
180
|
-
layerId?: string;
|
|
181
|
-
}): Promise<void> {
|
|
182
|
-
const instance = this.canvases.get(options.canvasId);
|
|
183
|
-
if (!instance) {
|
|
184
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const ctx = instance.context;
|
|
188
|
-
if (options.rect) {
|
|
189
|
-
ctx.clearRect(
|
|
190
|
-
options.rect.x,
|
|
191
|
-
options.rect.y,
|
|
192
|
-
options.rect.width,
|
|
193
|
-
options.rect.height,
|
|
194
|
-
);
|
|
195
|
-
} else {
|
|
196
|
-
ctx.clearRect(0, 0, instance.element.width, instance.element.height);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// MARK: - Layer Management
|
|
201
|
-
|
|
202
|
-
async createLayer(options: {
|
|
203
|
-
canvasId: string;
|
|
204
|
-
layer: Omit<CanvasLayer, "id">;
|
|
205
|
-
}): Promise<{ layerId: string }> {
|
|
206
|
-
const instance = this.canvases.get(options.canvasId);
|
|
207
|
-
if (!instance) {
|
|
208
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const layerId = `layer_${++this.layerIdCounter}`;
|
|
212
|
-
const layer: CanvasLayer = {
|
|
213
|
-
...options.layer,
|
|
214
|
-
id: layerId,
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
instance.layers.set(layerId, layer);
|
|
218
|
-
return { layerId };
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async updateLayer(options: {
|
|
222
|
-
canvasId: string;
|
|
223
|
-
layerId: string;
|
|
224
|
-
layer: Partial<CanvasLayer>;
|
|
225
|
-
}): Promise<void> {
|
|
226
|
-
const instance = this.canvases.get(options.canvasId);
|
|
227
|
-
if (!instance) {
|
|
228
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const layer = instance.layers.get(options.layerId);
|
|
232
|
-
if (!layer) {
|
|
233
|
-
throw new Error(`Layer not found: ${options.layerId}`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
Object.assign(layer, options.layer);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async deleteLayer(options: {
|
|
240
|
-
canvasId: string;
|
|
241
|
-
layerId: string;
|
|
242
|
-
}): Promise<void> {
|
|
243
|
-
const instance = this.canvases.get(options.canvasId);
|
|
244
|
-
if (!instance) {
|
|
245
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
instance.layers.delete(options.layerId);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async getLayers(options: {
|
|
252
|
-
canvasId: string;
|
|
253
|
-
}): Promise<{ layers: CanvasLayer[] }> {
|
|
254
|
-
const instance = this.canvases.get(options.canvasId);
|
|
255
|
-
if (!instance) {
|
|
256
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return { layers: Array.from(instance.layers.values()) };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// MARK: - Drawing Operations
|
|
263
|
-
|
|
264
|
-
private applyDrawOptions(
|
|
265
|
-
ctx: CanvasRenderingContext2D,
|
|
266
|
-
drawOptions?: CanvasDrawOptions,
|
|
267
|
-
): void {
|
|
268
|
-
if (!drawOptions) return;
|
|
269
|
-
|
|
270
|
-
if (drawOptions.opacity !== undefined) {
|
|
271
|
-
ctx.globalAlpha = drawOptions.opacity;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (drawOptions.blendMode) {
|
|
275
|
-
ctx.globalCompositeOperation = drawOptions.blendMode;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (drawOptions.shadow) {
|
|
279
|
-
ctx.shadowColor = colorToString(drawOptions.shadow.color);
|
|
280
|
-
ctx.shadowBlur = drawOptions.shadow.blur;
|
|
281
|
-
ctx.shadowOffsetX = drawOptions.shadow.offsetX;
|
|
282
|
-
ctx.shadowOffsetY = drawOptions.shadow.offsetY;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (drawOptions.transform) {
|
|
286
|
-
this.applyTransform(ctx, drawOptions.transform);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private applyTransform(
|
|
291
|
-
ctx: CanvasRenderingContext2D,
|
|
292
|
-
transform: CanvasTransform,
|
|
293
|
-
): void {
|
|
294
|
-
if (transform.translateX || transform.translateY) {
|
|
295
|
-
ctx.translate(transform.translateX || 0, transform.translateY || 0);
|
|
296
|
-
}
|
|
297
|
-
if (transform.rotation) {
|
|
298
|
-
ctx.rotate(transform.rotation);
|
|
299
|
-
}
|
|
300
|
-
if (transform.scaleX || transform.scaleY) {
|
|
301
|
-
ctx.scale(transform.scaleX || 1, transform.scaleY || 1);
|
|
302
|
-
}
|
|
303
|
-
if (transform.skewX || transform.skewY) {
|
|
304
|
-
ctx.transform(1, transform.skewY || 0, transform.skewX || 0, 1, 0, 0);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
private applyStroke(
|
|
309
|
-
ctx: CanvasRenderingContext2D,
|
|
310
|
-
stroke: CanvasStrokeStyle,
|
|
311
|
-
): void {
|
|
312
|
-
ctx.strokeStyle = colorToString(stroke.color);
|
|
313
|
-
ctx.lineWidth = stroke.width;
|
|
314
|
-
if (stroke.lineCap) ctx.lineCap = stroke.lineCap;
|
|
315
|
-
if (stroke.lineJoin) ctx.lineJoin = stroke.lineJoin;
|
|
316
|
-
if (stroke.dashPattern) ctx.setLineDash(stroke.dashPattern);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
private applyFill(
|
|
320
|
-
ctx: CanvasRenderingContext2D,
|
|
321
|
-
fill: CanvasFillStyle | CanvasGradient,
|
|
322
|
-
): void {
|
|
323
|
-
if ("type" in fill) {
|
|
324
|
-
ctx.fillStyle = createGradient(ctx, fill);
|
|
325
|
-
} else {
|
|
326
|
-
ctx.fillStyle = colorToString(fill.color);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
async drawRect(options: {
|
|
331
|
-
canvasId: string;
|
|
332
|
-
rect: CanvasRect;
|
|
333
|
-
fill?: CanvasFillStyle | CanvasGradient;
|
|
334
|
-
stroke?: CanvasStrokeStyle;
|
|
335
|
-
cornerRadius?: number;
|
|
336
|
-
drawOptions?: CanvasDrawOptions;
|
|
337
|
-
}): Promise<void> {
|
|
338
|
-
const instance = this.canvases.get(options.canvasId);
|
|
339
|
-
if (!instance) {
|
|
340
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const ctx = instance.context;
|
|
344
|
-
ctx.save();
|
|
345
|
-
this.applyDrawOptions(ctx, options.drawOptions);
|
|
346
|
-
|
|
347
|
-
const { x, y, width, height } = options.rect;
|
|
348
|
-
const radius = options.cornerRadius || 0;
|
|
349
|
-
|
|
350
|
-
ctx.beginPath();
|
|
351
|
-
if (radius > 0) {
|
|
352
|
-
ctx.moveTo(x + radius, y);
|
|
353
|
-
ctx.lineTo(x + width - radius, y);
|
|
354
|
-
ctx.arcTo(x + width, y, x + width, y + radius, radius);
|
|
355
|
-
ctx.lineTo(x + width, y + height - radius);
|
|
356
|
-
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
|
|
357
|
-
ctx.lineTo(x + radius, y + height);
|
|
358
|
-
ctx.arcTo(x, y + height, x, y + height - radius, radius);
|
|
359
|
-
ctx.lineTo(x, y + radius);
|
|
360
|
-
ctx.arcTo(x, y, x + radius, y, radius);
|
|
361
|
-
} else {
|
|
362
|
-
ctx.rect(x, y, width, height);
|
|
363
|
-
}
|
|
364
|
-
ctx.closePath();
|
|
365
|
-
|
|
366
|
-
if (options.fill) {
|
|
367
|
-
this.applyFill(ctx, options.fill);
|
|
368
|
-
ctx.fill();
|
|
369
|
-
}
|
|
370
|
-
if (options.stroke) {
|
|
371
|
-
this.applyStroke(ctx, options.stroke);
|
|
372
|
-
ctx.stroke();
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
ctx.restore();
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
async drawEllipse(options: {
|
|
379
|
-
canvasId: string;
|
|
380
|
-
center: CanvasPoint;
|
|
381
|
-
radiusX: number;
|
|
382
|
-
radiusY: number;
|
|
383
|
-
fill?: CanvasFillStyle | CanvasGradient;
|
|
384
|
-
stroke?: CanvasStrokeStyle;
|
|
385
|
-
drawOptions?: CanvasDrawOptions;
|
|
386
|
-
}): Promise<void> {
|
|
387
|
-
const instance = this.canvases.get(options.canvasId);
|
|
388
|
-
if (!instance) {
|
|
389
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
const ctx = instance.context;
|
|
393
|
-
ctx.save();
|
|
394
|
-
this.applyDrawOptions(ctx, options.drawOptions);
|
|
395
|
-
|
|
396
|
-
ctx.beginPath();
|
|
397
|
-
ctx.ellipse(
|
|
398
|
-
options.center.x,
|
|
399
|
-
options.center.y,
|
|
400
|
-
options.radiusX,
|
|
401
|
-
options.radiusY,
|
|
402
|
-
0,
|
|
403
|
-
0,
|
|
404
|
-
Math.PI * 2,
|
|
405
|
-
);
|
|
406
|
-
ctx.closePath();
|
|
407
|
-
|
|
408
|
-
if (options.fill) {
|
|
409
|
-
this.applyFill(ctx, options.fill);
|
|
410
|
-
ctx.fill();
|
|
411
|
-
}
|
|
412
|
-
if (options.stroke) {
|
|
413
|
-
this.applyStroke(ctx, options.stroke);
|
|
414
|
-
ctx.stroke();
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
ctx.restore();
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async drawLine(options: {
|
|
421
|
-
canvasId: string;
|
|
422
|
-
from: CanvasPoint;
|
|
423
|
-
to: CanvasPoint;
|
|
424
|
-
stroke: CanvasStrokeStyle;
|
|
425
|
-
drawOptions?: CanvasDrawOptions;
|
|
426
|
-
}): Promise<void> {
|
|
427
|
-
const instance = this.canvases.get(options.canvasId);
|
|
428
|
-
if (!instance) {
|
|
429
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const ctx = instance.context;
|
|
433
|
-
ctx.save();
|
|
434
|
-
this.applyDrawOptions(ctx, options.drawOptions);
|
|
435
|
-
this.applyStroke(ctx, options.stroke);
|
|
436
|
-
|
|
437
|
-
ctx.beginPath();
|
|
438
|
-
ctx.moveTo(options.from.x, options.from.y);
|
|
439
|
-
ctx.lineTo(options.to.x, options.to.y);
|
|
440
|
-
ctx.stroke();
|
|
441
|
-
|
|
442
|
-
ctx.restore();
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
async drawPath(options: {
|
|
446
|
-
canvasId: string;
|
|
447
|
-
path: CanvasPath;
|
|
448
|
-
fill?: CanvasFillStyle | CanvasGradient;
|
|
449
|
-
stroke?: CanvasStrokeStyle;
|
|
450
|
-
drawOptions?: CanvasDrawOptions;
|
|
451
|
-
}): Promise<void> {
|
|
452
|
-
const instance = this.canvases.get(options.canvasId);
|
|
453
|
-
if (!instance) {
|
|
454
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const ctx = instance.context;
|
|
458
|
-
ctx.save();
|
|
459
|
-
this.applyDrawOptions(ctx, options.drawOptions);
|
|
460
|
-
|
|
461
|
-
ctx.beginPath();
|
|
462
|
-
for (const cmd of options.path.commands) {
|
|
463
|
-
switch (cmd.type) {
|
|
464
|
-
case "moveTo":
|
|
465
|
-
ctx.moveTo(cmd.args[0], cmd.args[1]);
|
|
466
|
-
break;
|
|
467
|
-
case "lineTo":
|
|
468
|
-
ctx.lineTo(cmd.args[0], cmd.args[1]);
|
|
469
|
-
break;
|
|
470
|
-
case "quadraticCurveTo":
|
|
471
|
-
ctx.quadraticCurveTo(
|
|
472
|
-
cmd.args[0],
|
|
473
|
-
cmd.args[1],
|
|
474
|
-
cmd.args[2],
|
|
475
|
-
cmd.args[3],
|
|
476
|
-
);
|
|
477
|
-
break;
|
|
478
|
-
case "bezierCurveTo":
|
|
479
|
-
ctx.bezierCurveTo(
|
|
480
|
-
cmd.args[0],
|
|
481
|
-
cmd.args[1],
|
|
482
|
-
cmd.args[2],
|
|
483
|
-
cmd.args[3],
|
|
484
|
-
cmd.args[4],
|
|
485
|
-
cmd.args[5],
|
|
486
|
-
);
|
|
487
|
-
break;
|
|
488
|
-
case "arcTo":
|
|
489
|
-
ctx.arcTo(
|
|
490
|
-
cmd.args[0],
|
|
491
|
-
cmd.args[1],
|
|
492
|
-
cmd.args[2],
|
|
493
|
-
cmd.args[3],
|
|
494
|
-
cmd.args[4],
|
|
495
|
-
);
|
|
496
|
-
break;
|
|
497
|
-
case "arc":
|
|
498
|
-
ctx.arc(
|
|
499
|
-
cmd.args[0],
|
|
500
|
-
cmd.args[1],
|
|
501
|
-
cmd.args[2],
|
|
502
|
-
cmd.args[3],
|
|
503
|
-
cmd.args[4],
|
|
504
|
-
cmd.args[5] === 1,
|
|
505
|
-
);
|
|
506
|
-
break;
|
|
507
|
-
case "ellipse":
|
|
508
|
-
ctx.ellipse(
|
|
509
|
-
cmd.args[0],
|
|
510
|
-
cmd.args[1],
|
|
511
|
-
cmd.args[2],
|
|
512
|
-
cmd.args[3],
|
|
513
|
-
cmd.args[4],
|
|
514
|
-
cmd.args[5],
|
|
515
|
-
cmd.args[6],
|
|
516
|
-
cmd.args[7] === 1,
|
|
517
|
-
);
|
|
518
|
-
break;
|
|
519
|
-
case "rect":
|
|
520
|
-
ctx.rect(cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]);
|
|
521
|
-
break;
|
|
522
|
-
case "closePath":
|
|
523
|
-
ctx.closePath();
|
|
524
|
-
break;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (options.fill) {
|
|
529
|
-
this.applyFill(ctx, options.fill);
|
|
530
|
-
ctx.fill();
|
|
531
|
-
}
|
|
532
|
-
if (options.stroke) {
|
|
533
|
-
this.applyStroke(ctx, options.stroke);
|
|
534
|
-
ctx.stroke();
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
ctx.restore();
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
async drawText(options: {
|
|
541
|
-
canvasId: string;
|
|
542
|
-
text: string;
|
|
543
|
-
position: CanvasPoint;
|
|
544
|
-
style: CanvasTextStyle;
|
|
545
|
-
drawOptions?: CanvasDrawOptions;
|
|
546
|
-
}): Promise<void> {
|
|
547
|
-
const instance = this.canvases.get(options.canvasId);
|
|
548
|
-
if (!instance) {
|
|
549
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const ctx = instance.context;
|
|
553
|
-
ctx.save();
|
|
554
|
-
this.applyDrawOptions(ctx, options.drawOptions);
|
|
555
|
-
|
|
556
|
-
ctx.font = `${options.style.size}px ${options.style.font}`;
|
|
557
|
-
ctx.fillStyle = colorToString(options.style.color);
|
|
558
|
-
if (options.style.align) ctx.textAlign = options.style.align;
|
|
559
|
-
if (options.style.baseline) ctx.textBaseline = options.style.baseline;
|
|
560
|
-
|
|
561
|
-
if (options.style.maxWidth) {
|
|
562
|
-
ctx.fillText(
|
|
563
|
-
options.text,
|
|
564
|
-
options.position.x,
|
|
565
|
-
options.position.y,
|
|
566
|
-
options.style.maxWidth,
|
|
567
|
-
);
|
|
568
|
-
} else {
|
|
569
|
-
ctx.fillText(options.text, options.position.x, options.position.y);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
ctx.restore();
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
async drawImage(options: {
|
|
576
|
-
canvasId: string;
|
|
577
|
-
image: CanvasImageData | string;
|
|
578
|
-
destRect: CanvasRect;
|
|
579
|
-
srcRect?: CanvasRect;
|
|
580
|
-
drawOptions?: CanvasDrawOptions;
|
|
581
|
-
}): Promise<void> {
|
|
582
|
-
const instance = this.canvases.get(options.canvasId);
|
|
583
|
-
if (!instance) {
|
|
584
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const ctx = instance.context;
|
|
588
|
-
|
|
589
|
-
return new Promise((resolve, reject) => {
|
|
590
|
-
const img = new Image();
|
|
591
|
-
img.onload = () => {
|
|
592
|
-
ctx.save();
|
|
593
|
-
this.applyDrawOptions(ctx, options.drawOptions);
|
|
594
|
-
|
|
595
|
-
if (options.srcRect) {
|
|
596
|
-
ctx.drawImage(
|
|
597
|
-
img,
|
|
598
|
-
options.srcRect.x,
|
|
599
|
-
options.srcRect.y,
|
|
600
|
-
options.srcRect.width,
|
|
601
|
-
options.srcRect.height,
|
|
602
|
-
options.destRect.x,
|
|
603
|
-
options.destRect.y,
|
|
604
|
-
options.destRect.width,
|
|
605
|
-
options.destRect.height,
|
|
606
|
-
);
|
|
607
|
-
} else {
|
|
608
|
-
ctx.drawImage(
|
|
609
|
-
img,
|
|
610
|
-
options.destRect.x,
|
|
611
|
-
options.destRect.y,
|
|
612
|
-
options.destRect.width,
|
|
613
|
-
options.destRect.height,
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
ctx.restore();
|
|
618
|
-
resolve();
|
|
619
|
-
};
|
|
620
|
-
img.onerror = () => reject(new Error("Failed to load image"));
|
|
621
|
-
|
|
622
|
-
if (typeof options.image === "string") {
|
|
623
|
-
img.src = options.image;
|
|
624
|
-
} else {
|
|
625
|
-
img.src = `data:image/${options.image.format};base64,${options.image.base64}`;
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
async drawBatch(options: {
|
|
631
|
-
canvasId: string;
|
|
632
|
-
commands: CanvasDrawBatchCommand[];
|
|
633
|
-
}): Promise<void> {
|
|
634
|
-
const base = { canvasId: options.canvasId };
|
|
635
|
-
for (const cmd of options.commands) {
|
|
636
|
-
switch (cmd.type) {
|
|
637
|
-
case "rect":
|
|
638
|
-
await this.drawRect({ ...base, ...cmd.args });
|
|
639
|
-
break;
|
|
640
|
-
case "ellipse":
|
|
641
|
-
await this.drawEllipse({ ...base, ...cmd.args });
|
|
642
|
-
break;
|
|
643
|
-
case "line":
|
|
644
|
-
await this.drawLine({ ...base, ...cmd.args });
|
|
645
|
-
break;
|
|
646
|
-
case "path":
|
|
647
|
-
await this.drawPath({ ...base, ...cmd.args });
|
|
648
|
-
break;
|
|
649
|
-
case "text":
|
|
650
|
-
await this.drawText({ ...base, ...cmd.args });
|
|
651
|
-
break;
|
|
652
|
-
case "image":
|
|
653
|
-
await this.drawImage({ ...base, ...cmd.args });
|
|
654
|
-
break;
|
|
655
|
-
case "clear":
|
|
656
|
-
await this.clear({ ...base, ...cmd.args });
|
|
657
|
-
break;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// MARK: - Pixel Data
|
|
663
|
-
|
|
664
|
-
async getPixelData(options: {
|
|
665
|
-
canvasId: string;
|
|
666
|
-
rect?: CanvasRect;
|
|
667
|
-
}): Promise<{
|
|
668
|
-
data: Uint8ClampedArray;
|
|
669
|
-
width: number;
|
|
670
|
-
height: number;
|
|
671
|
-
}> {
|
|
672
|
-
const instance = this.canvases.get(options.canvasId);
|
|
673
|
-
if (!instance) {
|
|
674
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const ctx = instance.context;
|
|
678
|
-
const rect = options.rect || {
|
|
679
|
-
x: 0,
|
|
680
|
-
y: 0,
|
|
681
|
-
width: instance.element.width,
|
|
682
|
-
height: instance.element.height,
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
const imageData = ctx.getImageData(rect.x, rect.y, rect.width, rect.height);
|
|
686
|
-
return {
|
|
687
|
-
data: imageData.data,
|
|
688
|
-
width: imageData.width,
|
|
689
|
-
height: imageData.height,
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// MARK: - Export
|
|
694
|
-
|
|
695
|
-
async toImage(options: {
|
|
696
|
-
canvasId: string;
|
|
697
|
-
format?: "png" | "jpeg" | "webp";
|
|
698
|
-
quality?: number;
|
|
699
|
-
layerIds?: string[];
|
|
700
|
-
}): Promise<CanvasImageData> {
|
|
701
|
-
const instance = this.canvases.get(options.canvasId);
|
|
702
|
-
if (!instance) {
|
|
703
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const format = options.format || "png";
|
|
707
|
-
const mimeType =
|
|
708
|
-
format === "jpeg"
|
|
709
|
-
? "image/jpeg"
|
|
710
|
-
: format === "webp"
|
|
711
|
-
? "image/webp"
|
|
712
|
-
: "image/png";
|
|
713
|
-
const quality =
|
|
714
|
-
options.quality !== undefined ? options.quality / 100 : 0.92;
|
|
715
|
-
|
|
716
|
-
const dataUrl = instance.element.toDataURL(mimeType, quality);
|
|
717
|
-
const base64 = dataUrl.split(",")[1];
|
|
718
|
-
|
|
719
|
-
return {
|
|
720
|
-
base64,
|
|
721
|
-
format,
|
|
722
|
-
width: instance.element.width,
|
|
723
|
-
height: instance.element.height,
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// MARK: - Transform
|
|
728
|
-
|
|
729
|
-
async setTransform(options: {
|
|
730
|
-
canvasId: string;
|
|
731
|
-
transform: CanvasTransform;
|
|
732
|
-
}): Promise<void> {
|
|
733
|
-
const instance = this.canvases.get(options.canvasId);
|
|
734
|
-
if (!instance) {
|
|
735
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
const ctx = instance.context;
|
|
739
|
-
ctx.resetTransform();
|
|
740
|
-
this.applyTransform(ctx, options.transform);
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
async resetTransform(options: { canvasId: string }): Promise<void> {
|
|
744
|
-
const instance = this.canvases.get(options.canvasId);
|
|
745
|
-
if (!instance) {
|
|
746
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
instance.context.resetTransform();
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// MARK: - Touch Input
|
|
753
|
-
|
|
754
|
-
async setTouchEnabled(options: {
|
|
755
|
-
canvasId: string;
|
|
756
|
-
enabled: boolean;
|
|
757
|
-
}): Promise<void> {
|
|
758
|
-
const instance = this.canvases.get(options.canvasId);
|
|
759
|
-
if (!instance) {
|
|
760
|
-
throw new Error(`Canvas not found: ${options.canvasId}`);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
instance.touchEnabled = options.enabled;
|
|
764
|
-
|
|
765
|
-
if (options.enabled) {
|
|
766
|
-
this.setupTouchListeners(instance);
|
|
767
|
-
} else {
|
|
768
|
-
this.removeTouchListeners(instance);
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
private setupTouchListeners(instance: CanvasInstance): void {
|
|
773
|
-
const canvas = instance.element;
|
|
774
|
-
|
|
775
|
-
const createTouchEvent = (
|
|
776
|
-
e: TouchEvent,
|
|
777
|
-
type: "start" | "move" | "end" | "cancel",
|
|
778
|
-
): CanvasTouchEvent => {
|
|
779
|
-
const rect = canvas.getBoundingClientRect();
|
|
780
|
-
const touches = Array.from(e.touches).map((t) => ({
|
|
781
|
-
id: t.identifier,
|
|
782
|
-
x: t.clientX - rect.left,
|
|
783
|
-
y: t.clientY - rect.top,
|
|
784
|
-
force: t.force,
|
|
785
|
-
}));
|
|
786
|
-
return { type, touches, timestamp: Date.now() };
|
|
787
|
-
};
|
|
788
|
-
|
|
789
|
-
const handlers = {
|
|
790
|
-
touchstart: (e: TouchEvent) => {
|
|
791
|
-
e.preventDefault();
|
|
792
|
-
this.notifyListeners("touch", createTouchEvent(e, "start"));
|
|
793
|
-
},
|
|
794
|
-
touchmove: (e: TouchEvent) => {
|
|
795
|
-
e.preventDefault();
|
|
796
|
-
this.notifyListeners("touch", createTouchEvent(e, "move"));
|
|
797
|
-
},
|
|
798
|
-
touchend: (e: TouchEvent) => {
|
|
799
|
-
e.preventDefault();
|
|
800
|
-
this.notifyListeners("touch", createTouchEvent(e, "end"));
|
|
801
|
-
},
|
|
802
|
-
touchcancel: (e: TouchEvent) => {
|
|
803
|
-
e.preventDefault();
|
|
804
|
-
this.notifyListeners("touch", createTouchEvent(e, "cancel"));
|
|
805
|
-
},
|
|
806
|
-
};
|
|
807
|
-
|
|
808
|
-
for (const [event, handler] of Object.entries(handlers)) {
|
|
809
|
-
canvas.addEventListener(event, handler as EventListener);
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// Store handlers for removal
|
|
813
|
-
(
|
|
814
|
-
canvas as HTMLCanvasElement & { _touchHandlers?: typeof handlers }
|
|
815
|
-
)._touchHandlers = handlers;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
private removeTouchListeners(instance: CanvasInstance): void {
|
|
819
|
-
const canvas = instance.element as HTMLCanvasElement & {
|
|
820
|
-
_touchHandlers?: Record<string, EventListener>;
|
|
821
|
-
};
|
|
822
|
-
const handlers = canvas._touchHandlers;
|
|
823
|
-
if (handlers) {
|
|
824
|
-
for (const [event, handler] of Object.entries(handlers)) {
|
|
825
|
-
canvas.removeEventListener(event, handler);
|
|
826
|
-
}
|
|
827
|
-
delete canvas._touchHandlers;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// MARK: - Event Listeners
|
|
832
|
-
|
|
833
|
-
private notifyListeners<T>(eventName: string, data: T): void {
|
|
834
|
-
for (const listener of this.listeners) {
|
|
835
|
-
if (listener.eventName === eventName) {
|
|
836
|
-
(listener.callback as EventCallback<T>)(data);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
async addListener(
|
|
842
|
-
eventName: "touch",
|
|
843
|
-
listenerFunc: (event: CanvasTouchEvent) => void,
|
|
844
|
-
): Promise<PluginListenerHandle>;
|
|
845
|
-
async addListener(
|
|
846
|
-
eventName: "render",
|
|
847
|
-
listenerFunc: (event: CanvasRenderEvent) => void,
|
|
848
|
-
): Promise<PluginListenerHandle>;
|
|
849
|
-
async addListener(
|
|
850
|
-
eventName: string,
|
|
851
|
-
listenerFunc: EventCallback<CanvasEvent>,
|
|
852
|
-
): Promise<PluginListenerHandle> {
|
|
853
|
-
const entry: ListenerEntry = { eventName, callback: listenerFunc };
|
|
854
|
-
this.listeners.push(entry);
|
|
855
|
-
|
|
856
|
-
return {
|
|
857
|
-
remove: async () => {
|
|
858
|
-
const idx = this.listeners.indexOf(entry);
|
|
859
|
-
if (idx >= 0) {
|
|
860
|
-
this.listeners.splice(idx, 1);
|
|
861
|
-
}
|
|
862
|
-
},
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
async removeAllListeners(): Promise<void> {
|
|
867
|
-
this.listeners = [];
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
// Export the plugin instance
|
|
872
|
-
export const Canvas = new CanvasElectrobun();
|
package/electrobun/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"lib": ["ES2020", "DOM"],
|
|
7
|
-
"declaration": true,
|
|
8
|
-
"strict": true,
|
|
9
|
-
"noUnusedLocals": true,
|
|
10
|
-
"noUnusedParameters": true,
|
|
11
|
-
"esModuleInterop": true,
|
|
12
|
-
"skipLibCheck": true,
|
|
13
|
-
"outDir": "./dist",
|
|
14
|
-
"rootDir": "./src"
|
|
15
|
-
},
|
|
16
|
-
"include": ["src/**/*.ts"],
|
|
17
|
-
"exclude": ["node_modules", "dist"]
|
|
18
|
-
}
|