js-draw 0.3.1 → 0.4.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/.github/ISSUE_TEMPLATE/translation.md +4 -1
- package/CHANGELOG.md +16 -0
- package/README.md +1 -3
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +15 -1
- package/dist/src/Editor.js +221 -78
- package/dist/src/EditorImage.js +4 -1
- package/dist/src/Pointer.d.ts +1 -1
- package/dist/src/Pointer.js +8 -3
- package/dist/src/SVGLoader.d.ts +4 -1
- package/dist/src/SVGLoader.js +78 -33
- package/dist/src/UndoRedoHistory.d.ts +1 -0
- package/dist/src/UndoRedoHistory.js +6 -0
- package/dist/src/Viewport.d.ts +2 -0
- package/dist/src/Viewport.js +26 -5
- package/dist/src/commands/lib.d.ts +2 -1
- package/dist/src/commands/lib.js +2 -1
- package/dist/src/commands/localization.d.ts +1 -0
- package/dist/src/commands/localization.js +1 -0
- package/dist/src/commands/uniteCommands.d.ts +4 -0
- package/dist/src/commands/uniteCommands.js +105 -0
- package/dist/src/components/AbstractComponent.d.ts +2 -0
- package/dist/src/components/AbstractComponent.js +41 -5
- package/dist/src/components/ImageComponent.d.ts +27 -0
- package/dist/src/components/ImageComponent.js +129 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +2 -2
- package/dist/src/components/lib.d.ts +4 -2
- package/dist/src/components/lib.js +4 -2
- package/dist/src/components/localization.d.ts +2 -0
- package/dist/src/components/localization.js +2 -0
- package/dist/src/language/assertions.d.ts +1 -0
- package/dist/src/language/assertions.js +5 -0
- package/dist/src/math/LineSegment2.d.ts +2 -0
- package/dist/src/math/LineSegment2.js +3 -0
- package/dist/src/math/Mat33.d.ts +38 -2
- package/dist/src/math/Mat33.js +30 -1
- package/dist/src/math/Path.d.ts +1 -1
- package/dist/src/math/Path.js +10 -8
- package/dist/src/math/Vec3.d.ts +11 -1
- package/dist/src/math/Vec3.js +15 -0
- package/dist/src/math/rounding.d.ts +1 -0
- package/dist/src/math/rounding.js +13 -6
- package/dist/src/rendering/localization.d.ts +3 -0
- package/dist/src/rendering/localization.js +3 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -0
- package/dist/src/rendering/renderers/AbstractRenderer.js +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +5 -2
- package/dist/src/rendering/renderers/SVGRenderer.js +45 -20
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
- package/dist/src/toolbar/HTMLToolbar.js +5 -4
- package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
- package/dist/src/tools/BaseTool.d.ts +3 -1
- package/dist/src/tools/BaseTool.js +6 -0
- package/dist/src/tools/PasteHandler.d.ts +16 -0
- package/dist/src/tools/PasteHandler.js +144 -0
- package/dist/src/tools/Pen.js +1 -1
- package/dist/src/tools/SelectionTool/Selection.d.ts +54 -0
- package/dist/src/tools/SelectionTool/Selection.js +337 -0
- package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +35 -0
- package/dist/src/tools/SelectionTool/SelectionHandle.js +75 -0
- package/dist/src/tools/SelectionTool/SelectionTool.d.ts +31 -0
- package/dist/src/tools/SelectionTool/SelectionTool.js +276 -0
- package/dist/src/tools/SelectionTool/TransformMode.d.ts +34 -0
- package/dist/src/tools/SelectionTool/TransformMode.js +98 -0
- package/dist/src/tools/SelectionTool/types.d.ts +9 -0
- package/dist/src/tools/SelectionTool/types.js +11 -0
- package/dist/src/tools/ToolController.js +37 -28
- package/dist/src/tools/lib.d.ts +2 -1
- package/dist/src/tools/lib.js +2 -1
- package/dist/src/tools/localization.d.ts +3 -0
- package/dist/src/tools/localization.js +3 -0
- package/dist/src/types.d.ts +14 -3
- package/dist/src/types.js +2 -0
- package/package.json +1 -1
- package/src/Editor.css +1 -0
- package/src/Editor.ts +275 -109
- package/src/EditorImage.ts +7 -1
- package/src/Pointer.ts +8 -3
- package/src/SVGLoader.ts +90 -36
- package/src/UndoRedoHistory.test.ts +33 -0
- package/src/UndoRedoHistory.ts +8 -0
- package/src/Viewport.ts +30 -6
- package/src/commands/lib.ts +2 -0
- package/src/commands/localization.ts +2 -0
- package/src/commands/uniteCommands.test.ts +23 -0
- package/src/commands/uniteCommands.ts +121 -0
- package/src/components/AbstractComponent.ts +53 -11
- package/src/components/ImageComponent.ts +149 -0
- package/src/components/Text.ts +2 -6
- package/src/components/builders/FreehandLineBuilder.ts +2 -2
- package/src/components/lib.ts +7 -2
- package/src/components/localization.ts +4 -0
- package/src/language/assertions.ts +6 -0
- package/src/math/LineSegment2.test.ts +9 -0
- package/src/math/LineSegment2.ts +5 -0
- package/src/math/Mat33.test.ts +14 -0
- package/src/math/Mat33.ts +43 -2
- package/src/math/Path.toString.test.ts +12 -1
- package/src/math/Path.ts +11 -9
- package/src/math/Vec3.ts +22 -1
- package/src/math/rounding.test.ts +30 -5
- package/src/math/rounding.ts +16 -7
- package/src/rendering/localization.ts +6 -0
- package/src/rendering/renderers/AbstractRenderer.ts +19 -2
- package/src/rendering/renderers/CanvasRenderer.ts +10 -1
- package/src/rendering/renderers/DummyRenderer.ts +6 -1
- package/src/rendering/renderers/SVGRenderer.ts +50 -21
- package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
- package/src/toolbar/HTMLToolbar.ts +5 -4
- package/src/toolbar/widgets/SelectionToolWidget.ts +1 -1
- package/src/tools/BaseTool.ts +9 -1
- package/src/tools/PasteHandler.ts +159 -0
- package/src/tools/Pen.ts +1 -1
- package/src/tools/SelectionTool/Selection.ts +455 -0
- package/src/tools/SelectionTool/SelectionHandle.ts +99 -0
- package/src/tools/SelectionTool/SelectionTool.css +22 -0
- package/src/tools/{SelectionTool.test.ts → SelectionTool/SelectionTool.test.ts} +21 -21
- package/src/tools/SelectionTool/SelectionTool.ts +335 -0
- package/src/tools/SelectionTool/TransformMode.ts +114 -0
- package/src/tools/SelectionTool/types.ts +11 -0
- package/src/tools/ToolController.ts +52 -45
- package/src/tools/lib.ts +2 -1
- package/src/tools/localization.ts +8 -0
- package/src/types.ts +17 -3
- package/dist/src/tools/SelectionTool.d.ts +0 -59
- package/dist/src/tools/SelectionTool.js +0 -589
- package/src/tools/SelectionTool.ts +0 -725
@@ -0,0 +1,31 @@
|
|
1
|
+
import AbstractComponent from '../../components/AbstractComponent';
|
2
|
+
import Editor from '../../Editor';
|
3
|
+
import { CopyEvent, KeyPressEvent, KeyUpEvent, PointerEvt } from '../../types';
|
4
|
+
import BaseTool from '../BaseTool';
|
5
|
+
import Selection from './Selection';
|
6
|
+
export declare const cssPrefix = "selection-tool-";
|
7
|
+
export default class SelectionTool extends BaseTool {
|
8
|
+
private editor;
|
9
|
+
private handleOverlay;
|
10
|
+
private prevSelectionBox;
|
11
|
+
private selectionBox;
|
12
|
+
private lastEvtTarget;
|
13
|
+
constructor(editor: Editor, description: string);
|
14
|
+
private makeSelectionBox;
|
15
|
+
private selectionBoxHandlingEvt;
|
16
|
+
onPointerDown(event: PointerEvt): boolean;
|
17
|
+
onPointerMove(event: PointerEvt): void;
|
18
|
+
private onSelectionUpdated;
|
19
|
+
private onGestureEnd;
|
20
|
+
private zoomToSelection;
|
21
|
+
onPointerUp(event: PointerEvt): void;
|
22
|
+
onGestureCancel(): void;
|
23
|
+
private static handleableKeys;
|
24
|
+
onKeyPress(event: KeyPressEvent): boolean;
|
25
|
+
onKeyUp(evt: KeyUpEvent): boolean;
|
26
|
+
onCopy(event: CopyEvent): boolean;
|
27
|
+
setEnabled(enabled: boolean): void;
|
28
|
+
getSelection(): Selection | null;
|
29
|
+
setSelection(objects: AbstractComponent[]): void;
|
30
|
+
clearSelection(): void;
|
31
|
+
}
|
@@ -0,0 +1,276 @@
|
|
1
|
+
// Allows users to select/transform portions of the `EditorImage`.
|
2
|
+
// With respect to `extend`ing, `SelectionTool` is not stable.
|
3
|
+
// @packageDocumentation
|
4
|
+
import Mat33 from '../../math/Mat33';
|
5
|
+
import { Vec2 } from '../../math/Vec2';
|
6
|
+
import { EditorEventType } from '../../types';
|
7
|
+
import Viewport from '../../Viewport';
|
8
|
+
import BaseTool from '../BaseTool';
|
9
|
+
import SVGRenderer from '../../rendering/renderers/SVGRenderer';
|
10
|
+
import Selection from './Selection';
|
11
|
+
export const cssPrefix = 'selection-tool-';
|
12
|
+
// {@inheritDoc SelectionTool!}
|
13
|
+
export default class SelectionTool extends BaseTool {
|
14
|
+
constructor(editor, description) {
|
15
|
+
super(editor.notifier, description);
|
16
|
+
this.editor = editor;
|
17
|
+
this.lastEvtTarget = null;
|
18
|
+
this.selectionBoxHandlingEvt = false;
|
19
|
+
this.handleOverlay = document.createElement('div');
|
20
|
+
editor.createHTMLOverlay(this.handleOverlay);
|
21
|
+
this.handleOverlay.style.display = 'none';
|
22
|
+
this.handleOverlay.classList.add('handleOverlay');
|
23
|
+
editor.notifier.on(EditorEventType.ViewportChanged, _data => {
|
24
|
+
var _a;
|
25
|
+
(_a = this.selectionBox) === null || _a === void 0 ? void 0 : _a.updateUI();
|
26
|
+
});
|
27
|
+
this.editor.handleKeyEventsFrom(this.handleOverlay);
|
28
|
+
this.editor.handlePointerEventsFrom(this.handleOverlay, (eventName, htmlEvent) => {
|
29
|
+
if (eventName === 'pointerdown') {
|
30
|
+
this.lastEvtTarget = htmlEvent.target;
|
31
|
+
}
|
32
|
+
return true;
|
33
|
+
});
|
34
|
+
}
|
35
|
+
makeSelectionBox(selectionStartPos) {
|
36
|
+
this.prevSelectionBox = this.selectionBox;
|
37
|
+
this.selectionBox = new Selection(selectionStartPos, this.editor);
|
38
|
+
// Remove any previous selection rects
|
39
|
+
this.handleOverlay.replaceChildren();
|
40
|
+
this.selectionBox.addTo(this.handleOverlay);
|
41
|
+
}
|
42
|
+
onPointerDown(event) {
|
43
|
+
var _a;
|
44
|
+
if (event.allPointers.length === 1 && event.current.isPrimary) {
|
45
|
+
if (this.lastEvtTarget && ((_a = this.selectionBox) === null || _a === void 0 ? void 0 : _a.onDragStart(event.current, this.lastEvtTarget))) {
|
46
|
+
this.selectionBoxHandlingEvt = true;
|
47
|
+
}
|
48
|
+
else {
|
49
|
+
this.makeSelectionBox(event.current.canvasPos);
|
50
|
+
}
|
51
|
+
return true;
|
52
|
+
}
|
53
|
+
return false;
|
54
|
+
}
|
55
|
+
onPointerMove(event) {
|
56
|
+
if (!this.selectionBox)
|
57
|
+
return;
|
58
|
+
if (this.selectionBoxHandlingEvt) {
|
59
|
+
this.selectionBox.onDragUpdate(event.current);
|
60
|
+
}
|
61
|
+
else {
|
62
|
+
this.selectionBox.setToPoint(event.current.canvasPos);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
onSelectionUpdated() {
|
66
|
+
var _a, _b;
|
67
|
+
// Note that the selection has changed
|
68
|
+
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|
69
|
+
kind: EditorEventType.ToolUpdated,
|
70
|
+
tool: this,
|
71
|
+
});
|
72
|
+
const selectedItemCount = (_b = (_a = this.selectionBox) === null || _a === void 0 ? void 0 : _a.getSelectedItemCount()) !== null && _b !== void 0 ? _b : 0;
|
73
|
+
if (selectedItemCount > 0) {
|
74
|
+
this.editor.announceForAccessibility(this.editor.localization.selectedElements(selectedItemCount));
|
75
|
+
this.zoomToSelection();
|
76
|
+
}
|
77
|
+
else if (this.selectionBox) {
|
78
|
+
this.selectionBox.cancelSelection();
|
79
|
+
this.prevSelectionBox = this.selectionBox;
|
80
|
+
this.selectionBox = null;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
onGestureEnd() {
|
84
|
+
this.lastEvtTarget = null;
|
85
|
+
if (!this.selectionBox)
|
86
|
+
return;
|
87
|
+
if (!this.selectionBoxHandlingEvt) {
|
88
|
+
// Expand/shrink the selection rectangle, if applicable
|
89
|
+
this.selectionBox.resolveToObjects();
|
90
|
+
this.onSelectionUpdated();
|
91
|
+
}
|
92
|
+
else {
|
93
|
+
this.selectionBox.onDragEnd();
|
94
|
+
}
|
95
|
+
this.selectionBoxHandlingEvt = false;
|
96
|
+
}
|
97
|
+
zoomToSelection() {
|
98
|
+
if (this.selectionBox) {
|
99
|
+
const selectionRect = this.selectionBox.region;
|
100
|
+
this.editor.dispatchNoAnnounce(this.editor.viewport.zoomTo(selectionRect, false), false);
|
101
|
+
}
|
102
|
+
}
|
103
|
+
onPointerUp(event) {
|
104
|
+
if (!this.selectionBox)
|
105
|
+
return;
|
106
|
+
this.selectionBox.setToPoint(event.current.canvasPos);
|
107
|
+
this.onGestureEnd();
|
108
|
+
}
|
109
|
+
onGestureCancel() {
|
110
|
+
var _a, _b, _c;
|
111
|
+
if (this.selectionBoxHandlingEvt) {
|
112
|
+
(_a = this.selectionBox) === null || _a === void 0 ? void 0 : _a.onDragCancel();
|
113
|
+
}
|
114
|
+
else {
|
115
|
+
// Revert to the previous selection, if any.
|
116
|
+
(_b = this.selectionBox) === null || _b === void 0 ? void 0 : _b.cancelSelection();
|
117
|
+
this.selectionBox = this.prevSelectionBox;
|
118
|
+
(_c = this.selectionBox) === null || _c === void 0 ? void 0 : _c.addTo(this.handleOverlay);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
onKeyPress(event) {
|
122
|
+
let rotationSteps = 0;
|
123
|
+
let xTranslateSteps = 0;
|
124
|
+
let yTranslateSteps = 0;
|
125
|
+
let xScaleSteps = 0;
|
126
|
+
let yScaleSteps = 0;
|
127
|
+
switch (event.key) {
|
128
|
+
case 'a':
|
129
|
+
case 'h':
|
130
|
+
case 'ArrowLeft':
|
131
|
+
xTranslateSteps -= 1;
|
132
|
+
break;
|
133
|
+
case 'd':
|
134
|
+
case 'l':
|
135
|
+
case 'ArrowRight':
|
136
|
+
xTranslateSteps += 1;
|
137
|
+
break;
|
138
|
+
case 'q':
|
139
|
+
case 'k':
|
140
|
+
case 'ArrowUp':
|
141
|
+
yTranslateSteps -= 1;
|
142
|
+
break;
|
143
|
+
case 'e':
|
144
|
+
case 'j':
|
145
|
+
case 'ArrowDown':
|
146
|
+
yTranslateSteps += 1;
|
147
|
+
break;
|
148
|
+
case 'r':
|
149
|
+
rotationSteps += 1;
|
150
|
+
break;
|
151
|
+
case 'R':
|
152
|
+
rotationSteps -= 1;
|
153
|
+
break;
|
154
|
+
case 'i':
|
155
|
+
xScaleSteps -= 1;
|
156
|
+
break;
|
157
|
+
case 'I':
|
158
|
+
xScaleSteps += 1;
|
159
|
+
break;
|
160
|
+
case 'o':
|
161
|
+
yScaleSteps -= 1;
|
162
|
+
break;
|
163
|
+
case 'O':
|
164
|
+
yScaleSteps += 1;
|
165
|
+
break;
|
166
|
+
}
|
167
|
+
let handled = xTranslateSteps !== 0
|
168
|
+
|| yTranslateSteps !== 0
|
169
|
+
|| rotationSteps !== 0
|
170
|
+
|| xScaleSteps !== 0
|
171
|
+
|| yScaleSteps !== 0;
|
172
|
+
if (!this.selectionBox) {
|
173
|
+
handled = false;
|
174
|
+
}
|
175
|
+
else if (handled) {
|
176
|
+
const translateStepSize = 10 * this.editor.viewport.getSizeOfPixelOnCanvas();
|
177
|
+
const rotateStepSize = Math.PI / 8;
|
178
|
+
const scaleStepSize = 5 / 4;
|
179
|
+
const region = this.selectionBox.region;
|
180
|
+
const scaleFactor = Vec2.of(Math.pow(scaleStepSize, xScaleSteps), Math.pow(scaleStepSize, yScaleSteps));
|
181
|
+
const rotationMat = Mat33.zRotation(rotationSteps * rotateStepSize);
|
182
|
+
const roundedRotationMatrix = rotationMat.mapEntries(component => Viewport.roundScaleRatio(component));
|
183
|
+
const regionCenter = this.editor.viewport.roundPoint(region.center);
|
184
|
+
const transform = Mat33.scaling2D(scaleFactor, this.editor.viewport.roundPoint(region.topLeft)).rightMul(Mat33.translation(regionCenter).rightMul(roundedRotationMatrix).rightMul(Mat33.translation(regionCenter.times(-1)))).rightMul(Mat33.translation(this.editor.viewport.roundPoint(Vec2.of(xTranslateSteps, yTranslateSteps).times(translateStepSize))));
|
185
|
+
const oldTransform = this.selectionBox.getTransform();
|
186
|
+
this.selectionBox.setTransform(oldTransform.rightMul(transform));
|
187
|
+
}
|
188
|
+
if (this.selectionBox && !handled && (event.key === 'Delete' || event.key === 'Backspace')) {
|
189
|
+
this.editor.dispatch(this.selectionBox.deleteSelectedObjects());
|
190
|
+
this.clearSelection();
|
191
|
+
handled = true;
|
192
|
+
}
|
193
|
+
return handled;
|
194
|
+
}
|
195
|
+
onKeyUp(evt) {
|
196
|
+
if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
|
197
|
+
this.selectionBox.finalizeTransform();
|
198
|
+
return true;
|
199
|
+
}
|
200
|
+
return false;
|
201
|
+
}
|
202
|
+
onCopy(event) {
|
203
|
+
if (!this.selectionBox) {
|
204
|
+
return false;
|
205
|
+
}
|
206
|
+
const selectedElems = this.selectionBox.getSelectedObjects();
|
207
|
+
const bbox = this.selectionBox.region;
|
208
|
+
if (selectedElems.length === 0) {
|
209
|
+
return false;
|
210
|
+
}
|
211
|
+
const exportViewport = new Viewport(this.editor.notifier);
|
212
|
+
exportViewport.updateScreenSize(Vec2.of(bbox.w, bbox.h));
|
213
|
+
exportViewport.resetTransform(Mat33.translation(bbox.topLeft));
|
214
|
+
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
215
|
+
const exportElem = document.createElementNS(svgNameSpace, 'svg');
|
216
|
+
const sanitize = true;
|
217
|
+
const renderer = new SVGRenderer(exportElem, exportViewport, sanitize);
|
218
|
+
for (const elem of selectedElems) {
|
219
|
+
elem.render(renderer);
|
220
|
+
}
|
221
|
+
event.setData('image/svg+xml', exportElem.outerHTML);
|
222
|
+
return true;
|
223
|
+
}
|
224
|
+
setEnabled(enabled) {
|
225
|
+
super.setEnabled(enabled);
|
226
|
+
// Clear the selection
|
227
|
+
this.handleOverlay.replaceChildren();
|
228
|
+
this.selectionBox = null;
|
229
|
+
this.handleOverlay.style.display = enabled ? 'block' : 'none';
|
230
|
+
if (enabled) {
|
231
|
+
this.handleOverlay.tabIndex = 0;
|
232
|
+
this.handleOverlay.setAttribute('aria-label', this.editor.localization.selectionToolKeyboardShortcuts);
|
233
|
+
}
|
234
|
+
else {
|
235
|
+
this.handleOverlay.tabIndex = -1;
|
236
|
+
}
|
237
|
+
}
|
238
|
+
// Get the object responsible for displaying this' selection.
|
239
|
+
getSelection() {
|
240
|
+
return this.selectionBox;
|
241
|
+
}
|
242
|
+
setSelection(objects) {
|
243
|
+
let bbox = null;
|
244
|
+
for (const object of objects) {
|
245
|
+
if (bbox) {
|
246
|
+
bbox = bbox.union(object.getBBox());
|
247
|
+
}
|
248
|
+
else {
|
249
|
+
bbox = object.getBBox();
|
250
|
+
}
|
251
|
+
}
|
252
|
+
if (!bbox) {
|
253
|
+
return;
|
254
|
+
}
|
255
|
+
this.clearSelection();
|
256
|
+
if (!this.selectionBox) {
|
257
|
+
this.makeSelectionBox(bbox.topLeft);
|
258
|
+
}
|
259
|
+
this.selectionBox.setSelectedObjects(objects, bbox);
|
260
|
+
this.onSelectionUpdated();
|
261
|
+
}
|
262
|
+
clearSelection() {
|
263
|
+
this.handleOverlay.replaceChildren();
|
264
|
+
this.prevSelectionBox = this.selectionBox;
|
265
|
+
this.selectionBox = null;
|
266
|
+
this.onSelectionUpdated();
|
267
|
+
}
|
268
|
+
}
|
269
|
+
SelectionTool.handleableKeys = [
|
270
|
+
'a', 'h', 'ArrowLeft',
|
271
|
+
'd', 'l', 'ArrowRight',
|
272
|
+
'q', 'k', 'ArrowUp',
|
273
|
+
'e', 'j', 'ArrowDown',
|
274
|
+
'r', 'R',
|
275
|
+
'i', 'I', 'o', 'O',
|
276
|
+
];
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import Vec3 from '../../math/Vec3';
|
3
|
+
import Selection from './Selection';
|
4
|
+
import { ResizeMode } from './types';
|
5
|
+
export declare class DragTransformer {
|
6
|
+
private readonly editor;
|
7
|
+
private selection;
|
8
|
+
private dragStartPoint;
|
9
|
+
constructor(editor: Editor, selection: Selection);
|
10
|
+
onDragStart(startPoint: Vec3): void;
|
11
|
+
onDragUpdate(canvasPos: Vec3): void;
|
12
|
+
onDragEnd(): void;
|
13
|
+
}
|
14
|
+
export declare class ResizeTransformer {
|
15
|
+
private readonly editor;
|
16
|
+
private selection;
|
17
|
+
private mode;
|
18
|
+
private dragStartPoint;
|
19
|
+
constructor(editor: Editor, selection: Selection);
|
20
|
+
onDragStart(startPoint: Vec3, mode: ResizeMode): void;
|
21
|
+
onDragUpdate(canvasPos: Vec3): void;
|
22
|
+
onDragEnd(): void;
|
23
|
+
}
|
24
|
+
export declare class RotateTransformer {
|
25
|
+
private readonly editor;
|
26
|
+
private selection;
|
27
|
+
private startAngle;
|
28
|
+
constructor(editor: Editor, selection: Selection);
|
29
|
+
private getAngle;
|
30
|
+
private roundAngle;
|
31
|
+
onDragStart(startPoint: Vec3): void;
|
32
|
+
onDragUpdate(canvasPos: Vec3): void;
|
33
|
+
onDragEnd(): void;
|
34
|
+
}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import Mat33 from '../../math/Mat33';
|
2
|
+
import { Vec2 } from '../../math/Vec2';
|
3
|
+
import Viewport from '../../Viewport';
|
4
|
+
import { ResizeMode } from './types';
|
5
|
+
export class DragTransformer {
|
6
|
+
constructor(editor, selection) {
|
7
|
+
this.editor = editor;
|
8
|
+
this.selection = selection;
|
9
|
+
}
|
10
|
+
onDragStart(startPoint) {
|
11
|
+
this.selection.setTransform(Mat33.identity);
|
12
|
+
this.dragStartPoint = startPoint;
|
13
|
+
}
|
14
|
+
onDragUpdate(canvasPos) {
|
15
|
+
const delta = this.editor.viewport.roundPoint(canvasPos.minus(this.dragStartPoint));
|
16
|
+
this.selection.setTransform(Mat33.translation(delta));
|
17
|
+
}
|
18
|
+
onDragEnd() {
|
19
|
+
this.selection.finalizeTransform();
|
20
|
+
}
|
21
|
+
}
|
22
|
+
export class ResizeTransformer {
|
23
|
+
constructor(editor, selection) {
|
24
|
+
this.editor = editor;
|
25
|
+
this.selection = selection;
|
26
|
+
this.mode = ResizeMode.Both;
|
27
|
+
}
|
28
|
+
onDragStart(startPoint, mode) {
|
29
|
+
this.selection.setTransform(Mat33.identity);
|
30
|
+
this.mode = mode;
|
31
|
+
this.dragStartPoint = startPoint;
|
32
|
+
}
|
33
|
+
onDragUpdate(canvasPos) {
|
34
|
+
const canvasDelta = canvasPos.minus(this.dragStartPoint);
|
35
|
+
const origWidth = this.selection.preTransformRegion.width;
|
36
|
+
const origHeight = this.selection.preTransformRegion.height;
|
37
|
+
let scale = Vec2.of(1, 1);
|
38
|
+
if (this.mode === ResizeMode.HorizontalOnly) {
|
39
|
+
const newWidth = origWidth + canvasDelta.x;
|
40
|
+
scale = Vec2.of(newWidth / origWidth, scale.y);
|
41
|
+
}
|
42
|
+
if (this.mode === ResizeMode.VerticalOnly) {
|
43
|
+
const newHeight = origHeight + canvasDelta.y;
|
44
|
+
scale = Vec2.of(scale.x, newHeight / origHeight);
|
45
|
+
}
|
46
|
+
if (this.mode === ResizeMode.Both) {
|
47
|
+
const delta = Math.abs(canvasDelta.x) > Math.abs(canvasDelta.y) ? canvasDelta.x : canvasDelta.y;
|
48
|
+
const newWidth = origWidth + delta;
|
49
|
+
scale = Vec2.of(newWidth / origWidth, newWidth / origWidth);
|
50
|
+
}
|
51
|
+
// Round: If this isn't done, scaling can create numbers with long decimal representations.
|
52
|
+
// long decimal representations => large file sizes.
|
53
|
+
scale = scale.map(component => Viewport.roundScaleRatio(component, 2));
|
54
|
+
if (scale.x > 0 && scale.y > 0) {
|
55
|
+
const origin = this.editor.viewport.roundPoint(this.selection.preTransformRegion.topLeft);
|
56
|
+
this.selection.setTransform(Mat33.scaling2D(scale, origin));
|
57
|
+
}
|
58
|
+
}
|
59
|
+
onDragEnd() {
|
60
|
+
this.selection.finalizeTransform();
|
61
|
+
}
|
62
|
+
}
|
63
|
+
export class RotateTransformer {
|
64
|
+
constructor(editor, selection) {
|
65
|
+
this.editor = editor;
|
66
|
+
this.selection = selection;
|
67
|
+
this.startAngle = 0;
|
68
|
+
}
|
69
|
+
getAngle(canvasPoint) {
|
70
|
+
const selectionCenter = this.selection.preTransformRegion.center;
|
71
|
+
const offset = canvasPoint.minus(selectionCenter);
|
72
|
+
return offset.angle();
|
73
|
+
}
|
74
|
+
roundAngle(angle) {
|
75
|
+
// Round angles to the nearest 16th of a turn
|
76
|
+
const roundingFactor = 16 / 2 / Math.PI;
|
77
|
+
return Math.round(angle * roundingFactor) / roundingFactor;
|
78
|
+
}
|
79
|
+
onDragStart(startPoint) {
|
80
|
+
this.selection.setTransform(Mat33.identity);
|
81
|
+
this.startAngle = this.getAngle(startPoint);
|
82
|
+
}
|
83
|
+
onDragUpdate(canvasPos) {
|
84
|
+
const targetRotation = this.roundAngle(this.getAngle(canvasPos) - this.startAngle);
|
85
|
+
// Transform in canvas space
|
86
|
+
const canvasSelCenter = this.editor.viewport.roundPoint(this.selection.preTransformRegion.center);
|
87
|
+
const unrounded = Mat33.zRotation(targetRotation);
|
88
|
+
const roundedRotationTransform = unrounded.mapEntries(entry => Viewport.roundScaleRatio(entry));
|
89
|
+
const fullRoundedTransform = Mat33
|
90
|
+
.translation(canvasSelCenter)
|
91
|
+
.rightMul(roundedRotationTransform)
|
92
|
+
.rightMul(Mat33.translation(canvasSelCenter.times(-1)));
|
93
|
+
this.selection.setTransform(fullRoundedTransform);
|
94
|
+
}
|
95
|
+
onDragEnd() {
|
96
|
+
this.selection.finalizeTransform();
|
97
|
+
}
|
98
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
export var ResizeMode;
|
2
|
+
(function (ResizeMode) {
|
3
|
+
ResizeMode[ResizeMode["Both"] = 0] = "Both";
|
4
|
+
ResizeMode[ResizeMode["HorizontalOnly"] = 1] = "HorizontalOnly";
|
5
|
+
ResizeMode[ResizeMode["VerticalOnly"] = 2] = "VerticalOnly";
|
6
|
+
})(ResizeMode || (ResizeMode = {}));
|
7
|
+
export var TransformMode;
|
8
|
+
(function (TransformMode) {
|
9
|
+
TransformMode[TransformMode["Snap"] = 0] = "Snap";
|
10
|
+
TransformMode[TransformMode["NoSnap"] = 1] = "NoSnap";
|
11
|
+
})(TransformMode || (TransformMode = {}));
|
@@ -3,12 +3,13 @@ import PanZoom, { PanZoomMode } from './PanZoom';
|
|
3
3
|
import Pen from './Pen';
|
4
4
|
import ToolEnabledGroup from './ToolEnabledGroup';
|
5
5
|
import Eraser from './Eraser';
|
6
|
-
import SelectionTool from './SelectionTool';
|
6
|
+
import SelectionTool from './SelectionTool/SelectionTool';
|
7
7
|
import Color4 from '../Color4';
|
8
8
|
import UndoRedoShortcut from './UndoRedoShortcut';
|
9
9
|
import TextTool from './TextTool';
|
10
10
|
import PipetteTool from './PipetteTool';
|
11
11
|
import ToolSwitcherShortcut from './ToolSwitcherShortcut';
|
12
|
+
import PasteHandler from './PasteHandler';
|
12
13
|
export default class ToolController {
|
13
14
|
/** @internal */
|
14
15
|
constructor(editor, localization) {
|
@@ -35,6 +36,7 @@ export default class ToolController {
|
|
35
36
|
keyboardPanZoomTool,
|
36
37
|
new UndoRedoShortcut(editor),
|
37
38
|
new ToolSwitcherShortcut(editor),
|
39
|
+
new PasteHandler(editor),
|
38
40
|
];
|
39
41
|
primaryTools.forEach(tool => tool.setToolGroup(primaryToolGroup));
|
40
42
|
panZoomTool.setEnabled(true);
|
@@ -97,42 +99,49 @@ export default class ToolController {
|
|
97
99
|
this.activeTool = null;
|
98
100
|
handled = true;
|
99
101
|
}
|
100
|
-
else if (event.kind === InputEvtType.
|
101
|
-
|
102
|
-
|
103
|
-
|
102
|
+
else if (event.kind === InputEvtType.PointerMoveEvt) {
|
103
|
+
if (this.activeTool !== null) {
|
104
|
+
this.activeTool.onPointerMove(event);
|
105
|
+
handled = true;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
else if (event.kind === InputEvtType.GestureCancelEvt) {
|
109
|
+
if (this.activeTool !== null) {
|
110
|
+
this.activeTool.onGestureCancel();
|
111
|
+
this.activeTool = null;
|
112
|
+
}
|
113
|
+
}
|
114
|
+
else {
|
115
|
+
let allCasesHandledGuard;
|
104
116
|
for (const tool of this.tools) {
|
105
117
|
if (!tool.isEnabled()) {
|
106
118
|
continue;
|
107
119
|
}
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
120
|
+
switch (event.kind) {
|
121
|
+
case InputEvtType.KeyPressEvent:
|
122
|
+
handled = tool.onKeyPress(event);
|
123
|
+
break;
|
124
|
+
case InputEvtType.KeyUpEvent:
|
125
|
+
handled = tool.onKeyUp(event);
|
126
|
+
break;
|
127
|
+
case InputEvtType.WheelEvt:
|
128
|
+
handled = tool.onWheel(event);
|
129
|
+
break;
|
130
|
+
case InputEvtType.CopyEvent:
|
131
|
+
handled = tool.onCopy(event);
|
132
|
+
break;
|
133
|
+
case InputEvtType.PasteEvent:
|
134
|
+
handled = tool.onPaste(event);
|
135
|
+
break;
|
136
|
+
default:
|
137
|
+
allCasesHandledGuard = event;
|
138
|
+
return allCasesHandledGuard;
|
139
|
+
}
|
112
140
|
if (handled) {
|
113
141
|
break;
|
114
142
|
}
|
115
143
|
}
|
116
144
|
}
|
117
|
-
else if (this.activeTool !== null) {
|
118
|
-
let allCasesHandledGuard;
|
119
|
-
switch (event.kind) {
|
120
|
-
case InputEvtType.PointerMoveEvt:
|
121
|
-
this.activeTool.onPointerMove(event);
|
122
|
-
break;
|
123
|
-
case InputEvtType.GestureCancelEvt:
|
124
|
-
this.activeTool.onGestureCancel();
|
125
|
-
this.activeTool = null;
|
126
|
-
break;
|
127
|
-
default:
|
128
|
-
allCasesHandledGuard = event;
|
129
|
-
return allCasesHandledGuard;
|
130
|
-
}
|
131
|
-
handled = true;
|
132
|
-
}
|
133
|
-
else {
|
134
|
-
handled = false;
|
135
|
-
}
|
136
145
|
return handled;
|
137
146
|
}
|
138
147
|
getMatchingTools(type) {
|
package/dist/src/tools/lib.d.ts
CHANGED
@@ -9,5 +9,6 @@ export { default as ToolSwitcherShortcut } from './ToolSwitcherShortcut';
|
|
9
9
|
export { default as PanZoomTool, PanZoomMode } from './PanZoom';
|
10
10
|
export { default as PenTool, PenStyle } from './Pen';
|
11
11
|
export { default as TextTool } from './TextTool';
|
12
|
-
export { default as SelectionTool } from './SelectionTool';
|
12
|
+
export { default as SelectionTool } from './SelectionTool/SelectionTool';
|
13
13
|
export { default as EraserTool } from './Eraser';
|
14
|
+
export { default as PasteHandler } from './PasteHandler';
|
package/dist/src/tools/lib.js
CHANGED
@@ -9,5 +9,6 @@ export { default as ToolSwitcherShortcut } from './ToolSwitcherShortcut';
|
|
9
9
|
export { default as PanZoomTool, PanZoomMode } from './PanZoom';
|
10
10
|
export { default as PenTool } from './Pen';
|
11
11
|
export { default as TextTool } from './TextTool';
|
12
|
-
export { default as SelectionTool } from './SelectionTool';
|
12
|
+
export { default as SelectionTool } from './SelectionTool/SelectionTool';
|
13
13
|
export { default as EraserTool } from './Eraser';
|
14
|
+
export { default as PasteHandler } from './PasteHandler';
|
@@ -11,6 +11,9 @@ export interface ToolLocalization {
|
|
11
11
|
textTool: string;
|
12
12
|
enterTextToInsert: string;
|
13
13
|
changeTool: string;
|
14
|
+
pasteHandler: string;
|
15
|
+
copied: (count: number, description: string) => string;
|
16
|
+
pasted: (count: number, description: string) => string;
|
14
17
|
toolEnabledAnnouncement: (toolName: string) => string;
|
15
18
|
toolDisabledAnnouncement: (toolName: string) => string;
|
16
19
|
}
|
@@ -11,6 +11,9 @@ export const defaultToolLocalization = {
|
|
11
11
|
textTool: 'Text',
|
12
12
|
enterTextToInsert: 'Text to insert',
|
13
13
|
changeTool: 'Change tool',
|
14
|
+
pasteHandler: 'Copy paste handler',
|
15
|
+
copied: (count, description) => `Copied ${count} ${description}`,
|
16
|
+
pasted: (count, description) => `Pasted ${count} ${description}`,
|
14
17
|
toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
|
15
18
|
toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
|
16
19
|
};
|
package/dist/src/types.d.ts
CHANGED
@@ -8,7 +8,7 @@ import Rect2 from './math/Rect2';
|
|
8
8
|
import Pointer from './Pointer';
|
9
9
|
import Color4 from './Color4';
|
10
10
|
import Command from './commands/Command';
|
11
|
-
import
|
11
|
+
import BaseWidget from './toolbar/widgets/BaseWidget';
|
12
12
|
export interface PointerEvtListener {
|
13
13
|
onPointerDown(event: PointerEvt): boolean;
|
14
14
|
onPointerMove(event: PointerEvt): void;
|
@@ -22,7 +22,9 @@ export declare enum InputEvtType {
|
|
22
22
|
GestureCancelEvt = 3,
|
23
23
|
WheelEvt = 4,
|
24
24
|
KeyPressEvent = 5,
|
25
|
-
KeyUpEvent = 6
|
25
|
+
KeyUpEvent = 6,
|
26
|
+
CopyEvent = 7,
|
27
|
+
PasteEvent = 8
|
26
28
|
}
|
27
29
|
export interface WheelEvt {
|
28
30
|
readonly kind: InputEvtType.WheelEvt;
|
@@ -39,6 +41,15 @@ export interface KeyUpEvent {
|
|
39
41
|
readonly key: string;
|
40
42
|
readonly ctrlKey: boolean;
|
41
43
|
}
|
44
|
+
export interface CopyEvent {
|
45
|
+
readonly kind: InputEvtType.CopyEvent;
|
46
|
+
setData(mime: string, data: string): void;
|
47
|
+
}
|
48
|
+
export interface PasteEvent {
|
49
|
+
readonly kind: InputEvtType.PasteEvent;
|
50
|
+
readonly data: string;
|
51
|
+
readonly mime: string;
|
52
|
+
}
|
42
53
|
export interface GestureCancelEvt {
|
43
54
|
readonly kind: InputEvtType.GestureCancelEvt;
|
44
55
|
}
|
@@ -56,7 +67,7 @@ export interface PointerUpEvt extends PointerEvtBase {
|
|
56
67
|
readonly kind: InputEvtType.PointerUpEvt;
|
57
68
|
}
|
58
69
|
export declare type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
|
59
|
-
export declare type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt;
|
70
|
+
export declare type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt | CopyEvent | PasteEvent;
|
60
71
|
export declare type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;
|
61
72
|
export declare enum EditorEventType {
|
62
73
|
ToolEnabled = 0,
|
package/dist/src/types.js
CHANGED
@@ -8,6 +8,8 @@ export var InputEvtType;
|
|
8
8
|
InputEvtType[InputEvtType["WheelEvt"] = 4] = "WheelEvt";
|
9
9
|
InputEvtType[InputEvtType["KeyPressEvent"] = 5] = "KeyPressEvent";
|
10
10
|
InputEvtType[InputEvtType["KeyUpEvent"] = 6] = "KeyUpEvent";
|
11
|
+
InputEvtType[InputEvtType["CopyEvent"] = 7] = "CopyEvent";
|
12
|
+
InputEvtType[InputEvtType["PasteEvent"] = 8] = "PasteEvent";
|
11
13
|
})(InputEvtType || (InputEvtType = {}));
|
12
14
|
export var EditorEventType;
|
13
15
|
(function (EditorEventType) {
|
package/package.json
CHANGED