js-draw 0.2.3 → 0.3.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.
- package/CHANGELOG.md +11 -0
- package/dist/bundle.js +1 -1
- package/dist/src/EditorImage.js +0 -1
- package/dist/src/components/Stroke.js +11 -6
- package/dist/src/components/builders/FreehandLineBuilder.js +6 -6
- package/dist/src/components/lib.d.ts +2 -0
- package/dist/src/components/lib.js +2 -0
- package/dist/src/lib.d.ts +5 -1
- package/dist/src/lib.js +5 -1
- package/dist/src/math/LineSegment2.d.ts +2 -0
- package/dist/src/math/LineSegment2.js +6 -0
- package/dist/src/math/Path.d.ts +5 -1
- package/dist/src/math/Path.js +89 -7
- package/dist/src/math/Rect2.js +1 -1
- package/dist/src/math/Triangle.d.ts +11 -0
- package/dist/src/math/Triangle.js +19 -0
- package/dist/src/rendering/Display.js +3 -3
- package/dist/src/rendering/caching/RenderingCacheNode.js +2 -1
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +6 -6
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +10 -11
- package/dist/src/rendering/renderers/SVGRenderer.js +27 -68
- package/dist/src/toolbar/HTMLToolbar.d.ts +5 -1
- package/dist/src/toolbar/HTMLToolbar.js +30 -31
- package/dist/src/toolbar/icons.d.ts +1 -1
- package/dist/src/toolbar/icons.js +4 -0
- package/dist/src/toolbar/lib.d.ts +3 -0
- package/dist/src/toolbar/lib.js +4 -0
- package/dist/src/toolbar/makeColorInput.js +2 -2
- package/dist/src/toolbar/types.d.ts +0 -4
- package/dist/src/toolbar/types.js +1 -5
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +3 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +21 -1
- package/dist/src/toolbar/widgets/{EraserWidget.d.ts → EraserToolWidget.d.ts} +1 -1
- package/dist/src/toolbar/widgets/{EraserWidget.js → EraserToolWidget.js} +1 -1
- package/dist/src/toolbar/widgets/{PenWidget.d.ts → PenToolWidget.d.ts} +2 -3
- package/dist/src/toolbar/widgets/{PenWidget.js → PenToolWidget.js} +6 -7
- package/dist/src/toolbar/widgets/{SelectionWidget.d.ts → SelectionToolWidget.d.ts} +1 -1
- package/dist/src/toolbar/widgets/{SelectionWidget.js → SelectionToolWidget.js} +1 -1
- package/dist/src/toolbar/widgets/lib.d.ts +8 -0
- package/dist/src/toolbar/widgets/lib.js +8 -0
- package/dist/src/tools/BaseTool.d.ts +1 -2
- package/dist/src/tools/BaseTool.js +6 -0
- package/dist/src/tools/Eraser.d.ts +0 -2
- package/dist/src/tools/Eraser.js +0 -2
- package/dist/src/tools/PanZoom.d.ts +0 -2
- package/dist/src/tools/PanZoom.js +0 -2
- package/dist/src/tools/Pen.d.ts +9 -9
- package/dist/src/tools/Pen.js +23 -6
- package/dist/src/tools/PipetteTool.d.ts +0 -2
- package/dist/src/tools/PipetteTool.js +0 -2
- package/dist/src/tools/SelectionTool.d.ts +0 -2
- package/dist/src/tools/SelectionTool.js +4 -3
- package/dist/src/tools/TextTool.d.ts +0 -2
- package/dist/src/tools/TextTool.js +0 -2
- package/dist/src/tools/ToolController.d.ts +8 -11
- package/dist/src/tools/ToolController.js +37 -18
- package/dist/src/tools/ToolEnabledGroup.js +1 -1
- package/dist/src/tools/ToolSwitcherShortcut.d.ts +8 -0
- package/dist/src/tools/ToolSwitcherShortcut.js +26 -0
- package/dist/src/tools/UndoRedoShortcut.d.ts +0 -2
- package/dist/src/tools/UndoRedoShortcut.js +3 -2
- package/dist/src/tools/lib.d.ts +13 -0
- package/dist/src/tools/lib.js +13 -0
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +8 -2
- package/dist/src/types.js +1 -0
- package/package.json +2 -2
- package/src/EditorImage.ts +0 -1
- package/src/components/Stroke.test.ts +5 -0
- package/src/components/Stroke.ts +13 -7
- package/src/components/builders/FreehandLineBuilder.ts +6 -6
- package/src/components/lib.ts +3 -0
- package/src/lib.ts +5 -1
- package/src/math/LineSegment2.ts +8 -0
- package/src/math/Path.test.ts +53 -0
- package/src/math/Path.toString.test.ts +4 -2
- package/src/math/Path.ts +109 -11
- package/src/math/Rect2.ts +1 -1
- package/src/math/Triangle.ts +29 -0
- package/src/rendering/Display.ts +3 -3
- package/src/rendering/caching/RenderingCacheNode.ts +3 -1
- package/src/rendering/renderers/AbstractRenderer.ts +1 -0
- package/src/rendering/renderers/CanvasRenderer.ts +6 -6
- package/src/rendering/renderers/SVGRenderer.ts +30 -84
- package/src/toolbar/HTMLToolbar.ts +35 -38
- package/src/toolbar/icons.ts +5 -1
- package/src/toolbar/lib.ts +4 -0
- package/src/toolbar/makeColorInput.ts +1 -2
- package/src/toolbar/types.ts +1 -5
- package/src/toolbar/widgets/BaseWidget.ts +27 -1
- package/src/toolbar/widgets/{EraserWidget.ts → EraserToolWidget.ts} +1 -1
- package/src/toolbar/widgets/{PenWidget.ts → PenToolWidget.ts} +10 -9
- package/src/toolbar/widgets/{SelectionWidget.ts → SelectionToolWidget.ts} +1 -1
- package/src/toolbar/widgets/lib.ts +10 -0
- package/src/tools/BaseTool.ts +8 -3
- package/src/tools/Eraser.ts +0 -2
- package/src/tools/PanZoom.ts +0 -2
- package/src/tools/Pen.ts +32 -13
- package/src/tools/PipetteTool.ts +0 -3
- package/src/tools/SelectionTool.test.ts +1 -2
- package/src/tools/SelectionTool.ts +5 -3
- package/src/tools/TextTool.ts +0 -2
- package/src/tools/ToolController.ts +44 -20
- package/src/tools/ToolEnabledGroup.ts +1 -1
- package/src/tools/ToolSwitcherShortcut.ts +34 -0
- package/src/tools/UndoRedoShortcut.ts +4 -4
- package/src/tools/lib.ts +18 -0
- package/src/tools/localization.ts +2 -0
- package/src/types.ts +13 -1
@@ -1,5 +1,5 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
|
-
import { InputEvtType } from '../../types';
|
2
|
+
import { EditorEventType, InputEvtType } from '../../types';
|
3
3
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
4
4
|
import { makeDropdownIcon } from '../icons';
|
5
5
|
import { ToolbarLocalization } from '../localization';
|
@@ -14,6 +14,7 @@ export default abstract class BaseWidget {
|
|
14
14
|
#hasDropdown: boolean;
|
15
15
|
private disabled: boolean = false;
|
16
16
|
private subWidgets: BaseWidget[] = [];
|
17
|
+
private toplevel: boolean = true;
|
17
18
|
|
18
19
|
public constructor(
|
19
20
|
protected editor: Editor,
|
@@ -46,6 +47,7 @@ export default abstract class BaseWidget {
|
|
46
47
|
|
47
48
|
for (const widget of this.subWidgets) {
|
48
49
|
widget.addTo(dropdown);
|
50
|
+
widget.setIsToplevel(false);
|
49
51
|
}
|
50
52
|
return true;
|
51
53
|
}
|
@@ -103,6 +105,7 @@ export default abstract class BaseWidget {
|
|
103
105
|
}
|
104
106
|
|
105
107
|
// Adds this to [parent]. This can only be called once for each ToolbarWidget.
|
108
|
+
// @internal
|
106
109
|
public addTo(parent: HTMLElement) {
|
107
110
|
this.label.innerText = this.getTitle();
|
108
111
|
|
@@ -119,6 +122,19 @@ export default abstract class BaseWidget {
|
|
119
122
|
this.dropdownIcon = this.createDropdownIcon();
|
120
123
|
this.button.appendChild(this.dropdownIcon);
|
121
124
|
this.container.appendChild(this.dropdownContainer);
|
125
|
+
|
126
|
+
this.editor.notifier.on(EditorEventType.ToolbarDropdownShown, (evt) => {
|
127
|
+
if (
|
128
|
+
evt.kind === EditorEventType.ToolbarDropdownShown
|
129
|
+
&& evt.parentWidget !== this
|
130
|
+
|
131
|
+
// Don't hide if a submenu wash shown (it might be a submenu of
|
132
|
+
// the current menu).
|
133
|
+
&& evt.parentWidget.toplevel
|
134
|
+
) {
|
135
|
+
this.setDropdownVisible(false);
|
136
|
+
}
|
137
|
+
});
|
122
138
|
}
|
123
139
|
|
124
140
|
this.setDropdownVisible(false);
|
@@ -171,6 +187,11 @@ export default abstract class BaseWidget {
|
|
171
187
|
this.editor.announceForAccessibility(
|
172
188
|
this.localizationTable.dropdownShown(this.getTitle())
|
173
189
|
);
|
190
|
+
|
191
|
+
this.editor.notifier.dispatch(EditorEventType.ToolbarDropdownShown, {
|
192
|
+
kind: EditorEventType.ToolbarDropdownShown,
|
193
|
+
parentWidget: this,
|
194
|
+
});
|
174
195
|
} else {
|
175
196
|
this.dropdownContainer.classList.add('hidden');
|
176
197
|
this.container.classList.remove('dropdownVisible');
|
@@ -195,6 +216,11 @@ export default abstract class BaseWidget {
|
|
195
216
|
}
|
196
217
|
}
|
197
218
|
|
219
|
+
/** Set whether the widget is contained within another. @internal */
|
220
|
+
protected setIsToplevel(toplevel: boolean) {
|
221
|
+
this.toplevel = toplevel;
|
222
|
+
}
|
223
|
+
|
198
224
|
protected isDropdownVisible(): boolean {
|
199
225
|
return !this.dropdownContainer.classList.contains('hidden');
|
200
226
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { makeEraserIcon } from '../icons';
|
2
2
|
import BaseToolWidget from './BaseToolWidget';
|
3
3
|
|
4
|
-
export default class
|
4
|
+
export default class EraserToolWidget extends BaseToolWidget {
|
5
5
|
protected getTitle(): string {
|
6
6
|
return this.localizationTable.eraser;
|
7
7
|
}
|
@@ -1,5 +1,3 @@
|
|
1
|
-
// @internal @packageDocumentation
|
2
|
-
|
3
1
|
import { makeArrowBuilder } from '../../components/builders/ArrowBuilder';
|
4
2
|
import { makeFreehandLineBuilder } from '../../components/builders/FreehandLineBuilder';
|
5
3
|
import { makeLineBuilder } from '../../components/builders/LineBuilder';
|
@@ -15,12 +13,15 @@ import makeColorInput from '../makeColorInput';
|
|
15
13
|
import BaseToolWidget from './BaseToolWidget';
|
16
14
|
|
17
15
|
|
18
|
-
interface PenTypeRecord {
|
16
|
+
export interface PenTypeRecord {
|
17
|
+
// Description of the factory (e.g. 'Freehand line')
|
19
18
|
name: string;
|
19
|
+
|
20
|
+
// Creates an `AbstractComponent` from pen input.
|
20
21
|
factory: ComponentBuilderFactory;
|
21
22
|
}
|
22
23
|
|
23
|
-
export default class
|
24
|
+
export default class PenToolWidget extends BaseToolWidget {
|
24
25
|
private updateInputs: ()=> void = () => {};
|
25
26
|
protected penTypes: PenTypeRecord[];
|
26
27
|
|
@@ -97,8 +98,8 @@ export default class PenWidget extends BaseToolWidget {
|
|
97
98
|
const objectTypeSelect = document.createElement('select');
|
98
99
|
|
99
100
|
// Give inputs IDs so we can label them with a <label for=...>Label text</label>
|
100
|
-
thicknessInput.id = `${toolbarCSSPrefix}thicknessInput${
|
101
|
-
objectTypeSelect.id = `${toolbarCSSPrefix}builderSelect${
|
101
|
+
thicknessInput.id = `${toolbarCSSPrefix}thicknessInput${PenToolWidget.idCounter++}`;
|
102
|
+
objectTypeSelect.id = `${toolbarCSSPrefix}builderSelect${PenToolWidget.idCounter++}`;
|
102
103
|
|
103
104
|
thicknessLabel.innerText = this.localizationTable.thicknessLabel;
|
104
105
|
thicknessLabel.setAttribute('for', thicknessInput.id);
|
@@ -106,7 +107,7 @@ export default class PenWidget extends BaseToolWidget {
|
|
106
107
|
objectSelectLabel.setAttribute('for', objectTypeSelect.id);
|
107
108
|
|
108
109
|
thicknessInput.type = 'range';
|
109
|
-
thicknessInput.min = '
|
110
|
+
thicknessInput.min = '2';
|
110
111
|
thicknessInput.max = '20';
|
111
112
|
thicknessInput.step = '1';
|
112
113
|
thicknessInput.oninput = () => {
|
@@ -133,7 +134,7 @@ export default class PenWidget extends BaseToolWidget {
|
|
133
134
|
this.tool.setColor(color);
|
134
135
|
});
|
135
136
|
|
136
|
-
colorInput.id = `${toolbarCSSPrefix}colorInput${
|
137
|
+
colorInput.id = `${toolbarCSSPrefix}colorInput${PenToolWidget.idCounter++}`;
|
137
138
|
colorLabel.innerText = this.localizationTable.colorLabel;
|
138
139
|
colorLabel.setAttribute('for', colorInput.id);
|
139
140
|
|
@@ -164,4 +165,4 @@ export default class PenWidget extends BaseToolWidget {
|
|
164
165
|
dropdown.replaceChildren(container);
|
165
166
|
return true;
|
166
167
|
}
|
167
|
-
}
|
168
|
+
}
|
@@ -6,7 +6,7 @@ import { ToolbarLocalization } from '../localization';
|
|
6
6
|
import ActionButtonWidget from './ActionButtonWidget';
|
7
7
|
import BaseToolWidget from './BaseToolWidget';
|
8
8
|
|
9
|
-
export class
|
9
|
+
export default class SelectionToolWidget extends BaseToolWidget {
|
10
10
|
public constructor(
|
11
11
|
editor: Editor, private tool: SelectionTool, localization: ToolbarLocalization
|
12
12
|
) {
|
@@ -0,0 +1,10 @@
|
|
1
|
+
|
2
|
+
export { default as ActionButtonWidget } from './ActionButtonWidget';
|
3
|
+
export { default as BaseToolWidget } from './BaseToolWidget';
|
4
|
+
export { default as BaseWidget } from './BaseWidget';
|
5
|
+
|
6
|
+
export { default as PenToolWidget } from './PenToolWidget';
|
7
|
+
export { default as TextToolWidget } from './TextToolWidget';
|
8
|
+
export { default as HandToolWidget } from './HandToolWidget';
|
9
|
+
export { default as SelectionToolWidget } from './SelectionToolWidget';
|
10
|
+
export { default as EraserToolWidget } from './EraserToolWidget';
|
package/src/tools/BaseTool.ts
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, EditorEventType, KeyPressEvent, KeyUpEvent } from '../types';
|
2
|
-
import { ToolType } from './ToolController';
|
3
2
|
import ToolEnabledGroup from './ToolEnabledGroup';
|
4
3
|
|
5
4
|
export default abstract class BaseTool implements PointerEvtListener {
|
@@ -11,8 +10,6 @@ export default abstract class BaseTool implements PointerEvtListener {
|
|
11
10
|
public onPointerUp(_event: PointerEvt) { }
|
12
11
|
public onGestureCancel() { }
|
13
12
|
|
14
|
-
public abstract readonly kind: ToolType;
|
15
|
-
|
16
13
|
protected constructor(private notifier: EditorNotifier, public readonly description: string) {
|
17
14
|
}
|
18
15
|
|
@@ -59,5 +56,13 @@ export default abstract class BaseTool implements PointerEvtListener {
|
|
59
56
|
|
60
57
|
this.group = group;
|
61
58
|
}
|
59
|
+
|
60
|
+
public getToolGroup(): ToolEnabledGroup|null {
|
61
|
+
if (this.group) {
|
62
|
+
return this.group;
|
63
|
+
}
|
64
|
+
|
65
|
+
return null;
|
66
|
+
}
|
62
67
|
}
|
63
68
|
|
package/src/tools/Eraser.ts
CHANGED
@@ -4,14 +4,12 @@ import Editor from '../Editor';
|
|
4
4
|
import { Point2 } from '../math/Vec2';
|
5
5
|
import LineSegment2 from '../math/LineSegment2';
|
6
6
|
import Erase from '../commands/Erase';
|
7
|
-
import { ToolType } from './ToolController';
|
8
7
|
import AbstractComponent from '../components/AbstractComponent';
|
9
8
|
import { PointerDevice } from '../Pointer';
|
10
9
|
|
11
10
|
export default class Eraser extends BaseTool {
|
12
11
|
private lastPoint: Point2;
|
13
12
|
private command: Erase|null = null;
|
14
|
-
public kind: ToolType = ToolType.Eraser;
|
15
13
|
private toRemove: AbstractComponent[];
|
16
14
|
|
17
15
|
public constructor(private editor: Editor, description: string) {
|
package/src/tools/PanZoom.ts
CHANGED
@@ -7,7 +7,6 @@ import Pointer, { PointerDevice } from '../Pointer';
|
|
7
7
|
import { EditorEventType, KeyPressEvent, PointerEvt, WheelEvt } from '../types';
|
8
8
|
import { Viewport, ViewportTransform } from '../Viewport';
|
9
9
|
import BaseTool from './BaseTool';
|
10
|
-
import { ToolType } from './ToolController';
|
11
10
|
|
12
11
|
interface PinchData {
|
13
12
|
canvasCenter: Point2;
|
@@ -25,7 +24,6 @@ export enum PanZoomMode {
|
|
25
24
|
}
|
26
25
|
|
27
26
|
export default class PanZoom extends BaseTool {
|
28
|
-
public readonly kind: ToolType.PanZoom = ToolType.PanZoom;
|
29
27
|
private transform: ViewportTransform|null = null;
|
30
28
|
|
31
29
|
private lastAngle: number;
|
package/src/tools/Pen.ts
CHANGED
@@ -3,9 +3,8 @@ import Editor from '../Editor';
|
|
3
3
|
import EditorImage from '../EditorImage';
|
4
4
|
import Pointer, { PointerDevice } from '../Pointer';
|
5
5
|
import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder';
|
6
|
-
import { EditorEventType, PointerEvt, StrokeDataPoint } from '../types';
|
6
|
+
import { EditorEventType, KeyPressEvent, PointerEvt, StrokeDataPoint } from '../types';
|
7
7
|
import BaseTool from './BaseTool';
|
8
|
-
import { ToolType } from './ToolController';
|
9
8
|
import { ComponentBuilder, ComponentBuilderFactory } from '../components/builders/types';
|
10
9
|
|
11
10
|
export interface PenStyle {
|
@@ -14,12 +13,10 @@ export interface PenStyle {
|
|
14
13
|
}
|
15
14
|
|
16
15
|
export default class Pen extends BaseTool {
|
17
|
-
|
18
|
-
|
16
|
+
protected builder: ComponentBuilder|null = null;
|
17
|
+
protected builderFactory: ComponentBuilderFactory = makeFreehandLineBuilder;
|
19
18
|
private lastPoint: StrokeDataPoint|null = null;
|
20
19
|
|
21
|
-
public readonly kind: ToolType = ToolType.Pen;
|
22
|
-
|
23
20
|
public constructor(
|
24
21
|
private editor: Editor,
|
25
22
|
description: string,
|
@@ -32,7 +29,8 @@ export default class Pen extends BaseTool {
|
|
32
29
|
return 1 / this.editor.viewport.getScaleFactor() * this.style.thickness;
|
33
30
|
}
|
34
31
|
|
35
|
-
|
32
|
+
// Converts a `pointer` to a `StrokeDataPoint`.
|
33
|
+
protected toStrokePoint(pointer: Pointer): StrokeDataPoint {
|
36
34
|
const minPressure = 0.3;
|
37
35
|
let pressure = Math.max(pointer.pressure ?? 1.0, minPressure);
|
38
36
|
|
@@ -52,12 +50,14 @@ export default class Pen extends BaseTool {
|
|
52
50
|
};
|
53
51
|
}
|
54
52
|
|
55
|
-
|
53
|
+
// Displays the stroke that is currently being built with the display's `wetInkRenderer`.
|
54
|
+
protected previewStroke() {
|
56
55
|
this.editor.clearWetInk();
|
57
56
|
this.builder?.preview(this.editor.display.getWetInkRenderer());
|
58
57
|
}
|
59
58
|
|
60
|
-
|
59
|
+
// Throws if no stroke builder exists.
|
60
|
+
protected addPointToStroke(point: StrokeDataPoint) {
|
61
61
|
if (!this.builder) {
|
62
62
|
throw new Error('No stroke is currently being generated.');
|
63
63
|
}
|
@@ -78,7 +78,7 @@ export default class Pen extends BaseTool {
|
|
78
78
|
}
|
79
79
|
|
80
80
|
if ((allPointers.length === 1 && !isEraser) || anyDeviceIsStylus) {
|
81
|
-
this.builder = this.builderFactory(this.
|
81
|
+
this.builder = this.builderFactory(this.toStrokePoint(current), this.editor.viewport);
|
82
82
|
return true;
|
83
83
|
}
|
84
84
|
|
@@ -86,7 +86,7 @@ export default class Pen extends BaseTool {
|
|
86
86
|
}
|
87
87
|
|
88
88
|
public onPointerMove({ current }: PointerEvt): void {
|
89
|
-
this.addPointToStroke(this.
|
89
|
+
this.addPointToStroke(this.toStrokePoint(current));
|
90
90
|
}
|
91
91
|
|
92
92
|
public onPointerUp({ current }: PointerEvt): void {
|
@@ -95,7 +95,7 @@ export default class Pen extends BaseTool {
|
|
95
95
|
}
|
96
96
|
|
97
97
|
// onPointerUp events can have zero pressure. Use the last pressure instead.
|
98
|
-
const currentPoint = this.
|
98
|
+
const currentPoint = this.toStrokePoint(current);
|
99
99
|
const strokePoint = {
|
100
100
|
...currentPoint,
|
101
101
|
width: this.lastPoint?.width ?? currentPoint.width,
|
@@ -118,7 +118,7 @@ export default class Pen extends BaseTool {
|
|
118
118
|
this.editor.clearWetInk();
|
119
119
|
}
|
120
120
|
|
121
|
-
public onGestureCancel()
|
121
|
+
public onGestureCancel() {
|
122
122
|
this.editor.clearWetInk();
|
123
123
|
}
|
124
124
|
|
@@ -159,4 +159,23 @@ export default class Pen extends BaseTool {
|
|
159
159
|
public getThickness() { return this.style.thickness; }
|
160
160
|
public getColor() { return this.style.color; }
|
161
161
|
public getStrokeFactory() { return this.builderFactory; }
|
162
|
+
|
163
|
+
public onKeyPress({ key }: KeyPressEvent): boolean {
|
164
|
+
key = key.toLowerCase();
|
165
|
+
|
166
|
+
let newThickness: number|undefined;
|
167
|
+
if (key === '-' || key === '_') {
|
168
|
+
newThickness = this.getThickness() * 2/3;
|
169
|
+
} else if (key === '+' || key === '=') {
|
170
|
+
newThickness = this.getThickness() * 3/2;
|
171
|
+
}
|
172
|
+
|
173
|
+
if (newThickness !== undefined) {
|
174
|
+
newThickness = Math.min(Math.max(1, newThickness), 128);
|
175
|
+
this.setThickness(newThickness);
|
176
|
+
return true;
|
177
|
+
}
|
178
|
+
|
179
|
+
return false;
|
180
|
+
}
|
162
181
|
}
|
package/src/tools/PipetteTool.ts
CHANGED
@@ -4,13 +4,10 @@ import Color4 from '../Color4';
|
|
4
4
|
import Editor from '../Editor';
|
5
5
|
import { PointerEvt } from '../types';
|
6
6
|
import BaseTool from './BaseTool';
|
7
|
-
import { ToolType } from './ToolController';
|
8
7
|
|
9
8
|
type ColorListener = (color: Color4|null)=>void;
|
10
9
|
|
11
10
|
export default class PipetteTool extends BaseTool {
|
12
|
-
public kind: ToolType = ToolType.Pipette;
|
13
|
-
|
14
11
|
private colorPreviewListener: ColorListener|null = null;
|
15
12
|
private colorSelectListener: ColorListener|null = null;
|
16
13
|
|
@@ -6,11 +6,10 @@ import Path from '../math/Path';
|
|
6
6
|
import { Vec2 } from '../math/Vec2';
|
7
7
|
import { InputEvtType } from '../types';
|
8
8
|
import SelectionTool from './SelectionTool';
|
9
|
-
import { ToolType } from './ToolController';
|
10
9
|
import createEditor from '../testing/createEditor';
|
11
10
|
|
12
11
|
const getSelectionTool = (editor: Editor): SelectionTool => {
|
13
|
-
return editor.toolController.getMatchingTools(
|
12
|
+
return editor.toolController.getMatchingTools(SelectionTool)[0];
|
14
13
|
};
|
15
14
|
|
16
15
|
const createSquareStroke = () => {
|
@@ -1,17 +1,19 @@
|
|
1
|
+
// Allows users to select/transform portions of the `EditorImage`.
|
2
|
+
// With respect to `extend`ing, `SelectionTool` is not stable.
|
3
|
+
// @packageDocumentation
|
4
|
+
|
1
5
|
import Command from '../commands/Command';
|
2
6
|
import Duplicate from '../commands/Duplicate';
|
3
7
|
import Erase from '../commands/Erase';
|
4
8
|
import AbstractComponent from '../components/AbstractComponent';
|
5
9
|
import Editor from '../Editor';
|
6
10
|
import Mat33 from '../math/Mat33';
|
7
|
-
// import Mat33 from "../geometry/Mat33";
|
8
11
|
import Rect2 from '../math/Rect2';
|
9
12
|
import { Point2, Vec2 } from '../math/Vec2';
|
10
13
|
import { EditorLocalization } from '../localization';
|
11
14
|
import { EditorEventType, KeyPressEvent, KeyUpEvent, PointerEvt } from '../types';
|
12
15
|
import Viewport from '../Viewport';
|
13
16
|
import BaseTool from './BaseTool';
|
14
|
-
import { ToolType } from './ToolController';
|
15
17
|
import SerializableCommand from '../commands/SerializableCommand';
|
16
18
|
|
17
19
|
const handleScreenSize = 30;
|
@@ -502,11 +504,11 @@ class Selection {
|
|
502
504
|
}
|
503
505
|
}
|
504
506
|
|
507
|
+
// {@inheritDoc SelectionTool!}
|
505
508
|
export default class SelectionTool extends BaseTool {
|
506
509
|
private handleOverlay: HTMLElement;
|
507
510
|
private prevSelectionBox: Selection|null;
|
508
511
|
private selectionBox: Selection|null;
|
509
|
-
public readonly kind: ToolType = ToolType.Selection;
|
510
512
|
|
511
513
|
public constructor(private editor: Editor, description: string) {
|
512
514
|
super(editor.notifier, description);
|
package/src/tools/TextTool.ts
CHANGED
@@ -8,11 +8,9 @@ import { PointerDevice } from '../Pointer';
|
|
8
8
|
import { EditorEventType, PointerEvt } from '../types';
|
9
9
|
import BaseTool from './BaseTool';
|
10
10
|
import { ToolLocalization } from './localization';
|
11
|
-
import { ToolType } from './ToolController';
|
12
11
|
|
13
12
|
const overlayCssClass = 'textEditorOverlay';
|
14
13
|
export default class TextTool extends BaseTool {
|
15
|
-
public kind: ToolType = ToolType.Text;
|
16
14
|
private textStyle: TextStyle;
|
17
15
|
|
18
16
|
private textEditOverlay: HTMLElement;
|
@@ -11,38 +11,31 @@ import { ToolLocalization } from './localization';
|
|
11
11
|
import UndoRedoShortcut from './UndoRedoShortcut';
|
12
12
|
import TextTool from './TextTool';
|
13
13
|
import PipetteTool from './PipetteTool';
|
14
|
-
|
15
|
-
export enum ToolType {
|
16
|
-
Pen,
|
17
|
-
Selection,
|
18
|
-
Eraser,
|
19
|
-
PanZoom,
|
20
|
-
Text,
|
21
|
-
UndoRedoShortcut,
|
22
|
-
Pipette,
|
23
|
-
Other,
|
24
|
-
}
|
14
|
+
import ToolSwitcherShortcut from './ToolSwitcherShortcut';
|
25
15
|
|
26
16
|
export default class ToolController {
|
27
17
|
private tools: BaseTool[];
|
28
|
-
private activeTool: BaseTool|null;
|
18
|
+
private activeTool: BaseTool|null = null;
|
19
|
+
private primaryToolGroup: ToolEnabledGroup;
|
29
20
|
|
21
|
+
/** @internal */
|
30
22
|
public constructor(editor: Editor, localization: ToolLocalization) {
|
31
|
-
const
|
23
|
+
const primaryToolGroup = new ToolEnabledGroup();
|
24
|
+
this.primaryToolGroup = primaryToolGroup;
|
25
|
+
|
32
26
|
const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
|
33
27
|
const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
|
34
28
|
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
|
35
29
|
const primaryTools = [
|
36
|
-
new SelectionTool(editor, localization.selectionTool),
|
37
|
-
new Eraser(editor, localization.eraserTool),
|
38
|
-
|
39
30
|
// Three pens
|
40
31
|
primaryPenTool,
|
41
|
-
new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness:
|
32
|
+
new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
|
42
33
|
|
43
34
|
// Highlighter-like pen with width=64
|
44
35
|
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
|
45
36
|
|
37
|
+
new Eraser(editor, localization.eraserTool),
|
38
|
+
new SelectionTool(editor, localization.selectionTool),
|
46
39
|
new TextTool(editor, localization.textTool, localization),
|
47
40
|
];
|
48
41
|
this.tools = [
|
@@ -51,8 +44,9 @@ export default class ToolController {
|
|
51
44
|
...primaryTools,
|
52
45
|
keyboardPanZoomTool,
|
53
46
|
new UndoRedoShortcut(editor),
|
47
|
+
new ToolSwitcherShortcut(editor),
|
54
48
|
];
|
55
|
-
primaryTools.forEach(tool => tool.setToolGroup(
|
49
|
+
primaryTools.forEach(tool => tool.setToolGroup(primaryToolGroup));
|
56
50
|
panZoomTool.setEnabled(true);
|
57
51
|
primaryPenTool.setEnabled(true);
|
58
52
|
|
@@ -70,6 +64,36 @@ export default class ToolController {
|
|
70
64
|
this.activeTool = null;
|
71
65
|
}
|
72
66
|
|
67
|
+
// Replaces the current set of tools with `tools`. This should only be done before
|
68
|
+
// the creation of the app's toolbar (if using `HTMLToolbar`).
|
69
|
+
public setTools(tools: BaseTool[], primaryToolGroup?: ToolEnabledGroup) {
|
70
|
+
this.tools = tools;
|
71
|
+
this.primaryToolGroup = primaryToolGroup ?? new ToolEnabledGroup();
|
72
|
+
}
|
73
|
+
|
74
|
+
// Add a tool that acts like one of the primary tools (only one primary tool can be enabled at a time).
|
75
|
+
// This should be called before creating the app's toolbar.
|
76
|
+
public addPrimaryTool(tool: BaseTool) {
|
77
|
+
tool.setToolGroup(this.primaryToolGroup);
|
78
|
+
if (tool.isEnabled()) {
|
79
|
+
this.primaryToolGroup.notifyEnabled(tool);
|
80
|
+
}
|
81
|
+
|
82
|
+
this.addTool(tool);
|
83
|
+
}
|
84
|
+
|
85
|
+
public getPrimaryTools(): BaseTool[] {
|
86
|
+
return this.tools.filter(tool => {
|
87
|
+
return tool.getToolGroup() === this.primaryToolGroup;
|
88
|
+
});
|
89
|
+
}
|
90
|
+
|
91
|
+
// Add a tool to the end of this' tool list (the added tool receives events after tools already added to this).
|
92
|
+
// This should be called before creating the app's toolbar.
|
93
|
+
public addTool(tool: BaseTool) {
|
94
|
+
this.tools.push(tool);
|
95
|
+
}
|
96
|
+
|
73
97
|
// Returns true if the event was handled
|
74
98
|
public dispatchInputEvent(event: InputEvt): boolean {
|
75
99
|
let handled = false;
|
@@ -132,8 +156,8 @@ export default class ToolController {
|
|
132
156
|
return handled;
|
133
157
|
}
|
134
158
|
|
135
|
-
public getMatchingTools(
|
136
|
-
return this.tools.filter(tool => tool
|
159
|
+
public getMatchingTools<Type extends BaseTool>(type: new (...args: any[])=>Type): Type[] {
|
160
|
+
return this.tools.filter(tool => tool instanceof type) as Type[];
|
137
161
|
}
|
138
162
|
}
|
139
163
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import BaseTool from './BaseTool';
|
2
2
|
|
3
|
-
// Connects a group of tools -- at most one tool in the group
|
3
|
+
// Connects a group of tools -- at most one tool in the group can be enabled.
|
4
4
|
export default class ToolEnabledGroup {
|
5
5
|
private activeTool: BaseTool|null;
|
6
6
|
public constructor() { }
|
@@ -0,0 +1,34 @@
|
|
1
|
+
// Handles ctrl+1, ctrl+2, ctrl+3, ..., shortcuts for switching tools.
|
2
|
+
// @packageDocumentation
|
3
|
+
|
4
|
+
import Editor from '../Editor';
|
5
|
+
import { KeyPressEvent } from '../types';
|
6
|
+
import BaseTool from './BaseTool';
|
7
|
+
|
8
|
+
// {@inheritDoc ToolSwitcherShortcut!}
|
9
|
+
export default class ToolSwitcherShortcut extends BaseTool {
|
10
|
+
public constructor(private editor: Editor) {
|
11
|
+
super(editor.notifier, editor.localization.changeTool);
|
12
|
+
}
|
13
|
+
|
14
|
+
public onKeyPress({ key }: KeyPressEvent): boolean {
|
15
|
+
const toolController = this.editor.toolController;
|
16
|
+
const primaryTools = toolController.getPrimaryTools();
|
17
|
+
|
18
|
+
// Map keys 0-9 to primary tools.
|
19
|
+
const keyMatch = /^[0-9]$/.exec(key);
|
20
|
+
|
21
|
+
let targetTool: BaseTool|undefined;
|
22
|
+
if (keyMatch) {
|
23
|
+
const targetIdx = parseInt(keyMatch[0], 10) - 1;
|
24
|
+
targetTool = primaryTools[targetIdx];
|
25
|
+
}
|
26
|
+
|
27
|
+
if (targetTool) {
|
28
|
+
targetTool.setEnabled(true);
|
29
|
+
return true;
|
30
|
+
}
|
31
|
+
|
32
|
+
return false;
|
33
|
+
}
|
34
|
+
}
|
@@ -1,12 +1,12 @@
|
|
1
|
+
// Handles ctrl+Z, ctrl+Shift+Z keyboard shortcuts.
|
2
|
+
// @packageDocumentation
|
3
|
+
|
1
4
|
import Editor from '../Editor';
|
2
5
|
import { KeyPressEvent } from '../types';
|
3
6
|
import BaseTool from './BaseTool';
|
4
|
-
import { ToolType } from './ToolController';
|
5
|
-
|
6
7
|
|
8
|
+
// {@inheritDoc UndoRedoShortcut!}
|
7
9
|
export default class UndoRedoShortcut extends BaseTool {
|
8
|
-
public kind: ToolType.UndoRedoShortcut = ToolType.UndoRedoShortcut;
|
9
|
-
|
10
10
|
public constructor(private editor: Editor) {
|
11
11
|
super(editor.notifier, editor.localization.undoRedoTool);
|
12
12
|
}
|
package/src/tools/lib.ts
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
/**
|
3
|
+
* @packageDocumentation
|
4
|
+
*/
|
5
|
+
|
6
|
+
export { default as BaseTool } from './BaseTool';
|
7
|
+
export { default as ToolController } from './ToolController';
|
8
|
+
export { default as ToolEnabledGroup } from './ToolEnabledGroup';
|
9
|
+
|
10
|
+
export { default as UndoRedoShortcut } from './UndoRedoShortcut';
|
11
|
+
export { default as ToolSwitcherShortcut } from './ToolSwitcherShortcut';
|
12
|
+
export { default as PanZoomTool, PanZoomMode } from './PanZoom';
|
13
|
+
|
14
|
+
export { default as PenTool, PenStyle } from './Pen';
|
15
|
+
export { default as TextTool } from './TextTool';
|
16
|
+
export { default as SelectionTool } from './SelectionTool';
|
17
|
+
export { default as EraserTool } from './Eraser';
|
18
|
+
|
@@ -12,6 +12,7 @@ export interface ToolLocalization {
|
|
12
12
|
|
13
13
|
textTool: string;
|
14
14
|
enterTextToInsert: string;
|
15
|
+
changeTool: string;
|
15
16
|
|
16
17
|
toolEnabledAnnouncement: (toolName: string) => string;
|
17
18
|
toolDisabledAnnouncement: (toolName: string) => string;
|
@@ -30,6 +31,7 @@ export const defaultToolLocalization: ToolLocalization = {
|
|
30
31
|
|
31
32
|
textTool: 'Text',
|
32
33
|
enterTextToInsert: 'Text to insert',
|
34
|
+
changeTool: 'Change tool',
|
33
35
|
|
34
36
|
toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
|
35
37
|
toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
|
package/src/types.ts
CHANGED
@@ -10,6 +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
14
|
|
14
15
|
|
15
16
|
export interface PointerEvtListener {
|
@@ -88,6 +89,10 @@ export type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataTyp
|
|
88
89
|
|
89
90
|
|
90
91
|
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
91
96
|
export enum EditorEventType {
|
92
97
|
ToolEnabled,
|
93
98
|
ToolDisabled,
|
@@ -103,6 +108,7 @@ export enum EditorEventType {
|
|
103
108
|
|
104
109
|
ColorPickerToggled,
|
105
110
|
ColorPickerColorSelected,
|
111
|
+
ToolbarDropdownShown,
|
106
112
|
}
|
107
113
|
|
108
114
|
type EditorToolEventType = EditorEventType.ToolEnabled
|
@@ -157,10 +163,16 @@ export interface ColorPickerColorSelected {
|
|
157
163
|
readonly color: Color4;
|
158
164
|
}
|
159
165
|
|
166
|
+
export interface ToolbarDropdownShownEvent {
|
167
|
+
readonly kind: EditorEventType.ToolbarDropdownShown;
|
168
|
+
readonly parentWidget: BaseWidget;
|
169
|
+
}
|
170
|
+
|
160
171
|
export type EditorEventDataType = EditorToolEvent | EditorObjectEvent
|
161
172
|
| EditorViewportChangedEvent | DisplayResizedEvent
|
162
173
|
| EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent
|
163
|
-
| ColorPickerToggled | ColorPickerColorSelected
|
174
|
+
| ColorPickerToggled | ColorPickerColorSelected
|
175
|
+
| ToolbarDropdownShownEvent;
|
164
176
|
|
165
177
|
|
166
178
|
// Returns a Promise to indicate that the event source should pause until the Promise resolves.
|