js-draw 0.1.9 → 0.1.12
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 +18 -0
- package/README.md +15 -3
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +7 -2
- package/dist/src/Editor.js +74 -25
- package/dist/src/EditorImage.d.ts +1 -1
- package/dist/src/EditorImage.js +2 -2
- package/dist/src/Pointer.d.ts +1 -1
- package/dist/src/Pointer.js +1 -1
- package/dist/src/SVGLoader.d.ts +1 -1
- package/dist/src/SVGLoader.js +14 -6
- package/dist/src/UndoRedoHistory.js +3 -0
- package/dist/src/Viewport.d.ts +8 -25
- package/dist/src/Viewport.js +17 -10
- package/dist/src/bundle/bundled.d.ts +2 -1
- package/dist/src/bundle/bundled.js +2 -1
- package/dist/src/commands/Command.d.ts +2 -2
- package/dist/src/commands/Command.js +4 -4
- package/dist/src/commands/Duplicate.d.ts +1 -1
- package/dist/src/commands/Duplicate.js +1 -1
- package/dist/src/commands/Erase.d.ts +1 -1
- package/dist/src/commands/Erase.js +1 -1
- package/dist/src/commands/localization.d.ts +1 -1
- package/dist/src/components/AbstractComponent.d.ts +3 -3
- package/dist/src/components/AbstractComponent.js +2 -2
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +3 -3
- package/dist/src/components/SVGGlobalAttributesObject.js +1 -1
- package/dist/src/components/Stroke.d.ts +4 -4
- package/dist/src/components/Stroke.js +2 -2
- package/dist/src/components/Text.d.ts +3 -3
- package/dist/src/components/Text.js +3 -3
- package/dist/src/components/UnknownSVGObject.d.ts +3 -3
- package/dist/src/components/UnknownSVGObject.js +1 -1
- package/dist/src/components/builders/ArrowBuilder.d.ts +1 -1
- package/dist/src/components/builders/ArrowBuilder.js +1 -1
- package/dist/src/components/builders/FreehandLineBuilder.d.ts +8 -3
- package/dist/src/components/builders/FreehandLineBuilder.js +142 -71
- package/dist/src/components/builders/LineBuilder.d.ts +1 -1
- package/dist/src/components/builders/LineBuilder.js +1 -1
- package/dist/src/components/builders/RectangleBuilder.d.ts +1 -1
- package/dist/src/components/builders/RectangleBuilder.js +3 -3
- package/dist/src/components/builders/types.d.ts +1 -1
- package/dist/src/localization.d.ts +1 -0
- package/dist/src/localization.js +5 -1
- package/dist/src/localizations/en.d.ts +3 -0
- package/dist/src/localizations/en.js +4 -0
- package/dist/src/localizations/es.d.ts +3 -0
- package/dist/src/localizations/es.js +18 -0
- package/dist/src/localizations/getLocalizationTable.d.ts +3 -0
- package/dist/src/localizations/getLocalizationTable.js +43 -0
- package/dist/src/{geometry → math}/LineSegment2.d.ts +1 -0
- package/dist/src/{geometry → math}/LineSegment2.js +16 -0
- package/dist/src/{geometry → math}/Mat33.d.ts +0 -0
- package/dist/src/{geometry → math}/Mat33.js +0 -0
- package/dist/src/{geometry → math}/Path.d.ts +2 -1
- package/dist/src/{geometry → math}/Path.js +58 -51
- package/dist/src/{geometry → math}/Rect2.d.ts +1 -0
- package/dist/src/{geometry → math}/Rect2.js +16 -0
- package/dist/src/{geometry → math}/Vec2.d.ts +0 -0
- package/dist/src/{geometry → math}/Vec2.js +0 -0
- package/dist/src/{geometry → math}/Vec3.d.ts +1 -1
- package/dist/src/{geometry → math}/Vec3.js +1 -1
- package/dist/src/math/rounding.d.ts +3 -0
- package/dist/src/math/rounding.js +120 -0
- package/dist/src/rendering/Display.d.ts +3 -1
- package/dist/src/rendering/Display.js +16 -10
- package/dist/src/rendering/caching/CacheRecord.d.ts +2 -2
- package/dist/src/rendering/caching/CacheRecord.js +1 -1
- package/dist/src/rendering/caching/CacheRecordManager.d.ts +1 -1
- package/dist/src/rendering/caching/RenderingCache.js +1 -1
- package/dist/src/rendering/caching/RenderingCacheNode.d.ts +2 -1
- package/dist/src/rendering/caching/RenderingCacheNode.js +18 -7
- package/dist/src/rendering/caching/testUtils.js +1 -1
- package/dist/src/rendering/caching/types.d.ts +1 -1
- package/dist/src/rendering/localization.d.ts +2 -0
- package/dist/src/rendering/localization.js +2 -0
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/AbstractRenderer.js +2 -2
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/CanvasRenderer.js +1 -1
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +4 -4
- package/dist/src/rendering/renderers/DummyRenderer.js +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +3 -3
- package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +5 -3
- package/dist/src/rendering/renderers/TextOnlyRenderer.js +13 -3
- package/dist/src/toolbar/HTMLToolbar.js +5 -7
- package/dist/src/toolbar/icons.d.ts +3 -0
- package/dist/src/toolbar/icons.js +152 -142
- package/dist/src/toolbar/localization.d.ts +4 -1
- package/dist/src/toolbar/localization.js +4 -1
- package/dist/src/toolbar/makeColorInput.js +2 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +21 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +31 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +10 -3
- package/dist/src/toolbar/widgets/SelectionWidget.d.ts +0 -1
- package/dist/src/toolbar/widgets/SelectionWidget.js +23 -30
- package/dist/src/tools/BaseTool.d.ts +2 -1
- package/dist/src/tools/BaseTool.js +3 -0
- package/dist/src/tools/Eraser.js +1 -1
- package/dist/src/tools/PanZoom.d.ts +3 -2
- package/dist/src/tools/PanZoom.js +32 -16
- package/dist/src/tools/SelectionTool.d.ts +11 -4
- package/dist/src/tools/SelectionTool.js +135 -23
- package/dist/src/tools/TextTool.js +1 -1
- package/dist/src/tools/ToolController.js +6 -2
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +13 -6
- package/dist/src/types.js +1 -0
- package/package.json +9 -1
- package/src/Editor.ts +86 -24
- package/src/EditorImage.test.ts +2 -4
- package/src/EditorImage.ts +2 -2
- package/src/Pointer.ts +1 -1
- package/src/SVGLoader.ts +14 -6
- package/src/UndoRedoHistory.ts +4 -0
- package/src/Viewport.ts +21 -17
- package/src/bundle/bundled.ts +2 -1
- package/src/commands/Command.ts +5 -5
- package/src/commands/Duplicate.ts +1 -1
- package/src/commands/Erase.ts +1 -1
- package/src/commands/localization.ts +1 -1
- package/src/components/AbstractComponent.ts +4 -4
- package/src/components/SVGGlobalAttributesObject.ts +3 -3
- package/src/components/Stroke.test.ts +3 -5
- package/src/components/Stroke.ts +4 -4
- package/src/components/Text.test.ts +2 -2
- package/src/components/Text.ts +3 -3
- package/src/components/UnknownSVGObject.ts +3 -3
- package/src/components/builders/ArrowBuilder.ts +2 -2
- package/src/components/builders/FreehandLineBuilder.ts +190 -80
- package/src/components/builders/LineBuilder.ts +2 -2
- package/src/components/builders/RectangleBuilder.ts +3 -3
- package/src/components/builders/types.ts +1 -1
- package/src/localization.ts +6 -0
- package/src/localizations/en.ts +8 -0
- package/src/localizations/es.ts +63 -0
- package/src/localizations/getLocalizationTable.test.ts +27 -0
- package/src/localizations/getLocalizationTable.ts +53 -0
- package/src/{geometry → math}/LineSegment2.test.ts +15 -0
- package/src/{geometry → math}/LineSegment2.ts +20 -0
- package/src/{geometry → math}/Mat33.test.ts +0 -0
- package/src/{geometry → math}/Mat33.ts +0 -0
- package/src/{geometry → math}/Path.fromString.test.ts +0 -0
- package/src/{geometry → math}/Path.test.ts +0 -0
- package/src/{geometry → math}/Path.toString.test.ts +11 -2
- package/src/{geometry → math}/Path.ts +60 -57
- package/src/{geometry → math}/Rect2.test.ts +20 -7
- package/src/{geometry → math}/Rect2.ts +19 -1
- package/src/{geometry → math}/Vec2.test.ts +0 -0
- package/src/{geometry → math}/Vec2.ts +0 -0
- package/src/{geometry → math}/Vec3.test.ts +0 -0
- package/src/{geometry → math}/Vec3.ts +2 -2
- package/src/math/rounding.test.ts +40 -0
- package/src/math/rounding.ts +145 -0
- package/src/rendering/Display.ts +18 -10
- package/src/rendering/caching/CacheRecord.test.ts +2 -2
- package/src/rendering/caching/CacheRecord.ts +2 -2
- package/src/rendering/caching/CacheRecordManager.ts +1 -1
- package/src/rendering/caching/RenderingCache.test.ts +3 -3
- package/src/rendering/caching/RenderingCache.ts +1 -1
- package/src/rendering/caching/RenderingCacheNode.ts +23 -7
- package/src/rendering/caching/testUtils.ts +1 -1
- package/src/rendering/caching/types.ts +1 -1
- package/src/rendering/localization.ts +4 -0
- package/src/rendering/renderers/AbstractRenderer.ts +4 -4
- package/src/rendering/renderers/CanvasRenderer.ts +4 -4
- package/src/rendering/renderers/DummyRenderer.test.ts +2 -2
- package/src/rendering/renderers/DummyRenderer.ts +4 -4
- package/src/rendering/renderers/SVGRenderer.ts +10 -4
- package/src/rendering/renderers/TextOnlyRenderer.ts +17 -6
- package/src/toolbar/HTMLToolbar.ts +5 -8
- package/src/toolbar/icons.ts +167 -147
- package/src/toolbar/localization.ts +8 -2
- package/src/toolbar/makeColorInput.ts +2 -1
- package/src/toolbar/toolbar.css +7 -3
- package/src/toolbar/widgets/ActionButtonWidget.ts +31 -0
- package/src/toolbar/widgets/BaseWidget.ts +36 -0
- package/src/toolbar/widgets/HandToolWidget.ts +14 -3
- package/src/toolbar/widgets/SelectionWidget.ts +46 -41
- package/src/tools/BaseTool.ts +5 -1
- package/src/tools/Eraser.ts +2 -2
- package/src/tools/PanZoom.ts +39 -18
- package/src/tools/SelectionTool.test.ts +26 -5
- package/src/tools/SelectionTool.ts +162 -27
- package/src/tools/TextTool.ts +2 -2
- package/src/tools/ToolController.ts +6 -2
- package/src/tools/UndoRedoShortcut.test.ts +1 -1
- package/src/tools/localization.ts +2 -0
- package/src/types.ts +14 -5
- package/dist-test/test-dist-bundle.html +0 -42
@@ -19,10 +19,13 @@ export interface ToolbarLocalization {
|
|
19
19
|
resizeImageToSelection: string;
|
20
20
|
deleteSelection: string;
|
21
21
|
duplicateSelection: string;
|
22
|
-
|
22
|
+
pickColorFromScreen: string;
|
23
|
+
clickToPickColorAnnouncement: string;
|
23
24
|
undo: string;
|
24
25
|
redo: string;
|
25
26
|
zoom: string;
|
27
|
+
resetView: string;
|
28
|
+
selectionToolKeyboardShortcuts: string;
|
26
29
|
|
27
30
|
dropdownShown: (toolName: string)=> string;
|
28
31
|
dropdownHidden: (toolName: string)=> string;
|
@@ -36,6 +39,7 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
36
39
|
select: 'Select',
|
37
40
|
handTool: 'Pan',
|
38
41
|
zoom: 'Zoom',
|
42
|
+
resetView: 'Reset view',
|
39
43
|
thicknessLabel: 'Thickness: ',
|
40
44
|
colorLabel: 'Color: ',
|
41
45
|
fontLabel: 'Font: ',
|
@@ -45,7 +49,9 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
45
49
|
undo: 'Undo',
|
46
50
|
redo: 'Redo',
|
47
51
|
selectObjectType: 'Object type: ',
|
48
|
-
|
52
|
+
pickColorFromScreen: 'Pick color from screen',
|
53
|
+
clickToPickColorAnnouncement: 'Click on the screen to pick a color',
|
54
|
+
selectionToolKeyboardShortcuts: 'Selection tool: Use arrow keys to move selected items, lowercase/uppercase ‘i’ and ‘o’ to resize.',
|
49
55
|
|
50
56
|
touchPanning: 'Touchscreen panning',
|
51
57
|
anyDevicePanning: 'Any device panning',
|
@@ -69,7 +69,7 @@ export const makeColorInput = (editor: Editor, onColorChange: OnColorChangeListe
|
|
69
69
|
const addPipetteTool = (editor: Editor, container: HTMLElement, onColorChange: OnColorChangeListener) => {
|
70
70
|
const pipetteButton = document.createElement('button');
|
71
71
|
pipetteButton.classList.add('pipetteButton');
|
72
|
-
pipetteButton.title = editor.localization.
|
72
|
+
pipetteButton.title = editor.localization.pickColorFromScreen;
|
73
73
|
pipetteButton.setAttribute('alt', pipetteButton.title);
|
74
74
|
|
75
75
|
const updatePipetteIcon = (color?: Color4) => {
|
@@ -111,6 +111,7 @@ const addPipetteTool = (editor: Editor, container: HTMLElement, onColorChange: O
|
|
111
111
|
);
|
112
112
|
if (pipetteTool) {
|
113
113
|
pipetteButton.classList.add('active');
|
114
|
+
editor.announceForAccessibility(editor.localization.clickToPickColorAnnouncement);
|
114
115
|
}
|
115
116
|
};
|
116
117
|
|
package/src/toolbar/toolbar.css
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
.toolbar-root {
|
2
2
|
background-color: var(--primary-background-color);
|
3
|
+
--icon-color: var(--primary-foreground-color);
|
4
|
+
|
3
5
|
|
4
6
|
border: 1px solid var(--secondary-background-color);
|
5
7
|
border-radius: 2px;
|
@@ -38,6 +40,7 @@
|
|
38
40
|
text-align: center;
|
39
41
|
border-radius: 6px;
|
40
42
|
|
43
|
+
--icon-color: var(--primary-foreground-color);
|
41
44
|
background-color: var(--primary-background-color);
|
42
45
|
color: var(--primary-foreground-color);
|
43
46
|
border: none;
|
@@ -80,9 +83,10 @@
|
|
80
83
|
min-height: 30px;
|
81
84
|
}
|
82
85
|
|
83
|
-
.toolbar-toolContainer.selected .toolbar-button {
|
86
|
+
.toolbar-toolContainer.selected > .toolbar-button {
|
84
87
|
background-color: var(--secondary-background-color);
|
85
88
|
color: var(--secondary-foreground-color);
|
89
|
+
--icon-color: var(--secondary-foreground-color);
|
86
90
|
}
|
87
91
|
|
88
92
|
.toolbar-toolContainer:not(.selected):not(.dropdownShowable) > .toolbar-button > .toolbar-showHideDropdownIcon {
|
@@ -151,8 +155,7 @@
|
|
151
155
|
}
|
152
156
|
|
153
157
|
.toolbar-root .toolbar-zoomLevelEditor button {
|
154
|
-
width:
|
155
|
-
height: min-content;
|
158
|
+
min-width: 48px;
|
156
159
|
}
|
157
160
|
|
158
161
|
.color-input-container {
|
@@ -173,4 +176,5 @@
|
|
173
176
|
|
174
177
|
.color-input-container .pipetteButton.active {
|
175
178
|
background-color: var(--secondary-background-color);
|
179
|
+
--icon-color: var(--secondary-foreground-color);
|
176
180
|
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import { ToolbarLocalization } from '../localization';
|
3
|
+
import BaseWidget from './BaseWidget';
|
4
|
+
|
5
|
+
export default class ActionButtonWidget extends BaseWidget {
|
6
|
+
public constructor(
|
7
|
+
editor: Editor, localizationTable: ToolbarLocalization,
|
8
|
+
protected makeIcon: ()=> Element,
|
9
|
+
protected title: string,
|
10
|
+
|
11
|
+
protected clickAction: ()=>void,
|
12
|
+
) {
|
13
|
+
super(editor, localizationTable);
|
14
|
+
}
|
15
|
+
|
16
|
+
protected handleClick() {
|
17
|
+
this.clickAction();
|
18
|
+
}
|
19
|
+
|
20
|
+
protected getTitle(): string {
|
21
|
+
return this.title;
|
22
|
+
}
|
23
|
+
|
24
|
+
protected createIcon(): Element {
|
25
|
+
return this.makeIcon();
|
26
|
+
}
|
27
|
+
|
28
|
+
protected fillDropdown(_dropdown: HTMLElement): boolean {
|
29
|
+
return false;
|
30
|
+
}
|
31
|
+
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
|
+
import { InputEvtType } from '../../types';
|
2
3
|
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
3
4
|
import { makeDropdownIcon } from '../icons';
|
4
5
|
import { ToolbarLocalization } from '../localization';
|
@@ -50,6 +51,39 @@ export default abstract class BaseWidget {
|
|
50
51
|
}
|
51
52
|
|
52
53
|
protected setupActionBtnClickListener(button: HTMLElement) {
|
54
|
+
const clickTriggers = { Enter: true, ' ': true, };
|
55
|
+
button.onkeydown = (evt) => {
|
56
|
+
let handled = false;
|
57
|
+
|
58
|
+
if (evt.key in clickTriggers) {
|
59
|
+
if (!this.disabled) {
|
60
|
+
this.handleClick();
|
61
|
+
handled = true;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
// If we didn't do anything with the event, send it to the editor.
|
66
|
+
if (!handled) {
|
67
|
+
this.editor.toolController.dispatchInputEvent({
|
68
|
+
kind: InputEvtType.KeyPressEvent,
|
69
|
+
key: evt.key,
|
70
|
+
ctrlKey: evt.ctrlKey,
|
71
|
+
});
|
72
|
+
}
|
73
|
+
};
|
74
|
+
|
75
|
+
button.onkeyup = evt => {
|
76
|
+
if (evt.key in clickTriggers) {
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
|
80
|
+
this.editor.toolController.dispatchInputEvent({
|
81
|
+
kind: InputEvtType.KeyUpEvent,
|
82
|
+
key: evt.key,
|
83
|
+
ctrlKey: evt.ctrlKey,
|
84
|
+
});
|
85
|
+
};
|
86
|
+
|
53
87
|
button.onclick = () => {
|
54
88
|
if (!this.disabled) {
|
55
89
|
this.handleClick();
|
@@ -103,8 +137,10 @@ export default abstract class BaseWidget {
|
|
103
137
|
this.disabled = disabled;
|
104
138
|
if (this.disabled) {
|
105
139
|
this.button.classList.add('disabled');
|
140
|
+
this.button.setAttribute('aria-disabled', 'true');
|
106
141
|
} else {
|
107
142
|
this.button.classList.remove('disabled');
|
143
|
+
this.button.removeAttribute('aria-disabled');
|
108
144
|
}
|
109
145
|
}
|
110
146
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
|
-
import Mat33 from '../../
|
2
|
+
import Mat33 from '../../math/Mat33';
|
3
3
|
import PanZoom, { PanZoomMode } from '../../tools/PanZoom';
|
4
4
|
import { EditorEventType } from '../../types';
|
5
5
|
import Viewport from '../../Viewport';
|
@@ -14,10 +14,12 @@ const makeZoomControl = (localizationTable: ToolbarLocalization, editor: Editor)
|
|
14
14
|
|
15
15
|
const increaseButton = document.createElement('button');
|
16
16
|
const decreaseButton = document.createElement('button');
|
17
|
+
const resetViewButton = document.createElement('button');
|
17
18
|
const zoomLevelDisplay = document.createElement('span');
|
18
19
|
increaseButton.innerText = '+';
|
19
20
|
decreaseButton.innerText = '-';
|
20
|
-
|
21
|
+
resetViewButton.innerText = localizationTable.resetView;
|
22
|
+
zoomLevelRow.replaceChildren(zoomLevelDisplay, increaseButton, decreaseButton, resetViewButton);
|
21
23
|
|
22
24
|
zoomLevelRow.classList.add(`${toolbarCSSPrefix}zoomLevelEditor`);
|
23
25
|
zoomLevelDisplay.classList.add('zoomDisplay');
|
@@ -42,13 +44,16 @@ const makeZoomControl = (localizationTable: ToolbarLocalization, editor: Editor)
|
|
42
44
|
editor.notifier.on(EditorEventType.ViewportChanged, (event) => {
|
43
45
|
if (event.kind === EditorEventType.ViewportChanged) {
|
44
46
|
updateZoomDisplay();
|
47
|
+
|
48
|
+
// Can't reset if already reset.
|
49
|
+
resetViewButton.disabled = event.newTransform.eq(Mat33.identity);
|
45
50
|
}
|
46
51
|
});
|
47
52
|
|
48
53
|
const zoomBy = (factor: number) => {
|
49
54
|
const screenCenter = editor.viewport.visibleRect.center;
|
50
55
|
const transformUpdate = Mat33.scaling2D(factor, screenCenter);
|
51
|
-
editor.dispatch(
|
56
|
+
editor.dispatch(Viewport.transformBy(transformUpdate), false);
|
52
57
|
};
|
53
58
|
|
54
59
|
increaseButton.onclick = () => {
|
@@ -59,6 +64,12 @@ const makeZoomControl = (localizationTable: ToolbarLocalization, editor: Editor)
|
|
59
64
|
zoomBy(4.0/5);
|
60
65
|
};
|
61
66
|
|
67
|
+
resetViewButton.onclick = () => {
|
68
|
+
editor.dispatch(Viewport.transformBy(
|
69
|
+
editor.viewport.canvasToScreenTransform.inverse()
|
70
|
+
), true);
|
71
|
+
};
|
72
|
+
|
62
73
|
return zoomLevelRow;
|
63
74
|
};
|
64
75
|
|
@@ -1,8 +1,9 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
2
|
import SelectionTool from '../../tools/SelectionTool';
|
3
3
|
import { EditorEventType } from '../../types';
|
4
|
-
import { makeSelectionIcon } from '../icons';
|
4
|
+
import { makeDeleteSelectionIcon, makeDuplicateSelectionIcon, makeResizeViewportIcon, makeSelectionIcon } from '../icons';
|
5
5
|
import { ToolbarLocalization } from '../localization';
|
6
|
+
import ActionButtonWidget from './ActionButtonWidget';
|
6
7
|
import BaseToolWidget from './BaseToolWidget';
|
7
8
|
|
8
9
|
export class SelectionWidget extends BaseToolWidget {
|
@@ -10,44 +11,46 @@ export class SelectionWidget extends BaseToolWidget {
|
|
10
11
|
editor: Editor, private tool: SelectionTool, localization: ToolbarLocalization
|
11
12
|
) {
|
12
13
|
super(editor, tool, localization);
|
13
|
-
}
|
14
|
-
|
15
|
-
protected getTitle(): string {
|
16
|
-
return this.localizationTable.select;
|
17
|
-
}
|
18
|
-
|
19
|
-
protected createIcon(): Element {
|
20
|
-
return makeSelectionIcon();
|
21
|
-
}
|
22
14
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
deleteButton
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
15
|
+
const resizeButton = new ActionButtonWidget(
|
16
|
+
editor, localization,
|
17
|
+
makeResizeViewportIcon,
|
18
|
+
this.localizationTable.resizeImageToSelection,
|
19
|
+
() => {
|
20
|
+
const selection = this.tool.getSelection();
|
21
|
+
this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
|
22
|
+
},
|
23
|
+
);
|
24
|
+
const deleteButton = new ActionButtonWidget(
|
25
|
+
editor, localization,
|
26
|
+
makeDeleteSelectionIcon,
|
27
|
+
this.localizationTable.deleteSelection,
|
28
|
+
() => {
|
29
|
+
const selection = this.tool.getSelection();
|
30
|
+
this.editor.dispatch(selection!.deleteSelectedObjects());
|
31
|
+
this.tool.clearSelection();
|
32
|
+
},
|
33
|
+
);
|
34
|
+
const duplicateButton = new ActionButtonWidget(
|
35
|
+
editor, localization,
|
36
|
+
makeDuplicateSelectionIcon,
|
37
|
+
this.localizationTable.duplicateSelection,
|
38
|
+
() => {
|
39
|
+
const selection = this.tool.getSelection();
|
40
|
+
this.editor.dispatch(selection!.duplicateSelectedObjects());
|
41
|
+
},
|
42
|
+
);
|
40
43
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
this.tool.clearSelection();
|
45
|
-
};
|
44
|
+
this.addSubWidget(resizeButton);
|
45
|
+
this.addSubWidget(deleteButton);
|
46
|
+
this.addSubWidget(duplicateButton);
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
const updateDisabled = (disabled: boolean) => {
|
49
|
+
resizeButton.setDisabled(disabled);
|
50
|
+
deleteButton.setDisabled(disabled);
|
51
|
+
duplicateButton.setDisabled(disabled);
|
50
52
|
};
|
53
|
+
updateDisabled(true);
|
51
54
|
|
52
55
|
// Enable/disable actions based on whether items are selected
|
53
56
|
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
@@ -59,14 +62,16 @@ export class SelectionWidget extends BaseToolWidget {
|
|
59
62
|
const selection = this.tool.getSelection();
|
60
63
|
const hasSelection = selection && selection.region.area > 0;
|
61
64
|
|
62
|
-
|
63
|
-
deleteButton.disabled = resizeButton.disabled;
|
64
|
-
duplicateButton.disabled = resizeButton.disabled;
|
65
|
+
updateDisabled(!hasSelection);
|
65
66
|
}
|
66
67
|
});
|
68
|
+
}
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
protected getTitle(): string {
|
71
|
+
return this.localizationTable.select;
|
72
|
+
}
|
73
|
+
|
74
|
+
protected createIcon(): Element {
|
75
|
+
return makeSelectionIcon();
|
71
76
|
}
|
72
77
|
}
|
package/src/tools/BaseTool.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, EditorEventType, KeyPressEvent } from '../types';
|
1
|
+
import { PointerEvtListener, WheelEvt, PointerEvt, EditorNotifier, EditorEventType, KeyPressEvent, KeyUpEvent } from '../types';
|
2
2
|
import { ToolType } from './ToolController';
|
3
3
|
import ToolEnabledGroup from './ToolEnabledGroup';
|
4
4
|
|
@@ -24,6 +24,10 @@ export default abstract class BaseTool implements PointerEvtListener {
|
|
24
24
|
return false;
|
25
25
|
}
|
26
26
|
|
27
|
+
public onKeyUp(_event: KeyUpEvent): boolean {
|
28
|
+
return false;
|
29
|
+
}
|
30
|
+
|
27
31
|
public setEnabled(enabled: boolean) {
|
28
32
|
this.enabled = enabled;
|
29
33
|
|
package/src/tools/Eraser.ts
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
import { PointerEvt } from '../types';
|
2
2
|
import BaseTool from './BaseTool';
|
3
3
|
import Editor from '../Editor';
|
4
|
-
import { Point2 } from '../
|
5
|
-
import LineSegment2 from '../
|
4
|
+
import { Point2 } from '../math/Vec2';
|
5
|
+
import LineSegment2 from '../math/LineSegment2';
|
6
6
|
import Erase from '../commands/Erase';
|
7
7
|
import { ToolType } from './ToolController';
|
8
8
|
import AbstractComponent from '../components/AbstractComponent';
|
package/src/tools/PanZoom.ts
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
|
2
2
|
import { Editor } from '../Editor';
|
3
|
-
import Mat33 from '../
|
4
|
-
import { Point2, Vec2 } from '../
|
5
|
-
import Vec3 from '../
|
3
|
+
import Mat33 from '../math/Mat33';
|
4
|
+
import { Point2, Vec2 } from '../math/Vec2';
|
5
|
+
import Vec3 from '../math/Vec3';
|
6
6
|
import Pointer, { PointerDevice } from '../Pointer';
|
7
7
|
import { EditorEventType, KeyPressEvent, PointerEvt, WheelEvt } from '../types';
|
8
|
-
import { Viewport } from '../Viewport';
|
8
|
+
import { Viewport, ViewportTransform } from '../Viewport';
|
9
9
|
import BaseTool from './BaseTool';
|
10
10
|
import { ToolType } from './ToolController';
|
11
11
|
|
@@ -16,16 +16,18 @@ interface PinchData {
|
|
16
16
|
dist: number;
|
17
17
|
}
|
18
18
|
|
19
|
+
|
19
20
|
export enum PanZoomMode {
|
20
21
|
OneFingerTouchGestures = 0x1,
|
21
22
|
TwoFingerTouchGestures = 0x1 << 1,
|
22
23
|
RightClickDrags = 0x1 << 2,
|
23
24
|
SinglePointerGestures = 0x1 << 3,
|
25
|
+
Keyboard = 0x1 << 4,
|
24
26
|
}
|
25
27
|
|
26
28
|
export default class PanZoom extends BaseTool {
|
27
29
|
public readonly kind: ToolType.PanZoom = ToolType.PanZoom;
|
28
|
-
private transform:
|
30
|
+
private transform: ViewportTransform|null = null;
|
29
31
|
|
30
32
|
private lastAngle: number;
|
31
33
|
private lastDist: number;
|
@@ -72,7 +74,7 @@ export default class PanZoom extends BaseTool {
|
|
72
74
|
}
|
73
75
|
|
74
76
|
if (handlingGesture) {
|
75
|
-
this.transform ??=
|
77
|
+
this.transform ??= Viewport.transformBy(Mat33.identity);
|
76
78
|
this.editor.display.setDraftMode(true);
|
77
79
|
}
|
78
80
|
|
@@ -98,14 +100,14 @@ export default class PanZoom extends BaseTool {
|
|
98
100
|
this.lastScreenCenter = screenCenter;
|
99
101
|
this.lastDist = dist;
|
100
102
|
this.lastAngle = angle;
|
101
|
-
this.transform =
|
103
|
+
this.transform = Viewport.transformBy(
|
102
104
|
this.transform!.transform.rightMul(transformUpdate)
|
103
105
|
);
|
104
106
|
}
|
105
107
|
|
106
108
|
private handleOneFingerMove(pointer: Pointer) {
|
107
109
|
const delta = this.getCenterDelta(pointer.screenPos);
|
108
|
-
this.transform =
|
110
|
+
this.transform = Viewport.transformBy(
|
109
111
|
this.transform!.transform.rightMul(
|
110
112
|
Mat33.translation(delta)
|
111
113
|
)
|
@@ -114,7 +116,7 @@ export default class PanZoom extends BaseTool {
|
|
114
116
|
}
|
115
117
|
|
116
118
|
public onPointerMove({ allPointers }: PointerEvt): void {
|
117
|
-
this.transform ??=
|
119
|
+
this.transform ??= Viewport.transformBy(Mat33.identity);
|
118
120
|
|
119
121
|
const lastTransform = this.transform;
|
120
122
|
if (allPointers.length === 2) {
|
@@ -144,21 +146,25 @@ export default class PanZoom extends BaseTool {
|
|
144
146
|
|
145
147
|
// Applies [transformUpdate] to the editor. This stacks on top of the
|
146
148
|
// current transformation, if it exists.
|
147
|
-
private updateTransform(transformUpdate: Mat33) {
|
149
|
+
private updateTransform(transformUpdate: Mat33, announce: boolean = false) {
|
148
150
|
let newTransform = transformUpdate;
|
149
151
|
if (this.transform) {
|
150
152
|
newTransform = this.transform.transform.rightMul(transformUpdate);
|
151
153
|
}
|
152
154
|
|
153
155
|
this.transform?.unapply(this.editor);
|
154
|
-
this.transform =
|
156
|
+
this.transform = Viewport.transformBy(newTransform);
|
155
157
|
this.transform.apply(this.editor);
|
158
|
+
|
159
|
+
if (announce) {
|
160
|
+
this.editor.announceForAccessibility(this.transform.description(this.editor, this.editor.localization));
|
161
|
+
}
|
156
162
|
}
|
157
163
|
|
158
164
|
public onWheel({ delta, screenPos }: WheelEvt): boolean {
|
159
|
-
|
160
|
-
|
161
|
-
|
165
|
+
// Reset the transformation -- wheel events are individual events, so we don't
|
166
|
+
// need to unapply/reapply.
|
167
|
+
this.transform = Viewport.transformBy(Mat33.identity);
|
162
168
|
|
163
169
|
const canvasPos = this.editor.viewport.screenToCanvas(screenPos);
|
164
170
|
const toCanvas = this.editor.viewport.screenToCanvasTransform;
|
@@ -170,16 +176,23 @@ export default class PanZoom extends BaseTool {
|
|
170
176
|
);
|
171
177
|
const pinchZoomScaleFactor = 1.04;
|
172
178
|
const transformUpdate = Mat33.scaling2D(
|
173
|
-
Math.pow(pinchZoomScaleFactor, -delta.z), canvasPos
|
179
|
+
Math.max(0.25, Math.min(Math.pow(pinchZoomScaleFactor, -delta.z), 4)), canvasPos
|
174
180
|
).rightMul(
|
175
181
|
Mat33.translation(translation)
|
176
182
|
);
|
177
|
-
this.updateTransform(transformUpdate);
|
183
|
+
this.updateTransform(transformUpdate, true);
|
178
184
|
|
179
185
|
return true;
|
180
186
|
}
|
181
187
|
|
182
188
|
public onKeyPress({ key }: KeyPressEvent): boolean {
|
189
|
+
if (!(this.mode & PanZoomMode.Keyboard)) {
|
190
|
+
return false;
|
191
|
+
}
|
192
|
+
|
193
|
+
// No need to keep the same the transform for keyboard events.
|
194
|
+
this.transform = Viewport.transformBy(Mat33.identity);
|
195
|
+
|
183
196
|
let translation = Vec2.zero;
|
184
197
|
let scale = 1;
|
185
198
|
let rotation = 0;
|
@@ -196,10 +209,12 @@ export default class PanZoom extends BaseTool {
|
|
196
209
|
case 'ArrowRight':
|
197
210
|
translation = Vec2.of(1, 0);
|
198
211
|
break;
|
212
|
+
case 'q':
|
199
213
|
case 'k':
|
200
214
|
case 'ArrowUp':
|
201
215
|
translation = Vec2.of(0, -1);
|
202
216
|
break;
|
217
|
+
case 'e':
|
203
218
|
case 'j':
|
204
219
|
case 'ArrowDown':
|
205
220
|
translation = Vec2.of(0, 1);
|
@@ -222,13 +237,19 @@ export default class PanZoom extends BaseTool {
|
|
222
237
|
|
223
238
|
// For each keypress,
|
224
239
|
translation = translation.times(30); // Move at most 30 units
|
225
|
-
rotation *= Math.PI / 8; // Rotate at
|
240
|
+
rotation *= Math.PI / 8; // Rotate at least a sixteenth of a rotation
|
226
241
|
|
227
242
|
// Transform the canvas, not the viewport:
|
228
243
|
translation = translation.times(-1);
|
229
244
|
rotation = rotation * -1;
|
230
245
|
scale = 1 / scale;
|
231
246
|
|
247
|
+
// Work around an issue that seems to be related to rotation matricies losing precision on inversion.
|
248
|
+
// TODO: Figure out why and implement a better solution.
|
249
|
+
if (rotation !== 0) {
|
250
|
+
rotation += 0.0001;
|
251
|
+
}
|
252
|
+
|
232
253
|
const toCanvas = this.editor.viewport.screenToCanvasTransform;
|
233
254
|
|
234
255
|
// Transform without translating (treat toCanvas as a linear instead of
|
@@ -244,7 +265,7 @@ export default class PanZoom extends BaseTool {
|
|
244
265
|
)).rightMul(Mat33.translation(
|
245
266
|
translation
|
246
267
|
));
|
247
|
-
this.updateTransform(transformUpdate);
|
268
|
+
this.updateTransform(transformUpdate, true);
|
248
269
|
|
249
270
|
return true;
|
250
271
|
}
|
@@ -1,11 +1,9 @@
|
|
1
|
-
/* @jest-environment jsdom */
|
2
|
-
|
3
1
|
import Color4 from '../Color4';
|
4
2
|
import Stroke from '../components/Stroke';
|
5
3
|
import Editor from '../Editor';
|
6
4
|
import EditorImage from '../EditorImage';
|
7
|
-
import Path from '../
|
8
|
-
import { Vec2 } from '../
|
5
|
+
import Path from '../math/Path';
|
6
|
+
import { Vec2 } from '../math/Vec2';
|
9
7
|
import { InputEvtType } from '../types';
|
10
8
|
import SelectionTool from './SelectionTool';
|
11
9
|
import { ToolType } from './ToolController';
|
@@ -66,7 +64,7 @@ describe('SelectionTool', () => {
|
|
66
64
|
|
67
65
|
// Drag the object
|
68
66
|
selection!.handleBackgroundDrag(Vec2.of(5, 5));
|
69
|
-
selection!.
|
67
|
+
selection!.finalizeTransform();
|
70
68
|
|
71
69
|
expect(testStroke.getBBox().topLeft).toMatchObject({
|
72
70
|
x: 5,
|
@@ -80,4 +78,27 @@ describe('SelectionTool', () => {
|
|
80
78
|
y: 0,
|
81
79
|
});
|
82
80
|
});
|
81
|
+
|
82
|
+
it('moving the selection with a keyboard should move the view to keep the selection in view', () => {
|
83
|
+
const { addTestStrokeCommand } = createSquareStroke();
|
84
|
+
const editor = createEditor();
|
85
|
+
editor.dispatch(addTestStrokeCommand);
|
86
|
+
|
87
|
+
// Select the stroke
|
88
|
+
const selectionTool = getSelectionTool(editor);
|
89
|
+
selectionTool.setEnabled(true);
|
90
|
+
editor.sendPenEvent(InputEvtType.PointerDownEvt, Vec2.of(0, 0));
|
91
|
+
editor.sendPenEvent(InputEvtType.PointerMoveEvt, Vec2.of(10, 10));
|
92
|
+
editor.sendPenEvent(InputEvtType.PointerUpEvt, Vec2.of(100, 100));
|
93
|
+
|
94
|
+
const selection = selectionTool.getSelection();
|
95
|
+
if (selection === null) {
|
96
|
+
// Throw to allow TypeScript's non-null checker to understand that selection
|
97
|
+
// must be non-null after this.
|
98
|
+
throw new Error('Selection should be non-null.');
|
99
|
+
}
|
100
|
+
|
101
|
+
selection.handleBackgroundDrag(Vec2.of(0, -1000));
|
102
|
+
expect(editor.viewport.visibleRect.containsPoint(selection.region.center)).toBe(true);
|
103
|
+
});
|
83
104
|
});
|