js-draw 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/src/Editor.d.ts +1 -1
  3. package/dist/src/Editor.js +7 -2
  4. package/dist/src/EditorImage.d.ts +2 -0
  5. package/dist/src/EditorImage.js +29 -5
  6. package/dist/src/Pointer.js +1 -1
  7. package/dist/src/Viewport.d.ts +1 -1
  8. package/dist/src/components/AbstractComponent.d.ts +1 -1
  9. package/dist/src/components/Stroke.d.ts +1 -1
  10. package/dist/src/components/Stroke.js +1 -1
  11. package/dist/src/components/UnknownSVGObject.d.ts +1 -1
  12. package/dist/src/components/builders/ArrowBuilder.d.ts +17 -0
  13. package/dist/src/components/builders/ArrowBuilder.js +83 -0
  14. package/dist/src/{StrokeBuilder.d.ts → components/builders/FreehandLineBuilder.d.ts} +9 -13
  15. package/dist/src/{StrokeBuilder.js → components/builders/FreehandLineBuilder.js} +27 -9
  16. package/dist/src/components/builders/LineBuilder.d.ts +16 -0
  17. package/dist/src/components/builders/LineBuilder.js +57 -0
  18. package/dist/src/components/builders/RectangleBuilder.d.ts +18 -0
  19. package/dist/src/components/builders/RectangleBuilder.js +41 -0
  20. package/dist/src/components/builders/types.d.ts +12 -0
  21. package/dist/src/components/builders/types.js +1 -0
  22. package/dist/src/geometry/Path.d.ts +1 -0
  23. package/dist/src/geometry/Path.js +32 -0
  24. package/dist/src/geometry/Vec3.d.ts +2 -0
  25. package/dist/src/geometry/Vec3.js +13 -0
  26. package/dist/src/rendering/AbstractRenderer.js +3 -25
  27. package/dist/src/toolbar/HTMLToolbar.d.ts +4 -1
  28. package/dist/src/toolbar/HTMLToolbar.js +143 -16
  29. package/dist/src/toolbar/types.d.ts +6 -0
  30. package/dist/src/tools/Pen.d.ts +13 -3
  31. package/dist/src/tools/Pen.js +37 -28
  32. package/dist/src/tools/ToolController.js +3 -3
  33. package/dist/src/types.d.ts +14 -2
  34. package/dist/src/types.js +1 -0
  35. package/package.json +4 -3
  36. package/src/Editor.ts +9 -2
  37. package/src/EditorImage.ts +31 -3
  38. package/src/Pointer.ts +1 -1
  39. package/src/Viewport.ts +1 -1
  40. package/src/components/AbstractComponent.ts +1 -1
  41. package/src/components/Stroke.ts +2 -2
  42. package/src/components/UnknownSVGObject.ts +1 -1
  43. package/src/components/builders/ArrowBuilder.ts +104 -0
  44. package/src/{StrokeBuilder.ts → components/builders/FreehandLineBuilder.ts} +36 -18
  45. package/src/components/builders/LineBuilder.ts +75 -0
  46. package/src/components/builders/RectangleBuilder.ts +59 -0
  47. package/src/components/builders/types.ts +15 -0
  48. package/src/geometry/Path.ts +43 -0
  49. package/src/geometry/Vec2.test.ts +1 -0
  50. package/src/geometry/Vec3.test.ts +14 -0
  51. package/src/geometry/Vec3.ts +16 -0
  52. package/src/rendering/AbstractRenderer.ts +3 -32
  53. package/src/{editorStyles.js → styles.js} +0 -0
  54. package/src/toolbar/HTMLToolbar.ts +172 -22
  55. package/src/toolbar/toolbar.css +12 -0
  56. package/src/toolbar/types.ts +6 -0
  57. package/src/tools/Pen.ts +56 -34
  58. package/src/tools/ToolController.ts +3 -3
  59. package/src/types.ts +16 -1
@@ -1,6 +1,4 @@
1
- import { PathCommandType } from '../geometry/Path';
2
- import Rect2 from '../geometry/Rect2';
3
- import { Vec2 } from '../geometry/Vec2';
1
+ import Path, { PathCommandType } from '../geometry/Path';
4
2
  const stylesEqual = (a, b) => {
5
3
  var _a, _b, _c, _d, _e;
6
4
  return a === b || (a.fill.eq(b.fill)
@@ -65,28 +63,8 @@ export default class AbstractRenderer {
65
63
  }
66
64
  // Draw a rectangle. Boundary lines have width [lineWidth] and are filled with [lineFill]
67
65
  drawRect(rect, lineWidth, lineFill) {
68
- const commands = [];
69
- // Vector from the top left corner or bottom right corner to the edge of the
70
- // stroked region.
71
- const cornerToEdge = Vec2.of(lineWidth, lineWidth).times(0.5);
72
- const innerRect = Rect2.fromCorners(rect.topLeft.plus(cornerToEdge), rect.bottomRight.minus(cornerToEdge));
73
- const outerRect = Rect2.fromCorners(rect.topLeft.minus(cornerToEdge), rect.bottomRight.plus(cornerToEdge));
74
- const corners = [
75
- innerRect.corners[3],
76
- ...innerRect.corners,
77
- ...outerRect.corners.reverse(),
78
- ];
79
- for (const corner of corners) {
80
- commands.push({
81
- kind: PathCommandType.LineTo,
82
- point: corner,
83
- });
84
- }
85
- this.drawPath({
86
- startPoint: outerRect.corners[3],
87
- commands,
88
- style: lineFill,
89
- });
66
+ const path = Path.fromRect(rect, lineWidth);
67
+ this.drawPath(path.toRenderable(lineFill));
90
68
  }
91
69
  // Note the start/end of an object with the given bounding box.
92
70
  startObject(_boundingBox) {
@@ -4,9 +4,12 @@ export default class HTMLToolbar {
4
4
  private editor;
5
5
  private localizationTable;
6
6
  private container;
7
+ private penTypes;
7
8
  static defaultLocalization: ToolbarLocalization;
8
9
  constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
10
+ setupColorPickers(): void;
9
11
  addActionButton(text: string, command: () => void, parent?: Element): HTMLButtonElement;
10
12
  private addUndoRedoButtons;
11
- private addElements;
13
+ addDefaultToolWidgets(): void;
14
+ addDefaultActionButtons(): void;
12
15
  }
@@ -5,6 +5,14 @@ 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';
8
16
  const primaryForegroundFill = `
9
17
  style='fill: var(--primary-foreground-color);'
10
18
  `;
@@ -12,6 +20,7 @@ const primaryForegroundStrokeFill = `
12
20
  style='fill: var(--primary-foreground-color); stroke: var(--primary-foreground-color);'
13
21
  `;
14
22
  const toolbarCSSPrefix = 'toolbar-';
23
+ const svgNamespace = 'http://www.w3.org/2000/svg';
15
24
  class ToolbarWidget {
16
25
  constructor(editor, targetTool, localizationTable) {
17
26
  this.editor = editor;
@@ -121,7 +130,7 @@ class ToolbarWidget {
121
130
  return !this.dropdownContainer.classList.contains('hidden');
122
131
  }
123
132
  createDropdownIcon() {
124
- const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
133
+ const icon = document.createElementNS(svgNamespace, 'svg');
125
134
  icon.innerHTML = `
126
135
  <g>
127
136
  <path
@@ -140,7 +149,7 @@ class EraserWidget extends ToolbarWidget {
140
149
  return this.localizationTable.eraser;
141
150
  }
142
151
  createIcon() {
143
- const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
152
+ const icon = document.createElementNS(svgNamespace, 'svg');
144
153
  // Draw an eraser-like shape
145
154
  icon.innerHTML = `
146
155
  <g>
@@ -168,7 +177,7 @@ class SelectionWidget extends ToolbarWidget {
168
177
  return this.localizationTable.select;
169
178
  }
170
179
  createIcon() {
171
- const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
180
+ const icon = document.createElementNS(svgNamespace, 'svg');
172
181
  // Draw a cursor-like shape
173
182
  icon.innerHTML = `
174
183
  <g>
@@ -209,7 +218,7 @@ class TouchDrawingWidget extends ToolbarWidget {
209
218
  return this.localizationTable.touchDrawing;
210
219
  }
211
220
  createIcon() {
212
- const icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
221
+ const icon = document.createElementNS(svgNamespace, 'svg');
213
222
  // Draw a cursor-like shape
214
223
  icon.innerHTML = `
215
224
  <g>
@@ -241,9 +250,10 @@ class TouchDrawingWidget extends ToolbarWidget {
241
250
  }
242
251
  }
243
252
  class PenWidget extends ToolbarWidget {
244
- constructor(editor, tool, localization) {
253
+ constructor(editor, tool, localization, penTypes) {
245
254
  super(editor, tool, localization);
246
255
  this.tool = tool;
256
+ this.penTypes = penTypes;
247
257
  this.updateInputs = () => { };
248
258
  this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
249
259
  if (toolEvt.kind !== EditorEventType.ToolUpdated) {
@@ -259,17 +269,14 @@ class PenWidget extends ToolbarWidget {
259
269
  getTitle() {
260
270
  return this.targetTool.description;
261
271
  }
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');
272
+ makePenIcon(elem) {
266
273
  // Use a square-root scale to prevent the pen's tip from overflowing.
267
274
  const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 2);
268
275
  const color = this.tool.getColor();
269
276
  // Draw a pen-like shape
270
277
  const primaryStrokeTipPath = `M14,63 L${50 - scale},95 L${50 + scale},90 L88,60 Z`;
271
278
  const backgroundStrokeTipPath = `M14,63 L${50 - scale},85 L${50 + scale},83 L88,60 Z`;
272
- icon.innerHTML = `
279
+ elem.innerHTML = `
273
280
  <defs>
274
281
  <pattern
275
282
  id='checkerboard'
@@ -302,18 +309,61 @@ class PenWidget extends ToolbarWidget {
302
309
  />
303
310
  </g>
304
311
  `;
312
+ }
313
+ // Draws an icon with the pen.
314
+ makeDrawnIcon(icon) {
315
+ const strokeFactory = this.tool.getStrokeFactory();
316
+ const toolThickness = this.tool.getThickness();
317
+ const nowTime = (new Date()).getTime();
318
+ const startPoint = {
319
+ pos: Vec2.of(10, 10),
320
+ width: toolThickness / 5,
321
+ color: this.tool.getColor(),
322
+ time: nowTime - 100,
323
+ };
324
+ const endPoint = {
325
+ pos: Vec2.of(90, 90),
326
+ width: toolThickness / 5,
327
+ color: this.tool.getColor(),
328
+ time: nowTime,
329
+ };
330
+ const builder = strokeFactory(startPoint, this.editor.viewport);
331
+ builder.addPoint(endPoint);
332
+ const viewport = new Viewport(new EventDispatcher());
333
+ viewport.updateScreenSize(Vec2.of(100, 100));
334
+ const renderer = new SVGRenderer(icon, viewport);
335
+ builder.preview(renderer);
336
+ }
337
+ createIcon() {
338
+ // We need to use createElementNS to embed an SVG element in HTML.
339
+ // See http://zhangwenli.com/blog/2017/07/26/createelementns/
340
+ const icon = document.createElementNS(svgNamespace, 'svg');
305
341
  icon.setAttribute('viewBox', '0 0 100 100');
342
+ const strokeFactory = this.tool.getStrokeFactory();
343
+ if (strokeFactory === makeFreehandLineBuilder) {
344
+ this.makePenIcon(icon);
345
+ }
346
+ else {
347
+ this.makeDrawnIcon(icon);
348
+ }
306
349
  return icon;
307
350
  }
308
351
  fillDropdown(dropdown) {
309
352
  const container = document.createElement('div');
310
- // Thickness: Value of the input is squared to allow for finer control/larger values.
311
353
  const thicknessRow = document.createElement('div');
354
+ const objectTypeRow = document.createElement('div');
355
+ // Thickness: Value of the input is squared to allow for finer control/larger values.
312
356
  const thicknessLabel = document.createElement('label');
313
357
  const thicknessInput = document.createElement('input');
358
+ const objectSelectLabel = document.createElement('label');
359
+ const objectTypeSelect = document.createElement('select');
360
+ // Give inputs IDs so we can label them with a <label for=...>Label text</label>
314
361
  thicknessInput.id = `${toolbarCSSPrefix}thicknessInput${PenWidget.idCounter++}`;
362
+ objectTypeSelect.id = `${toolbarCSSPrefix}builderSelect${PenWidget.idCounter++}`;
315
363
  thicknessLabel.innerText = this.localizationTable.thicknessLabel;
316
364
  thicknessLabel.setAttribute('for', thicknessInput.id);
365
+ objectSelectLabel.innerText = this.localizationTable.selectObjectType;
366
+ objectSelectLabel.setAttribute('for', objectTypeSelect.id);
317
367
  thicknessInput.type = 'range';
318
368
  thicknessInput.min = '1';
319
369
  thicknessInput.max = '20';
@@ -323,6 +373,16 @@ class PenWidget extends ToolbarWidget {
323
373
  };
324
374
  thicknessRow.appendChild(thicknessLabel);
325
375
  thicknessRow.appendChild(thicknessInput);
376
+ objectTypeSelect.oninput = () => {
377
+ const penTypeIdx = parseInt(objectTypeSelect.value);
378
+ if (penTypeIdx < 0 || penTypeIdx >= this.penTypes.length) {
379
+ console.error('Invalid pen type index', penTypeIdx);
380
+ return;
381
+ }
382
+ this.tool.setStrokeFactory(this.penTypes[penTypeIdx].factory);
383
+ };
384
+ objectTypeRow.appendChild(objectSelectLabel);
385
+ objectTypeRow.appendChild(objectTypeSelect);
326
386
  const colorRow = document.createElement('div');
327
387
  const colorLabel = document.createElement('label');
328
388
  const colorInput = document.createElement('input');
@@ -334,14 +394,37 @@ class PenWidget extends ToolbarWidget {
334
394
  colorInput.oninput = () => {
335
395
  this.tool.setColor(Color4.fromHex(colorInput.value));
336
396
  };
397
+ colorInput.addEventListener('open', () => {
398
+ this.editor.notifier.dispatch(EditorEventType.ColorPickerToggled, {
399
+ kind: EditorEventType.ColorPickerToggled,
400
+ open: true,
401
+ });
402
+ });
403
+ colorInput.addEventListener('close', () => {
404
+ this.editor.notifier.dispatch(EditorEventType.ColorPickerToggled, {
405
+ kind: EditorEventType.ColorPickerToggled,
406
+ open: false,
407
+ });
408
+ });
337
409
  colorRow.appendChild(colorLabel);
338
410
  colorRow.appendChild(colorInput);
339
411
  this.updateInputs = () => {
340
412
  colorInput.value = this.tool.getColor().toHexString();
341
413
  thicknessInput.value = Math.sqrt(this.tool.getThickness()).toString();
414
+ objectTypeSelect.replaceChildren();
415
+ for (let i = 0; i < this.penTypes.length; i++) {
416
+ const penType = this.penTypes[i];
417
+ const option = document.createElement('option');
418
+ option.value = i.toString();
419
+ option.innerText = penType.name;
420
+ objectTypeSelect.appendChild(option);
421
+ if (penType.factory === this.tool.getStrokeFactory()) {
422
+ objectTypeSelect.value = i.toString();
423
+ }
424
+ }
342
425
  };
343
426
  this.updateInputs();
344
- container.replaceChildren(colorRow, thicknessRow);
427
+ container.replaceChildren(colorRow, thicknessRow, objectTypeRow);
345
428
  dropdown.replaceChildren(container);
346
429
  return true;
347
430
  }
@@ -354,10 +437,37 @@ export default class HTMLToolbar {
354
437
  this.container = document.createElement('div');
355
438
  this.container.classList.add(`${toolbarCSSPrefix}root`);
356
439
  this.container.setAttribute('role', 'toolbar');
357
- this.addElements();
358
440
  parent.appendChild(this.container);
359
- // Initialize color choosers
360
441
  colorisInit();
442
+ this.setupColorPickers();
443
+ // Default pen types
444
+ this.penTypes = [
445
+ {
446
+ name: localizationTable.freehandPen,
447
+ factory: makeFreehandLineBuilder,
448
+ },
449
+ {
450
+ name: localizationTable.arrowPen,
451
+ factory: makeArrowBuilder,
452
+ },
453
+ {
454
+ name: localizationTable.linePen,
455
+ factory: makeLineBuilder,
456
+ },
457
+ {
458
+ name: localizationTable.filledRectanglePen,
459
+ factory: makeFilledRectangleBuilder,
460
+ },
461
+ {
462
+ name: localizationTable.outlinedRectanglePen,
463
+ factory: makeOutlinedRectangleBuilder,
464
+ },
465
+ ];
466
+ }
467
+ setupColorPickers() {
468
+ const closePickerOverlay = document.createElement('div');
469
+ closePickerOverlay.className = `${toolbarCSSPrefix}closeColorPickerOverlay`;
470
+ this.editor.createHTMLOverlay(closePickerOverlay);
361
471
  coloris({
362
472
  el: '.coloris_input',
363
473
  format: 'hex',
@@ -373,6 +483,14 @@ export default class HTMLToolbar {
373
483
  Color4.white.toHexString(),
374
484
  ],
375
485
  });
486
+ this.editor.notifier.on(EditorEventType.ColorPickerToggled, event => {
487
+ if (event.kind !== EditorEventType.ColorPickerToggled) {
488
+ return;
489
+ }
490
+ // Show/hide the overlay. Making the overlay visible gives users a surface to click
491
+ // on that shows/hides the color picker.
492
+ closePickerOverlay.style.display = event.open ? 'block' : 'none';
493
+ });
376
494
  }
377
495
  addActionButton(text, command, parent) {
378
496
  const button = document.createElement('button');
@@ -402,13 +520,13 @@ export default class HTMLToolbar {
402
520
  redoButton.disabled = event.redoStackSize === 0;
403
521
  });
404
522
  }
405
- addElements() {
523
+ addDefaultToolWidgets() {
406
524
  const toolController = this.editor.toolController;
407
525
  for (const tool of toolController.getMatchingTools(ToolType.Pen)) {
408
526
  if (!(tool instanceof Pen)) {
409
527
  throw new Error('All `Pen` tools must have kind === ToolType.Pen');
410
528
  }
411
- const widget = new PenWidget(this.editor, tool, this.localizationTable);
529
+ const widget = new PenWidget(this.editor, tool, this.localizationTable, this.penTypes);
412
530
  widget.addTo(this.container);
413
531
  }
414
532
  for (const tool of toolController.getMatchingTools(ToolType.Eraser)) {
@@ -426,6 +544,9 @@ export default class HTMLToolbar {
426
544
  for (const tool of toolController.getMatchingTools(ToolType.TouchPanZoom)) {
427
545
  (new TouchDrawingWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
428
546
  }
547
+ this.setupColorPickers();
548
+ }
549
+ addDefaultActionButtons() {
429
550
  this.addUndoRedoButtons();
430
551
  }
431
552
  }
@@ -439,6 +560,12 @@ HTMLToolbar.defaultLocalization = {
439
560
  resizeImageToSelection: 'Resize image to selection',
440
561
  undo: 'Undo',
441
562
  redo: 'Redo',
563
+ selectObjectType: 'Object type: ',
564
+ freehandPen: 'Freehand',
565
+ arrowPen: 'Arrow',
566
+ linePen: 'Line',
567
+ outlinedRectanglePen: 'Outlined rectangle',
568
+ filledRectanglePen: 'Filled rectangle',
442
569
  dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
443
570
  dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
444
571
  };
@@ -3,6 +3,12 @@ export declare enum ToolbarButtonType {
3
3
  ActionButton = 1
4
4
  }
5
5
  export interface ToolbarLocalization {
6
+ outlinedRectanglePen: string;
7
+ filledRectanglePen: string;
8
+ linePen: string;
9
+ arrowPen: string;
10
+ freehandPen: string;
11
+ selectObjectType: string;
6
12
  colorLabel: string;
7
13
  pen: string;
8
14
  eraser: string;
@@ -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
  }
@@ -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 = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "main": "dist/src/Editor.js",
6
6
  "types": "dist/src/Editor.d.ts",
@@ -10,7 +10,7 @@
10
10
  "default": "./dist/src/Editor.js"
11
11
  },
12
12
  "./styles": {
13
- "default": "./src/editorStyles.js"
13
+ "default": "./src/styles.js"
14
14
  },
15
15
  "./Editor": {
16
16
  "types": "./dist/src/Editor.d.ts",
@@ -74,12 +74,13 @@
74
74
  },
75
75
  "homepage": "https://github.com/personalizedrefrigerator/js-draw#readme",
76
76
  "directories": {
77
- "doc": "doc"
77
+ "doc": "docs"
78
78
  },
79
79
  "keywords": [
80
80
  "ink",
81
81
  "drawing",
82
82
  "pen",
83
+ "freehand",
83
84
  "svg"
84
85
  ]
85
86
  }
package/src/Editor.ts CHANGED
@@ -110,8 +110,15 @@ export class Editor {
110
110
  this.accessibilityAnnounceArea.innerText = message;
111
111
  }
112
112
 
113
- public addToolbar(): HTMLToolbar {
114
- return new HTMLToolbar(this, this.container, this.localization);
113
+ public addToolbar(defaultLayout: boolean = true): HTMLToolbar {
114
+ const toolbar = new HTMLToolbar(this, this.container, this.localization);
115
+
116
+ if (defaultLayout) {
117
+ toolbar.addDefaultToolWidgets();
118
+ toolbar.addDefaultActionButtons();
119
+ }
120
+
121
+ return toolbar;
115
122
  }
116
123
 
117
124
  private registerListeners() {