js-draw 0.1.1 → 0.1.4

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 (86) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +21 -12
  3. package/dist/bundle.js +1 -1
  4. package/dist/src/Editor.d.ts +2 -1
  5. package/dist/src/Editor.js +24 -6
  6. package/dist/src/EditorImage.js +3 -0
  7. package/dist/src/Pointer.d.ts +3 -2
  8. package/dist/src/Pointer.js +12 -3
  9. package/dist/src/SVGLoader.d.ts +11 -0
  10. package/dist/src/SVGLoader.js +113 -4
  11. package/dist/src/Viewport.d.ts +1 -1
  12. package/dist/src/Viewport.js +12 -2
  13. package/dist/src/components/AbstractComponent.d.ts +6 -0
  14. package/dist/src/components/AbstractComponent.js +11 -0
  15. package/dist/src/components/SVGGlobalAttributesObject.js +0 -1
  16. package/dist/src/components/Stroke.js +1 -1
  17. package/dist/src/components/Text.d.ts +30 -0
  18. package/dist/src/components/Text.js +111 -0
  19. package/dist/src/components/localization.d.ts +1 -0
  20. package/dist/src/components/localization.js +1 -0
  21. package/dist/src/geometry/Mat33.d.ts +1 -0
  22. package/dist/src/geometry/Mat33.js +30 -0
  23. package/dist/src/geometry/Path.js +105 -67
  24. package/dist/src/geometry/Rect2.d.ts +2 -0
  25. package/dist/src/geometry/Rect2.js +6 -0
  26. package/dist/src/rendering/renderers/AbstractRenderer.d.ts +7 -1
  27. package/dist/src/rendering/renderers/AbstractRenderer.js +13 -1
  28. package/dist/src/rendering/renderers/CanvasRenderer.d.ts +3 -0
  29. package/dist/src/rendering/renderers/CanvasRenderer.js +28 -8
  30. package/dist/src/rendering/renderers/DummyRenderer.d.ts +3 -0
  31. package/dist/src/rendering/renderers/DummyRenderer.js +5 -0
  32. package/dist/src/rendering/renderers/SVGRenderer.d.ts +6 -2
  33. package/dist/src/rendering/renderers/SVGRenderer.js +50 -7
  34. package/dist/src/testing/loadExpectExtensions.js +1 -4
  35. package/dist/src/toolbar/HTMLToolbar.d.ts +2 -1
  36. package/dist/src/toolbar/HTMLToolbar.js +242 -154
  37. package/dist/src/toolbar/icons.d.ts +12 -0
  38. package/dist/src/toolbar/icons.js +198 -0
  39. package/dist/src/toolbar/localization.d.ts +5 -1
  40. package/dist/src/toolbar/localization.js +5 -1
  41. package/dist/src/toolbar/types.d.ts +4 -0
  42. package/dist/src/tools/PanZoom.d.ts +9 -6
  43. package/dist/src/tools/PanZoom.js +30 -21
  44. package/dist/src/tools/Pen.js +8 -3
  45. package/dist/src/tools/SelectionTool.js +1 -1
  46. package/dist/src/tools/TextTool.d.ts +30 -0
  47. package/dist/src/tools/TextTool.js +173 -0
  48. package/dist/src/tools/ToolController.d.ts +5 -5
  49. package/dist/src/tools/ToolController.js +10 -9
  50. package/dist/src/tools/localization.d.ts +3 -0
  51. package/dist/src/tools/localization.js +3 -0
  52. package/dist-test/test-dist-bundle.html +8 -1
  53. package/package.json +1 -1
  54. package/src/Editor.css +2 -0
  55. package/src/Editor.ts +26 -7
  56. package/src/EditorImage.ts +4 -0
  57. package/src/Pointer.ts +13 -4
  58. package/src/SVGLoader.ts +146 -5
  59. package/src/Viewport.ts +15 -3
  60. package/src/components/AbstractComponent.ts +16 -1
  61. package/src/components/SVGGlobalAttributesObject.ts +0 -1
  62. package/src/components/Stroke.ts +1 -1
  63. package/src/components/Text.ts +140 -0
  64. package/src/components/localization.ts +2 -0
  65. package/src/geometry/Mat33.test.ts +44 -0
  66. package/src/geometry/Mat33.ts +41 -0
  67. package/src/geometry/Path.fromString.test.ts +94 -4
  68. package/src/geometry/Path.toString.test.ts +7 -3
  69. package/src/geometry/Path.ts +110 -68
  70. package/src/geometry/Rect2.ts +8 -0
  71. package/src/rendering/renderers/AbstractRenderer.ts +18 -1
  72. package/src/rendering/renderers/CanvasRenderer.ts +34 -10
  73. package/src/rendering/renderers/DummyRenderer.ts +8 -0
  74. package/src/rendering/renderers/SVGRenderer.ts +57 -10
  75. package/src/testing/loadExpectExtensions.ts +1 -4
  76. package/src/toolbar/HTMLToolbar.ts +294 -170
  77. package/src/toolbar/icons.ts +227 -0
  78. package/src/toolbar/localization.ts +11 -2
  79. package/src/toolbar/toolbar.css +27 -11
  80. package/src/toolbar/types.ts +5 -0
  81. package/src/tools/PanZoom.ts +37 -27
  82. package/src/tools/Pen.ts +7 -3
  83. package/src/tools/SelectionTool.ts +1 -1
  84. package/src/tools/TextTool.ts +225 -0
  85. package/src/tools/ToolController.ts +7 -5
  86. package/src/tools/localization.ts +7 -0
@@ -0,0 +1,227 @@
1
+ import { ComponentBuilderFactory } from '../components/builders/types';
2
+ import { TextStyle } from '../components/Text';
3
+ import EventDispatcher from '../EventDispatcher';
4
+ import { Vec2 } from '../geometry/Vec2';
5
+ import SVGRenderer from '../rendering/renderers/SVGRenderer';
6
+ import Pen from '../tools/Pen';
7
+ import { StrokeDataPoint } from '../types';
8
+ import Viewport from '../Viewport';
9
+
10
+ const svgNamespace = 'http://www.w3.org/2000/svg';
11
+ const primaryForegroundFill = `
12
+ style='fill: var(--primary-foreground-color);'
13
+ `;
14
+ const primaryForegroundStrokeFill = `
15
+ style='fill: var(--primary-foreground-color); stroke: var(--primary-foreground-color);'
16
+ `;
17
+
18
+ export const makeUndoIcon = () => {
19
+ return makeRedoIcon(true);
20
+ };
21
+
22
+ export const makeRedoIcon = (mirror: boolean = false) => {
23
+ const icon = document.createElementNS(svgNamespace, 'svg');
24
+ icon.innerHTML = `
25
+ <style>
26
+ .toolbar-svg-undo-redo-icon {
27
+ stroke: var(--primary-foreground-color);
28
+ stroke-width: 12;
29
+ stroke-linejoin: round;
30
+ stroke-linecap: round;
31
+ fill: none;
32
+
33
+ transform-origin: center;
34
+ }
35
+ </style>
36
+ <path
37
+ d='M20,20 A15,15 0 0 1 70,80 L80,90 L60,70 L65,90 L87,90 L65,80'
38
+ class='toolbar-svg-undo-redo-icon'
39
+ style='${mirror ? 'transform: scale(-1, 1);' : ''}'/>
40
+ `;
41
+ icon.setAttribute('viewBox', '0 0 100 100');
42
+ return icon;
43
+ };
44
+
45
+ export const makeDropdownIcon = () => {
46
+ const icon = document.createElementNS(svgNamespace, 'svg');
47
+ icon.innerHTML = `
48
+ <g>
49
+ <path
50
+ d='M5,10 L50,90 L95,10 Z'
51
+ ${primaryForegroundFill}
52
+ />
53
+ </g>
54
+ `;
55
+ icon.setAttribute('viewBox', '0 0 100 100');
56
+ return icon;
57
+ };
58
+
59
+ export const makeEraserIcon = () => {
60
+ const icon = document.createElementNS(svgNamespace, 'svg');
61
+
62
+ // Draw an eraser-like shape
63
+ icon.innerHTML = `
64
+ <g>
65
+ <rect x=10 y=50 width=80 height=30 rx=10 fill='pink' />
66
+ <rect
67
+ x=10 y=10 width=80 height=50
68
+ ${primaryForegroundFill}
69
+ />
70
+ </g>
71
+ `;
72
+ icon.setAttribute('viewBox', '0 0 100 100');
73
+ return icon;
74
+ };
75
+
76
+ export const makeSelectionIcon = () => {
77
+ const icon = document.createElementNS(svgNamespace, 'svg');
78
+
79
+ // Draw a cursor-like shape
80
+ icon.innerHTML = `
81
+ <g>
82
+ <rect x=10 y=10 width=70 height=70 fill='pink' stroke='black'/>
83
+ <rect x=75 y=75 width=10 height=10 fill='white' stroke='black'/>
84
+ </g>
85
+ `;
86
+ icon.setAttribute('viewBox', '0 0 100 100');
87
+
88
+ return icon;
89
+ };
90
+
91
+ export const makeHandToolIcon = () => {
92
+ const icon = document.createElementNS(svgNamespace, 'svg');
93
+
94
+ // Draw a cursor-like shape
95
+ icon.innerHTML = `
96
+ <g>
97
+ <path d='
98
+ m 10,60
99
+ 5,30
100
+ H 90
101
+ V 30
102
+ C 90,20 75,20 75,30
103
+ V 60
104
+ 20
105
+ C 75,10 60,10 60,20
106
+ V 60
107
+ 15
108
+ C 60,5 45,5 45,15
109
+ V 60
110
+ 25
111
+ C 45,15 30,15 30,25
112
+ V 60
113
+ 75
114
+ L 25,60
115
+ C 20,45 10,50 10,60
116
+ Z'
117
+
118
+ fill='none'
119
+ style='
120
+ stroke: var(--primary-foreground-color);
121
+ stroke-width: 2;
122
+ '
123
+ />
124
+ </g>
125
+ `;
126
+ icon.setAttribute('viewBox', '0 0 100 100');
127
+ return icon;
128
+ };
129
+
130
+ export const makeTextIcon = (textStyle: TextStyle) => {
131
+ const icon = document.createElementNS(svgNamespace, 'svg');
132
+ icon.setAttribute('viewBox', '0 0 100 100');
133
+
134
+ const textNode = document.createElementNS(svgNamespace, 'text');
135
+ textNode.appendChild(document.createTextNode('T'));
136
+
137
+ textNode.style.fontFamily = textStyle.fontFamily;
138
+ textNode.style.fontWeight = textStyle.fontWeight ?? '';
139
+ textNode.style.fontVariant = textStyle.fontVariant ?? '';
140
+ textNode.style.fill = textStyle.renderingStyle.fill.toHexString();
141
+
142
+ textNode.style.textAnchor = 'middle';
143
+ textNode.setAttribute('x', '50');
144
+ textNode.setAttribute('y', '75');
145
+ textNode.style.fontSize = '65px';
146
+ textNode.style.filter = 'drop-shadow(0px 0px 10px var(--primary-shadow-color))';
147
+
148
+ icon.appendChild(textNode);
149
+
150
+ return icon;
151
+ };
152
+
153
+ export const makePenIcon = (tipThickness: number, color: string) => {
154
+ const icon = document.createElementNS(svgNamespace, 'svg');
155
+ icon.setAttribute('viewBox', '0 0 100 100');
156
+
157
+ const halfThickness = tipThickness / 2;
158
+
159
+ // Draw a pen-like shape
160
+ const primaryStrokeTipPath = `M14,63 L${50 - halfThickness},95 L${50 + halfThickness},90 L88,60 Z`;
161
+ const backgroundStrokeTipPath = `M14,63 L${50 - halfThickness},85 L${50 + halfThickness},83 L88,60 Z`;
162
+ icon.innerHTML = `
163
+ <defs>
164
+ <pattern
165
+ id='checkerboard'
166
+ viewBox='0,0,10,10'
167
+ width='20%'
168
+ height='20%'
169
+ patternUnits='userSpaceOnUse'
170
+ >
171
+ <rect x=0 y=0 width=10 height=10 fill='white'/>
172
+ <rect x=0 y=0 width=5 height=5 fill='gray'/>
173
+ <rect x=5 y=5 width=5 height=5 fill='gray'/>
174
+ </pattern>
175
+ </defs>
176
+ <g>
177
+ <!-- Pen grip -->
178
+ <path
179
+ d='M10,10 L90,10 L90,60 L${50 + halfThickness},80 L${50 - halfThickness},80 L10,60 Z'
180
+ ${primaryForegroundStrokeFill}
181
+ />
182
+ </g>
183
+ <g>
184
+ <!-- Checkerboard background for slightly transparent pens -->
185
+ <path d='${backgroundStrokeTipPath}' fill='url(#checkerboard)'/>
186
+
187
+ <!-- Actual pen tip -->
188
+ <path
189
+ d='${primaryStrokeTipPath}'
190
+ fill='${color}'
191
+ stroke='${color}'
192
+ />
193
+ </g>
194
+ `;
195
+ return icon;
196
+ };
197
+
198
+ export const makeIconFromFactory = (pen: Pen, factory: ComponentBuilderFactory) => {
199
+ const toolThickness = pen.getThickness();
200
+
201
+ const nowTime = (new Date()).getTime();
202
+ const startPoint: StrokeDataPoint = {
203
+ pos: Vec2.of(10, 10),
204
+ width: toolThickness / 5,
205
+ color: pen.getColor(),
206
+ time: nowTime - 100,
207
+ };
208
+ const endPoint: StrokeDataPoint = {
209
+ pos: Vec2.of(90, 90),
210
+ width: toolThickness / 5,
211
+ color: pen.getColor(),
212
+ time: nowTime,
213
+ };
214
+
215
+ const viewport = new Viewport(new EventDispatcher());
216
+ const builder = factory(startPoint, viewport);
217
+ builder.addPoint(endPoint);
218
+
219
+ const icon = document.createElementNS(svgNamespace, 'svg');
220
+ icon.setAttribute('viewBox', '0 0 100 100');
221
+ viewport.updateScreenSize(Vec2.of(100, 100));
222
+
223
+ const renderer = new SVGRenderer(icon, viewport);
224
+ builder.preview(renderer);
225
+
226
+ return icon;
227
+ };
@@ -1,6 +1,9 @@
1
1
 
2
2
 
3
3
  export interface ToolbarLocalization {
4
+ fontLabel: string;
5
+ anyDevicePanning: string;
6
+ touchPanning: string;
4
7
  outlinedRectanglePen: string;
5
8
  filledRectanglePen: string;
6
9
  linePen: string;
@@ -11,7 +14,7 @@ export interface ToolbarLocalization {
11
14
  pen: string;
12
15
  eraser: string;
13
16
  select: string;
14
- touchDrawing: string;
17
+ handTool: string;
15
18
  thicknessLabel: string;
16
19
  resizeImageToSelection: string;
17
20
  deleteSelection: string;
@@ -20,21 +23,26 @@ export interface ToolbarLocalization {
20
23
 
21
24
  dropdownShown: (toolName: string)=>string;
22
25
  dropdownHidden: (toolName: string)=>string;
26
+ zoomLevel: (zoomPercentage: number)=> string;
23
27
  }
24
28
 
25
29
  export const defaultToolbarLocalization: ToolbarLocalization = {
26
30
  pen: 'Pen',
27
31
  eraser: 'Eraser',
28
32
  select: 'Select',
29
- touchDrawing: 'Touch Drawing',
33
+ handTool: 'Pan',
30
34
  thicknessLabel: 'Thickness: ',
31
35
  colorLabel: 'Color: ',
36
+ fontLabel: 'Font: ',
32
37
  resizeImageToSelection: 'Resize image to selection',
33
38
  deleteSelection: 'Delete selection',
34
39
  undo: 'Undo',
35
40
  redo: 'Redo',
36
41
  selectObjectType: 'Object type: ',
37
42
 
43
+ touchPanning: 'Touchscreen panning',
44
+ anyDevicePanning: 'Any device panning',
45
+
38
46
  freehandPen: 'Freehand',
39
47
  arrowPen: 'Arrow',
40
48
  linePen: 'Line',
@@ -43,4 +51,5 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
43
51
 
44
52
  dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
45
53
  dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
54
+ zoomLevel: (zoomPercent: number) => `Zoom: ${zoomPercent}%`,
46
55
  };
@@ -12,6 +12,9 @@
12
12
  flex-direction: row;
13
13
  justify-content: center;
14
14
 
15
+ /* Display above selection dialogs, etc. */
16
+ z-index: 1000;
17
+
15
18
  font-family: system-ui, -apple-system, sans-serif;
16
19
  }
17
20
 
@@ -32,6 +35,7 @@
32
35
 
33
36
  min-width: 40px;
34
37
  max-width: 70px;
38
+ width: min-content;
35
39
  font-size: 11pt;
36
40
 
37
41
  cursor: pointer;
@@ -40,17 +44,13 @@
40
44
  background-color: var(--primary-background-color);
41
45
  color: var(--primary-foreground-color);
42
46
  border: none;
43
- box-shadow: 0px 0px 2px var(--primary-foreground-color);
47
+ box-shadow: 0px 0px 2px var(--primary-shadow-color);
44
48
 
45
49
  transition: background-color 0.25s ease, box-shadow 0.25s ease, opacity 0.3s ease;
46
50
  }
47
51
 
48
52
  .toolbar-button:hover, .toolbar-root button:not(:disabled):hover {
49
- box-shadow: 0px 2px 4px var(--primary-foreground-color);
50
- }
51
-
52
- .toolbar-root button {
53
- height: auto;
53
+ box-shadow: 0px 2px 4px var(--primary-shadow-color);
54
54
  }
55
55
 
56
56
  .toolbar-root button:disabled {
@@ -58,7 +58,7 @@
58
58
  filter: opacity(0.5);
59
59
  }
60
60
 
61
- .toolbar-button .toolbar-icon {
61
+ .toolbar-root .toolbar-icon {
62
62
  flex-shrink: 1;
63
63
  min-width: 30px;
64
64
  }
@@ -68,11 +68,11 @@
68
68
  color: var(--secondary-foreground-color);
69
69
  }
70
70
 
71
- .toolbar-toolContainer:not(.selected) .toolbar-showHideDropdownIcon {
71
+ .toolbar-toolContainer:not(.selected):not(.dropdownShowable) .toolbar-showHideDropdownIcon {
72
72
  display: none;
73
73
  }
74
74
 
75
- .toolbar-toolContainer.selected .toolbar-showHideDropdownIcon {
75
+ .toolbar-toolContainer .toolbar-showHideDropdownIcon {
76
76
  height: 10px;
77
77
  transition: transform 0.5s ease;
78
78
  }
@@ -81,7 +81,8 @@
81
81
  transform: rotate(180deg);
82
82
  }
83
83
 
84
- .toolbar-dropdown.hidden, .toolbar-toolContainer:not(.selected) > .toolbar-dropdown {
84
+ .toolbar-dropdown.hidden,
85
+ .toolbar-toolContainer:not(.selected):not(.dropdownShowable) > .toolbar-dropdown {
85
86
  display: none;
86
87
  }
87
88
 
@@ -92,12 +93,13 @@
92
93
  /* Prevent overlap/being displayed under the undo/redo buttons */
93
94
  z-index: 2;
94
95
  background-color: var(--primary-background-color);
95
- box-shadow: 0px 3px 3px var(--primary-foreground-color);
96
+ box-shadow: 0px 3px 3px var(--primary-shadow-color);
96
97
  }
97
98
 
98
99
  .toolbar-buttonGroup {
99
100
  display: flex;
100
101
  flex-direction: row;
102
+ justify-content: center;
101
103
  }
102
104
 
103
105
  .toolbar-closeColorPickerOverlay {
@@ -120,3 +122,17 @@
120
122
  margin-left: 0;
121
123
  margin-right: 0;
122
124
  }
125
+
126
+ .toolbar-root .toolbar-zoomLevelEditor {
127
+ display: flex;
128
+ flex-direction: row;
129
+ }
130
+
131
+ .toolbar-root .toolbar-zoomLevelEditor .zoomDisplay {
132
+ flex-grow: 1;
133
+ }
134
+
135
+ .toolbar-root .toolbar-zoomLevelEditor button {
136
+ width: min-content;
137
+ height: min-content;
138
+ }
@@ -2,3 +2,8 @@ export enum ToolbarButtonType {
2
2
  ToggleButton,
3
3
  ActionButton,
4
4
  }
5
+
6
+ export interface ActionButtonIcon {
7
+ icon: Element;
8
+ label: string;
9
+ }
@@ -4,7 +4,7 @@ import Mat33 from '../geometry/Mat33';
4
4
  import { Point2, Vec2 } from '../geometry/Vec2';
5
5
  import Vec3 from '../geometry/Vec3';
6
6
  import Pointer, { PointerDevice } from '../Pointer';
7
- import { KeyPressEvent, PointerEvt, WheelEvt } from '../types';
7
+ import { EditorEventType, KeyPressEvent, PointerEvt, WheelEvt } from '../types';
8
8
  import { Viewport } from '../Viewport';
9
9
  import BaseTool from './BaseTool';
10
10
  import { ToolType } from './ToolController';
@@ -17,18 +17,14 @@ interface PinchData {
17
17
  }
18
18
 
19
19
  export enum PanZoomMode {
20
- // Handle one-pointer gestures (touchscreen only unless AnyDevice is set)
21
- OneFingerGestures = 0x1,
22
-
23
- // Handle two-pointer gestures (touchscreen only unless AnyDevice is set)
24
- TwoFingerGestures = 0x1 << 1,
25
-
26
- // / Handle gestures from any device, rather than just touch
27
- AnyDevice = 0x1 << 2,
20
+ OneFingerTouchGestures = 0x1,
21
+ TwoFingerTouchGestures = 0x1 << 1,
22
+ RightClickDrags = 0x1 << 2,
23
+ SinglePointerGestures = 0x1 << 3,
28
24
  }
29
25
 
30
26
  export default class PanZoom extends BaseTool {
31
- public readonly kind: ToolType.PanZoom|ToolType.TouchPanZoom = ToolType.PanZoom;
27
+ public readonly kind: ToolType.PanZoom = ToolType.PanZoom;
32
28
  private transform: Viewport.ViewportTransform|null = null;
33
29
 
34
30
  private lastAngle: number;
@@ -37,10 +33,6 @@ export default class PanZoom extends BaseTool {
37
33
 
38
34
  public constructor(private editor: Editor, private mode: PanZoomMode, description: string) {
39
35
  super(editor.notifier, description);
40
-
41
- if (mode === PanZoomMode.OneFingerGestures) {
42
- this.kind = ToolType.TouchPanZoom;
43
- }
44
36
  }
45
37
 
46
38
  // Returns information about the pointers in a gesture
@@ -54,25 +46,28 @@ export default class PanZoom extends BaseTool {
54
46
  return { canvasCenter, screenCenter, angle, dist };
55
47
  }
56
48
 
57
- private pointersHaveCorrectDeviceType(pointers: Pointer[]) {
58
- return this.mode & PanZoomMode.AnyDevice || pointers.every(
59
- pointer => pointer.device === PointerDevice.Touch
60
- );
49
+ private allPointersAreOfType(pointers: Pointer[], kind: PointerDevice) {
50
+ return pointers.every(pointer => pointer.device === kind);
61
51
  }
62
52
 
63
- public onPointerDown({ allPointers }: PointerEvt): boolean {
53
+ public onPointerDown({ allPointers: pointers }: PointerEvt): boolean {
64
54
  let handlingGesture = false;
65
55
 
66
- if (!this.pointersHaveCorrectDeviceType(allPointers)) {
67
- handlingGesture = false;
68
- } else if (allPointers.length === 2 && this.mode & PanZoomMode.TwoFingerGestures) {
69
- const { screenCenter, angle, dist } = this.computePinchData(allPointers[0], allPointers[1]);
56
+ const allAreTouch = this.allPointersAreOfType(pointers, PointerDevice.Touch);
57
+ const isRightClick = this.allPointersAreOfType(pointers, PointerDevice.RightButtonMouse);
58
+
59
+ if (allAreTouch && pointers.length === 2 && this.mode & PanZoomMode.TwoFingerTouchGestures) {
60
+ const { screenCenter, angle, dist } = this.computePinchData(pointers[0], pointers[1]);
70
61
  this.lastAngle = angle;
71
62
  this.lastDist = dist;
72
63
  this.lastScreenCenter = screenCenter;
73
64
  handlingGesture = true;
74
- } else if (allPointers.length === 1 && this.mode & PanZoomMode.OneFingerGestures) {
75
- this.lastScreenCenter = allPointers[0].screenPos;
65
+ } else if (pointers.length === 1 && (
66
+ (this.mode & PanZoomMode.OneFingerTouchGestures && allAreTouch)
67
+ || (isRightClick && this.mode & PanZoomMode.RightClickDrags)
68
+ || (this.mode & PanZoomMode.SinglePointerGestures)
69
+ )) {
70
+ this.lastScreenCenter = pointers[0].screenPos;
76
71
  handlingGesture = true;
77
72
  }
78
73
 
@@ -122,9 +117,9 @@ export default class PanZoom extends BaseTool {
122
117
  this.transform ??= new Viewport.ViewportTransform(Mat33.identity);
123
118
 
124
119
  const lastTransform = this.transform;
125
- if (allPointers.length === 2 && this.mode & PanZoomMode.TwoFingerGestures) {
120
+ if (allPointers.length === 2) {
126
121
  this.handleTwoFingerMove(allPointers);
127
- } else if (allPointers.length === 1 && this.mode & PanZoomMode.OneFingerGestures) {
122
+ } else if (allPointers.length === 1) {
128
123
  this.handleOneFingerMove(allPointers[0]);
129
124
  }
130
125
  lastTransform.unapply(this.editor);
@@ -253,4 +248,19 @@ export default class PanZoom extends BaseTool {
253
248
 
254
249
  return true;
255
250
  }
251
+
252
+ public setMode(mode: PanZoomMode) {
253
+ if (mode !== this.mode) {
254
+ this.mode = mode;
255
+
256
+ this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
257
+ kind: EditorEventType.ToolUpdated,
258
+ tool: this,
259
+ });
260
+ }
261
+ }
262
+
263
+ public getMode(): PanZoomMode {
264
+ return this.mode;
265
+ }
256
266
  }
package/src/tools/Pen.ts CHANGED
@@ -91,9 +91,13 @@ export default class Pen extends BaseTool {
91
91
  const stroke = this.builder.build();
92
92
  this.previewStroke();
93
93
 
94
- const canFlatten = true;
95
- const action = new EditorImage.AddElementCommand(stroke, canFlatten);
96
- this.editor.dispatch(action);
94
+ if (stroke.getBBox().area > 0) {
95
+ const canFlatten = true;
96
+ const action = new EditorImage.AddElementCommand(stroke, canFlatten);
97
+ this.editor.dispatch(action);
98
+ } else {
99
+ console.warn('Pen: Not adding empty stroke', stroke, 'to the canvas.');
100
+ }
97
101
  }
98
102
  this.builder = null;
99
103
  this.editor.clearWetInk();
@@ -494,7 +494,7 @@ export default class SelectionTool extends BaseTool {
494
494
  );
495
495
 
496
496
  const selectionRect = this.selectionBox.region;
497
- this.editor.viewport.zoomTo(selectionRect).apply(this.editor);
497
+ this.editor.viewport.zoomTo(selectionRect, false).apply(this.editor);
498
498
  }
499
499
  }
500
500