@lvl99dotcom/paintcanvas 0.3.1 → 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/README.md +30 -15
- package/dist/lib/brushes/StrokeEngine/index.d.ts +2 -2
- package/dist/lib/brushes/StrokeEngine/index.js +26 -28
- package/dist/lib/brushes/presets/round/index.d.ts +1 -1
- package/dist/lib/brushes/presets/round/index.js +12 -7
- package/dist/lib/brushes/presets/square/index.d.ts +1 -1
- package/dist/lib/brushes/presets/square/index.js +7 -3
- package/dist/lib/brushes/shapeImage/index.d.ts +7 -3
- package/dist/lib/brushes/shapeImage/index.js +15 -9
- package/dist/lib/brushes/shapes/index.d.ts +18 -0
- package/dist/lib/brushes/shapes/index.js +86 -0
- package/dist/lib/classes/BrushSettings/index.d.ts +53 -2
- package/dist/lib/classes/BrushSettings/index.js +85 -14
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +47 -7
- package/dist/lib/classes/KeyboardShortcuts/index.js +145 -20
- package/dist/lib/classes/PaintCanvas/index.d.ts +123 -27
- package/dist/lib/classes/PaintCanvas/index.js +208 -41
- package/dist/lib/classes/PaintCanvas/tool.d.ts +15 -0
- package/dist/lib/classes/PaintCanvas/tool.js +18 -0
- package/dist/lib/classes/Palette/index.d.ts +58 -0
- package/dist/lib/classes/Palette/index.js +92 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +53 -0
- package/dist/lib/classes/SettingsAutosave/index.js +98 -0
- package/dist/lib/classes/ShortcutSettings/index.d.ts +63 -0
- package/dist/lib/classes/ShortcutSettings/index.js +110 -0
- package/dist/lib/classes/UiSettings/index.d.ts +68 -0
- package/dist/lib/classes/UiSettings/index.js +82 -0
- package/dist/lib/palette/index.d.ts +49 -0
- package/dist/lib/palette/index.js +82 -0
- package/dist/lib/palette/schema.d.ts +45 -0
- package/dist/lib/palette/schema.js +41 -0
- package/dist/lib/rendering/cursors/index.d.ts +2 -0
- package/dist/lib/rendering/cursors/index.js +3 -1
- package/dist/lib/rendering/sample/index.js +3 -2
- package/dist/lib/settings/index.d.ts +63 -0
- package/dist/lib/settings/index.js +93 -0
- package/dist/lib/settings/schema.d.ts +168 -0
- package/dist/lib/settings/schema.js +116 -0
- package/dist/lib/storage/index.d.ts +36 -5
- package/dist/lib/storage/index.js +87 -29
- package/dist/lib/utils/colour/index.d.ts +72 -0
- package/dist/lib/utils/colour/index.js +154 -0
- package/dist/lib/utils/shortcuts/index.d.ts +174 -11
- package/dist/lib/utils/shortcuts/index.js +391 -145
- package/dist/lib/validation/validators.generated.d.ts +5 -0
- package/dist/lib/validation/validators.generated.js +1040 -1
- package/dist/locales/en-GB.js +96 -3
- package/dist/locales/en-US.js +96 -3
- package/dist/locales/fr.js +96 -3
- package/dist/locales/ja.js +96 -3
- package/dist/locales/ko.js +96 -3
- package/dist/locales/zh-Hans.js +96 -3
- package/dist/locales/zh-Hant.js +96 -3
- package/dist/package.js +1 -1
- package/dist/testing/recordingCanvas.js +2 -1
- package/dist/ui/base/index.d.ts +2 -2
- package/dist/ui/dismiss/index.d.ts +21 -0
- package/dist/ui/dismiss/index.js +27 -0
- package/dist/ui/drag/index.d.ts +51 -0
- package/dist/ui/drag/index.js +81 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +57 -50
- package/dist/ui/elements/PaintCanvas/index.js +141 -174
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +59 -0
- package/dist/ui/elements/PcAppSettings/index.js +222 -0
- package/dist/ui/elements/PcBrushConfig/index.d.ts +9 -2
- package/dist/ui/elements/PcBrushConfig/index.js +47 -7
- package/dist/ui/elements/PcBrushConfig/style.js +1 -1
- package/dist/ui/elements/PcBrushSize/index.d.ts +53 -0
- package/dist/ui/elements/PcBrushSize/index.js +66 -0
- package/dist/ui/elements/PcBrushTest/index.d.ts +26 -0
- package/dist/ui/elements/PcBrushTest/index.js +73 -5
- package/dist/ui/elements/PcBrushTest/style.js +1 -1
- package/dist/ui/elements/PcCanvas/index.d.ts +22 -2
- package/dist/ui/elements/PcCanvas/index.js +60 -15
- package/dist/ui/elements/PcCanvasMap/style.js +1 -1
- package/dist/ui/elements/PcClipboardTools/index.d.ts +48 -0
- package/dist/ui/elements/PcClipboardTools/index.js +118 -0
- package/dist/ui/elements/PcColourPair/index.d.ts +43 -0
- package/dist/ui/elements/PcColourPair/index.js +125 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +71 -34
- package/dist/ui/elements/PcContextMenu/index.js +128 -97
- package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
- package/dist/ui/elements/PcInputSettings/index.js +16 -1
- package/dist/ui/elements/PcLayerItem/index.d.ts +13 -34
- package/dist/ui/elements/PcLayerItem/index.js +39 -91
- package/dist/ui/elements/PcLayerItem/style.js +1 -1
- package/dist/ui/elements/PcMenu/index.d.ts +138 -0
- package/dist/ui/elements/PcMenu/index.js +296 -0
- package/dist/ui/elements/PcNumberSlider/index.d.ts +137 -0
- package/dist/ui/elements/PcNumberSlider/index.js +247 -0
- package/dist/ui/elements/PcPane/index.d.ts +120 -0
- package/dist/ui/elements/PcPane/index.js +394 -0
- package/dist/ui/elements/PcPanel/index.d.ts +26 -8
- package/dist/ui/elements/PcPanel/index.js +76 -8
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.d.ts +22 -21
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.js +24 -57
- package/dist/ui/elements/PcPanelBrushes/style.js +3 -0
- package/dist/ui/elements/PcPanelColours/index.d.ts +171 -0
- package/dist/ui/elements/PcPanelColours/index.js +650 -0
- package/dist/ui/elements/PcPanelColours/style.js +3 -0
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.d.ts +17 -4
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.js +17 -10
- package/dist/ui/elements/PcPanelLayers/style.js +3 -0
- package/dist/ui/elements/PcPopover/index.d.ts +39 -1
- package/dist/ui/elements/PcPopover/index.js +72 -4
- package/dist/ui/elements/PcShortcutSettings/index.d.ts +69 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +273 -0
- package/dist/ui/elements/PcThemePicker/index.d.ts +17 -15
- package/dist/ui/elements/PcThemePicker/index.js +22 -21
- package/dist/ui/elements/PcToolSettings/index.d.ts +48 -9
- package/dist/ui/elements/PcToolSettings/index.js +384 -319
- package/dist/ui/elements/PcToolSettings/style.js +1 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +62 -12
- package/dist/ui/elements/PcToolbar/index.js +119 -57
- package/dist/ui/elements/PcToolbar/style.js +1 -1
- package/dist/ui/icons/images/icon-brush@2x.js +3 -0
- package/dist/ui/icons/images/icon-circle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-clipboard@2x.js +3 -0
- package/dist/ui/icons/images/icon-cog@2x.js +3 -0
- package/dist/ui/icons/images/icon-cursor@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-tlbr-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-trbl-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-eraser@2x.js +3 -0
- package/dist/ui/icons/images/icon-eyedropper@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-horizontal@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-vertical@2x.js +3 -0
- package/dist/ui/icons/images/icon-focus-pen@2x.js +3 -0
- package/dist/ui/icons/images/icon-freehand-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-hidden@2x.js +3 -0
- package/dist/ui/icons/images/icon-horizontal-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-layers@2x.js +3 -0
- package/dist/ui/icons/images/icon-linked@2x.js +3 -0
- package/dist/ui/icons/images/icon-locked@2x.js +3 -0
- package/dist/ui/icons/images/icon-moon@2x.js +3 -0
- package/dist/ui/icons/images/icon-pan@2x.js +3 -0
- package/dist/ui/icons/images/icon-polygon-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-pressure@2x.js +3 -0
- package/dist/ui/icons/images/icon-randomise@2x.js +3 -0
- package/dist/ui/icons/images/icon-rectangle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-redo@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-left@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-right@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-cubic@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-quad@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-linear@2x.js +3 -0
- package/dist/ui/icons/images/icon-star@2x.js +3 -0
- package/dist/ui/icons/images/icon-sun@2x.js +3 -0
- package/dist/ui/icons/images/icon-tilt@2x.js +3 -0
- package/dist/ui/icons/images/icon-undo@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlinked@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlocked@2x.js +3 -0
- package/dist/ui/icons/images/icon-vertical-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-visible@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-in@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-out@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom@2x.js +3 -0
- package/dist/ui/icons/index.d.ts +1 -1
- package/dist/ui/icons/index.js +84 -72
- package/dist/ui/index.d.ts +13 -2
- package/dist/ui/index.js +16 -4
- package/dist/ui/layout/index.d.ts +9 -1
- package/dist/ui/layout/index.js +11 -2
- package/dist/ui/tokens/index.d.ts +5 -4
- package/dist/ui/tokens/index.js +15 -8
- package/locales/en-GB.json +96 -3
- package/locales/en-US.json +96 -3
- package/locales/fr.json +96 -3
- package/locales/ja.json +96 -3
- package/locales/ko.json +96 -3
- package/locales/zh-Hans.json +96 -3
- package/locales/zh-Hant.json +96 -3
- package/package.json +11 -4
- package/dist/ui/elements/PcBrushMenu/style.js +0 -3
- package/dist/ui/elements/PcLayersMenu/style.js +0 -3
- package/dist/ui/icons/images/brush.js +0 -3
- package/dist/ui/icons/images/circle-mask.js +0 -3
- package/dist/ui/icons/images/clipboard.js +0 -3
- package/dist/ui/icons/images/cursor.js +0 -3
- package/dist/ui/icons/images/eraser.js +0 -3
- package/dist/ui/icons/images/eyedropper.js +0 -3
- package/dist/ui/icons/images/flip-horizontal.js +0 -3
- package/dist/ui/icons/images/flip-vertical.js +0 -3
- package/dist/ui/icons/images/focus-pen.js +0 -3
- package/dist/ui/icons/images/freehand-mask.js +0 -3
- package/dist/ui/icons/images/hidden.js +0 -3
- package/dist/ui/icons/images/layers.js +0 -3
- package/dist/ui/icons/images/linked.js +0 -3
- package/dist/ui/icons/images/locked.js +0 -3
- package/dist/ui/icons/images/moon.js +0 -3
- package/dist/ui/icons/images/pan.js +0 -3
- package/dist/ui/icons/images/polygon-mask.js +0 -3
- package/dist/ui/icons/images/pressure.js +0 -3
- package/dist/ui/icons/images/randomise.js +0 -3
- package/dist/ui/icons/images/rectangle-mask.js +0 -3
- package/dist/ui/icons/images/redo.js +0 -3
- package/dist/ui/icons/images/rotate-90-left.js +0 -3
- package/dist/ui/icons/images/rotate-90-right.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-cubic.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-quad.js +0 -3
- package/dist/ui/icons/images/smoothing-linear.js +0 -3
- package/dist/ui/icons/images/star.js +0 -3
- package/dist/ui/icons/images/sun.js +0 -3
- package/dist/ui/icons/images/tilt.js +0 -3
- package/dist/ui/icons/images/undo.js +0 -3
- package/dist/ui/icons/images/unlinked.js +0 -3
- package/dist/ui/icons/images/unlocked.js +0 -3
- package/dist/ui/icons/images/visible.js +0 -3
- package/dist/ui/icons/images/zoom-in.js +0 -3
- package/dist/ui/icons/images/zoom-out.js +0 -3
- package/dist/ui/icons/images/zoom.js +0 -3
|
@@ -5,6 +5,10 @@ var MIN_BRUSH_SIZE = 1;
|
|
|
5
5
|
var MAX_BRUSH_SIZE = 256;
|
|
6
6
|
/** Fraction of the current size added or removed by `grow` and `shrink` ("]" and "[" keys) */
|
|
7
7
|
var BRUSH_SIZE_STEP = .1;
|
|
8
|
+
/** Opacity step for the opacity keyboard shortcuts, in percent */
|
|
9
|
+
var BRUSH_OPACITY_STEP = 10;
|
|
10
|
+
/** Lowest opacity the opacity keyboard shortcuts go to, in percent */
|
|
11
|
+
var MIN_BRUSH_OPACITY = 1;
|
|
8
12
|
/**
|
|
9
13
|
* Clamp a value between min and max.
|
|
10
14
|
*
|
|
@@ -13,6 +17,35 @@ var BRUSH_SIZE_STEP = .1;
|
|
|
13
17
|
*/
|
|
14
18
|
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
15
19
|
/**
|
|
20
|
+
* The next opacity step up or down, in percent: to the next multiple of `BRUSH_OPACITY_STEP`, from
|
|
21
|
+
* `MIN_BRUSH_OPACITY` to 100.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* stepOpacity(45, 1); // 50
|
|
25
|
+
* stepOpacity(10, -1); // 1
|
|
26
|
+
* stepOpacity(1, 1); // 10
|
|
27
|
+
*/
|
|
28
|
+
var stepOpacity = (percent, direction) => {
|
|
29
|
+
const steps = percent / 10;
|
|
30
|
+
return clamp((direction > 0 ? Math.floor(steps + 1e-9) + 1 : Math.ceil(steps - 1e-9) - 1) * 10, 1, 100);
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* A brush size with the given width and proportions (width ÷ height), in whole pixels. The width is kept within the
|
|
34
|
+
* range where the smaller side is at least `MIN_BRUSH_SIZE` and the larger side at most `MAX_BRUSH_SIZE`, so the
|
|
35
|
+
* proportions are kept at either end.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* proportionalSize(0, 3); // { width: 3, height: 1 }
|
|
39
|
+
* proportionalSize(1000, 0.5); // { width: 128, height: 256 }
|
|
40
|
+
*/
|
|
41
|
+
var proportionalSize = (width, aspect) => {
|
|
42
|
+
const fitted = Math.round(clamp(width, Math.max(1, 1 * aspect), Math.min(256, 256 * aspect)));
|
|
43
|
+
return {
|
|
44
|
+
width: fitted,
|
|
45
|
+
height: Math.round(clamp(fitted / aspect, 1, 256))
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
16
49
|
* The settings of a brush or eraser: the selected preset, an editable copy of its settings (changed from the tool
|
|
17
50
|
* settings bar or the configuration window, without changing the preset) and the size. The brush and eraser each have
|
|
18
51
|
* their own instance.
|
|
@@ -35,6 +68,11 @@ var BrushSettings = class extends EventTarget {
|
|
|
35
68
|
presetId;
|
|
36
69
|
/** The current settings: a copy of the selected preset, with any changes */
|
|
37
70
|
current;
|
|
71
|
+
/**
|
|
72
|
+
* The brush's proportions (width ÷ height), kept apart from the rounded size so resizing to either end and back
|
|
73
|
+
* doesn't change them. Set by the preset and by setting the size while proportions aren't constrained.
|
|
74
|
+
*/
|
|
75
|
+
aspect = 1;
|
|
38
76
|
constructor(presets) {
|
|
39
77
|
super();
|
|
40
78
|
const first = Object.values(presets)[0];
|
|
@@ -101,39 +139,64 @@ var BrushSettings = class extends EventTarget {
|
|
|
101
139
|
* Set the height, clamped to 1–256 pixels. If the settings constrain proportions, width follows height.
|
|
102
140
|
*/
|
|
103
141
|
setHeight(height) {
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
const ratio = this.width / this.height;
|
|
109
|
-
this.setSize(clamp(height, 1, 256) * ratio);
|
|
142
|
+
if (this.current.constrainProportions) this.setProportionalSize(height * this.aspect);
|
|
143
|
+
else this.setSize(this.width, height);
|
|
110
144
|
}
|
|
111
145
|
/**
|
|
112
|
-
* Set the size, clamped to 1–256 pixels. If the settings constrain proportions, height follows width
|
|
146
|
+
* Set the size, clamped to 1–256 pixels. If the settings constrain proportions, height follows width, and the width
|
|
147
|
+
* stops where either side would leave that range.
|
|
113
148
|
*/
|
|
114
149
|
setSize(width, height = width) {
|
|
115
|
-
|
|
150
|
+
if (this.current.constrainProportions) {
|
|
151
|
+
this.setProportionalSize(width);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
116
154
|
this.width = Math.round(clamp(width, 1, 256));
|
|
117
|
-
this.height = Math.round(clamp(
|
|
155
|
+
this.height = Math.round(clamp(height, 1, 256));
|
|
156
|
+
this.aspect = this.width / this.height;
|
|
118
157
|
this.notify();
|
|
119
158
|
}
|
|
120
159
|
/**
|
|
121
|
-
* Change the size by a number of pixels (positive = bigger)
|
|
160
|
+
* Change the size by a number of pixels (positive = bigger), keeping the proportions: the larger side changes by
|
|
161
|
+
* `delta`. Stops when the smaller side reaches 1 pixel or the larger 256. Used when dragging up/right (bigger) or
|
|
162
|
+
* down/left (smaller).
|
|
122
163
|
*/
|
|
123
164
|
resize(delta) {
|
|
124
|
-
|
|
165
|
+
const larger = Math.max(this.width, this.height) + delta;
|
|
166
|
+
this.setProportionalSize(this.aspect >= 1 ? larger : larger * this.aspect);
|
|
167
|
+
}
|
|
168
|
+
/** The larger side (what `resize` changes), which `grow` and `shrink` step from */
|
|
169
|
+
get largerSide() {
|
|
170
|
+
return Math.max(this.width, this.height);
|
|
125
171
|
}
|
|
126
172
|
/**
|
|
127
173
|
* Increase the size by one step (at least 1 pixel).
|
|
128
174
|
*/
|
|
129
175
|
grow() {
|
|
130
|
-
this.resize(Math.max(1, Math.round(this.
|
|
176
|
+
this.resize(Math.max(1, Math.round(this.largerSide * BRUSH_SIZE_STEP)));
|
|
131
177
|
}
|
|
132
178
|
/**
|
|
133
179
|
* Decrease the size by one step (at least 1 pixel).
|
|
134
180
|
*/
|
|
135
181
|
shrink() {
|
|
136
|
-
this.resize(-Math.max(1, Math.round(this.
|
|
182
|
+
this.resize(-Math.max(1, Math.round(this.largerSide * BRUSH_SIZE_STEP)));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Increase the opacity by one step (see `stepOpacity`).
|
|
186
|
+
*/
|
|
187
|
+
moreOpaque() {
|
|
188
|
+
this.stepOpacity(1);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Decrease the opacity by one step (see `stepOpacity`).
|
|
192
|
+
*/
|
|
193
|
+
lessOpaque() {
|
|
194
|
+
this.stepOpacity(-1);
|
|
195
|
+
}
|
|
196
|
+
/** Change the opacity by one step up or down */
|
|
197
|
+
stepOpacity(direction) {
|
|
198
|
+
const percent = Math.round((this.current.alpha ?? 1) * 100);
|
|
199
|
+
this.update({ alpha: stepOpacity(percent, direction) / 100 });
|
|
137
200
|
}
|
|
138
201
|
/**
|
|
139
202
|
* Use a preset's (or configuration's) default size.
|
|
@@ -141,6 +204,14 @@ var BrushSettings = class extends EventTarget {
|
|
|
141
204
|
applyPresetSize(preset) {
|
|
142
205
|
this.width = preset.width ?? 1;
|
|
143
206
|
this.height = preset.height ?? this.width;
|
|
207
|
+
this.aspect = this.width / this.height;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Set the width, with the height following the stored proportions (see `proportionalSize`).
|
|
211
|
+
*/
|
|
212
|
+
setProportionalSize(width) {
|
|
213
|
+
Object.assign(this, proportionalSize(width, this.aspect));
|
|
214
|
+
this.notify();
|
|
144
215
|
}
|
|
145
216
|
/**
|
|
146
217
|
* Dispatch a `change` event.
|
|
@@ -149,4 +220,4 @@ var BrushSettings = class extends EventTarget {
|
|
|
149
220
|
this.dispatchEvent(new Event("change"));
|
|
150
221
|
}
|
|
151
222
|
};
|
|
152
|
-
export { BRUSH_SIZE_STEP, MAX_BRUSH_SIZE, MIN_BRUSH_SIZE, clamp, BrushSettings as default };
|
|
223
|
+
export { BRUSH_OPACITY_STEP, BRUSH_SIZE_STEP, MAX_BRUSH_SIZE, MIN_BRUSH_OPACITY, MIN_BRUSH_SIZE, clamp, BrushSettings as default, proportionalSize, stepOpacity };
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import { type default as PaintCanvas } from "../PaintCanvas";
|
|
2
2
|
import { type ShortcutAction } from "../../utils/shortcuts";
|
|
3
|
+
/**
|
|
4
|
+
* How long (milliseconds) the paste shortcut waits for the browser's `paste` event (which carries other apps' images)
|
|
5
|
+
* before pasting the image data copied here
|
|
6
|
+
*/
|
|
7
|
+
export declare const PASTE_EVENT_WAIT_MS = 50;
|
|
8
|
+
/**
|
|
9
|
+
* The first image in a paste's clipboard data (e.g. copied in another app), if any.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* pastedImage(event.clipboardData); // File of type "image/png", or undefined
|
|
13
|
+
*/
|
|
14
|
+
export declare const pastedImage: (data: DataTransfer | null) => File | undefined;
|
|
3
15
|
/** Event dispatched on the window for shortcut actions handled elsewhere (e.g. saving) */
|
|
4
16
|
export declare const SHORTCUT_EVENT = "pc-shortcut";
|
|
5
17
|
/**
|
|
6
|
-
* Listens for key presses and runs the matching shortcut on the app
|
|
18
|
+
* Listens for key presses and runs the matching shortcut on the app, using the user's key combinations
|
|
19
|
+
* (`app.shortcuts`). Shortcuts are ignored while typing in text fields and inside open `<dialog>` elements. Key presses are matched on keyboards with
|
|
20
|
+
* different layouts (see `comboFromEvent`).
|
|
7
21
|
*
|
|
8
|
-
* Tool selection, brush/eraser size, interface visibility, select all/deselect, undo/redo,
|
|
9
|
-
* masked image data (Delete or Backspace)
|
|
10
|
-
* lasso shape are handled here
|
|
11
|
-
*
|
|
22
|
+
* Tool selection, brush/eraser size and opacity, interface visibility, select all/invert/deselect, undo/redo,
|
|
23
|
+
* cut/copy/paste, deleting the masked image data (Delete or Backspace), fills, swapping colours, zooming, layer opacity
|
|
24
|
+
* and finishing or cancelling a lasso shape are handled here. Pasting (the shortcut, or the browser's own paste, e.g. from
|
|
25
|
+
* its Edit menu) pastes the image data copied here, unless the window has lost focus since it was copied and the
|
|
26
|
+
* clipboard has an image (e.g. copied in another app): that image is pasted instead (see `PaintCanvas.pasteImage`).
|
|
27
|
+
* Other actions other actions (and Escape/Enter for later features)
|
|
28
|
+
* dispatch an `pc-shortcut` event with the action as `detail`. Holding the hold-to-pan key (Space by default) pans
|
|
29
|
+
* temporarily; releasing it returns to the selected tool.
|
|
12
30
|
*
|
|
13
31
|
* @example
|
|
14
32
|
* const shortcuts = new KeyboardShortcuts(app);
|
|
@@ -17,10 +35,19 @@ export declare const SHORTCUT_EVENT = "pc-shortcut";
|
|
|
17
35
|
export default class KeyboardShortcuts {
|
|
18
36
|
private readonly app;
|
|
19
37
|
private target;
|
|
38
|
+
/** The hold-to-pan key while it is held */
|
|
39
|
+
private heldKey?;
|
|
20
40
|
private readonly onKeyDown;
|
|
21
41
|
private readonly onKeyUp;
|
|
42
|
+
/** Turns pasted image files into images (replaceable for tests) */
|
|
43
|
+
private readonly decodeImage;
|
|
44
|
+
/** `app.copyCount` when the window last lost focus: copies since then were made here */
|
|
45
|
+
private copiesBeforeBlur;
|
|
46
|
+
/** Pastes the image data copied here if the browser doesn't fire `paste` soon after the shortcut */
|
|
47
|
+
private pasteTimer?;
|
|
22
48
|
private readonly onBlur;
|
|
23
|
-
|
|
49
|
+
private readonly onPaste;
|
|
50
|
+
constructor(app: PaintCanvas, decodeImage?: (blob: Blob) => Promise<ImageBitmap>);
|
|
24
51
|
/**
|
|
25
52
|
* Start listening for key presses on a target (usually `window`).
|
|
26
53
|
*/
|
|
@@ -34,13 +61,26 @@ export default class KeyboardShortcuts {
|
|
|
34
61
|
*/
|
|
35
62
|
handle(event: KeyboardEvent): ShortcutAction | undefined;
|
|
36
63
|
/**
|
|
37
|
-
*
|
|
64
|
+
* Paste from the browser's `paste` event: the image data copied here if it was copied since the window last lost
|
|
65
|
+
* focus (or the clipboard has no image), otherwise the clipboard's image. Ignored in text fields and open dialogs.
|
|
66
|
+
*/
|
|
67
|
+
handlePaste(event: ClipboardEvent): Promise<void>;
|
|
68
|
+
/** Stop waiting for the browser's `paste` event */
|
|
69
|
+
private cancelPasteTimer;
|
|
70
|
+
/**
|
|
71
|
+
* Return to the selected tool when the hold-to-pan key is released (whatever modifiers are held by then).
|
|
38
72
|
*/
|
|
39
73
|
handleKeyUp(event: KeyboardEvent): void;
|
|
74
|
+
/** Stop holding the hold-to-pan key */
|
|
75
|
+
private release;
|
|
40
76
|
/**
|
|
41
77
|
* Run a shortcut action.
|
|
42
78
|
*/
|
|
43
79
|
run(action: ShortcutAction): void;
|
|
80
|
+
/** The brush or eraser settings while that tool is selected */
|
|
81
|
+
private brushSettings;
|
|
82
|
+
/** The middle of the viewport, which zoom shortcuts zoom around */
|
|
83
|
+
private viewportCentre;
|
|
44
84
|
/**
|
|
45
85
|
* Dispatch an action as an `pc-shortcut` event for features handled elsewhere.
|
|
46
86
|
*/
|
|
@@ -1,15 +1,39 @@
|
|
|
1
|
-
import { Tool } from "../PaintCanvas/
|
|
2
|
-
import
|
|
1
|
+
import { Tool } from "../PaintCanvas/tool.js";
|
|
2
|
+
import "../PaintCanvas/index.js";
|
|
3
|
+
import { comboFromEvent, isTypingTarget, sameCombo } from "../../utils/shortcuts/index.js";
|
|
3
4
|
// src/lib/classes/KeyboardShortcuts/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Whether a key press happened inside an open `<dialog>` (e.g. a settings window), where shortcuts must not apply and
|
|
7
|
+
* Escape is left to close it.
|
|
8
|
+
*/
|
|
9
|
+
var inOpenDialog = (path) => path.some((target) => target instanceof HTMLDialogElement && target.open);
|
|
10
|
+
/**
|
|
11
|
+
* How long (milliseconds) the paste shortcut waits for the browser's `paste` event (which carries other apps' images)
|
|
12
|
+
* before pasting the image data copied here
|
|
13
|
+
*/
|
|
14
|
+
var PASTE_EVENT_WAIT_MS = 50;
|
|
15
|
+
/**
|
|
16
|
+
* The first image in a paste's clipboard data (e.g. copied in another app), if any.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* pastedImage(event.clipboardData); // File of type "image/png", or undefined
|
|
20
|
+
*/
|
|
21
|
+
var pastedImage = (data) => [...data?.files ?? []].find((file) => file.type.startsWith("image/"));
|
|
4
22
|
/** Event dispatched on the window for shortcut actions handled elsewhere (e.g. saving) */
|
|
5
23
|
var SHORTCUT_EVENT = "pc-shortcut";
|
|
6
24
|
/**
|
|
7
|
-
* Listens for key presses and runs the matching shortcut on the app
|
|
25
|
+
* Listens for key presses and runs the matching shortcut on the app, using the user's key combinations
|
|
26
|
+
* (`app.shortcuts`). Shortcuts are ignored while typing in text fields and inside open `<dialog>` elements. Key presses are matched on keyboards with
|
|
27
|
+
* different layouts (see `comboFromEvent`).
|
|
8
28
|
*
|
|
9
|
-
* Tool selection, brush/eraser size, interface visibility, select all/deselect, undo/redo,
|
|
10
|
-
* masked image data (Delete or Backspace)
|
|
11
|
-
* lasso shape are handled here
|
|
12
|
-
*
|
|
29
|
+
* Tool selection, brush/eraser size and opacity, interface visibility, select all/invert/deselect, undo/redo,
|
|
30
|
+
* cut/copy/paste, deleting the masked image data (Delete or Backspace), fills, swapping colours, zooming, layer opacity
|
|
31
|
+
* and finishing or cancelling a lasso shape are handled here. Pasting (the shortcut, or the browser's own paste, e.g. from
|
|
32
|
+
* its Edit menu) pastes the image data copied here, unless the window has lost focus since it was copied and the
|
|
33
|
+
* clipboard has an image (e.g. copied in another app): that image is pasted instead (see `PaintCanvas.pasteImage`).
|
|
34
|
+
* Other actions other actions (and Escape/Enter for later features)
|
|
35
|
+
* dispatch an `pc-shortcut` event with the action as `detail`. Holding the hold-to-pan key (Space by default) pans
|
|
36
|
+
* temporarily; releasing it returns to the selected tool.
|
|
13
37
|
*
|
|
14
38
|
* @example
|
|
15
39
|
* const shortcuts = new KeyboardShortcuts(app);
|
|
@@ -18,11 +42,24 @@ var SHORTCUT_EVENT = "pc-shortcut";
|
|
|
18
42
|
var KeyboardShortcuts = class {
|
|
19
43
|
app;
|
|
20
44
|
target = null;
|
|
45
|
+
/** The hold-to-pan key while it is held */
|
|
46
|
+
heldKey;
|
|
21
47
|
onKeyDown = (event) => this.handle(event);
|
|
22
48
|
onKeyUp = (event) => this.handleKeyUp(event);
|
|
23
|
-
|
|
24
|
-
|
|
49
|
+
/** Turns pasted image files into images (replaceable for tests) */
|
|
50
|
+
decodeImage;
|
|
51
|
+
/** `app.copyCount` when the window last lost focus: copies since then were made here */
|
|
52
|
+
copiesBeforeBlur = 0;
|
|
53
|
+
/** Pastes the image data copied here if the browser doesn't fire `paste` soon after the shortcut */
|
|
54
|
+
pasteTimer;
|
|
55
|
+
onBlur = () => {
|
|
56
|
+
this.release();
|
|
57
|
+
this.copiesBeforeBlur = this.app.copyCount;
|
|
58
|
+
};
|
|
59
|
+
onPaste = (event) => void this.handlePaste(event);
|
|
60
|
+
constructor(app, decodeImage = (blob) => createImageBitmap(blob)) {
|
|
25
61
|
this.app = app;
|
|
62
|
+
this.decodeImage = decodeImage;
|
|
26
63
|
}
|
|
27
64
|
/**
|
|
28
65
|
* Start listening for key presses on a target (usually `window`).
|
|
@@ -33,6 +70,7 @@ var KeyboardShortcuts = class {
|
|
|
33
70
|
target.addEventListener("keydown", this.onKeyDown);
|
|
34
71
|
target.addEventListener("keyup", this.onKeyUp);
|
|
35
72
|
target.addEventListener("blur", this.onBlur);
|
|
73
|
+
target.addEventListener("paste", this.onPaste);
|
|
36
74
|
}
|
|
37
75
|
/**
|
|
38
76
|
* Stop listening for key presses.
|
|
@@ -41,29 +79,74 @@ var KeyboardShortcuts = class {
|
|
|
41
79
|
this.target?.removeEventListener("keydown", this.onKeyDown);
|
|
42
80
|
this.target?.removeEventListener("keyup", this.onKeyUp);
|
|
43
81
|
this.target?.removeEventListener("blur", this.onBlur);
|
|
82
|
+
this.target?.removeEventListener("paste", this.onPaste);
|
|
44
83
|
this.target = null;
|
|
84
|
+
this.cancelPasteTimer();
|
|
45
85
|
}
|
|
46
86
|
/**
|
|
47
87
|
* Run the shortcut for a key press, if any. Returns the action run.
|
|
48
88
|
*/
|
|
49
89
|
handle(event) {
|
|
50
|
-
|
|
51
|
-
if (
|
|
90
|
+
const path = event.composedPath();
|
|
91
|
+
if (isTypingTarget(path[0] ?? event.target) || inOpenDialog(path)) return void 0;
|
|
92
|
+
const combo = comboFromEvent(event);
|
|
93
|
+
if (!combo) return void 0;
|
|
94
|
+
const { holdToPan, shortcuts } = this.app.shortcuts;
|
|
95
|
+
if (holdToPan && sameCombo(combo, holdToPan)) {
|
|
52
96
|
event.preventDefault();
|
|
97
|
+
this.heldKey = combo.key;
|
|
53
98
|
this.app.holdTool(Tool.Pan);
|
|
54
99
|
return;
|
|
55
100
|
}
|
|
56
|
-
const action =
|
|
101
|
+
const action = shortcuts.find((shortcut) => sameCombo(shortcut, combo))?.action;
|
|
57
102
|
if (!action) return void 0;
|
|
103
|
+
if (action.type === "paste") {
|
|
104
|
+
this.cancelPasteTimer();
|
|
105
|
+
this.pasteTimer = setTimeout(() => {
|
|
106
|
+
this.pasteTimer = void 0;
|
|
107
|
+
this.app.paste();
|
|
108
|
+
}, 50);
|
|
109
|
+
return action;
|
|
110
|
+
}
|
|
58
111
|
event.preventDefault();
|
|
59
112
|
this.run(action);
|
|
60
113
|
return action;
|
|
61
114
|
}
|
|
62
115
|
/**
|
|
63
|
-
*
|
|
116
|
+
* Paste from the browser's `paste` event: the image data copied here if it was copied since the window last lost
|
|
117
|
+
* focus (or the clipboard has no image), otherwise the clipboard's image. Ignored in text fields and open dialogs.
|
|
118
|
+
*/
|
|
119
|
+
async handlePaste(event) {
|
|
120
|
+
const path = event.composedPath();
|
|
121
|
+
if (isTypingTarget(path[0] ?? event.target) || inOpenDialog(path)) return;
|
|
122
|
+
this.cancelPasteTimer();
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
const app = this.app;
|
|
125
|
+
const image = pastedImage(event.clipboardData);
|
|
126
|
+
const copiedHere = app.copyCount > this.copiesBeforeBlur;
|
|
127
|
+
if (!image || copiedHere && app.hasClipboard) {
|
|
128
|
+
app.paste();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
app.pasteImage(await this.decodeImage(image));
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
135
|
+
/** Stop waiting for the browser's `paste` event */
|
|
136
|
+
cancelPasteTimer() {
|
|
137
|
+
clearTimeout(this.pasteTimer);
|
|
138
|
+
this.pasteTimer = void 0;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Return to the selected tool when the hold-to-pan key is released (whatever modifiers are held by then).
|
|
64
142
|
*/
|
|
65
143
|
handleKeyUp(event) {
|
|
66
|
-
if (event
|
|
144
|
+
if (this.heldKey !== void 0 && comboFromEvent(event)?.key === this.heldKey) this.release();
|
|
145
|
+
}
|
|
146
|
+
/** Stop holding the hold-to-pan key */
|
|
147
|
+
release() {
|
|
148
|
+
this.heldKey = void 0;
|
|
149
|
+
this.app.releaseHeldTool();
|
|
67
150
|
}
|
|
68
151
|
/**
|
|
69
152
|
* Run a shortcut action.
|
|
@@ -75,21 +158,41 @@ var KeyboardShortcuts = class {
|
|
|
75
158
|
app.selectTool(action.tool);
|
|
76
159
|
break;
|
|
77
160
|
case "brush-smaller":
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
161
|
+
this.brushSettings()?.shrink();
|
|
162
|
+
break;
|
|
163
|
+
case "brush-bigger":
|
|
164
|
+
this.brushSettings()?.grow();
|
|
165
|
+
break;
|
|
166
|
+
case "brush-less-opaque":
|
|
167
|
+
this.brushSettings()?.lessOpaque();
|
|
168
|
+
break;
|
|
169
|
+
case "brush-more-opaque":
|
|
170
|
+
this.brushSettings()?.moreOpaque();
|
|
82
171
|
break;
|
|
83
|
-
}
|
|
84
172
|
case "toggle-interface":
|
|
85
173
|
app.toggleInterface();
|
|
86
174
|
break;
|
|
87
175
|
case "select-all":
|
|
88
176
|
app.selection.selectAll();
|
|
89
177
|
break;
|
|
178
|
+
case "invert-selection":
|
|
179
|
+
app.selection.invert();
|
|
180
|
+
break;
|
|
90
181
|
case "deselect":
|
|
91
182
|
app.selection.clear();
|
|
92
183
|
break;
|
|
184
|
+
case "zoom-in":
|
|
185
|
+
app.viewport.zoomIn(this.viewportCentre());
|
|
186
|
+
break;
|
|
187
|
+
case "zoom-out":
|
|
188
|
+
app.viewport.zoomOut(this.viewportCentre());
|
|
189
|
+
break;
|
|
190
|
+
case "reset-zoom":
|
|
191
|
+
app.viewport.zoomAt(this.viewportCentre(), 1);
|
|
192
|
+
break;
|
|
193
|
+
case "layer-opacity":
|
|
194
|
+
app.layers.update(app.layers.active.id, { opacity: action.opacity });
|
|
195
|
+
break;
|
|
93
196
|
case "undo":
|
|
94
197
|
app.undo();
|
|
95
198
|
break;
|
|
@@ -102,6 +205,15 @@ var KeyboardShortcuts = class {
|
|
|
102
205
|
case "delete":
|
|
103
206
|
app.deleteMasked();
|
|
104
207
|
break;
|
|
208
|
+
case "fill-foreground":
|
|
209
|
+
app.fill("foreground");
|
|
210
|
+
break;
|
|
211
|
+
case "fill-background":
|
|
212
|
+
app.fill("background");
|
|
213
|
+
break;
|
|
214
|
+
case "swap-colours":
|
|
215
|
+
app.swapColours();
|
|
216
|
+
break;
|
|
105
217
|
case "copy":
|
|
106
218
|
app.copy();
|
|
107
219
|
break;
|
|
@@ -127,6 +239,19 @@ var KeyboardShortcuts = class {
|
|
|
127
239
|
default: this.dispatch(action);
|
|
128
240
|
}
|
|
129
241
|
}
|
|
242
|
+
/** The brush or eraser settings while that tool is selected */
|
|
243
|
+
brushSettings() {
|
|
244
|
+
const { app } = this;
|
|
245
|
+
return app.tool === Tool.Eraser ? app.eraser : app.tool === Tool.Brush ? app.brush : void 0;
|
|
246
|
+
}
|
|
247
|
+
/** The middle of the viewport, which zoom shortcuts zoom around */
|
|
248
|
+
viewportCentre() {
|
|
249
|
+
const { width, height } = this.app.viewport;
|
|
250
|
+
return {
|
|
251
|
+
x: width / 2,
|
|
252
|
+
y: height / 2
|
|
253
|
+
};
|
|
254
|
+
}
|
|
130
255
|
/**
|
|
131
256
|
* Dispatch an action as an `pc-shortcut` event for features handled elsewhere.
|
|
132
257
|
*/
|
|
@@ -134,4 +259,4 @@ var KeyboardShortcuts = class {
|
|
|
134
259
|
(this.target ?? this.app).dispatchEvent(new CustomEvent(SHORTCUT_EVENT, { detail: action }));
|
|
135
260
|
}
|
|
136
261
|
};
|
|
137
|
-
export { SHORTCUT_EVENT, KeyboardShortcuts as default };
|
|
262
|
+
export { PASTE_EVENT_WAIT_MS, SHORTCUT_EVENT, KeyboardShortcuts as default, pastedImage };
|