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.
Files changed (132) hide show
  1. package/.github/ISSUE_TEMPLATE/translation.md +4 -1
  2. package/CHANGELOG.md +16 -0
  3. package/README.md +1 -3
  4. package/dist/bundle.js +1 -1
  5. package/dist/src/Editor.d.ts +15 -1
  6. package/dist/src/Editor.js +221 -78
  7. package/dist/src/EditorImage.js +4 -1
  8. package/dist/src/Pointer.d.ts +1 -1
  9. package/dist/src/Pointer.js +8 -3
  10. package/dist/src/SVGLoader.d.ts +4 -1
  11. package/dist/src/SVGLoader.js +78 -33
  12. package/dist/src/UndoRedoHistory.d.ts +1 -0
  13. package/dist/src/UndoRedoHistory.js +6 -0
  14. package/dist/src/Viewport.d.ts +2 -0
  15. package/dist/src/Viewport.js +26 -5
  16. package/dist/src/commands/lib.d.ts +2 -1
  17. package/dist/src/commands/lib.js +2 -1
  18. package/dist/src/commands/localization.d.ts +1 -0
  19. package/dist/src/commands/localization.js +1 -0
  20. package/dist/src/commands/uniteCommands.d.ts +4 -0
  21. package/dist/src/commands/uniteCommands.js +105 -0
  22. package/dist/src/components/AbstractComponent.d.ts +2 -0
  23. package/dist/src/components/AbstractComponent.js +41 -5
  24. package/dist/src/components/ImageComponent.d.ts +27 -0
  25. package/dist/src/components/ImageComponent.js +129 -0
  26. package/dist/src/components/builders/FreehandLineBuilder.js +2 -2
  27. package/dist/src/components/lib.d.ts +4 -2
  28. package/dist/src/components/lib.js +4 -2
  29. package/dist/src/components/localization.d.ts +2 -0
  30. package/dist/src/components/localization.js +2 -0
  31. package/dist/src/language/assertions.d.ts +1 -0
  32. package/dist/src/language/assertions.js +5 -0
  33. package/dist/src/math/LineSegment2.d.ts +2 -0
  34. package/dist/src/math/LineSegment2.js +3 -0
  35. package/dist/src/math/Mat33.d.ts +38 -2
  36. package/dist/src/math/Mat33.js +30 -1
  37. package/dist/src/math/Path.d.ts +1 -1
  38. package/dist/src/math/Path.js +10 -8
  39. package/dist/src/math/Vec3.d.ts +11 -1
  40. package/dist/src/math/Vec3.js +15 -0
  41. package/dist/src/math/rounding.d.ts +1 -0
  42. package/dist/src/math/rounding.js +13 -6
  43. package/dist/src/rendering/localization.d.ts +3 -0
  44. package/dist/src/rendering/localization.js +3 -0
  45. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -0
  46. package/dist/src/rendering/renderers/AbstractRenderer.js +2 -1
  47. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
  48. package/dist/src/rendering/renderers/CanvasRenderer.js +7 -0
  49. package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -1
  50. package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
  51. package/dist/src/rendering/renderers/SVGRenderer.d.ts +5 -2
  52. package/dist/src/rendering/renderers/SVGRenderer.js +45 -20
  53. package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +3 -1
  54. package/dist/src/rendering/renderers/TextOnlyRenderer.js +8 -1
  55. package/dist/src/toolbar/HTMLToolbar.js +5 -4
  56. package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
  57. package/dist/src/tools/BaseTool.d.ts +3 -1
  58. package/dist/src/tools/BaseTool.js +6 -0
  59. package/dist/src/tools/PasteHandler.d.ts +16 -0
  60. package/dist/src/tools/PasteHandler.js +144 -0
  61. package/dist/src/tools/Pen.js +1 -1
  62. package/dist/src/tools/SelectionTool/Selection.d.ts +54 -0
  63. package/dist/src/tools/SelectionTool/Selection.js +337 -0
  64. package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +35 -0
  65. package/dist/src/tools/SelectionTool/SelectionHandle.js +75 -0
  66. package/dist/src/tools/SelectionTool/SelectionTool.d.ts +31 -0
  67. package/dist/src/tools/SelectionTool/SelectionTool.js +276 -0
  68. package/dist/src/tools/SelectionTool/TransformMode.d.ts +34 -0
  69. package/dist/src/tools/SelectionTool/TransformMode.js +98 -0
  70. package/dist/src/tools/SelectionTool/types.d.ts +9 -0
  71. package/dist/src/tools/SelectionTool/types.js +11 -0
  72. package/dist/src/tools/ToolController.js +37 -28
  73. package/dist/src/tools/lib.d.ts +2 -1
  74. package/dist/src/tools/lib.js +2 -1
  75. package/dist/src/tools/localization.d.ts +3 -0
  76. package/dist/src/tools/localization.js +3 -0
  77. package/dist/src/types.d.ts +14 -3
  78. package/dist/src/types.js +2 -0
  79. package/package.json +1 -1
  80. package/src/Editor.css +1 -0
  81. package/src/Editor.ts +275 -109
  82. package/src/EditorImage.ts +7 -1
  83. package/src/Pointer.ts +8 -3
  84. package/src/SVGLoader.ts +90 -36
  85. package/src/UndoRedoHistory.test.ts +33 -0
  86. package/src/UndoRedoHistory.ts +8 -0
  87. package/src/Viewport.ts +30 -6
  88. package/src/commands/lib.ts +2 -0
  89. package/src/commands/localization.ts +2 -0
  90. package/src/commands/uniteCommands.test.ts +23 -0
  91. package/src/commands/uniteCommands.ts +121 -0
  92. package/src/components/AbstractComponent.ts +53 -11
  93. package/src/components/ImageComponent.ts +149 -0
  94. package/src/components/Text.ts +2 -6
  95. package/src/components/builders/FreehandLineBuilder.ts +2 -2
  96. package/src/components/lib.ts +7 -2
  97. package/src/components/localization.ts +4 -0
  98. package/src/language/assertions.ts +6 -0
  99. package/src/math/LineSegment2.test.ts +9 -0
  100. package/src/math/LineSegment2.ts +5 -0
  101. package/src/math/Mat33.test.ts +14 -0
  102. package/src/math/Mat33.ts +43 -2
  103. package/src/math/Path.toString.test.ts +12 -1
  104. package/src/math/Path.ts +11 -9
  105. package/src/math/Vec3.ts +22 -1
  106. package/src/math/rounding.test.ts +30 -5
  107. package/src/math/rounding.ts +16 -7
  108. package/src/rendering/localization.ts +6 -0
  109. package/src/rendering/renderers/AbstractRenderer.ts +19 -2
  110. package/src/rendering/renderers/CanvasRenderer.ts +10 -1
  111. package/src/rendering/renderers/DummyRenderer.ts +6 -1
  112. package/src/rendering/renderers/SVGRenderer.ts +50 -21
  113. package/src/rendering/renderers/TextOnlyRenderer.ts +10 -2
  114. package/src/toolbar/HTMLToolbar.ts +5 -4
  115. package/src/toolbar/widgets/SelectionToolWidget.ts +1 -1
  116. package/src/tools/BaseTool.ts +9 -1
  117. package/src/tools/PasteHandler.ts +159 -0
  118. package/src/tools/Pen.ts +1 -1
  119. package/src/tools/SelectionTool/Selection.ts +455 -0
  120. package/src/tools/SelectionTool/SelectionHandle.ts +99 -0
  121. package/src/tools/SelectionTool/SelectionTool.css +22 -0
  122. package/src/tools/{SelectionTool.test.ts → SelectionTool/SelectionTool.test.ts} +21 -21
  123. package/src/tools/SelectionTool/SelectionTool.ts +335 -0
  124. package/src/tools/SelectionTool/TransformMode.ts +114 -0
  125. package/src/tools/SelectionTool/types.ts +11 -0
  126. package/src/tools/ToolController.ts +52 -45
  127. package/src/tools/lib.ts +2 -1
  128. package/src/tools/localization.ts +8 -0
  129. package/src/types.ts +17 -3
  130. package/dist/src/tools/SelectionTool.d.ts +0 -59
  131. package/dist/src/tools/SelectionTool.js +0 -589
  132. package/src/tools/SelectionTool.ts +0 -725
package/src/types.ts CHANGED
@@ -10,7 +10,7 @@ import Rect2 from './math/Rect2';
10
10
  import Pointer from './Pointer';
11
11
  import Color4 from './Color4';
12
12
  import Command from './commands/Command';
13
- import { BaseWidget } from './lib';
13
+ import BaseWidget from './toolbar/widgets/BaseWidget';
14
14
 
15
15
 
16
16
  export interface PointerEvtListener {
@@ -35,7 +35,10 @@ export enum InputEvtType {
35
35
 
36
36
  WheelEvt,
37
37
  KeyPressEvent,
38
- KeyUpEvent
38
+ KeyUpEvent,
39
+
40
+ CopyEvent,
41
+ PasteEvent,
39
42
  }
40
43
 
41
44
  // [delta.x] is horizontal scroll,
@@ -59,6 +62,17 @@ export interface KeyUpEvent {
59
62
  readonly ctrlKey: boolean;
60
63
  }
61
64
 
65
+ export interface CopyEvent {
66
+ readonly kind: InputEvtType.CopyEvent;
67
+ setData(mime: string, data: string): void;
68
+ }
69
+
70
+ export interface PasteEvent {
71
+ readonly kind: InputEvtType.PasteEvent;
72
+ readonly data: string;
73
+ readonly mime: string;
74
+ }
75
+
62
76
  // Event triggered when pointer capture is taken by a different [PointerEvtListener].
63
77
  export interface GestureCancelEvt {
64
78
  readonly kind: InputEvtType.GestureCancelEvt;
@@ -82,7 +96,7 @@ export interface PointerUpEvt extends PointerEvtBase {
82
96
  }
83
97
 
84
98
  export type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
85
- export type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt;
99
+ export type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt | CopyEvent | PasteEvent;
86
100
 
87
101
  export type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;
88
102
 
@@ -1,59 +0,0 @@
1
- import Command from '../commands/Command';
2
- import Editor from '../Editor';
3
- import Mat33 from '../math/Mat33';
4
- import Rect2 from '../math/Rect2';
5
- import { Point2, Vec2 } from '../math/Vec2';
6
- import { KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
7
- import BaseTool from './BaseTool';
8
- declare class Selection {
9
- startPoint: Point2;
10
- private editor;
11
- region: Rect2;
12
- private boxRotation;
13
- private backgroundBox;
14
- private rotateCircle;
15
- private selectedElems;
16
- private transform;
17
- private transformationCommands;
18
- constructor(startPoint: Point2, editor: Editor);
19
- handleBackgroundDrag(deltaPosition: Vec2): void;
20
- handleResizeCornerDrag(deltaPosition: Vec2): void;
21
- handleRotateCircleDrag(offset: Vec2): void;
22
- private computeTransformCommands;
23
- transformPreview(transform: Mat33): void;
24
- finalizeTransform(): void;
25
- private static ApplyTransformationCommand;
26
- private previewTransformCmds;
27
- appendBackgroundBoxTo(elem: HTMLElement): void;
28
- setToPoint(point: Point2): void;
29
- cancelSelection(): void;
30
- resolveToObjects(): boolean;
31
- recomputeRegion(): boolean;
32
- getMinCanvasSize(): number;
33
- private recomputeBoxRotation;
34
- getSelectedItemCount(): number;
35
- updateUI(): void;
36
- scrollTo(): void;
37
- deleteSelectedObjects(): Command;
38
- duplicateSelectedObjects(): Command;
39
- }
40
- export default class SelectionTool extends BaseTool {
41
- private editor;
42
- private handleOverlay;
43
- private prevSelectionBox;
44
- private selectionBox;
45
- constructor(editor: Editor, description: string);
46
- onPointerDown(event: PointerEvt): boolean;
47
- onPointerMove(event: PointerEvt): void;
48
- private onGestureEnd;
49
- private zoomToSelection;
50
- onPointerUp(event: PointerEvt): void;
51
- onGestureCancel(): void;
52
- private static handleableKeys;
53
- onKeyPress(event: KeyPressEvent): boolean;
54
- onKeyUp(evt: KeyUpEvent): boolean;
55
- setEnabled(enabled: boolean): void;
56
- getSelection(): Selection | null;
57
- clearSelection(): void;
58
- }
59
- export {};
@@ -1,589 +0,0 @@
1
- // Allows users to select/transform portions of the `EditorImage`.
2
- // With respect to `extend`ing, `SelectionTool` is not stable.
3
- // @packageDocumentation
4
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
- return new (P || (P = Promise))(function (resolve, reject) {
7
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
- step((generator = generator.apply(thisArg, _arguments || [])).next());
11
- });
12
- };
13
- var _a;
14
- import Duplicate from '../commands/Duplicate';
15
- import Erase from '../commands/Erase';
16
- import Mat33 from '../math/Mat33';
17
- import Rect2 from '../math/Rect2';
18
- import { Vec2 } from '../math/Vec2';
19
- import { EditorEventType } from '../types';
20
- import Viewport from '../Viewport';
21
- import BaseTool from './BaseTool';
22
- import SerializableCommand from '../commands/SerializableCommand';
23
- const handleScreenSize = 30;
24
- const styles = `
25
- .handleOverlay {
26
- }
27
-
28
- .handleOverlay > .selectionBox {
29
- position: absolute;
30
- z-index: 0;
31
- transform-origin: center;
32
- }
33
-
34
- .handleOverlay > .selectionBox .draggableBackground {
35
- position: absolute;
36
- top: 0;
37
- left: 0;
38
- right: 0;
39
- bottom: 0;
40
-
41
- background-color: var(--secondary-background-color);
42
- opacity: 0.8;
43
- border: 1px solid var(--primary-background-color);
44
- }
45
-
46
- .handleOverlay .resizeCorner {
47
- width: ${handleScreenSize}px;
48
- height: ${handleScreenSize}px;
49
- margin-right: -${handleScreenSize / 2}px;
50
- margin-bottom: -${handleScreenSize / 2}px;
51
-
52
- position: absolute;
53
- bottom: 0;
54
- right: 0;
55
-
56
- opacity: 0.8;
57
- background-color: var(--primary-background-color);
58
- border: 1px solid var(--primary-foreground-color);
59
- }
60
-
61
- .handleOverlay > .selectionBox .rotateCircleContainer {
62
- position: absolute;
63
- top: 50%;
64
- bottom: 50%;
65
- left: 50%;
66
- right: 50%;
67
- }
68
-
69
- .handleOverlay .rotateCircle {
70
- width: ${handleScreenSize}px;
71
- height: ${handleScreenSize}px;
72
- margin-left: -${handleScreenSize / 2}px;
73
- margin-top: -${handleScreenSize / 2}px;
74
- opacity: 0.8;
75
-
76
- border: 1px solid var(--primary-foreground-color);
77
- background-color: var(--primary-background-color);
78
- border-radius: 100%;
79
- }
80
- `;
81
- const makeDraggable = (element, onDrag, onDragEnd) => {
82
- element.style.touchAction = 'none';
83
- let down = false;
84
- // Work around a Safari bug
85
- element.addEventListener('touchstart', evt => evt.preventDefault());
86
- let lastX;
87
- let lastY;
88
- element.addEventListener('pointerdown', event => {
89
- if (event.isPrimary) {
90
- down = true;
91
- element.setPointerCapture(event.pointerId);
92
- lastX = event.pageX;
93
- lastY = event.pageY;
94
- return true;
95
- }
96
- return false;
97
- });
98
- element.addEventListener('pointermove', event => {
99
- if (event.isPrimary && down) {
100
- // Safari/iOS doesn't seem to support movementX/movementY on pointer events.
101
- // Calculate manually:
102
- const delta = Vec2.of(event.pageX - lastX, event.pageY - lastY);
103
- onDrag(delta, Vec2.of(event.offsetX, event.offsetY));
104
- lastX = event.pageX;
105
- lastY = event.pageY;
106
- return true;
107
- }
108
- return false;
109
- });
110
- const onPointerEnd = (event) => {
111
- if (event.isPrimary) {
112
- down = false;
113
- onDragEnd();
114
- return true;
115
- }
116
- return false;
117
- };
118
- element.addEventListener('pointerup', onPointerEnd);
119
- element.addEventListener('pointercancel', onPointerEnd);
120
- };
121
- // Maximum number of strokes to transform without a re-render.
122
- const updateChunkSize = 100;
123
- // @internal
124
- class Selection {
125
- constructor(startPoint, editor) {
126
- this.startPoint = startPoint;
127
- this.editor = editor;
128
- this.boxRotation = this.editor.viewport.getRotationAngle();
129
- this.selectedElems = [];
130
- this.region = Rect2.bboxOf([startPoint]);
131
- // Create draggable rectangles
132
- this.backgroundBox = document.createElement('div');
133
- const draggableBackground = document.createElement('div');
134
- const resizeCorner = document.createElement('div');
135
- this.rotateCircle = document.createElement('div');
136
- const rotateCircleContainer = document.createElement('div');
137
- this.backgroundBox.classList.add('selectionBox');
138
- draggableBackground.classList.add('draggableBackground');
139
- resizeCorner.classList.add('resizeCorner');
140
- this.rotateCircle.classList.add('rotateCircle');
141
- rotateCircleContainer.classList.add('rotateCircleContainer');
142
- rotateCircleContainer.appendChild(this.rotateCircle);
143
- this.backgroundBox.appendChild(draggableBackground);
144
- this.backgroundBox.appendChild(rotateCircleContainer);
145
- this.backgroundBox.appendChild(resizeCorner);
146
- this.transformationCommands = [];
147
- this.transform = Mat33.identity;
148
- makeDraggable(draggableBackground, (deltaPosition) => {
149
- this.handleBackgroundDrag(deltaPosition);
150
- }, () => this.finalizeTransform());
151
- makeDraggable(resizeCorner, (deltaPosition) => {
152
- this.handleResizeCornerDrag(deltaPosition);
153
- }, () => this.finalizeTransform());
154
- makeDraggable(this.rotateCircle, (_deltaPosition, offset) => {
155
- this.handleRotateCircleDrag(offset);
156
- }, () => this.finalizeTransform());
157
- }
158
- // Note a small change in the position of this' background while dragging
159
- // At the end of a drag, changes should be applied by calling this.finishDragging()
160
- handleBackgroundDrag(deltaPosition) {
161
- // Re-scale the change in position
162
- // (use a Vec3 transform to avoid translating deltaPosition)
163
- deltaPosition = this.editor.viewport.screenToCanvasTransform.transformVec3(deltaPosition);
164
- // Snap position to a multiple of 10 (additional decimal points lead to larger files).
165
- deltaPosition = this.editor.viewport.roundPoint(deltaPosition);
166
- this.transformPreview(Mat33.translation(deltaPosition));
167
- }
168
- handleResizeCornerDrag(deltaPosition) {
169
- deltaPosition = this.editor.viewport.screenToCanvasTransform.transformVec3(deltaPosition);
170
- deltaPosition = this.editor.viewport.roundPoint(deltaPosition);
171
- const oldWidth = this.region.w;
172
- const oldHeight = this.region.h;
173
- const newSize = this.region.size.plus(deltaPosition);
174
- if (newSize.y > 0 && newSize.x > 0) {
175
- const scaleFactor = Vec2.of(newSize.x / oldWidth, newSize.y / oldHeight);
176
- this.transformPreview(Mat33.scaling2D(scaleFactor, this.region.topLeft));
177
- }
178
- }
179
- handleRotateCircleDrag(offset) {
180
- let targetRotation = offset.angle();
181
- targetRotation = targetRotation % (2 * Math.PI);
182
- if (targetRotation < 0) {
183
- targetRotation += 2 * Math.PI;
184
- }
185
- let deltaRotation = (targetRotation - this.boxRotation);
186
- const rotationStep = Math.PI / 12;
187
- if (Math.abs(deltaRotation) < rotationStep || !isFinite(deltaRotation)) {
188
- return;
189
- }
190
- else {
191
- const rotationDirection = Math.sign(deltaRotation);
192
- // Step exactly one rotationStep
193
- deltaRotation = Math.floor(Math.abs(deltaRotation) / rotationStep) * rotationStep;
194
- deltaRotation *= rotationDirection;
195
- }
196
- this.transformPreview(Mat33.zRotation(deltaRotation, this.region.center));
197
- }
198
- computeTransformCommands() {
199
- return this.selectedElems.map(elem => {
200
- return elem.transformBy(this.transform);
201
- });
202
- }
203
- // Applies, previews, but doesn't finalize the given transformation.
204
- transformPreview(transform) {
205
- this.transform = this.transform.rightMul(transform);
206
- const deltaRotation = transform.transformVec3(Vec2.unitX).angle();
207
- transform = transform.rightMul(Mat33.zRotation(-deltaRotation, this.region.center));
208
- this.boxRotation += deltaRotation;
209
- this.boxRotation = this.boxRotation % (2 * Math.PI);
210
- if (this.boxRotation < 0) {
211
- this.boxRotation += 2 * Math.PI;
212
- }
213
- const newSize = transform.transformVec3(this.region.size);
214
- const translation = transform.transformVec2(this.region.topLeft).minus(this.region.topLeft);
215
- this.region = this.region.resizedTo(newSize);
216
- this.region = this.region.translatedBy(translation);
217
- this.previewTransformCmds();
218
- this.scrollTo();
219
- }
220
- // Applies the current transformation to the selection
221
- finalizeTransform() {
222
- this.transformationCommands.forEach(cmd => {
223
- cmd.unapply(this.editor);
224
- });
225
- const fullTransform = this.transform;
226
- const inverseTransform = this.transform.inverse();
227
- const deltaBoxRotation = this.boxRotation;
228
- const currentTransfmCommands = this.computeTransformCommands();
229
- // Reset for the next drag
230
- this.transformationCommands = [];
231
- this.transform = Mat33.identity;
232
- this.region = this.region.transformedBoundingBox(inverseTransform);
233
- // Make the commands undo-able
234
- this.editor.dispatch(new Selection.ApplyTransformationCommand(this, currentTransfmCommands, fullTransform, deltaBoxRotation));
235
- }
236
- // Preview the effects of the current transformation on the selection
237
- previewTransformCmds() {
238
- // Don't render what we're moving if it's likely to be slow.
239
- if (this.selectedElems.length > updateChunkSize) {
240
- this.updateUI();
241
- return;
242
- }
243
- this.transformationCommands.forEach(cmd => cmd.unapply(this.editor));
244
- this.transformationCommands = this.computeTransformCommands();
245
- this.transformationCommands.forEach(cmd => cmd.apply(this.editor));
246
- this.updateUI();
247
- }
248
- appendBackgroundBoxTo(elem) {
249
- if (this.backgroundBox.parentElement) {
250
- this.backgroundBox.remove();
251
- }
252
- elem.appendChild(this.backgroundBox);
253
- }
254
- setToPoint(point) {
255
- this.region = this.region.grownToPoint(point);
256
- this.recomputeBoxRotation();
257
- this.updateUI();
258
- }
259
- cancelSelection() {
260
- if (this.backgroundBox.parentElement) {
261
- this.backgroundBox.remove();
262
- }
263
- this.region = Rect2.empty;
264
- }
265
- // Find the objects corresponding to this in the document,
266
- // select them.
267
- // Returns false iff nothing was selected.
268
- resolveToObjects() {
269
- // Grow the rectangle, if necessary
270
- if (this.region.w === 0 || this.region.h === 0) {
271
- const padding = this.editor.viewport.visibleRect.maxDimension / 100;
272
- this.region = Rect2.bboxOf(this.region.corners, padding);
273
- }
274
- this.selectedElems = this.editor.image.getElementsIntersectingRegion(this.region).filter(elem => {
275
- if (this.region.containsRect(elem.getBBox())) {
276
- return true;
277
- }
278
- // Calculated bounding boxes can be slightly larger than their actual contents' bounding box.
279
- // As such, test with more lines than just this' edges.
280
- const testLines = [];
281
- for (const subregion of this.region.divideIntoGrid(2, 2)) {
282
- testLines.push(...subregion.getEdges());
283
- }
284
- return testLines.some(edge => elem.intersects(edge));
285
- });
286
- // Find the bounding box of all selected elements.
287
- if (!this.recomputeRegion()) {
288
- return false;
289
- }
290
- this.updateUI();
291
- return true;
292
- }
293
- // Recompute this' region from the selected elements. Resets rotation to zero.
294
- // Returns false if the selection is empty.
295
- recomputeRegion() {
296
- const newRegion = this.selectedElems.reduce((accumulator, elem) => {
297
- return (accumulator !== null && accumulator !== void 0 ? accumulator : elem.getBBox()).union(elem.getBBox());
298
- }, null);
299
- if (!newRegion) {
300
- this.cancelSelection();
301
- return false;
302
- }
303
- this.region = newRegion;
304
- const minSize = this.getMinCanvasSize();
305
- if (this.region.w < minSize || this.region.h < minSize) {
306
- // Add padding
307
- const padding = minSize / 2;
308
- this.region = Rect2.bboxOf(this.region.corners, padding);
309
- }
310
- this.recomputeBoxRotation();
311
- return true;
312
- }
313
- getMinCanvasSize() {
314
- const canvasHandleSize = handleScreenSize / this.editor.viewport.getScaleFactor();
315
- return canvasHandleSize * 2;
316
- }
317
- recomputeBoxRotation() {
318
- this.boxRotation = this.editor.viewport.getRotationAngle();
319
- }
320
- getSelectedItemCount() {
321
- return this.selectedElems.length;
322
- }
323
- updateUI() {
324
- if (!this.backgroundBox) {
325
- return;
326
- }
327
- const rightSideDirection = this.region.topRight.minus(this.region.bottomRight);
328
- const topSideDirection = this.region.topLeft.minus(this.region.topRight);
329
- const toScreen = this.editor.viewport.canvasToScreenTransform;
330
- const centerOnScreen = toScreen.transformVec2(this.region.center);
331
- const heightOnScreen = toScreen.transformVec3(rightSideDirection).magnitude();
332
- const widthOnScreen = toScreen.transformVec3(topSideDirection).magnitude();
333
- this.backgroundBox.style.marginLeft = `${centerOnScreen.x - widthOnScreen / 2}px`;
334
- this.backgroundBox.style.marginTop = `${centerOnScreen.y - heightOnScreen / 2}px`;
335
- this.backgroundBox.style.width = `${widthOnScreen}px`;
336
- this.backgroundBox.style.height = `${heightOnScreen}px`;
337
- const rotationDeg = this.boxRotation * 180 / Math.PI;
338
- this.backgroundBox.style.transform = `rotate(${rotationDeg}deg)`;
339
- this.rotateCircle.style.transform = `rotate(${-rotationDeg}deg)`;
340
- }
341
- // Scroll the viewport to this. Does not zoom
342
- scrollTo() {
343
- const viewport = this.editor.viewport;
344
- const visibleRect = viewport.visibleRect;
345
- if (!visibleRect.containsPoint(this.region.center)) {
346
- const closestPoint = visibleRect.getClosestPointOnBoundaryTo(this.region.center);
347
- const delta = this.region.center.minus(closestPoint);
348
- this.editor.dispatchNoAnnounce(Viewport.transformBy(Mat33.translation(delta.times(-1))), false);
349
- }
350
- }
351
- deleteSelectedObjects() {
352
- return new Erase(this.selectedElems);
353
- }
354
- duplicateSelectedObjects() {
355
- return new Duplicate(this.selectedElems);
356
- }
357
- }
358
- _a = Selection;
359
- (() => {
360
- SerializableCommand.register('selection-tool-transform', (json, editor) => {
361
- // The selection box is lost when serializing/deserializing. No need to store box rotation
362
- const guiBoxRotation = 0;
363
- const fullTransform = new Mat33(...json.transform);
364
- const commands = json.commands.map(data => SerializableCommand.deserialize(data, editor));
365
- return new _a.ApplyTransformationCommand(null, commands, fullTransform, guiBoxRotation);
366
- });
367
- })();
368
- Selection.ApplyTransformationCommand = class extends SerializableCommand {
369
- constructor(selection, currentTransfmCommands, fullTransform, deltaBoxRotation) {
370
- super('selection-tool-transform');
371
- this.selection = selection;
372
- this.currentTransfmCommands = currentTransfmCommands;
373
- this.fullTransform = fullTransform;
374
- this.deltaBoxRotation = deltaBoxRotation;
375
- }
376
- apply(editor) {
377
- var _b, _c;
378
- return __awaiter(this, void 0, void 0, function* () {
379
- // Approximate the new selection
380
- if (this.selection) {
381
- this.selection.region = this.selection.region.transformedBoundingBox(this.fullTransform);
382
- this.selection.boxRotation += this.deltaBoxRotation;
383
- this.selection.updateUI();
384
- }
385
- yield editor.asyncApplyCommands(this.currentTransfmCommands, updateChunkSize);
386
- (_b = this.selection) === null || _b === void 0 ? void 0 : _b.recomputeRegion();
387
- (_c = this.selection) === null || _c === void 0 ? void 0 : _c.updateUI();
388
- });
389
- }
390
- unapply(editor) {
391
- var _b, _c;
392
- return __awaiter(this, void 0, void 0, function* () {
393
- if (this.selection) {
394
- this.selection.region = this.selection.region.transformedBoundingBox(this.fullTransform.inverse());
395
- this.selection.boxRotation -= this.deltaBoxRotation;
396
- this.selection.updateUI();
397
- }
398
- yield editor.asyncUnapplyCommands(this.currentTransfmCommands, updateChunkSize);
399
- (_b = this.selection) === null || _b === void 0 ? void 0 : _b.recomputeRegion();
400
- (_c = this.selection) === null || _c === void 0 ? void 0 : _c.updateUI();
401
- });
402
- }
403
- serializeToJSON() {
404
- return {
405
- commands: this.currentTransfmCommands.map(command => command.serialize()),
406
- transform: this.fullTransform.toArray(),
407
- };
408
- }
409
- description(_editor, localizationTable) {
410
- return localizationTable.transformedElements(this.currentTransfmCommands.length);
411
- }
412
- };
413
- // {@inheritDoc SelectionTool!}
414
- export default class SelectionTool extends BaseTool {
415
- constructor(editor, description) {
416
- super(editor.notifier, description);
417
- this.editor = editor;
418
- this.handleOverlay = document.createElement('div');
419
- editor.createHTMLOverlay(this.handleOverlay);
420
- editor.addStyleSheet(styles);
421
- this.handleOverlay.style.display = 'none';
422
- this.handleOverlay.classList.add('handleOverlay');
423
- editor.notifier.on(EditorEventType.ViewportChanged, _data => {
424
- var _b, _c;
425
- (_b = this.selectionBox) === null || _b === void 0 ? void 0 : _b.recomputeRegion();
426
- (_c = this.selectionBox) === null || _c === void 0 ? void 0 : _c.updateUI();
427
- });
428
- this.editor.handleKeyEventsFrom(this.handleOverlay);
429
- }
430
- onPointerDown(event) {
431
- if (event.allPointers.length === 1 && event.current.isPrimary) {
432
- this.prevSelectionBox = this.selectionBox;
433
- this.selectionBox = new Selection(event.current.canvasPos, this.editor);
434
- // Remove any previous selection rects
435
- this.handleOverlay.replaceChildren();
436
- this.selectionBox.appendBackgroundBoxTo(this.handleOverlay);
437
- return true;
438
- }
439
- return false;
440
- }
441
- onPointerMove(event) {
442
- if (!this.selectionBox)
443
- return;
444
- this.selectionBox.setToPoint(event.current.canvasPos);
445
- }
446
- onGestureEnd() {
447
- if (!this.selectionBox)
448
- return;
449
- // Expand/shrink the selection rectangle, if applicable
450
- const hasSelection = this.selectionBox.resolveToObjects();
451
- // Note that the selection has changed
452
- this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
453
- kind: EditorEventType.ToolUpdated,
454
- tool: this,
455
- });
456
- if (hasSelection) {
457
- this.editor.announceForAccessibility(this.editor.localization.selectedElements(this.selectionBox.getSelectedItemCount()));
458
- this.zoomToSelection();
459
- }
460
- }
461
- zoomToSelection() {
462
- if (this.selectionBox) {
463
- const selectionRect = this.selectionBox.region;
464
- this.editor.dispatchNoAnnounce(this.editor.viewport.zoomTo(selectionRect, false), false);
465
- }
466
- }
467
- onPointerUp(event) {
468
- if (!this.selectionBox)
469
- return;
470
- this.selectionBox.setToPoint(event.current.canvasPos);
471
- this.onGestureEnd();
472
- }
473
- onGestureCancel() {
474
- var _b, _c;
475
- // Revert to the previous selection, if any.
476
- (_b = this.selectionBox) === null || _b === void 0 ? void 0 : _b.cancelSelection();
477
- this.selectionBox = this.prevSelectionBox;
478
- (_c = this.selectionBox) === null || _c === void 0 ? void 0 : _c.appendBackgroundBoxTo(this.handleOverlay);
479
- }
480
- onKeyPress(event) {
481
- let rotationSteps = 0;
482
- let xTranslateSteps = 0;
483
- let yTranslateSteps = 0;
484
- let xScaleSteps = 0;
485
- let yScaleSteps = 0;
486
- switch (event.key) {
487
- case 'a':
488
- case 'h':
489
- case 'ArrowLeft':
490
- xTranslateSteps -= 1;
491
- break;
492
- case 'd':
493
- case 'l':
494
- case 'ArrowRight':
495
- xTranslateSteps += 1;
496
- break;
497
- case 'q':
498
- case 'k':
499
- case 'ArrowUp':
500
- yTranslateSteps -= 1;
501
- break;
502
- case 'e':
503
- case 'j':
504
- case 'ArrowDown':
505
- yTranslateSteps += 1;
506
- break;
507
- case 'r':
508
- rotationSteps += 1;
509
- break;
510
- case 'R':
511
- rotationSteps -= 1;
512
- break;
513
- case 'i':
514
- xScaleSteps -= 1;
515
- break;
516
- case 'I':
517
- xScaleSteps += 1;
518
- break;
519
- case 'o':
520
- yScaleSteps -= 1;
521
- break;
522
- case 'O':
523
- yScaleSteps += 1;
524
- break;
525
- }
526
- let handled = xTranslateSteps !== 0
527
- || yTranslateSteps !== 0
528
- || rotationSteps !== 0
529
- || xScaleSteps !== 0
530
- || yScaleSteps !== 0;
531
- if (!this.selectionBox) {
532
- handled = false;
533
- }
534
- else if (handled) {
535
- const translateStepSize = 10 * this.editor.viewport.getSizeOfPixelOnCanvas();
536
- const rotateStepSize = Math.PI / 8;
537
- const scaleStepSize = translateStepSize / 2;
538
- const region = this.selectionBox.region;
539
- const scaledSize = this.selectionBox.region.size.plus(Vec2.of(xScaleSteps, yScaleSteps).times(scaleStepSize));
540
- const transform = Mat33.scaling2D(Vec2.of(
541
- // Don't more-than-half the size of the selection
542
- Math.max(0.5, scaledSize.x / region.size.x), Math.max(0.5, scaledSize.y / region.size.y)), region.topLeft).rightMul(Mat33.zRotation(rotationSteps * rotateStepSize, region.center)).rightMul(Mat33.translation(Vec2.of(xTranslateSteps, yTranslateSteps).times(translateStepSize)));
543
- this.selectionBox.transformPreview(transform);
544
- }
545
- return handled;
546
- }
547
- onKeyUp(evt) {
548
- if (this.selectionBox && SelectionTool.handleableKeys.some(key => key === evt.key)) {
549
- this.selectionBox.finalizeTransform();
550
- return true;
551
- }
552
- return false;
553
- }
554
- setEnabled(enabled) {
555
- super.setEnabled(enabled);
556
- // Clear the selection
557
- this.handleOverlay.replaceChildren();
558
- this.selectionBox = null;
559
- this.handleOverlay.style.display = enabled ? 'block' : 'none';
560
- if (enabled) {
561
- this.handleOverlay.tabIndex = 0;
562
- this.handleOverlay.setAttribute('aria-label', this.editor.localization.selectionToolKeyboardShortcuts);
563
- }
564
- else {
565
- this.handleOverlay.tabIndex = -1;
566
- }
567
- }
568
- // Get the object responsible for displaying this' selection.
569
- getSelection() {
570
- return this.selectionBox;
571
- }
572
- clearSelection() {
573
- this.handleOverlay.replaceChildren();
574
- this.prevSelectionBox = this.selectionBox;
575
- this.selectionBox = null;
576
- this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
577
- kind: EditorEventType.ToolUpdated,
578
- tool: this,
579
- });
580
- }
581
- }
582
- SelectionTool.handleableKeys = [
583
- 'a', 'h', 'ArrowLeft',
584
- 'd', 'l', 'ArrowRight',
585
- 'q', 'k', 'ArrowUp',
586
- 'e', 'j', 'ArrowDown',
587
- 'r', 'R',
588
- 'i', 'I', 'o', 'O',
589
- ];