js-draw 1.2.1 → 1.3.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 -30
- package/dist/Editor.css +70 -4
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +73 -40
- package/dist/cjs/Editor.js +90 -24
- package/dist/cjs/EditorImage.d.ts +58 -6
- package/dist/cjs/EditorImage.js +336 -60
- package/dist/cjs/SVGLoader.d.ts +10 -4
- package/dist/cjs/SVGLoader.js +30 -10
- package/dist/cjs/UndoRedoHistory.d.ts +2 -2
- package/dist/cjs/UndoRedoHistory.js +4 -2
- package/dist/cjs/Viewport.d.ts +2 -1
- package/dist/cjs/Viewport.js +12 -3
- package/dist/cjs/commands/Command.d.ts +1 -0
- package/dist/cjs/commands/Command.js +1 -0
- package/dist/cjs/commands/Erase.js +1 -1
- package/dist/cjs/commands/SerializableCommand.d.ts +1 -1
- package/dist/cjs/commands/SerializableCommand.js +16 -2
- package/dist/cjs/commands/localization.d.ts +2 -0
- package/dist/cjs/commands/localization.js +2 -0
- package/dist/cjs/components/AbstractComponent.d.ts +38 -0
- package/dist/cjs/components/AbstractComponent.js +31 -0
- package/dist/cjs/components/BackgroundComponent.d.ts +10 -1
- package/dist/cjs/components/BackgroundComponent.js +60 -6
- package/dist/cjs/components/SVGGlobalAttributesObject.d.ts +2 -1
- package/dist/cjs/components/SVGGlobalAttributesObject.js +30 -1
- package/dist/cjs/components/Stroke.d.ts +1 -0
- package/dist/cjs/components/Stroke.js +44 -0
- package/dist/cjs/components/UnknownSVGObject.d.ts +2 -1
- package/dist/cjs/components/UnknownSVGObject.js +30 -1
- package/dist/cjs/components/lib.d.ts +2 -2
- package/dist/cjs/components/lib.js +15 -2
- package/dist/cjs/lib.d.ts +2 -45
- package/dist/cjs/lib.js +2 -45
- package/dist/cjs/rendering/RenderablePathSpec.d.ts +1 -0
- package/dist/cjs/rendering/RenderablePathSpec.js +1 -0
- package/dist/cjs/rendering/RenderingStyle.d.ts +1 -0
- package/dist/cjs/rendering/lib.d.ts +1 -0
- package/dist/cjs/rendering/lib.js +5 -1
- package/dist/cjs/rendering/renderers/AbstractRenderer.js +1 -1
- package/dist/cjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
- package/dist/cjs/shortcuts/KeyboardShortcutManager.js +2 -2
- package/dist/cjs/toolbar/localization.d.ts +1 -0
- package/dist/cjs/toolbar/localization.js +1 -0
- package/dist/cjs/toolbar/widgets/BaseWidget.js +5 -0
- package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +54 -25
- package/dist/cjs/toolbar/widgets/components/makeGridSelector.js +8 -0
- package/dist/cjs/tools/PanZoom.js +13 -8
- package/dist/cjs/tools/ScrollbarTool.d.ts +18 -0
- package/dist/cjs/tools/ScrollbarTool.js +85 -0
- package/dist/cjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
- package/dist/cjs/tools/ToolController.js +2 -0
- package/dist/cjs/types.d.ts +3 -1
- package/dist/cjs/util/adjustEditorThemeForContrast.js +1 -0
- package/dist/cjs/util/assertions.d.ts +4 -0
- package/dist/cjs/util/assertions.js +12 -1
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +73 -40
- package/dist/mjs/Editor.mjs +90 -24
- package/dist/mjs/EditorImage.d.ts +58 -6
- package/dist/mjs/EditorImage.mjs +313 -61
- package/dist/mjs/SVGLoader.d.ts +10 -4
- package/dist/mjs/SVGLoader.mjs +29 -9
- package/dist/mjs/UndoRedoHistory.d.ts +2 -2
- package/dist/mjs/UndoRedoHistory.mjs +4 -2
- package/dist/mjs/Viewport.d.ts +2 -1
- package/dist/mjs/Viewport.mjs +12 -3
- package/dist/mjs/commands/Command.d.ts +1 -0
- package/dist/mjs/commands/Command.mjs +1 -0
- package/dist/mjs/commands/Erase.mjs +1 -1
- package/dist/mjs/commands/SerializableCommand.d.ts +1 -1
- package/dist/mjs/commands/SerializableCommand.mjs +16 -2
- package/dist/mjs/commands/localization.d.ts +2 -0
- package/dist/mjs/commands/localization.mjs +2 -0
- package/dist/mjs/components/AbstractComponent.d.ts +38 -0
- package/dist/mjs/components/AbstractComponent.mjs +30 -0
- package/dist/mjs/components/BackgroundComponent.d.ts +10 -1
- package/dist/mjs/components/BackgroundComponent.mjs +37 -6
- package/dist/mjs/components/SVGGlobalAttributesObject.d.ts +2 -1
- package/dist/mjs/components/SVGGlobalAttributesObject.mjs +7 -1
- package/dist/mjs/components/Stroke.d.ts +1 -0
- package/dist/mjs/components/Stroke.mjs +44 -0
- package/dist/mjs/components/UnknownSVGObject.d.ts +2 -1
- package/dist/mjs/components/UnknownSVGObject.mjs +7 -1
- package/dist/mjs/components/lib.d.ts +2 -2
- package/dist/mjs/components/lib.mjs +2 -2
- package/dist/mjs/lib.d.ts +2 -45
- package/dist/mjs/lib.mjs +2 -45
- package/dist/mjs/rendering/RenderablePathSpec.d.ts +1 -0
- package/dist/mjs/rendering/RenderablePathSpec.mjs +1 -0
- package/dist/mjs/rendering/RenderingStyle.d.ts +1 -0
- package/dist/mjs/rendering/lib.d.ts +1 -0
- package/dist/mjs/rendering/lib.mjs +1 -0
- package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +1 -1
- package/dist/mjs/shortcuts/KeyboardShortcutManager.d.ts +2 -2
- package/dist/mjs/shortcuts/KeyboardShortcutManager.mjs +2 -2
- package/dist/mjs/toolbar/localization.d.ts +1 -0
- package/dist/mjs/toolbar/localization.mjs +1 -0
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +5 -0
- package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +54 -25
- package/dist/mjs/toolbar/widgets/components/makeGridSelector.mjs +8 -0
- package/dist/mjs/tools/PanZoom.mjs +13 -8
- package/dist/mjs/tools/ScrollbarTool.d.ts +18 -0
- package/dist/mjs/tools/ScrollbarTool.mjs +79 -0
- package/dist/mjs/tools/SelectionTool/SelectionTool.selecting.test.d.ts +1 -0
- package/dist/mjs/tools/ToolController.mjs +2 -0
- package/dist/mjs/types.d.ts +3 -1
- package/dist/mjs/util/adjustEditorThemeForContrast.mjs +1 -0
- package/dist/mjs/util/assertions.d.ts +4 -0
- package/dist/mjs/util/assertions.mjs +10 -0
- package/dist/mjs/version.mjs +1 -1
- package/package.json +3 -4
- package/src/Editor.scss +8 -0
- package/src/dialogs/dialogs.scss +2 -1
- package/src/toolbar/AbstractToolbar.scss +3 -0
- package/src/toolbar/EdgeToolbar.scss +4 -1
- package/src/toolbar/widgets/DocumentPropertiesWidget.scss +12 -0
- package/src/toolbar/widgets/components/makeGridSelector.scss +6 -1
- package/src/tools/ScrollbarTool.scss +57 -0
- package/src/tools/{SoundUITool.css → SoundUITool.scss} +4 -0
- package/src/tools/tools.scss +2 -1
@@ -5,9 +5,11 @@ import { Rect2 } from '@js-draw/math';
|
|
5
5
|
import RenderingCache from './rendering/caching/RenderingCache';
|
6
6
|
import SerializableCommand from './commands/SerializableCommand';
|
7
7
|
import EventDispatcher from './EventDispatcher';
|
8
|
+
import Command from './commands/Command';
|
8
9
|
export declare const sortLeavesByZIndex: (leaves: Array<ImageNode>) => void;
|
9
10
|
export declare enum EditorImageEventType {
|
10
|
-
ExportViewportChanged = 0
|
11
|
+
ExportViewportChanged = 0,
|
12
|
+
AutoresizeModeChanged = 1
|
11
13
|
}
|
12
14
|
export type EditorImageNotifier = EventDispatcher<EditorImageEventType, {
|
13
15
|
image: EditorImage;
|
@@ -19,6 +21,7 @@ export default class EditorImage {
|
|
19
21
|
private componentCount;
|
20
22
|
/** Viewport for the exported/imported image. */
|
21
23
|
private importExportViewport;
|
24
|
+
private shouldAutoresizeExportViewport;
|
22
25
|
readonly notifier: EditorImageNotifier;
|
23
26
|
constructor();
|
24
27
|
getBackgroundComponents(): AbstractComponent[];
|
@@ -35,14 +38,20 @@ export default class EditorImage {
|
|
35
38
|
render(renderer: AbstractRenderer, viewport: Viewport | null): void;
|
36
39
|
/** Renders all nodes, even ones not within the viewport. @internal */
|
37
40
|
renderAll(renderer: AbstractRenderer): void;
|
38
|
-
/**
|
41
|
+
/**
|
42
|
+
* @returns all elements in the image, sorted by z-index. This can be slow for large images.
|
43
|
+
*
|
44
|
+
* Does not include background elements. See {@link getBackgroundComponents}.
|
45
|
+
*/
|
39
46
|
getAllElements(): AbstractComponent[];
|
40
47
|
/** Returns the number of elements added to this image. @internal */
|
41
48
|
estimateNumElements(): number;
|
42
49
|
/** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
|
43
|
-
getElementsIntersectingRegion(region: Rect2): AbstractComponent[];
|
44
|
-
/** Called whenever an element is completely removed. @internal */
|
50
|
+
getElementsIntersectingRegion(region: Rect2, includeBackground?: boolean): AbstractComponent[];
|
51
|
+
/** Called whenever (just after) an element is completely removed. @internal */
|
45
52
|
onDestroyElement(elem: AbstractComponent): void;
|
53
|
+
/** Called just after an element is added. @internal */
|
54
|
+
private onElementAdded;
|
46
55
|
/**
|
47
56
|
* @returns the AbstractComponent with `id`, if it exists.
|
48
57
|
*
|
@@ -66,11 +75,40 @@ export default class EditorImage {
|
|
66
75
|
* @returns a `Viewport` for rendering the image when importing/exporting.
|
67
76
|
*/
|
68
77
|
getImportExportViewport(): Viewport;
|
78
|
+
/**
|
79
|
+
* @see {@link setImportExportRect}
|
80
|
+
*/
|
81
|
+
getImportExportRect(): Rect2;
|
82
|
+
/**
|
83
|
+
* Sets the import/export rectangle to the given `imageRect`. Disables
|
84
|
+
* autoresize (if it was previously enabled).
|
85
|
+
*/
|
69
86
|
setImportExportRect(imageRect: Rect2): SerializableCommand;
|
87
|
+
getAutoresizeEnabled(): boolean;
|
88
|
+
/** Returns a `Command` that sets whether the image should autoresize. */
|
89
|
+
setAutoresizeEnabled(autoresize: boolean): Command;
|
90
|
+
private setAutoresizeEnabledDirectly;
|
91
|
+
/** Updates the size/position of the viewport */
|
92
|
+
private autoresizeExportViewport;
|
93
|
+
private settingExportRect;
|
94
|
+
/**
|
95
|
+
* Sets the import/export viewport directly, without returning a `Command`.
|
96
|
+
* As such, this is not undoable.
|
97
|
+
*
|
98
|
+
* See setImportExportRect
|
99
|
+
*
|
100
|
+
* Returns true if changes to the viewport were made (and thus
|
101
|
+
* ExportViewportChanged was fired.)
|
102
|
+
*/
|
103
|
+
private setExportRectDirectly;
|
104
|
+
private onExportViewportChanged;
|
70
105
|
private static SetImportExportRectCommand;
|
71
106
|
}
|
72
107
|
type TooSmallToRenderCheck = (rect: Rect2) => boolean;
|
73
|
-
/**
|
108
|
+
/**
|
109
|
+
* Part of the Editor's image. Does not handle fullscreen/invisible components.
|
110
|
+
* @internal
|
111
|
+
*/
|
74
112
|
export declare class ImageNode {
|
75
113
|
private parent;
|
76
114
|
private content;
|
@@ -84,17 +122,31 @@ export declare class ImageNode {
|
|
84
122
|
onContentChange(): void;
|
85
123
|
getContent(): AbstractComponent | null;
|
86
124
|
getParent(): ImageNode | null;
|
87
|
-
|
125
|
+
protected getChildrenIntersectingRegion(region: Rect2, isTooSmallFilter?: TooSmallToRenderCheck): ImageNode[];
|
88
126
|
getChildrenOrSelfIntersectingRegion(region: Rect2): ImageNode[];
|
89
127
|
getLeavesIntersectingRegion(region: Rect2, isTooSmall?: TooSmallToRenderCheck): ImageNode[];
|
90
128
|
getChildWithContent(target: AbstractComponent): ImageNode | null;
|
91
129
|
getLeaves(): ImageNode[];
|
92
130
|
addLeaf(leaf: AbstractComponent): ImageNode;
|
131
|
+
protected static createLeafNode(parent: ImageNode, content: AbstractComponent): ImageNode;
|
93
132
|
getBBox(): Rect2;
|
94
133
|
recomputeBBox(bubbleUp: boolean): void;
|
134
|
+
private unionBBoxWith;
|
95
135
|
private updateParents;
|
96
136
|
private rebalance;
|
137
|
+
protected removeChild(child: ImageNode): void;
|
97
138
|
remove(): void;
|
98
139
|
render(renderer: AbstractRenderer, visibleRect?: Rect2): void;
|
140
|
+
renderDebugBoundingBoxes(renderer: AbstractRenderer, visibleRect: Rect2, depth?: number): void;
|
141
|
+
}
|
142
|
+
/** An `ImageNode` that can properly handle fullscreen/data components. @internal */
|
143
|
+
export declare class RootImageNode extends ImageNode {
|
144
|
+
private fullscreenChildren;
|
145
|
+
private dataComponents;
|
146
|
+
protected getChildrenIntersectingRegion(region: Rect2, _isTooSmall?: TooSmallToRenderCheck): ImageNode[];
|
147
|
+
getLeaves(): ImageNode[];
|
148
|
+
removeChild(child: ImageNode): void;
|
149
|
+
getChildWithContent(target: AbstractComponent): ImageNode | null;
|
150
|
+
addLeaf(leafContent: AbstractComponent): ImageNode;
|
99
151
|
}
|
100
152
|
export {};
|
package/dist/mjs/EditorImage.mjs
CHANGED
@@ -4,11 +4,12 @@ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, p
|
|
4
4
|
};
|
5
5
|
var _a, _b, _c;
|
6
6
|
import Viewport from './Viewport.mjs';
|
7
|
-
import AbstractComponent from './components/AbstractComponent.mjs';
|
8
|
-
import { Rect2, Vec2, Mat33 } from '@js-draw/math';
|
7
|
+
import AbstractComponent, { ComponentSizingMode } from './components/AbstractComponent.mjs';
|
8
|
+
import { Rect2, Vec2, Mat33, Color4 } from '@js-draw/math';
|
9
9
|
import SerializableCommand from './commands/SerializableCommand.mjs';
|
10
10
|
import EventDispatcher from './EventDispatcher.mjs';
|
11
|
-
import { assertIsNumber, assertIsNumberArray } from './util/assertions.mjs';
|
11
|
+
import { assertIsBoolean, assertIsNumber, assertIsNumberArray } from './util/assertions.mjs';
|
12
|
+
import Command from './commands/Command.mjs';
|
12
13
|
// @internal Sort by z-index, low to high
|
13
14
|
export const sortLeavesByZIndex = (leaves) => {
|
14
15
|
leaves.sort((a, b) => a.getContent().getZIndex() - b.getContent().getZIndex());
|
@@ -16,23 +17,25 @@ export const sortLeavesByZIndex = (leaves) => {
|
|
16
17
|
export var EditorImageEventType;
|
17
18
|
(function (EditorImageEventType) {
|
18
19
|
EditorImageEventType[EditorImageEventType["ExportViewportChanged"] = 0] = "ExportViewportChanged";
|
20
|
+
EditorImageEventType[EditorImageEventType["AutoresizeModeChanged"] = 1] = "AutoresizeModeChanged";
|
19
21
|
})(EditorImageEventType || (EditorImageEventType = {}));
|
22
|
+
const debugMode = false;
|
20
23
|
// Handles lookup/storage of elements in the image
|
21
24
|
class EditorImage {
|
22
25
|
// @internal
|
23
26
|
constructor() {
|
24
27
|
this.componentCount = 0;
|
25
|
-
this.
|
26
|
-
this.
|
28
|
+
this.settingExportRect = false;
|
29
|
+
this.root = new RootImageNode();
|
30
|
+
this.background = new RootImageNode();
|
27
31
|
this.componentsById = {};
|
28
32
|
this.notifier = new EventDispatcher();
|
29
33
|
this.importExportViewport = new Viewport(() => {
|
30
|
-
this.
|
31
|
-
image: this,
|
32
|
-
});
|
34
|
+
this.onExportViewportChanged();
|
33
35
|
});
|
34
36
|
// Default to a 500x500 image
|
35
37
|
this.importExportViewport.updateScreenSize(Vec2.of(500, 500));
|
38
|
+
this.shouldAutoresizeExportViewport = false;
|
36
39
|
}
|
37
40
|
// Returns all components that make up the background of this image. These
|
38
41
|
// components are rendered below all other components.
|
@@ -66,7 +69,13 @@ class EditorImage {
|
|
66
69
|
/** @internal */
|
67
70
|
renderWithCache(screenRenderer, cache, viewport) {
|
68
71
|
this.background.render(screenRenderer, viewport.visibleRect);
|
69
|
-
|
72
|
+
// If in debug mode, avoid rendering with cache to show additional debug information
|
73
|
+
if (!debugMode) {
|
74
|
+
cache.render(screenRenderer, this.root, viewport);
|
75
|
+
}
|
76
|
+
else {
|
77
|
+
this.root.render(screenRenderer, viewport.visibleRect);
|
78
|
+
}
|
70
79
|
}
|
71
80
|
/**
|
72
81
|
* Renders all nodes visible from `viewport` (or all nodes if `viewport = null`).
|
@@ -82,7 +91,11 @@ class EditorImage {
|
|
82
91
|
renderAll(renderer) {
|
83
92
|
this.render(renderer, null);
|
84
93
|
}
|
85
|
-
/**
|
94
|
+
/**
|
95
|
+
* @returns all elements in the image, sorted by z-index. This can be slow for large images.
|
96
|
+
*
|
97
|
+
* Does not include background elements. See {@link getBackgroundComponents}.
|
98
|
+
*/
|
86
99
|
getAllElements() {
|
87
100
|
const leaves = this.root.getLeaves();
|
88
101
|
sortLeavesByZIndex(leaves);
|
@@ -93,15 +106,25 @@ class EditorImage {
|
|
93
106
|
return this.componentCount;
|
94
107
|
}
|
95
108
|
/** @returns a list of `AbstractComponent`s intersecting `region`, sorted by z-index. */
|
96
|
-
getElementsIntersectingRegion(region) {
|
97
|
-
|
109
|
+
getElementsIntersectingRegion(region, includeBackground = false) {
|
110
|
+
let leaves = this.root.getLeavesIntersectingRegion(region);
|
111
|
+
if (includeBackground) {
|
112
|
+
leaves = leaves.concat(this.background.getLeavesIntersectingRegion(region));
|
113
|
+
}
|
98
114
|
sortLeavesByZIndex(leaves);
|
99
115
|
return leaves.map(leaf => leaf.getContent());
|
100
116
|
}
|
101
|
-
/** Called whenever an element is completely removed. @internal */
|
117
|
+
/** Called whenever (just after) an element is completely removed. @internal */
|
102
118
|
onDestroyElement(elem) {
|
103
119
|
this.componentCount--;
|
104
120
|
delete this.componentsById[elem.getId()];
|
121
|
+
this.autoresizeExportViewport();
|
122
|
+
}
|
123
|
+
/** Called just after an element is added. @internal */
|
124
|
+
onElementAdded(elem) {
|
125
|
+
this.componentCount++;
|
126
|
+
this.componentsById[elem.getId()] = elem;
|
127
|
+
this.autoresizeExportViewport();
|
105
128
|
}
|
106
129
|
/**
|
107
130
|
* @returns the AbstractComponent with `id`, if it exists.
|
@@ -112,13 +135,15 @@ class EditorImage {
|
|
112
135
|
return this.componentsById[id] ?? null;
|
113
136
|
}
|
114
137
|
addElementDirectly(elem) {
|
138
|
+
// Because onAddToImage can affect the element's bounding box,
|
139
|
+
// this needs to be called before parentTree.addLeaf.
|
115
140
|
elem.onAddToImage(this);
|
116
|
-
this.componentCount++;
|
117
|
-
this.componentsById[elem.getId()] = elem;
|
118
141
|
// If a background component, add to the background. Else,
|
119
142
|
// add to the normal component tree.
|
120
143
|
const parentTree = elem.isBackground() ? this.background : this.root;
|
121
|
-
|
144
|
+
const result = parentTree.addLeaf(elem);
|
145
|
+
this.onElementAdded(elem);
|
146
|
+
return result;
|
122
147
|
}
|
123
148
|
removeElementDirectly(element) {
|
124
149
|
const container = this.findParent(element);
|
@@ -149,11 +174,82 @@ class EditorImage {
|
|
149
174
|
getImportExportViewport() {
|
150
175
|
return this.importExportViewport;
|
151
176
|
}
|
177
|
+
/**
|
178
|
+
* @see {@link setImportExportRect}
|
179
|
+
*/
|
180
|
+
getImportExportRect() {
|
181
|
+
return this.getImportExportViewport().visibleRect;
|
182
|
+
}
|
183
|
+
/**
|
184
|
+
* Sets the import/export rectangle to the given `imageRect`. Disables
|
185
|
+
* autoresize (if it was previously enabled).
|
186
|
+
*/
|
152
187
|
setImportExportRect(imageRect) {
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
return
|
188
|
+
return EditorImage.SetImportExportRectCommand.of(this, imageRect, false);
|
189
|
+
}
|
190
|
+
getAutoresizeEnabled() {
|
191
|
+
return this.shouldAutoresizeExportViewport;
|
192
|
+
}
|
193
|
+
/** Returns a `Command` that sets whether the image should autoresize. */
|
194
|
+
setAutoresizeEnabled(autoresize) {
|
195
|
+
if (autoresize === this.shouldAutoresizeExportViewport) {
|
196
|
+
return Command.empty;
|
197
|
+
}
|
198
|
+
const newBBox = this.root.getBBox();
|
199
|
+
return EditorImage.SetImportExportRectCommand.of(this, newBBox, autoresize);
|
200
|
+
}
|
201
|
+
setAutoresizeEnabledDirectly(shouldAutoresize) {
|
202
|
+
if (shouldAutoresize !== this.shouldAutoresizeExportViewport) {
|
203
|
+
this.shouldAutoresizeExportViewport = shouldAutoresize;
|
204
|
+
this.notifier.dispatch(EditorImageEventType.AutoresizeModeChanged, {
|
205
|
+
image: this,
|
206
|
+
});
|
207
|
+
}
|
208
|
+
}
|
209
|
+
/** Updates the size/position of the viewport */
|
210
|
+
autoresizeExportViewport() {
|
211
|
+
// Only autoresize if in autoresize mode -- otherwise resizing the image
|
212
|
+
// should be done with undoable commands.
|
213
|
+
if (this.shouldAutoresizeExportViewport) {
|
214
|
+
this.setExportRectDirectly(this.root.getBBox());
|
215
|
+
}
|
216
|
+
}
|
217
|
+
/**
|
218
|
+
* Sets the import/export viewport directly, without returning a `Command`.
|
219
|
+
* As such, this is not undoable.
|
220
|
+
*
|
221
|
+
* See setImportExportRect
|
222
|
+
*
|
223
|
+
* Returns true if changes to the viewport were made (and thus
|
224
|
+
* ExportViewportChanged was fired.)
|
225
|
+
*/
|
226
|
+
setExportRectDirectly(newRect) {
|
227
|
+
const viewport = this.getImportExportViewport();
|
228
|
+
const lastSize = viewport.getScreenRectSize();
|
229
|
+
const lastTransform = viewport.canvasToScreenTransform;
|
230
|
+
const newTransform = Mat33.translation(newRect.topLeft.times(-1));
|
231
|
+
if (!lastSize.eq(newRect.size) || !lastTransform.eq(newTransform)) {
|
232
|
+
// Prevent the ExportViewportChanged event from being fired
|
233
|
+
// multiple times for the same viewport change: Set settingExportRect
|
234
|
+
// to true.
|
235
|
+
this.settingExportRect = true;
|
236
|
+
viewport.updateScreenSize(newRect.size);
|
237
|
+
viewport.resetTransform(newTransform);
|
238
|
+
this.settingExportRect = false;
|
239
|
+
this.onExportViewportChanged();
|
240
|
+
return true;
|
241
|
+
}
|
242
|
+
return false;
|
243
|
+
}
|
244
|
+
onExportViewportChanged() {
|
245
|
+
// Prevent firing duplicate events -- changes
|
246
|
+
// made by exportViewport.resetTransform may cause this method to be
|
247
|
+
// called.
|
248
|
+
if (!this.settingExportRect) {
|
249
|
+
this.notifier.dispatch(EditorImageEventType.ExportViewportChanged, {
|
250
|
+
image: this,
|
251
|
+
});
|
252
|
+
}
|
157
253
|
}
|
158
254
|
}
|
159
255
|
_a = EditorImage;
|
@@ -214,37 +310,57 @@ EditorImage.AddElementCommand = (_b = class extends SerializableCommand {
|
|
214
310
|
_b);
|
215
311
|
// Handles resizing the background import/export region of the image.
|
216
312
|
EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand {
|
217
|
-
constructor(originalSize, originalTransform,
|
313
|
+
constructor(originalSize, originalTransform, originalAutoresize, newExportRect, newAutoresize) {
|
218
314
|
super(EditorImage.SetImportExportRectCommand.commandId);
|
219
315
|
this.originalSize = originalSize;
|
220
316
|
this.originalTransform = originalTransform;
|
221
|
-
this.
|
317
|
+
this.originalAutoresize = originalAutoresize;
|
318
|
+
this.newExportRect = newExportRect;
|
319
|
+
this.newAutoresize = newAutoresize;
|
320
|
+
}
|
321
|
+
// Uses `image` to store the original size/transform
|
322
|
+
static of(image, newExportRect, newAutoresize) {
|
323
|
+
const importExportViewport = image.getImportExportViewport();
|
324
|
+
const originalSize = importExportViewport.visibleRect.size;
|
325
|
+
const originalTransform = importExportViewport.canvasToScreenTransform;
|
326
|
+
const originalAutoresize = image.getAutoresizeEnabled();
|
327
|
+
return new EditorImage.SetImportExportRectCommand(originalSize, originalTransform, originalAutoresize, newExportRect, newAutoresize);
|
222
328
|
}
|
223
329
|
apply(editor) {
|
224
|
-
|
225
|
-
|
226
|
-
viewport.resetTransform(Mat33.translation(this.finalRect.topLeft.times(-1)));
|
330
|
+
editor.image.setAutoresizeEnabledDirectly(this.newAutoresize);
|
331
|
+
editor.image.setExportRectDirectly(this.newExportRect);
|
227
332
|
editor.queueRerender();
|
228
333
|
}
|
229
334
|
unapply(editor) {
|
230
335
|
const viewport = editor.image.getImportExportViewport();
|
336
|
+
editor.image.setAutoresizeEnabledDirectly(this.originalAutoresize);
|
231
337
|
viewport.updateScreenSize(this.originalSize);
|
232
338
|
viewport.resetTransform(this.originalTransform);
|
233
339
|
editor.queueRerender();
|
234
340
|
}
|
235
341
|
description(_editor, localization) {
|
236
|
-
|
342
|
+
if (this.newAutoresize !== this.originalAutoresize) {
|
343
|
+
if (this.newAutoresize) {
|
344
|
+
return localization.enabledAutoresizeOutputCommand;
|
345
|
+
}
|
346
|
+
else {
|
347
|
+
return localization.disabledAutoresizeOutputCommand;
|
348
|
+
}
|
349
|
+
}
|
350
|
+
return localization.resizeOutputCommand(this.newExportRect);
|
237
351
|
}
|
238
352
|
serializeToJSON() {
|
239
353
|
return {
|
240
354
|
originalSize: this.originalSize.xy,
|
241
355
|
originalTransform: this.originalTransform.toArray(),
|
242
356
|
newRegion: {
|
243
|
-
x: this.
|
244
|
-
y: this.
|
245
|
-
w: this.
|
246
|
-
h: this.
|
357
|
+
x: this.newExportRect.x,
|
358
|
+
y: this.newExportRect.y,
|
359
|
+
w: this.newExportRect.w,
|
360
|
+
h: this.newExportRect.h,
|
247
361
|
},
|
362
|
+
autoresize: this.newAutoresize,
|
363
|
+
originalAutoresize: this.originalAutoresize,
|
248
364
|
};
|
249
365
|
}
|
250
366
|
},
|
@@ -262,15 +378,22 @@ EditorImage.SetImportExportRectCommand = (_c = class extends SerializableCommand
|
|
262
378
|
json.newRegion.w,
|
263
379
|
json.newRegion.h,
|
264
380
|
]);
|
381
|
+
assertIsBoolean(json.autoresize ?? false);
|
382
|
+
assertIsBoolean(json.originalAutoresize ?? false);
|
265
383
|
const originalSize = Vec2.ofXY(json.originalSize);
|
266
384
|
const originalTransform = new Mat33(...json.originalTransform);
|
267
385
|
const finalRect = new Rect2(json.newRegion.x, json.newRegion.y, json.newRegion.w, json.newRegion.h);
|
268
|
-
|
386
|
+
const autoresize = json.autoresize ?? false;
|
387
|
+
const originalAutoresize = json.originalAutoresize ?? false;
|
388
|
+
return new EditorImage.SetImportExportRectCommand(originalSize, originalTransform, originalAutoresize, finalRect, autoresize);
|
269
389
|
});
|
270
390
|
})(),
|
271
391
|
_c);
|
272
392
|
export default EditorImage;
|
273
|
-
/**
|
393
|
+
/**
|
394
|
+
* Part of the Editor's image. Does not handle fullscreen/invisible components.
|
395
|
+
* @internal
|
396
|
+
*/
|
274
397
|
export class ImageNode {
|
275
398
|
constructor(parent = null) {
|
276
399
|
this.parent = parent;
|
@@ -292,9 +415,11 @@ export class ImageNode {
|
|
292
415
|
getParent() {
|
293
416
|
return this.parent;
|
294
417
|
}
|
295
|
-
|
418
|
+
// Override this to change how children are considered within a given region.
|
419
|
+
getChildrenIntersectingRegion(region, isTooSmallFilter) {
|
296
420
|
return this.children.filter(child => {
|
297
|
-
|
421
|
+
const bbox = child.getBBox();
|
422
|
+
return !isTooSmallFilter?.(bbox) && bbox.intersects(region);
|
298
423
|
});
|
299
424
|
}
|
300
425
|
getChildrenOrSelfIntersectingRegion(region) {
|
@@ -304,29 +429,25 @@ export class ImageNode {
|
|
304
429
|
return this.getChildrenIntersectingRegion(region);
|
305
430
|
}
|
306
431
|
// Returns a list of `ImageNode`s with content (and thus no children).
|
432
|
+
// Override getChildrenIntersectingRegion to customize how this method
|
433
|
+
// determines whether/which children are in `region`.
|
307
434
|
getLeavesIntersectingRegion(region, isTooSmall) {
|
308
435
|
const result = [];
|
309
|
-
let current;
|
310
436
|
const workList = [];
|
311
437
|
workList.push(this);
|
312
|
-
const toNext = () => {
|
313
|
-
current = undefined;
|
314
|
-
const next = workList.pop();
|
315
|
-
if (next && !isTooSmall?.(next.bbox)) {
|
316
|
-
current = next;
|
317
|
-
if (current.content !== null && current.getBBox().intersection(region)) {
|
318
|
-
result.push(current);
|
319
|
-
}
|
320
|
-
workList.push(...current.getChildrenIntersectingRegion(region));
|
321
|
-
}
|
322
|
-
};
|
323
438
|
while (workList.length > 0) {
|
324
|
-
|
439
|
+
const current = workList.pop();
|
440
|
+
if (current.content !== null) {
|
441
|
+
result.push(current);
|
442
|
+
}
|
443
|
+
workList.push(...current.getChildrenIntersectingRegion(region, isTooSmall));
|
325
444
|
}
|
326
445
|
return result;
|
327
446
|
}
|
328
447
|
// Returns the child of this with the target content or `null` if no
|
329
448
|
// such child exists.
|
449
|
+
//
|
450
|
+
// Note: Relies on all children to have valid bounding boxes.
|
330
451
|
getChildWithContent(target) {
|
331
452
|
const candidates = this.getLeavesIntersectingRegion(target.getBBox());
|
332
453
|
for (const candidate of candidates) {
|
@@ -390,12 +511,19 @@ export class ImageNode {
|
|
390
511
|
result.rebalance();
|
391
512
|
return result;
|
392
513
|
}
|
393
|
-
const newNode =
|
514
|
+
const newNode = ImageNode.createLeafNode(this, leaf);
|
394
515
|
this.children.push(newNode);
|
395
|
-
newNode.content = leaf;
|
396
516
|
newNode.recomputeBBox(true);
|
397
517
|
return newNode;
|
398
518
|
}
|
519
|
+
// Creates a new leaf node with the given content.
|
520
|
+
// This only establishes the parent-child linking in one direction. Callers
|
521
|
+
// must add the resultant node to the list of children manually.
|
522
|
+
static createLeafNode(parent, content) {
|
523
|
+
const newNode = new ImageNode(parent);
|
524
|
+
newNode.content = content;
|
525
|
+
return newNode;
|
526
|
+
}
|
399
527
|
getBBox() {
|
400
528
|
return this.bbox;
|
401
529
|
}
|
@@ -411,9 +539,20 @@ export class ImageNode {
|
|
411
539
|
this.bbox = Rect2.union(...this.children.map(child => child.getBBox()));
|
412
540
|
}
|
413
541
|
if (bubbleUp && !oldBBox.eq(this.bbox)) {
|
414
|
-
this.
|
542
|
+
if (!this.bbox.containsRect(oldBBox)) {
|
543
|
+
this.parent?.unionBBoxWith(this.bbox);
|
544
|
+
}
|
545
|
+
else {
|
546
|
+
this.parent?.recomputeBBox(true);
|
547
|
+
}
|
415
548
|
}
|
416
549
|
}
|
550
|
+
// Grows this' bounding box to also include `other`.
|
551
|
+
// Always bubbles up.
|
552
|
+
unionBBoxWith(other) {
|
553
|
+
this.bbox = this.bbox.union(other);
|
554
|
+
this.parent?.unionBBoxWith(other);
|
555
|
+
}
|
417
556
|
updateParents(recursive = false) {
|
418
557
|
for (const child of this.children) {
|
419
558
|
child.parent = this;
|
@@ -444,6 +583,19 @@ export class ImageNode {
|
|
444
583
|
}
|
445
584
|
}
|
446
585
|
}
|
586
|
+
// Removes the parent-to-child link.
|
587
|
+
// Called internally by `.remove`
|
588
|
+
removeChild(child) {
|
589
|
+
const oldChildCount = this.children.length;
|
590
|
+
this.children = this.children.filter(node => {
|
591
|
+
return node !== child;
|
592
|
+
});
|
593
|
+
console.assert(this.children.length === oldChildCount - 1, `${oldChildCount - 1} ≠ ${this.children.length} after removing all nodes equal to ${child}. Nodes should only be removed once.`);
|
594
|
+
this.children.forEach(child => {
|
595
|
+
child.rebalance();
|
596
|
+
});
|
597
|
+
this.recomputeBBox(true);
|
598
|
+
}
|
447
599
|
// Remove this node and all of its children
|
448
600
|
remove() {
|
449
601
|
this.content?.onRemoveFromImage();
|
@@ -452,18 +604,10 @@ export class ImageNode {
|
|
452
604
|
this.children = [];
|
453
605
|
return;
|
454
606
|
}
|
455
|
-
|
456
|
-
|
457
|
-
return node !== this;
|
458
|
-
});
|
459
|
-
console.assert(this.parent.children.length === oldChildCount - 1, `${oldChildCount - 1} ≠ ${this.parent.children.length} after removing all nodes equal to ${this}. Nodes should only be removed once.`);
|
460
|
-
this.parent.children.forEach(child => {
|
461
|
-
child.rebalance();
|
462
|
-
});
|
463
|
-
this.parent.recomputeBBox(true);
|
464
|
-
// Invalidate/disconnect this.
|
465
|
-
this.content = null;
|
607
|
+
this.parent.removeChild(this);
|
608
|
+
// Remove the child-to-parent link and invalid this
|
466
609
|
this.parent = null;
|
610
|
+
this.content = null;
|
467
611
|
this.children = [];
|
468
612
|
}
|
469
613
|
render(renderer, visibleRect) {
|
@@ -479,6 +623,114 @@ export class ImageNode {
|
|
479
623
|
// Leaves by definition have content
|
480
624
|
leaf.getContent().render(renderer, visibleRect);
|
481
625
|
}
|
626
|
+
// Show debug information
|
627
|
+
if (debugMode && visibleRect) {
|
628
|
+
this.renderDebugBoundingBoxes(renderer, visibleRect);
|
629
|
+
}
|
630
|
+
}
|
631
|
+
// Debug only: Shows bounding boxes of this and all children.
|
632
|
+
renderDebugBoundingBoxes(renderer, visibleRect, depth = 0) {
|
633
|
+
const bbox = this.getBBox();
|
634
|
+
const pixelSize = 1 / (renderer.getSizeOfCanvasPixelOnScreen() || 1);
|
635
|
+
if (bbox.maxDimension < 3 * pixelSize || !bbox.intersects(visibleRect)) {
|
636
|
+
return;
|
637
|
+
}
|
638
|
+
// Render debug information for this
|
639
|
+
renderer.startObject(bbox);
|
640
|
+
// Different styling for leaf nodes
|
641
|
+
const isLeaf = !!this.content;
|
642
|
+
const fill = isLeaf ? Color4.ofRGBA(1, 0, 1, 0.4) : Color4.ofRGBA(0, 1, Math.sin(depth), 0.6);
|
643
|
+
const lineWidth = isLeaf ? 1 * pixelSize : 2 * pixelSize;
|
644
|
+
renderer.drawRect(bbox.intersection(visibleRect), lineWidth, { fill });
|
645
|
+
renderer.endObject();
|
646
|
+
// Render debug information for children
|
647
|
+
for (const child of this.children) {
|
648
|
+
child.renderDebugBoundingBoxes(renderer, visibleRect, depth + 1);
|
649
|
+
}
|
482
650
|
}
|
483
651
|
}
|
484
652
|
ImageNode.idCounter = 0;
|
653
|
+
/** An `ImageNode` that can properly handle fullscreen/data components. @internal */
|
654
|
+
export class RootImageNode extends ImageNode {
|
655
|
+
constructor() {
|
656
|
+
super(...arguments);
|
657
|
+
// Nodes that will always take up the entire screen
|
658
|
+
this.fullscreenChildren = [];
|
659
|
+
// Nodes that will never be visible unless a full render is done.
|
660
|
+
this.dataComponents = [];
|
661
|
+
}
|
662
|
+
getChildrenIntersectingRegion(region, _isTooSmall) {
|
663
|
+
const result = super.getChildrenIntersectingRegion(region);
|
664
|
+
for (const node of this.fullscreenChildren) {
|
665
|
+
result.push(node);
|
666
|
+
}
|
667
|
+
return result;
|
668
|
+
}
|
669
|
+
getLeaves() {
|
670
|
+
const leaves = super.getLeaves();
|
671
|
+
// Add fullscreen/data components — this method should
|
672
|
+
// return *all* leaves.
|
673
|
+
return this.dataComponents.concat(this.fullscreenChildren, leaves);
|
674
|
+
}
|
675
|
+
removeChild(child) {
|
676
|
+
let removed = false;
|
677
|
+
const checkTargetChild = (component) => {
|
678
|
+
const isTarget = component === child;
|
679
|
+
removed ||= isTarget;
|
680
|
+
return !isTarget;
|
681
|
+
};
|
682
|
+
// Check whether the child is stored in the data/fullscreen
|
683
|
+
// component arrays first.
|
684
|
+
this.dataComponents = this.dataComponents
|
685
|
+
.filter(checkTargetChild);
|
686
|
+
this.fullscreenChildren = this.fullscreenChildren
|
687
|
+
.filter(checkTargetChild);
|
688
|
+
if (!removed) {
|
689
|
+
super.removeChild(child);
|
690
|
+
}
|
691
|
+
}
|
692
|
+
getChildWithContent(target) {
|
693
|
+
const searchExtendedChildren = () => {
|
694
|
+
// Search through all extended children
|
695
|
+
const candidates = this.fullscreenChildren.concat(this.dataComponents);
|
696
|
+
for (const candidate of candidates) {
|
697
|
+
if (candidate.getContent() === target) {
|
698
|
+
return candidate;
|
699
|
+
}
|
700
|
+
}
|
701
|
+
return null;
|
702
|
+
};
|
703
|
+
// If positioned as if a standard child, search using the superclass first.
|
704
|
+
// Because it could be mislabeled, also search the extended children if the superclass
|
705
|
+
// search fails.
|
706
|
+
if (target.getSizingMode() === ComponentSizingMode.BoundingBox) {
|
707
|
+
return super.getChildWithContent(target) ?? searchExtendedChildren();
|
708
|
+
}
|
709
|
+
// Fall back to the superclass -- it's possible that the component has
|
710
|
+
// changed labels.
|
711
|
+
return super.getChildWithContent(target) ?? searchExtendedChildren();
|
712
|
+
}
|
713
|
+
addLeaf(leafContent) {
|
714
|
+
const sizingMode = leafContent.getSizingMode();
|
715
|
+
if (sizingMode === ComponentSizingMode.BoundingBox) {
|
716
|
+
return super.addLeaf(leafContent);
|
717
|
+
}
|
718
|
+
else if (sizingMode === ComponentSizingMode.FillScreen) {
|
719
|
+
this.onContentChange();
|
720
|
+
const newNode = ImageNode.createLeafNode(this, leafContent);
|
721
|
+
this.fullscreenChildren.push(newNode);
|
722
|
+
return newNode;
|
723
|
+
}
|
724
|
+
else if (sizingMode === ComponentSizingMode.Anywhere) {
|
725
|
+
this.onContentChange();
|
726
|
+
const newNode = ImageNode.createLeafNode(this, leafContent);
|
727
|
+
this.dataComponents.push(newNode);
|
728
|
+
return newNode;
|
729
|
+
}
|
730
|
+
else {
|
731
|
+
const exhaustivenessCheck = sizingMode;
|
732
|
+
throw new Error(`Invalid sizing mode, ${sizingMode}`);
|
733
|
+
return exhaustivenessCheck;
|
734
|
+
}
|
735
|
+
}
|
736
|
+
}
|