@lvl99dotcom/paintcanvas 0.4.0 → 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/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 +29 -2
- package/dist/lib/classes/BrushSettings/index.js +51 -14
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +31 -2
- package/dist/lib/classes/KeyboardShortcuts/index.js +66 -4
- package/dist/lib/classes/PaintCanvas/index.d.ts +46 -0
- package/dist/lib/classes/PaintCanvas/index.js +67 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +3 -2
- package/dist/lib/classes/SettingsAutosave/index.js +5 -3
- package/dist/lib/classes/UiSettings/index.d.ts +68 -0
- package/dist/lib/classes/UiSettings/index.js +82 -0
- package/dist/lib/settings/index.d.ts +1 -1
- package/dist/lib/settings/index.js +4 -2
- package/dist/lib/settings/schema.d.ts +18 -0
- package/dist/lib/settings/schema.js +14 -0
- package/dist/lib/validation/validators.generated.js +169 -1
- package/dist/locales/en-GB.js +9 -2
- package/dist/locales/en-US.js +9 -2
- package/dist/locales/fr.js +9 -2
- package/dist/locales/ja.js +9 -2
- package/dist/locales/ko.js +9 -2
- package/dist/locales/zh-Hans.js +9 -2
- package/dist/locales/zh-Hant.js +9 -2
- 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 +17 -12
- package/dist/ui/elements/PaintCanvas/index.js +34 -17
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +1 -1
- package/dist/ui/elements/PcAppSettings/index.js +1 -1
- 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 +21 -1
- package/dist/ui/elements/PcCanvas/index.js +53 -10
- 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/PcContextMenu/index.d.ts +2 -2
- package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
- package/dist/ui/elements/PcInputSettings/index.js +16 -1
- package/dist/ui/elements/PcMenu/index.d.ts +48 -3
- package/dist/ui/elements/PcMenu/index.js +111 -19
- 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 +3 -1
- package/dist/ui/elements/PcPane/index.js +44 -7
- package/dist/ui/elements/PcPanel/index.d.ts +2 -2
- package/dist/ui/elements/PcPanel/index.js +8 -8
- package/dist/ui/elements/PcPanelBrushes/index.d.ts +2 -15
- package/dist/ui/elements/PcPanelBrushes/index.js +6 -43
- package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
- package/dist/ui/elements/PcPopover/index.d.ts +39 -1
- package/dist/ui/elements/PcPopover/index.js +72 -4
- 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 +382 -318
- package/dist/ui/elements/PcToolSettings/style.js +1 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +15 -0
- package/dist/ui/elements/PcToolbar/index.js +55 -14
- 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 +7 -2
- package/dist/ui/index.js +10 -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 +12 -7
- package/locales/en-GB.json +9 -2
- package/locales/en-US.json +9 -2
- package/locales/fr.json +9 -2
- package/locales/ja.json +9 -2
- package/locales/ko.json +9 -2
- package/locales/zh-Hans.json +9 -2
- package/locales/zh-Hant.json +9 -2
- package/package.json +11 -4
- 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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/ui/dismiss/index.ts
|
|
2
|
+
/** Presses that closed a panel or menu by landing outside it */
|
|
3
|
+
var dismissingPresses = /* @__PURE__ */ new WeakSet();
|
|
4
|
+
/**
|
|
5
|
+
* Note that a press closed a panel or menu (by landing outside it), so what it landed on can treat it differently, e.g.
|
|
6
|
+
* the canvas only draws once it's dragged (see `isDismissingPress`). Call it from a capturing `pointerdown` listener,
|
|
7
|
+
* which runs before the listeners of what was pressed.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* document.addEventListener("pointerdown", (event) => {
|
|
11
|
+
* if (open && !event.composedPath().includes(panel)) {
|
|
12
|
+
* markDismissingPress(event);
|
|
13
|
+
* close();
|
|
14
|
+
* }
|
|
15
|
+
* }, true);
|
|
16
|
+
*/
|
|
17
|
+
var markDismissingPress = (event) => {
|
|
18
|
+
dismissingPresses.add(event);
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Whether a press closed a panel or menu (see `markDismissingPress`).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* if (isDismissingPress(event)) return; // just closing something
|
|
25
|
+
*/
|
|
26
|
+
var isDismissingPress = (event) => dismissingPresses.has(event);
|
|
27
|
+
export { isDismissingPress, markDismissingPress };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** How far (in pixels) a press must move before it counts as a drag, where a press can also be a tap */
|
|
2
|
+
export declare const DRAG_THRESHOLD = 4;
|
|
3
|
+
/** A pointer position in the viewport */
|
|
4
|
+
interface Point {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* How much a pointer moved between two points as one value: right and up are positive, left and down negative.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* dragDelta({ x: 0, y: 0 }, { x: 5, y: -5 }); // 10
|
|
13
|
+
*/
|
|
14
|
+
export declare const dragDelta: (from: Point, to: Point) => number;
|
|
15
|
+
/**
|
|
16
|
+
* Turns pressing and dragging on an element into changes of a value: dragging right or up gives positive deltas, left
|
|
17
|
+
* or down negative ones (see `dragDelta`), in whole pixels (keeping any fraction for the next move). With a `threshold`, the press only becomes a drag once the pointer has moved
|
|
18
|
+
* that far from where it was pressed, so a press that barely moves can still be a tap (e.g. to focus a field).
|
|
19
|
+
*
|
|
20
|
+
* The element is given the pointer while dragging, so the drag carries on outside it.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const drag = new PointerDrag((delta) => brush.resize(delta));
|
|
24
|
+
* html`<div @pointerdown=${drag.start} @pointermove=${drag.move} @pointerup=${drag.end} @pointercancel=${drag.end}></div>`
|
|
25
|
+
*/
|
|
26
|
+
export declare class PointerDrag {
|
|
27
|
+
private readonly onDelta;
|
|
28
|
+
private readonly threshold;
|
|
29
|
+
/** Where the press started */
|
|
30
|
+
private origin;
|
|
31
|
+
/** The last position a delta was given from, once dragging */
|
|
32
|
+
private last;
|
|
33
|
+
/** Movement not yet given, under a whole pixel (pens can move by fractions of a pixel) */
|
|
34
|
+
private remainder;
|
|
35
|
+
constructor(onDelta: (delta: number) => void, threshold?: number);
|
|
36
|
+
/** Whether the press has become a drag */
|
|
37
|
+
get dragging(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Start a press (on `pointerdown`).
|
|
40
|
+
*/
|
|
41
|
+
readonly start: (event: PointerEvent) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Give the change since the last move, once the press has become a drag (on `pointermove`).
|
|
44
|
+
*/
|
|
45
|
+
readonly move: (event: PointerEvent) => void;
|
|
46
|
+
/**
|
|
47
|
+
* End the press (on `pointerup` or `pointercancel`). Returns whether it was a drag.
|
|
48
|
+
*/
|
|
49
|
+
readonly end: () => boolean;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/ui/drag/index.ts
|
|
2
|
+
/** How far (in pixels) a press must move before it counts as a drag, where a press can also be a tap */
|
|
3
|
+
var DRAG_THRESHOLD = 4;
|
|
4
|
+
/**
|
|
5
|
+
* How much a pointer moved between two points as one value: right and up are positive, left and down negative.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* dragDelta({ x: 0, y: 0 }, { x: 5, y: -5 }); // 10
|
|
9
|
+
*/
|
|
10
|
+
var dragDelta = (from, to) => to.x - from.x + (from.y - to.y);
|
|
11
|
+
/**
|
|
12
|
+
* Turns pressing and dragging on an element into changes of a value: dragging right or up gives positive deltas, left
|
|
13
|
+
* or down negative ones (see `dragDelta`), in whole pixels (keeping any fraction for the next move). With a `threshold`, the press only becomes a drag once the pointer has moved
|
|
14
|
+
* that far from where it was pressed, so a press that barely moves can still be a tap (e.g. to focus a field).
|
|
15
|
+
*
|
|
16
|
+
* The element is given the pointer while dragging, so the drag carries on outside it.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const drag = new PointerDrag((delta) => brush.resize(delta));
|
|
20
|
+
* html`<div @pointerdown=${drag.start} @pointermove=${drag.move} @pointerup=${drag.end} @pointercancel=${drag.end}></div>`
|
|
21
|
+
*/
|
|
22
|
+
var PointerDrag = class {
|
|
23
|
+
onDelta;
|
|
24
|
+
threshold;
|
|
25
|
+
/** Where the press started */
|
|
26
|
+
origin = null;
|
|
27
|
+
/** The last position a delta was given from, once dragging */
|
|
28
|
+
last = null;
|
|
29
|
+
/** Movement not yet given, under a whole pixel (pens can move by fractions of a pixel) */
|
|
30
|
+
remainder = 0;
|
|
31
|
+
constructor(onDelta, threshold = 0) {
|
|
32
|
+
this.onDelta = onDelta;
|
|
33
|
+
this.threshold = threshold;
|
|
34
|
+
}
|
|
35
|
+
/** Whether the press has become a drag */
|
|
36
|
+
get dragging() {
|
|
37
|
+
return this.last !== null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Start a press (on `pointerdown`).
|
|
41
|
+
*/
|
|
42
|
+
start = (event) => {
|
|
43
|
+
this.origin = {
|
|
44
|
+
x: event.clientX,
|
|
45
|
+
y: event.clientY
|
|
46
|
+
};
|
|
47
|
+
this.last = this.threshold > 0 ? null : this.origin;
|
|
48
|
+
this.remainder = 0;
|
|
49
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Give the change since the last move, once the press has become a drag (on `pointermove`).
|
|
53
|
+
*/
|
|
54
|
+
move = (event) => {
|
|
55
|
+
if (!this.origin) return;
|
|
56
|
+
const point = {
|
|
57
|
+
x: event.clientX,
|
|
58
|
+
y: event.clientY
|
|
59
|
+
};
|
|
60
|
+
if (!this.last) {
|
|
61
|
+
if (Math.hypot(point.x - this.origin.x, point.y - this.origin.y) < this.threshold) return;
|
|
62
|
+
this.last = this.origin;
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
}
|
|
65
|
+
const total = this.remainder + dragDelta(this.last, point);
|
|
66
|
+
const delta = Math.trunc(total);
|
|
67
|
+
this.remainder = total - delta;
|
|
68
|
+
this.last = point;
|
|
69
|
+
if (delta !== 0) this.onDelta(delta);
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* End the press (on `pointerup` or `pointercancel`). Returns whether it was a drag.
|
|
73
|
+
*/
|
|
74
|
+
end = () => {
|
|
75
|
+
const dragged = this.dragging;
|
|
76
|
+
this.origin = null;
|
|
77
|
+
this.last = null;
|
|
78
|
+
return dragged;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
export { DRAG_THRESHOLD, PointerDrag, dragDelta };
|
|
@@ -5,8 +5,8 @@ import type { BrushConfig } from "../../../lib/brushes/types";
|
|
|
5
5
|
import { type SaveMethod } from "../../../lib/rendering/screenshot";
|
|
6
6
|
import { type InputKind, type LayoutState } from "../../layout";
|
|
7
7
|
import "../PcPanel";
|
|
8
|
-
import "../PcToolbar";
|
|
9
8
|
import "../PcToolSettings";
|
|
9
|
+
import "../PcClipboardTools";
|
|
10
10
|
import "../PcCanvas";
|
|
11
11
|
import "../PcCanvasMap";
|
|
12
12
|
import "../PcPanelBrushes";
|
|
@@ -20,8 +20,10 @@ import "../PcDialog";
|
|
|
20
20
|
* window), with slots to add or replace content:
|
|
21
21
|
*
|
|
22
22
|
* - `header`: across the top (empty by default)
|
|
23
|
-
* - `left`: `<pc-toolbar>` (a pane) by default
|
|
24
|
-
*
|
|
23
|
+
* - `left`: `<pc-toolbar>` (a pane) by default; toolbars in the sides are minimised (their tools one button that opens a
|
|
24
|
+
* menu of them) while the layout is narrow
|
|
25
|
+
* - `top`: a horizontal `<pc-pane>` with `<pc-tool-settings>` (for tools that have settings) and, at its end,
|
|
26
|
+
* `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow) by default
|
|
25
27
|
* - `right`: a `<pc-pane>` with `<pc-panel-layers>` by default
|
|
26
28
|
* - `center`: `<pc-canvas>` by default
|
|
27
29
|
* - `overlay`: drawn over the canvas; `<pc-canvas-map>` by default
|
|
@@ -35,8 +37,9 @@ import "../PcDialog";
|
|
|
35
37
|
*
|
|
36
38
|
* Each side has a mode, chosen by the layout's width from `left-modes` and `right-modes` (default `"docked,
|
|
37
39
|
* rail@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
|
|
38
|
-
* column of buttons (one per panel) that open each panel over the canvas;
|
|
39
|
-
* box that collapses to a button
|
|
40
|
+
* column of buttons (one per panel) that open each panel over the canvas; `floating`, the pane over the canvas as a
|
|
41
|
+
* box that collapses to a button; or `overlay`, a rail over the canvas without a background (the canvas fills the
|
|
42
|
+
* width behind it). Give side content in a `<pc-pane>` to have it follow the side's mode.
|
|
40
43
|
*
|
|
41
44
|
* Reports its state for styling as attributes (`narrow`, `input="pen|touch|mouse"`, `ui-hidden`, `left-mode`,
|
|
42
45
|
* `right-mode`) and as `pc-state-change` events (see `LayoutState`). Creates its own canvas unless given one, and
|
|
@@ -46,8 +49,8 @@ import "../PcDialog";
|
|
|
46
49
|
* reloading the page (see `LayerAutosave`). While another window is drawing on it, the layout is blocked by a message
|
|
47
50
|
* that offers to draw here instead. Fires `pc-toast` when the drawing can't be loaded or saved.
|
|
48
51
|
*
|
|
49
|
-
* With a `settings-store` name, the user's app settings (input settings, keyboard shortcuts, colour palette
|
|
50
|
-
*
|
|
52
|
+
* With a `settings-store` name, the user's app settings (input settings, keyboard shortcuts, colour palette, colours,
|
|
53
|
+
* theme and language) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when they can't be.
|
|
51
54
|
*
|
|
52
55
|
* @example
|
|
53
56
|
* html`<paint-canvas style="height: 600px"></paint-canvas>`
|
|
@@ -163,7 +166,7 @@ export declare class PaintCanvasElement extends PcElement {
|
|
|
163
166
|
private keys?;
|
|
164
167
|
/** The state last reported, to report only changes */
|
|
165
168
|
private reported?;
|
|
166
|
-
/** Space the floating sides cover over the canvas */
|
|
169
|
+
/** Space the floating and overlay sides cover over the canvas */
|
|
167
170
|
private floatingInsets;
|
|
168
171
|
private readonly resizeObserver?;
|
|
169
172
|
private readonly onPointerDown;
|
|
@@ -185,9 +188,9 @@ export declare class PaintCanvasElement extends PcElement {
|
|
|
185
188
|
protected updated(): void;
|
|
186
189
|
/** The panes in a side: the default one, or those given for it */
|
|
187
190
|
private panesIn;
|
|
188
|
-
/** Show each side's panes in the side's mode, facing the canvas */
|
|
191
|
+
/** Show each side's panes in the side's mode, facing the canvas, with toolbars minimised while the layout is narrow */
|
|
189
192
|
private setPaneModes;
|
|
190
|
-
/** Keep the canvas clear of floating sides when fitting it */
|
|
193
|
+
/** Keep the canvas clear of floating and overlay sides when fitting it */
|
|
191
194
|
private measureFloatingSides;
|
|
192
195
|
/**
|
|
193
196
|
* Keep the canvas's layers under the `store` name, replacing an autosave the layout started earlier (also on the
|
|
@@ -238,9 +241,11 @@ export declare class PaintCanvasElement extends PcElement {
|
|
|
238
241
|
/** A side: its panes, in its mode (set on them after rendering) */
|
|
239
242
|
private renderSide;
|
|
240
243
|
/**
|
|
241
|
-
* Close the colour palette on a press outside it and the
|
|
244
|
+
* Close the open panel (brush, eraser, layers, app menu or colour palette) on a press outside it and the button that
|
|
245
|
+
* opens it, marking the press (see `markDismissingPress`) so the canvas doesn't draw unless it's dragged. Presses in
|
|
246
|
+
* dialogs (e.g. one the panel opened) don't count as outside.
|
|
242
247
|
*/
|
|
243
|
-
private
|
|
248
|
+
private closePanelOutside;
|
|
244
249
|
/** The menus the toolbar opens, floating over the canvas beside it */
|
|
245
250
|
private renderPanels;
|
|
246
251
|
/** The brush configuration window, while open */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import style_default from "./style.js";
|
|
2
2
|
import { PcElement, defineElement, emit } from "../../base/index.js";
|
|
3
|
+
import { markDismissingPress } from "../../dismiss/index.js";
|
|
3
4
|
import ChangeController from "../../ChangeController/index.js";
|
|
4
5
|
import PaintCanvas, { Panel } from "../../../lib/classes/PaintCanvas/index.js";
|
|
5
6
|
import KeyboardShortcuts, { SHORTCUT_EVENT } from "../../../lib/classes/KeyboardShortcuts/index.js";
|
|
@@ -13,7 +14,8 @@ import { DEFAULT_SIDE_MODES, STATE_EVENT, parseSideModes, sideModeFor } from "..
|
|
|
13
14
|
import { hasToolSettings } from "../PcToolSettings/index.js";
|
|
14
15
|
import "../PcPanel/index.js";
|
|
15
16
|
import { PANE_COLLAPSE_EVENT, PcPane } from "../PcPane/index.js";
|
|
16
|
-
import "../PcToolbar/index.js";
|
|
17
|
+
import { PcToolbar } from "../PcToolbar/index.js";
|
|
18
|
+
import "../PcClipboardTools/index.js";
|
|
17
19
|
import "../PcCanvas/index.js";
|
|
18
20
|
import "../PcCanvasMap/index.js";
|
|
19
21
|
import "../PcPanelBrushes/index.js";
|
|
@@ -40,8 +42,10 @@ var TAKE_OVER_CHOICE = "take-over";
|
|
|
40
42
|
* window), with slots to add or replace content:
|
|
41
43
|
*
|
|
42
44
|
* - `header`: across the top (empty by default)
|
|
43
|
-
* - `left`: `<pc-toolbar>` (a pane) by default
|
|
44
|
-
*
|
|
45
|
+
* - `left`: `<pc-toolbar>` (a pane) by default; toolbars in the sides are minimised (their tools one button that opens a
|
|
46
|
+
* menu of them) while the layout is narrow
|
|
47
|
+
* - `top`: a horizontal `<pc-pane>` with `<pc-tool-settings>` (for tools that have settings) and, at its end,
|
|
48
|
+
* `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow) by default
|
|
45
49
|
* - `right`: a `<pc-pane>` with `<pc-panel-layers>` by default
|
|
46
50
|
* - `center`: `<pc-canvas>` by default
|
|
47
51
|
* - `overlay`: drawn over the canvas; `<pc-canvas-map>` by default
|
|
@@ -55,8 +59,9 @@ var TAKE_OVER_CHOICE = "take-over";
|
|
|
55
59
|
*
|
|
56
60
|
* Each side has a mode, chosen by the layout's width from `left-modes` and `right-modes` (default `"docked,
|
|
57
61
|
* rail@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
|
|
58
|
-
* column of buttons (one per panel) that open each panel over the canvas;
|
|
59
|
-
* box that collapses to a button
|
|
62
|
+
* column of buttons (one per panel) that open each panel over the canvas; `floating`, the pane over the canvas as a
|
|
63
|
+
* box that collapses to a button; or `overlay`, a rail over the canvas without a background (the canvas fills the
|
|
64
|
+
* width behind it). Give side content in a `<pc-pane>` to have it follow the side's mode.
|
|
60
65
|
*
|
|
61
66
|
* Reports its state for styling as attributes (`narrow`, `input="pen|touch|mouse"`, `ui-hidden`, `left-mode`,
|
|
62
67
|
* `right-mode`) and as `pc-state-change` events (see `LayoutState`). Creates its own canvas unless given one, and
|
|
@@ -66,8 +71,8 @@ var TAKE_OVER_CHOICE = "take-over";
|
|
|
66
71
|
* reloading the page (see `LayerAutosave`). While another window is drawing on it, the layout is blocked by a message
|
|
67
72
|
* that offers to draw here instead. Fires `pc-toast` when the drawing can't be loaded or saved.
|
|
68
73
|
*
|
|
69
|
-
* With a `settings-store` name, the user's app settings (input settings, keyboard shortcuts, colour palette
|
|
70
|
-
*
|
|
74
|
+
* With a `settings-store` name, the user's app settings (input settings, keyboard shortcuts, colour palette, colours,
|
|
75
|
+
* theme and language) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when they can't be.
|
|
71
76
|
*
|
|
72
77
|
* @example
|
|
73
78
|
* html`<paint-canvas style="height: 600px"></paint-canvas>`
|
|
@@ -137,7 +142,7 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
137
142
|
keys;
|
|
138
143
|
/** The state last reported, to report only changes */
|
|
139
144
|
reported;
|
|
140
|
-
/** Space the floating sides cover over the canvas */
|
|
145
|
+
/** Space the floating and overlay sides cover over the canvas */
|
|
141
146
|
floatingInsets = NO_INSETS;
|
|
142
147
|
resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(([entry]) => {
|
|
143
148
|
this.width = Math.round(entry?.contentRect.width ?? 0);
|
|
@@ -157,7 +162,7 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
157
162
|
onShortcut = (event) => {
|
|
158
163
|
if (event.detail.type === "save") this.saveScreenshot();
|
|
159
164
|
};
|
|
160
|
-
onDocumentPointerDown = (event) => this.
|
|
165
|
+
onDocumentPointerDown = (event) => this.closePanelOutside(event);
|
|
161
166
|
onPaneCollapse = () => this.requestUpdate();
|
|
162
167
|
constructor() {
|
|
163
168
|
super();
|
|
@@ -233,16 +238,18 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
233
238
|
const given = slot.assignedElements();
|
|
234
239
|
return (given.length ? given : [...slot.children]).filter((element) => element instanceof PcPane);
|
|
235
240
|
}
|
|
236
|
-
/** Show each side's panes in the side's mode, facing the canvas */
|
|
241
|
+
/** Show each side's panes in the side's mode, facing the canvas, with toolbars minimised while the layout is narrow */
|
|
237
242
|
setPaneModes() {
|
|
243
|
+
const narrow = this.width < this.narrowWidth;
|
|
238
244
|
for (const side of SIDES) for (const pane of this.panesIn(side)) {
|
|
239
245
|
pane.mode = this.modeOf(side);
|
|
240
246
|
pane.edge = side;
|
|
247
|
+
if (pane instanceof PcToolbar) pane.minimised = narrow;
|
|
241
248
|
}
|
|
242
249
|
}
|
|
243
|
-
/** Keep the canvas clear of floating sides when fitting it */
|
|
250
|
+
/** Keep the canvas clear of floating and overlay sides when fitting it */
|
|
244
251
|
measureFloatingSides() {
|
|
245
|
-
const width = (side) => this.panesIn(side).filter((pane) => pane.mode === "floating"
|
|
252
|
+
const width = (side) => this.panesIn(side).filter((pane) => pane.mode === "floating" ? !pane.collapsed : pane.mode === "overlay").reduce((widest, pane) => Math.max(widest, pane.getBoundingClientRect().width), 0);
|
|
246
253
|
const insets = {
|
|
247
254
|
...NO_INSETS,
|
|
248
255
|
left: width("left"),
|
|
@@ -419,12 +426,16 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
419
426
|
</div>`;
|
|
420
427
|
}
|
|
421
428
|
/**
|
|
422
|
-
* Close the colour palette on a press outside it and the
|
|
429
|
+
* Close the open panel (brush, eraser, layers, app menu or colour palette) on a press outside it and the button that
|
|
430
|
+
* opens it, marking the press (see `markDismissingPress`) so the canvas doesn't draw unless it's dragged. Presses in
|
|
431
|
+
* dialogs (e.g. one the panel opened) don't count as outside.
|
|
423
432
|
*/
|
|
424
|
-
|
|
425
|
-
const
|
|
426
|
-
if (
|
|
427
|
-
if (
|
|
433
|
+
closePanelOutside(event) {
|
|
434
|
+
const panel = this.app?.panel;
|
|
435
|
+
if (!panel) return;
|
|
436
|
+
if (event.composedPath().some((target) => target instanceof Element && (target.classList.contains("panel") && target.getRootNode() === this.renderRoot || target.matches("dialog, pc-dialog") || target.getAttribute("data-panel") === panel || panel === Panel.Palette && target.localName === "pc-colour-pair"))) return;
|
|
437
|
+
markDismissingPress(event);
|
|
438
|
+
this.app.closePanel();
|
|
428
439
|
}
|
|
429
440
|
/** The menus the toolbar opens, floating over the canvas beside it */
|
|
430
441
|
renderPanels(app) {
|
|
@@ -502,11 +513,17 @@ var PaintCanvasElement = class extends PcElement {
|
|
|
502
513
|
.app=${app}
|
|
503
514
|
@pc-configure-brush=${(event) => this.configure(event.detail)}
|
|
504
515
|
></pc-tool-settings
|
|
516
|
+
><pc-clipboard-tools
|
|
517
|
+
slot="end"
|
|
518
|
+
.app=${app}
|
|
519
|
+
?narrow=${this.width < this.narrowWidth}
|
|
520
|
+
></pc-clipboard-tools
|
|
505
521
|
></pc-pane>` : nothing)}
|
|
506
522
|
</div>
|
|
507
523
|
<div
|
|
508
524
|
class="center"
|
|
509
525
|
part="center"
|
|
526
|
+
style="--pc-left-inset: ${this.floatingInsets.left}px"
|
|
510
527
|
role="region"
|
|
511
528
|
aria-label=${this.t("paintCanvas.layout.canvas")}
|
|
512
529
|
?inert=${blocked}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/ui/elements/PaintCanvas/style.css?inline
|
|
2
|
-
var style_default = "/* The layout: header across the top, footer across the bottom, sides left and right, and between them the tool\n settings, the canvas and the bottom bar. Empty areas take no space. */\n:host {\n position: relative;\n display: grid;\n grid-template:\n \"header header header\" auto\n \"left top right\" auto\n \"left center right\" 1fr\n \"left bottom right\" auto\n \"footer footer footer\" auto / auto 1fr auto;\n overflow: hidden;\n background: var(--pc-bar);\n font-family: var(--pc-font);\n font-size: var(--pc-font-size);\n}\nheader {\n grid-area: header;\n}\n.top {\n grid-area: top;\n min-width: 0;\n}\n/* The tool settings fill the top pane */\n.top pc-tool-settings {\n flex: 1;\n}\n.center {\n position: relative;\n grid-area: center;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n.bottom {\n grid-area: bottom;\n min-width: 0;\n}\nfooter {\n grid-area: footer;\n}\n.side {\n position: relative;\n z-index: 3;\n display: flex;\n min-height: 0;\n}\n/* A side's panes fill it */\n.side pc-pane,\n.side pc-toolbar,\n.side ::slotted(*) {\n flex: 1;\n}\n.side.left {\n grid-area: left;\n}\n.side.right {\n grid-area: right;\n}\n\n/* Hiding the interface (Tab key) leaves only the canvas */\n:host([ui-hidden]) header,\n:host([ui-hidden]) .top,\n:host([ui-hidden]) .bottom,\n:host([ui-hidden]) footer,\n:host([ui-hidden]) .side {\n display: none;\n}\n\n/* The canvas fills the middle; things drawn over it go in the overlay */\n.center ::slotted([slot=\"center\"]),\n.center pc-canvas {\n position: absolute;\n inset: 0;\n}\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.overlay > * {\n pointer-events: auto;\n}\npc-canvas-map {\n position: absolute;\n right: 20px;\n bottom: 20px;\n}\n\n/* Menus opened from the toolbar float over the canvas, beside the toolbar */\n.panel {\n position: absolute;\n z-index: 4;\n left: 0;\n bottom: 0;\n max-height: 100%;\n overflow: auto;\n}\n.panel.top-panel {\n top: 0;\n bottom: auto;\n}\n/* The app menu: the implementer's content in a panel */\n.menu-panel:not([open]) {\n display: none;\n}\n\n/* Floating: the side's panes float over the canvas (see pc-pane), so the side takes no space */\n.side[data-mode=\"floating\"] {\n /* Beside the canvas only, so it floats over the canvas and not the tool settings */\n grid-row: 3;\n width: 0;\n}\n/* While another window is drawing, its message covers only the layout (not the whole page) */\npc-dialog.elsewhere {\n position: absolute;\n}\n";
|
|
2
|
+
var style_default = "/* The layout: header across the top, footer across the bottom, sides left and right, and between them the tool\n settings, the canvas and the bottom bar. Empty areas take no space. */\n:host {\n position: relative;\n display: grid;\n grid-template:\n \"header header header\" auto\n \"left top right\" auto\n \"left center right\" 1fr\n \"left bottom right\" auto\n \"footer footer footer\" auto / auto 1fr auto;\n overflow: hidden;\n background: var(--pc-bar);\n font-family: var(--pc-font);\n font-size: var(--pc-font-size);\n}\nheader {\n grid-area: header;\n}\n.top {\n grid-area: top;\n min-width: 0;\n}\n/* The tool settings fill the top pane */\n.top pc-tool-settings {\n flex: 1;\n}\n.center {\n position: relative;\n grid-area: center;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n.bottom {\n grid-area: bottom;\n min-width: 0;\n}\nfooter {\n grid-area: footer;\n}\n.side {\n position: relative;\n z-index: 3;\n display: flex;\n min-height: 0;\n}\n/* A side's panes fill it */\n.side pc-pane,\n.side pc-toolbar,\n.side ::slotted(*) {\n flex: 1;\n}\n.side.left {\n grid-area: left;\n}\n.side.right {\n grid-area: right;\n}\n\n/* Hiding the interface (Tab key) leaves only the canvas */\n:host([ui-hidden]) header,\n:host([ui-hidden]) .top,\n:host([ui-hidden]) .bottom,\n:host([ui-hidden]) footer,\n:host([ui-hidden]) .side {\n display: none;\n}\n\n/* The canvas fills the middle; things drawn over it go in the overlay */\n.center ::slotted([slot=\"center\"]),\n.center pc-canvas {\n position: absolute;\n inset: 0;\n}\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.overlay > * {\n pointer-events: auto;\n}\npc-canvas-map {\n position: absolute;\n right: 20px;\n bottom: 20px;\n}\n\n/* Menus opened from the toolbar float over the canvas, beside the toolbar */\n.panel {\n position: absolute;\n z-index: 4;\n left: 0;\n bottom: 0;\n max-height: 100%;\n overflow: auto;\n}\n.panel.top-panel {\n top: 0;\n bottom: auto;\n}\n/* The app menu: the implementer's content in a panel */\n.menu-panel:not([open]) {\n display: none;\n}\n\n/* Floating: the side's panes float over the canvas (see pc-pane), so the side takes no space */\n.side[data-mode=\"floating\"] {\n /* Beside the canvas only, so it floats over the canvas and not the tool settings */\n grid-row: 3;\n width: 0;\n}\n/* Overlay: the side's rail sits over the canvas (beside it only, not the tool settings), so the canvas fills the\n width behind it; presses between its panes' buttons (which take presses themselves) reach the canvas */\n.side[data-mode=\"overlay\"] {\n grid-row: 3;\n grid-column: 1 / -1;\n pointer-events: none;\n}\n.side.left[data-mode=\"overlay\"] {\n justify-self: start;\n}\n.side.right[data-mode=\"overlay\"] {\n justify-self: end;\n}\n/* The toolbar's panels open beside a left overlay rail, not under it */\n:host([left-mode=\"overlay\"]) .panel {\n left: var(--pc-left-inset);\n}\n/* While another window is drawing, its message covers only the layout (not the whole page) */\npc-dialog.elsewhere {\n position: absolute;\n}\n";
|
|
3
3
|
export { style_default as default };
|
|
@@ -6,7 +6,7 @@ import { type SaveMethod } from "../../../lib/rendering/screenshot";
|
|
|
6
6
|
/**
|
|
7
7
|
* Buttons for the user's app settings: "Keyboard shortcuts", which opens a window to change them (see
|
|
8
8
|
* `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
|
|
9
|
-
* settings, keyboard shortcuts, colour palette and
|
|
9
|
+
* settings, keyboard shortcuts, colour palette, colours, theme and language). The window is a modal `<dialog>` above the whole page; it
|
|
10
10
|
* closes with its Close button, Escape, or a press on the backdrop, and the browser gives focus back to the button. Fires `pc-toast` after importing, or when importing or
|
|
11
11
|
* exporting fails.
|
|
12
12
|
*
|
|
@@ -14,7 +14,7 @@ var SETTINGS_FILE_ERROR_KEYS = {
|
|
|
14
14
|
/**
|
|
15
15
|
* Buttons for the user's app settings: "Keyboard shortcuts", which opens a window to change them (see
|
|
16
16
|
* `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
|
|
17
|
-
* settings, keyboard shortcuts, colour palette and
|
|
17
|
+
* settings, keyboard shortcuts, colour palette, colours, theme and language). The window is a modal `<dialog>` above the whole page; it
|
|
18
18
|
* closes with its Close button, Escape, or a press on the backdrop, and the browser gives focus back to the button. Fires `pc-toast` after importing, or when importing or
|
|
19
19
|
* exporting fails.
|
|
20
20
|
*
|
|
@@ -23,7 +23,8 @@ export declare const SMOOTHING_CHOICES: readonly [{
|
|
|
23
23
|
readonly icon: "smoothingBezierCubic";
|
|
24
24
|
}];
|
|
25
25
|
/**
|
|
26
|
-
* The brush (or eraser) configuration window: a testing area, the shape (
|
|
26
|
+
* The brush (or eraser) configuration window: a testing area, the shape (pressing it picks a built-in shape from the
|
|
27
|
+
* device's own list, which says "Custom" for an uploaded or pasted one; or upload or paste one), smoothing, width, height,
|
|
27
28
|
* opacity, rotation, offsets, sample rate, spacing and snapping to the pixel grid. Width, height, opacity, rotation and the offsets can vary with
|
|
28
29
|
* pressure, tilt and randomness; when any is on, the setting has a min and max.
|
|
29
30
|
*
|
|
@@ -82,7 +83,9 @@ export declare class PcBrushConfig extends PcElement {
|
|
|
82
83
|
prompt: PromptFn;
|
|
83
84
|
/** Turns files into brush shapes (replaceable for tests) */
|
|
84
85
|
readShape: (file: File) => Promise<ShapeImage>;
|
|
85
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Reads a brush shape from the clipboard, or else from image data copied on the canvas (replaceable for tests)
|
|
88
|
+
*/
|
|
86
89
|
readClipboard: () => Promise<ShapeImage>;
|
|
87
90
|
private readonly onKeyDown;
|
|
88
91
|
constructor();
|
|
@@ -124,6 +127,10 @@ export declare class PcBrushConfig extends PcElement {
|
|
|
124
127
|
private slider;
|
|
125
128
|
/** Render a setting that can vary: its value (or min and max), and its pressure, tilt and randomise icon toggles */
|
|
126
129
|
private renderParam;
|
|
130
|
+
/**
|
|
131
|
+
* Use a built-in shape (see `BUILT_IN_SHAPES`), keeping the width; they're all square, so the height is the same.
|
|
132
|
+
*/
|
|
133
|
+
private useBuiltInShape;
|
|
127
134
|
/** Render the shape and smoothing row */
|
|
128
135
|
private renderShape;
|
|
129
136
|
render(): import("lit").TemplateResult<1> | typeof nothing;
|
|
@@ -6,8 +6,10 @@ import "../PcBrushTest/index.js";
|
|
|
6
6
|
import { SmoothingType } from "../../../lib/brushes/types/index.js";
|
|
7
7
|
import { PARAM_LIMITS, VARIATION_SOURCES, readRange, toggleSource, writeRange } from "../../../lib/brushes/configRanges/index.js";
|
|
8
8
|
import { SHAPE_ERRORS, ShapeImageError, readClipboardShape, readShapeImage } from "../../../lib/brushes/shapeImage/index.js";
|
|
9
|
+
import { BUILT_IN_SHAPES, builtInShapeId } from "../../../lib/brushes/shapes/index.js";
|
|
9
10
|
import "../../../lib/brushes/StrokeSampler/index.js";
|
|
10
11
|
import { html, nothing, unsafeCSS } from "lit";
|
|
12
|
+
import { live } from "lit/directives/live.js";
|
|
11
13
|
import { styleMap } from "lit/directives/style-map.js";
|
|
12
14
|
// src/ui/elements/PcBrushConfig/index.ts
|
|
13
15
|
/** The smoothing choices, in the order shown: value, translation key of the name, and icon name */
|
|
@@ -40,6 +42,8 @@ var SOURCE_LABELS = {
|
|
|
40
42
|
tilt: "paintCanvas.toolSettings.tilt",
|
|
41
43
|
randomise: "paintCanvas.brushConfig.randomise"
|
|
42
44
|
};
|
|
45
|
+
/** The shape picker's value for a shape the user uploaded or pasted */
|
|
46
|
+
var CUSTOM_SHAPE = "custom";
|
|
43
47
|
/** Translation keys of the brush shape error messages */
|
|
44
48
|
var SHAPE_ERROR_KEYS = {
|
|
45
49
|
[SHAPE_ERRORS.type]: "paintCanvas.shape.notPng",
|
|
@@ -55,7 +59,8 @@ var numberValue = (event) => {
|
|
|
55
59
|
return Number.isFinite(value) ? value : void 0;
|
|
56
60
|
};
|
|
57
61
|
/**
|
|
58
|
-
* The brush (or eraser) configuration window: a testing area, the shape (
|
|
62
|
+
* The brush (or eraser) configuration window: a testing area, the shape (pressing it picks a built-in shape from the
|
|
63
|
+
* device's own list, which says "Custom" for an uploaded or pasted one; or upload or paste one), smoothing, width, height,
|
|
59
64
|
* opacity, rotation, offsets, sample rate, spacing and snapping to the pixel grid. Width, height, opacity, rotation and the offsets can vary with
|
|
60
65
|
* pressure, tilt and randomness; when any is on, the setting has a min and max.
|
|
61
66
|
*
|
|
@@ -88,8 +93,10 @@ var PcBrushConfig = class extends PcElement {
|
|
|
88
93
|
prompt = (message, value) => window.prompt(message, value);
|
|
89
94
|
/** Turns files into brush shapes (replaceable for tests) */
|
|
90
95
|
readShape = readShapeImage;
|
|
91
|
-
/**
|
|
92
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Reads a brush shape from the clipboard, or else from image data copied on the canvas (replaceable for tests)
|
|
98
|
+
*/
|
|
99
|
+
readClipboard = () => readClipboardShape(navigator.clipboard, {}, async () => this.app?.copiedPng());
|
|
93
100
|
onKeyDown = (event) => {
|
|
94
101
|
if (event.key === "Escape") this.close();
|
|
95
102
|
};
|
|
@@ -295,15 +302,48 @@ var PcBrushConfig = class extends PcElement {
|
|
|
295
302
|
</label>`)}
|
|
296
303
|
</div>`;
|
|
297
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Use a built-in shape (see `BUILT_IN_SHAPES`), keeping the width; they're all square, so the height is the same.
|
|
307
|
+
*/
|
|
308
|
+
useBuiltInShape(id) {
|
|
309
|
+
const shape = BUILT_IN_SHAPES.find((built) => built.id === id);
|
|
310
|
+
if (!shape) return;
|
|
311
|
+
const width = this.draft?.width ?? 1;
|
|
312
|
+
this.change({
|
|
313
|
+
image: shape.image,
|
|
314
|
+
height: width
|
|
315
|
+
});
|
|
316
|
+
}
|
|
298
317
|
/** Render the shape and smoothing row */
|
|
299
318
|
renderShape(draft) {
|
|
319
|
+
const shapeId = builtInShapeId(draft.image);
|
|
300
320
|
const smoothing = draft.smoothingType === SmoothingType.None || draft.smoothingType === void 0 ? SmoothingType.Linear : draft.smoothingType;
|
|
301
321
|
return html`<div class="row shape-row">
|
|
302
322
|
<span class="label">${this.t("paintCanvas.brushConfig.shape")}</span>
|
|
303
|
-
<span
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
323
|
+
<span class="shape-picker">
|
|
324
|
+
<span
|
|
325
|
+
class=${draft.image ? "shape-preview image" : "shape-preview"}
|
|
326
|
+
style=${styleMap({ maskImage: typeof draft.image === "string" ? `url("${draft.image}")` : void 0 })}
|
|
327
|
+
></span>
|
|
328
|
+
<!-- Over the shape, so pressing it opens the device's own list -->
|
|
329
|
+
<select
|
|
330
|
+
class="shape-select"
|
|
331
|
+
aria-label=${this.t("paintCanvas.brushConfig.shape")}
|
|
332
|
+
title=${this.t("paintCanvas.brushConfig.chooseShape")}
|
|
333
|
+
?disabled=${this.busy}
|
|
334
|
+
@change=${(e) => this.useBuiltInShape(e.target.value)}
|
|
335
|
+
>
|
|
336
|
+
${shapeId === void 0 ? html`<option value=${CUSTOM_SHAPE} .selected=${true}>
|
|
337
|
+
${this.t("paintCanvas.brushConfig.customShape")}
|
|
338
|
+
</option>` : nothing}
|
|
339
|
+
${BUILT_IN_SHAPES.map((shape) => html`<option
|
|
340
|
+
value=${shape.id}
|
|
341
|
+
.selected=${live(shape.id === shapeId)}
|
|
342
|
+
>
|
|
343
|
+
${shape.name}
|
|
344
|
+
</option>`)}
|
|
345
|
+
</select>
|
|
346
|
+
</span>
|
|
307
347
|
<button
|
|
308
348
|
type="button"
|
|
309
349
|
class="pill upload"
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// src/ui/elements/PcBrushConfig/style.css?inline
|
|
2
|
-
var style_default = ":host {\n position: fixed;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--pc-backdrop);\n}\n[role=\"dialog\"] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n width: min(560px, 95vw);\n max-height: 95vh;\n overflow: auto;\n padding: 8px;\n background: var(--pc-surface);\n}\n.row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n}\n.row > .label {\n width: 6em;\n}\ninput[type=\"number\"] {\n width: 4.5em;\n}\ninput[type=\"range\"] {\n flex: 1;\n min-width: 60px;\n}\n.shape-preview {\n display: inline-block;\n width: 40px;\n height: 40px;\n background: var(--pc-text);\n border-radius: 50%;\n}\n.shape-preview.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: contain;\n mask-position: center;\n}\n/* The name, with \"Close\" on the right */\n.header .close {\n margin-left: auto;\n}\n/* The shape (preview, Upload and Paste) and the smoothing icons on one row */\n.shape-row {\n flex-wrap: nowrap;\n}\n
|
|
2
|
+
var style_default = ":host {\n position: fixed;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--pc-backdrop);\n}\n[role=\"dialog\"] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n width: min(560px, 95vw);\n max-height: 95vh;\n overflow: auto;\n padding: 8px;\n background: var(--pc-surface);\n}\n.row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n}\n.row > .label {\n width: 6em;\n}\ninput[type=\"number\"] {\n width: 4.5em;\n}\ninput[type=\"range\"] {\n flex: 1;\n min-width: 60px;\n}\n.shape-preview {\n display: inline-block;\n width: 40px;\n height: 40px;\n background: var(--pc-text);\n border-radius: 50%;\n}\n.shape-preview.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: contain;\n mask-position: center;\n}\n/* The name, with \"Close\" on the right */\n.header .close {\n margin-left: auto;\n}\n/* The shape (preview, Upload and Paste) and the smoothing icons on one row */\n.shape-row {\n flex-wrap: nowrap;\n}\n/* The shape, with an invisible list over it: pressing the shape opens the device's own list of shapes */\n.shape-picker {\n position: relative;\n display: inline-flex;\n flex: none;\n border-radius: var(--pc-radius-small);\n}\n.shape-picker:hover {\n background: var(--pc-surface-hover);\n}\n.shape-picker:focus-within {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n}\n.shape-select {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\nfieldset.smoothing {\n display: flex;\n align-items: center;\n gap: 4px;\n margin: 0 0 0 auto;\n padding: 0;\n border: 0;\n}\nfieldset.smoothing legend {\n float: left;\n margin-right: 4px;\n padding: 0;\n}\n";
|
|
3
3
|
export { style_default as default };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { nothing } from "lit";
|
|
2
|
+
import { PcElement } from "../../base";
|
|
3
|
+
import "../PcNumberSlider";
|
|
4
|
+
import { type default as BrushSettings } from "../../../lib/classes/BrushSettings";
|
|
5
|
+
/**
|
|
6
|
+
* A brush's size as text: one number when the width and height are the same, otherwise "width × height".
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* sizeText({ width: 20, height: 20 }); // "20"
|
|
10
|
+
* sizeText({ width: 30, height: 10 }); // "30 × 10"
|
|
11
|
+
*/
|
|
12
|
+
export declare const sizeText: ({ width, height, }: {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
}) => string;
|
|
16
|
+
/**
|
|
17
|
+
* A brush or eraser's size, as a `pc-number-slider`: pressed and dragged right or up it gets bigger, left or down
|
|
18
|
+
* smaller, keeping its proportions (see `BrushSettings.resize`). Tapping it lets you type the larger side's size. With
|
|
19
|
+
* keyboard focus, the arrow keys change it by a pixel and Page Up and Page Down by a step (like `]` and `[`).
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* html`<pc-brush-size .settings=${app.brush} label="Brush size"></pc-brush-size>`
|
|
23
|
+
*/
|
|
24
|
+
export declare class PcBrushSize extends PcElement {
|
|
25
|
+
static properties: {
|
|
26
|
+
settings: {
|
|
27
|
+
attribute: boolean;
|
|
28
|
+
};
|
|
29
|
+
label: {
|
|
30
|
+
type: StringConstructor;
|
|
31
|
+
};
|
|
32
|
+
translations: {
|
|
33
|
+
attribute: boolean;
|
|
34
|
+
};
|
|
35
|
+
icons: {
|
|
36
|
+
attribute: boolean;
|
|
37
|
+
};
|
|
38
|
+
theme: {
|
|
39
|
+
type: StringConstructor;
|
|
40
|
+
reflect: boolean;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
static styles: import("lit").CSSResultGroup[];
|
|
44
|
+
/** The brush or eraser settings it shows and changes */
|
|
45
|
+
settings?: BrushSettings;
|
|
46
|
+
/** Its name for screen readers, e.g. "Brush size" */
|
|
47
|
+
label: string;
|
|
48
|
+
private changes;
|
|
49
|
+
constructor();
|
|
50
|
+
/** Resize to the slider's value (the larger side), keeping the proportions */
|
|
51
|
+
private onChange;
|
|
52
|
+
render(): import("lit").TemplateResult<1> | typeof nothing;
|
|
53
|
+
}
|