@gradio/imageeditor 0.12.13 → 0.13.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/CHANGELOG.md +23 -0
- package/Example.svelte +1 -1
- package/IMAGE_EDITOR_OVERVIEW.md +224 -0
- package/ImageEditor.stories.svelte +4 -4
- package/Index.svelte +35 -46
- package/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +56 -103
- package/dist/Example.svelte.d.ts +1 -1
- package/dist/Index.svelte +29 -33
- package/dist/Index.svelte.d.ts +17 -16
- package/dist/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +47 -88
- package/dist/{shared/InteractiveImageEditor.svelte.d.ts → InteractiveImageEditor.svelte.d.ts} +13 -15
- package/dist/shared/Anchor.svelte +311 -0
- package/dist/shared/Anchor.svelte.d.ts +18 -0
- package/dist/shared/Controls.svelte +162 -23
- package/dist/shared/Controls.svelte.d.ts +7 -2
- package/dist/shared/{tools/IconButton.svelte → IconButton.svelte} +19 -3
- package/dist/shared/{tools/IconButton.svelte.d.ts → IconButton.svelte.d.ts} +4 -1
- package/dist/shared/ImageEditor.svelte +546 -289
- package/dist/shared/ImageEditor.svelte.d.ts +51 -56
- package/dist/shared/Layers.svelte +233 -0
- package/dist/shared/Layers.svelte.d.ts +35 -0
- package/dist/shared/Resize.svelte +218 -0
- package/dist/shared/Resize.svelte.d.ts +29 -0
- package/dist/shared/SecondaryToolbar.svelte +54 -0
- package/dist/shared/SecondaryToolbar.svelte.d.ts +35 -0
- package/dist/shared/Toolbar.svelte +337 -0
- package/dist/shared/Toolbar.svelte.d.ts +42 -0
- package/dist/shared/{tools → brush}/BrushOptions.svelte +68 -64
- package/dist/shared/{tools → brush}/BrushOptions.svelte.d.ts +9 -9
- package/dist/shared/{tools → brush}/BrushSize.svelte +2 -27
- package/dist/shared/{tools → brush}/ColorField.svelte +2 -1
- package/dist/shared/{tools → brush}/ColorField.svelte.d.ts +1 -0
- package/dist/shared/{tools → brush}/ColorPicker.svelte +81 -9
- package/dist/shared/{tools → brush}/ColorPicker.svelte.d.ts +1 -0
- package/dist/shared/{tools → brush}/ColorSwatch.svelte +78 -38
- package/dist/shared/{tools → brush}/ColorSwatch.svelte.d.ts +6 -2
- package/dist/shared/brush/brush-cursor.d.ts +101 -0
- package/dist/shared/brush/brush-cursor.js +327 -0
- package/dist/shared/brush/brush-textures.d.ts +71 -0
- package/dist/shared/brush/brush-textures.js +404 -0
- package/dist/shared/brush/brush-utils.d.ts +32 -0
- package/dist/shared/brush/brush-utils.js +59 -0
- package/dist/shared/brush/brush.d.ts +152 -0
- package/dist/shared/brush/brush.js +396 -0
- package/dist/shared/brush/types.d.ts +37 -0
- package/dist/shared/brush/types.js +1 -0
- package/dist/shared/core/editor.d.ts +252 -0
- package/dist/shared/core/editor.js +1221 -0
- package/dist/shared/crop/crop.d.ts +201 -0
- package/dist/shared/crop/crop.js +727 -0
- package/dist/shared/image/image.d.ts +89 -0
- package/dist/shared/image/image.js +185 -0
- package/dist/shared/resize/resize.d.ts +270 -0
- package/dist/shared/resize/resize.js +1413 -0
- package/dist/shared/types.d.ts +12 -0
- package/dist/shared/utils/pixi.d.ts +16 -57
- package/dist/shared/utils/pixi.js +15 -113
- package/dist/shared/zoom/zoom.d.ts +136 -0
- package/dist/shared/zoom/zoom.js +548 -0
- package/package.json +11 -10
- package/shared/Anchor.svelte +384 -0
- package/shared/Controls.svelte +194 -27
- package/shared/{tools/IconButton.svelte → IconButton.svelte} +21 -4
- package/shared/ImageEditor.svelte +682 -349
- package/shared/Layers.svelte +249 -0
- package/shared/Resize.svelte +239 -0
- package/shared/SecondaryToolbar.svelte +61 -0
- package/shared/Toolbar.svelte +386 -0
- package/shared/brush/BRUSH_TOOL.md +134 -0
- package/shared/brush/BrushOptions.svelte +200 -0
- package/shared/{tools → brush}/BrushSize.svelte +2 -30
- package/shared/{tools → brush}/ColorField.svelte +3 -1
- package/shared/{tools → brush}/ColorPicker.svelte +86 -9
- package/shared/{tools → brush}/ColorSwatch.svelte +95 -42
- package/shared/brush/brush-cursor.ts +408 -0
- package/shared/brush/brush-textures.ts +508 -0
- package/shared/brush/brush-utils.ts +76 -0
- package/shared/brush/brush.ts +506 -0
- package/shared/brush/types.ts +41 -0
- package/shared/core/EDITOR.md +182 -0
- package/shared/core/editor.ts +1730 -0
- package/shared/crop/CROP.md +139 -0
- package/shared/crop/crop.ts +1027 -0
- package/shared/image/IMAGE.md +135 -0
- package/shared/image/image.ts +300 -0
- package/shared/resize/resize.ts +1894 -0
- package/shared/types.ts +14 -0
- package/shared/utils/pixi.ts +19 -246
- package/shared/zoom/zoom.ts +729 -0
- package/dist/shared/layers/Layers.svelte +0 -231
- package/dist/shared/layers/Layers.svelte.d.ts +0 -18
- package/dist/shared/layers/utils.d.ts +0 -73
- package/dist/shared/layers/utils.js +0 -156
- package/dist/shared/tools/Brush.svelte +0 -215
- package/dist/shared/tools/Brush.svelte.d.ts +0 -29
- package/dist/shared/tools/Crop.svelte +0 -155
- package/dist/shared/tools/Crop.svelte.d.ts +0 -16
- package/dist/shared/tools/Cropper.svelte +0 -271
- package/dist/shared/tools/Cropper.svelte.d.ts +0 -41
- package/dist/shared/tools/Handle.svelte +0 -260
- package/dist/shared/tools/Handle.svelte.d.ts +0 -29
- package/dist/shared/tools/Sources.svelte +0 -257
- package/dist/shared/tools/Sources.svelte.d.ts +0 -34
- package/dist/shared/tools/Tools.svelte +0 -102
- package/dist/shared/tools/Tools.svelte.d.ts +0 -48
- package/dist/shared/tools/brush.d.ts +0 -62
- package/dist/shared/tools/brush.js +0 -133
- package/dist/shared/tools/crop.d.ts +0 -15
- package/dist/shared/tools/crop.js +0 -154
- package/dist/shared/tools/index.d.ts +0 -5
- package/dist/shared/tools/index.js +0 -5
- package/dist/shared/tools/sources.d.ts +0 -46
- package/dist/shared/tools/sources.js +0 -121
- package/dist/shared/tools/types.d.ts +0 -1
- package/shared/layers/Layers.svelte +0 -250
- package/shared/layers/utils.ts +0 -294
- package/shared/tools/Brush.svelte +0 -280
- package/shared/tools/BrushOptions.svelte +0 -180
- package/shared/tools/Crop.svelte +0 -189
- package/shared/tools/Cropper.svelte +0 -346
- package/shared/tools/Handle.svelte +0 -289
- package/shared/tools/Sources.svelte +0 -283
- package/shared/tools/Tools.svelte +0 -162
- package/shared/tools/brush.ts +0 -247
- package/shared/tools/crop.ts +0 -213
- package/shared/tools/index.ts +0 -5
- package/shared/tools/sources.ts +0 -195
- package/shared/tools/types.ts +0 -1
- /package/dist/shared/{tools → brush}/BrushSize.svelte.d.ts +0 -0
- /package/dist/shared/{tools/types.js → types.js} +0 -0
|
@@ -0,0 +1,506 @@
|
|
|
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
|
+
// Don't draw if not over image
|
|
217
|
+
if (this.current_tool !== "erase" && this.current_tool !== "draw") {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (this.brush_cursor && !this.brush_cursor.is_over_image()) return;
|
|
222
|
+
|
|
223
|
+
// Preserve the current canvas state
|
|
224
|
+
if (this.brush_textures) {
|
|
225
|
+
this.brush_textures.preserve_canvas_state();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Get the local position
|
|
229
|
+
const local_pos = this.image_editor_context.image_container.toLocal(
|
|
230
|
+
event.global
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Start drawing
|
|
234
|
+
this.is_drawing = true;
|
|
235
|
+
this.last_x = local_pos.x;
|
|
236
|
+
this.last_y = local_pos.y;
|
|
237
|
+
|
|
238
|
+
// Draw a single point
|
|
239
|
+
if (this.brush_textures) {
|
|
240
|
+
this.brush_textures.draw_segment(
|
|
241
|
+
local_pos.x,
|
|
242
|
+
local_pos.y,
|
|
243
|
+
local_pos.x,
|
|
244
|
+
local_pos.y,
|
|
245
|
+
this.state.brush_size,
|
|
246
|
+
this.state.color,
|
|
247
|
+
this.state.opacity,
|
|
248
|
+
this.state.mode
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Called when the user moves the mouse while drawing.
|
|
255
|
+
* @param {FederatedPointerEvent} event - The pointer event.
|
|
256
|
+
* @private
|
|
257
|
+
*/
|
|
258
|
+
private on_pointer_move(event: FederatedPointerEvent): void {
|
|
259
|
+
// Update cursor position
|
|
260
|
+
if (this.brush_cursor) {
|
|
261
|
+
this.brush_cursor.update_cursor_position(event);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// If not drawing, exit
|
|
265
|
+
if (!this.is_drawing) return;
|
|
266
|
+
|
|
267
|
+
// Get the local position
|
|
268
|
+
const local_pos = this.image_editor_context.image_container.toLocal(
|
|
269
|
+
event.global
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Draw line from last position to current position
|
|
273
|
+
if (this.brush_textures) {
|
|
274
|
+
this.brush_textures.draw_segment(
|
|
275
|
+
this.last_x,
|
|
276
|
+
this.last_y,
|
|
277
|
+
local_pos.x,
|
|
278
|
+
local_pos.y,
|
|
279
|
+
this.state.brush_size,
|
|
280
|
+
this.state.color,
|
|
281
|
+
this.state.opacity,
|
|
282
|
+
this.state.mode
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Update last position
|
|
287
|
+
this.last_x = local_pos.x;
|
|
288
|
+
this.last_y = local_pos.y;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Called when the user releases the mouse button.
|
|
293
|
+
* @private
|
|
294
|
+
*/
|
|
295
|
+
private on_pointer_up(): void {
|
|
296
|
+
// If not drawing, exit
|
|
297
|
+
if (!this.is_drawing) return;
|
|
298
|
+
|
|
299
|
+
// Stop drawing
|
|
300
|
+
this.is_drawing = false;
|
|
301
|
+
|
|
302
|
+
// Commit the stroke
|
|
303
|
+
if (this.brush_textures) {
|
|
304
|
+
this.brush_textures.commit_stroke();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.notify("change");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Sets up event listeners for drawing.
|
|
312
|
+
* @private
|
|
313
|
+
*/
|
|
314
|
+
private setup_event_listeners(): void {
|
|
315
|
+
// Remove existing listeners if they exist
|
|
316
|
+
this.cleanup_event_listeners();
|
|
317
|
+
|
|
318
|
+
// Create bound event handlers
|
|
319
|
+
this._bound_pointer_down = this.on_pointer_down.bind(this);
|
|
320
|
+
this._bound_pointer_move = this.on_pointer_move.bind(this);
|
|
321
|
+
this._bound_pointer_up = this.on_pointer_up.bind(this);
|
|
322
|
+
|
|
323
|
+
// Set interactive mode for image container
|
|
324
|
+
const image_container = this.image_editor_context.image_container;
|
|
325
|
+
image_container.eventMode = "static";
|
|
326
|
+
image_container.interactiveChildren = true;
|
|
327
|
+
|
|
328
|
+
// Setup for stage
|
|
329
|
+
const stage = this.image_editor_context.app.stage;
|
|
330
|
+
stage.eventMode = "static";
|
|
331
|
+
|
|
332
|
+
// Add pointer events to the stage for global tracking
|
|
333
|
+
stage.on("pointerdown", this._bound_pointer_down);
|
|
334
|
+
stage.on("pointermove", this._bound_pointer_move);
|
|
335
|
+
stage.on("pointerup", this._bound_pointer_up);
|
|
336
|
+
stage.on("pointerupoutside", this._bound_pointer_up);
|
|
337
|
+
|
|
338
|
+
// Set up cursor event listeners
|
|
339
|
+
if (this.brush_cursor) {
|
|
340
|
+
this.brush_cursor.setup_event_listeners();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Cleans up event listeners.
|
|
346
|
+
* @private
|
|
347
|
+
*/
|
|
348
|
+
private cleanup_event_listeners(): void {
|
|
349
|
+
const stage = this.image_editor_context.app.stage;
|
|
350
|
+
|
|
351
|
+
// Remove event listeners if they exist
|
|
352
|
+
if (this._bound_pointer_down) {
|
|
353
|
+
stage.off("pointerdown", this._bound_pointer_down);
|
|
354
|
+
this._bound_pointer_down = null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (this._bound_pointer_move) {
|
|
358
|
+
stage.off("pointermove", this._bound_pointer_move);
|
|
359
|
+
this._bound_pointer_move = null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (this._bound_pointer_up) {
|
|
363
|
+
stage.off("pointerup", this._bound_pointer_up);
|
|
364
|
+
stage.off("pointerupoutside", this._bound_pointer_up);
|
|
365
|
+
this._bound_pointer_up = null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Clean up cursor event listeners
|
|
369
|
+
if (this.brush_cursor) {
|
|
370
|
+
this.brush_cursor.cleanup_event_listeners();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Sets the brush size.
|
|
376
|
+
* @param {number} size - The new brush size.
|
|
377
|
+
*/
|
|
378
|
+
set_brush_size(size: number): void {
|
|
379
|
+
// Set brush size
|
|
380
|
+
this.brush_size = size;
|
|
381
|
+
|
|
382
|
+
// Update state if in draw mode
|
|
383
|
+
if (this.state.mode === "draw") {
|
|
384
|
+
this.state.brush_size = size;
|
|
385
|
+
|
|
386
|
+
// Update cursor
|
|
387
|
+
if (this.brush_cursor) {
|
|
388
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Sets the brush color.
|
|
395
|
+
* @param {string|ColorInput} color - The new brush color.
|
|
396
|
+
*/
|
|
397
|
+
set_brush_color(color: string | ColorInput): void {
|
|
398
|
+
// Convert to hex string
|
|
399
|
+
const color_string = tinycolor(color).toHexString();
|
|
400
|
+
|
|
401
|
+
// Set color
|
|
402
|
+
this.state.color = color_string;
|
|
403
|
+
|
|
404
|
+
// Update cursor
|
|
405
|
+
if (this.brush_cursor) {
|
|
406
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Sets the brush opacity.
|
|
412
|
+
* @param {number} opacity - The new brush opacity.
|
|
413
|
+
*/
|
|
414
|
+
set_brush_opacity(opacity: number): void {
|
|
415
|
+
// Clamp opacity between 0 and 1
|
|
416
|
+
const clamped_opacity = Math.max(0, Math.min(1, opacity));
|
|
417
|
+
|
|
418
|
+
// Set opacity
|
|
419
|
+
this.state.opacity = clamped_opacity;
|
|
420
|
+
|
|
421
|
+
// Update cursor
|
|
422
|
+
if (this.brush_cursor) {
|
|
423
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Sets the eraser size.
|
|
429
|
+
* @param {number} size - The new eraser size.
|
|
430
|
+
*/
|
|
431
|
+
set_eraser_size(size: number): void {
|
|
432
|
+
// Store the new eraser size
|
|
433
|
+
this.eraser_size = size;
|
|
434
|
+
|
|
435
|
+
// Update state if in erase mode
|
|
436
|
+
if (this.state.mode === "erase") {
|
|
437
|
+
this.state.brush_size = size;
|
|
438
|
+
|
|
439
|
+
// Update cursor
|
|
440
|
+
if (this.brush_cursor) {
|
|
441
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Gets the current size based on mode (brush or eraser).
|
|
448
|
+
* @returns {number} The current size.
|
|
449
|
+
*/
|
|
450
|
+
get_current_size(): number {
|
|
451
|
+
return this.state.mode === "draw" ? this.brush_size : this.eraser_size;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Shows or hides the brush preview.
|
|
456
|
+
* @param {boolean} show - Whether to show the preview.
|
|
457
|
+
*/
|
|
458
|
+
preview_brush(show: boolean): void {
|
|
459
|
+
if (this.brush_cursor) {
|
|
460
|
+
this.brush_cursor.preview_brush(show);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Cleans up resources used by the brush tool.
|
|
466
|
+
*/
|
|
467
|
+
cleanup(): void {
|
|
468
|
+
// Commit any pending changes
|
|
469
|
+
this.commit_pending_changes();
|
|
470
|
+
|
|
471
|
+
// Clean up event listeners
|
|
472
|
+
this.cleanup_event_listeners();
|
|
473
|
+
|
|
474
|
+
// Clean up brush textures
|
|
475
|
+
if (this.brush_textures) {
|
|
476
|
+
this.brush_textures.cleanup();
|
|
477
|
+
this.brush_textures = null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Clean up brush cursor
|
|
481
|
+
if (this.brush_cursor) {
|
|
482
|
+
this.brush_cursor.cleanup();
|
|
483
|
+
this.brush_cursor = null;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
on<T extends string>(event: T, callback: () => void): void {
|
|
488
|
+
this.event_callbacks.set(event, [
|
|
489
|
+
...(this.event_callbacks.get(event) || []),
|
|
490
|
+
callback
|
|
491
|
+
]);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
off<T extends string>(event: T, callback: () => void): void {
|
|
495
|
+
this.event_callbacks.set(
|
|
496
|
+
event,
|
|
497
|
+
this.event_callbacks.get(event)?.filter((cb) => cb !== callback) || []
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private notify<T extends string>(event: T): void {
|
|
502
|
+
for (const callback of this.event_callbacks.get(event) || []) {
|
|
503
|
+
callback();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
@@ -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
|
+
}
|