js-draw 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +2 -2
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/EditorImage.js +0 -1
  5. package/dist/src/components/builders/FreehandLineBuilder.js +18 -9
  6. package/dist/src/components/lib.d.ts +2 -0
  7. package/dist/src/components/lib.js +2 -0
  8. package/dist/src/lib.d.ts +5 -1
  9. package/dist/src/lib.js +5 -1
  10. package/dist/src/rendering/Display.js +2 -2
  11. package/dist/src/rendering/caching/RenderingCacheNode.js +2 -1
  12. package/dist/src/rendering/renderers/CanvasRenderer.js +6 -6
  13. package/dist/src/toolbar/HTMLToolbar.d.ts +4 -1
  14. package/dist/src/toolbar/HTMLToolbar.js +29 -31
  15. package/dist/src/toolbar/icons.d.ts +1 -1
  16. package/dist/src/toolbar/icons.js +4 -0
  17. package/dist/src/toolbar/lib.d.ts +3 -0
  18. package/dist/src/toolbar/lib.js +4 -0
  19. package/dist/src/toolbar/makeColorInput.js +2 -2
  20. package/dist/src/toolbar/types.d.ts +0 -4
  21. package/dist/src/toolbar/types.js +1 -5
  22. package/dist/src/toolbar/widgets/{EraserWidget.d.ts → EraserToolWidget.d.ts} +1 -1
  23. package/dist/src/toolbar/widgets/{EraserWidget.js → EraserToolWidget.js} +1 -1
  24. package/dist/src/toolbar/widgets/{PenWidget.d.ts → PenToolWidget.d.ts} +2 -3
  25. package/dist/src/toolbar/widgets/{PenWidget.js → PenToolWidget.js} +6 -7
  26. package/dist/src/toolbar/widgets/{SelectionWidget.d.ts → SelectionToolWidget.d.ts} +1 -1
  27. package/dist/src/toolbar/widgets/{SelectionWidget.js → SelectionToolWidget.js} +1 -1
  28. package/dist/src/toolbar/widgets/lib.d.ts +8 -0
  29. package/dist/src/toolbar/widgets/lib.js +8 -0
  30. package/dist/src/tools/BaseTool.d.ts +0 -2
  31. package/dist/src/tools/Eraser.d.ts +0 -2
  32. package/dist/src/tools/Eraser.js +0 -2
  33. package/dist/src/tools/PanZoom.d.ts +0 -2
  34. package/dist/src/tools/PanZoom.js +0 -2
  35. package/dist/src/tools/Pen.d.ts +8 -9
  36. package/dist/src/tools/Pen.js +15 -9
  37. package/dist/src/tools/PipetteTool.d.ts +0 -2
  38. package/dist/src/tools/PipetteTool.js +0 -2
  39. package/dist/src/tools/SelectionTool.d.ts +0 -2
  40. package/dist/src/tools/SelectionTool.js +4 -3
  41. package/dist/src/tools/TextTool.d.ts +0 -2
  42. package/dist/src/tools/TextTool.js +0 -2
  43. package/dist/src/tools/ToolController.d.ts +7 -11
  44. package/dist/src/tools/ToolController.js +28 -16
  45. package/dist/src/tools/ToolEnabledGroup.js +1 -1
  46. package/dist/src/tools/UndoRedoShortcut.d.ts +0 -2
  47. package/dist/src/tools/UndoRedoShortcut.js +3 -2
  48. package/dist/src/tools/lib.d.ts +12 -0
  49. package/dist/src/tools/lib.js +12 -0
  50. package/package.json +1 -1
  51. package/src/EditorImage.ts +0 -1
  52. package/src/components/builders/FreehandLineBuilder.ts +27 -17
  53. package/src/components/lib.ts +3 -0
  54. package/src/lib.ts +5 -1
  55. package/src/rendering/Display.ts +2 -2
  56. package/src/rendering/caching/RenderingCacheNode.ts +3 -1
  57. package/src/rendering/renderers/CanvasRenderer.ts +6 -6
  58. package/src/toolbar/HTMLToolbar.ts +34 -37
  59. package/src/toolbar/icons.ts +5 -1
  60. package/src/toolbar/lib.ts +4 -0
  61. package/src/toolbar/makeColorInput.ts +1 -2
  62. package/src/toolbar/types.ts +0 -4
  63. package/src/toolbar/widgets/{EraserWidget.ts → EraserToolWidget.ts} +1 -1
  64. package/src/toolbar/widgets/{PenWidget.ts → PenToolWidget.ts} +10 -9
  65. package/src/toolbar/widgets/{SelectionWidget.ts → SelectionToolWidget.ts} +1 -1
  66. package/src/toolbar/widgets/lib.ts +10 -0
  67. package/src/tools/BaseTool.ts +0 -3
  68. package/src/tools/Eraser.ts +0 -2
  69. package/src/tools/PanZoom.ts +0 -2
  70. package/src/tools/Pen.ts +21 -15
  71. package/src/tools/PipetteTool.ts +0 -3
  72. package/src/tools/SelectionTool.test.ts +1 -2
  73. package/src/tools/SelectionTool.ts +5 -3
  74. package/src/tools/TextTool.ts +0 -2
  75. package/src/tools/ToolController.ts +34 -17
  76. package/src/tools/ToolEnabledGroup.ts +1 -1
  77. package/src/tools/UndoRedoShortcut.ts +4 -4
  78. package/src/tools/lib.ts +17 -0
@@ -1,31 +1,32 @@
1
1
  import Editor from '../Editor';
2
- import { ToolType } from '../tools/ToolController';
3
2
  import { EditorEventType } from '../types';
4
3
 
5
4
  import { coloris, init as colorisInit } from '@melloware/coloris';
6
5
  import Color4 from '../Color4';
7
- import Pen from '../tools/Pen';
8
- import Eraser from '../tools/Eraser';
9
6
  import SelectionTool from '../tools/SelectionTool';
10
7
  import { defaultToolbarLocalization, ToolbarLocalization } from './localization';
11
8
  import { ActionButtonIcon } from './types';
12
9
  import { makeRedoIcon, makeUndoIcon } from './icons';
13
10
  import PanZoom from '../tools/PanZoom';
14
11
  import TextTool from '../tools/TextTool';
15
- import PenWidget from './widgets/PenWidget';
16
- import EraserWidget from './widgets/EraserWidget';
17
- import { SelectionWidget } from './widgets/SelectionWidget';
12
+ import PenToolWidget from './widgets/PenToolWidget';
13
+ import EraserWidget from './widgets/EraserToolWidget';
14
+ import SelectionToolWidget from './widgets/SelectionToolWidget';
18
15
  import TextToolWidget from './widgets/TextToolWidget';
19
16
  import HandToolWidget from './widgets/HandToolWidget';
17
+ import BaseWidget from './widgets/BaseWidget';
18
+ import { EraserTool, PenTool } from '../tools/lib';
20
19
 
21
20
 
22
21
  export const toolbarCSSPrefix = 'toolbar-';
23
22
 
23
+ type UpdateColorisCallback = ()=>void;
24
24
 
25
25
  export default class HTMLToolbar {
26
26
  private container: HTMLElement;
27
27
 
28
28
  private static colorisStarted: boolean = false;
29
+ private updateColoris: UpdateColorisCallback|null = null;
29
30
 
30
31
  public constructor(
31
32
  private editor: Editor, parent: HTMLElement,
@@ -45,6 +46,12 @@ export default class HTMLToolbar {
45
46
 
46
47
  // @internal
47
48
  public setupColorPickers() {
49
+ // Much of the setup only needs to be done once.
50
+ if (this.updateColoris) {
51
+ this.updateColoris();
52
+ return;
53
+ }
54
+
48
55
  const closePickerOverlay = document.createElement('div');
49
56
  closePickerOverlay.className = `${toolbarCSSPrefix}closeColorPickerOverlay`;
50
57
  this.editor.createHTMLOverlay(closePickerOverlay);
@@ -73,6 +80,7 @@ export default class HTMLToolbar {
73
80
  });
74
81
  };
75
82
  initColoris();
83
+ this.updateColoris = initColoris;
76
84
 
77
85
  const addColorToSwatch = (newColor: string) => {
78
86
  let alreadyPresent = false;
@@ -110,6 +118,13 @@ export default class HTMLToolbar {
110
118
  });
111
119
  }
112
120
 
121
+ // Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
122
+ // (i.e. its `addTo` method should not have been called).
123
+ public addWidget(widget: BaseWidget) {
124
+ widget.addTo(this.container);
125
+ this.setupColorPickers();
126
+ }
127
+
113
128
  public addActionButton(title: string|ActionButtonIcon, command: ()=> void, parent?: Element) {
114
129
  const button = document.createElement('button');
115
130
  button.classList.add(`${toolbarCSSPrefix}button`);
@@ -134,7 +149,7 @@ export default class HTMLToolbar {
134
149
  return button;
135
150
  }
136
151
 
137
- private addUndoRedoButtons() {
152
+ public addUndoRedoButtons() {
138
153
  const undoRedoGroup = document.createElement('div');
139
154
  undoRedoGroup.classList.add(`${toolbarCSSPrefix}buttonGroup`);
140
155
 
@@ -166,47 +181,29 @@ export default class HTMLToolbar {
166
181
 
167
182
  public addDefaultToolWidgets() {
168
183
  const toolController = this.editor.toolController;
169
- for (const tool of toolController.getMatchingTools(ToolType.Pen)) {
170
- if (!(tool instanceof Pen)) {
171
- throw new Error('All `Pen` tools must have kind === ToolType.Pen');
172
- }
173
-
174
- const widget = new PenWidget(
184
+ for (const tool of toolController.getMatchingTools(PenTool)) {
185
+ const widget = new PenToolWidget(
175
186
  this.editor, tool, this.localizationTable,
176
187
  );
177
- widget.addTo(this.container);
188
+ this.addWidget(widget);
178
189
  }
179
190
 
180
- for (const tool of toolController.getMatchingTools(ToolType.Eraser)) {
181
- if (!(tool instanceof Eraser)) {
182
- throw new Error('All Erasers must have kind === ToolType.Eraser!');
183
- }
184
-
185
- (new EraserWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
191
+ for (const tool of toolController.getMatchingTools(EraserTool)) {
192
+ this.addWidget(new EraserWidget(this.editor, tool, this.localizationTable));
186
193
  }
187
194
 
188
- for (const tool of toolController.getMatchingTools(ToolType.Selection)) {
189
- if (!(tool instanceof SelectionTool)) {
190
- throw new Error('All SelectionTools must have kind === ToolType.Selection');
191
- }
192
-
193
- (new SelectionWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
195
+ for (const tool of toolController.getMatchingTools(SelectionTool)) {
196
+ this.addWidget(new SelectionToolWidget(this.editor, tool, this.localizationTable));
194
197
  }
195
198
 
196
- for (const tool of toolController.getMatchingTools(ToolType.Text)) {
197
- if (!(tool instanceof TextTool)) {
198
- throw new Error('All text tools must have kind === ToolType.Text');
199
- }
200
-
201
- (new TextToolWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
199
+ for (const tool of toolController.getMatchingTools(TextTool)) {
200
+ this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
202
201
  }
203
202
 
204
- const panZoomTool = toolController.getMatchingTools(ToolType.PanZoom)[0];
205
- if (panZoomTool && panZoomTool instanceof PanZoom) {
206
- (new HandToolWidget(this.editor, panZoomTool, this.localizationTable)).addTo(this.container);
203
+ const panZoomTool = toolController.getMatchingTools(PanZoom)[0];
204
+ if (panZoomTool) {
205
+ this.addWidget(new HandToolWidget(this.editor, panZoomTool, this.localizationTable));
207
206
  }
208
-
209
- this.setupColorPickers();
210
207
  }
211
208
 
212
209
  public addDefaultActionButtons() {
@@ -287,7 +287,11 @@ export const makeTextIcon = (textStyle: TextStyle) => {
287
287
  return icon;
288
288
  };
289
289
 
290
- export const makePenIcon = (tipThickness: number, color: string) => {
290
+ export const makePenIcon = (tipThickness: number, color: string|Color4) => {
291
+ if (color instanceof Color4) {
292
+ color = color.toHexString();
293
+ }
294
+
291
295
  const icon = document.createElementNS(svgNamespace, 'svg');
292
296
  icon.setAttribute('viewBox', '0 0 100 100');
293
297
 
@@ -0,0 +1,4 @@
1
+
2
+ export * from './widgets/lib';
3
+ export * as icons from './icons';
4
+ export * from './makeColorInput';
@@ -1,7 +1,6 @@
1
1
  import Color4 from '../Color4';
2
2
  import Editor from '../Editor';
3
3
  import PipetteTool from '../tools/PipetteTool';
4
- import { ToolType } from '../tools/ToolController';
5
4
  import { EditorEventType } from '../types';
6
5
  import { makePipetteIcon } from './icons';
7
6
 
@@ -77,7 +76,7 @@ const addPipetteTool = (editor: Editor, container: HTMLElement, onColorChange: O
77
76
  };
78
77
  updatePipetteIcon();
79
78
 
80
- const pipetteTool: PipetteTool|undefined = editor.toolController.getMatchingTools(ToolType.Pipette)[0] as PipetteTool|undefined;
79
+ const pipetteTool: PipetteTool|undefined = editor.toolController.getMatchingTools(PipetteTool)[0];
81
80
  const endColorSelectMode = () => {
82
81
  pipetteTool?.clearColorListener();
83
82
  updatePipetteIcon();
@@ -1,7 +1,3 @@
1
- export enum ToolbarButtonType {
2
- ToggleButton,
3
- ActionButton,
4
- }
5
1
 
6
2
  export interface ActionButtonIcon {
7
3
  icon: Element;
@@ -1,7 +1,7 @@
1
1
  import { makeEraserIcon } from '../icons';
2
2
  import BaseToolWidget from './BaseToolWidget';
3
3
 
4
- export default class EraserWidget extends BaseToolWidget {
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 PenWidget extends BaseToolWidget {
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${PenWidget.idCounter++}`;
101
- objectTypeSelect.id = `${toolbarCSSPrefix}builderSelect${PenWidget.idCounter++}`;
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 = '1';
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${PenWidget.idCounter++}`;
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 SelectionWidget extends BaseToolWidget {
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';
@@ -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
 
@@ -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) {
@@ -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
@@ -5,7 +5,6 @@ import Pointer, { PointerDevice } from '../Pointer';
5
5
  import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder';
6
6
  import { EditorEventType, 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
- private builder: ComponentBuilder|null = null;
18
- private builderFactory: ComponentBuilderFactory = makeFreehandLineBuilder;
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
- private getStrokePoint(pointer: Pointer): StrokeDataPoint {
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
- private previewStroke() {
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
- private addPointToStroke(point: StrokeDataPoint) {
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
  }
@@ -67,12 +67,18 @@ export default class Pen extends BaseTool {
67
67
  }
68
68
 
69
69
  public onPointerDown({ current, allPointers }: PointerEvt): boolean {
70
- if (current.device === PointerDevice.Eraser) {
71
- return false;
70
+ const isEraser = current.device === PointerDevice.Eraser;
71
+
72
+ let anyDeviceIsStylus = false;
73
+ for (const pointer of allPointers) {
74
+ if (pointer.device === PointerDevice.Pen) {
75
+ anyDeviceIsStylus = true;
76
+ break;
77
+ }
72
78
  }
73
79
 
74
- if (allPointers.length === 1 || current.device === PointerDevice.Pen) {
75
- this.builder = this.builderFactory(this.getStrokePoint(current), this.editor.viewport);
80
+ if ((allPointers.length === 1 && !isEraser) || anyDeviceIsStylus) {
81
+ this.builder = this.builderFactory(this.toStrokePoint(current), this.editor.viewport);
76
82
  return true;
77
83
  }
78
84
 
@@ -80,7 +86,7 @@ export default class Pen extends BaseTool {
80
86
  }
81
87
 
82
88
  public onPointerMove({ current }: PointerEvt): void {
83
- this.addPointToStroke(this.getStrokePoint(current));
89
+ this.addPointToStroke(this.toStrokePoint(current));
84
90
  }
85
91
 
86
92
  public onPointerUp({ current }: PointerEvt): void {
@@ -89,7 +95,7 @@ export default class Pen extends BaseTool {
89
95
  }
90
96
 
91
97
  // onPointerUp events can have zero pressure. Use the last pressure instead.
92
- const currentPoint = this.getStrokePoint(current);
98
+ const currentPoint = this.toStrokePoint(current);
93
99
  const strokePoint = {
94
100
  ...currentPoint,
95
101
  width: this.lastPoint?.width ?? currentPoint.width,
@@ -112,7 +118,7 @@ export default class Pen extends BaseTool {
112
118
  this.editor.clearWetInk();
113
119
  }
114
120
 
115
- public onGestureCancel(): void {
121
+ public onGestureCancel() {
116
122
  this.editor.clearWetInk();
117
123
  }
118
124
 
@@ -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(ToolType.Selection)[0] as SelectionTool;
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);
@@ -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;
@@ -12,23 +12,16 @@ import UndoRedoShortcut from './UndoRedoShortcut';
12
12
  import TextTool from './TextTool';
13
13
  import PipetteTool from './PipetteTool';
14
14
 
15
- export enum ToolType {
16
- Pen,
17
- Selection,
18
- Eraser,
19
- PanZoom,
20
- Text,
21
- UndoRedoShortcut,
22
- Pipette,
23
- Other,
24
- }
25
-
26
15
  export default class ToolController {
27
16
  private tools: BaseTool[];
28
- private activeTool: BaseTool|null;
17
+ private activeTool: BaseTool|null = null;
18
+ private primaryToolGroup: ToolEnabledGroup;
29
19
 
20
+ /** @internal */
30
21
  public constructor(editor: Editor, localization: ToolLocalization) {
31
- const primaryToolEnabledGroup = new ToolEnabledGroup();
22
+ const primaryToolGroup = new ToolEnabledGroup();
23
+ this.primaryToolGroup = primaryToolGroup;
24
+
32
25
  const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
33
26
  const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
34
27
  const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
@@ -38,7 +31,7 @@ export default class ToolController {
38
31
 
39
32
  // Three pens
40
33
  primaryPenTool,
41
- new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 8 }),
34
+ new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
42
35
 
43
36
  // Highlighter-like pen with width=64
44
37
  new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
@@ -52,7 +45,7 @@ export default class ToolController {
52
45
  keyboardPanZoomTool,
53
46
  new UndoRedoShortcut(editor),
54
47
  ];
55
- primaryTools.forEach(tool => tool.setToolGroup(primaryToolEnabledGroup));
48
+ primaryTools.forEach(tool => tool.setToolGroup(primaryToolGroup));
56
49
  panZoomTool.setEnabled(true);
57
50
  primaryPenTool.setEnabled(true);
58
51
 
@@ -70,6 +63,30 @@ export default class ToolController {
70
63
  this.activeTool = null;
71
64
  }
72
65
 
66
+ // Replaces the current set of tools with `tools`. This should only be done before
67
+ // the creation of the app's toolbar (if using `HTMLToolbar`).
68
+ public setTools(tools: BaseTool[], primaryToolGroup?: ToolEnabledGroup) {
69
+ this.tools = tools;
70
+ this.primaryToolGroup = primaryToolGroup ?? new ToolEnabledGroup();
71
+ }
72
+
73
+ // Add a tool that acts like one of the primary tools (only one primary tool can be enabled at a time).
74
+ // This should be called before creating the app's toolbar.
75
+ public addPrimaryTool(tool: BaseTool) {
76
+ tool.setToolGroup(this.primaryToolGroup);
77
+ if (tool.isEnabled()) {
78
+ this.primaryToolGroup.notifyEnabled(tool);
79
+ }
80
+
81
+ this.addTool(tool);
82
+ }
83
+
84
+ // Add a tool to the end of this' tool list (the added tool receives events after tools already added to this).
85
+ // This should be called before creating the app's toolbar.
86
+ public addTool(tool: BaseTool) {
87
+ this.tools.push(tool);
88
+ }
89
+
73
90
  // Returns true if the event was handled
74
91
  public dispatchInputEvent(event: InputEvt): boolean {
75
92
  let handled = false;
@@ -132,8 +149,8 @@ export default class ToolController {
132
149
  return handled;
133
150
  }
134
151
 
135
- public getMatchingTools(kind: ToolType): BaseTool[] {
136
- return this.tools.filter(tool => tool.kind === kind);
152
+ public getMatchingTools<Type extends BaseTool>(type: new (...args: any[])=>Type): Type[] {
153
+ return this.tools.filter(tool => tool instanceof type) as Type[];
137
154
  }
138
155
  }
139
156
 
@@ -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 must be enabled.
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() { }
@@ -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
  }
@@ -0,0 +1,17 @@
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 PanZoomTool, PanZoomMode } from './PanZoom';
12
+
13
+ export { default as PenTool, PenStyle } from './Pen';
14
+ export { default as TextTool } from './TextTool';
15
+ export { default as SelectionTool } from './SelectionTool';
16
+ export { default as EraserTool } from './Eraser';
17
+