js-draw 0.18.2 → 0.20.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 (269) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +10 -0
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -0
  5. package/dist/cjs/src/Color4.d.ts +8 -0
  6. package/dist/cjs/src/Color4.js +67 -0
  7. package/dist/cjs/src/Editor.d.ts +2 -2
  8. package/dist/cjs/src/Editor.js +7 -7
  9. package/dist/cjs/src/SVGLoader.js +77 -12
  10. package/dist/cjs/src/Viewport.d.ts +2 -0
  11. package/dist/cjs/src/Viewport.js +6 -2
  12. package/dist/cjs/src/components/AbstractComponent.d.ts +2 -2
  13. package/dist/cjs/src/components/AbstractComponent.js +3 -3
  14. package/dist/cjs/src/components/{ImageBackground.d.ts → BackgroundComponent.d.ts} +23 -3
  15. package/dist/cjs/src/components/BackgroundComponent.js +309 -0
  16. package/dist/cjs/src/components/RestylableComponent.d.ts +21 -2
  17. package/dist/cjs/src/components/Stroke.d.ts +35 -0
  18. package/dist/cjs/src/components/Stroke.js +37 -3
  19. package/dist/cjs/src/components/TextComponent.d.ts +27 -17
  20. package/dist/cjs/src/components/TextComponent.js +23 -1
  21. package/dist/cjs/src/components/lib.d.ts +4 -3
  22. package/dist/cjs/src/components/lib.js +2 -2
  23. package/dist/cjs/src/components/util/StrokeSmoother.js +25 -15
  24. package/dist/cjs/src/lib.d.ts +30 -0
  25. package/dist/cjs/src/lib.js +30 -0
  26. package/dist/cjs/src/localizations/de.js +1 -1
  27. package/dist/cjs/src/localizations/es.js +1 -1
  28. package/dist/cjs/src/math/Path.js +1 -1
  29. package/dist/cjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
  30. package/dist/cjs/src/math/polynomial/QuadraticBezier.js +115 -0
  31. package/dist/cjs/src/math/polynomial/solveQuadratic.d.ts +6 -0
  32. package/dist/cjs/src/math/polynomial/solveQuadratic.js +36 -0
  33. package/dist/cjs/src/rendering/RenderingStyle.d.ts +4 -4
  34. package/dist/cjs/src/rendering/TextRenderingStyle.d.ts +10 -10
  35. package/dist/cjs/src/rendering/lib.d.ts +2 -0
  36. package/dist/cjs/src/rendering/renderers/AbstractRenderer.d.ts +2 -2
  37. package/dist/cjs/src/rendering/renderers/CanvasRenderer.d.ts +2 -2
  38. package/dist/cjs/src/rendering/renderers/CanvasRenderer.js +5 -3
  39. package/dist/cjs/src/rendering/renderers/DummyRenderer.d.ts +2 -2
  40. package/dist/cjs/src/rendering/renderers/SVGRenderer.d.ts +2 -2
  41. package/dist/cjs/src/rendering/renderers/SVGRenderer.js +15 -6
  42. package/dist/cjs/src/rendering/renderers/TextOnlyRenderer.d.ts +2 -2
  43. package/dist/cjs/src/toolbar/IconProvider.d.ts +2 -2
  44. package/dist/cjs/src/toolbar/localization.d.ts +2 -1
  45. package/dist/cjs/src/toolbar/localization.js +3 -2
  46. package/dist/cjs/src/toolbar/widgets/BaseWidget.js +1 -1
  47. package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +5 -0
  48. package/dist/cjs/src/toolbar/widgets/DocumentPropertiesWidget.js +77 -2
  49. package/dist/cjs/src/toolbar/widgets/PenToolWidget.js +1 -1
  50. package/dist/cjs/src/tools/FindTool.js +1 -1
  51. package/dist/cjs/src/tools/SoundUITool.d.ts +24 -0
  52. package/dist/cjs/src/tools/SoundUITool.js +164 -0
  53. package/dist/cjs/src/tools/TextTool.d.ts +2 -2
  54. package/dist/cjs/src/tools/ToolController.js +6 -1
  55. package/dist/cjs/src/tools/lib.d.ts +1 -0
  56. package/dist/cjs/src/tools/lib.js +3 -1
  57. package/dist/cjs/src/tools/localization.d.ts +3 -0
  58. package/dist/cjs/src/tools/localization.js +3 -0
  59. package/dist/mjs/src/Color4.d.ts +8 -0
  60. package/dist/mjs/src/Color4.mjs +64 -0
  61. package/dist/mjs/src/Editor.d.ts +2 -2
  62. package/dist/mjs/src/Editor.mjs +6 -6
  63. package/dist/mjs/src/SVGLoader.mjs +76 -11
  64. package/dist/mjs/src/Viewport.d.ts +2 -0
  65. package/dist/mjs/src/Viewport.mjs +6 -2
  66. package/dist/mjs/src/components/AbstractComponent.d.ts +2 -2
  67. package/dist/mjs/src/components/AbstractComponent.mjs +3 -3
  68. package/dist/mjs/src/components/{ImageBackground.d.ts → BackgroundComponent.d.ts} +23 -3
  69. package/dist/mjs/src/components/BackgroundComponent.mjs +279 -0
  70. package/dist/mjs/src/components/RestylableComponent.d.ts +21 -2
  71. package/dist/mjs/src/components/Stroke.d.ts +35 -0
  72. package/dist/mjs/src/components/Stroke.mjs +37 -3
  73. package/dist/mjs/src/components/TextComponent.d.ts +27 -17
  74. package/dist/mjs/src/components/TextComponent.mjs +23 -1
  75. package/dist/mjs/src/components/lib.d.ts +4 -3
  76. package/dist/mjs/src/components/lib.mjs +2 -2
  77. package/dist/mjs/src/components/util/StrokeSmoother.mjs +25 -15
  78. package/dist/mjs/src/lib.d.ts +30 -0
  79. package/dist/mjs/src/lib.mjs +30 -0
  80. package/dist/mjs/src/localizations/de.mjs +1 -1
  81. package/dist/mjs/src/localizations/es.mjs +1 -1
  82. package/dist/mjs/src/math/Path.mjs +1 -1
  83. package/dist/mjs/src/math/polynomial/QuadraticBezier.d.ts +28 -0
  84. package/dist/mjs/src/math/polynomial/QuadraticBezier.mjs +109 -0
  85. package/dist/mjs/src/math/polynomial/solveQuadratic.d.ts +6 -0
  86. package/dist/mjs/src/math/polynomial/solveQuadratic.mjs +34 -0
  87. package/dist/mjs/src/rendering/RenderingStyle.d.ts +4 -4
  88. package/dist/mjs/src/rendering/TextRenderingStyle.d.ts +10 -10
  89. package/dist/mjs/src/rendering/lib.d.ts +2 -0
  90. package/dist/mjs/src/rendering/renderers/AbstractRenderer.d.ts +2 -2
  91. package/dist/mjs/src/rendering/renderers/CanvasRenderer.d.ts +2 -2
  92. package/dist/mjs/src/rendering/renderers/CanvasRenderer.mjs +5 -3
  93. package/dist/mjs/src/rendering/renderers/DummyRenderer.d.ts +2 -2
  94. package/dist/mjs/src/rendering/renderers/SVGRenderer.d.ts +2 -2
  95. package/dist/mjs/src/rendering/renderers/SVGRenderer.mjs +15 -6
  96. package/dist/mjs/src/rendering/renderers/TextOnlyRenderer.d.ts +2 -2
  97. package/dist/mjs/src/toolbar/IconProvider.d.ts +2 -2
  98. package/dist/mjs/src/toolbar/localization.d.ts +2 -1
  99. package/dist/mjs/src/toolbar/localization.mjs +3 -2
  100. package/dist/mjs/src/toolbar/widgets/BaseWidget.mjs +1 -1
  101. package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +5 -0
  102. package/dist/mjs/src/toolbar/widgets/DocumentPropertiesWidget.mjs +54 -2
  103. package/dist/mjs/src/toolbar/widgets/PenToolWidget.mjs +1 -1
  104. package/dist/mjs/src/tools/FindTool.mjs +1 -1
  105. package/dist/mjs/src/tools/SoundUITool.d.ts +24 -0
  106. package/dist/mjs/src/tools/SoundUITool.mjs +158 -0
  107. package/dist/mjs/src/tools/TextTool.d.ts +2 -2
  108. package/dist/mjs/src/tools/ToolController.mjs +6 -1
  109. package/dist/mjs/src/tools/lib.d.ts +1 -0
  110. package/dist/mjs/src/tools/lib.mjs +1 -0
  111. package/dist/mjs/src/tools/localization.d.ts +3 -0
  112. package/dist/mjs/src/tools/localization.mjs +3 -0
  113. package/jest.config.js +1 -1
  114. package/package.json +19 -17
  115. package/src/Editor.css +2 -2
  116. package/src/tools/SoundUITool.css +15 -0
  117. package/src/tools/tools.css +4 -0
  118. package/dist/cjs/src/components/ImageBackground.js +0 -146
  119. package/dist/mjs/src/components/ImageBackground.mjs +0 -139
  120. package/src/Color4.test.ts +0 -40
  121. package/src/Color4.ts +0 -236
  122. package/src/Editor.loadFrom.test.ts +0 -24
  123. package/src/Editor.toSVG.test.ts +0 -111
  124. package/src/Editor.ts +0 -1122
  125. package/src/EditorImage.test.ts +0 -120
  126. package/src/EditorImage.ts +0 -603
  127. package/src/EventDispatcher.test.ts +0 -123
  128. package/src/EventDispatcher.ts +0 -71
  129. package/src/Pointer.ts +0 -127
  130. package/src/SVGLoader.test.ts +0 -114
  131. package/src/SVGLoader.ts +0 -511
  132. package/src/UndoRedoHistory.test.ts +0 -33
  133. package/src/UndoRedoHistory.ts +0 -102
  134. package/src/Viewport.ts +0 -319
  135. package/src/bundle/bundled.ts +0 -7
  136. package/src/commands/Command.ts +0 -45
  137. package/src/commands/Duplicate.ts +0 -48
  138. package/src/commands/Erase.ts +0 -74
  139. package/src/commands/SerializableCommand.ts +0 -49
  140. package/src/commands/UnresolvedCommand.ts +0 -37
  141. package/src/commands/invertCommand.ts +0 -51
  142. package/src/commands/lib.ts +0 -16
  143. package/src/commands/localization.ts +0 -47
  144. package/src/commands/uniteCommands.test.ts +0 -23
  145. package/src/commands/uniteCommands.ts +0 -135
  146. package/src/components/AbstractComponent.transformBy.test.ts +0 -22
  147. package/src/components/AbstractComponent.ts +0 -364
  148. package/src/components/ImageBackground.test.ts +0 -35
  149. package/src/components/ImageBackground.ts +0 -176
  150. package/src/components/ImageComponent.ts +0 -171
  151. package/src/components/RestylableComponent.ts +0 -142
  152. package/src/components/SVGGlobalAttributesObject.ts +0 -81
  153. package/src/components/Stroke.test.ts +0 -139
  154. package/src/components/Stroke.ts +0 -245
  155. package/src/components/TextComponent.test.ts +0 -99
  156. package/src/components/TextComponent.ts +0 -315
  157. package/src/components/UnknownSVGObject.test.ts +0 -10
  158. package/src/components/UnknownSVGObject.ts +0 -60
  159. package/src/components/builders/ArrowBuilder.ts +0 -107
  160. package/src/components/builders/FreehandLineBuilder.ts +0 -212
  161. package/src/components/builders/LineBuilder.ts +0 -77
  162. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +0 -454
  163. package/src/components/builders/RectangleBuilder.ts +0 -74
  164. package/src/components/builders/types.ts +0 -15
  165. package/src/components/lib.ts +0 -25
  166. package/src/components/localization.ts +0 -22
  167. package/src/components/util/StrokeSmoother.ts +0 -293
  168. package/src/components/util/describeComponentList.ts +0 -18
  169. package/src/lib.ts +0 -37
  170. package/src/localization.ts +0 -34
  171. package/src/localizations/de.ts +0 -98
  172. package/src/localizations/en.ts +0 -8
  173. package/src/localizations/es.ts +0 -74
  174. package/src/localizations/getLocalizationTable.test.ts +0 -27
  175. package/src/localizations/getLocalizationTable.ts +0 -55
  176. package/src/math/LineSegment2.test.ts +0 -99
  177. package/src/math/LineSegment2.ts +0 -160
  178. package/src/math/Mat33.test.ts +0 -244
  179. package/src/math/Mat33.ts +0 -437
  180. package/src/math/Path.fromString.test.ts +0 -223
  181. package/src/math/Path.test.ts +0 -198
  182. package/src/math/Path.toString.test.ts +0 -77
  183. package/src/math/Path.ts +0 -790
  184. package/src/math/Rect2.test.ts +0 -204
  185. package/src/math/Rect2.ts +0 -315
  186. package/src/math/Triangle.ts +0 -29
  187. package/src/math/Vec2.test.ts +0 -30
  188. package/src/math/Vec2.ts +0 -18
  189. package/src/math/Vec3.test.ts +0 -44
  190. package/src/math/Vec3.ts +0 -218
  191. package/src/math/lib.ts +0 -15
  192. package/src/math/rounding.test.ts +0 -65
  193. package/src/math/rounding.ts +0 -156
  194. package/src/rendering/Display.ts +0 -249
  195. package/src/rendering/RenderingStyle.test.ts +0 -68
  196. package/src/rendering/RenderingStyle.ts +0 -55
  197. package/src/rendering/TextRenderingStyle.ts +0 -45
  198. package/src/rendering/caching/CacheRecord.test.ts +0 -49
  199. package/src/rendering/caching/CacheRecord.ts +0 -77
  200. package/src/rendering/caching/CacheRecordManager.ts +0 -71
  201. package/src/rendering/caching/RenderingCache.test.ts +0 -44
  202. package/src/rendering/caching/RenderingCache.ts +0 -66
  203. package/src/rendering/caching/RenderingCacheNode.ts +0 -405
  204. package/src/rendering/caching/testUtils.ts +0 -35
  205. package/src/rendering/caching/types.ts +0 -34
  206. package/src/rendering/lib.ts +0 -6
  207. package/src/rendering/localization.ts +0 -20
  208. package/src/rendering/renderers/AbstractRenderer.ts +0 -222
  209. package/src/rendering/renderers/CanvasRenderer.ts +0 -296
  210. package/src/rendering/renderers/DummyRenderer.test.ts +0 -42
  211. package/src/rendering/renderers/DummyRenderer.ts +0 -136
  212. package/src/rendering/renderers/SVGRenderer.ts +0 -354
  213. package/src/rendering/renderers/TextOnlyRenderer.ts +0 -70
  214. package/src/testing/beforeEachFile.ts +0 -8
  215. package/src/testing/createEditor.ts +0 -11
  216. package/src/testing/global.d.ts +0 -17
  217. package/src/testing/lib.ts +0 -3
  218. package/src/testing/loadExpectExtensions.ts +0 -25
  219. package/src/testing/sendPenEvent.ts +0 -31
  220. package/src/testing/sendTouchEvent.ts +0 -78
  221. package/src/toolbar/HTMLToolbar.ts +0 -492
  222. package/src/toolbar/IconProvider.ts +0 -736
  223. package/src/toolbar/lib.ts +0 -4
  224. package/src/toolbar/localization.ts +0 -106
  225. package/src/toolbar/makeColorInput.ts +0 -145
  226. package/src/toolbar/types.ts +0 -5
  227. package/src/toolbar/widgets/ActionButtonWidget.ts +0 -39
  228. package/src/toolbar/widgets/BaseToolWidget.ts +0 -56
  229. package/src/toolbar/widgets/BaseWidget.ts +0 -377
  230. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +0 -167
  231. package/src/toolbar/widgets/EraserToolWidget.ts +0 -85
  232. package/src/toolbar/widgets/HandToolWidget.ts +0 -250
  233. package/src/toolbar/widgets/InsertImageWidget.ts +0 -223
  234. package/src/toolbar/widgets/OverflowWidget.ts +0 -92
  235. package/src/toolbar/widgets/PenToolWidget.ts +0 -288
  236. package/src/toolbar/widgets/SelectionToolWidget.ts +0 -190
  237. package/src/toolbar/widgets/TextToolWidget.ts +0 -145
  238. package/src/toolbar/widgets/lib.ts +0 -13
  239. package/src/tools/BaseTool.ts +0 -76
  240. package/src/tools/Eraser.test.ts +0 -103
  241. package/src/tools/Eraser.ts +0 -139
  242. package/src/tools/FindTool.ts +0 -152
  243. package/src/tools/PanZoom.test.ts +0 -310
  244. package/src/tools/PanZoom.ts +0 -520
  245. package/src/tools/PasteHandler.ts +0 -95
  246. package/src/tools/Pen.test.ts +0 -194
  247. package/src/tools/Pen.ts +0 -226
  248. package/src/tools/PipetteTool.ts +0 -55
  249. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +0 -28
  250. package/src/tools/SelectionTool/Selection.ts +0 -607
  251. package/src/tools/SelectionTool/SelectionHandle.ts +0 -108
  252. package/src/tools/SelectionTool/SelectionTool.test.ts +0 -261
  253. package/src/tools/SelectionTool/SelectionTool.ts +0 -480
  254. package/src/tools/SelectionTool/TransformMode.ts +0 -114
  255. package/src/tools/SelectionTool/types.ts +0 -11
  256. package/src/tools/TextTool.ts +0 -326
  257. package/src/tools/ToolController.ts +0 -178
  258. package/src/tools/ToolEnabledGroup.ts +0 -14
  259. package/src/tools/ToolSwitcherShortcut.ts +0 -39
  260. package/src/tools/ToolbarShortcutHandler.ts +0 -34
  261. package/src/tools/UndoRedoShortcut.test.ts +0 -56
  262. package/src/tools/UndoRedoShortcut.ts +0 -25
  263. package/src/tools/lib.ts +0 -21
  264. package/src/tools/localization.ts +0 -66
  265. package/src/types.ts +0 -234
  266. package/src/util/assertions.ts +0 -55
  267. package/src/util/fileToBase64.ts +0 -18
  268. package/src/util/untilNextAnimationFrame.ts +0 -9
  269. package/src/util/waitForTimeout.ts +0 -9
@@ -1,607 +0,0 @@
1
- /**
2
- * @internal
3
- * @packageDocumentation
4
- */
5
-
6
- import SerializableCommand from '../../commands/SerializableCommand';
7
- import Editor from '../../Editor';
8
- import Mat33 from '../../math/Mat33';
9
- import Rect2 from '../../math/Rect2';
10
- import { Point2, Vec2 } from '../../math/Vec2';
11
- import Pointer from '../../Pointer';
12
- import SelectionHandle, { HandleShape, handleSize } from './SelectionHandle';
13
- import { cssPrefix } from './SelectionTool';
14
- import AbstractComponent from '../../components/AbstractComponent';
15
- import { Mat33Array } from '../../math/Mat33';
16
- import { EditorLocalization } from '../../localization';
17
- import Viewport from '../../Viewport';
18
- import Erase from '../../commands/Erase';
19
- import Duplicate from '../../commands/Duplicate';
20
- import Command from '../../commands/Command';
21
- import { DragTransformer, ResizeTransformer, RotateTransformer } from './TransformMode';
22
- import { ResizeMode } from './types';
23
- import EditorImage from '../../EditorImage';
24
-
25
- const updateChunkSize = 100;
26
- const maxPreviewElemCount = 500;
27
-
28
- // @internal
29
- export default class Selection {
30
- private handles: SelectionHandle[];
31
- private originalRegion: Rect2;
32
-
33
- private transformers;
34
- private transform: Mat33 = Mat33.identity;
35
-
36
- private selectedElems: AbstractComponent[] = [];
37
-
38
- private container: HTMLElement;
39
- private backgroundElem: HTMLElement;
40
-
41
- private hasParent: boolean = true;
42
-
43
- public constructor(startPoint: Point2, private editor: Editor) {
44
- this.originalRegion = new Rect2(startPoint.x, startPoint.y, 0, 0);
45
- this.transformers = {
46
- drag: new DragTransformer(editor, this),
47
- resize: new ResizeTransformer(editor, this),
48
- rotate: new RotateTransformer(editor, this),
49
- };
50
-
51
- this.container = document.createElement('div');
52
- this.backgroundElem = document.createElement('div');
53
- this.backgroundElem.classList.add(`${cssPrefix}selection-background`);
54
- this.container.appendChild(this.backgroundElem);
55
-
56
- const resizeHorizontalHandle = new SelectionHandle(
57
- HandleShape.Square,
58
- Vec2.of(1, 0.5),
59
- this,
60
- (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.HorizontalOnly),
61
- (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint),
62
- () => this.transformers.resize.onDragEnd(),
63
- );
64
-
65
- const resizeVerticalHandle = new SelectionHandle(
66
- HandleShape.Square,
67
- Vec2.of(0.5, 1),
68
- this,
69
- (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.VerticalOnly),
70
- (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint),
71
- () => this.transformers.resize.onDragEnd(),
72
- );
73
-
74
- const resizeBothHandle = new SelectionHandle(
75
- HandleShape.Square,
76
- Vec2.of(1, 1),
77
- this,
78
- (startPoint) => this.transformers.resize.onDragStart(startPoint, ResizeMode.Both),
79
- (currentPoint) => this.transformers.resize.onDragUpdate(currentPoint),
80
- () => this.transformers.resize.onDragEnd(),
81
- );
82
-
83
- const rotationHandle = new SelectionHandle(
84
- HandleShape.Circle,
85
- Vec2.of(0.5, 0),
86
- this,
87
- (startPoint) => this.transformers.rotate.onDragStart(startPoint),
88
- (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint),
89
- () => this.transformers.rotate.onDragEnd(),
90
- );
91
-
92
- this.handles = [
93
- resizeBothHandle,
94
- resizeHorizontalHandle,
95
- resizeVerticalHandle,
96
- rotationHandle,
97
- ];
98
-
99
- for (const handle of this.handles) {
100
- handle.addTo(this.backgroundElem);
101
- }
102
- }
103
-
104
- // @internal Intended for unit tests
105
- public getBackgroundElem(): HTMLElement {
106
- return this.backgroundElem;
107
- }
108
-
109
- public getTransform(): Mat33 {
110
- return this.transform;
111
- }
112
-
113
- public get preTransformRegion(): Rect2 {
114
- return this.originalRegion;
115
- }
116
-
117
- public get region(): Rect2 {
118
- // TODO: This currently assumes that the region rotates about its center.
119
- // This may not be true.
120
- const rotationMatrix = Mat33.zRotation(this.regionRotation, this.originalRegion.center);
121
- const scaleAndTranslateMat = this.transform.rightMul(rotationMatrix.inverse());
122
- return this.originalRegion.transformedBoundingBox(scaleAndTranslateMat);
123
- }
124
-
125
- /**
126
- * Computes and returns the bounding box of the selection without
127
- * any additional padding. Computes directly from the elements that are selected.
128
- * @internal
129
- */
130
- public computeTightBoundingBox() {
131
- const bbox = this.selectedElems.reduce((
132
- accumulator: Rect2|null, elem: AbstractComponent
133
- ): Rect2 => {
134
- return (accumulator ?? elem.getBBox()).union(elem.getBBox());
135
- }, null);
136
-
137
- return bbox ?? Rect2.empty;
138
- }
139
-
140
- public get regionRotation(): number {
141
- return this.transform.transformVec3(Vec2.unitX).angle();
142
- }
143
-
144
- public get preTransformedScreenRegion(): Rect2 {
145
- const toScreen = (vec: Point2) => this.editor.viewport.canvasToScreen(vec);
146
- return Rect2.fromCorners(
147
- toScreen(this.preTransformRegion.topLeft),
148
- toScreen(this.preTransformRegion.bottomRight)
149
- );
150
- }
151
-
152
- public get preTransformedScreenRegionRotation(): number {
153
- return this.editor.viewport.getRotationAngle();
154
- }
155
-
156
- public get screenRegion(): Rect2 {
157
- const toScreen = this.editor.viewport.canvasToScreenTransform;
158
- const scaleFactor = this.editor.viewport.getScaleFactor();
159
-
160
- const screenCenter = toScreen.transformVec2(this.region.center);
161
-
162
- return new Rect2(
163
- screenCenter.x, screenCenter.y, scaleFactor * this.region.width, scaleFactor * this.region.height
164
- ).translatedBy(this.region.size.times(-scaleFactor/2));
165
- }
166
-
167
- public get screenRegionRotation(): number {
168
- return this.regionRotation + this.editor.viewport.getRotationAngle();
169
- }
170
-
171
- // Applies, previews, but doesn't finalize the given transformation.
172
- public setTransform(transform: Mat33, preview: boolean = true) {
173
- this.transform = transform;
174
-
175
- if (preview && this.hasParent) {
176
- this.scrollTo();
177
- this.previewTransformCmds();
178
- }
179
- }
180
-
181
- // Applies the current transformation to the selection
182
- public finalizeTransform() {
183
- const fullTransform = this.transform;
184
- const selectedElems = this.selectedElems;
185
-
186
- // Reset for the next drag
187
- this.originalRegion = this.originalRegion.transformedBoundingBox(this.transform);
188
- this.transform = Mat33.identity;
189
-
190
- // Make the commands undo-able
191
- this.editor.dispatch(new Selection.ApplyTransformationCommand(
192
- this, selectedElems, fullTransform
193
- ));
194
- }
195
-
196
- static {
197
- SerializableCommand.register('selection-tool-transform', (json: any, _editor) => {
198
- // The selection box is lost when serializing/deserializing. No need to store box rotation
199
- const fullTransform: Mat33 = new Mat33(...(json.transform as Mat33Array));
200
- const elemIds: string[] = (json.elems as any[] ?? []);
201
-
202
- return new this.ApplyTransformationCommand(null, elemIds, fullTransform);
203
- });
204
- }
205
-
206
- private static ApplyTransformationCommand = class extends SerializableCommand {
207
- private transformCommands: Command[];
208
- private selectedElemIds: string[];
209
-
210
- public constructor(
211
- private selection: Selection|null,
212
-
213
- // If a `string[]`, selectedElems is a list of element IDs.
214
- selectedElems: AbstractComponent[]|string[],
215
-
216
- // Full transformation used to transform elements.
217
- private fullTransform: Mat33,
218
- ) {
219
- super('selection-tool-transform');
220
-
221
- const isIDList = (arr: AbstractComponent[]|string[]): arr is string[] => {
222
- return typeof arr[0] === 'string';
223
- };
224
-
225
- // If a list of element IDs,
226
- if (isIDList(selectedElems)) {
227
- this.selectedElemIds = selectedElems as string[];
228
- } else {
229
- this.selectedElemIds = (selectedElems as AbstractComponent[]).map(elem => elem.getId());
230
- this.transformCommands = selectedElems.map(elem => {
231
- return elem.transformBy(this.fullTransform);
232
- });
233
- }
234
- }
235
-
236
- private resolveToElems(editor: Editor) {
237
- if (this.transformCommands) {
238
- return;
239
- }
240
-
241
- this.transformCommands = this.selectedElemIds.map(id => {
242
- const elem = editor.image.lookupElement(id);
243
-
244
- if (!elem) {
245
- throw new Error(`Unable to find element with ID, ${id}.`);
246
- }
247
-
248
- return elem.transformBy(this.fullTransform);
249
- });
250
- }
251
-
252
- public async apply(editor: Editor) {
253
- this.resolveToElems(editor);
254
-
255
- this.selection?.setTransform(this.fullTransform, false);
256
- this.selection?.updateUI();
257
- await editor.asyncApplyCommands(this.transformCommands, updateChunkSize);
258
- this.selection?.setTransform(Mat33.identity, false);
259
- this.selection?.recomputeRegion();
260
- this.selection?.updateUI();
261
- }
262
-
263
- public async unapply(editor: Editor) {
264
- this.resolveToElems(editor);
265
-
266
- this.selection?.setTransform(this.fullTransform.inverse(), false);
267
- this.selection?.updateUI();
268
-
269
- await editor.asyncUnapplyCommands(this.transformCommands, updateChunkSize, true);
270
- this.selection?.setTransform(Mat33.identity);
271
- this.selection?.recomputeRegion();
272
- this.selection?.updateUI();
273
- }
274
-
275
- protected serializeToJSON() {
276
- return {
277
- elems: this.selectedElemIds,
278
- transform: this.fullTransform.toArray(),
279
- };
280
- }
281
-
282
- public description(_editor: Editor, localizationTable: EditorLocalization) {
283
- return localizationTable.transformedElements(this.selectedElemIds.length);
284
- }
285
- };
286
-
287
- // Preview the effects of the current transformation on the selection
288
- private previewTransformCmds() {
289
- // Don't render what we're moving if it's likely to be slow.
290
- if (this.selectedElems.length > maxPreviewElemCount) {
291
- this.updateUI();
292
- return;
293
- }
294
-
295
- const wetInkRenderer = this.editor.display.getWetInkRenderer();
296
- wetInkRenderer.clear();
297
- wetInkRenderer.pushTransform(this.transform);
298
-
299
- const viewportVisibleRect = this.editor.viewport.visibleRect;
300
- const visibleRect = viewportVisibleRect.transformedBoundingBox(this.transform.inverse());
301
-
302
- for (const elem of this.selectedElems) {
303
- elem.render(wetInkRenderer, visibleRect);
304
- }
305
-
306
- wetInkRenderer.popTransform();
307
-
308
- this.updateUI();
309
- }
310
-
311
- // Find the objects corresponding to this in the document,
312
- // select them.
313
- // Returns false iff nothing was selected.
314
- public resolveToObjects(): boolean {
315
- let singleItemSelectionMode = false;
316
- this.transform = Mat33.identity;
317
-
318
- // Grow the rectangle, if necessary
319
- if (this.region.w === 0 || this.region.h === 0) {
320
- const padding = this.editor.viewport.visibleRect.maxDimension / 200;
321
- this.originalRegion = Rect2.bboxOf(this.region.corners, padding);
322
-
323
- // Only select one item if the rectangle was very small.
324
- singleItemSelectionMode = true;
325
- }
326
-
327
- this.selectedElems = this.editor.image.getElementsIntersectingRegion(this.region).filter(elem => {
328
- return elem.intersectsRect(this.region) && elem.isSelectable();
329
- });
330
-
331
- if (singleItemSelectionMode && this.selectedElems.length > 0) {
332
- this.selectedElems = [ this.selectedElems[this.selectedElems.length - 1] ];
333
- }
334
-
335
- // Find the bounding box of all selected elements.
336
- if (!this.recomputeRegion()) {
337
- return false;
338
- }
339
- this.updateUI();
340
-
341
- return true;
342
- }
343
-
344
- // Recompute this' region from the selected elements.
345
- // Returns false if the selection is empty.
346
- public recomputeRegion(): boolean {
347
- const newRegion = this.computeTightBoundingBox();
348
-
349
- if (!newRegion) {
350
- this.cancelSelection();
351
- return false;
352
- }
353
-
354
- this.originalRegion = newRegion;
355
-
356
- const minSize = this.getMinCanvasSize();
357
- if (this.originalRegion.w < minSize || this.originalRegion.h < minSize) {
358
- // Add padding
359
- const padding = minSize / 2;
360
- this.originalRegion = Rect2.bboxOf(
361
- this.originalRegion.corners, padding
362
- );
363
- }
364
-
365
- return true;
366
- }
367
-
368
- public getMinCanvasSize(): number {
369
- const canvasHandleSize = handleSize / this.editor.viewport.getScaleFactor();
370
- return canvasHandleSize * 2;
371
- }
372
-
373
- public getSelectedItemCount() {
374
- return this.selectedElems.length;
375
- }
376
-
377
- // @internal
378
- public updateUI() {
379
- // Don't update old selections.
380
- if (!this.hasParent) {
381
- return;
382
- }
383
-
384
- // marginLeft, marginTop: Display relative to the top left of the selection overlay.
385
- // left, top don't work for this.
386
- this.backgroundElem.style.marginLeft = `${this.screenRegion.topLeft.x}px`;
387
- this.backgroundElem.style.marginTop = `${this.screenRegion.topLeft.y}px`;
388
-
389
- this.backgroundElem.style.width = `${this.screenRegion.width}px`;
390
- this.backgroundElem.style.height = `${this.screenRegion.height}px`;
391
-
392
- const rotationDeg = this.screenRegionRotation * 180 / Math.PI;
393
- this.backgroundElem.style.transform = `rotate(${rotationDeg}deg)`;
394
- this.backgroundElem.style.transformOrigin = 'center';
395
-
396
- for (const handle of this.handles) {
397
- handle.updatePosition();
398
- }
399
- }
400
-
401
- // Maps IDs to whether we removed the component from the image
402
- private removedFromImage: Record<string, boolean> = {};
403
-
404
- // Add/remove the contents of this' seleciton from the editor.
405
- // Used to prevent previewed content from looking like duplicate content
406
- // while dragging.
407
- //
408
- // Does nothing if a large number of elements are selected (and so modifying
409
- // the editor image is likely to be slow.)
410
- //
411
- // If removed from the image, selected elements are drawn as wet ink.
412
- private addRemoveSelectionFromImage(inImage: boolean) {
413
- // Don't hide elements if doing so will be slow.
414
- if (!inImage && this.selectedElems.length > maxPreviewElemCount) {
415
- return;
416
- }
417
-
418
- for (const elem of this.selectedElems) {
419
- const parent = this.editor.image.findParent(elem);
420
-
421
- if (!inImage && parent) {
422
- this.removedFromImage[elem.getId()] = true;
423
- parent.remove();
424
- }
425
- // If we're making things visible and the selected object wasn't previously
426
- // visible,
427
- else if (!parent && this.removedFromImage[elem.getId()]) {
428
- EditorImage.addElement(elem).apply(this.editor);
429
-
430
- this.removedFromImage[elem.getId()] = false;
431
- delete this.removedFromImage[elem.getId()];
432
- }
433
- }
434
-
435
- // Don't await queueRerender. If we're running in a test, the re-render might never
436
- // happen.
437
- this.editor.queueRerender().then(() => {
438
- if (!inImage) {
439
- this.previewTransformCmds();
440
- }
441
- });
442
- }
443
-
444
- private removeDeletedElemsFromSelection() {
445
- // Remove any deleted elements from the selection.
446
- this.selectedElems = this.selectedElems.filter(elem => {
447
- const hasParent = !!this.editor.image.findParent(elem);
448
-
449
- // If we removed the element and haven't added it back yet, don't remove it
450
- // from the selection.
451
- const weRemoved = this.removedFromImage[elem.getId()];
452
- return hasParent || weRemoved;
453
- });
454
- }
455
-
456
- private targetHandle: SelectionHandle|null = null;
457
- private backgroundDragging: boolean = false;
458
- public onDragStart(pointer: Pointer, target: EventTarget): boolean {
459
- this.removeDeletedElemsFromSelection();
460
- this.addRemoveSelectionFromImage(false);
461
-
462
- for (const handle of this.handles) {
463
- if (handle.isTarget(target)) {
464
- handle.handleDragStart(pointer);
465
- this.targetHandle = handle;
466
- return true;
467
- }
468
- }
469
-
470
- if (this.backgroundElem === target) {
471
- this.backgroundDragging = true;
472
- this.transformers.drag.onDragStart(pointer.canvasPos);
473
- return true;
474
- }
475
-
476
- return false;
477
- }
478
-
479
- public onDragUpdate(pointer: Pointer) {
480
- if (this.backgroundDragging) {
481
- this.transformers.drag.onDragUpdate(pointer.canvasPos);
482
- }
483
-
484
- if (this.targetHandle) {
485
- this.targetHandle.handleDragUpdate(pointer);
486
- }
487
- }
488
-
489
- public onDragEnd() {
490
- if (this.backgroundDragging) {
491
- this.transformers.drag.onDragEnd();
492
- }
493
- else if (this.targetHandle) {
494
- this.targetHandle.handleDragEnd();
495
- }
496
-
497
- this.addRemoveSelectionFromImage(true);
498
-
499
- this.backgroundDragging = false;
500
- this.targetHandle = null;
501
- this.updateUI();
502
- }
503
-
504
- public onDragCancel() {
505
- this.backgroundDragging = false;
506
- this.targetHandle = null;
507
- this.setTransform(Mat33.identity);
508
-
509
- this.addRemoveSelectionFromImage(true);
510
- }
511
-
512
- // Scroll the viewport to this. Does not zoom
513
- public async scrollTo() {
514
- if (this.selectedElems.length === 0) {
515
- return;
516
- }
517
-
518
- const screenRect = new Rect2(0, 0, this.editor.display.width, this.editor.display.height);
519
- if (!screenRect.containsPoint(this.screenRegion.center)) {
520
- const closestPoint = screenRect.getClosestPointOnBoundaryTo(this.screenRegion.center);
521
- const screenDelta = this.screenRegion.center.minus(closestPoint);
522
- const delta = this.editor.viewport.screenToCanvasTransform.transformVec3(screenDelta);
523
- await this.editor.dispatchNoAnnounce(
524
- Viewport.transformBy(Mat33.translation(delta.times(-1))), false
525
- );
526
-
527
- // Re-renders clear wet ink, so we need to re-draw the preview
528
- // after the full re-render.
529
- await this.editor.queueRerender();
530
- this.previewTransformCmds();
531
- }
532
- }
533
-
534
- public deleteSelectedObjects(): Command {
535
- if (this.backgroundDragging || this.targetHandle) {
536
- this.onDragEnd();
537
- }
538
-
539
- return new Erase(this.selectedElems);
540
- }
541
-
542
- public async duplicateSelectedObjects(): Promise<Command> {
543
- const wasTransforming = this.backgroundDragging || this.targetHandle;
544
- let tmpApplyCommand: Command|null = null;
545
-
546
- if (wasTransforming) {
547
- // Don't update the selection's focus when redoing/undoing
548
- const selectionToUpdate: Selection|null = null;
549
- tmpApplyCommand = new Selection.ApplyTransformationCommand(
550
- selectionToUpdate, this.selectedElems, this.transform
551
- );
552
-
553
- // Transform to ensure that the duplicates are in the correct location
554
- await tmpApplyCommand.apply(this.editor);
555
-
556
- // Show items again
557
- this.addRemoveSelectionFromImage(true);
558
- }
559
-
560
- const duplicateCommand = new Duplicate(this.selectedElems);
561
-
562
- if (wasTransforming) {
563
- // Move the selected objects back to the correct location.
564
- await tmpApplyCommand?.unapply(this.editor);
565
- this.addRemoveSelectionFromImage(false);
566
-
567
- this.previewTransformCmds();
568
- this.updateUI();
569
- }
570
-
571
- return duplicateCommand;
572
- }
573
-
574
- public addTo(elem: HTMLElement) {
575
- if (this.container.parentElement) {
576
- this.container.remove();
577
- }
578
-
579
- elem.appendChild(this.container);
580
- this.hasParent = true;
581
- }
582
-
583
- public setToPoint(point: Point2) {
584
- this.originalRegion = this.originalRegion.grownToPoint(point);
585
- this.updateUI();
586
- }
587
-
588
- public cancelSelection() {
589
- if (this.container.parentElement) {
590
- this.container.remove();
591
- }
592
- this.originalRegion = Rect2.empty;
593
- this.hasParent = false;
594
- }
595
-
596
- public setSelectedObjects(objects: AbstractComponent[], bbox: Rect2) {
597
- this.addRemoveSelectionFromImage(true);
598
- this.originalRegion = bbox;
599
- this.selectedElems = objects.filter(object => object.isSelectable());
600
- this.updateUI();
601
- }
602
-
603
- public getSelectedObjects(): AbstractComponent[] {
604
- return this.selectedElems;
605
- }
606
- }
607
-
@@ -1,108 +0,0 @@
1
- import { assertUnreachable } from '../../util/assertions';
2
- import { Point2, Vec2 } from '../../math/Vec2';
3
- import { cssPrefix } from './SelectionTool';
4
- import Selection from './Selection';
5
- import Pointer from '../../Pointer';
6
-
7
- export enum HandleShape {
8
- Circle,
9
- Square,
10
- }
11
-
12
- export const handleSize = 30;
13
-
14
- // `startPoint` is in screen coordinates
15
- export type DragStartCallback = (startPoint: Point2)=>void;
16
- export type DragUpdateCallback = (canvasPoint: Point2)=> void;
17
- export type DragEndCallback = ()=> void;
18
-
19
- export default class SelectionHandle {
20
- private element: HTMLElement;
21
- private snapToGrid: boolean;
22
-
23
- // Bounding box in screen coordinates.
24
-
25
- public constructor(
26
- readonly shape: HandleShape,
27
- private readonly parentSide: Vec2,
28
- private readonly parent: Selection,
29
-
30
- private readonly onDragStart: DragStartCallback,
31
- private readonly onDragUpdate: DragUpdateCallback,
32
- private readonly onDragEnd: DragEndCallback,
33
- ) {
34
- this.element = document.createElement('div');
35
- this.element.classList.add(`${cssPrefix}handle`);
36
-
37
- switch (shape) {
38
- case HandleShape.Circle:
39
- this.element.classList.add(`${cssPrefix}circle`);
40
- break;
41
- case HandleShape.Square:
42
- this.element.classList.add(`${cssPrefix}square`);
43
- break;
44
- default:
45
- assertUnreachable(shape);
46
- }
47
-
48
- this.updatePosition();
49
- }
50
-
51
- /**
52
- * Adds this to `container`, where `conatiner` should be the background/selection
53
- * element visible on the screen.
54
- */
55
- public addTo(container: HTMLElement) {
56
- container.appendChild(this.element);
57
- }
58
-
59
- public updatePosition() {
60
- const parentRect = this.parent.screenRegion;
61
- const size = Vec2.of(handleSize, handleSize);
62
- const topLeft = parentRect.size.scale(this.parentSide)
63
- // Center
64
- .minus(size.times(1/2));
65
-
66
- // Position within the selection box.
67
- this.element.style.marginLeft = `${topLeft.x}px`;
68
- this.element.style.marginTop = `${topLeft.y}px`;
69
- this.element.style.width = `${size.x}px`;
70
- this.element.style.height = `${size.y}px`;
71
- }
72
-
73
- /**
74
- * @returns `true` if the given `EventTarget` matches this.
75
- */
76
- public isTarget(target: EventTarget): boolean {
77
- return target === this.element;
78
- }
79
-
80
- private dragLastPos: Vec2|null = null;
81
- public handleDragStart(pointer: Pointer) {
82
- this.onDragStart(pointer.canvasPos);
83
- this.dragLastPos = pointer.canvasPos;
84
- }
85
-
86
- public handleDragUpdate(pointer: Pointer) {
87
- if (!this.dragLastPos) {
88
- return;
89
- }
90
-
91
- this.onDragUpdate(pointer.canvasPos);
92
- }
93
-
94
- public handleDragEnd() {
95
- if (!this.dragLastPos) {
96
- return;
97
- }
98
- this.onDragEnd();
99
- }
100
-
101
- public setSnapToGrid(snap: boolean) {
102
- this.snapToGrid = snap;
103
- }
104
-
105
- public isSnappingToGrid() {
106
- return this.snapToGrid;
107
- }
108
- }