js-draw 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/Editor.css +30 -4
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +5 -0
- package/dist/cjs/Editor.js +53 -70
- package/dist/cjs/components/BackgroundComponent.js +6 -1
- package/dist/cjs/components/TextComponent.d.ts +1 -1
- package/dist/cjs/components/TextComponent.js +19 -12
- package/dist/cjs/localization.d.ts +2 -0
- package/dist/cjs/localization.js +2 -0
- package/dist/cjs/localizations/comments.js +1 -0
- package/dist/cjs/rendering/RenderablePathSpec.js +16 -1
- package/dist/cjs/rendering/caching/CacheRecordManager.d.ts +1 -0
- package/dist/cjs/rendering/caching/CacheRecordManager.js +18 -0
- package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -0
- package/dist/cjs/rendering/caching/RenderingCache.js +3 -0
- package/dist/cjs/rendering/renderers/CanvasRenderer.js +3 -2
- package/dist/cjs/tools/SelectionTool/Selection.d.ts +5 -4
- package/dist/cjs/tools/SelectionTool/Selection.js +75 -48
- package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +2 -2
- package/dist/cjs/tools/SelectionTool/SelectionHandle.js +8 -3
- package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +3 -1
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +36 -16
- package/dist/cjs/tools/SelectionTool/ToPointerAutoscroller.d.ts +23 -0
- package/dist/cjs/tools/SelectionTool/ToPointerAutoscroller.js +83 -0
- package/dist/cjs/tools/SelectionTool/TransformMode.d.ts +10 -3
- package/dist/cjs/tools/SelectionTool/TransformMode.js +52 -9
- package/dist/cjs/util/listenForKeyboardEventsFrom.d.ts +16 -0
- package/dist/cjs/util/listenForKeyboardEventsFrom.js +142 -0
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +5 -0
- package/dist/mjs/Editor.mjs +53 -70
- package/dist/mjs/components/BackgroundComponent.mjs +6 -1
- package/dist/mjs/components/TextComponent.d.ts +1 -1
- package/dist/mjs/components/TextComponent.mjs +19 -12
- package/dist/mjs/localization.d.ts +2 -0
- package/dist/mjs/localization.mjs +2 -0
- package/dist/mjs/localizations/comments.mjs +1 -0
- package/dist/mjs/rendering/RenderablePathSpec.mjs +16 -1
- package/dist/mjs/rendering/caching/CacheRecordManager.d.ts +1 -0
- package/dist/mjs/rendering/caching/CacheRecordManager.mjs +18 -0
- package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -0
- package/dist/mjs/rendering/caching/RenderingCache.mjs +3 -0
- package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +3 -2
- package/dist/mjs/tools/SelectionTool/Selection.d.ts +5 -4
- package/dist/mjs/tools/SelectionTool/Selection.mjs +75 -48
- package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +2 -2
- package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +8 -3
- package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +3 -1
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +36 -16
- package/dist/mjs/tools/SelectionTool/ToPointerAutoscroller.d.ts +23 -0
- package/dist/mjs/tools/SelectionTool/ToPointerAutoscroller.mjs +77 -0
- package/dist/mjs/tools/SelectionTool/TransformMode.d.ts +10 -3
- package/dist/mjs/tools/SelectionTool/TransformMode.mjs +52 -9
- package/dist/mjs/util/listenForKeyboardEventsFrom.d.ts +16 -0
- package/dist/mjs/util/listenForKeyboardEventsFrom.mjs +140 -0
- package/dist/mjs/version.mjs +1 -1
- package/package.json +4 -4
- package/src/tools/SelectionTool/SelectionTool.scss +62 -9
@@ -149,9 +149,10 @@ class CanvasRenderer extends AbstractRenderer_1.default {
|
|
149
149
|
return;
|
150
150
|
}
|
151
151
|
// If part of a huge object, it might be worth trimming the path
|
152
|
-
|
152
|
+
const visibleRect = this.getViewport().visibleRect;
|
153
|
+
if (this.currentObjectBBox?.containsRect(visibleRect)) {
|
153
154
|
// Try to trim/remove parts of the path outside of the bounding box.
|
154
|
-
path = (0, RenderablePathSpec_1.visualEquivalent)(path,
|
155
|
+
path = (0, RenderablePathSpec_1.visualEquivalent)(path, visibleRect);
|
155
156
|
}
|
156
157
|
super.drawPath(path);
|
157
158
|
}
|
@@ -15,7 +15,8 @@ export default class Selection {
|
|
15
15
|
private transformers;
|
16
16
|
private transform;
|
17
17
|
private selectedElems;
|
18
|
-
private
|
18
|
+
private outerContainer;
|
19
|
+
private innerContainer;
|
19
20
|
private backgroundElem;
|
20
21
|
private hasParent;
|
21
22
|
constructor(startPoint: Point2, editor: Editor);
|
@@ -32,10 +33,10 @@ export default class Selection {
|
|
32
33
|
get regionRotation(): number;
|
33
34
|
get preTransformedScreenRegion(): Rect2;
|
34
35
|
get preTransformedScreenRegionRotation(): number;
|
35
|
-
|
36
|
+
getScreenRegion(): Rect2;
|
36
37
|
get screenRegionRotation(): number;
|
37
38
|
setTransform(transform: Mat33, preview?: boolean): void;
|
38
|
-
finalizeTransform(): Promise<void>;
|
39
|
+
finalizeTransform(): void | Promise<void>;
|
39
40
|
private static ApplyTransformationCommand;
|
40
41
|
private previewTransformCmds;
|
41
42
|
resolveToObjects(): boolean;
|
@@ -53,7 +54,7 @@ export default class Selection {
|
|
53
54
|
onDragUpdate(pointer: Pointer): void;
|
54
55
|
onDragEnd(): void;
|
55
56
|
onDragCancel(): void;
|
56
|
-
scrollTo():
|
57
|
+
scrollTo(): boolean;
|
57
58
|
deleteSelectedObjects(): Command;
|
58
59
|
private selectionDuplicatedAnimationTimeout;
|
59
60
|
private runSelectionDuplicatedAnimation;
|
@@ -64,22 +64,34 @@ class Selection {
|
|
64
64
|
resize: new TransformMode_1.ResizeTransformer(editor, this),
|
65
65
|
rotate: new TransformMode_1.RotateTransformer(editor, this),
|
66
66
|
};
|
67
|
-
|
67
|
+
// We need two containers for some CSS to apply (the outer container
|
68
|
+
// needs zero height, the inner needs to prevent the selection background
|
69
|
+
// from being visible outside of the editor).
|
70
|
+
this.outerContainer = document.createElement('div');
|
71
|
+
this.outerContainer.classList.add(`${SelectionTool_1.cssPrefix}selection-outer-container`);
|
72
|
+
this.innerContainer = document.createElement('div');
|
73
|
+
this.innerContainer.classList.add(`${SelectionTool_1.cssPrefix}selection-inner-container`);
|
68
74
|
this.backgroundElem = document.createElement('div');
|
69
75
|
this.backgroundElem.classList.add(`${SelectionTool_1.cssPrefix}selection-background`);
|
70
|
-
this.
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
}
|
76
|
+
this.innerContainer.appendChild(this.backgroundElem);
|
77
|
+
this.outerContainer.appendChild(this.innerContainer);
|
78
|
+
const makeResizeHandle = (mode, side) => {
|
79
|
+
const modeToAction = {
|
80
|
+
[types_1.ResizeMode.Both]: SelectionHandle_1.HandleAction.ResizeXY,
|
81
|
+
[types_1.ResizeMode.HorizontalOnly]: SelectionHandle_1.HandleAction.ResizeX,
|
82
|
+
[types_1.ResizeMode.VerticalOnly]: SelectionHandle_1.HandleAction.ResizeY,
|
83
|
+
};
|
84
|
+
return new SelectionHandle_1.default({
|
85
|
+
action: modeToAction[mode],
|
86
|
+
side,
|
87
|
+
}, this, this.editor.viewport, (startPoint) => this.transformers.resize.onDragStart(startPoint, mode), (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint), () => this.transformers.resize.onDragEnd());
|
88
|
+
};
|
89
|
+
const resizeHorizontalHandles = [
|
90
|
+
makeResizeHandle(types_1.ResizeMode.HorizontalOnly, math_1.Vec2.of(0, 0.5)),
|
91
|
+
makeResizeHandle(types_1.ResizeMode.HorizontalOnly, math_1.Vec2.of(1, 0.5)),
|
92
|
+
];
|
93
|
+
const resizeVerticalHandle = makeResizeHandle(types_1.ResizeMode.VerticalOnly, math_1.Vec2.of(0.5, 1));
|
94
|
+
const resizeBothHandle = makeResizeHandle(types_1.ResizeMode.Both, math_1.Vec2.of(1, 1));
|
83
95
|
const rotationHandle = new SelectionHandle_1.default({
|
84
96
|
action: SelectionHandle_1.HandleAction.Rotate,
|
85
97
|
side: math_1.Vec2.of(0.5, 0),
|
@@ -87,7 +99,7 @@ class Selection {
|
|
87
99
|
}, this, this.editor.viewport, (startPoint) => this.transformers.rotate.onDragStart(startPoint), (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint), () => this.transformers.rotate.onDragEnd());
|
88
100
|
this.handles = [
|
89
101
|
resizeBothHandle,
|
90
|
-
|
102
|
+
...resizeHorizontalHandles,
|
91
103
|
resizeVerticalHandle,
|
92
104
|
rotationHandle,
|
93
105
|
];
|
@@ -133,7 +145,7 @@ class Selection {
|
|
133
145
|
get preTransformedScreenRegionRotation() {
|
134
146
|
return this.editor.viewport.getRotationAngle();
|
135
147
|
}
|
136
|
-
|
148
|
+
getScreenRegion() {
|
137
149
|
const toScreen = this.editor.viewport.canvasToScreenTransform;
|
138
150
|
const scaleFactor = this.editor.viewport.getScaleFactor();
|
139
151
|
const screenCenter = toScreen.transformVec2(this.region.center);
|
@@ -146,29 +158,33 @@ class Selection {
|
|
146
158
|
setTransform(transform, preview = true) {
|
147
159
|
this.transform = transform;
|
148
160
|
if (preview && this.hasParent) {
|
149
|
-
this.scrollTo();
|
150
161
|
this.previewTransformCmds();
|
151
162
|
}
|
152
163
|
}
|
153
164
|
// Applies the current transformation to the selection
|
154
|
-
|
165
|
+
finalizeTransform() {
|
155
166
|
const fullTransform = this.transform;
|
156
167
|
const selectedElems = this.selectedElems;
|
157
168
|
// Reset for the next drag
|
158
169
|
this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
|
159
170
|
this.transform = math_1.Mat33.identity;
|
171
|
+
this.scrollTo();
|
160
172
|
// Make the commands undo-able.
|
161
173
|
// Don't check for non-empty transforms because this breaks changing the
|
162
174
|
// z-index of the just-transformed commands.
|
163
175
|
//
|
164
176
|
// TODO: Check whether the selectedElems are already all toplevel.
|
165
|
-
|
177
|
+
const transformPromise = this.editor.dispatch(new _a.ApplyTransformationCommand(this, selectedElems, fullTransform));
|
166
178
|
// Clear renderings of any in-progress transformations
|
167
179
|
const wetInkRenderer = this.editor.display.getWetInkRenderer();
|
168
180
|
wetInkRenderer.clear();
|
181
|
+
return transformPromise;
|
169
182
|
}
|
170
183
|
// Preview the effects of the current transformation on the selection
|
171
184
|
previewTransformCmds() {
|
185
|
+
if (this.selectedElems.length === 0) {
|
186
|
+
return;
|
187
|
+
}
|
172
188
|
// Don't render what we're moving if it's likely to be slow.
|
173
189
|
if (this.selectedElems.length > maxPreviewElemCount) {
|
174
190
|
this.updateUI();
|
@@ -249,28 +265,29 @@ class Selection {
|
|
249
265
|
if (!this.hasParent) {
|
250
266
|
return;
|
251
267
|
}
|
268
|
+
const screenRegion = this.getScreenRegion();
|
252
269
|
// marginLeft, marginTop: Display relative to the top left of the selection overlay.
|
253
270
|
// left, top don't work for this.
|
254
|
-
this.backgroundElem.style.marginLeft = `${
|
255
|
-
this.backgroundElem.style.marginTop = `${
|
256
|
-
this.backgroundElem.style.width = `${
|
257
|
-
this.backgroundElem.style.height = `${
|
271
|
+
this.backgroundElem.style.marginLeft = `${screenRegion.topLeft.x}px`;
|
272
|
+
this.backgroundElem.style.marginTop = `${screenRegion.topLeft.y}px`;
|
273
|
+
this.backgroundElem.style.width = `${screenRegion.width}px`;
|
274
|
+
this.backgroundElem.style.height = `${screenRegion.height}px`;
|
258
275
|
const rotationDeg = this.screenRegionRotation * 180 / Math.PI;
|
259
276
|
this.backgroundElem.style.transform = `rotate(${rotationDeg}deg)`;
|
260
277
|
this.backgroundElem.style.transformOrigin = 'center';
|
261
278
|
// If closer to perpendicular, apply different CSS
|
262
279
|
const perpendicularClassName = `${SelectionTool_1.cssPrefix}rotated-near-perpendicular`;
|
263
280
|
if (Math.abs(Math.sin(this.screenRegionRotation)) > 0.5) {
|
264
|
-
this.
|
281
|
+
this.innerContainer.classList.add(perpendicularClassName);
|
265
282
|
}
|
266
283
|
else {
|
267
|
-
this.
|
284
|
+
this.innerContainer.classList.remove(perpendicularClassName);
|
268
285
|
}
|
269
286
|
for (const handle of this.handles) {
|
270
287
|
handle.updatePosition();
|
271
288
|
}
|
272
289
|
}
|
273
|
-
// Add/remove the contents of this
|
290
|
+
// Add/remove the contents of this seleciton from the editor.
|
274
291
|
// Used to prevent previewed content from looking like duplicate content
|
275
292
|
// while dragging.
|
276
293
|
//
|
@@ -278,6 +295,9 @@ class Selection {
|
|
278
295
|
// the editor image is likely to be slow.)
|
279
296
|
//
|
280
297
|
// If removed from the image, selected elements are drawn as wet ink.
|
298
|
+
//
|
299
|
+
// [inImage] should be `true` if the selected elements should be added to the
|
300
|
+
// main image, `false` if they should be removed.
|
281
301
|
addRemoveSelectionFromImage(inImage) {
|
282
302
|
// Don't hide elements if doing so will be slow.
|
283
303
|
if (!inImage && this.selectedElems.length > maxPreviewElemCount) {
|
@@ -320,17 +340,18 @@ class Selection {
|
|
320
340
|
document.getSelection()?.removeAllRanges();
|
321
341
|
this.targetHandle = null;
|
322
342
|
let result = false;
|
343
|
+
this.backgroundDragging = false;
|
344
|
+
if (this.region.containsPoint(pointer.canvasPos)) {
|
345
|
+
this.backgroundDragging = true;
|
346
|
+
result = true;
|
347
|
+
}
|
323
348
|
for (const handle of this.handles) {
|
324
349
|
if (handle.containsPoint(pointer.canvasPos)) {
|
325
350
|
this.targetHandle = handle;
|
351
|
+
this.backgroundDragging = false;
|
326
352
|
result = true;
|
327
353
|
}
|
328
354
|
}
|
329
|
-
this.backgroundDragging = false;
|
330
|
-
if (this.region.containsPoint(pointer.canvasPos)) {
|
331
|
-
this.backgroundDragging = true;
|
332
|
-
result = true;
|
333
|
-
}
|
334
355
|
if (result) {
|
335
356
|
this.removeDeletedElemsFromSelection();
|
336
357
|
this.addRemoveSelectionFromImage(false);
|
@@ -368,23 +389,29 @@ class Selection {
|
|
368
389
|
this.targetHandle = null;
|
369
390
|
this.setTransform(math_1.Mat33.identity);
|
370
391
|
this.addRemoveSelectionFromImage(true);
|
392
|
+
this.updateUI();
|
371
393
|
}
|
372
394
|
// Scroll the viewport to this. Does not zoom
|
373
|
-
|
395
|
+
scrollTo() {
|
374
396
|
if (this.selectedElems.length === 0) {
|
375
|
-
return;
|
397
|
+
return false;
|
376
398
|
}
|
377
|
-
const
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
const
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
this.
|
399
|
+
const screenSize = this.editor.viewport.getScreenRectSize();
|
400
|
+
const screenRect = new math_1.Rect2(0, 0, screenSize.x, screenSize.y);
|
401
|
+
const selectionScreenRegion = this.getScreenRegion();
|
402
|
+
if (!screenRect.containsPoint(selectionScreenRegion.center)) {
|
403
|
+
const targetPointScreen = selectionScreenRegion.center;
|
404
|
+
const closestPointScreen = screenRect.getClosestPointOnBoundaryTo(targetPointScreen);
|
405
|
+
const closestPointCanvas = this.editor.viewport.screenToCanvas(closestPointScreen);
|
406
|
+
const targetPointCanvas = this.region.center;
|
407
|
+
const delta = closestPointCanvas.minus(targetPointCanvas);
|
408
|
+
this.editor.dispatchNoAnnounce(Viewport_1.default.transformBy(math_1.Mat33.translation(delta.times(0.5))), false);
|
409
|
+
this.editor.queueRerender().then(() => {
|
410
|
+
this.previewTransformCmds();
|
411
|
+
});
|
412
|
+
return true;
|
387
413
|
}
|
414
|
+
return false;
|
388
415
|
}
|
389
416
|
deleteSelectedObjects() {
|
390
417
|
if (this.backgroundDragging || this.targetHandle) {
|
@@ -429,10 +456,10 @@ class Selection {
|
|
429
456
|
return duplicateCommand;
|
430
457
|
}
|
431
458
|
addTo(elem) {
|
432
|
-
if (this.
|
433
|
-
this.
|
459
|
+
if (this.outerContainer.parentElement) {
|
460
|
+
this.outerContainer.remove();
|
434
461
|
}
|
435
|
-
elem.appendChild(this.
|
462
|
+
elem.appendChild(this.outerContainer);
|
436
463
|
this.hasParent = true;
|
437
464
|
}
|
438
465
|
setToPoint(point) {
|
@@ -441,8 +468,8 @@ class Selection {
|
|
441
468
|
this.updateUI();
|
442
469
|
}
|
443
470
|
cancelSelection() {
|
444
|
-
if (this.
|
445
|
-
this.
|
471
|
+
if (this.outerContainer.parentElement) {
|
472
|
+
this.outerContainer.remove();
|
446
473
|
}
|
447
474
|
this.originalRegion = math_1.Rect2.empty;
|
448
475
|
this.selectionTightBoundingBox = null;
|
@@ -16,7 +16,7 @@ export interface HandlePresentation {
|
|
16
16
|
export declare const handleSize = 30;
|
17
17
|
export type DragStartCallback = (startPoint: Point2) => void;
|
18
18
|
export type DragUpdateCallback = (canvasPoint: Point2) => void;
|
19
|
-
export type DragEndCallback = () => void;
|
19
|
+
export type DragEndCallback = () => Promise<void> | void;
|
20
20
|
export default class SelectionHandle {
|
21
21
|
readonly presentation: HandlePresentation;
|
22
22
|
private readonly parent;
|
@@ -50,7 +50,7 @@ export default class SelectionHandle {
|
|
50
50
|
private dragLastPos;
|
51
51
|
handleDragStart(pointer: Pointer): void;
|
52
52
|
handleDragUpdate(pointer: Pointer): void;
|
53
|
-
handleDragEnd(): void
|
53
|
+
handleDragEnd(): void | Promise<void>;
|
54
54
|
setSnapToGrid(snap: boolean): void;
|
55
55
|
isSnappingToGrid(): boolean;
|
56
56
|
}
|
@@ -16,6 +16,7 @@ var HandleAction;
|
|
16
16
|
HandleAction["ResizeX"] = "resize-x";
|
17
17
|
HandleAction["ResizeY"] = "resize-y";
|
18
18
|
})(HandleAction || (exports.HandleAction = HandleAction = {}));
|
19
|
+
// The *interactable* handle size. The visual size will be slightly smaller.
|
19
20
|
exports.handleSize = 30;
|
20
21
|
class SelectionHandle {
|
21
22
|
constructor(presentation, parent, viewport, onDragStart, onDragUpdate, onDragEnd) {
|
@@ -28,10 +29,14 @@ class SelectionHandle {
|
|
28
29
|
this.dragLastPos = null;
|
29
30
|
this.element = document.createElement('div');
|
30
31
|
this.element.classList.add(`${SelectionTool_1.cssPrefix}handle`, `${SelectionTool_1.cssPrefix}${presentation.action}`);
|
32
|
+
// Create a slightly smaller content/background element.
|
33
|
+
const visibleContent = document.createElement('div');
|
34
|
+
visibleContent.classList.add(`${SelectionTool_1.cssPrefix}content`);
|
35
|
+
this.element.appendChild(visibleContent);
|
31
36
|
this.parentSide = presentation.side;
|
32
37
|
const icon = presentation.icon;
|
33
38
|
if (icon) {
|
34
|
-
|
39
|
+
visibleContent.appendChild(icon);
|
35
40
|
icon.classList.add('icon');
|
36
41
|
}
|
37
42
|
if (presentation.action === HandleAction.Rotate) {
|
@@ -64,7 +69,7 @@ class SelectionHandle {
|
|
64
69
|
* selection box.
|
65
70
|
*/
|
66
71
|
getBBoxParentCoords() {
|
67
|
-
const parentRect = this.parent.
|
72
|
+
const parentRect = this.parent.getScreenRegion();
|
68
73
|
const size = math_1.Vec2.of(exports.handleSize, exports.handleSize);
|
69
74
|
const topLeft = parentRect.size.scale(this.parentSide)
|
70
75
|
// Center
|
@@ -118,7 +123,7 @@ class SelectionHandle {
|
|
118
123
|
if (!this.dragLastPos) {
|
119
124
|
return;
|
120
125
|
}
|
121
|
-
this.onDragEnd();
|
126
|
+
return this.onDragEnd();
|
122
127
|
}
|
123
128
|
setSnapToGrid(snap) {
|
124
129
|
this.snapToGrid = snap;
|
@@ -13,13 +13,15 @@ export default class SelectionTool extends BaseTool {
|
|
13
13
|
private expandingSelectionBox;
|
14
14
|
private shiftKeyPressed;
|
15
15
|
private snapToGrid;
|
16
|
+
private lastPointer;
|
17
|
+
private autoscroller;
|
16
18
|
constructor(editor: Editor, description: string);
|
17
19
|
private makeSelectionBox;
|
18
20
|
private snapSelectionToGrid;
|
19
21
|
private selectionBoxHandlingEvt;
|
20
22
|
onPointerDown({ allPointers, current }: PointerEvt): boolean;
|
21
23
|
onPointerMove(event: PointerEvt): void;
|
22
|
-
private
|
24
|
+
private onMainPointerUpdated;
|
23
25
|
onPointerUp(event: PointerEvt): void;
|
24
26
|
onGestureCancel(): void;
|
25
27
|
private lastSelectedObjects;
|
@@ -12,6 +12,7 @@ const SVGRenderer_1 = __importDefault(require("../../rendering/renderers/SVGRend
|
|
12
12
|
const Selection_1 = __importDefault(require("./Selection"));
|
13
13
|
const TextComponent_1 = __importDefault(require("../../components/TextComponent"));
|
14
14
|
const keybindings_1 = require("../keybindings");
|
15
|
+
const ToPointerAutoscroller_1 = __importDefault(require("./ToPointerAutoscroller"));
|
15
16
|
exports.cssPrefix = 'selection-tool-';
|
16
17
|
// Allows users to select/transform portions of the `EditorImage`.
|
17
18
|
// With respect to `extend`ing, `SelectionTool` is not stable.
|
@@ -23,8 +24,19 @@ class SelectionTool extends BaseTool_1.default {
|
|
23
24
|
this.expandingSelectionBox = false;
|
24
25
|
this.shiftKeyPressed = false;
|
25
26
|
this.snapToGrid = false;
|
27
|
+
this.lastPointer = null;
|
26
28
|
this.selectionBoxHandlingEvt = false;
|
27
29
|
this.lastSelectedObjects = [];
|
30
|
+
this.autoscroller = new ToPointerAutoscroller_1.default(editor.viewport, (scrollBy) => {
|
31
|
+
editor.dispatch(Viewport_1.default.transformBy(math_1.Mat33.translation(scrollBy)), false);
|
32
|
+
// Update the selection box/content to match the new viewport.
|
33
|
+
if (this.lastPointer) {
|
34
|
+
// The viewport has changed -- ensure that the screen and canvas positions
|
35
|
+
// of the pointer are both correct
|
36
|
+
const updatedPointer = this.lastPointer.withScreenPosition(this.lastPointer.screenPos, editor.viewport);
|
37
|
+
this.onMainPointerUpdated(updatedPointer);
|
38
|
+
}
|
39
|
+
});
|
28
40
|
this.handleOverlay = document.createElement('div');
|
29
41
|
editor.createHTMLOverlay(this.handleOverlay);
|
30
42
|
this.handleOverlay.style.display = 'none';
|
@@ -87,14 +99,22 @@ class SelectionTool extends BaseTool_1.default {
|
|
87
99
|
this.expandingSelectionBox = this.shiftKeyPressed;
|
88
100
|
this.makeSelectionBox(current.canvasPos);
|
89
101
|
}
|
102
|
+
else {
|
103
|
+
// Only autoscroll if we're transforming an existing selection
|
104
|
+
this.autoscroller.start();
|
105
|
+
}
|
90
106
|
return true;
|
91
107
|
}
|
92
108
|
return false;
|
93
109
|
}
|
94
110
|
onPointerMove(event) {
|
111
|
+
this.onMainPointerUpdated(event.current);
|
112
|
+
}
|
113
|
+
onMainPointerUpdated(currentPointer) {
|
114
|
+
this.lastPointer = currentPointer;
|
95
115
|
if (!this.selectionBox)
|
96
116
|
return;
|
97
|
-
|
117
|
+
this.autoscroller.onPointerMove(currentPointer.screenPos);
|
98
118
|
if (!this.expandingSelectionBox && this.shiftKeyPressed && this.startPoint) {
|
99
119
|
const screenPos = this.editor.viewport.canvasToScreen(this.startPoint);
|
100
120
|
currentPointer = currentPointer.lockedToXYAxesScreen(screenPos, this.editor.viewport);
|
@@ -109,21 +129,8 @@ class SelectionTool extends BaseTool_1.default {
|
|
109
129
|
this.selectionBox.setToPoint(currentPointer.canvasPos);
|
110
130
|
}
|
111
131
|
}
|
112
|
-
// Called after a gestureCancel and a pointerUp
|
113
|
-
onGestureEnd() {
|
114
|
-
if (!this.selectionBox)
|
115
|
-
return;
|
116
|
-
if (!this.selectionBoxHandlingEvt) {
|
117
|
-
// Expand/shrink the selection rectangle, if applicable
|
118
|
-
this.selectionBox.resolveToObjects();
|
119
|
-
this.onSelectionUpdated();
|
120
|
-
}
|
121
|
-
else {
|
122
|
-
this.selectionBox.onDragEnd();
|
123
|
-
}
|
124
|
-
this.selectionBoxHandlingEvt = false;
|
125
|
-
}
|
126
132
|
onPointerUp(event) {
|
133
|
+
this.autoscroller.stop();
|
127
134
|
if (!this.selectionBox)
|
128
135
|
return;
|
129
136
|
let currentPointer = event.current;
|
@@ -142,10 +149,20 @@ class SelectionTool extends BaseTool_1.default {
|
|
142
149
|
]);
|
143
150
|
}
|
144
151
|
else {
|
145
|
-
this.
|
152
|
+
if (!this.selectionBoxHandlingEvt) {
|
153
|
+
// Expand/shrink the selection rectangle, if applicable
|
154
|
+
this.selectionBox.resolveToObjects();
|
155
|
+
this.onSelectionUpdated();
|
156
|
+
}
|
157
|
+
else {
|
158
|
+
this.selectionBox.onDragEnd();
|
159
|
+
}
|
160
|
+
this.selectionBoxHandlingEvt = false;
|
161
|
+
this.lastPointer = null;
|
146
162
|
}
|
147
163
|
}
|
148
164
|
onGestureCancel() {
|
165
|
+
this.autoscroller.stop();
|
149
166
|
if (this.selectionBoxHandlingEvt) {
|
150
167
|
this.selectionBox?.onDragCancel();
|
151
168
|
}
|
@@ -158,6 +175,8 @@ class SelectionTool extends BaseTool_1.default {
|
|
158
175
|
this.prevSelectionBox = null;
|
159
176
|
}
|
160
177
|
this.expandingSelectionBox = false;
|
178
|
+
this.lastPointer = null;
|
179
|
+
this.selectionBoxHandlingEvt = false;
|
161
180
|
}
|
162
181
|
onSelectionUpdated() {
|
163
182
|
const selectedItemCount = this.selectionBox?.getSelectedItemCount() ?? 0;
|
@@ -283,6 +302,7 @@ class SelectionTool extends BaseTool_1.default {
|
|
283
302
|
const transform = math_1.Mat33.scaling2D(scaleFactor, this.editor.viewport.roundPoint(region.topLeft)).rightMul(math_1.Mat33.translation(regionCenter).rightMul(roundedRotationMatrix).rightMul(math_1.Mat33.translation(regionCenter.times(-1)))).rightMul(math_1.Mat33.translation(this.editor.viewport.roundPoint(math_1.Vec2.of(xTranslateSteps, yTranslateSteps).times(translateStepSize))));
|
284
303
|
const oldTransform = this.selectionBox.getTransform();
|
285
304
|
this.selectionBox.setTransform(oldTransform.rightMul(transform));
|
305
|
+
this.selectionBox.scrollTo();
|
286
306
|
}
|
287
307
|
if (this.selectionBox && !handled && (event.key === 'Delete' || event.key === 'Backspace')) {
|
288
308
|
this.editor.dispatch(this.selectionBox.deleteSelectedObjects());
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { Point2, Vec2 } from '@js-draw/math';
|
2
|
+
import Viewport from '../../Viewport';
|
3
|
+
type ScrollByCallback = (delta: Vec2) => void;
|
4
|
+
/**
|
5
|
+
* Automatically scrolls the viewport such that the user's pointer is visible.
|
6
|
+
*/
|
7
|
+
export default class ToPointerAutoscroller {
|
8
|
+
private viewport;
|
9
|
+
private scrollByCanvasDelta;
|
10
|
+
private started;
|
11
|
+
private updateLoopId;
|
12
|
+
private updateLoopRunning;
|
13
|
+
private targetPoint;
|
14
|
+
private scrollRate;
|
15
|
+
constructor(viewport: Viewport, scrollByCanvasDelta: ScrollByCallback);
|
16
|
+
private getScrollForPoint;
|
17
|
+
start(): void;
|
18
|
+
onPointerMove(pointerScreenPosition: Point2): void;
|
19
|
+
stop(): void;
|
20
|
+
private startUpdateLoop;
|
21
|
+
private stopUpdateLoop;
|
22
|
+
}
|
23
|
+
export {};
|
@@ -0,0 +1,83 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const math_1 = require("@js-draw/math");
|
7
|
+
const untilNextAnimationFrame_1 = __importDefault(require("../../util/untilNextAnimationFrame"));
|
8
|
+
/**
|
9
|
+
* Automatically scrolls the viewport such that the user's pointer is visible.
|
10
|
+
*/
|
11
|
+
class ToPointerAutoscroller {
|
12
|
+
constructor(viewport, scrollByCanvasDelta) {
|
13
|
+
this.viewport = viewport;
|
14
|
+
this.scrollByCanvasDelta = scrollByCanvasDelta;
|
15
|
+
this.started = false;
|
16
|
+
this.updateLoopId = 0;
|
17
|
+
this.updateLoopRunning = false;
|
18
|
+
this.targetPoint = null;
|
19
|
+
this.scrollRate = 1000; // px/s
|
20
|
+
}
|
21
|
+
getScrollForPoint(screenPoint) {
|
22
|
+
const screenSize = this.viewport.getScreenRectSize();
|
23
|
+
const screenRect = new math_1.Rect2(0, 0, screenSize.x, screenSize.y);
|
24
|
+
// Starts autoscrolling when the cursor is **outside of** this region
|
25
|
+
const marginSize = 44;
|
26
|
+
const autoscrollBoundary = screenRect.grownBy(-marginSize);
|
27
|
+
if (autoscrollBoundary.containsPoint(screenPoint)) {
|
28
|
+
return math_1.Vec2.zero;
|
29
|
+
}
|
30
|
+
const closestEdgePoint = autoscrollBoundary.getClosestPointOnBoundaryTo(screenPoint);
|
31
|
+
const distToEdge = closestEdgePoint.minus(screenPoint).magnitude();
|
32
|
+
const toEdge = closestEdgePoint.minus(screenPoint);
|
33
|
+
// Go faster for points further away from the boundary.
|
34
|
+
const maximumScaleFactor = 1.25;
|
35
|
+
const scaleFactor = Math.min(distToEdge / marginSize, maximumScaleFactor);
|
36
|
+
return toEdge.normalizedOrZero().times(scaleFactor);
|
37
|
+
}
|
38
|
+
start() {
|
39
|
+
this.started = true;
|
40
|
+
}
|
41
|
+
onPointerMove(pointerScreenPosition) {
|
42
|
+
if (!this.started) {
|
43
|
+
return;
|
44
|
+
}
|
45
|
+
if (this.getScrollForPoint(pointerScreenPosition) === math_1.Vec2.zero) {
|
46
|
+
this.stopUpdateLoop();
|
47
|
+
}
|
48
|
+
else {
|
49
|
+
this.targetPoint = pointerScreenPosition;
|
50
|
+
this.startUpdateLoop();
|
51
|
+
}
|
52
|
+
}
|
53
|
+
stop() {
|
54
|
+
this.targetPoint = null;
|
55
|
+
this.started = false;
|
56
|
+
this.stopUpdateLoop();
|
57
|
+
}
|
58
|
+
startUpdateLoop() {
|
59
|
+
if (this.updateLoopRunning) {
|
60
|
+
return;
|
61
|
+
}
|
62
|
+
(async () => {
|
63
|
+
this.updateLoopId++;
|
64
|
+
const currentUpdateLoopId = this.updateLoopId;
|
65
|
+
let lastUpdateTime = performance.now();
|
66
|
+
while (this.updateLoopId === currentUpdateLoopId && this.targetPoint) {
|
67
|
+
this.updateLoopRunning = true;
|
68
|
+
const currentTime = performance.now();
|
69
|
+
const deltaTimeMs = currentTime - lastUpdateTime;
|
70
|
+
const scrollDirection = this.getScrollForPoint(this.targetPoint);
|
71
|
+
const screenScrollAmount = scrollDirection.times(this.scrollRate * deltaTimeMs / 1000);
|
72
|
+
this.scrollByCanvasDelta(this.viewport.screenToCanvasTransform.transformVec3(screenScrollAmount));
|
73
|
+
lastUpdateTime = currentTime;
|
74
|
+
await (0, untilNextAnimationFrame_1.default)();
|
75
|
+
}
|
76
|
+
this.updateLoopRunning = false;
|
77
|
+
})();
|
78
|
+
}
|
79
|
+
stopUpdateLoop() {
|
80
|
+
this.updateLoopId++;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
exports.default = ToPointerAutoscroller;
|
@@ -9,26 +9,33 @@ export declare class DragTransformer {
|
|
9
9
|
constructor(editor: Editor, selection: Selection);
|
10
10
|
onDragStart(startPoint: Vec3): void;
|
11
11
|
onDragUpdate(canvasPos: Vec3): void;
|
12
|
-
onDragEnd(): void
|
12
|
+
onDragEnd(): void | Promise<void>;
|
13
13
|
}
|
14
14
|
export declare class ResizeTransformer {
|
15
15
|
private readonly editor;
|
16
16
|
private selection;
|
17
17
|
private mode;
|
18
18
|
private dragStartPoint;
|
19
|
+
private transformOrigin;
|
20
|
+
private scaleRate;
|
19
21
|
constructor(editor: Editor, selection: Selection);
|
20
22
|
onDragStart(startPoint: Vec3, mode: ResizeMode): void;
|
23
|
+
private computeOriginAndScaleRate;
|
21
24
|
onDragUpdate(canvasPos: Vec3): void;
|
22
|
-
onDragEnd(): void
|
25
|
+
onDragEnd(): void | Promise<void>;
|
23
26
|
}
|
24
27
|
export declare class RotateTransformer {
|
25
28
|
private readonly editor;
|
26
29
|
private selection;
|
27
30
|
private startAngle;
|
31
|
+
private targetRotation;
|
32
|
+
private maximumDistFromStart;
|
33
|
+
private startPoint;
|
28
34
|
constructor(editor: Editor, selection: Selection);
|
29
35
|
private getAngle;
|
30
36
|
private roundAngle;
|
31
37
|
onDragStart(startPoint: Vec3): void;
|
38
|
+
private setRotationTo;
|
32
39
|
onDragUpdate(canvasPos: Vec3): void;
|
33
|
-
onDragEnd(): void
|
40
|
+
onDragEnd(): void | Promise<void>;
|
34
41
|
}
|