js-draw 0.3.2 → 0.4.1

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 (104) hide show
  1. package/.github/pull_request_template.md +15 -0
  2. package/.github/workflows/firebase-hosting-merge.yml +7 -0
  3. package/.github/workflows/firebase-hosting-pull-request.yml +10 -0
  4. package/.github/workflows/github-pages.yml +2 -0
  5. package/CHANGELOG.md +16 -1
  6. package/README.md +1 -3
  7. package/dist/bundle.js +1 -1
  8. package/dist/src/Editor.d.ts +11 -0
  9. package/dist/src/Editor.js +107 -77
  10. package/dist/src/Pointer.d.ts +1 -1
  11. package/dist/src/Pointer.js +8 -3
  12. package/dist/src/Viewport.d.ts +1 -0
  13. package/dist/src/Viewport.js +14 -1
  14. package/dist/src/components/AbstractComponent.js +1 -0
  15. package/dist/src/components/ImageComponent.d.ts +2 -2
  16. package/dist/src/components/Stroke.js +15 -9
  17. package/dist/src/components/Text.d.ts +1 -1
  18. package/dist/src/components/Text.js +1 -1
  19. package/dist/src/components/builders/FreehandLineBuilder.d.ts +1 -0
  20. package/dist/src/components/builders/FreehandLineBuilder.js +34 -36
  21. package/dist/src/language/assertions.d.ts +1 -0
  22. package/dist/src/language/assertions.js +5 -0
  23. package/dist/src/math/Mat33.d.ts +38 -2
  24. package/dist/src/math/Mat33.js +30 -1
  25. package/dist/src/math/Path.d.ts +1 -1
  26. package/dist/src/math/Path.js +10 -8
  27. package/dist/src/math/Vec3.d.ts +12 -2
  28. package/dist/src/math/Vec3.js +16 -1
  29. package/dist/src/math/rounding.d.ts +1 -0
  30. package/dist/src/math/rounding.js +13 -6
  31. package/dist/src/rendering/renderers/AbstractRenderer.js +2 -1
  32. package/dist/src/testing/beforeEachFile.d.ts +1 -0
  33. package/dist/src/testing/beforeEachFile.js +3 -0
  34. package/dist/src/testing/createEditor.d.ts +1 -0
  35. package/dist/src/testing/createEditor.js +7 -1
  36. package/dist/src/testing/loadExpectExtensions.d.ts +0 -15
  37. package/dist/src/toolbar/HTMLToolbar.js +5 -4
  38. package/dist/src/toolbar/widgets/SelectionToolWidget.d.ts +1 -1
  39. package/dist/src/tools/PasteHandler.js +3 -1
  40. package/dist/src/tools/Pen.js +1 -1
  41. package/dist/src/tools/SelectionTool/Selection.d.ts +54 -0
  42. package/dist/src/tools/SelectionTool/Selection.js +337 -0
  43. package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +35 -0
  44. package/dist/src/tools/SelectionTool/SelectionHandle.js +75 -0
  45. package/dist/src/tools/SelectionTool/SelectionTool.d.ts +31 -0
  46. package/dist/src/tools/SelectionTool/SelectionTool.js +284 -0
  47. package/dist/src/tools/SelectionTool/TransformMode.d.ts +34 -0
  48. package/dist/src/tools/SelectionTool/TransformMode.js +98 -0
  49. package/dist/src/tools/SelectionTool/types.d.ts +9 -0
  50. package/dist/src/tools/SelectionTool/types.js +11 -0
  51. package/dist/src/tools/ToolController.js +1 -1
  52. package/dist/src/tools/lib.d.ts +1 -1
  53. package/dist/src/tools/lib.js +1 -1
  54. package/dist/src/types.d.ts +1 -1
  55. package/jest.config.js +5 -0
  56. package/package.json +15 -14
  57. package/src/Editor.css +1 -0
  58. package/src/Editor.ts +147 -108
  59. package/src/Pointer.ts +8 -3
  60. package/src/Viewport.ts +17 -2
  61. package/src/components/AbstractComponent.ts +4 -6
  62. package/src/components/ImageComponent.ts +2 -6
  63. package/src/components/Stroke.test.ts +0 -3
  64. package/src/components/Stroke.ts +14 -7
  65. package/src/components/Text.test.ts +0 -3
  66. package/src/components/Text.ts +4 -8
  67. package/src/components/builders/FreehandLineBuilder.ts +37 -43
  68. package/src/language/assertions.ts +6 -0
  69. package/src/math/LineSegment2.test.ts +8 -10
  70. package/src/math/Mat33.test.ts +14 -2
  71. package/src/math/Mat33.ts +43 -2
  72. package/src/math/Path.toString.test.ts +12 -1
  73. package/src/math/Path.ts +11 -9
  74. package/src/math/Rect2.test.ts +0 -3
  75. package/src/math/Vec2.test.ts +0 -3
  76. package/src/math/Vec3.test.ts +0 -3
  77. package/src/math/Vec3.ts +23 -2
  78. package/src/math/rounding.test.ts +30 -5
  79. package/src/math/rounding.ts +16 -7
  80. package/src/rendering/renderers/AbstractRenderer.ts +3 -2
  81. package/src/testing/beforeEachFile.ts +3 -0
  82. package/src/testing/createEditor.ts +8 -1
  83. package/src/testing/global.d.ts +17 -0
  84. package/src/testing/loadExpectExtensions.ts +0 -15
  85. package/src/toolbar/HTMLToolbar.ts +5 -4
  86. package/src/toolbar/toolbar.css +3 -2
  87. package/src/toolbar/widgets/SelectionToolWidget.ts +1 -1
  88. package/src/tools/PasteHandler.ts +4 -1
  89. package/src/tools/Pen.test.ts +150 -0
  90. package/src/tools/Pen.ts +1 -1
  91. package/src/tools/SelectionTool/Selection.ts +455 -0
  92. package/src/tools/SelectionTool/SelectionHandle.ts +99 -0
  93. package/src/tools/SelectionTool/SelectionTool.css +22 -0
  94. package/src/tools/{SelectionTool.test.ts → SelectionTool/SelectionTool.test.ts} +21 -21
  95. package/src/tools/SelectionTool/SelectionTool.ts +344 -0
  96. package/src/tools/SelectionTool/TransformMode.ts +114 -0
  97. package/src/tools/SelectionTool/types.ts +11 -0
  98. package/src/tools/ToolController.ts +1 -1
  99. package/src/tools/lib.ts +1 -1
  100. package/src/types.ts +1 -1
  101. package/tsconfig.json +3 -1
  102. package/dist/src/tools/SelectionTool.d.ts +0 -65
  103. package/dist/src/tools/SelectionTool.js +0 -647
  104. package/src/tools/SelectionTool.ts +0 -797
@@ -0,0 +1,337 @@
1
+ /**
2
+ * @internal
3
+ * @packageDocumentation
4
+ */
5
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
6
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
7
+ return new (P || (P = Promise))(function (resolve, reject) {
8
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
9
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
11
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
12
+ });
13
+ };
14
+ var _a;
15
+ import SerializableCommand from '../../commands/SerializableCommand';
16
+ import { Mat33, Rect2 } from '../../math/lib';
17
+ import { Vec2 } from '../../math/Vec2';
18
+ import SelectionHandle, { HandleShape, handleSize } from './SelectionHandle';
19
+ import { cssPrefix } from './SelectionTool';
20
+ import Viewport from '../../Viewport';
21
+ import Erase from '../../commands/Erase';
22
+ import Duplicate from '../../commands/Duplicate';
23
+ import { DragTransformer, ResizeTransformer, RotateTransformer } from './TransformMode';
24
+ import { ResizeMode } from './types';
25
+ const updateChunkSize = 100;
26
+ // @internal
27
+ export default class Selection {
28
+ constructor(startPoint, editor) {
29
+ this.editor = editor;
30
+ this.transform = Mat33.identity;
31
+ this.transformCommands = [];
32
+ this.selectedElems = [];
33
+ this.targetHandle = null;
34
+ this.backgroundDragging = false;
35
+ this.originalRegion = new Rect2(startPoint.x, startPoint.y, 0, 0);
36
+ this.transformers = {
37
+ drag: new DragTransformer(editor, this),
38
+ resize: new ResizeTransformer(editor, this),
39
+ rotate: new RotateTransformer(editor, this),
40
+ };
41
+ this.container = document.createElement('div');
42
+ this.backgroundElem = document.createElement('div');
43
+ this.backgroundElem.classList.add(`${cssPrefix}selection-background`);
44
+ this.container.appendChild(this.backgroundElem);
45
+ const resizeHorizontalHandle = new SelectionHandle(HandleShape.Square, Vec2.of(1, 0.5), this, (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.HorizontalOnly), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
46
+ const resizeVerticalHandle = new SelectionHandle(HandleShape.Square, Vec2.of(0.5, 1), this, (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.VerticalOnly), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
47
+ const resizeBothHandle = new SelectionHandle(HandleShape.Square, Vec2.of(1, 1), this, (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.Both), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
48
+ const rotationHandle = new SelectionHandle(HandleShape.Circle, Vec2.of(0.5, 0), this, (startPoint) => this.transformers.rotate.onDragStart(startPoint), (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint), () => this.transformers.rotate.onDragEnd());
49
+ this.handles = [
50
+ resizeBothHandle,
51
+ resizeHorizontalHandle,
52
+ resizeVerticalHandle,
53
+ rotationHandle,
54
+ ];
55
+ for (const handle of this.handles) {
56
+ handle.addTo(this.backgroundElem);
57
+ }
58
+ }
59
+ getTransform() {
60
+ return this.transform;
61
+ }
62
+ get preTransformRegion() {
63
+ return this.originalRegion;
64
+ }
65
+ get region() {
66
+ // TODO: This currently assumes that the region rotates about its center.
67
+ // This may not be true.
68
+ const rotationMatrix = Mat33.zRotation(this.regionRotation, this.originalRegion.center);
69
+ const scaleAndTranslateMat = this.transform.rightMul(rotationMatrix.inverse());
70
+ return this.originalRegion.transformedBoundingBox(scaleAndTranslateMat);
71
+ }
72
+ get regionRotation() {
73
+ return this.transform.transformVec3(Vec2.unitX).angle();
74
+ }
75
+ get preTransformedScreenRegion() {
76
+ const toScreen = (vec) => this.editor.viewport.canvasToScreen(vec);
77
+ return Rect2.fromCorners(toScreen(this.preTransformRegion.topLeft), toScreen(this.preTransformRegion.bottomRight));
78
+ }
79
+ get preTransformedScreenRegionRotation() {
80
+ return this.editor.viewport.getRotationAngle();
81
+ }
82
+ get screenRegion() {
83
+ const toScreen = this.editor.viewport.canvasToScreenTransform;
84
+ const scaleFactor = this.editor.viewport.getScaleFactor();
85
+ const screenCenter = toScreen.transformVec2(this.region.center);
86
+ return new Rect2(screenCenter.x, screenCenter.y, scaleFactor * this.region.width, scaleFactor * this.region.height).translatedBy(this.region.size.times(-scaleFactor / 2));
87
+ }
88
+ get screenRegionRotation() {
89
+ return this.regionRotation + this.editor.viewport.getRotationAngle();
90
+ }
91
+ computeTransformCommands() {
92
+ return this.selectedElems.map(elem => {
93
+ return elem.transformBy(this.transform);
94
+ });
95
+ }
96
+ // Applies, previews, but doesn't finalize the given transformation.
97
+ setTransform(transform, preview = true) {
98
+ this.transform = transform;
99
+ if (preview) {
100
+ this.previewTransformCmds();
101
+ this.scrollTo();
102
+ }
103
+ }
104
+ // Applies the current transformation to the selection
105
+ finalizeTransform() {
106
+ this.transformCommands.forEach(cmd => {
107
+ cmd.unapply(this.editor);
108
+ });
109
+ const fullTransform = this.transform;
110
+ const currentTransfmCommands = this.computeTransformCommands();
111
+ // Reset for the next drag
112
+ this.transformCommands = [];
113
+ this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
114
+ this.transform = Mat33.identity;
115
+ // Make the commands undo-able
116
+ this.editor.dispatch(new Selection.ApplyTransformationCommand(this, currentTransfmCommands, fullTransform));
117
+ }
118
+ // Preview the effects of the current transformation on the selection
119
+ previewTransformCmds() {
120
+ // Don't render what we're moving if it's likely to be slow.
121
+ if (this.selectedElems.length > updateChunkSize) {
122
+ this.updateUI();
123
+ return;
124
+ }
125
+ this.transformCommands.forEach(cmd => cmd.unapply(this.editor));
126
+ this.transformCommands = this.computeTransformCommands();
127
+ this.transformCommands.forEach(cmd => cmd.apply(this.editor));
128
+ this.updateUI();
129
+ }
130
+ // Find the objects corresponding to this in the document,
131
+ // select them.
132
+ // Returns false iff nothing was selected.
133
+ resolveToObjects() {
134
+ let singleItemSelectionMode = false;
135
+ this.transform = Mat33.identity;
136
+ // Grow the rectangle, if necessary
137
+ if (this.region.w === 0 || this.region.h === 0) {
138
+ const padding = this.editor.viewport.visibleRect.maxDimension / 200;
139
+ this.originalRegion = Rect2.bboxOf(this.region.corners, padding);
140
+ // Only select one item if the rectangle was very small.
141
+ singleItemSelectionMode = true;
142
+ }
143
+ this.selectedElems = this.editor.image.getElementsIntersectingRegion(this.region).filter(elem => {
144
+ if (this.region.containsRect(elem.getBBox())) {
145
+ return true;
146
+ }
147
+ // Calculated bounding boxes can be slightly larger than their actual contents' bounding box.
148
+ // As such, test with more lines than just this' edges.
149
+ const testLines = [];
150
+ for (const subregion of this.region.divideIntoGrid(2, 2)) {
151
+ testLines.push(...subregion.getEdges());
152
+ }
153
+ return testLines.some(edge => elem.intersects(edge));
154
+ });
155
+ if (singleItemSelectionMode && this.selectedElems.length > 0) {
156
+ this.selectedElems = [this.selectedElems[this.selectedElems.length - 1]];
157
+ }
158
+ // Find the bounding box of all selected elements.
159
+ if (!this.recomputeRegion()) {
160
+ return false;
161
+ }
162
+ this.updateUI();
163
+ return true;
164
+ }
165
+ // Recompute this' region from the selected elements.
166
+ // Returns false if the selection is empty.
167
+ recomputeRegion() {
168
+ const newRegion = this.selectedElems.reduce((accumulator, elem) => {
169
+ return (accumulator !== null && accumulator !== void 0 ? accumulator : elem.getBBox()).union(elem.getBBox());
170
+ }, null);
171
+ if (!newRegion) {
172
+ this.cancelSelection();
173
+ return false;
174
+ }
175
+ this.originalRegion = newRegion;
176
+ const minSize = this.getMinCanvasSize();
177
+ if (this.originalRegion.w < minSize || this.originalRegion.h < minSize) {
178
+ // Add padding
179
+ const padding = minSize / 2;
180
+ this.originalRegion = Rect2.bboxOf(this.originalRegion.corners, padding);
181
+ }
182
+ return true;
183
+ }
184
+ getMinCanvasSize() {
185
+ const canvasHandleSize = handleSize / this.editor.viewport.getScaleFactor();
186
+ return canvasHandleSize * 2;
187
+ }
188
+ getSelectedItemCount() {
189
+ return this.selectedElems.length;
190
+ }
191
+ // @internal
192
+ updateUI() {
193
+ // marginLeft, marginTop: Display relative to the top left of the selection overlay.
194
+ // left, top don't work for this.
195
+ this.backgroundElem.style.marginLeft = `${this.screenRegion.topLeft.x}px`;
196
+ this.backgroundElem.style.marginTop = `${this.screenRegion.topLeft.y}px`;
197
+ this.backgroundElem.style.width = `${this.screenRegion.width}px`;
198
+ this.backgroundElem.style.height = `${this.screenRegion.height}px`;
199
+ const rotationDeg = this.screenRegionRotation * 180 / Math.PI;
200
+ this.backgroundElem.style.transform = `rotate(${rotationDeg}deg)`;
201
+ this.backgroundElem.style.transformOrigin = 'center';
202
+ for (const handle of this.handles) {
203
+ handle.updatePosition();
204
+ }
205
+ }
206
+ onDragStart(pointer, target) {
207
+ for (const handle of this.handles) {
208
+ if (handle.isTarget(target)) {
209
+ handle.handleDragStart(pointer);
210
+ this.targetHandle = handle;
211
+ return true;
212
+ }
213
+ }
214
+ if (this.backgroundElem === target) {
215
+ this.backgroundDragging = true;
216
+ this.transformers.drag.onDragStart(pointer.canvasPos);
217
+ return true;
218
+ }
219
+ return false;
220
+ }
221
+ onDragUpdate(pointer) {
222
+ if (this.backgroundDragging) {
223
+ this.transformers.drag.onDragUpdate(pointer.canvasPos);
224
+ }
225
+ if (this.targetHandle) {
226
+ this.targetHandle.handleDragUpdate(pointer);
227
+ }
228
+ this.updateUI();
229
+ }
230
+ onDragEnd() {
231
+ if (this.backgroundDragging) {
232
+ this.transformers.drag.onDragEnd();
233
+ }
234
+ else if (this.targetHandle) {
235
+ this.targetHandle.handleDragEnd();
236
+ }
237
+ this.backgroundDragging = false;
238
+ this.targetHandle = null;
239
+ this.updateUI();
240
+ }
241
+ onDragCancel() {
242
+ this.backgroundDragging = false;
243
+ this.targetHandle = null;
244
+ this.setTransform(Mat33.identity);
245
+ }
246
+ // Scroll the viewport to this. Does not zoom
247
+ scrollTo() {
248
+ if (this.selectedElems.length === 0) {
249
+ return;
250
+ }
251
+ const screenRect = new Rect2(0, 0, this.editor.display.width, this.editor.display.height);
252
+ if (!screenRect.containsPoint(this.screenRegion.center)) {
253
+ const closestPoint = screenRect.getClosestPointOnBoundaryTo(this.screenRegion.center);
254
+ const screenDelta = this.screenRegion.center.minus(closestPoint);
255
+ const delta = this.editor.viewport.screenToCanvasTransform.transformVec3(screenDelta);
256
+ this.editor.dispatchNoAnnounce(Viewport.transformBy(Mat33.translation(delta.times(-1))), false);
257
+ }
258
+ }
259
+ deleteSelectedObjects() {
260
+ return new Erase(this.selectedElems);
261
+ }
262
+ duplicateSelectedObjects() {
263
+ return new Duplicate(this.selectedElems);
264
+ }
265
+ addTo(elem) {
266
+ if (this.container.parentElement) {
267
+ this.container.remove();
268
+ }
269
+ elem.appendChild(this.container);
270
+ }
271
+ setToPoint(point) {
272
+ this.originalRegion = this.originalRegion.grownToPoint(point);
273
+ this.updateUI();
274
+ }
275
+ cancelSelection() {
276
+ if (this.container.parentElement) {
277
+ this.container.remove();
278
+ }
279
+ this.originalRegion = Rect2.empty;
280
+ }
281
+ setSelectedObjects(objects, bbox) {
282
+ this.originalRegion = bbox;
283
+ this.selectedElems = objects;
284
+ this.updateUI();
285
+ }
286
+ getSelectedObjects() {
287
+ return this.selectedElems;
288
+ }
289
+ }
290
+ _a = Selection;
291
+ (() => {
292
+ SerializableCommand.register('selection-tool-transform', (json, editor) => {
293
+ // The selection box is lost when serializing/deserializing. No need to store box rotation
294
+ const fullTransform = new Mat33(...json.transform);
295
+ const commands = json.commands.map(data => SerializableCommand.deserialize(data, editor));
296
+ return new _a.ApplyTransformationCommand(null, commands, fullTransform);
297
+ });
298
+ })();
299
+ Selection.ApplyTransformationCommand = class extends SerializableCommand {
300
+ constructor(selection, currentTransfmCommands, fullTransform) {
301
+ super('selection-tool-transform');
302
+ this.selection = selection;
303
+ this.currentTransfmCommands = currentTransfmCommands;
304
+ this.fullTransform = fullTransform;
305
+ }
306
+ apply(editor) {
307
+ var _b, _c, _d, _e, _f;
308
+ return __awaiter(this, void 0, void 0, function* () {
309
+ (_b = this.selection) === null || _b === void 0 ? void 0 : _b.setTransform(this.fullTransform, false);
310
+ (_c = this.selection) === null || _c === void 0 ? void 0 : _c.updateUI();
311
+ yield editor.asyncApplyCommands(this.currentTransfmCommands, updateChunkSize);
312
+ (_d = this.selection) === null || _d === void 0 ? void 0 : _d.setTransform(Mat33.identity, false);
313
+ (_e = this.selection) === null || _e === void 0 ? void 0 : _e.recomputeRegion();
314
+ (_f = this.selection) === null || _f === void 0 ? void 0 : _f.updateUI();
315
+ });
316
+ }
317
+ unapply(editor) {
318
+ var _b, _c, _d, _e, _f;
319
+ return __awaiter(this, void 0, void 0, function* () {
320
+ (_b = this.selection) === null || _b === void 0 ? void 0 : _b.setTransform(this.fullTransform.inverse(), false);
321
+ (_c = this.selection) === null || _c === void 0 ? void 0 : _c.updateUI();
322
+ yield editor.asyncUnapplyCommands(this.currentTransfmCommands, updateChunkSize);
323
+ (_d = this.selection) === null || _d === void 0 ? void 0 : _d.setTransform(Mat33.identity);
324
+ (_e = this.selection) === null || _e === void 0 ? void 0 : _e.recomputeRegion();
325
+ (_f = this.selection) === null || _f === void 0 ? void 0 : _f.updateUI();
326
+ });
327
+ }
328
+ serializeToJSON() {
329
+ return {
330
+ commands: this.currentTransfmCommands.map(command => command.serialize()),
331
+ transform: this.fullTransform.toArray(),
332
+ };
333
+ }
334
+ description(_editor, localizationTable) {
335
+ return localizationTable.transformedElements(this.currentTransfmCommands.length);
336
+ }
337
+ };
@@ -0,0 +1,35 @@
1
+ import { Point2, Vec2 } from '../../math/Vec2';
2
+ import Selection from './Selection';
3
+ import Pointer from '../../Pointer';
4
+ export declare enum HandleShape {
5
+ Circle = 0,
6
+ Square = 1
7
+ }
8
+ export declare const handleSize = 30;
9
+ export declare type DragStartCallback = (startPoint: Point2) => void;
10
+ export declare type DragUpdateCallback = (canvasPoint: Point2) => void;
11
+ export declare type DragEndCallback = () => void;
12
+ export default class SelectionHandle {
13
+ readonly shape: HandleShape;
14
+ private readonly parentSide;
15
+ private readonly parent;
16
+ private readonly onDragStart;
17
+ private readonly onDragUpdate;
18
+ private readonly onDragEnd;
19
+ private element;
20
+ constructor(shape: HandleShape, parentSide: Vec2, parent: Selection, onDragStart: DragStartCallback, onDragUpdate: DragUpdateCallback, onDragEnd: DragEndCallback);
21
+ /**
22
+ * Adds this to `container`, where `conatiner` should be the background/selection
23
+ * element visible on the screen.
24
+ */
25
+ addTo(container: HTMLElement): void;
26
+ updatePosition(): void;
27
+ /**
28
+ * @returns `true` if the given `EventTarget` matches this.
29
+ */
30
+ isTarget(target: EventTarget): boolean;
31
+ private dragLastPos;
32
+ handleDragStart(pointer: Pointer): void;
33
+ handleDragUpdate(pointer: Pointer): void;
34
+ handleDragEnd(): void;
35
+ }
@@ -0,0 +1,75 @@
1
+ import { assertUnreachable } from '../../language/assertions';
2
+ import { Vec2 } from '../../math/Vec2';
3
+ import { cssPrefix } from './SelectionTool';
4
+ export var HandleShape;
5
+ (function (HandleShape) {
6
+ HandleShape[HandleShape["Circle"] = 0] = "Circle";
7
+ HandleShape[HandleShape["Square"] = 1] = "Square";
8
+ })(HandleShape || (HandleShape = {}));
9
+ export const handleSize = 30;
10
+ export default class SelectionHandle {
11
+ // Bounding box in screen coordinates.
12
+ constructor(shape, parentSide, parent, onDragStart, onDragUpdate, onDragEnd) {
13
+ this.shape = shape;
14
+ this.parentSide = parentSide;
15
+ this.parent = parent;
16
+ this.onDragStart = onDragStart;
17
+ this.onDragUpdate = onDragUpdate;
18
+ this.onDragEnd = onDragEnd;
19
+ this.dragLastPos = null;
20
+ this.element = document.createElement('div');
21
+ this.element.classList.add(`${cssPrefix}handle`);
22
+ switch (shape) {
23
+ case HandleShape.Circle:
24
+ this.element.classList.add(`${cssPrefix}circle`);
25
+ break;
26
+ case HandleShape.Square:
27
+ this.element.classList.add(`${cssPrefix}square`);
28
+ break;
29
+ default:
30
+ assertUnreachable(shape);
31
+ }
32
+ this.updatePosition();
33
+ }
34
+ /**
35
+ * Adds this to `container`, where `conatiner` should be the background/selection
36
+ * element visible on the screen.
37
+ */
38
+ addTo(container) {
39
+ container.appendChild(this.element);
40
+ }
41
+ updatePosition() {
42
+ const parentRect = this.parent.screenRegion;
43
+ const size = Vec2.of(handleSize, handleSize);
44
+ const topLeft = parentRect.size.scale(this.parentSide)
45
+ // Center
46
+ .minus(size.times(1 / 2));
47
+ // Position within the selection box.
48
+ this.element.style.marginLeft = `${topLeft.x}px`;
49
+ this.element.style.marginTop = `${topLeft.y}px`;
50
+ this.element.style.width = `${size.x}px`;
51
+ this.element.style.height = `${size.y}px`;
52
+ }
53
+ /**
54
+ * @returns `true` if the given `EventTarget` matches this.
55
+ */
56
+ isTarget(target) {
57
+ return target === this.element;
58
+ }
59
+ handleDragStart(pointer) {
60
+ this.onDragStart(pointer.canvasPos);
61
+ this.dragLastPos = pointer.canvasPos;
62
+ }
63
+ handleDragUpdate(pointer) {
64
+ if (!this.dragLastPos) {
65
+ return;
66
+ }
67
+ this.onDragUpdate(pointer.canvasPos);
68
+ }
69
+ handleDragEnd() {
70
+ if (!this.dragLastPos) {
71
+ return;
72
+ }
73
+ this.onDragEnd();
74
+ }
75
+ }
@@ -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
+ }