js-draw 0.0.3 → 0.0.6

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 (87) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +108 -0
  3. package/build_tools/BundledFile.ts +167 -0
  4. package/build_tools/bundle.ts +11 -0
  5. package/dist/build_tools/BundledFile.d.ts +13 -0
  6. package/dist/build_tools/BundledFile.js +157 -0
  7. package/dist/build_tools/bundle.d.ts +1 -0
  8. package/dist/build_tools/bundle.js +5 -0
  9. package/dist/bundle.js +1 -0
  10. package/dist/src/Display.js +4 -1
  11. package/dist/src/Editor.d.ts +9 -2
  12. package/dist/src/Editor.js +36 -7
  13. package/dist/src/EditorImage.d.ts +4 -2
  14. package/dist/src/EditorImage.js +29 -5
  15. package/dist/src/Pointer.js +1 -1
  16. package/dist/src/SVGLoader.js +3 -1
  17. package/dist/src/Viewport.d.ts +2 -2
  18. package/dist/src/bundle/bundled.d.ts +4 -0
  19. package/dist/src/bundle/bundled.js +5 -0
  20. package/dist/src/components/AbstractComponent.d.ts +1 -1
  21. package/dist/src/components/Stroke.d.ts +1 -1
  22. package/dist/src/components/Stroke.js +1 -1
  23. package/dist/src/components/UnknownSVGObject.d.ts +1 -1
  24. package/dist/src/components/builders/ArrowBuilder.d.ts +17 -0
  25. package/dist/src/components/builders/ArrowBuilder.js +83 -0
  26. package/dist/src/{StrokeBuilder.d.ts → components/builders/FreehandLineBuilder.d.ts} +9 -13
  27. package/dist/src/{StrokeBuilder.js → components/builders/FreehandLineBuilder.js} +42 -12
  28. package/dist/src/components/builders/LineBuilder.d.ts +16 -0
  29. package/dist/src/components/builders/LineBuilder.js +57 -0
  30. package/dist/src/components/builders/RectangleBuilder.d.ts +18 -0
  31. package/dist/src/components/builders/RectangleBuilder.js +41 -0
  32. package/dist/src/components/builders/types.d.ts +12 -0
  33. package/dist/src/components/builders/types.js +1 -0
  34. package/dist/src/geometry/Path.d.ts +1 -0
  35. package/dist/src/geometry/Path.js +43 -0
  36. package/dist/src/geometry/Vec3.d.ts +2 -0
  37. package/dist/src/geometry/Vec3.js +13 -0
  38. package/dist/src/localization.d.ts +1 -1
  39. package/dist/src/localization.js +2 -2
  40. package/dist/src/rendering/AbstractRenderer.js +3 -25
  41. package/dist/src/toolbar/HTMLToolbar.d.ts +5 -3
  42. package/dist/src/toolbar/HTMLToolbar.js +139 -30
  43. package/dist/src/toolbar/localization.d.ts +20 -0
  44. package/dist/src/toolbar/localization.js +19 -0
  45. package/dist/src/toolbar/types.d.ts +0 -13
  46. package/dist/src/tools/Pen.d.ts +13 -3
  47. package/dist/src/tools/Pen.js +37 -28
  48. package/dist/src/tools/SelectionTool.js +1 -1
  49. package/dist/src/tools/ToolController.js +3 -3
  50. package/dist/src/types.d.ts +14 -2
  51. package/dist/src/types.js +1 -0
  52. package/dist-test/test-dist-bundle.html +35 -0
  53. package/package.json +15 -5
  54. package/src/Display.ts +3 -1
  55. package/src/Editor.css +0 -1
  56. package/src/Editor.ts +62 -13
  57. package/src/EditorImage.test.ts +5 -3
  58. package/src/EditorImage.ts +31 -3
  59. package/src/Pointer.ts +1 -1
  60. package/src/SVGLoader.ts +5 -1
  61. package/src/Viewport.ts +1 -1
  62. package/src/bundle/bundled.ts +7 -0
  63. package/src/components/AbstractComponent.ts +1 -1
  64. package/src/components/Stroke.ts +2 -2
  65. package/src/components/UnknownSVGObject.ts +1 -1
  66. package/src/components/builders/ArrowBuilder.ts +104 -0
  67. package/src/{StrokeBuilder.ts → components/builders/FreehandLineBuilder.ts} +59 -22
  68. package/src/components/builders/LineBuilder.ts +75 -0
  69. package/src/components/builders/RectangleBuilder.ts +59 -0
  70. package/src/components/builders/types.ts +15 -0
  71. package/src/geometry/Path.fromString.test.ts +11 -24
  72. package/src/geometry/Path.ts +56 -0
  73. package/src/geometry/Vec2.test.ts +1 -0
  74. package/src/geometry/Vec3.test.ts +14 -0
  75. package/src/geometry/Vec3.ts +16 -0
  76. package/src/localization.ts +2 -3
  77. package/src/rendering/AbstractRenderer.ts +3 -32
  78. package/src/{editorStyles.js → styles.js} +0 -0
  79. package/src/toolbar/HTMLToolbar.ts +167 -39
  80. package/src/toolbar/localization.ts +44 -0
  81. package/src/toolbar/toolbar.css +12 -0
  82. package/src/toolbar/types.ts +0 -16
  83. package/src/tools/Pen.ts +56 -34
  84. package/src/tools/SelectionTool.test.ts +1 -1
  85. package/src/tools/SelectionTool.ts +1 -1
  86. package/src/tools/ToolController.ts +3 -3
  87. package/src/types.ts +16 -1
@@ -5,6 +5,15 @@ import Color4 from '../Color4';
5
5
  import Pen from '../tools/Pen';
6
6
  import Eraser from '../tools/Eraser';
7
7
  import SelectionTool from '../tools/SelectionTool';
8
+ import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder';
9
+ import { Vec2 } from '../geometry/Vec2';
10
+ import SVGRenderer from '../rendering/SVGRenderer';
11
+ import Viewport from '../Viewport';
12
+ import EventDispatcher from '../EventDispatcher';
13
+ import { makeArrowBuilder } from '../components/builders/ArrowBuilder';
14
+ import { makeLineBuilder } from '../components/builders/LineBuilder';
15
+ import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../components/builders/RectangleBuilder';
16
+ import { defaultToolbarLocalization } from './localization';
8
17
  const primaryForegroundFill = `
9
18
  style='fill: var(--primary-foreground-color);'
10
19
  `;
@@ -12,6 +21,7 @@ const primaryForegroundStrokeFill = `
12
21
  style='fill: var(--primary-foreground-color); stroke: var(--primary-foreground-color);'
13
22
  `;
14
23
  const toolbarCSSPrefix = 'toolbar-';
24
+ const svgNamespace = 'http://www.w3.org/2000/svg';
15
25
  class ToolbarWidget {
16
26
  constructor(editor, targetTool, localizationTable) {
17
27
  this.editor = editor;
@@ -121,7 +131,7 @@ class ToolbarWidget {
121
131
  return !this.dropdownContainer.classList.contains('hidden');
122
132
  }
123
133
  createDropdownIcon() {
124
- const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
134
+ const icon = document.createElementNS(svgNamespace, 'svg');
125
135
  icon.innerHTML = `
126
136
  <g>
127
137
  <path
@@ -140,7 +150,7 @@ class EraserWidget extends ToolbarWidget {
140
150
  return this.localizationTable.eraser;
141
151
  }
142
152
  createIcon() {
143
- const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
153
+ const icon = document.createElementNS(svgNamespace, 'svg');
144
154
  // Draw an eraser-like shape
145
155
  icon.innerHTML = `
146
156
  <g>
@@ -168,7 +178,7 @@ class SelectionWidget extends ToolbarWidget {
168
178
  return this.localizationTable.select;
169
179
  }
170
180
  createIcon() {
171
- const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
181
+ const icon = document.createElementNS(svgNamespace, 'svg');
172
182
  // Draw a cursor-like shape
173
183
  icon.innerHTML = `
174
184
  <g>
@@ -209,7 +219,7 @@ class TouchDrawingWidget extends ToolbarWidget {
209
219
  return this.localizationTable.touchDrawing;
210
220
  }
211
221
  createIcon() {
212
- const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
222
+ const icon = document.createElementNS(svgNamespace, 'svg');
213
223
  // Draw a cursor-like shape
214
224
  icon.innerHTML = `
215
225
  <g>
@@ -241,9 +251,10 @@ class TouchDrawingWidget extends ToolbarWidget {
241
251
  }
242
252
  }
243
253
  class PenWidget extends ToolbarWidget {
244
- constructor(editor, tool, localization) {
254
+ constructor(editor, tool, localization, penTypes) {
245
255
  super(editor, tool, localization);
246
256
  this.tool = tool;
257
+ this.penTypes = penTypes;
247
258
  this.updateInputs = () => { };
248
259
  this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
249
260
  if (toolEvt.kind !== EditorEventType.ToolUpdated) {
@@ -259,17 +270,14 @@ class PenWidget extends ToolbarWidget {
259
270
  getTitle() {
260
271
  return this.targetTool.description;
261
272
  }
262
- createIcon() {
263
- // We need to use createElementNS to embed an SVG element in HTML.
264
- // See http://zhangwenli.com/blog/2017/07/26/createelementns/
265
- const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
273
+ makePenIcon(elem) {
266
274
  // Use a square-root scale to prevent the pen's tip from overflowing.
267
275
  const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 2);
268
276
  const color = this.tool.getColor();
269
277
  // Draw a pen-like shape
270
278
  const primaryStrokeTipPath = `M14,63 L${50 - scale},95 L${50 + scale},90 L88,60 Z`;
271
279
  const backgroundStrokeTipPath = `M14,63 L${50 - scale},85 L${50 + scale},83 L88,60 Z`;
272
- icon.innerHTML = `
280
+ elem.innerHTML = `
273
281
  <defs>
274
282
  <pattern
275
283
  id='checkerboard'
@@ -302,18 +310,61 @@ class PenWidget extends ToolbarWidget {
302
310
  />
303
311
  </g>
304
312
  `;
313
+ }
314
+ // Draws an icon with the pen.
315
+ makeDrawnIcon(icon) {
316
+ const strokeFactory = this.tool.getStrokeFactory();
317
+ const toolThickness = this.tool.getThickness();
318
+ const nowTime = (new Date()).getTime();
319
+ const startPoint = {
320
+ pos: Vec2.of(10, 10),
321
+ width: toolThickness / 5,
322
+ color: this.tool.getColor(),
323
+ time: nowTime - 100,
324
+ };
325
+ const endPoint = {
326
+ pos: Vec2.of(90, 90),
327
+ width: toolThickness / 5,
328
+ color: this.tool.getColor(),
329
+ time: nowTime,
330
+ };
331
+ const builder = strokeFactory(startPoint, this.editor.viewport);
332
+ builder.addPoint(endPoint);
333
+ const viewport = new Viewport(new EventDispatcher());
334
+ viewport.updateScreenSize(Vec2.of(100, 100));
335
+ const renderer = new SVGRenderer(icon, viewport);
336
+ builder.preview(renderer);
337
+ }
338
+ createIcon() {
339
+ // We need to use createElementNS to embed an SVG element in HTML.
340
+ // See http://zhangwenli.com/blog/2017/07/26/createelementns/
341
+ const icon = document.createElementNS(svgNamespace, 'svg');
305
342
  icon.setAttribute('viewBox', '0 0 100 100');
343
+ const strokeFactory = this.tool.getStrokeFactory();
344
+ if (strokeFactory === makeFreehandLineBuilder) {
345
+ this.makePenIcon(icon);
346
+ }
347
+ else {
348
+ this.makeDrawnIcon(icon);
349
+ }
306
350
  return icon;
307
351
  }
308
352
  fillDropdown(dropdown) {
309
353
  const container = document.createElement('div');
310
- // Thickness: Value of the input is squared to allow for finer control/larger values.
311
354
  const thicknessRow = document.createElement('div');
355
+ const objectTypeRow = document.createElement('div');
356
+ // Thickness: Value of the input is squared to allow for finer control/larger values.
312
357
  const thicknessLabel = document.createElement('label');
313
358
  const thicknessInput = document.createElement('input');
359
+ const objectSelectLabel = document.createElement('label');
360
+ const objectTypeSelect = document.createElement('select');
361
+ // Give inputs IDs so we can label them with a <label for=...>Label text</label>
314
362
  thicknessInput.id = `${toolbarCSSPrefix}thicknessInput${PenWidget.idCounter++}`;
363
+ objectTypeSelect.id = `${toolbarCSSPrefix}builderSelect${PenWidget.idCounter++}`;
315
364
  thicknessLabel.innerText = this.localizationTable.thicknessLabel;
316
365
  thicknessLabel.setAttribute('for', thicknessInput.id);
366
+ objectSelectLabel.innerText = this.localizationTable.selectObjectType;
367
+ objectSelectLabel.setAttribute('for', objectTypeSelect.id);
317
368
  thicknessInput.type = 'range';
318
369
  thicknessInput.min = '1';
319
370
  thicknessInput.max = '20';
@@ -323,6 +374,16 @@ class PenWidget extends ToolbarWidget {
323
374
  };
324
375
  thicknessRow.appendChild(thicknessLabel);
325
376
  thicknessRow.appendChild(thicknessInput);
377
+ objectTypeSelect.oninput = () => {
378
+ const penTypeIdx = parseInt(objectTypeSelect.value);
379
+ if (penTypeIdx < 0 || penTypeIdx >= this.penTypes.length) {
380
+ console.error('Invalid pen type index', penTypeIdx);
381
+ return;
382
+ }
383
+ this.tool.setStrokeFactory(this.penTypes[penTypeIdx].factory);
384
+ };
385
+ objectTypeRow.appendChild(objectSelectLabel);
386
+ objectTypeRow.appendChild(objectTypeSelect);
326
387
  const colorRow = document.createElement('div');
327
388
  const colorLabel = document.createElement('label');
328
389
  const colorInput = document.createElement('input');
@@ -334,30 +395,80 @@ class PenWidget extends ToolbarWidget {
334
395
  colorInput.oninput = () => {
335
396
  this.tool.setColor(Color4.fromHex(colorInput.value));
336
397
  };
398
+ colorInput.addEventListener('open', () => {
399
+ this.editor.notifier.dispatch(EditorEventType.ColorPickerToggled, {
400
+ kind: EditorEventType.ColorPickerToggled,
401
+ open: true,
402
+ });
403
+ });
404
+ colorInput.addEventListener('close', () => {
405
+ this.editor.notifier.dispatch(EditorEventType.ColorPickerToggled, {
406
+ kind: EditorEventType.ColorPickerToggled,
407
+ open: false,
408
+ });
409
+ });
337
410
  colorRow.appendChild(colorLabel);
338
411
  colorRow.appendChild(colorInput);
339
412
  this.updateInputs = () => {
340
413
  colorInput.value = this.tool.getColor().toHexString();
341
414
  thicknessInput.value = Math.sqrt(this.tool.getThickness()).toString();
415
+ objectTypeSelect.replaceChildren();
416
+ for (let i = 0; i < this.penTypes.length; i++) {
417
+ const penType = this.penTypes[i];
418
+ const option = document.createElement('option');
419
+ option.value = i.toString();
420
+ option.innerText = penType.name;
421
+ objectTypeSelect.appendChild(option);
422
+ if (penType.factory === this.tool.getStrokeFactory()) {
423
+ objectTypeSelect.value = i.toString();
424
+ }
425
+ }
342
426
  };
343
427
  this.updateInputs();
344
- container.replaceChildren(colorRow, thicknessRow);
428
+ container.replaceChildren(colorRow, thicknessRow, objectTypeRow);
345
429
  dropdown.replaceChildren(container);
346
430
  return true;
347
431
  }
348
432
  }
349
433
  PenWidget.idCounter = 0;
350
434
  export default class HTMLToolbar {
351
- constructor(editor, parent, localizationTable = HTMLToolbar.defaultLocalization) {
435
+ constructor(editor, parent, localizationTable = defaultToolbarLocalization) {
352
436
  this.editor = editor;
353
437
  this.localizationTable = localizationTable;
354
438
  this.container = document.createElement('div');
355
439
  this.container.classList.add(`${toolbarCSSPrefix}root`);
356
440
  this.container.setAttribute('role', 'toolbar');
357
- this.addElements();
358
441
  parent.appendChild(this.container);
359
- // Initialize color choosers
360
442
  colorisInit();
443
+ this.setupColorPickers();
444
+ // Default pen types
445
+ this.penTypes = [
446
+ {
447
+ name: localizationTable.freehandPen,
448
+ factory: makeFreehandLineBuilder,
449
+ },
450
+ {
451
+ name: localizationTable.arrowPen,
452
+ factory: makeArrowBuilder,
453
+ },
454
+ {
455
+ name: localizationTable.linePen,
456
+ factory: makeLineBuilder,
457
+ },
458
+ {
459
+ name: localizationTable.filledRectanglePen,
460
+ factory: makeFilledRectangleBuilder,
461
+ },
462
+ {
463
+ name: localizationTable.outlinedRectanglePen,
464
+ factory: makeOutlinedRectangleBuilder,
465
+ },
466
+ ];
467
+ }
468
+ setupColorPickers() {
469
+ const closePickerOverlay = document.createElement('div');
470
+ closePickerOverlay.className = `${toolbarCSSPrefix}closeColorPickerOverlay`;
471
+ this.editor.createHTMLOverlay(closePickerOverlay);
361
472
  coloris({
362
473
  el: '.coloris_input',
363
474
  format: 'hex',
@@ -373,6 +484,14 @@ export default class HTMLToolbar {
373
484
  Color4.white.toHexString(),
374
485
  ],
375
486
  });
487
+ this.editor.notifier.on(EditorEventType.ColorPickerToggled, event => {
488
+ if (event.kind !== EditorEventType.ColorPickerToggled) {
489
+ return;
490
+ }
491
+ // Show/hide the overlay. Making the overlay visible gives users a surface to click
492
+ // on that shows/hides the color picker.
493
+ closePickerOverlay.style.display = event.open ? 'block' : 'none';
494
+ });
376
495
  }
377
496
  addActionButton(text, command, parent) {
378
497
  const button = document.createElement('button');
@@ -402,13 +521,13 @@ export default class HTMLToolbar {
402
521
  redoButton.disabled = event.redoStackSize === 0;
403
522
  });
404
523
  }
405
- addElements() {
524
+ addDefaultToolWidgets() {
406
525
  const toolController = this.editor.toolController;
407
526
  for (const tool of toolController.getMatchingTools(ToolType.Pen)) {
408
527
  if (!(tool instanceof Pen)) {
409
528
  throw new Error('All `Pen` tools must have kind === ToolType.Pen');
410
529
  }
411
- const widget = new PenWidget(this.editor, tool, this.localizationTable);
530
+ const widget = new PenWidget(this.editor, tool, this.localizationTable, this.penTypes);
412
531
  widget.addTo(this.container);
413
532
  }
414
533
  for (const tool of toolController.getMatchingTools(ToolType.Eraser)) {
@@ -426,19 +545,9 @@ export default class HTMLToolbar {
426
545
  for (const tool of toolController.getMatchingTools(ToolType.TouchPanZoom)) {
427
546
  (new TouchDrawingWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
428
547
  }
548
+ this.setupColorPickers();
549
+ }
550
+ addDefaultActionButtons() {
429
551
  this.addUndoRedoButtons();
430
552
  }
431
553
  }
432
- HTMLToolbar.defaultLocalization = {
433
- pen: 'Pen',
434
- eraser: 'Eraser',
435
- select: 'Select',
436
- touchDrawing: 'Touch Drawing',
437
- thicknessLabel: 'Thickness: ',
438
- colorLabel: 'Color: ',
439
- resizeImageToSelection: 'Resize image to selection',
440
- undo: 'Undo',
441
- redo: 'Redo',
442
- dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
443
- dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
444
- };
@@ -0,0 +1,20 @@
1
+ export interface ToolbarLocalization {
2
+ outlinedRectanglePen: string;
3
+ filledRectanglePen: string;
4
+ linePen: string;
5
+ arrowPen: string;
6
+ freehandPen: string;
7
+ selectObjectType: string;
8
+ colorLabel: string;
9
+ pen: string;
10
+ eraser: string;
11
+ select: string;
12
+ touchDrawing: string;
13
+ thicknessLabel: string;
14
+ resizeImageToSelection: string;
15
+ undo: string;
16
+ redo: string;
17
+ dropdownShown: (toolName: string) => string;
18
+ dropdownHidden: (toolName: string) => string;
19
+ }
20
+ export declare const defaultToolbarLocalization: ToolbarLocalization;
@@ -0,0 +1,19 @@
1
+ export const defaultToolbarLocalization = {
2
+ pen: 'Pen',
3
+ eraser: 'Eraser',
4
+ select: 'Select',
5
+ touchDrawing: 'Touch Drawing',
6
+ thicknessLabel: 'Thickness: ',
7
+ colorLabel: 'Color: ',
8
+ resizeImageToSelection: 'Resize image to selection',
9
+ undo: 'Undo',
10
+ redo: 'Redo',
11
+ selectObjectType: 'Object type: ',
12
+ freehandPen: 'Freehand',
13
+ arrowPen: 'Arrow',
14
+ linePen: 'Line',
15
+ outlinedRectanglePen: 'Outlined rectangle',
16
+ filledRectanglePen: 'Filled rectangle',
17
+ dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
18
+ dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
19
+ };
@@ -2,16 +2,3 @@ export declare enum ToolbarButtonType {
2
2
  ToggleButton = 0,
3
3
  ActionButton = 1
4
4
  }
5
- export interface ToolbarLocalization {
6
- colorLabel: string;
7
- pen: string;
8
- eraser: string;
9
- select: string;
10
- touchDrawing: string;
11
- thicknessLabel: string;
12
- resizeImageToSelection: string;
13
- undo: string;
14
- redo: string;
15
- dropdownShown: (toolName: string) => string;
16
- dropdownHidden: (toolName: string) => string;
17
- }
@@ -3,15 +3,22 @@ import Editor from '../Editor';
3
3
  import { PointerEvt } from '../types';
4
4
  import BaseTool from './BaseTool';
5
5
  import { ToolType } from './ToolController';
6
+ import { ComponentBuilderFactory } from '../components/builders/types';
7
+ interface PenStyle {
8
+ color: Color4;
9
+ thickness: number;
10
+ }
6
11
  export default class Pen extends BaseTool {
7
12
  private editor;
8
- private color;
9
- private thickness;
13
+ private style;
10
14
  private builder;
15
+ private builderFactory;
16
+ private lastPoint;
11
17
  readonly kind: ToolType;
12
- constructor(editor: Editor, description: string, color?: Color4, thickness?: number);
18
+ constructor(editor: Editor, description: string, style: PenStyle);
13
19
  private getPressureMultiplier;
14
20
  private getStrokePoint;
21
+ private previewStroke;
15
22
  private addPointToStroke;
16
23
  onPointerDown({ current, allPointers }: PointerEvt): boolean;
17
24
  onPointerMove({ current }: PointerEvt): void;
@@ -20,6 +27,9 @@ export default class Pen extends BaseTool {
20
27
  private noteUpdated;
21
28
  setColor(color: Color4): void;
22
29
  setThickness(thickness: number): void;
30
+ setStrokeFactory(factory: ComponentBuilderFactory): void;
23
31
  getThickness(): number;
24
32
  getColor(): Color4;
33
+ getStrokeFactory(): ComponentBuilderFactory;
25
34
  }
35
+ export {};
@@ -1,22 +1,21 @@
1
- import Color4 from '../Color4';
2
1
  import EditorImage from '../EditorImage';
3
- import { Vec2 } from '../geometry/Vec2';
4
2
  import { PointerDevice } from '../Pointer';
5
- import StrokeBuilder from '../StrokeBuilder';
3
+ import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder';
6
4
  import { EditorEventType } from '../types';
7
5
  import BaseTool from './BaseTool';
8
6
  import { ToolType } from './ToolController';
9
7
  export default class Pen extends BaseTool {
10
- constructor(editor, description, color = Color4.purple, thickness = 16.0) {
8
+ constructor(editor, description, style) {
11
9
  super(editor.notifier, description);
12
10
  this.editor = editor;
13
- this.color = color;
14
- this.thickness = thickness;
11
+ this.style = style;
15
12
  this.builder = null;
13
+ this.builderFactory = makeFreehandLineBuilder;
14
+ this.lastPoint = null;
16
15
  this.kind = ToolType.Pen;
17
16
  }
18
17
  getPressureMultiplier() {
19
- return 1 / this.editor.viewport.getScaleFactor() * this.thickness;
18
+ return 1 / this.editor.viewport.getScaleFactor() * this.style.thickness;
20
19
  }
21
20
  getStrokePoint(pointer) {
22
21
  var _a;
@@ -25,45 +24,48 @@ export default class Pen extends BaseTool {
25
24
  return {
26
25
  pos: pointer.canvasPos,
27
26
  width: pressure * this.getPressureMultiplier(),
28
- color: this.color,
27
+ color: this.style.color,
29
28
  time: pointer.timeStamp,
30
29
  };
31
30
  }
32
- addPointToStroke(pointer) {
31
+ previewStroke() {
32
+ var _a;
33
+ this.editor.clearWetInk();
34
+ (_a = this.builder) === null || _a === void 0 ? void 0 : _a.preview(this.editor.display.getWetInkRenderer());
35
+ }
36
+ addPointToStroke(point) {
33
37
  if (!this.builder) {
34
38
  throw new Error('No stroke is currently being generated.');
35
39
  }
36
- this.builder.addPoint(this.getStrokePoint(pointer));
37
- this.editor.clearWetInk();
38
- this.editor.drawWetInk(...this.builder.preview());
40
+ this.builder.addPoint(point);
41
+ this.lastPoint = point;
42
+ this.previewStroke();
39
43
  }
40
44
  onPointerDown({ current, allPointers }) {
41
45
  if (current.device === PointerDevice.Eraser) {
42
46
  return false;
43
47
  }
44
48
  if (allPointers.length === 1 || current.device === PointerDevice.Pen) {
45
- // Don't smooth if input is more than ± 7 pixels from the true curve, do smooth if
46
- // less than ± 2 px from the curve.
47
- const canvasTransform = this.editor.viewport.screenToCanvasTransform;
48
- const maxSmoothingDist = canvasTransform.transformVec3(Vec2.unitX).magnitude() * 7;
49
- const minSmoothingDist = canvasTransform.transformVec3(Vec2.unitX).magnitude() * 2;
50
- this.builder = new StrokeBuilder(this.getStrokePoint(current), minSmoothingDist, maxSmoothingDist);
49
+ this.builder = this.builderFactory(this.getStrokePoint(current), this.editor.viewport);
51
50
  return true;
52
51
  }
53
52
  return false;
54
53
  }
55
54
  onPointerMove({ current }) {
56
- this.addPointToStroke(current);
55
+ this.addPointToStroke(this.getStrokePoint(current));
57
56
  }
58
57
  onPointerUp({ current }) {
58
+ var _a, _b;
59
59
  if (!this.builder) {
60
60
  return;
61
61
  }
62
- this.addPointToStroke(current);
62
+ // onPointerUp events can have zero pressure. Use the last pressure instead.
63
+ const currentPoint = this.getStrokePoint(current);
64
+ const strokePoint = Object.assign(Object.assign({}, currentPoint), { width: (_b = (_a = this.lastPoint) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : currentPoint.width });
65
+ this.addPointToStroke(strokePoint);
63
66
  if (this.builder && current.isPrimary) {
64
67
  const stroke = this.builder.build();
65
- this.editor.clearWetInk();
66
- this.editor.drawWetInk(...this.builder.preview());
68
+ this.previewStroke();
67
69
  const canFlatten = true;
68
70
  const action = new EditorImage.AddElementCommand(stroke, canFlatten);
69
71
  this.editor.dispatch(action);
@@ -81,17 +83,24 @@ export default class Pen extends BaseTool {
81
83
  });
82
84
  }
83
85
  setColor(color) {
84
- if (color.toHexString() !== this.color.toHexString()) {
85
- this.color = color;
86
+ if (color.toHexString() !== this.style.color.toHexString()) {
87
+ this.style = Object.assign(Object.assign({}, this.style), { color });
86
88
  this.noteUpdated();
87
89
  }
88
90
  }
89
91
  setThickness(thickness) {
90
- if (thickness !== this.thickness) {
91
- this.thickness = thickness;
92
+ if (thickness !== this.style.thickness) {
93
+ this.style = Object.assign(Object.assign({}, this.style), { thickness });
94
+ this.noteUpdated();
95
+ }
96
+ }
97
+ setStrokeFactory(factory) {
98
+ if (factory !== this.builderFactory) {
99
+ this.builderFactory = factory;
92
100
  this.noteUpdated();
93
101
  }
94
102
  }
95
- getThickness() { return this.thickness; }
96
- getColor() { return this.color; }
103
+ getThickness() { return this.style.thickness; }
104
+ getColor() { return this.style.color; }
105
+ getStrokeFactory() { return this.builderFactory; }
97
106
  }
@@ -21,7 +21,7 @@ const styles = `
21
21
  }
22
22
 
23
23
  .handleOverlay > .selectionBox {
24
- position: fixed;
24
+ position: absolute;
25
25
  z-index: 0;
26
26
  transform-origin: center;
27
27
  }
@@ -17,15 +17,15 @@ export default class ToolController {
17
17
  constructor(editor, localization) {
18
18
  const primaryToolEnabledGroup = new ToolEnabledGroup();
19
19
  const touchPanZoom = new PanZoom(editor, PanZoomMode.OneFingerGestures, localization.touchPanTool);
20
- const primaryPenTool = new Pen(editor, localization.penTool(1));
20
+ const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
21
21
  const primaryTools = [
22
22
  new SelectionTool(editor, localization.selectionTool),
23
23
  new Eraser(editor, localization.eraserTool),
24
24
  // Three pens
25
25
  primaryPenTool,
26
- new Pen(editor, localization.penTool(2), Color4.clay, 8),
26
+ new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 8 }),
27
27
  // Highlighter-like pen with width=64
28
- new Pen(editor, localization.penTool(3), Color4.ofRGBA(1, 1, 0, 0.5), 64),
28
+ new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
29
29
  ];
30
30
  this.tools = [
31
31
  touchPanZoom,
@@ -6,6 +6,7 @@ import BaseTool from './tools/BaseTool';
6
6
  import AbstractComponent from './components/AbstractComponent';
7
7
  import Rect2 from './geometry/Rect2';
8
8
  import Pointer from './Pointer';
9
+ import Color4 from './Color4';
9
10
  export interface PointerEvtListener {
10
11
  onPointerDown(event: PointerEvt): boolean;
11
12
  onPointerMove(event: PointerEvt): void;
@@ -55,7 +56,8 @@ export declare enum EditorEventType {
55
56
  UndoRedoStackUpdated = 3,
56
57
  ObjectAdded = 4,
57
58
  ViewportChanged = 5,
58
- DisplayResized = 6
59
+ DisplayResized = 6,
60
+ ColorPickerToggled = 7
59
61
  }
60
62
  declare type EditorToolEventType = EditorEventType.ToolEnabled | EditorEventType.ToolDisabled | EditorEventType.ToolUpdated;
61
63
  export interface EditorToolEvent {
@@ -79,10 +81,20 @@ export interface EditorUndoStackUpdated {
79
81
  readonly undoStackSize: number;
80
82
  readonly redoStackSize: number;
81
83
  }
82
- export declare type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated;
84
+ export interface ColorPickerToggled {
85
+ readonly kind: EditorEventType.ColorPickerToggled;
86
+ readonly open: boolean;
87
+ }
88
+ export declare type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | ColorPickerToggled;
83
89
  export declare type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null;
84
90
  export declare type ComponentAddedListener = (component: AbstractComponent) => void;
85
91
  export interface ImageLoader {
86
92
  start(onAddComponent: ComponentAddedListener, onProgressListener: OnProgressListener): Promise<Rect2>;
87
93
  }
94
+ export interface StrokeDataPoint {
95
+ pos: Point2;
96
+ width: number;
97
+ time: number;
98
+ color: Color4;
99
+ }
88
100
  export {};
package/dist/src/types.js CHANGED
@@ -17,4 +17,5 @@ export var EditorEventType;
17
17
  EditorEventType[EditorEventType["ObjectAdded"] = 4] = "ObjectAdded";
18
18
  EditorEventType[EditorEventType["ViewportChanged"] = 5] = "ViewportChanged";
19
19
  EditorEventType[EditorEventType["DisplayResized"] = 6] = "DisplayResized";
20
+ EditorEventType[EditorEventType["ColorPickerToggled"] = 7] = "ColorPickerToggled";
20
21
  })(EditorEventType || (EditorEventType = {}));
@@ -0,0 +1,35 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width,initial-scale=1.0"/>
5
+ <meta charset="utf-8"/>
6
+ <title>Editor from a bundle</title>
7
+ <style>
8
+ body .imageEditorContainer {
9
+ height: 800px;
10
+
11
+ --primary-background-color: green;
12
+ --primary-background-color-transparent: rgba(255, 240, 200, 0.5);
13
+ --secondary-background-color: yellow;
14
+ --primary-foreground-color: black;
15
+ --secondary-foreground-color: black;
16
+ }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <p>
21
+ This file tests the bundled version of <code>js-draw</code>.
22
+ Be sure to run <code>yarn build</code> before opening this!
23
+ </p>
24
+ <script src="../dist/bundle.js"></script>
25
+ <script>
26
+ const editor1 = new jsdraw.Editor(document.body, {
27
+ wheelEventsEnabled: false,
28
+ });
29
+ editor1.addToolbar();
30
+
31
+ const editor2 = new jsdraw.Editor(document.body);
32
+ editor2.addToolbar();
33
+ </script>
34
+ </body>
35
+ </html>