@gradio/imageeditor 0.4.10 → 0.5.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 +38 -0
- package/ImageEditor.stories.svelte +6 -2
- package/Index.svelte +39 -13
- package/package.json +8 -8
- package/shared/Controls.svelte +2 -2
- package/shared/ImageEditor.svelte +37 -12
- package/shared/InteractiveImageEditor.svelte +103 -20
- package/shared/layers/Layers.svelte +113 -56
- package/shared/layers/utils.ts +44 -4
- package/shared/tools/Brush.svelte +42 -51
- package/shared/tools/{BrushColor.svelte → BrushOptions.svelte} +73 -32
- package/shared/tools/BrushSize.svelte +13 -20
- package/shared/tools/ColorSwatch.svelte +66 -34
- package/shared/tools/Crop.svelte +12 -17
- package/shared/tools/Cropper.svelte +2 -0
- package/shared/tools/Handle.svelte +1 -1
- package/shared/tools/Sources.svelte +45 -14
- package/shared/tools/Tools.svelte +42 -57
- package/shared/tools/crop.ts +9 -4
- package/shared/utils/events.ts +2 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { getContext, onMount, tick } from "svelte";
|
|
3
|
+
|
|
4
4
|
import { click_outside } from "../utils/events";
|
|
5
5
|
import { layer_manager, type LayerScene } from "./utils";
|
|
6
6
|
import { EDITOR_KEY, type EditorContext } from "../ImageEditor.svelte";
|
|
7
7
|
import type { FileData } from "@gradio/client";
|
|
8
|
+
import { Layers } from "@gradio/icons";
|
|
8
9
|
|
|
9
10
|
let show_layers = false;
|
|
10
11
|
|
|
11
12
|
export let layer_files: (FileData | null)[] | null = [];
|
|
13
|
+
export let enable_layers = true;
|
|
12
14
|
|
|
13
15
|
const { pixi, current_layer, dimensions, register_context } =
|
|
14
16
|
getContext<EditorContext>(EDITOR_KEY);
|
|
@@ -25,6 +27,20 @@
|
|
|
25
27
|
}
|
|
26
28
|
});
|
|
27
29
|
|
|
30
|
+
async function validate_layers(): Promise<void> {
|
|
31
|
+
let invalid = layers.some(
|
|
32
|
+
(layer) =>
|
|
33
|
+
layer.composite.texture?.width != $dimensions[0] ||
|
|
34
|
+
layer.composite.texture?.height != $dimensions[1]
|
|
35
|
+
);
|
|
36
|
+
if (invalid) {
|
|
37
|
+
LayerManager.reset();
|
|
38
|
+
if (!layer_files || layer_files.length == 0) new_layer();
|
|
39
|
+
else render_layer_files(layer_files);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
$: $dimensions, validate_layers();
|
|
43
|
+
|
|
28
44
|
async function new_layer(): Promise<void> {
|
|
29
45
|
if (!$pixi) return;
|
|
30
46
|
|
|
@@ -48,7 +64,11 @@
|
|
|
48
64
|
_layer_files: typeof layer_files
|
|
49
65
|
): Promise<void> {
|
|
50
66
|
await tick();
|
|
51
|
-
if (!_layer_files || _layer_files.length == 0)
|
|
67
|
+
if (!_layer_files || _layer_files.length == 0) {
|
|
68
|
+
LayerManager.reset();
|
|
69
|
+
new_layer();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
52
72
|
if (!$pixi) return;
|
|
53
73
|
|
|
54
74
|
const fetch_promises = await Promise.all(
|
|
@@ -73,7 +93,8 @@
|
|
|
73
93
|
last_layer = await LayerManager.add_layer_from_blob(
|
|
74
94
|
$pixi.layer_container,
|
|
75
95
|
$pixi.renderer,
|
|
76
|
-
blob
|
|
96
|
+
blob,
|
|
97
|
+
$pixi.view
|
|
77
98
|
);
|
|
78
99
|
}
|
|
79
100
|
|
|
@@ -91,75 +112,79 @@
|
|
|
91
112
|
});
|
|
92
113
|
</script>
|
|
93
114
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<button aria-label="Show Layers" on:click={() => (show_layers = !show_layers)}
|
|
100
|
-
>Layers<span class="layer-toggle"><DropdownArrow /></span></button
|
|
115
|
+
{#if enable_layers}
|
|
116
|
+
<div
|
|
117
|
+
class="layer-wrap"
|
|
118
|
+
class:closed={!show_layers}
|
|
119
|
+
use:click_outside={() => (show_layers = false)}
|
|
101
120
|
>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{
|
|
121
|
+
<button
|
|
122
|
+
aria-label="Show Layers"
|
|
123
|
+
on:click={() => (show_layers = !show_layers)}
|
|
124
|
+
><span class="icon"><Layers /></span> Layer {layers.findIndex(
|
|
125
|
+
(l) => l === $current_layer
|
|
126
|
+
) + 1}
|
|
127
|
+
</button>
|
|
128
|
+
{#if show_layers}
|
|
129
|
+
<ul>
|
|
130
|
+
{#each layers as layer, i (i)}
|
|
131
|
+
<li>
|
|
132
|
+
<button
|
|
133
|
+
class:selected_layer={$current_layer === layer}
|
|
134
|
+
on:click={() =>
|
|
135
|
+
($current_layer = LayerManager.change_active_layer(i))}
|
|
136
|
+
>Layer {i + 1}</button
|
|
137
|
+
>
|
|
138
|
+
</li>
|
|
139
|
+
{/each}
|
|
105
140
|
<li>
|
|
106
|
-
<button
|
|
107
|
-
class:selected_layer={$current_layer === layer}
|
|
108
|
-
on:click={() =>
|
|
109
|
-
($current_layer = LayerManager.change_active_layer(i))}
|
|
110
|
-
>Layer {i + 1}</button
|
|
111
|
-
>
|
|
141
|
+
<button aria-label="Add Layer" on:click={new_layer}> +</button>
|
|
112
142
|
</li>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<button aria-label="Add Layer" on:click={new_layer}> +</button>
|
|
116
|
-
</li>
|
|
117
|
-
</ul>
|
|
118
|
-
{/if}
|
|
119
|
-
</div>
|
|
143
|
+
</ul>
|
|
144
|
+
{/if}
|
|
120
145
|
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
transform: rotate(0deg);
|
|
125
|
-
}
|
|
146
|
+
<span class="sep"></span>
|
|
147
|
+
</div>
|
|
148
|
+
{/if}
|
|
126
149
|
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
<style>
|
|
151
|
+
.icon {
|
|
152
|
+
width: 14px;
|
|
153
|
+
margin-right: var(--spacing-md);
|
|
154
|
+
color: var(--block-label-text-color);
|
|
155
|
+
margin-right: var(--spacing-lg);
|
|
156
|
+
margin-top: 1px;
|
|
129
157
|
}
|
|
130
158
|
|
|
131
159
|
.layer-wrap {
|
|
132
|
-
position:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
border: 1px solid var(--block-border-color);
|
|
137
|
-
border-radius: var(--radius-md);
|
|
138
|
-
|
|
139
|
-
transition: var(--button-transition);
|
|
140
|
-
box-shadow: var(--button-shadow);
|
|
141
|
-
|
|
142
|
-
text-align: left;
|
|
143
|
-
border-bottom: none;
|
|
144
|
-
border-left: none;
|
|
145
|
-
border-bottom-right-radius: 0;
|
|
146
|
-
border-top-left-radius: 0;
|
|
147
|
-
background-color: var(--background-fill-primary);
|
|
148
|
-
overflow: hidden;
|
|
160
|
+
position: relative;
|
|
161
|
+
display: flex;
|
|
162
|
+
justify-content: center;
|
|
163
|
+
align-items: center;
|
|
149
164
|
}
|
|
150
165
|
|
|
151
166
|
.layer-wrap button {
|
|
152
|
-
display: inline-flex;
|
|
153
167
|
justify-content: flex-start;
|
|
154
168
|
align-items: flex-start;
|
|
155
|
-
padding: var(--size-2) var(--size-4);
|
|
156
169
|
width: 100%;
|
|
157
170
|
border-bottom: 1px solid var(--block-border-color);
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
justify-content: center;
|
|
174
|
+
font-size: var(--scale-000);
|
|
175
|
+
line-height: var(--line-sm);
|
|
176
|
+
padding-bottom: 1px;
|
|
177
|
+
margin-left: var(--spacing-xl);
|
|
178
|
+
padding: var(--spacing-sm) 0;
|
|
158
179
|
}
|
|
159
180
|
|
|
160
181
|
.layer-wrap li:last-child button {
|
|
161
182
|
border-bottom: none;
|
|
162
183
|
text-align: center;
|
|
184
|
+
font-size: var(--scale-0);
|
|
185
|
+
line-height: 1;
|
|
186
|
+
font-weight: var(--weight-bold);
|
|
187
|
+
padding: 5px 0 1px 0;
|
|
163
188
|
}
|
|
164
189
|
|
|
165
190
|
.closed > button {
|
|
@@ -167,12 +192,44 @@
|
|
|
167
192
|
}
|
|
168
193
|
|
|
169
194
|
.layer-wrap button:hover {
|
|
170
|
-
background-color:
|
|
195
|
+
background-color: none;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.layer-wrap button:hover .icon {
|
|
199
|
+
color: var(--color-accent);
|
|
171
200
|
}
|
|
172
201
|
|
|
173
202
|
.selected_layer {
|
|
174
|
-
background-color: var(--
|
|
175
|
-
color:
|
|
203
|
+
background-color: var(--block-background-fill);
|
|
204
|
+
color: var(--color-accent);
|
|
176
205
|
font-weight: bold;
|
|
177
206
|
}
|
|
207
|
+
|
|
208
|
+
ul {
|
|
209
|
+
position: absolute;
|
|
210
|
+
bottom: 0;
|
|
211
|
+
left: 0;
|
|
212
|
+
background: var(--block-background-fill);
|
|
213
|
+
width: calc(100% + 1px);
|
|
214
|
+
list-style: none;
|
|
215
|
+
z-index: var(--layer-top);
|
|
216
|
+
border: 1px solid var(--block-border-color);
|
|
217
|
+
padding: var(--spacing-sm) 0;
|
|
218
|
+
text-wrap: none;
|
|
219
|
+
transform: translate(-1px, 1px);
|
|
220
|
+
border-radius: var(--radius-sm);
|
|
221
|
+
border-bottom-right-radius: 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.layer-wrap ul > li > button {
|
|
225
|
+
margin-left: 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.sep {
|
|
229
|
+
height: 12px;
|
|
230
|
+
background-color: var(--block-border-color);
|
|
231
|
+
width: 1px;
|
|
232
|
+
display: block;
|
|
233
|
+
margin-left: var(--spacing-xl);
|
|
234
|
+
}
|
|
178
235
|
</style>
|
package/shared/layers/utils.ts
CHANGED
|
@@ -93,7 +93,8 @@ interface LayerManager {
|
|
|
93
93
|
add_layer_from_blob(
|
|
94
94
|
container: Container,
|
|
95
95
|
renderer: IRenderer,
|
|
96
|
-
blob: Blob
|
|
96
|
+
blob: Blob,
|
|
97
|
+
view: HTMLCanvasElement
|
|
97
98
|
): Promise<[LayerScene, LayerScene[]]>;
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -197,19 +198,32 @@ export function layer_manager(): LayerManager {
|
|
|
197
198
|
async add_layer_from_blob(
|
|
198
199
|
container: Container,
|
|
199
200
|
renderer: IRenderer,
|
|
200
|
-
blob: Blob
|
|
201
|
+
blob: Blob,
|
|
202
|
+
view: HTMLCanvasElement
|
|
201
203
|
) {
|
|
202
204
|
const img = await createImageBitmap(blob);
|
|
203
205
|
const bitmap_texture = Texture.from(img);
|
|
206
|
+
|
|
207
|
+
const [w, h] = resize_to_fit(
|
|
208
|
+
bitmap_texture.width,
|
|
209
|
+
bitmap_texture.height,
|
|
210
|
+
view.width,
|
|
211
|
+
view.height
|
|
212
|
+
);
|
|
213
|
+
|
|
204
214
|
const sprite = new Sprite(bitmap_texture) as Sprite & DisplayObject;
|
|
205
215
|
sprite.zIndex = 0;
|
|
206
216
|
|
|
217
|
+
sprite.width = w;
|
|
218
|
+
sprite.height = h;
|
|
219
|
+
|
|
207
220
|
const [layer, layers] = this.add_layer(
|
|
208
221
|
container,
|
|
209
222
|
renderer,
|
|
210
|
-
|
|
211
|
-
|
|
223
|
+
view.width,
|
|
224
|
+
view.height
|
|
212
225
|
);
|
|
226
|
+
|
|
213
227
|
renderer.render(sprite, {
|
|
214
228
|
renderTexture: layer.draw_texture
|
|
215
229
|
});
|
|
@@ -221,3 +235,29 @@ export function layer_manager(): LayerManager {
|
|
|
221
235
|
}
|
|
222
236
|
};
|
|
223
237
|
}
|
|
238
|
+
|
|
239
|
+
function resize_to_fit(
|
|
240
|
+
inner_width: number,
|
|
241
|
+
inner_height: number,
|
|
242
|
+
outer_width: number,
|
|
243
|
+
outer_height: number
|
|
244
|
+
): [number, number] {
|
|
245
|
+
if (inner_width <= outer_width && inner_height <= outer_height) {
|
|
246
|
+
return [inner_width, inner_height];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const inner_aspect = inner_width / inner_height;
|
|
250
|
+
const outer_aspect = outer_width / outer_height;
|
|
251
|
+
|
|
252
|
+
let new_width, new_height;
|
|
253
|
+
|
|
254
|
+
if (inner_aspect > outer_aspect) {
|
|
255
|
+
new_width = outer_width;
|
|
256
|
+
new_height = outer_width / inner_aspect;
|
|
257
|
+
} else {
|
|
258
|
+
new_height = outer_height;
|
|
259
|
+
new_width = outer_height * inner_aspect;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return [new_width, new_height];
|
|
263
|
+
}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
color_mode: "fixed" | "defaults";
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
type brush_option_type =
|
|
26
|
+
type brush_option_type = boolean;
|
|
27
27
|
</script>
|
|
28
28
|
|
|
29
29
|
<script lang="ts">
|
|
@@ -32,11 +32,9 @@
|
|
|
32
32
|
|
|
33
33
|
import { getContext, onMount, tick } from "svelte";
|
|
34
34
|
import { type ToolContext, TOOL_KEY } from "./Tools.svelte";
|
|
35
|
-
import { Palette, BrushSize as SizeIcon } from "@gradio/icons";
|
|
36
35
|
import { type EditorContext, EDITOR_KEY } from "../ImageEditor.svelte";
|
|
37
36
|
import { draw_path, type DrawCommand } from "./brush";
|
|
38
|
-
import
|
|
39
|
-
import BrushSize from "./BrushSize.svelte";
|
|
37
|
+
import BrushOptions from "./BrushOptions.svelte";
|
|
40
38
|
import type { FederatedPointerEvent } from "pixi.js";
|
|
41
39
|
|
|
42
40
|
export let default_size: Brush["default_size"];
|
|
@@ -46,7 +44,7 @@
|
|
|
46
44
|
export let mode: "erase" | "draw";
|
|
47
45
|
|
|
48
46
|
const processed_colors = colors
|
|
49
|
-
? colors.map(process_color).filter((_, i) => i <
|
|
47
|
+
? colors.map(process_color).filter((_, i) => i < 4)
|
|
50
48
|
: [];
|
|
51
49
|
|
|
52
50
|
let selected_color =
|
|
@@ -56,32 +54,7 @@
|
|
|
56
54
|
? "black"
|
|
57
55
|
: process_color(default_color);
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
color: {
|
|
61
|
-
icon: Palette,
|
|
62
|
-
label: "Color",
|
|
63
|
-
order: 0,
|
|
64
|
-
id: "brush_color",
|
|
65
|
-
cb() {
|
|
66
|
-
current_option = "color";
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
size: {
|
|
70
|
-
icon: SizeIcon,
|
|
71
|
-
label: "Size",
|
|
72
|
-
order: 1,
|
|
73
|
-
id: "brush_size",
|
|
74
|
-
cb() {
|
|
75
|
-
current_option = "size";
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
} as const;
|
|
79
|
-
|
|
80
|
-
let brush_options: (typeof paint_meta)[brush_option_type][];
|
|
81
|
-
$: brush_options =
|
|
82
|
-
mode === "draw" ? Object.values(paint_meta) : [paint_meta.size];
|
|
83
|
-
|
|
84
|
-
let current_option: brush_option_type | null = null;
|
|
57
|
+
let brush_options: brush_option_type = false;
|
|
85
58
|
|
|
86
59
|
const {
|
|
87
60
|
pixi,
|
|
@@ -90,7 +63,8 @@
|
|
|
90
63
|
command_manager,
|
|
91
64
|
register_context,
|
|
92
65
|
editor_box,
|
|
93
|
-
crop
|
|
66
|
+
crop,
|
|
67
|
+
toolbar_box
|
|
94
68
|
} = getContext<EditorContext>(EDITOR_KEY);
|
|
95
69
|
|
|
96
70
|
const { active_tool, register_tool, current_color } =
|
|
@@ -216,8 +190,7 @@
|
|
|
216
190
|
|
|
217
191
|
onMount(() => {
|
|
218
192
|
const unregister = register_tool(mode, {
|
|
219
|
-
|
|
220
|
-
options: brush_options
|
|
193
|
+
cb: toggle_options
|
|
221
194
|
});
|
|
222
195
|
|
|
223
196
|
return () => {
|
|
@@ -232,19 +205,37 @@
|
|
|
232
205
|
return tinycolor(color).toRgbString();
|
|
233
206
|
}
|
|
234
207
|
|
|
235
|
-
$: {
|
|
236
|
-
if ($active_tool !== mode) {
|
|
237
|
-
current_option = null;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
208
|
let pos = { x: 0, y: 0 };
|
|
242
209
|
$: brush_size =
|
|
243
210
|
(selected_size / $dimensions[0]) * $editor_box.child_width * 2;
|
|
211
|
+
|
|
212
|
+
function debounce_toggle(): (should_close?: boolean) => void {
|
|
213
|
+
let timeout: NodeJS.Timeout | null = null;
|
|
214
|
+
|
|
215
|
+
return function executedFunction(should_close?: boolean) {
|
|
216
|
+
const later = (): void => {
|
|
217
|
+
if (timeout) {
|
|
218
|
+
clearTimeout(timeout);
|
|
219
|
+
}
|
|
220
|
+
if (should_close !== undefined) {
|
|
221
|
+
brush_options = should_close;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
brush_options = !brush_options;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
if (timeout) {
|
|
228
|
+
clearTimeout(timeout);
|
|
229
|
+
}
|
|
230
|
+
timeout = setTimeout(later, 100);
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const toggle_options = debounce_toggle();
|
|
244
235
|
</script>
|
|
245
236
|
|
|
246
237
|
<svelte:window
|
|
247
|
-
on:keydown={({ key }) => key === "Escape" && (
|
|
238
|
+
on:keydown={({ key }) => key === "Escape" && toggle_options(false)}
|
|
248
239
|
/>
|
|
249
240
|
|
|
250
241
|
<span
|
|
@@ -260,23 +251,23 @@
|
|
|
260
251
|
style:opacity={brush_cursor ? 1 : 0}
|
|
261
252
|
/>
|
|
262
253
|
|
|
263
|
-
{#if
|
|
254
|
+
{#if brush_options}
|
|
264
255
|
<div>
|
|
265
|
-
<
|
|
266
|
-
|
|
256
|
+
<BrushOptions
|
|
257
|
+
show_swatch={mode === "draw"}
|
|
258
|
+
on:click_outside={() => toggle_options()}
|
|
267
259
|
colors={processed_colors}
|
|
268
260
|
bind:selected_color
|
|
269
261
|
{color_mode}
|
|
270
262
|
bind:recent_colors
|
|
263
|
+
bind:selected_size
|
|
264
|
+
dimensions={$dimensions}
|
|
265
|
+
parent_width={$editor_box.parent_width}
|
|
266
|
+
parent_height={$editor_box.parent_height}
|
|
267
|
+
parent_left={$editor_box.parent_left}
|
|
268
|
+
toolbar_box={$toolbar_box}
|
|
271
269
|
/>
|
|
272
270
|
</div>
|
|
273
|
-
{:else if current_option === "size"}
|
|
274
|
-
<BrushSize
|
|
275
|
-
on:click_outside={() => (current_option = null)}
|
|
276
|
-
max={$dimensions[0] / 10}
|
|
277
|
-
min={1}
|
|
278
|
-
bind:selected_size
|
|
279
|
-
/>
|
|
280
271
|
{/if}
|
|
281
272
|
|
|
282
273
|
<style>
|
|
@@ -6,11 +6,19 @@
|
|
|
6
6
|
import ColorPicker from "./ColorPicker.svelte";
|
|
7
7
|
import ColorSwatch from "./ColorSwatch.svelte";
|
|
8
8
|
import ColorField from "./ColorField.svelte";
|
|
9
|
+
import BrushSize from "./BrushSize.svelte";
|
|
9
10
|
|
|
10
11
|
export let colors: string[];
|
|
11
12
|
export let selected_color: string;
|
|
12
13
|
export let color_mode: Brush["color_mode"] | undefined = undefined;
|
|
13
14
|
export let recent_colors: (string | null)[] = [];
|
|
15
|
+
export let selected_size: number;
|
|
16
|
+
export let dimensions: [number, number];
|
|
17
|
+
export let parent_width: number;
|
|
18
|
+
export let parent_height: number;
|
|
19
|
+
export let parent_left: number;
|
|
20
|
+
export let toolbar_box: DOMRect;
|
|
21
|
+
export let show_swatch: boolean;
|
|
14
22
|
|
|
15
23
|
let color_picker = false;
|
|
16
24
|
let current_mode: "hex" | "rgb" | "hsl" = "hex";
|
|
@@ -54,15 +62,29 @@
|
|
|
54
62
|
let c_width = 0;
|
|
55
63
|
let c_height = 0;
|
|
56
64
|
let wrap_el: HTMLDivElement;
|
|
57
|
-
let
|
|
58
|
-
let
|
|
59
|
-
|
|
65
|
+
let full_screen = false;
|
|
66
|
+
let anchor: "default" | "center" | "top" = "default";
|
|
67
|
+
let left_anchor = 0;
|
|
68
|
+
let top_anchor = null;
|
|
60
69
|
$: {
|
|
61
70
|
if (wrap_el && (width || height || c_height || c_width)) {
|
|
62
71
|
const box = wrap_el.getBoundingClientRect();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
color_picker;
|
|
73
|
+
|
|
74
|
+
if (box.width > parent_width) {
|
|
75
|
+
full_screen = true;
|
|
76
|
+
} else if (parent_width > box.width + 230 * 2 + 25) {
|
|
77
|
+
anchor = "default";
|
|
78
|
+
|
|
79
|
+
left_anchor = toolbar_box.right - parent_left + 24;
|
|
80
|
+
} else {
|
|
81
|
+
anchor = "center";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (box.height + 50 > parent_height) {
|
|
85
|
+
anchor = "top";
|
|
86
|
+
top_anchor = true;
|
|
87
|
+
}
|
|
66
88
|
}
|
|
67
89
|
}
|
|
68
90
|
</script>
|
|
@@ -73,12 +95,13 @@
|
|
|
73
95
|
class="wrap"
|
|
74
96
|
class:padded={!color_picker}
|
|
75
97
|
use:click_outside={() => dispatch("click_outside")}
|
|
76
|
-
class:
|
|
77
|
-
class:
|
|
78
|
-
class:
|
|
98
|
+
class:anchor_default={anchor === "default"}
|
|
99
|
+
class:anchor_center={anchor === "center"}
|
|
100
|
+
class:anchor_top={anchor === "top"}
|
|
79
101
|
bind:this={wrap_el}
|
|
80
102
|
bind:clientWidth={c_width}
|
|
81
103
|
bind:clientHeight={c_height}
|
|
104
|
+
class:anchor={`${left_anchor}px`}
|
|
82
105
|
>
|
|
83
106
|
{#if color_mode === "defaults"}
|
|
84
107
|
{#if color_picker}
|
|
@@ -91,49 +114,67 @@
|
|
|
91
114
|
/>
|
|
92
115
|
{/if}
|
|
93
116
|
{/if}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
117
|
+
{#if show_swatch}
|
|
118
|
+
<ColorSwatch
|
|
119
|
+
bind:color_picker
|
|
120
|
+
{colors}
|
|
121
|
+
on:select={({ detail }) => handle_color_selection(detail, "core")}
|
|
122
|
+
on:edit={({ detail }) => handle_color_selection(detail, "user")}
|
|
123
|
+
user_colors={color_mode === "defaults" ? recent_colors : null}
|
|
124
|
+
{selected_color}
|
|
125
|
+
{current_mode}
|
|
126
|
+
/>
|
|
127
|
+
{/if}
|
|
128
|
+
|
|
129
|
+
{#if color_picker || show_swatch}
|
|
130
|
+
<div class="sep"></div>
|
|
131
|
+
{/if}
|
|
132
|
+
|
|
133
|
+
<BrushSize max={dimensions[0] / 10} min={1} bind:selected_size />
|
|
103
134
|
</div>
|
|
104
135
|
|
|
105
136
|
<style>
|
|
106
137
|
.wrap {
|
|
107
|
-
width: 230px;
|
|
138
|
+
max-width: 230px;
|
|
139
|
+
width: 90%;
|
|
108
140
|
position: absolute;
|
|
109
141
|
display: flex;
|
|
110
142
|
flex-direction: column;
|
|
111
143
|
gap: 5px;
|
|
112
144
|
background: var(--background-fill-secondary);
|
|
113
145
|
border: 1px solid var(--block-border-color);
|
|
114
|
-
|
|
115
146
|
border-radius: var(--radius-md);
|
|
116
|
-
box-shadow:
|
|
117
|
-
0 0 5px rgba(0, 0, 0, 0.1),
|
|
118
|
-
0 5px 30px rgba(0, 0, 0, 0.2);
|
|
147
|
+
box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.1);
|
|
119
148
|
padding-bottom: var(--size-2);
|
|
120
149
|
pointer-events: all;
|
|
121
150
|
cursor: default;
|
|
122
151
|
z-index: var(--layer-top);
|
|
152
|
+
overflow: hidden;
|
|
123
153
|
}
|
|
124
154
|
|
|
125
|
-
.
|
|
126
|
-
|
|
155
|
+
.anchor_default {
|
|
156
|
+
position: absolute;
|
|
157
|
+
left: var(--left-anchor);
|
|
158
|
+
bottom: 8px;
|
|
127
159
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
160
|
+
.anchor_center {
|
|
161
|
+
position: absolute;
|
|
162
|
+
bottom: 50px;
|
|
163
|
+
left: 50%;
|
|
164
|
+
transform: translateX(-50%);
|
|
131
165
|
}
|
|
132
166
|
|
|
133
|
-
.
|
|
134
|
-
|
|
167
|
+
.anchor_top {
|
|
168
|
+
position: absolute;
|
|
169
|
+
top: 1px;
|
|
170
|
+
left: 50%;
|
|
171
|
+
transform: translateX(-50%);
|
|
135
172
|
}
|
|
136
|
-
|
|
137
|
-
|
|
173
|
+
|
|
174
|
+
.sep {
|
|
175
|
+
height: 1px;
|
|
176
|
+
background-color: var(--block-border-color);
|
|
177
|
+
margin: 0 var(--size-3);
|
|
178
|
+
margin-top: var(--size-1);
|
|
138
179
|
}
|
|
139
180
|
</style>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { click_outside } from "../utils/events";
|
|
3
3
|
import { createEventDispatcher } from "svelte";
|
|
4
|
+
import { BrushSize } from "@gradio/icons";
|
|
4
5
|
|
|
5
6
|
export let selected_size: number;
|
|
6
7
|
export let min: number;
|
|
@@ -40,36 +41,28 @@
|
|
|
40
41
|
class:top={anchor_top}
|
|
41
42
|
class:bottom={!anchor_top}
|
|
42
43
|
>
|
|
44
|
+
<span>
|
|
45
|
+
<BrushSize />
|
|
46
|
+
</span>
|
|
43
47
|
<input type="range" bind:value={selected_size} {min} {max} step={1} />
|
|
44
48
|
</div>
|
|
45
49
|
|
|
46
50
|
<style>
|
|
47
51
|
.wrap {
|
|
48
|
-
width:
|
|
49
|
-
position: absolute;
|
|
52
|
+
width: 100%;
|
|
50
53
|
display: flex;
|
|
51
|
-
|
|
52
|
-
gap: 5px;
|
|
54
|
+
gap: var(--size-4);
|
|
53
55
|
background: var(--background-fill-secondary);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
border-radius: var(--radius-md);
|
|
57
|
-
box-shadow:
|
|
58
|
-
0 0 5px rgba(0, 0, 0, 0.1),
|
|
59
|
-
0 5px 30px rgba(0, 0, 0, 0.2);
|
|
60
|
-
padding: var(--size-2);
|
|
56
|
+
padding: 0 var(--size-4);
|
|
61
57
|
cursor: default;
|
|
58
|
+
padding-top: var(--size-2-5);
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.top {
|
|
69
|
-
top: 30px;
|
|
61
|
+
input {
|
|
62
|
+
width: 100%;
|
|
70
63
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
span {
|
|
65
|
+
width: 26px;
|
|
66
|
+
color: var(--body-text-color);
|
|
74
67
|
}
|
|
75
68
|
</style>
|