@gradio/imageeditor 0.19.0 → 0.20.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 +21 -0
- package/Example.svelte +9 -3
- package/Index.svelte +23 -24
- package/InteractiveImageEditor.svelte +99 -61
- package/dist/Example.svelte +9 -3
- package/dist/Example.svelte.d.ts +4 -18
- package/dist/Index.svelte +23 -24
- package/dist/Index.svelte.d.ts +2 -19
- package/dist/InteractiveImageEditor.svelte +99 -61
- package/dist/InteractiveImageEditor.svelte.d.ts +15 -32
- package/dist/shared/Anchor.svelte +13 -11
- package/dist/shared/Anchor.svelte.d.ts +4 -19
- package/dist/shared/Controls.svelte +63 -39
- package/dist/shared/Controls.svelte.d.ts +13 -28
- package/dist/shared/IconButton.svelte +38 -17
- package/dist/shared/IconButton.svelte.d.ts +5 -19
- package/dist/shared/ImageEditor.svelte +235 -160
- package/dist/shared/ImageEditor.svelte.d.ts +55 -2
- package/dist/shared/Layers.svelte +43 -29
- package/dist/shared/Layers.svelte.d.ts +10 -25
- package/dist/shared/Resize.svelte +22 -16
- package/dist/shared/Resize.svelte.d.ts +6 -22
- package/dist/shared/SecondaryToolbar.svelte +34 -18
- package/dist/shared/SecondaryToolbar.svelte.d.ts +10 -25
- package/dist/shared/Toolbar.svelte +60 -76
- package/dist/shared/Toolbar.svelte.d.ts +9 -24
- package/dist/shared/brush/BrushOptions.svelte +49 -29
- package/dist/shared/brush/BrushOptions.svelte.d.ts +6 -21
- package/dist/shared/brush/BrushSize.svelte +12 -9
- package/dist/shared/brush/BrushSize.svelte.d.ts +5 -20
- package/dist/shared/brush/ColorField.svelte +21 -16
- package/dist/shared/brush/ColorField.svelte.d.ts +6 -22
- package/dist/shared/brush/ColorPicker.svelte +22 -16
- package/dist/shared/brush/ColorPicker.svelte.d.ts +4 -18
- package/dist/shared/brush/ColorSwatch.svelte +41 -21
- package/dist/shared/brush/ColorSwatch.svelte.d.ts +10 -24
- package/dist/shared/brush/brush.js +7 -8
- package/dist/shared/core/editor.d.ts +5 -0
- package/dist/shared/core/editor.js +43 -2
- package/dist/shared/resize/resize.d.ts +2 -0
- package/dist/shared/resize/resize.js +27 -18
- package/package.json +8 -8
- package/shared/Anchor.svelte +13 -11
- package/shared/Controls.svelte +63 -39
- package/shared/IconButton.svelte +38 -17
- package/shared/ImageEditor.svelte +235 -160
- package/shared/Layers.svelte +43 -29
- package/shared/Resize.svelte +22 -16
- package/shared/SecondaryToolbar.svelte +34 -18
- package/shared/Toolbar.svelte +60 -76
- package/shared/brush/BrushOptions.svelte +49 -29
- package/shared/brush/BrushSize.svelte +12 -9
- package/shared/brush/ColorField.svelte +21 -16
- package/shared/brush/ColorPicker.svelte +22 -16
- package/shared/brush/ColorSwatch.svelte +41 -21
- package/shared/brush/brush.ts +8 -10
- package/shared/core/editor.ts +49 -2
- package/shared/resize/resize.ts +28 -20
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Eyedropper, Plus } from "@gradio/icons";
|
|
3
|
-
import { createEventDispatcher } from "svelte";
|
|
4
3
|
|
|
5
4
|
import tinycolor from "tinycolor2";
|
|
6
5
|
import IconButton from "../IconButton.svelte";
|
|
7
|
-
export let color: string;
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
let {
|
|
8
|
+
color,
|
|
9
|
+
current_mode = $bindable<"hex" | "rgb" | "hsl">("hex"),
|
|
10
|
+
onselected,
|
|
11
|
+
onclose
|
|
12
|
+
}: {
|
|
13
|
+
color: string;
|
|
14
|
+
current_mode?: "hex" | "rgb" | "hsl";
|
|
15
|
+
onselected?: (color: string) => void;
|
|
16
|
+
onclose?: () => void;
|
|
17
|
+
} = $props();
|
|
10
18
|
|
|
11
|
-
const dispatch = createEventDispatcher<{
|
|
12
|
-
selected: string;
|
|
13
|
-
close: void;
|
|
14
|
-
add_color: void;
|
|
15
|
-
}>();
|
|
16
19
|
const modes = [
|
|
17
20
|
["Hex", "hex"],
|
|
18
21
|
["RGB", "rgb"],
|
|
@@ -28,8 +31,10 @@
|
|
|
28
31
|
return tinycolor(color).toHslString();
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
let color_string = $derived(format_color(color, current_mode));
|
|
35
|
+
$effect(() => {
|
|
36
|
+
if (color_string) onselected?.(color_string);
|
|
37
|
+
});
|
|
33
38
|
|
|
34
39
|
function request_eyedropper(): void {
|
|
35
40
|
// @ts-ignore
|
|
@@ -44,23 +49,23 @@
|
|
|
44
49
|
const eyedropper_supported = !!window.EyeDropper;
|
|
45
50
|
|
|
46
51
|
function handle_click(): void {
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
onselected?.(color_string);
|
|
53
|
+
onclose?.();
|
|
49
54
|
}
|
|
50
55
|
</script>
|
|
51
56
|
|
|
52
57
|
<div class="input">
|
|
53
|
-
<button class="swatch" style:background={color}
|
|
58
|
+
<button class="swatch" style:background={color} onclick={handle_click}
|
|
54
59
|
></button>
|
|
55
60
|
<div>
|
|
56
61
|
<div class="input-wrap">
|
|
57
62
|
<input
|
|
58
63
|
type="text"
|
|
59
64
|
value={color_string}
|
|
60
|
-
|
|
65
|
+
onchange={(e) => (color = e.currentTarget.value)}
|
|
61
66
|
/>
|
|
62
67
|
|
|
63
|
-
<button class="eyedropper"
|
|
68
|
+
<button class="eyedropper" onclick={request_eyedropper}>
|
|
64
69
|
{#if eyedropper_supported}
|
|
65
70
|
<Eyedropper />
|
|
66
71
|
{/if}
|
|
@@ -71,7 +76,7 @@
|
|
|
71
76
|
<button
|
|
72
77
|
class="button"
|
|
73
78
|
class:active={current_mode === value}
|
|
74
|
-
|
|
79
|
+
onclick={() => (current_mode = value)}>{label}</button
|
|
75
80
|
>
|
|
76
81
|
{/each}
|
|
77
82
|
</div>
|
|
@@ -3,19 +3,25 @@
|
|
|
3
3
|
import tinycolor from "tinycolor2";
|
|
4
4
|
import { clamp } from "../utils/pixi";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
let {
|
|
7
|
+
color = $bindable("rgb(255, 255, 255)"),
|
|
8
|
+
opacity = $bindable(1)
|
|
9
|
+
}: {
|
|
10
|
+
color?: string;
|
|
11
|
+
opacity?: number;
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
let sl_marker_pos = $state([0, 0]);
|
|
9
15
|
let sl_rect: DOMRect | null = null;
|
|
10
|
-
let sl_moving = false;
|
|
11
|
-
let sl = [0, 0];
|
|
16
|
+
let sl_moving = $state(false);
|
|
17
|
+
let sl = $state([0, 0]);
|
|
12
18
|
|
|
13
|
-
let hue = 0;
|
|
14
|
-
let hue_marker_pos = 0;
|
|
19
|
+
let hue = $state(0);
|
|
20
|
+
let hue_marker_pos = $state(0);
|
|
15
21
|
let hue_rect: DOMRect | null = null;
|
|
16
|
-
let hue_moving = false;
|
|
22
|
+
let hue_moving = $state(false);
|
|
17
23
|
|
|
18
|
-
let opacity_moving = false;
|
|
24
|
+
let opacity_moving = $state(false);
|
|
19
25
|
|
|
20
26
|
function handle_hue_down(
|
|
21
27
|
event: MouseEvent & { currentTarget: HTMLDivElement }
|
|
@@ -121,13 +127,13 @@
|
|
|
121
127
|
hue_marker_pos = (hsva.h / 360) * hue_rect!.width;
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
|
|
130
|
+
$effect(() => {
|
|
131
|
+
update_mouse_from_color(color);
|
|
132
|
+
});
|
|
125
133
|
|
|
126
134
|
let sl_wrap: HTMLDivElement;
|
|
127
135
|
let hue_wrap: HTMLDivElement;
|
|
128
136
|
|
|
129
|
-
export let opacity = 1;
|
|
130
|
-
|
|
131
137
|
let opacity_wrap: HTMLDivElement;
|
|
132
138
|
|
|
133
139
|
function handle_opacity_down(
|
|
@@ -150,12 +156,12 @@
|
|
|
150
156
|
});
|
|
151
157
|
</script>
|
|
152
158
|
|
|
153
|
-
<svelte:window
|
|
159
|
+
<svelte:window onmousemove={handle_move} onmouseup={handle_end} />
|
|
154
160
|
|
|
155
161
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
156
162
|
<div
|
|
157
163
|
class="color-gradient"
|
|
158
|
-
|
|
164
|
+
onmousedown={handle_sl_down}
|
|
159
165
|
style="--hue:{hue}"
|
|
160
166
|
bind:this={sl_wrap}
|
|
161
167
|
>
|
|
@@ -168,7 +174,7 @@
|
|
|
168
174
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
169
175
|
<div
|
|
170
176
|
class="opacity-slider"
|
|
171
|
-
|
|
177
|
+
onmousedown={handle_opacity_down}
|
|
172
178
|
style="--color:{color === 'auto' ? 'transparent' : color}"
|
|
173
179
|
bind:this={opacity_wrap}
|
|
174
180
|
>
|
|
@@ -179,7 +185,7 @@
|
|
|
179
185
|
/>
|
|
180
186
|
</div>
|
|
181
187
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
182
|
-
<div class="hue-slider"
|
|
188
|
+
<div class="hue-slider" onmousedown={handle_hue_down} bind:this={hue_wrap}>
|
|
183
189
|
<div
|
|
184
190
|
class="marker"
|
|
185
191
|
style:background={"hsl(" + hue + ", 100%, 50%)"}
|
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import tinycolor from "tinycolor2";
|
|
3
|
-
import { createEventDispatcher } from "svelte";
|
|
4
3
|
import type { ColorTuple } from "./types";
|
|
5
4
|
import type { ColorInput } from "tinycolor2";
|
|
6
5
|
import IconButton from "../IconButton.svelte";
|
|
7
6
|
import { Plus } from "@gradio/icons";
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
let {
|
|
9
|
+
selected_color,
|
|
10
|
+
colors,
|
|
11
|
+
user_colors = [],
|
|
12
|
+
show_empty = false,
|
|
13
|
+
current_mode = "hex",
|
|
14
|
+
color_picker = $bindable(false),
|
|
15
|
+
onselect,
|
|
16
|
+
onedit,
|
|
17
|
+
onadd_color
|
|
18
|
+
}: {
|
|
19
|
+
selected_color: string;
|
|
20
|
+
colors: (ColorInput | ColorTuple)[];
|
|
21
|
+
user_colors?: (ColorInput | ColorTuple)[] | null;
|
|
22
|
+
show_empty?: boolean;
|
|
23
|
+
current_mode?: "hex" | "rgb" | "hsl";
|
|
24
|
+
color_picker?: boolean;
|
|
25
|
+
onselect?: (value: {
|
|
26
|
+
index: number | null;
|
|
27
|
+
color: string | null;
|
|
28
|
+
opacity?: number;
|
|
29
|
+
}) => void;
|
|
30
|
+
onedit?: (value: {
|
|
31
|
+
index: number;
|
|
32
|
+
color: string | null;
|
|
33
|
+
opacity?: number;
|
|
34
|
+
}) => void;
|
|
35
|
+
onadd_color?: () => void;
|
|
36
|
+
} = $props();
|
|
21
37
|
|
|
22
|
-
|
|
38
|
+
let _colors = $derived(show_empty ? colors : colors.filter((c) => c));
|
|
23
39
|
|
|
24
40
|
function get_color(color_input: ColorInput | ColorTuple): string {
|
|
25
41
|
if (Array.isArray(color_input)) {
|
|
@@ -58,7 +74,7 @@
|
|
|
58
74
|
get_formatted_color(selected_color, current_mode)
|
|
59
75
|
)}`;
|
|
60
76
|
|
|
61
|
-
|
|
77
|
+
let selected_opacity = $derived(get_opacity(selected_color));
|
|
62
78
|
|
|
63
79
|
function handle_select(
|
|
64
80
|
type: "edit" | "select",
|
|
@@ -69,11 +85,15 @@
|
|
|
69
85
|
}
|
|
70
86
|
): void {
|
|
71
87
|
current_index = `${type}-${detail.index}`;
|
|
72
|
-
|
|
88
|
+
if (type === "edit") {
|
|
89
|
+
onedit?.(detail);
|
|
90
|
+
} else {
|
|
91
|
+
onselect?.(detail);
|
|
92
|
+
}
|
|
73
93
|
}
|
|
74
94
|
|
|
75
95
|
function handle_picker_click(): void {
|
|
76
|
-
|
|
96
|
+
onselect?.({ index: null, color: selected_color });
|
|
77
97
|
color_picker = !color_picker;
|
|
78
98
|
}
|
|
79
99
|
</script>
|
|
@@ -89,7 +109,7 @@
|
|
|
89
109
|
{#if color_picker}
|
|
90
110
|
<IconButton
|
|
91
111
|
Icon={Plus}
|
|
92
|
-
onclick={() =>
|
|
112
|
+
onclick={() => onadd_color?.()}
|
|
93
113
|
roundedness="very"
|
|
94
114
|
background={get_color(selected_color)}
|
|
95
115
|
color="white"
|
|
@@ -99,7 +119,7 @@
|
|
|
99
119
|
{@const color_string = get_color(color)}
|
|
100
120
|
{@const opacity = get_opacity(color)}
|
|
101
121
|
<button
|
|
102
|
-
|
|
122
|
+
onclick={() =>
|
|
103
123
|
handle_select("edit", {
|
|
104
124
|
index: i,
|
|
105
125
|
color: color_string,
|
|
@@ -115,7 +135,7 @@
|
|
|
115
135
|
{/each}
|
|
116
136
|
|
|
117
137
|
<button
|
|
118
|
-
|
|
138
|
+
onclick={handle_picker_click}
|
|
119
139
|
class="color colorpicker"
|
|
120
140
|
class:hidden={!color_picker}
|
|
121
141
|
></button>
|
|
@@ -126,7 +146,7 @@
|
|
|
126
146
|
{@const color_string = get_color(color_item)}
|
|
127
147
|
{@const opacity = get_opacity(color_item)}
|
|
128
148
|
<button
|
|
129
|
-
|
|
149
|
+
onclick={() =>
|
|
130
150
|
handle_select("select", {
|
|
131
151
|
index: i,
|
|
132
152
|
color: color_string,
|
package/shared/brush/brush.ts
CHANGED
|
@@ -150,22 +150,23 @@ export class BrushTool implements Tool {
|
|
|
150
150
|
* @param {Subtool} subtool - The current subtool.
|
|
151
151
|
*/
|
|
152
152
|
set_tool(tool: ToolbarTool, subtool: Subtool): void {
|
|
153
|
-
|
|
154
|
-
this.
|
|
153
|
+
const new_mode = tool === "erase" ? "erase" : "draw";
|
|
154
|
+
const mode_changed = this.state.mode !== new_mode;
|
|
155
|
+
const needs_brush_tool = tool === "erase" || tool === "draw";
|
|
155
156
|
|
|
156
|
-
if (this.
|
|
157
|
+
if (this.is_drawing && (!needs_brush_tool || mode_changed)) {
|
|
157
158
|
this.commit_pending_changes();
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
this.current_tool = tool;
|
|
162
|
+
this.current_subtool = subtool;
|
|
163
|
+
|
|
160
164
|
if (this.brush_cursor) {
|
|
161
165
|
const should_be_active = tool === "draw" || tool === "erase";
|
|
162
166
|
|
|
163
167
|
this.brush_cursor.set_active(should_be_active);
|
|
164
168
|
}
|
|
165
169
|
|
|
166
|
-
const new_mode = tool === "erase" ? "erase" : "draw";
|
|
167
|
-
const mode_changed = this.state.mode !== new_mode;
|
|
168
|
-
const needs_brush_tool = tool === "erase" || tool === "draw";
|
|
169
170
|
const textures_initialized =
|
|
170
171
|
this.brush_textures?.textures_initialized ?? false;
|
|
171
172
|
|
|
@@ -179,10 +180,7 @@ export class BrushTool implements Tool {
|
|
|
179
180
|
Math.round(current_bounds.height) !== Math.round(tex_dims.height);
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
if (
|
|
183
|
-
needs_brush_tool &&
|
|
184
|
-
(mode_changed || !textures_initialized || dimensions_changed)
|
|
185
|
-
) {
|
|
183
|
+
if (needs_brush_tool && (!textures_initialized || dimensions_changed)) {
|
|
186
184
|
this.brush_textures?.initialize_textures();
|
|
187
185
|
}
|
|
188
186
|
|
package/shared/core/editor.ts
CHANGED
|
@@ -74,6 +74,7 @@ export interface ImageEditorContext {
|
|
|
74
74
|
resize_canvas: (width: number, height: number) => void;
|
|
75
75
|
reset: () => void;
|
|
76
76
|
set_background_image: (image: Sprite) => void;
|
|
77
|
+
request_render: () => void;
|
|
77
78
|
pad_bottom: number;
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -82,6 +83,8 @@ const spring_config = {
|
|
|
82
83
|
damping: 0.8
|
|
83
84
|
};
|
|
84
85
|
|
|
86
|
+
const RENDER_IDLE_TIMEOUT_MS = 500;
|
|
87
|
+
|
|
85
88
|
interface EditorStatePublic {
|
|
86
89
|
position: { x: number; y: number };
|
|
87
90
|
scale: number;
|
|
@@ -204,6 +207,15 @@ export class ImageEditor {
|
|
|
204
207
|
private overlay_graphics!: Graphics;
|
|
205
208
|
private pad_bottom: number;
|
|
206
209
|
private theme_mode: "dark" | "light";
|
|
210
|
+
private render_idle_remaining = RENDER_IDLE_TIMEOUT_MS;
|
|
211
|
+
private static readonly WAKE_EVENTS: string[] = [
|
|
212
|
+
"pointerdown",
|
|
213
|
+
"pointermove",
|
|
214
|
+
"pointerup",
|
|
215
|
+
"pointerenter",
|
|
216
|
+
"wheel"
|
|
217
|
+
];
|
|
218
|
+
private wake_render_loop_bound = this.wake_render_loop.bind(this);
|
|
207
219
|
constructor(options: ImageEditorOptions) {
|
|
208
220
|
this.pad_bottom = options.pad_bottom || 0;
|
|
209
221
|
this.dark = options.dark || false;
|
|
@@ -278,7 +290,8 @@ export class ImageEditor {
|
|
|
278
290
|
execute_command: this.execute_command.bind(this),
|
|
279
291
|
resize_canvas: this.resize_canvas.bind(this),
|
|
280
292
|
reset: this.reset.bind(this),
|
|
281
|
-
set_background_image: this.set_background_image.bind(this)
|
|
293
|
+
set_background_image: this.set_background_image.bind(this),
|
|
294
|
+
request_render: this.wake_render_loop.bind(this)
|
|
282
295
|
};
|
|
283
296
|
}
|
|
284
297
|
|
|
@@ -337,17 +350,20 @@ export class ImageEditor {
|
|
|
337
350
|
this.dimensions_value = dimensions;
|
|
338
351
|
this.image_container.width = dimensions.width;
|
|
339
352
|
this.image_container.height = dimensions.height;
|
|
353
|
+
this.wake_render_loop();
|
|
340
354
|
});
|
|
341
355
|
|
|
342
356
|
this.scale.subscribe((scale) => {
|
|
343
357
|
this.scale_value = scale;
|
|
358
|
+
this.wake_render_loop();
|
|
344
359
|
});
|
|
345
360
|
|
|
346
361
|
this.position.subscribe((position) => {
|
|
347
362
|
this.position_value = position;
|
|
363
|
+
this.wake_render_loop();
|
|
348
364
|
});
|
|
349
365
|
|
|
350
|
-
this.app.ticker.add(() => {
|
|
366
|
+
this.app.ticker.add((ticker) => {
|
|
351
367
|
this.image_container.scale.set(this.scale_value);
|
|
352
368
|
this.image_container.position.set(
|
|
353
369
|
this.position_value.x,
|
|
@@ -442,11 +458,28 @@ export class ImageEditor {
|
|
|
442
458
|
} else {
|
|
443
459
|
this.overlay_graphics.clear();
|
|
444
460
|
}
|
|
461
|
+
|
|
462
|
+
this.render_idle_remaining -= ticker.deltaMS;
|
|
463
|
+
if (this.render_idle_remaining <= 0) {
|
|
464
|
+
ticker.stop();
|
|
465
|
+
}
|
|
445
466
|
});
|
|
446
467
|
|
|
468
|
+
for (const event_name of ImageEditor.WAKE_EVENTS) {
|
|
469
|
+
canvas.addEventListener(event_name, this.wake_render_loop_bound);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
this.wake_render_loop();
|
|
447
473
|
this.ready_resolve();
|
|
448
474
|
}
|
|
449
475
|
|
|
476
|
+
private wake_render_loop(): void {
|
|
477
|
+
this.render_idle_remaining = RENDER_IDLE_TIMEOUT_MS;
|
|
478
|
+
if (this.app?.ticker && !this.app.ticker.started) {
|
|
479
|
+
this.app.ticker.start();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
450
483
|
private setup_containers(): void {
|
|
451
484
|
this.image_container = new Container({
|
|
452
485
|
eventMode: "static",
|
|
@@ -542,6 +575,7 @@ export class ImageEditor {
|
|
|
542
575
|
if (this.outline_container) {
|
|
543
576
|
this.outline_container.position.set(app_center_x, app_center_y);
|
|
544
577
|
}
|
|
578
|
+
this.wake_render_loop();
|
|
545
579
|
}
|
|
546
580
|
|
|
547
581
|
reset(): void {
|
|
@@ -557,6 +591,7 @@ export class ImageEditor {
|
|
|
557
591
|
if (brush) {
|
|
558
592
|
this.set_tool("draw");
|
|
559
593
|
}
|
|
594
|
+
this.wake_render_loop();
|
|
560
595
|
}
|
|
561
596
|
|
|
562
597
|
async set_image_properties(properties: {
|
|
@@ -647,6 +682,7 @@ export class ImageEditor {
|
|
|
647
682
|
for (const tool of this.tools.values()) {
|
|
648
683
|
tool.set_tool(this.current_tool, this.current_subtool);
|
|
649
684
|
}
|
|
685
|
+
this.wake_render_loop();
|
|
650
686
|
}
|
|
651
687
|
|
|
652
688
|
set_subtool(subtool: Subtool): void {
|
|
@@ -655,6 +691,7 @@ export class ImageEditor {
|
|
|
655
691
|
for (const tool of this.tools.values()) {
|
|
656
692
|
tool.set_tool(this.current_tool, this.current_subtool);
|
|
657
693
|
}
|
|
694
|
+
this.wake_render_loop();
|
|
658
695
|
}
|
|
659
696
|
|
|
660
697
|
set_background_image(image: Sprite): void {
|
|
@@ -829,6 +866,7 @@ export class ImageEditor {
|
|
|
829
866
|
}
|
|
830
867
|
|
|
831
868
|
private notify(event: "change" | "input"): void {
|
|
869
|
+
this.wake_render_loop();
|
|
832
870
|
for (const callback of this.event_callbacks.get(event) || []) {
|
|
833
871
|
callback();
|
|
834
872
|
}
|
|
@@ -837,6 +875,13 @@ export class ImageEditor {
|
|
|
837
875
|
destroy(): void {
|
|
838
876
|
if (!this.app) return;
|
|
839
877
|
|
|
878
|
+
const canvas = this.app.canvas as HTMLCanvasElement | undefined;
|
|
879
|
+
if (canvas) {
|
|
880
|
+
for (const event_name of ImageEditor.WAKE_EVENTS) {
|
|
881
|
+
canvas.removeEventListener(event_name, this.wake_render_loop_bound);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
840
885
|
this.app?.destroy();
|
|
841
886
|
}
|
|
842
887
|
|
|
@@ -877,9 +922,11 @@ export class ImageEditor {
|
|
|
877
922
|
this.width,
|
|
878
923
|
this.height
|
|
879
924
|
);
|
|
925
|
+
this.wake_render_loop();
|
|
880
926
|
}
|
|
881
927
|
|
|
882
928
|
toggle_layer_visibility(id: string): void {
|
|
883
929
|
this.layer_manager.toggle_layer_visibility(id);
|
|
930
|
+
this.wake_render_loop();
|
|
884
931
|
}
|
|
885
932
|
}
|
package/shared/resize/resize.ts
CHANGED
|
@@ -108,6 +108,9 @@ export class ResizeTool implements Tool {
|
|
|
108
108
|
private dom_mouseup_handler: ((e: MouseEvent) => void) | null = null;
|
|
109
109
|
private event_callbacks: Map<string, (() => void)[]> = new Map();
|
|
110
110
|
private last_scale = 1;
|
|
111
|
+
private readonly handle_pointer_move_bound =
|
|
112
|
+
this.handle_pointer_move.bind(this);
|
|
113
|
+
private readonly handle_pointer_up_bound = this.handle_pointer_up.bind(this);
|
|
111
114
|
|
|
112
115
|
private original_state: {
|
|
113
116
|
width: number;
|
|
@@ -223,7 +226,7 @@ export class ResizeTool implements Tool {
|
|
|
223
226
|
* @returns {void}
|
|
224
227
|
*/
|
|
225
228
|
cleanup(): void {
|
|
226
|
-
if (this.resize_ui_container) {
|
|
229
|
+
if (this.resize_ui_container && this.image_editor_context?.app) {
|
|
227
230
|
this.image_editor_context.app.stage.removeChild(this.resize_ui_container);
|
|
228
231
|
}
|
|
229
232
|
this.cleanup_event_listeners();
|
|
@@ -241,10 +244,12 @@ export class ResizeTool implements Tool {
|
|
|
241
244
|
this.current_subtool = subtool;
|
|
242
245
|
|
|
243
246
|
if (tool === "image" && subtool === "size") {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
247
|
+
const context = this.image_editor_context;
|
|
248
|
+
if (!context) return;
|
|
249
|
+
|
|
250
|
+
if (context.background_image) {
|
|
251
|
+
const storedBorderRegion = (context.background_image as any)
|
|
252
|
+
.borderRegion;
|
|
248
253
|
if (typeof storedBorderRegion === "number" && storedBorderRegion > 0) {
|
|
249
254
|
this.borderRegion = storedBorderRegion;
|
|
250
255
|
}
|
|
@@ -252,9 +257,9 @@ export class ResizeTool implements Tool {
|
|
|
252
257
|
|
|
253
258
|
this.show_resize_ui();
|
|
254
259
|
|
|
255
|
-
if (
|
|
256
|
-
const bg =
|
|
257
|
-
const container =
|
|
260
|
+
if (context.background_image) {
|
|
261
|
+
const bg = context.background_image;
|
|
262
|
+
const container = context.image_container;
|
|
258
263
|
|
|
259
264
|
const bg_width = bg.width;
|
|
260
265
|
const bg_height = bg.height;
|
|
@@ -936,9 +941,9 @@ export class ResizeTool implements Tool {
|
|
|
936
941
|
private setup_event_listeners(): void {
|
|
937
942
|
const stage = this.image_editor_context.app.stage;
|
|
938
943
|
stage.eventMode = "static";
|
|
939
|
-
stage.on("pointermove", this.
|
|
940
|
-
stage.on("pointerup", this.
|
|
941
|
-
stage.on("pointerupoutside", this.
|
|
944
|
+
stage.on("pointermove", this.handle_pointer_move_bound);
|
|
945
|
+
stage.on("pointerup", this.handle_pointer_up_bound);
|
|
946
|
+
stage.on("pointerupoutside", this.handle_pointer_up_bound);
|
|
942
947
|
|
|
943
948
|
this.setup_dom_event_listeners();
|
|
944
949
|
}
|
|
@@ -949,7 +954,10 @@ export class ResizeTool implements Tool {
|
|
|
949
954
|
private setup_dom_event_listeners(): void {
|
|
950
955
|
this.cleanup_dom_event_listeners();
|
|
951
956
|
|
|
952
|
-
const
|
|
957
|
+
const context = this.image_editor_context;
|
|
958
|
+
if (!context?.app?.canvas) return;
|
|
959
|
+
|
|
960
|
+
const canvas = context.app.canvas;
|
|
953
961
|
|
|
954
962
|
this.dom_mousedown_handler = (e: MouseEvent) => {
|
|
955
963
|
if (this.current_subtool !== "size") return;
|
|
@@ -1070,10 +1078,10 @@ export class ResizeTool implements Tool {
|
|
|
1070
1078
|
* Cleans up DOM event listeners
|
|
1071
1079
|
*/
|
|
1072
1080
|
private cleanup_dom_event_listeners(): void {
|
|
1073
|
-
const canvas = this.image_editor_context
|
|
1081
|
+
const canvas = this.image_editor_context?.app?.canvas;
|
|
1074
1082
|
|
|
1075
1083
|
if (this.dom_mousedown_handler) {
|
|
1076
|
-
canvas
|
|
1084
|
+
canvas?.removeEventListener("mousedown", this.dom_mousedown_handler);
|
|
1077
1085
|
this.dom_mousedown_handler = null;
|
|
1078
1086
|
}
|
|
1079
1087
|
|
|
@@ -1092,11 +1100,11 @@ export class ResizeTool implements Tool {
|
|
|
1092
1100
|
* removes event listeners for the resize tool
|
|
1093
1101
|
*/
|
|
1094
1102
|
private cleanup_event_listeners(): void {
|
|
1095
|
-
const stage = this.image_editor_context
|
|
1103
|
+
const stage = this.image_editor_context?.app?.stage;
|
|
1096
1104
|
|
|
1097
|
-
stage
|
|
1098
|
-
stage
|
|
1099
|
-
stage
|
|
1105
|
+
stage?.off("pointermove", this.handle_pointer_move_bound);
|
|
1106
|
+
stage?.off("pointerup", this.handle_pointer_up_bound);
|
|
1107
|
+
stage?.off("pointerupoutside", this.handle_pointer_up_bound);
|
|
1100
1108
|
|
|
1101
1109
|
this.cleanup_dom_event_listeners();
|
|
1102
1110
|
}
|
|
@@ -1687,7 +1695,7 @@ export class ResizeTool implements Tool {
|
|
|
1687
1695
|
this.update_resize_ui();
|
|
1688
1696
|
}
|
|
1689
1697
|
|
|
1690
|
-
if (this.image_editor_context
|
|
1698
|
+
if (this.image_editor_context?.background_image) {
|
|
1691
1699
|
const bg = this.image_editor_context.background_image;
|
|
1692
1700
|
this.current_cursor = bg.cursor;
|
|
1693
1701
|
bg.cursor = "move";
|
|
@@ -1705,7 +1713,7 @@ export class ResizeTool implements Tool {
|
|
|
1705
1713
|
this.resize_ui_container.visible = false;
|
|
1706
1714
|
}
|
|
1707
1715
|
|
|
1708
|
-
if (this.image_editor_context
|
|
1716
|
+
if (this.image_editor_context?.background_image) {
|
|
1709
1717
|
const bg = this.image_editor_context.background_image;
|
|
1710
1718
|
bg.cursor = this.current_cursor;
|
|
1711
1719
|
}
|