@gradio/imageeditor 0.12.14 → 0.14.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 (131) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/Example.svelte +1 -1
  3. package/IMAGE_EDITOR_OVERVIEW.md +224 -0
  4. package/ImageEditor.stories.svelte +13 -5
  5. package/Index.svelte +46 -45
  6. package/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +65 -103
  7. package/README.md +2 -0
  8. package/dist/Example.svelte.d.ts +1 -1
  9. package/dist/Index.svelte +35 -33
  10. package/dist/Index.svelte.d.ts +25 -16
  11. package/dist/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +54 -88
  12. package/dist/{shared/InteractiveImageEditor.svelte.d.ts → InteractiveImageEditor.svelte.d.ts} +17 -15
  13. package/dist/shared/Anchor.svelte +311 -0
  14. package/dist/shared/Anchor.svelte.d.ts +18 -0
  15. package/dist/shared/Controls.svelte +169 -18
  16. package/dist/shared/Controls.svelte.d.ts +9 -2
  17. package/dist/shared/{tools/IconButton.svelte → IconButton.svelte} +19 -3
  18. package/dist/shared/{tools/IconButton.svelte.d.ts → IconButton.svelte.d.ts} +4 -1
  19. package/dist/shared/ImageEditor.svelte +572 -289
  20. package/dist/shared/ImageEditor.svelte.d.ts +54 -56
  21. package/dist/shared/Layers.svelte +260 -0
  22. package/dist/shared/Layers.svelte.d.ts +37 -0
  23. package/dist/shared/Resize.svelte +218 -0
  24. package/dist/shared/Resize.svelte.d.ts +29 -0
  25. package/dist/shared/SecondaryToolbar.svelte +55 -0
  26. package/dist/shared/SecondaryToolbar.svelte.d.ts +37 -0
  27. package/dist/shared/Toolbar.svelte +337 -0
  28. package/dist/shared/Toolbar.svelte.d.ts +42 -0
  29. package/dist/shared/{tools → brush}/BrushOptions.svelte +68 -64
  30. package/dist/shared/{tools → brush}/BrushOptions.svelte.d.ts +9 -9
  31. package/dist/shared/{tools → brush}/BrushSize.svelte +2 -27
  32. package/dist/shared/{tools → brush}/ColorField.svelte +2 -1
  33. package/dist/shared/{tools → brush}/ColorField.svelte.d.ts +1 -0
  34. package/dist/shared/{tools → brush}/ColorPicker.svelte +81 -9
  35. package/dist/shared/{tools → brush}/ColorPicker.svelte.d.ts +1 -0
  36. package/dist/shared/{tools → brush}/ColorSwatch.svelte +78 -38
  37. package/dist/shared/{tools → brush}/ColorSwatch.svelte.d.ts +6 -2
  38. package/dist/shared/brush/brush-cursor.d.ts +101 -0
  39. package/dist/shared/brush/brush-cursor.js +327 -0
  40. package/dist/shared/brush/brush-textures.d.ts +65 -0
  41. package/dist/shared/brush/brush-textures.js +385 -0
  42. package/dist/shared/brush/brush-utils.d.ts +32 -0
  43. package/dist/shared/brush/brush-utils.js +59 -0
  44. package/dist/shared/brush/brush.d.ts +152 -0
  45. package/dist/shared/brush/brush.js +397 -0
  46. package/dist/shared/brush/types.d.ts +37 -0
  47. package/dist/shared/brush/types.js +1 -0
  48. package/dist/shared/core/editor.d.ts +261 -0
  49. package/dist/shared/core/editor.js +1264 -0
  50. package/dist/shared/crop/crop.d.ts +201 -0
  51. package/dist/shared/crop/crop.js +727 -0
  52. package/dist/shared/image/image.d.ts +89 -0
  53. package/dist/shared/image/image.js +183 -0
  54. package/dist/shared/resize/resize.d.ts +270 -0
  55. package/dist/shared/resize/resize.js +1413 -0
  56. package/dist/shared/types.d.ts +16 -0
  57. package/dist/shared/utils/pixi.d.ts +16 -57
  58. package/dist/shared/utils/pixi.js +15 -113
  59. package/dist/shared/zoom/zoom.d.ts +136 -0
  60. package/dist/shared/zoom/zoom.js +548 -0
  61. package/package.json +8 -7
  62. package/shared/Anchor.svelte +384 -0
  63. package/shared/Controls.svelte +178 -37
  64. package/shared/{tools/IconButton.svelte → IconButton.svelte} +21 -4
  65. package/shared/ImageEditor.svelte +712 -350
  66. package/shared/Layers.svelte +282 -0
  67. package/shared/Resize.svelte +239 -0
  68. package/shared/SecondaryToolbar.svelte +67 -0
  69. package/shared/Toolbar.svelte +386 -0
  70. package/shared/brush/BRUSH_TOOL.md +134 -0
  71. package/shared/brush/BrushOptions.svelte +200 -0
  72. package/shared/{tools → brush}/BrushSize.svelte +2 -30
  73. package/shared/{tools → brush}/ColorField.svelte +3 -1
  74. package/shared/{tools → brush}/ColorPicker.svelte +86 -9
  75. package/shared/{tools → brush}/ColorSwatch.svelte +95 -42
  76. package/shared/brush/brush-cursor.ts +408 -0
  77. package/shared/brush/brush-textures.ts +485 -0
  78. package/shared/brush/brush-utils.ts +76 -0
  79. package/shared/brush/brush.ts +510 -0
  80. package/shared/brush/types.ts +41 -0
  81. package/shared/core/EDITOR.md +182 -0
  82. package/shared/core/editor.ts +1788 -0
  83. package/shared/crop/CROP.md +139 -0
  84. package/shared/crop/crop.ts +1027 -0
  85. package/shared/image/IMAGE.md +135 -0
  86. package/shared/image/image.ts +298 -0
  87. package/shared/resize/resize.ts +1894 -0
  88. package/shared/types.ts +19 -0
  89. package/shared/utils/pixi.ts +19 -246
  90. package/shared/zoom/zoom.ts +729 -0
  91. package/dist/shared/layers/Layers.svelte +0 -231
  92. package/dist/shared/layers/Layers.svelte.d.ts +0 -18
  93. package/dist/shared/layers/utils.d.ts +0 -73
  94. package/dist/shared/layers/utils.js +0 -156
  95. package/dist/shared/tools/Brush.svelte +0 -215
  96. package/dist/shared/tools/Brush.svelte.d.ts +0 -29
  97. package/dist/shared/tools/Crop.svelte +0 -155
  98. package/dist/shared/tools/Crop.svelte.d.ts +0 -16
  99. package/dist/shared/tools/Cropper.svelte +0 -271
  100. package/dist/shared/tools/Cropper.svelte.d.ts +0 -41
  101. package/dist/shared/tools/Handle.svelte +0 -260
  102. package/dist/shared/tools/Handle.svelte.d.ts +0 -29
  103. package/dist/shared/tools/Sources.svelte +0 -257
  104. package/dist/shared/tools/Sources.svelte.d.ts +0 -34
  105. package/dist/shared/tools/Tools.svelte +0 -102
  106. package/dist/shared/tools/Tools.svelte.d.ts +0 -48
  107. package/dist/shared/tools/brush.d.ts +0 -62
  108. package/dist/shared/tools/brush.js +0 -133
  109. package/dist/shared/tools/crop.d.ts +0 -15
  110. package/dist/shared/tools/crop.js +0 -154
  111. package/dist/shared/tools/index.d.ts +0 -5
  112. package/dist/shared/tools/index.js +0 -5
  113. package/dist/shared/tools/sources.d.ts +0 -46
  114. package/dist/shared/tools/sources.js +0 -121
  115. package/dist/shared/tools/types.d.ts +0 -1
  116. package/shared/layers/Layers.svelte +0 -250
  117. package/shared/layers/utils.ts +0 -294
  118. package/shared/tools/Brush.svelte +0 -280
  119. package/shared/tools/BrushOptions.svelte +0 -180
  120. package/shared/tools/Crop.svelte +0 -189
  121. package/shared/tools/Cropper.svelte +0 -346
  122. package/shared/tools/Handle.svelte +0 -289
  123. package/shared/tools/Sources.svelte +0 -283
  124. package/shared/tools/Tools.svelte +0 -162
  125. package/shared/tools/brush.ts +0 -247
  126. package/shared/tools/crop.ts +0 -213
  127. package/shared/tools/index.ts +0 -5
  128. package/shared/tools/sources.ts +0 -195
  129. package/shared/tools/types.ts +0 -1
  130. /package/dist/shared/{tools → brush}/BrushSize.svelte.d.ts +0 -0
  131. /package/dist/shared/{tools/types.js → types.js} +0 -0
@@ -0,0 +1,510 @@
1
+ import { Container, type FederatedPointerEvent } from "pixi.js";
2
+ import { type ImageEditorContext, type Tool } from "../core/editor";
3
+ import { type Tool as ToolbarTool, type Subtool } from "../Toolbar.svelte";
4
+ import type { ColorInput } from "tinycolor2";
5
+ import tinycolor from "tinycolor2";
6
+ import { BrushCursor } from "./brush-cursor";
7
+ import { BrushTextures } from "./brush-textures";
8
+ import { recurse_set_cursor } from "./brush-utils";
9
+ import { type BrushState } from "./types";
10
+
11
+ /**
12
+ * BrushTool class implements the Tool interface for drawing and erasing.
13
+ * @class
14
+ * @implements {Tool}
15
+ */
16
+ export class BrushTool implements Tool {
17
+ /**
18
+ * The name of the tool.
19
+ * @type {string}
20
+ */
21
+ name = "brush" as const;
22
+
23
+ /**
24
+ * The context of the image editor.
25
+ * @private
26
+ */
27
+ private image_editor_context!: ImageEditorContext;
28
+
29
+ /**
30
+ * The current tool selected in the toolbar.
31
+ * @private
32
+ */
33
+ private current_tool!: ToolbarTool;
34
+
35
+ /**
36
+ * The current subtool selected in the toolbar.
37
+ * @private
38
+ */
39
+ private current_subtool!: Subtool;
40
+
41
+ /**
42
+ * The state of the brush tool.
43
+ * @private
44
+ */
45
+ private state: BrushState = {
46
+ opacity: 1,
47
+ brush_size: 10,
48
+ color: "#000000",
49
+ mode: "draw"
50
+ };
51
+
52
+ /**
53
+ * Size settings for brush and eraser
54
+ * @private
55
+ */
56
+ private brush_size = 10;
57
+ private eraser_size = 20;
58
+
59
+ /**
60
+ * Drawing state
61
+ * @private
62
+ */
63
+ private is_drawing = false;
64
+ private last_x = 0;
65
+ private last_y = 0;
66
+ private scale = 1;
67
+
68
+ /**
69
+ * Event listeners
70
+ * @private
71
+ */
72
+ private _bound_pointer_down: ((event: FederatedPointerEvent) => void) | null =
73
+ null;
74
+ private _bound_pointer_move: ((event: FederatedPointerEvent) => void) | null =
75
+ null;
76
+ private _bound_pointer_up: (() => void) | null = null;
77
+ private event_callbacks: Map<string, (() => void)[]> = new Map();
78
+ /**
79
+ * Utility modules
80
+ * @private
81
+ */
82
+ private brush_cursor: BrushCursor | null = null;
83
+ private brush_textures: BrushTextures | null = null;
84
+
85
+ /**
86
+ * Sets up the brush tool with the given context, tool, and subtool.
87
+ * @param {ImageEditorContext} context - The image editor context.
88
+ * @param {ToolbarTool} tool - The tool from the toolbar.
89
+ * @param {Subtool} subtool - The subtool from the toolbar.
90
+ * @returns {Promise<void>}
91
+ */
92
+ async setup(
93
+ context: ImageEditorContext,
94
+ tool: ToolbarTool,
95
+ subtool: Subtool
96
+ ): Promise<void> {
97
+ // Store context and tool information
98
+ this.image_editor_context = context;
99
+ this.current_tool = tool;
100
+ this.current_subtool = subtool;
101
+
102
+ // Set mode based on tool
103
+ this.state.mode = tool === "erase" ? "erase" : "draw";
104
+
105
+ // Set size based on mode
106
+ if (this.state.mode === "draw") {
107
+ this.state.brush_size = this.brush_size;
108
+ } else {
109
+ this.state.brush_size = this.eraser_size;
110
+ }
111
+
112
+ // Subscribe to scale changes
113
+ context.scale.subscribe((scale) => {
114
+ this.scale = scale;
115
+ if (this.brush_cursor) {
116
+ this.brush_cursor.update_state(this.state, this.scale);
117
+ }
118
+ });
119
+
120
+ // Initialize brush cursor
121
+ this.brush_cursor = new BrushCursor(
122
+ this.image_editor_context,
123
+ this.state,
124
+ this.scale
125
+ );
126
+
127
+ // Set initial active state
128
+ this.brush_cursor.set_active(tool === "draw" || tool === "erase");
129
+
130
+ // Initialize brush textures
131
+ this.brush_textures = new BrushTextures(
132
+ this.image_editor_context,
133
+ context.app
134
+ );
135
+ this.brush_textures.initialize_textures();
136
+
137
+ // Set up event listeners
138
+ this.setup_event_listeners();
139
+
140
+ // Set cursor style for image container
141
+ this.handle_cursors(tool);
142
+ }
143
+
144
+ /**
145
+ * Handles cursor styles for the brush tool.
146
+ * @param {ToolbarTool} tool - The current tool.
147
+ * @private
148
+ */
149
+ private handle_cursors(tool: ToolbarTool): void {
150
+ // Set cursor style to 'none' for all children of the image container
151
+ recurse_set_cursor(
152
+ this.image_editor_context.image_container.children as Container[],
153
+ "none"
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Sets the current tool and subtool.
159
+ * @param {ToolbarTool} tool - The current tool.
160
+ * @param {Subtool} subtool - The current subtool.
161
+ */
162
+ set_tool(tool: ToolbarTool, subtool: Subtool): void {
163
+ // Update current tool and subtool
164
+ this.current_tool = tool;
165
+ this.current_subtool = subtool;
166
+
167
+ if (this.current_tool !== "erase" && this.current_tool !== "draw") {
168
+ this.commit_pending_changes();
169
+ }
170
+
171
+ // Commit any pending changes
172
+
173
+ // Set brush cursor active state
174
+ if (this.brush_cursor) {
175
+ this.brush_cursor.set_active(tool === "draw" || tool === "erase");
176
+ }
177
+
178
+ // Update mode based on tool
179
+ const new_mode = tool === "erase" ? "erase" : "draw";
180
+ if (tool === "erase" || tool === "draw") {
181
+ this.brush_textures?.initialize_textures();
182
+ }
183
+ if (this.state.mode !== new_mode) {
184
+ this.state.mode = new_mode;
185
+
186
+ // Update brush size based on mode
187
+ if (this.state.mode === "draw") {
188
+ this.state.brush_size = this.brush_size;
189
+ } else {
190
+ this.state.brush_size = this.eraser_size;
191
+ }
192
+
193
+ // Update cursor
194
+ if (this.brush_cursor) {
195
+ this.brush_cursor.update_state(this.state, this.scale);
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Commits any pending changes to the canvas.
202
+ * @private
203
+ */
204
+ private commit_pending_changes(): void {
205
+ if (this.is_drawing) {
206
+ this.on_pointer_up();
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Called when the user presses the mouse button.
212
+ * @param {FederatedPointerEvent} event - The pointer event.
213
+ * @private
214
+ */
215
+ private on_pointer_down(event: FederatedPointerEvent): void {
216
+ const current_layer =
217
+ this.image_editor_context.layer_manager.get_active_layer();
218
+ if (
219
+ !current_layer?.visible ||
220
+ (this.current_tool !== "erase" && this.current_tool !== "draw")
221
+ ) {
222
+ return;
223
+ }
224
+
225
+ if (this.brush_cursor && !this.brush_cursor.is_over_image()) return;
226
+
227
+ // Preserve the current canvas state
228
+ if (this.brush_textures) {
229
+ this.brush_textures.preserve_canvas_state();
230
+ }
231
+
232
+ // Get the local position
233
+ const local_pos = this.image_editor_context.image_container.toLocal(
234
+ event.global
235
+ );
236
+
237
+ // Start drawing
238
+ this.is_drawing = true;
239
+ this.last_x = local_pos.x;
240
+ this.last_y = local_pos.y;
241
+
242
+ // Draw a single point
243
+ if (this.brush_textures) {
244
+ this.brush_textures.draw_segment(
245
+ local_pos.x,
246
+ local_pos.y,
247
+ local_pos.x,
248
+ local_pos.y,
249
+ this.state.brush_size,
250
+ this.state.color,
251
+ this.state.opacity,
252
+ this.state.mode
253
+ );
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Called when the user moves the mouse while drawing.
259
+ * @param {FederatedPointerEvent} event - The pointer event.
260
+ * @private
261
+ */
262
+ private on_pointer_move(event: FederatedPointerEvent): void {
263
+ // Update cursor position
264
+ if (this.brush_cursor) {
265
+ this.brush_cursor.update_cursor_position(event);
266
+ }
267
+
268
+ // If not drawing, exit
269
+ if (!this.is_drawing) return;
270
+
271
+ // Get the local position
272
+ const local_pos = this.image_editor_context.image_container.toLocal(
273
+ event.global
274
+ );
275
+
276
+ // Draw line from last position to current position
277
+ if (this.brush_textures) {
278
+ this.brush_textures.draw_segment(
279
+ this.last_x,
280
+ this.last_y,
281
+ local_pos.x,
282
+ local_pos.y,
283
+ this.state.brush_size,
284
+ this.state.color,
285
+ this.state.opacity,
286
+ this.state.mode
287
+ );
288
+ }
289
+
290
+ // Update last position
291
+ this.last_x = local_pos.x;
292
+ this.last_y = local_pos.y;
293
+ }
294
+
295
+ /**
296
+ * Called when the user releases the mouse button.
297
+ * @private
298
+ */
299
+ private on_pointer_up(): void {
300
+ // If not drawing, exit
301
+ if (!this.is_drawing) return;
302
+
303
+ // Stop drawing
304
+ this.is_drawing = false;
305
+
306
+ // Commit the stroke
307
+ if (this.brush_textures) {
308
+ this.brush_textures.commit_stroke();
309
+ }
310
+
311
+ this.notify("change");
312
+ }
313
+
314
+ /**
315
+ * Sets up event listeners for drawing.
316
+ * @private
317
+ */
318
+ private setup_event_listeners(): void {
319
+ // Remove existing listeners if they exist
320
+ this.cleanup_event_listeners();
321
+
322
+ // Create bound event handlers
323
+ this._bound_pointer_down = this.on_pointer_down.bind(this);
324
+ this._bound_pointer_move = this.on_pointer_move.bind(this);
325
+ this._bound_pointer_up = this.on_pointer_up.bind(this);
326
+
327
+ // Set interactive mode for image container
328
+ const image_container = this.image_editor_context.image_container;
329
+ image_container.eventMode = "static";
330
+ image_container.interactiveChildren = true;
331
+
332
+ // Setup for stage
333
+ const stage = this.image_editor_context.app.stage;
334
+ stage.eventMode = "static";
335
+
336
+ // Add pointer events to the stage for global tracking
337
+ stage.on("pointerdown", this._bound_pointer_down);
338
+ stage.on("pointermove", this._bound_pointer_move);
339
+ stage.on("pointerup", this._bound_pointer_up);
340
+ stage.on("pointerupoutside", this._bound_pointer_up);
341
+
342
+ // Set up cursor event listeners
343
+ if (this.brush_cursor) {
344
+ this.brush_cursor.setup_event_listeners();
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Cleans up event listeners.
350
+ * @private
351
+ */
352
+ private cleanup_event_listeners(): void {
353
+ const stage = this.image_editor_context.app.stage;
354
+
355
+ // Remove event listeners if they exist
356
+ if (this._bound_pointer_down) {
357
+ stage.off("pointerdown", this._bound_pointer_down);
358
+ this._bound_pointer_down = null;
359
+ }
360
+
361
+ if (this._bound_pointer_move) {
362
+ stage.off("pointermove", this._bound_pointer_move);
363
+ this._bound_pointer_move = null;
364
+ }
365
+
366
+ if (this._bound_pointer_up) {
367
+ stage.off("pointerup", this._bound_pointer_up);
368
+ stage.off("pointerupoutside", this._bound_pointer_up);
369
+ this._bound_pointer_up = null;
370
+ }
371
+
372
+ // Clean up cursor event listeners
373
+ if (this.brush_cursor) {
374
+ this.brush_cursor.cleanup_event_listeners();
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Sets the brush size.
380
+ * @param {number} size - The new brush size.
381
+ */
382
+ set_brush_size(size: number): void {
383
+ // Set brush size
384
+ this.brush_size = size;
385
+
386
+ // Update state if in draw mode
387
+ if (this.state.mode === "draw") {
388
+ this.state.brush_size = size;
389
+
390
+ // Update cursor
391
+ if (this.brush_cursor) {
392
+ this.brush_cursor.update_state(this.state, this.scale);
393
+ }
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Sets the brush color.
399
+ * @param {string|ColorInput} color - The new brush color.
400
+ */
401
+ set_brush_color(color: string | ColorInput): void {
402
+ // Convert to hex string
403
+ const color_string = tinycolor(color).toHexString();
404
+
405
+ // Set color
406
+ this.state.color = color_string;
407
+
408
+ // Update cursor
409
+ if (this.brush_cursor) {
410
+ this.brush_cursor.update_state(this.state, this.scale);
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Sets the brush opacity.
416
+ * @param {number} opacity - The new brush opacity.
417
+ */
418
+ set_brush_opacity(opacity: number): void {
419
+ // Clamp opacity between 0 and 1
420
+ const clamped_opacity = Math.max(0, Math.min(1, opacity));
421
+
422
+ // Set opacity
423
+ this.state.opacity = clamped_opacity;
424
+
425
+ // Update cursor
426
+ if (this.brush_cursor) {
427
+ this.brush_cursor.update_state(this.state, this.scale);
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Sets the eraser size.
433
+ * @param {number} size - The new eraser size.
434
+ */
435
+ set_eraser_size(size: number): void {
436
+ // Store the new eraser size
437
+ this.eraser_size = size;
438
+
439
+ // Update state if in erase mode
440
+ if (this.state.mode === "erase") {
441
+ this.state.brush_size = size;
442
+
443
+ // Update cursor
444
+ if (this.brush_cursor) {
445
+ this.brush_cursor.update_state(this.state, this.scale);
446
+ }
447
+ }
448
+ }
449
+
450
+ /**
451
+ * Gets the current size based on mode (brush or eraser).
452
+ * @returns {number} The current size.
453
+ */
454
+ get_current_size(): number {
455
+ return this.state.mode === "draw" ? this.brush_size : this.eraser_size;
456
+ }
457
+
458
+ /**
459
+ * Shows or hides the brush preview.
460
+ * @param {boolean} show - Whether to show the preview.
461
+ */
462
+ preview_brush(show: boolean): void {
463
+ if (this.brush_cursor) {
464
+ this.brush_cursor.preview_brush(show);
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Cleans up resources used by the brush tool.
470
+ */
471
+ cleanup(): void {
472
+ // Commit any pending changes
473
+ this.commit_pending_changes();
474
+
475
+ // Clean up event listeners
476
+ this.cleanup_event_listeners();
477
+
478
+ // Clean up brush textures
479
+ if (this.brush_textures) {
480
+ this.brush_textures.cleanup();
481
+ this.brush_textures = null;
482
+ }
483
+
484
+ // Clean up brush cursor
485
+ if (this.brush_cursor) {
486
+ this.brush_cursor.cleanup();
487
+ this.brush_cursor = null;
488
+ }
489
+ }
490
+
491
+ on<T extends string>(event: T, callback: () => void): void {
492
+ this.event_callbacks.set(event, [
493
+ ...(this.event_callbacks.get(event) || []),
494
+ callback
495
+ ]);
496
+ }
497
+
498
+ off<T extends string>(event: T, callback: () => void): void {
499
+ this.event_callbacks.set(
500
+ event,
501
+ this.event_callbacks.get(event)?.filter((cb) => cb !== callback) || []
502
+ );
503
+ }
504
+
505
+ private notify<T extends string>(event: T): void {
506
+ for (const callback of this.event_callbacks.get(event) || []) {
507
+ callback();
508
+ }
509
+ }
510
+ }
@@ -0,0 +1,41 @@
1
+ import { type ColorInput } from "tinycolor2";
2
+
3
+ /**
4
+ * Represents the state of the brush tool.
5
+ * @interface BrushState
6
+ * @property {number} opacity - The opacity of the brush.
7
+ * @property {number} brush_size - The size of the brush.
8
+ * @property {string} color - The color of the brush in hex format.
9
+ * @property {"draw"|"erase"} mode - The mode of the brush, either draw or erase.
10
+ */
11
+ export interface BrushState {
12
+ opacity: number;
13
+ brush_size: number;
14
+ color: string;
15
+ mode: "draw" | "erase";
16
+ }
17
+
18
+ export interface Eraser {
19
+ /**
20
+ * The default size of the eraser.
21
+ */
22
+ default_size: number | "auto";
23
+ }
24
+
25
+ export type ColorTuple = [ColorInput, number]; // [color, opacity]
26
+
27
+ export interface Brush extends Eraser {
28
+ /**
29
+ * The default color of the brush.
30
+ */
31
+ default_color: ColorInput;
32
+ /**
33
+ * The colors to show in the color swatch.
34
+ * Can be either simple color strings or [color, opacity] tuples.
35
+ */
36
+ colors: (ColorInput | ColorTuple)[];
37
+ /**
38
+ * Whether to show _only_ the color swatches specified in `colors`, or to show the color swatches specified in `colors` along with the colorpicker.
39
+ */
40
+ color_mode: "fixed" | "defaults";
41
+ }