@lvl99dotcom/paintcanvas 0.0.0-stage → 0.2.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/LICENSE +201 -0
- package/README.md +131 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/lib/brushes/BrushTipCache/index.d.ts +32 -0
- package/dist/lib/brushes/BrushTipCache/index.js +60 -0
- package/dist/lib/brushes/StrokeEngine/index.d.ts +171 -0
- package/dist/lib/brushes/StrokeEngine/index.js +324 -0
- package/dist/lib/brushes/StrokePath/index.d.ts +53 -0
- package/dist/lib/brushes/StrokePath/index.js +161 -0
- package/dist/lib/brushes/StrokeSampler/index.d.ts +55 -0
- package/dist/lib/brushes/StrokeSampler/index.js +75 -0
- package/dist/lib/brushes/authors/index.d.ts +55 -0
- package/dist/lib/brushes/authors/index.js +60 -0
- package/dist/lib/brushes/configRanges/index.d.ts +52 -0
- package/dist/lib/brushes/configRanges/index.js +206 -0
- package/dist/lib/brushes/index.d.ts +5 -0
- package/dist/lib/brushes/index.js +19 -0
- package/dist/lib/brushes/params/index.d.ts +108 -0
- package/dist/lib/brushes/params/index.js +139 -0
- package/dist/lib/brushes/presetFile/index.d.ts +32 -0
- package/dist/lib/brushes/presetFile/index.js +55 -0
- package/dist/lib/brushes/presets/drawing/brush-pen.js +3 -0
- package/dist/lib/brushes/presets/drawing/felt-pen.js +3 -0
- package/dist/lib/brushes/presets/drawing/index.d.ts +13 -0
- package/dist/lib/brushes/presets/drawing/index.js +88 -0
- package/dist/lib/brushes/presets/drawing/pencil-2b.js +3 -0
- package/dist/lib/brushes/presets/drawing/pencil-hb.js +3 -0
- package/dist/lib/brushes/presets/painting/grass.js +3 -0
- package/dist/lib/brushes/presets/painting/hair.js +3 -0
- package/dist/lib/brushes/presets/painting/index.d.ts +18 -0
- package/dist/lib/brushes/presets/painting/index.js +118 -0
- package/dist/lib/brushes/presets/painting/splatter.js +3 -0
- package/dist/lib/brushes/presets/painting/textured-paint.js +3 -0
- package/dist/lib/brushes/presets/painting/watery-paint.js +3 -0
- package/dist/lib/brushes/presets/round/index.d.ts +9 -0
- package/dist/lib/brushes/presets/round/index.js +44 -0
- package/dist/lib/brushes/presets/round/round.js +3 -0
- package/dist/lib/brushes/presets/round/soft-round.js +3 -0
- package/dist/lib/brushes/presets/sized/index.d.ts +20 -0
- package/dist/lib/brushes/presets/sized/index.js +30 -0
- package/dist/lib/brushes/presets/sketching/index.d.ts +14 -0
- package/dist/lib/brushes/presets/sketching/index.js +107 -0
- package/dist/lib/brushes/presets/square/index.d.ts +7 -0
- package/dist/lib/brushes/presets/square/index.js +22 -0
- package/dist/lib/brushes/presets/square/square.js +3 -0
- package/dist/lib/brushes/shapeImage/index.d.ts +40 -0
- package/dist/lib/brushes/shapeImage/index.js +61 -0
- package/dist/lib/brushes/types/index.d.ts +243 -0
- package/dist/lib/brushes/types/index.js +20 -0
- package/dist/lib/classes/BackgroundWork/index.d.ts +55 -0
- package/dist/lib/classes/BackgroundWork/index.js +97 -0
- package/dist/lib/classes/BrushSettings/index.d.ts +91 -0
- package/dist/lib/classes/BrushSettings/index.js +152 -0
- package/dist/lib/classes/CustomPresets/index.d.ts +53 -0
- package/dist/lib/classes/CustomPresets/index.js +71 -0
- package/dist/lib/classes/ExternalSheets/index.d.ts +64 -0
- package/dist/lib/classes/ExternalSheets/index.js +112 -0
- package/dist/lib/classes/GestureTracker/index.d.ts +62 -0
- package/dist/lib/classes/GestureTracker/index.js +122 -0
- package/dist/lib/classes/History/index.d.ts +83 -0
- package/dist/lib/classes/History/index.js +126 -0
- package/dist/lib/classes/HistoryGuard/index.d.ts +58 -0
- package/dist/lib/classes/HistoryGuard/index.js +97 -0
- package/dist/lib/classes/InputSettings/index.d.ts +74 -0
- package/dist/lib/classes/InputSettings/index.js +101 -0
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +48 -0
- package/dist/lib/classes/KeyboardShortcuts/index.js +137 -0
- package/dist/lib/classes/Layer/index.d.ts +86 -0
- package/dist/lib/classes/Layer/index.js +111 -0
- package/dist/lib/classes/LayerAutosave/index.d.ts +180 -0
- package/dist/lib/classes/LayerAutosave/index.js +346 -0
- package/dist/lib/classes/LayerStack/index.d.ts +177 -0
- package/dist/lib/classes/LayerStack/index.js +394 -0
- package/dist/lib/classes/PaintCanvas/index.d.ts +537 -0
- package/dist/lib/classes/PaintCanvas/index.js +965 -0
- package/dist/lib/classes/StrokeLog/index.d.ts +149 -0
- package/dist/lib/classes/StrokeLog/index.js +257 -0
- package/dist/lib/classes/Viewport/index.d.ts +108 -0
- package/dist/lib/classes/Viewport/index.js +194 -0
- package/dist/lib/document/index.d.ts +17 -0
- package/dist/lib/document/index.js +25 -0
- package/dist/lib/document/schema.d.ts +132 -0
- package/dist/lib/document/schema.js +110 -0
- package/dist/lib/i18n/index.d.ts +94 -0
- package/dist/lib/i18n/index.js +182 -0
- package/dist/lib/image/index.d.ts +90 -0
- package/dist/lib/image/index.js +147 -0
- package/dist/lib/messages/PatchSender/index.d.ts +75 -0
- package/dist/lib/messages/PatchSender/index.js +125 -0
- package/dist/lib/messages/index.d.ts +26 -0
- package/dist/lib/messages/index.js +59 -0
- package/dist/lib/messages/schema.d.ts +1775 -0
- package/dist/lib/messages/schema.js +192 -0
- package/dist/lib/presets/index.d.ts +32 -0
- package/dist/lib/presets/index.js +35 -0
- package/dist/lib/presets/schema.d.ts +753 -0
- package/dist/lib/presets/schema.js +181 -0
- package/dist/lib/rendering/BrushTip/index.d.ts +63 -0
- package/dist/lib/rendering/BrushTip/index.js +112 -0
- package/dist/lib/rendering/checkerboard/index.d.ts +22 -0
- package/dist/lib/rendering/checkerboard/index.js +31 -0
- package/dist/lib/rendering/compositing/index.d.ts +64 -0
- package/dist/lib/rendering/compositing/index.js +124 -0
- package/dist/lib/rendering/cursor/index.d.ts +14 -0
- package/dist/lib/rendering/cursor/index.js +39 -0
- package/dist/lib/rendering/cursors/index.d.ts +18 -0
- package/dist/lib/rendering/cursors/index.js +23 -0
- package/dist/lib/rendering/maskOutline/index.d.ts +22 -0
- package/dist/lib/rendering/maskOutline/index.js +78 -0
- package/dist/lib/rendering/outline/index.d.ts +36 -0
- package/dist/lib/rendering/outline/index.js +120 -0
- package/dist/lib/rendering/sample/index.d.ts +55 -0
- package/dist/lib/rendering/sample/index.js +83 -0
- package/dist/lib/rendering/screenshot/index.d.ts +67 -0
- package/dist/lib/rendering/screenshot/index.js +96 -0
- package/dist/lib/rendering/transformBox/index.d.ts +10 -0
- package/dist/lib/rendering/transformBox/index.js +57 -0
- package/dist/lib/selection/Lasso/index.d.ts +70 -0
- package/dist/lib/selection/Lasso/index.js +156 -0
- package/dist/lib/selection/Selection/index.d.ts +86 -0
- package/dist/lib/selection/Selection/index.js +228 -0
- package/dist/lib/selection/geometry/index.d.ts +37 -0
- package/dist/lib/selection/geometry/index.js +57 -0
- package/dist/lib/storage/index.d.ts +63 -0
- package/dist/lib/storage/index.js +114 -0
- package/dist/lib/transform/Transformer/index.d.ts +124 -0
- package/dist/lib/transform/Transformer/index.js +286 -0
- package/dist/lib/transform/geometry/index.d.ts +95 -0
- package/dist/lib/transform/geometry/index.js +258 -0
- package/dist/lib/utils/array/index.d.ts +10 -0
- package/dist/lib/utils/array/index.js +21 -0
- package/dist/lib/utils/gestures/index.d.ts +35 -0
- package/dist/lib/utils/gestures/index.js +81 -0
- package/dist/lib/utils/navigation/index.d.ts +51 -0
- package/dist/lib/utils/navigation/index.js +59 -0
- package/dist/lib/utils/random/index.d.ts +18 -0
- package/dist/lib/utils/random/index.js +27 -0
- package/dist/lib/utils/shortcuts/index.d.ts +74 -0
- package/dist/lib/utils/shortcuts/index.js +189 -0
- package/dist/lib/validation/validators.generated.d.ts +27 -0
- package/dist/lib/validation/validators.generated.js +19826 -0
- package/dist/locales/en-GB.js +188 -0
- package/dist/locales/en-US.js +188 -0
- package/dist/locales/fr.js +188 -0
- package/dist/locales/ja.js +188 -0
- package/dist/locales/ko.js +188 -0
- package/dist/locales/zh-Hans.js +188 -0
- package/dist/locales/zh-Hant.js +188 -0
- package/dist/package.js +3 -0
- package/dist/testing/app.d.ts +7 -0
- package/dist/testing/app.js +16 -0
- package/dist/testing/elements.d.ts +42 -0
- package/dist/testing/elements.js +58 -0
- package/dist/testing/fakeCanvas.d.ts +21 -0
- package/dist/testing/fakeCanvas.js +26 -0
- package/dist/testing/layerStore.d.ts +26 -0
- package/dist/testing/layerStore.js +43 -0
- package/dist/testing/locks.d.ts +15 -0
- package/dist/testing/locks.js +41 -0
- package/dist/testing/recordingCanvas.d.ts +14 -0
- package/dist/testing/recordingCanvas.js +28 -0
- package/dist/testing/recordingContext.d.ts +10 -0
- package/dist/testing/recordingContext.js +19 -0
- package/dist/ui/ChangeController/index.d.ts +40 -0
- package/dist/ui/ChangeController/index.js +62 -0
- package/dist/ui/base/index.d.ts +69 -0
- package/dist/ui/base/index.js +94 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +249 -0
- package/dist/ui/elements/PaintCanvas/index.js +584 -0
- package/dist/ui/elements/PaintCanvas/style.js +3 -0
- package/dist/ui/elements/PcBrushConfig/index.d.ts +130 -0
- package/dist/ui/elements/PcBrushConfig/index.js +463 -0
- package/dist/ui/elements/PcBrushConfig/style.js +3 -0
- package/dist/ui/elements/PcBrushMenu/index.d.ts +121 -0
- package/dist/ui/elements/PcBrushMenu/index.js +362 -0
- package/dist/ui/elements/PcBrushMenu/style.js +3 -0
- package/dist/ui/elements/PcBrushTest/index.d.ts +71 -0
- package/dist/ui/elements/PcBrushTest/index.js +136 -0
- package/dist/ui/elements/PcBrushTest/style.js +3 -0
- package/dist/ui/elements/PcButton/index.d.ts +57 -0
- package/dist/ui/elements/PcButton/index.js +124 -0
- package/dist/ui/elements/PcButtonGroup/index.d.ts +44 -0
- package/dist/ui/elements/PcButtonGroup/index.js +93 -0
- package/dist/ui/elements/PcCanvas/index.d.ts +356 -0
- package/dist/ui/elements/PcCanvas/index.js +1004 -0
- package/dist/ui/elements/PcCanvas/style.js +3 -0
- package/dist/ui/elements/PcCanvasMap/index.d.ts +96 -0
- package/dist/ui/elements/PcCanvasMap/index.js +187 -0
- package/dist/ui/elements/PcCanvasMap/style.js +3 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +72 -0
- package/dist/ui/elements/PcContextMenu/index.js +148 -0
- package/dist/ui/elements/PcDialog/index.d.ts +63 -0
- package/dist/ui/elements/PcDialog/index.js +114 -0
- package/dist/ui/elements/PcIcon/index.d.ts +36 -0
- package/dist/ui/elements/PcIcon/index.js +43 -0
- package/dist/ui/elements/PcIconToggle/index.d.ts +46 -0
- package/dist/ui/elements/PcIconToggle/index.js +87 -0
- package/dist/ui/elements/PcInfoTip/index.d.ts +40 -0
- package/dist/ui/elements/PcInfoTip/index.js +95 -0
- package/dist/ui/elements/PcInputSettings/index.d.ts +69 -0
- package/dist/ui/elements/PcInputSettings/index.js +150 -0
- package/dist/ui/elements/PcLayerItem/index.d.ts +221 -0
- package/dist/ui/elements/PcLayerItem/index.js +561 -0
- package/dist/ui/elements/PcLayerItem/style.js +3 -0
- package/dist/ui/elements/PcLayersMenu/index.d.ts +108 -0
- package/dist/ui/elements/PcLayersMenu/index.js +208 -0
- package/dist/ui/elements/PcLayersMenu/style.js +3 -0
- package/dist/ui/elements/PcMatch/index.d.ts +31 -0
- package/dist/ui/elements/PcMatch/index.js +58 -0
- package/dist/ui/elements/PcPanel/index.d.ts +43 -0
- package/dist/ui/elements/PcPanel/index.js +51 -0
- package/dist/ui/elements/PcPopover/index.d.ts +48 -0
- package/dist/ui/elements/PcPopover/index.js +86 -0
- package/dist/ui/elements/PcSlider/index.d.ts +57 -0
- package/dist/ui/elements/PcSlider/index.js +88 -0
- package/dist/ui/elements/PcStats/index.d.ts +54 -0
- package/dist/ui/elements/PcStats/index.js +141 -0
- package/dist/ui/elements/PcThemePicker/index.d.ts +56 -0
- package/dist/ui/elements/PcThemePicker/index.js +134 -0
- package/dist/ui/elements/PcToast/index.d.ts +58 -0
- package/dist/ui/elements/PcToast/index.js +110 -0
- package/dist/ui/elements/PcToggleCheckbox/index.d.ts +37 -0
- package/dist/ui/elements/PcToggleCheckbox/index.js +105 -0
- package/dist/ui/elements/PcToolSettings/index.d.ts +128 -0
- package/dist/ui/elements/PcToolSettings/index.js +543 -0
- package/dist/ui/elements/PcToolSettings/style.js +3 -0
- package/dist/ui/elements/PcToolbar/index.d.ts +71 -0
- package/dist/ui/elements/PcToolbar/index.js +190 -0
- package/dist/ui/elements/PcToolbar/style.js +3 -0
- package/dist/ui/icons/images/brush.js +3 -0
- package/dist/ui/icons/images/circle-mask.js +3 -0
- package/dist/ui/icons/images/clipboard.js +3 -0
- package/dist/ui/icons/images/cursor.js +3 -0
- package/dist/ui/icons/images/eraser.js +3 -0
- package/dist/ui/icons/images/eyedropper.js +3 -0
- package/dist/ui/icons/images/flip-horizontal.js +3 -0
- package/dist/ui/icons/images/flip-vertical.js +3 -0
- package/dist/ui/icons/images/focus-pen.js +3 -0
- package/dist/ui/icons/images/freehand-mask.js +3 -0
- package/dist/ui/icons/images/hidden.js +3 -0
- package/dist/ui/icons/images/layers.js +3 -0
- package/dist/ui/icons/images/linked.js +3 -0
- package/dist/ui/icons/images/locked.js +3 -0
- package/dist/ui/icons/images/moon.js +3 -0
- package/dist/ui/icons/images/pan.js +3 -0
- package/dist/ui/icons/images/polygon-mask.js +3 -0
- package/dist/ui/icons/images/pressure.js +3 -0
- package/dist/ui/icons/images/randomise.js +3 -0
- package/dist/ui/icons/images/rectangle-mask.js +3 -0
- package/dist/ui/icons/images/redo.js +3 -0
- package/dist/ui/icons/images/rotate-90-left.js +3 -0
- package/dist/ui/icons/images/rotate-90-right.js +3 -0
- package/dist/ui/icons/images/smoothing-bezier-cubic.js +3 -0
- package/dist/ui/icons/images/smoothing-bezier-quad.js +3 -0
- package/dist/ui/icons/images/smoothing-linear.js +3 -0
- package/dist/ui/icons/images/star.js +3 -0
- package/dist/ui/icons/images/sun.js +3 -0
- package/dist/ui/icons/images/tilt.js +3 -0
- package/dist/ui/icons/images/undo.js +3 -0
- package/dist/ui/icons/images/unlinked.js +3 -0
- package/dist/ui/icons/images/unlocked.js +3 -0
- package/dist/ui/icons/images/visible.js +3 -0
- package/dist/ui/icons/images/zoom-in.js +3 -0
- package/dist/ui/icons/images/zoom-out.js +3 -0
- package/dist/ui/icons/images/zoom.js +3 -0
- package/dist/ui/icons/index.d.ts +42 -0
- package/dist/ui/icons/index.js +130 -0
- package/dist/ui/index.d.ts +21 -0
- package/dist/ui/index.js +22 -0
- package/dist/ui/layout/index.d.ts +88 -0
- package/dist/ui/layout/index.js +96 -0
- package/dist/ui/styles/index.d.ts +13 -0
- package/dist/ui/styles/index.js +195 -0
- package/dist/ui/tokens/index.d.ts +29 -0
- package/dist/ui/tokens/index.js +114 -0
- package/locales/en-GB.json +186 -0
- package/locales/en-US.json +186 -0
- package/locales/fr.json +186 -0
- package/locales/ja.json +186 -0
- package/locales/ko.json +186 -0
- package/locales/zh-Hans.json +186 -0
- package/locales/zh-Hant.json +186 -0
- package/package.json +97 -3
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import Layer, { createOffscreenCanvas } from "../Layer/index.js";
|
|
2
|
+
import History from "../History/index.js";
|
|
3
|
+
import { contentBounds, copyArea } from "../../transform/Transformer/index.js";
|
|
4
|
+
import { moveItem } from "../../utils/array/index.js";
|
|
5
|
+
// src/lib/classes/LayerStack/index.ts
|
|
6
|
+
/** Most layers a stack can have unless another limit is given */
|
|
7
|
+
var DEFAULT_MAX_LAYERS = 5;
|
|
8
|
+
/**
|
|
9
|
+
* Manages one user's ordered list of layers. Index 0 is the top-most layer.
|
|
10
|
+
*
|
|
11
|
+
* Dispatches a `change` event whenever layers or the active layer change.
|
|
12
|
+
*
|
|
13
|
+
* The user's actions are recorded in `history` so they can be undone: creating, duplicating, deleting and moving layers,
|
|
14
|
+
* changing a layer's blend mode or opacity, and changes to image data (only the changed area).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const stack = new LayerStack(2000, 2000);
|
|
18
|
+
* stack.create(); // "Layer 2", placed above and selected
|
|
19
|
+
* stack.addEventListener("change", render);
|
|
20
|
+
*/
|
|
21
|
+
var LayerStack = class extends EventTarget {
|
|
22
|
+
/** Canvas width in pixels */
|
|
23
|
+
width;
|
|
24
|
+
/** Canvas height in pixels */
|
|
25
|
+
height;
|
|
26
|
+
/** The user's undoable actions on these layers */
|
|
27
|
+
history = new History();
|
|
28
|
+
items = [];
|
|
29
|
+
activeId;
|
|
30
|
+
createdCount = 0;
|
|
31
|
+
/** The layer the change being announced is about, when only its pixels changed (see `changedLayerId`) */
|
|
32
|
+
onlyChanged;
|
|
33
|
+
createCanvas;
|
|
34
|
+
/** Most layers the stack can have */
|
|
35
|
+
maxLayers;
|
|
36
|
+
constructor(width, height, createCanvas = createOffscreenCanvas, maxLayers = 5) {
|
|
37
|
+
super();
|
|
38
|
+
this.width = width;
|
|
39
|
+
this.height = height;
|
|
40
|
+
this.createCanvas = createCanvas;
|
|
41
|
+
this.maxLayers = maxLayers;
|
|
42
|
+
const first = this.newLayer(this.nextDefaultName());
|
|
43
|
+
this.items = [first];
|
|
44
|
+
this.activeId = first.id;
|
|
45
|
+
}
|
|
46
|
+
/** Layers from top to bottom */
|
|
47
|
+
get layers() {
|
|
48
|
+
return this.items;
|
|
49
|
+
}
|
|
50
|
+
/** The selected layer that drawing applies to */
|
|
51
|
+
get active() {
|
|
52
|
+
return this.get(this.activeId);
|
|
53
|
+
}
|
|
54
|
+
/** Whether another layer can be created */
|
|
55
|
+
get canCreate() {
|
|
56
|
+
return this.items.length < this.maxLayers;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get a layer by ID. Throws if not found.
|
|
60
|
+
*/
|
|
61
|
+
get(id) {
|
|
62
|
+
const layer = this.items.find((l) => l.id === id);
|
|
63
|
+
if (!layer) throw new Error(`Layer ${id} not found`);
|
|
64
|
+
return layer;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get a layer's index (0 = top). Throws if not found.
|
|
68
|
+
*/
|
|
69
|
+
indexOf(id) {
|
|
70
|
+
return this.items.indexOf(this.get(id));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Select a layer.
|
|
74
|
+
*/
|
|
75
|
+
select(id) {
|
|
76
|
+
this.activeId = this.get(id).id;
|
|
77
|
+
this.notify();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Create an empty layer above the active layer and select it.
|
|
81
|
+
*/
|
|
82
|
+
create() {
|
|
83
|
+
this.assertCanCreate();
|
|
84
|
+
const layer = this.newLayer(this.nextDefaultName());
|
|
85
|
+
this.insertAboveActive(layer);
|
|
86
|
+
return layer;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Duplicate a layer (settings and image data) above the active layer and select it.
|
|
90
|
+
*/
|
|
91
|
+
duplicate(id) {
|
|
92
|
+
this.assertCanCreate();
|
|
93
|
+
const source = this.get(id);
|
|
94
|
+
const { name, blendMode, opacity, visible, alphaLock } = source.toConfig();
|
|
95
|
+
const layer = new Layer({
|
|
96
|
+
name: `${name} copy`,
|
|
97
|
+
blendMode,
|
|
98
|
+
opacity,
|
|
99
|
+
visible,
|
|
100
|
+
alphaLock
|
|
101
|
+
}, this.width, this.height, this.createCanvas);
|
|
102
|
+
layer.copyImageFrom(source);
|
|
103
|
+
const previousId = this.activeId;
|
|
104
|
+
this.activeId = source.id;
|
|
105
|
+
this.insertAboveActive(layer, previousId);
|
|
106
|
+
return layer;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Replace all layers with new, empty ones (e.g. to restore a saved drawing into), top first, and select the top one.
|
|
110
|
+
* Returns the new layers.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* const [top, bottom] = stack.replace([{ name: "Sketch" }, { name: "Background", opacity: 0.5 }]);
|
|
114
|
+
*/
|
|
115
|
+
replace(configs) {
|
|
116
|
+
if (configs.length === 0) throw new Error("Cannot have no layers");
|
|
117
|
+
if (configs.length > this.maxLayers) throw new Error(`Cannot have more than ${this.maxLayers} layers`);
|
|
118
|
+
this.items = configs.map((config) => new Layer(config, this.width, this.height, this.createCanvas));
|
|
119
|
+
this.activeId = this.items[0].id;
|
|
120
|
+
this.createdCount = this.items.length;
|
|
121
|
+
this.history.clear();
|
|
122
|
+
this.notify();
|
|
123
|
+
return this.items;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* While a `change` event is being dispatched: the layer it's about, when only that layer's image data changed (from
|
|
127
|
+
* `markChanged`, e.g. a brush stroke); undefined when anything may have changed.
|
|
128
|
+
*/
|
|
129
|
+
get changedLayerId() {
|
|
130
|
+
return this.onlyChanged;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Tell listeners a layer's image data changed (e.g. after a brush stroke), so thumbnails and the map redraw.
|
|
134
|
+
*/
|
|
135
|
+
markChanged(id) {
|
|
136
|
+
this.get(id);
|
|
137
|
+
this.onlyChanged = id;
|
|
138
|
+
try {
|
|
139
|
+
this.notify();
|
|
140
|
+
} finally {
|
|
141
|
+
this.onlyChanged = void 0;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Remove all image data from a layer, as one undoable action.
|
|
146
|
+
*/
|
|
147
|
+
clear(id) {
|
|
148
|
+
const layer = this.get(id);
|
|
149
|
+
const { data } = layer.context.getImageData(0, 0, this.width, this.height);
|
|
150
|
+
const area = contentBounds(data, this.width, this.height);
|
|
151
|
+
if (area) this.recordImage(id, area);
|
|
152
|
+
layer.clear();
|
|
153
|
+
this.notify();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Record a layer's image data in an area just before it changes, so the change can be undone. Pass `before` when the
|
|
157
|
+
* layer has already changed (a copy of the area's image data from before the change, the size of the area).
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* stack.recordImage(layer.id, { x: 10, y: 10, width: 50, height: 20 });
|
|
161
|
+
* layer.context.fillRect(10, 10, 50, 20);
|
|
162
|
+
* stack.markChanged(layer.id);
|
|
163
|
+
*/
|
|
164
|
+
recordImage(id, area, before) {
|
|
165
|
+
if (!this.history.recording) return;
|
|
166
|
+
const pixels = before ?? copyArea(this.createCanvas, this.get(id).canvas, area);
|
|
167
|
+
this.history.push(this.imageEntry(id, area, pixels));
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Change a layer's image data within an area as one undoable action, then tell listeners.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* stack.changeImage(layer.id, area, (ctx) => ctx.clearRect(area.x, area.y, area.width, area.height));
|
|
174
|
+
*/
|
|
175
|
+
changeImage(id, area, change) {
|
|
176
|
+
this.recordImage(id, area);
|
|
177
|
+
change(this.get(id).context);
|
|
178
|
+
this.notify();
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Whether a layer can be merged into the layer under it: there is one, and both are visible.
|
|
182
|
+
*/
|
|
183
|
+
canMergeDown(id) {
|
|
184
|
+
const below = this.items[this.indexOf(id) + 1];
|
|
185
|
+
return below !== void 0 && this.get(id).visible && below.visible;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Merge a layer into the layer under it as one undoable action, making the two one layer: its pixels are drawn onto
|
|
189
|
+
* the layer under it as they look (with its opacity and blend mode, whatever the alpha lock), and it is deleted. The
|
|
190
|
+
* merged layer keeps the lower layer's name and settings, and is selected. Does nothing unless `canMergeDown`.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* stack.mergeDown(stack.active.id);
|
|
194
|
+
*/
|
|
195
|
+
mergeDown(id) {
|
|
196
|
+
if (!this.canMergeDown(id)) return;
|
|
197
|
+
const layer = this.get(id);
|
|
198
|
+
const index = this.indexOf(id);
|
|
199
|
+
const below = this.items[index + 1];
|
|
200
|
+
const { data } = layer.context.getImageData(0, 0, this.width, this.height);
|
|
201
|
+
const area = contentBounds(data, this.width, this.height);
|
|
202
|
+
const before = area && copyArea(this.createCanvas, below.canvas, area);
|
|
203
|
+
const merge = () => {
|
|
204
|
+
if (area) {
|
|
205
|
+
const ctx = below.context;
|
|
206
|
+
const { x, y, width, height } = area;
|
|
207
|
+
ctx.save();
|
|
208
|
+
ctx.globalAlpha = layer.opacity;
|
|
209
|
+
ctx.globalCompositeOperation = layer.blendMode;
|
|
210
|
+
ctx.drawImage(layer.canvas, x, y, width, height, x, y, width, height);
|
|
211
|
+
ctx.restore();
|
|
212
|
+
}
|
|
213
|
+
this.items = this.items.filter((l) => l !== layer);
|
|
214
|
+
this.activeId = below.id;
|
|
215
|
+
this.notify();
|
|
216
|
+
};
|
|
217
|
+
merge();
|
|
218
|
+
this.history.push({
|
|
219
|
+
undo: () => {
|
|
220
|
+
if (area && before) {
|
|
221
|
+
const ctx = below.context;
|
|
222
|
+
ctx.clearRect(area.x, area.y, area.width, area.height);
|
|
223
|
+
ctx.drawImage(before, area.x, area.y);
|
|
224
|
+
}
|
|
225
|
+
this.insertAt(layer, index);
|
|
226
|
+
},
|
|
227
|
+
redo: merge
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Delete a layer. The last remaining layer cannot be deleted. If the active layer is deleted, the layer below it (or above, if it was the bottom) is selected.
|
|
232
|
+
*/
|
|
233
|
+
remove(id) {
|
|
234
|
+
if (this.items.length === 1) throw new Error("Cannot delete the only layer");
|
|
235
|
+
const layer = this.get(id);
|
|
236
|
+
const index = this.indexOf(id);
|
|
237
|
+
this.history.push({
|
|
238
|
+
undo: () => this.insertAt(layer, index),
|
|
239
|
+
redo: () => this.remove(id)
|
|
240
|
+
});
|
|
241
|
+
this.items = this.items.filter((l) => l.id !== id);
|
|
242
|
+
if (this.activeId === id) this.activeId = this.items[Math.min(index, this.items.length - 1)].id;
|
|
243
|
+
this.notify();
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Move a layer to a new index (0 = top). Layers in between shift to fill the gap.
|
|
247
|
+
*/
|
|
248
|
+
move(id, toIndex) {
|
|
249
|
+
const fromIndex = this.indexOf(id);
|
|
250
|
+
this.items = moveItem(this.items, fromIndex, toIndex);
|
|
251
|
+
const movedTo = this.indexOf(id);
|
|
252
|
+
if (movedTo !== fromIndex) this.history.push({
|
|
253
|
+
undo: () => this.moveAndSelect(id, fromIndex),
|
|
254
|
+
redo: () => this.moveAndSelect(id, movedTo)
|
|
255
|
+
});
|
|
256
|
+
this.notify();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Move a layer up one place.
|
|
260
|
+
*/
|
|
261
|
+
moveUp(id) {
|
|
262
|
+
this.move(id, this.indexOf(id) - 1);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Move a layer down one place.
|
|
266
|
+
*/
|
|
267
|
+
moveDown(id) {
|
|
268
|
+
this.move(id, this.indexOf(id) + 1);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Move a layer to the top.
|
|
272
|
+
*/
|
|
273
|
+
moveToTop(id) {
|
|
274
|
+
this.move(id, 0);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Move a layer to the bottom.
|
|
278
|
+
*/
|
|
279
|
+
moveToBottom(id) {
|
|
280
|
+
this.move(id, this.items.length - 1);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Rename a layer.
|
|
284
|
+
*/
|
|
285
|
+
rename(id, name) {
|
|
286
|
+
this.get(id).name = name;
|
|
287
|
+
this.notify();
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Change a layer's settings (anything except ID and name). Changes to blend mode and opacity can be undone; visibility
|
|
291
|
+
* and alpha lock cannot.
|
|
292
|
+
*/
|
|
293
|
+
update(id, changes) {
|
|
294
|
+
const layer = this.get(id);
|
|
295
|
+
const before = {
|
|
296
|
+
blendMode: layer.blendMode,
|
|
297
|
+
opacity: layer.opacity
|
|
298
|
+
};
|
|
299
|
+
Object.assign(layer, changes);
|
|
300
|
+
const after = {
|
|
301
|
+
blendMode: layer.blendMode,
|
|
302
|
+
opacity: layer.opacity
|
|
303
|
+
};
|
|
304
|
+
if (before.blendMode !== after.blendMode || before.opacity !== after.opacity) {
|
|
305
|
+
const apply = (settings) => {
|
|
306
|
+
Object.assign(this.get(id), settings);
|
|
307
|
+
this.select(id);
|
|
308
|
+
};
|
|
309
|
+
this.history.push({
|
|
310
|
+
undo: () => apply(before),
|
|
311
|
+
redo: () => apply(after)
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
this.notify();
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Move a layer to an index and select it.
|
|
318
|
+
*/
|
|
319
|
+
moveAndSelect(id, index) {
|
|
320
|
+
this.items = moveItem(this.items, this.indexOf(id), index);
|
|
321
|
+
this.select(id);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* An undoable change of image data: undo and redo both swap the stored pixels with the layer's, then select the layer.
|
|
325
|
+
*/
|
|
326
|
+
imageEntry(id, area, before) {
|
|
327
|
+
let stored = before;
|
|
328
|
+
const swap = () => {
|
|
329
|
+
const layer = this.get(id);
|
|
330
|
+
const current = copyArea(this.createCanvas, layer.canvas, area);
|
|
331
|
+
const ctx = layer.context;
|
|
332
|
+
ctx.clearRect(area.x, area.y, area.width, area.height);
|
|
333
|
+
ctx.drawImage(stored, area.x, area.y);
|
|
334
|
+
stored = current;
|
|
335
|
+
this.select(id);
|
|
336
|
+
};
|
|
337
|
+
return {
|
|
338
|
+
undo: swap,
|
|
339
|
+
redo: swap
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Create a layer with the stack's size and canvas factory.
|
|
344
|
+
*/
|
|
345
|
+
newLayer(name) {
|
|
346
|
+
return new Layer({ name }, this.width, this.height, this.createCanvas);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get the next default layer name, e.g. "Layer 2".
|
|
350
|
+
*/
|
|
351
|
+
nextDefaultName() {
|
|
352
|
+
this.createdCount += 1;
|
|
353
|
+
return `Layer ${this.createdCount}`;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Throw if the layer limit has been reached.
|
|
357
|
+
*/
|
|
358
|
+
assertCanCreate() {
|
|
359
|
+
if (!this.canCreate) throw new Error(`Cannot have more than ${this.maxLayers} layers`);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Insert a layer directly above the active layer and select it, as one undoable action. Undo selects `previousId`.
|
|
363
|
+
*/
|
|
364
|
+
insertAboveActive(layer, previousId = this.activeId) {
|
|
365
|
+
const index = this.indexOf(this.activeId);
|
|
366
|
+
this.insertAt(layer, index);
|
|
367
|
+
this.history.push({
|
|
368
|
+
undo: () => {
|
|
369
|
+
this.remove(layer.id);
|
|
370
|
+
this.select(previousId);
|
|
371
|
+
},
|
|
372
|
+
redo: () => this.insertAt(layer, index)
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Insert a layer at an index (0 = top) and select it.
|
|
377
|
+
*/
|
|
378
|
+
insertAt(layer, index) {
|
|
379
|
+
this.items = [
|
|
380
|
+
...this.items.slice(0, index),
|
|
381
|
+
layer,
|
|
382
|
+
...this.items.slice(index)
|
|
383
|
+
];
|
|
384
|
+
this.activeId = layer.id;
|
|
385
|
+
this.notify();
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Dispatch a `change` event.
|
|
389
|
+
*/
|
|
390
|
+
notify() {
|
|
391
|
+
this.dispatchEvent(new Event("change"));
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
export { DEFAULT_MAX_LAYERS, LayerStack as default };
|