js-draw 0.12.0 → 0.13.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/CHANGELOG.md +9 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +12 -0
- package/dist/src/Color4.js +16 -0
- package/dist/src/Editor.d.ts +33 -18
- package/dist/src/Editor.js +22 -19
- package/dist/src/EditorImage.d.ts +12 -0
- package/dist/src/EditorImage.js +12 -0
- package/dist/src/Pointer.d.ts +1 -0
- package/dist/src/Pointer.js +8 -0
- package/dist/src/SVGLoader.d.ts +5 -0
- package/dist/src/SVGLoader.js +6 -1
- package/dist/src/Viewport.d.ts +30 -1
- package/dist/src/Viewport.js +39 -9
- package/dist/src/commands/invertCommand.js +1 -1
- package/dist/src/components/AbstractComponent.d.ts +19 -0
- package/dist/src/components/AbstractComponent.js +17 -2
- package/dist/src/lib.d.ts +6 -3
- package/dist/src/lib.js +4 -1
- package/dist/src/math/Mat33.d.ts +1 -1
- package/dist/src/math/Mat33.js +1 -1
- package/dist/src/rendering/Display.d.ts +9 -11
- package/dist/src/rendering/Display.js +12 -14
- package/dist/src/rendering/lib.d.ts +3 -0
- package/dist/src/rendering/lib.js +3 -0
- package/dist/src/rendering/renderers/DummyRenderer.js +2 -2
- package/dist/src/rendering/renderers/SVGRenderer.js +4 -0
- package/dist/src/toolbar/IconProvider.d.ts +1 -1
- package/dist/src/toolbar/IconProvider.js +90 -29
- package/dist/src/tools/PanZoom.js +1 -1
- package/dist/src/tools/PasteHandler.d.ts +11 -4
- package/dist/src/tools/PasteHandler.js +12 -5
- package/dist/src/tools/Pen.d.ts +7 -2
- package/dist/src/tools/Pen.js +39 -6
- package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +3 -0
- package/dist/src/tools/SelectionTool/SelectionHandle.js +6 -0
- package/dist/src/tools/SelectionTool/SelectionTool.d.ts +3 -1
- package/dist/src/tools/SelectionTool/SelectionTool.js +53 -15
- package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
- package/dist/src/tools/ToolSwitcherShortcut.js +9 -3
- package/dist/src/tools/UndoRedoShortcut.js +2 -4
- package/package.json +2 -2
- package/src/Color4.test.ts +11 -0
- package/src/Color4.ts +22 -0
- package/src/Editor.ts +36 -22
- package/src/EditorImage.ts +12 -0
- package/src/Pointer.ts +19 -0
- package/src/SVGLoader.ts +6 -1
- package/src/Viewport.ts +50 -11
- package/src/commands/invertCommand.ts +1 -1
- package/src/components/AbstractComponent.ts +33 -2
- package/src/lib.ts +6 -3
- package/src/math/Mat33.ts +1 -1
- package/src/rendering/Display.ts +12 -15
- package/src/rendering/RenderingStyle.ts +1 -1
- package/src/rendering/lib.ts +4 -0
- package/src/rendering/renderers/DummyRenderer.ts +2 -3
- package/src/rendering/renderers/SVGRenderer.ts +4 -0
- package/src/rendering/renderers/TextOnlyRenderer.ts +0 -1
- package/src/toolbar/HTMLToolbar.ts +1 -1
- package/src/toolbar/IconProvider.ts +98 -31
- package/src/tools/PanZoom.ts +1 -1
- package/src/tools/PasteHandler.ts +12 -6
- package/src/tools/Pen.test.ts +44 -1
- package/src/tools/Pen.ts +53 -8
- package/src/tools/SelectionTool/SelectionHandle.ts +9 -0
- package/src/tools/SelectionTool/SelectionTool.ts +67 -15
- package/src/tools/ToolSwitcherShortcut.ts +10 -5
- package/src/tools/UndoRedoShortcut.ts +2 -5
- package/typedoc.json +2 -2
@@ -1,17 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
|
3
|
-
*
|
4
|
-
* @example
|
5
|
-
* ```
|
6
|
-
* const editor = new Editor(document.body);
|
7
|
-
* const w = editor.display.width;
|
8
|
-
* const h = editor.display.height;
|
9
|
-
* const center = Vec2.of(w / 2, h / 2);
|
10
|
-
* const colorAtCenter = editor.display.getColorAt(center);
|
11
|
-
* ```
|
12
|
-
*
|
13
|
-
* @packageDocumentation
|
14
|
-
*/
|
15
1
|
import CanvasRenderer from './renderers/CanvasRenderer';
|
16
2
|
import { EditorEventType } from '../types';
|
17
3
|
import DummyRenderer from './renderers/DummyRenderer';
|
@@ -25,6 +11,18 @@ export var RenderingMode;
|
|
25
11
|
RenderingMode[RenderingMode["CanvasRenderer"] = 1] = "CanvasRenderer";
|
26
12
|
// SVGRenderer is not supported by the main display
|
27
13
|
})(RenderingMode || (RenderingMode = {}));
|
14
|
+
/**
|
15
|
+
* Handles `HTMLCanvasElement`s (or other drawing surfaces if being used) used to display the editor's contents.
|
16
|
+
*
|
17
|
+
* @example
|
18
|
+
* ```
|
19
|
+
* const editor = new Editor(document.body);
|
20
|
+
* const w = editor.display.width;
|
21
|
+
* const h = editor.display.height;
|
22
|
+
* const center = Vec2.of(w / 2, h / 2);
|
23
|
+
* const colorAtCenter = editor.display.getColorAt(center);
|
24
|
+
* ```
|
25
|
+
*/
|
28
26
|
export default class Display {
|
29
27
|
/** @internal */
|
30
28
|
constructor(editor, mode, parent) {
|
@@ -1,6 +1,6 @@
|
|
1
|
-
// Renderer that outputs nothing. Useful for automated tests.
|
2
1
|
import { Vec2 } from '../../math/Vec2';
|
3
2
|
import AbstractRenderer from './AbstractRenderer';
|
3
|
+
// Renderer that outputs almost nothing. Useful for automated tests.
|
4
4
|
export default class DummyRenderer extends AbstractRenderer {
|
5
5
|
constructor(viewport) {
|
6
6
|
super(viewport);
|
@@ -17,7 +17,7 @@ export default class DummyRenderer extends AbstractRenderer {
|
|
17
17
|
}
|
18
18
|
displaySize() {
|
19
19
|
// Do we have a stored viewport size?
|
20
|
-
const viewportSize = this.getViewport().
|
20
|
+
const viewportSize = this.getViewport().getScreenRectSize();
|
21
21
|
// Don't use a 0x0 viewport — DummyRenderer is often used
|
22
22
|
// for tests that run without a display, so pretend we have a
|
23
23
|
// reasonable-sized display.
|
@@ -31,6 +31,10 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
31
31
|
stroke-linecap: round;
|
32
32
|
stroke-linejoin: round;
|
33
33
|
}
|
34
|
+
|
35
|
+
text {
|
36
|
+
white-space: pre;
|
37
|
+
}
|
34
38
|
`.replace(/\s+/g, '');
|
35
39
|
styleSheet.setAttribute('id', renderedStylesheetId);
|
36
40
|
this.elem.appendChild(styleSheet);
|
@@ -21,7 +21,7 @@ export default class IconProvider {
|
|
21
21
|
makeRotationLockIcon(): IconType;
|
22
22
|
makeInsertImageIcon(): IconType;
|
23
23
|
makeTextIcon(textStyle: TextStyle): IconType;
|
24
|
-
makePenIcon(
|
24
|
+
makePenIcon(strokeSize: number, color: string | Color4, rounded?: boolean): IconType;
|
25
25
|
makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType;
|
26
26
|
makePipetteIcon(color?: Color4): IconType;
|
27
27
|
makeResizeViewportIcon(): IconType;
|
@@ -30,7 +30,7 @@ export default class IconProvider {
|
|
30
30
|
makeUndoIcon() {
|
31
31
|
return this.makeRedoIcon(true);
|
32
32
|
}
|
33
|
-
// @param mirror - reflect across the x-axis
|
33
|
+
// @param mirror - reflect across the x-axis. This parameter is internal.
|
34
34
|
// @returns a redo icon.
|
35
35
|
makeRedoIcon(mirror = false) {
|
36
36
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
@@ -336,45 +336,106 @@ export default class IconProvider {
|
|
336
336
|
icon.appendChild(textNode);
|
337
337
|
return icon;
|
338
338
|
}
|
339
|
-
makePenIcon(
|
339
|
+
makePenIcon(strokeSize, color, rounded) {
|
340
340
|
if (color instanceof Color4) {
|
341
341
|
color = color.toHexString();
|
342
342
|
}
|
343
343
|
const icon = document.createElementNS(svgNamespace, 'svg');
|
344
344
|
icon.setAttribute('viewBox', '0 0 100 100');
|
345
|
-
const
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
345
|
+
const tipThickness = strokeSize / 2;
|
346
|
+
const inkTipPath = `
|
347
|
+
M ${15 - tipThickness},${80 - tipThickness}
|
348
|
+
${15 - tipThickness},${80 + tipThickness}
|
349
|
+
30,83
|
350
|
+
15,65
|
351
|
+
Z
|
352
|
+
`;
|
353
|
+
const trailStartEndY = 80 + tipThickness;
|
354
|
+
const inkTrailPath = `
|
355
|
+
m ${15 - tipThickness * 1.1},${trailStartEndY}
|
356
|
+
c 35,10 55,15 60,30
|
357
|
+
l ${35 + tipThickness * 1.2},${-10 - tipThickness}
|
358
|
+
C 80.47,98.32 50.5,${90 + tipThickness} 20,${trailStartEndY} Z
|
359
|
+
`;
|
360
|
+
const colorBubblePath = `
|
361
|
+
M 72.45,35.67
|
362
|
+
A 10,15 41.8 0 1 55,40.2 10,15 41.8 0 1 57.55,22.3 10,15 41.8 0 1 75,17.8 10,15 41.8 0 1 72.5,35.67
|
363
|
+
Z
|
364
|
+
`;
|
365
|
+
let gripMainPath = 'M 85,-25 25,35 h 10 v 10 h 10 v 10 h 10 v 10 h 10 l -5,10 60,-60 z';
|
366
|
+
let gripShadow1Path = 'M 25,35 H 35 L 90,-15 85,-25 Z';
|
367
|
+
let gripShadow2Path = 'M 60,75 65,65 H 55 l 55,-55 10,5 z';
|
368
|
+
if (rounded) {
|
369
|
+
gripMainPath = 'M 85,-25 25,35 c 15,0 40,30 35,40 l 60,-60 z';
|
370
|
+
gripShadow1Path = 'm 25,35 c 3.92361,0.384473 7.644275,0.980572 10,3 l 55,-53 -5,-10 z';
|
371
|
+
gripShadow2Path = 'M 60,75 C 61,66 59,65 56,59 l 54,-54 10,10 z';
|
354
372
|
}
|
355
|
-
const
|
356
|
-
const
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
373
|
+
const penTipPath = `M 25,35 ${10 - tipThickness / 4},${70 - tipThickness / 2} 20,75 25,85 60,75 70,55 45,25 Z`;
|
374
|
+
const pencilTipColor = Color4.fromHex('#f4d7d7');
|
375
|
+
const tipColor = pencilTipColor.mix(Color4.fromString(color), tipThickness / 40 - 0.1).toHexString();
|
376
|
+
const ink = `
|
377
|
+
<path
|
378
|
+
fill="${checkerboardPatternRef}"
|
379
|
+
d="${inkTipPath}"
|
380
|
+
/>
|
381
|
+
<path
|
382
|
+
fill="${checkerboardPatternRef}"
|
383
|
+
d="${inkTrailPath}"
|
384
|
+
/>
|
385
|
+
<path
|
386
|
+
fill="${color}"
|
387
|
+
d="${inkTipPath}"
|
388
|
+
/>
|
389
|
+
<path
|
390
|
+
fill="${color}"
|
391
|
+
d="${inkTrailPath}"
|
392
|
+
/>
|
393
|
+
`;
|
394
|
+
const penTip = `
|
395
|
+
<path
|
396
|
+
fill="${checkerboardPatternRef}"
|
397
|
+
d="${penTipPath}"
|
398
|
+
/>
|
399
|
+
<path
|
400
|
+
fill="${tipColor}"
|
401
|
+
stroke="${color}"
|
402
|
+
d="${penTipPath}"
|
403
|
+
/>
|
404
|
+
`;
|
405
|
+
const grip = `
|
363
406
|
<path
|
364
|
-
d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
|
365
407
|
${iconColorStrokeFill}
|
408
|
+
d="${gripMainPath}"
|
366
409
|
/>
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
410
|
+
|
411
|
+
<!-- shadows -->
|
412
|
+
<path
|
413
|
+
fill="rgba(150, 150, 150, 0.3)"
|
414
|
+
d="${gripShadow1Path}"
|
415
|
+
/>
|
416
|
+
<path
|
417
|
+
fill="rgba(100, 100, 100, 0.2)"
|
418
|
+
d="${gripShadow2Path}"
|
419
|
+
/>
|
420
|
+
|
421
|
+
<!-- color bubble -->
|
373
422
|
<path
|
374
|
-
|
375
|
-
|
376
|
-
stroke='${color}'
|
423
|
+
fill="${checkerboardPatternRef}"
|
424
|
+
d="${colorBubblePath}"
|
377
425
|
/>
|
426
|
+
<path
|
427
|
+
fill="${color}"
|
428
|
+
d="${colorBubblePath}"
|
429
|
+
/>
|
430
|
+
`;
|
431
|
+
icon.innerHTML = `
|
432
|
+
<defs>
|
433
|
+
${checkerboardPatternDef}
|
434
|
+
</defs>
|
435
|
+
<g>
|
436
|
+
${ink}
|
437
|
+
${penTip}
|
438
|
+
${grip}
|
378
439
|
</g>
|
379
440
|
`;
|
380
441
|
return icon;
|
@@ -266,7 +266,7 @@ export default class PanZoom extends BaseTool {
|
|
266
266
|
const toCanvas = this.editor.viewport.screenToCanvasTransform;
|
267
267
|
// Transform without including translation
|
268
268
|
const translation = toCanvas.transformVec3(Vec3.of(-delta.x, -delta.y, 0));
|
269
|
-
const pinchZoomScaleFactor = 1.
|
269
|
+
const pinchZoomScaleFactor = 1.03;
|
270
270
|
const transformUpdate = Mat33.scaling2D(Math.max(0.25, Math.min(Math.pow(pinchZoomScaleFactor, -delta.z), 4)), canvasPos).rightMul(Mat33.translation(translation));
|
271
271
|
this.updateTransform(transformUpdate, true);
|
272
272
|
return true;
|
@@ -1,10 +1,17 @@
|
|
1
|
-
/**
|
2
|
-
* A tool that handles paste events.
|
3
|
-
* @packageDocumentation
|
4
|
-
*/
|
5
1
|
import Editor from '../Editor';
|
6
2
|
import { PasteEvent } from '../types';
|
7
3
|
import BaseTool from './BaseTool';
|
4
|
+
/**
|
5
|
+
* A tool that handles paste events (e.g. as triggered by ctrl+V).
|
6
|
+
*
|
7
|
+
* @example
|
8
|
+
* While `ToolController` has a `PasteHandler` in its default list of tools,
|
9
|
+
* if a non-default set is being used, `PasteHandler` can be added as follows:
|
10
|
+
* ```ts
|
11
|
+
* const toolController = editor.toolController;
|
12
|
+
* toolController.addTool(new PasteHandler(editor));
|
13
|
+
* ```
|
14
|
+
*/
|
8
15
|
export default class PasteHandler extends BaseTool {
|
9
16
|
private editor;
|
10
17
|
constructor(editor: Editor);
|
@@ -1,7 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* A tool that handles paste events.
|
3
|
-
* @packageDocumentation
|
4
|
-
*/
|
5
1
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
6
2
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
7
3
|
return new (P || (P = Promise))(function (resolve, reject) {
|
@@ -18,12 +14,23 @@ import BaseTool from './BaseTool';
|
|
18
14
|
import TextTool from './TextTool';
|
19
15
|
import Color4 from '../Color4';
|
20
16
|
import ImageComponent from '../components/ImageComponent';
|
21
|
-
|
17
|
+
/**
|
18
|
+
* A tool that handles paste events (e.g. as triggered by ctrl+V).
|
19
|
+
*
|
20
|
+
* @example
|
21
|
+
* While `ToolController` has a `PasteHandler` in its default list of tools,
|
22
|
+
* if a non-default set is being used, `PasteHandler` can be added as follows:
|
23
|
+
* ```ts
|
24
|
+
* const toolController = editor.toolController;
|
25
|
+
* toolController.addTool(new PasteHandler(editor));
|
26
|
+
* ```
|
27
|
+
*/
|
22
28
|
export default class PasteHandler extends BaseTool {
|
23
29
|
constructor(editor) {
|
24
30
|
super(editor.notifier, editor.localization.pasteHandler);
|
25
31
|
this.editor = editor;
|
26
32
|
}
|
33
|
+
// @internal
|
27
34
|
onPaste(event) {
|
28
35
|
const mime = event.mime.toLowerCase();
|
29
36
|
if (mime === 'image/svg+xml') {
|
package/dist/src/tools/Pen.d.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import Editor from '../Editor';
|
3
3
|
import Pointer from '../Pointer';
|
4
|
-
import { KeyPressEvent, PointerEvt, StrokeDataPoint } from '../types';
|
4
|
+
import { KeyPressEvent, KeyUpEvent, PointerEvt, StrokeDataPoint } from '../types';
|
5
5
|
import BaseTool from './BaseTool';
|
6
6
|
import { ComponentBuilder, ComponentBuilderFactory } from '../components/builders/types';
|
7
7
|
export interface PenStyle {
|
@@ -14,6 +14,7 @@ export default class Pen extends BaseTool {
|
|
14
14
|
private builderFactory;
|
15
15
|
protected builder: ComponentBuilder | null;
|
16
16
|
private lastPoint;
|
17
|
+
private ctrlKeyPressed;
|
17
18
|
constructor(editor: Editor, description: string, style: PenStyle, builderFactory?: ComponentBuilderFactory);
|
18
19
|
private getPressureMultiplier;
|
19
20
|
protected toStrokePoint(pointer: Pointer): StrokeDataPoint;
|
@@ -23,6 +24,7 @@ export default class Pen extends BaseTool {
|
|
23
24
|
onPointerMove({ current }: PointerEvt): void;
|
24
25
|
onPointerUp({ current }: PointerEvt): void;
|
25
26
|
onGestureCancel(): void;
|
27
|
+
private finalizeStroke;
|
26
28
|
private noteUpdated;
|
27
29
|
setColor(color: Color4): void;
|
28
30
|
setThickness(thickness: number): void;
|
@@ -30,5 +32,8 @@ export default class Pen extends BaseTool {
|
|
30
32
|
getThickness(): number;
|
31
33
|
getColor(): Color4;
|
32
34
|
getStrokeFactory(): ComponentBuilderFactory;
|
33
|
-
|
35
|
+
setEnabled(enabled: boolean): void;
|
36
|
+
private isSnappingToGrid;
|
37
|
+
onKeyPress({ key, ctrlKey }: KeyPressEvent): boolean;
|
38
|
+
onKeyUp({ key }: KeyUpEvent): boolean;
|
34
39
|
}
|
package/dist/src/tools/Pen.js
CHANGED
@@ -11,6 +11,7 @@ export default class Pen extends BaseTool {
|
|
11
11
|
this.builderFactory = builderFactory;
|
12
12
|
this.builder = null;
|
13
13
|
this.lastPoint = null;
|
14
|
+
this.ctrlKeyPressed = false;
|
14
15
|
}
|
15
16
|
getPressureMultiplier() {
|
16
17
|
return 1 / this.editor.viewport.getScaleFactor() * this.style.thickness;
|
@@ -18,6 +19,9 @@ export default class Pen extends BaseTool {
|
|
18
19
|
// Converts a `pointer` to a `StrokeDataPoint`.
|
19
20
|
toStrokePoint(pointer) {
|
20
21
|
var _a;
|
22
|
+
if (this.isSnappingToGrid()) {
|
23
|
+
pointer = pointer.snappedToGrid(this.editor.viewport);
|
24
|
+
}
|
21
25
|
const minPressure = 0.3;
|
22
26
|
let pressure = Math.max((_a = pointer.pressure) !== null && _a !== void 0 ? _a : 1.0, minPressure);
|
23
27
|
if (!isFinite(pressure)) {
|
@@ -27,8 +31,9 @@ export default class Pen extends BaseTool {
|
|
27
31
|
console.assert(isFinite(pointer.canvasPos.length()), 'Non-finite canvas position!');
|
28
32
|
console.assert(isFinite(pointer.screenPos.length()), 'Non-finite screen position!');
|
29
33
|
console.assert(isFinite(pointer.timeStamp), 'Non-finite timeStamp on pointer!');
|
34
|
+
const pos = pointer.canvasPos;
|
30
35
|
return {
|
31
|
-
pos
|
36
|
+
pos,
|
32
37
|
width: pressure * this.getPressureMultiplier(),
|
33
38
|
color: this.style.color,
|
34
39
|
time: pointer.timeStamp,
|
@@ -65,6 +70,8 @@ export default class Pen extends BaseTool {
|
|
65
70
|
return false;
|
66
71
|
}
|
67
72
|
onPointerMove({ current }) {
|
73
|
+
if (!this.builder)
|
74
|
+
return;
|
68
75
|
this.addPointToStroke(this.toStrokePoint(current));
|
69
76
|
}
|
70
77
|
onPointerUp({ current }) {
|
@@ -76,7 +83,15 @@ export default class Pen extends BaseTool {
|
|
76
83
|
const currentPoint = this.toStrokePoint(current);
|
77
84
|
const strokePoint = Object.assign(Object.assign({}, currentPoint), { width: (_b = (_a = this.lastPoint) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : currentPoint.width });
|
78
85
|
this.addPointToStroke(strokePoint);
|
79
|
-
if (
|
86
|
+
if (current.isPrimary) {
|
87
|
+
this.finalizeStroke();
|
88
|
+
}
|
89
|
+
}
|
90
|
+
onGestureCancel() {
|
91
|
+
this.editor.clearWetInk();
|
92
|
+
}
|
93
|
+
finalizeStroke() {
|
94
|
+
if (this.builder) {
|
80
95
|
const stroke = this.builder.build();
|
81
96
|
this.previewStroke();
|
82
97
|
if (stroke.getBBox().area > 0) {
|
@@ -91,9 +106,6 @@ export default class Pen extends BaseTool {
|
|
91
106
|
this.builder = null;
|
92
107
|
this.editor.clearWetInk();
|
93
108
|
}
|
94
|
-
onGestureCancel() {
|
95
|
-
this.editor.clearWetInk();
|
96
|
-
}
|
97
109
|
noteUpdated() {
|
98
110
|
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|
99
111
|
kind: EditorEventType.ToolUpdated,
|
@@ -121,7 +133,12 @@ export default class Pen extends BaseTool {
|
|
121
133
|
getThickness() { return this.style.thickness; }
|
122
134
|
getColor() { return this.style.color; }
|
123
135
|
getStrokeFactory() { return this.builderFactory; }
|
124
|
-
|
136
|
+
setEnabled(enabled) {
|
137
|
+
super.setEnabled(enabled);
|
138
|
+
this.ctrlKeyPressed = false;
|
139
|
+
}
|
140
|
+
isSnappingToGrid() { return this.ctrlKeyPressed; }
|
141
|
+
onKeyPress({ key, ctrlKey }) {
|
125
142
|
key = key.toLowerCase();
|
126
143
|
let newThickness;
|
127
144
|
if (key === '-' || key === '_') {
|
@@ -135,6 +152,22 @@ export default class Pen extends BaseTool {
|
|
135
152
|
this.setThickness(newThickness);
|
136
153
|
return true;
|
137
154
|
}
|
155
|
+
if (key === 'control') {
|
156
|
+
this.ctrlKeyPressed = true;
|
157
|
+
return true;
|
158
|
+
}
|
159
|
+
// Ctrl+Z: End the stroke so that it can be undone/redone.
|
160
|
+
if (key === 'z' && ctrlKey && this.builder) {
|
161
|
+
this.finalizeStroke();
|
162
|
+
}
|
163
|
+
return false;
|
164
|
+
}
|
165
|
+
onKeyUp({ key }) {
|
166
|
+
key = key.toLowerCase();
|
167
|
+
if (key === 'control') {
|
168
|
+
this.ctrlKeyPressed = false;
|
169
|
+
return true;
|
170
|
+
}
|
138
171
|
return false;
|
139
172
|
}
|
140
173
|
}
|
@@ -17,6 +17,7 @@ export default class SelectionHandle {
|
|
17
17
|
private readonly onDragUpdate;
|
18
18
|
private readonly onDragEnd;
|
19
19
|
private element;
|
20
|
+
private snapToGrid;
|
20
21
|
constructor(shape: HandleShape, parentSide: Vec2, parent: Selection, onDragStart: DragStartCallback, onDragUpdate: DragUpdateCallback, onDragEnd: DragEndCallback);
|
21
22
|
/**
|
22
23
|
* Adds this to `container`, where `conatiner` should be the background/selection
|
@@ -32,4 +33,6 @@ export default class SelectionHandle {
|
|
32
33
|
handleDragStart(pointer: Pointer): void;
|
33
34
|
handleDragUpdate(pointer: Pointer): void;
|
34
35
|
handleDragEnd(): void;
|
36
|
+
setSnapToGrid(snap: boolean): void;
|
37
|
+
isSnappingToGrid(): boolean;
|
35
38
|
}
|
@@ -12,10 +12,12 @@ export default class SelectionTool extends BaseTool {
|
|
12
12
|
private lastEvtTarget;
|
13
13
|
private expandingSelectionBox;
|
14
14
|
private shiftKeyPressed;
|
15
|
+
private ctrlKeyPressed;
|
15
16
|
constructor(editor: Editor, description: string);
|
16
17
|
private makeSelectionBox;
|
18
|
+
private snapSelectionToGrid;
|
17
19
|
private selectionBoxHandlingEvt;
|
18
|
-
onPointerDown(
|
20
|
+
onPointerDown({ allPointers, current }: PointerEvt): boolean;
|
19
21
|
onPointerMove(event: PointerEvt): void;
|
20
22
|
private onSelectionUpdated;
|
21
23
|
private onGestureEnd;
|
@@ -1,6 +1,3 @@
|
|
1
|
-
// Allows users to select/transform portions of the `EditorImage`.
|
2
|
-
// With respect to `extend`ing, `SelectionTool` is not stable.
|
3
|
-
// @packageDocumentation
|
4
1
|
import Mat33 from '../../math/Mat33';
|
5
2
|
import { Vec2 } from '../../math/Vec2';
|
6
3
|
import { EditorEventType } from '../../types';
|
@@ -10,7 +7,8 @@ import SVGRenderer from '../../rendering/renderers/SVGRenderer';
|
|
10
7
|
import Selection from './Selection';
|
11
8
|
import TextComponent from '../../components/TextComponent';
|
12
9
|
export const cssPrefix = 'selection-tool-';
|
13
|
-
//
|
10
|
+
// Allows users to select/transform portions of the `EditorImage`.
|
11
|
+
// With respect to `extend`ing, `SelectionTool` is not stable.
|
14
12
|
export default class SelectionTool extends BaseTool {
|
15
13
|
constructor(editor, description) {
|
16
14
|
super(editor.notifier, description);
|
@@ -18,6 +16,7 @@ export default class SelectionTool extends BaseTool {
|
|
18
16
|
this.lastEvtTarget = null;
|
19
17
|
this.expandingSelectionBox = false;
|
20
18
|
this.shiftKeyPressed = false;
|
19
|
+
this.ctrlKeyPressed = false;
|
21
20
|
this.selectionBoxHandlingEvt = false;
|
22
21
|
this.handleOverlay = document.createElement('div');
|
23
22
|
editor.createHTMLOverlay(this.handleOverlay);
|
@@ -45,17 +44,37 @@ export default class SelectionTool extends BaseTool {
|
|
45
44
|
}
|
46
45
|
this.selectionBox.addTo(this.handleOverlay);
|
47
46
|
}
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
snapSelectionToGrid() {
|
48
|
+
if (!this.selectionBox)
|
49
|
+
throw new Error('No selection to snap!');
|
50
|
+
const topLeftOfBBox = this.selectionBox.region.topLeft;
|
51
|
+
const snapDistance = this.editor.viewport.snapToGrid(topLeftOfBBox).minus(topLeftOfBBox);
|
52
|
+
const oldTransform = this.selectionBox.getTransform();
|
53
|
+
this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(snapDistance)));
|
54
|
+
this.selectionBox.finalizeTransform();
|
55
|
+
}
|
56
|
+
onPointerDown({ allPointers, current }) {
|
57
|
+
const snapToGrid = this.ctrlKeyPressed;
|
58
|
+
if (snapToGrid) {
|
59
|
+
current = current.snappedToGrid(this.editor.viewport);
|
60
|
+
}
|
61
|
+
if (allPointers.length === 1 && current.isPrimary) {
|
62
|
+
let transforming = false;
|
63
|
+
if (this.lastEvtTarget && this.selectionBox) {
|
64
|
+
if (snapToGrid) {
|
65
|
+
this.snapSelectionToGrid();
|
66
|
+
}
|
67
|
+
const dragStartResult = this.selectionBox.onDragStart(current, this.lastEvtTarget);
|
68
|
+
if (dragStartResult) {
|
69
|
+
transforming = true;
|
70
|
+
this.selectionBoxHandlingEvt = true;
|
71
|
+
this.expandingSelectionBox = false;
|
72
|
+
}
|
54
73
|
}
|
55
|
-
|
74
|
+
if (!transforming) {
|
56
75
|
// Shift key: Combine the new and old selection boxes at the end of the gesture.
|
57
76
|
this.expandingSelectionBox = this.shiftKeyPressed;
|
58
|
-
this.makeSelectionBox(
|
77
|
+
this.makeSelectionBox(current.canvasPos);
|
59
78
|
}
|
60
79
|
return true;
|
61
80
|
}
|
@@ -64,11 +83,15 @@ export default class SelectionTool extends BaseTool {
|
|
64
83
|
onPointerMove(event) {
|
65
84
|
if (!this.selectionBox)
|
66
85
|
return;
|
86
|
+
let currentPointer = event.current;
|
87
|
+
if (this.ctrlKeyPressed) {
|
88
|
+
currentPointer = currentPointer.snappedToGrid(this.editor.viewport);
|
89
|
+
}
|
67
90
|
if (this.selectionBoxHandlingEvt) {
|
68
|
-
this.selectionBox.onDragUpdate(
|
91
|
+
this.selectionBox.onDragUpdate(currentPointer);
|
69
92
|
}
|
70
93
|
else {
|
71
|
-
this.selectionBox.setToPoint(
|
94
|
+
this.selectionBox.setToPoint(currentPointer.canvasPos);
|
72
95
|
}
|
73
96
|
}
|
74
97
|
onSelectionUpdated() {
|
@@ -113,7 +136,11 @@ export default class SelectionTool extends BaseTool {
|
|
113
136
|
onPointerUp(event) {
|
114
137
|
if (!this.selectionBox)
|
115
138
|
return;
|
116
|
-
|
139
|
+
let currentPointer = event.current;
|
140
|
+
if (this.ctrlKeyPressed) {
|
141
|
+
currentPointer = currentPointer.snappedToGrid(this.editor.viewport);
|
142
|
+
}
|
143
|
+
this.selectionBox.setToPoint(currentPointer.canvasPos);
|
117
144
|
// Were we expanding the previous selection?
|
118
145
|
if (this.expandingSelectionBox && this.prevSelectionBox) {
|
119
146
|
// If so, finish expanding.
|
@@ -142,6 +169,10 @@ export default class SelectionTool extends BaseTool {
|
|
142
169
|
this.expandingSelectionBox = false;
|
143
170
|
}
|
144
171
|
onKeyPress(event) {
|
172
|
+
if (event.key === 'Control') {
|
173
|
+
this.ctrlKeyPressed = true;
|
174
|
+
return true;
|
175
|
+
}
|
145
176
|
if (this.selectionBox && event.ctrlKey && event.key === 'd') {
|
146
177
|
// Handle duplication on key up — we don't want to accidentally duplicate
|
147
178
|
// many times.
|
@@ -235,6 +266,10 @@ export default class SelectionTool extends BaseTool {
|
|
235
266
|
return handled;
|
236
267
|
}
|
237
268
|
onKeyUp(evt) {
|
269
|
+
if (evt.key === 'Control') {
|
270
|
+
this.ctrlKeyPressed = false;
|
271
|
+
return true;
|
272
|
+
}
|
238
273
|
if (evt.key === 'Shift') {
|
239
274
|
this.shiftKeyPressed = false;
|
240
275
|
return true;
|
@@ -291,6 +326,8 @@ export default class SelectionTool extends BaseTool {
|
|
291
326
|
// Clear the selection
|
292
327
|
this.handleOverlay.replaceChildren();
|
293
328
|
this.selectionBox = null;
|
329
|
+
this.shiftKeyPressed = false;
|
330
|
+
this.ctrlKeyPressed = false;
|
294
331
|
this.handleOverlay.style.display = enabled ? 'block' : 'none';
|
295
332
|
if (enabled) {
|
296
333
|
this.handleOverlay.tabIndex = 0;
|
@@ -355,4 +392,5 @@ SelectionTool.handleableKeys = [
|
|
355
392
|
'e', 'j', 'ArrowDown',
|
356
393
|
'r', 'R',
|
357
394
|
'i', 'I', 'o', 'O',
|
395
|
+
'Control',
|
358
396
|
];
|
@@ -1,6 +1,14 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
2
|
import { KeyPressEvent } from '../types';
|
3
3
|
import BaseTool from './BaseTool';
|
4
|
+
/**
|
5
|
+
* Handles keyboard events used, by default, to select tools. By default,
|
6
|
+
* 1 maps to the first primary tool, 2 to the second primary tool, ... .
|
7
|
+
*
|
8
|
+
* This is in the default set of {@link ToolController} tools.
|
9
|
+
*
|
10
|
+
* @deprecated This may be replaced in the future.
|
11
|
+
*/
|
4
12
|
export default class ToolSwitcherShortcut extends BaseTool {
|
5
13
|
private editor;
|
6
14
|
constructor(editor: Editor);
|
@@ -1,12 +1,18 @@
|
|
1
|
-
// Handles ctrl+1, ctrl+2, ctrl+3, ..., shortcuts for switching tools.
|
2
|
-
// @packageDocumentation
|
3
1
|
import BaseTool from './BaseTool';
|
4
|
-
|
2
|
+
/**
|
3
|
+
* Handles keyboard events used, by default, to select tools. By default,
|
4
|
+
* 1 maps to the first primary tool, 2 to the second primary tool, ... .
|
5
|
+
*
|
6
|
+
* This is in the default set of {@link ToolController} tools.
|
7
|
+
*
|
8
|
+
* @deprecated This may be replaced in the future.
|
9
|
+
*/
|
5
10
|
export default class ToolSwitcherShortcut extends BaseTool {
|
6
11
|
constructor(editor) {
|
7
12
|
super(editor.notifier, editor.localization.changeTool);
|
8
13
|
this.editor = editor;
|
9
14
|
}
|
15
|
+
// @internal
|
10
16
|
onKeyPress({ key }) {
|
11
17
|
const toolController = this.editor.toolController;
|
12
18
|
const primaryTools = toolController.getPrimaryTools();
|
@@ -1,13 +1,11 @@
|
|
1
|
-
// Handles ctrl+Z, ctrl+Shift+Z keyboard shortcuts.
|
2
|
-
// @packageDocumentation
|
3
1
|
import BaseTool from './BaseTool';
|
4
|
-
//
|
2
|
+
// Handles ctrl+Z, ctrl+Shift+Z keyboard shortcuts.
|
5
3
|
export default class UndoRedoShortcut extends BaseTool {
|
6
4
|
constructor(editor) {
|
7
5
|
super(editor.notifier, editor.localization.undoRedoTool);
|
8
6
|
this.editor = editor;
|
9
7
|
}
|
10
|
-
//
|
8
|
+
// @internal
|
11
9
|
onKeyPress({ key, ctrlKey }) {
|
12
10
|
if (ctrlKey) {
|
13
11
|
if (key === 'z') {
|