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.
- package/CHANGELOG.md +17 -0
- package/README.md +108 -0
- package/build_tools/BundledFile.ts +167 -0
- package/build_tools/bundle.ts +11 -0
- package/dist/build_tools/BundledFile.d.ts +13 -0
- package/dist/build_tools/BundledFile.js +157 -0
- package/dist/build_tools/bundle.d.ts +1 -0
- package/dist/build_tools/bundle.js +5 -0
- package/dist/bundle.js +1 -0
- package/dist/src/Display.js +4 -1
- package/dist/src/Editor.d.ts +9 -2
- package/dist/src/Editor.js +36 -7
- package/dist/src/EditorImage.d.ts +4 -2
- package/dist/src/EditorImage.js +29 -5
- package/dist/src/Pointer.js +1 -1
- package/dist/src/SVGLoader.js +3 -1
- package/dist/src/Viewport.d.ts +2 -2
- package/dist/src/bundle/bundled.d.ts +4 -0
- package/dist/src/bundle/bundled.js +5 -0
- package/dist/src/components/AbstractComponent.d.ts +1 -1
- package/dist/src/components/Stroke.d.ts +1 -1
- package/dist/src/components/Stroke.js +1 -1
- package/dist/src/components/UnknownSVGObject.d.ts +1 -1
- package/dist/src/components/builders/ArrowBuilder.d.ts +17 -0
- package/dist/src/components/builders/ArrowBuilder.js +83 -0
- package/dist/src/{StrokeBuilder.d.ts → components/builders/FreehandLineBuilder.d.ts} +9 -13
- package/dist/src/{StrokeBuilder.js → components/builders/FreehandLineBuilder.js} +42 -12
- package/dist/src/components/builders/LineBuilder.d.ts +16 -0
- package/dist/src/components/builders/LineBuilder.js +57 -0
- package/dist/src/components/builders/RectangleBuilder.d.ts +18 -0
- package/dist/src/components/builders/RectangleBuilder.js +41 -0
- package/dist/src/components/builders/types.d.ts +12 -0
- package/dist/src/components/builders/types.js +1 -0
- package/dist/src/geometry/Path.d.ts +1 -0
- package/dist/src/geometry/Path.js +43 -0
- package/dist/src/geometry/Vec3.d.ts +2 -0
- package/dist/src/geometry/Vec3.js +13 -0
- package/dist/src/localization.d.ts +1 -1
- package/dist/src/localization.js +2 -2
- package/dist/src/rendering/AbstractRenderer.js +3 -25
- package/dist/src/toolbar/HTMLToolbar.d.ts +5 -3
- package/dist/src/toolbar/HTMLToolbar.js +139 -30
- package/dist/src/toolbar/localization.d.ts +20 -0
- package/dist/src/toolbar/localization.js +19 -0
- package/dist/src/toolbar/types.d.ts +0 -13
- package/dist/src/tools/Pen.d.ts +13 -3
- package/dist/src/tools/Pen.js +37 -28
- package/dist/src/tools/SelectionTool.js +1 -1
- package/dist/src/tools/ToolController.js +3 -3
- package/dist/src/types.d.ts +14 -2
- package/dist/src/types.js +1 -0
- package/dist-test/test-dist-bundle.html +35 -0
- package/package.json +15 -5
- package/src/Display.ts +3 -1
- package/src/Editor.css +0 -1
- package/src/Editor.ts +62 -13
- package/src/EditorImage.test.ts +5 -3
- package/src/EditorImage.ts +31 -3
- package/src/Pointer.ts +1 -1
- package/src/SVGLoader.ts +5 -1
- package/src/Viewport.ts +1 -1
- package/src/bundle/bundled.ts +7 -0
- package/src/components/AbstractComponent.ts +1 -1
- package/src/components/Stroke.ts +2 -2
- package/src/components/UnknownSVGObject.ts +1 -1
- package/src/components/builders/ArrowBuilder.ts +104 -0
- package/src/{StrokeBuilder.ts → components/builders/FreehandLineBuilder.ts} +59 -22
- package/src/components/builders/LineBuilder.ts +75 -0
- package/src/components/builders/RectangleBuilder.ts +59 -0
- package/src/components/builders/types.ts +15 -0
- package/src/geometry/Path.fromString.test.ts +11 -24
- package/src/geometry/Path.ts +56 -0
- package/src/geometry/Vec2.test.ts +1 -0
- package/src/geometry/Vec3.test.ts +14 -0
- package/src/geometry/Vec3.ts +16 -0
- package/src/localization.ts +2 -3
- package/src/rendering/AbstractRenderer.ts +3 -32
- package/src/{editorStyles.js → styles.js} +0 -0
- package/src/toolbar/HTMLToolbar.ts +167 -39
- package/src/toolbar/localization.ts +44 -0
- package/src/toolbar/toolbar.css +12 -0
- package/src/toolbar/types.ts +0 -16
- package/src/tools/Pen.ts +56 -34
- package/src/tools/SelectionTool.test.ts +1 -1
- package/src/tools/SelectionTool.ts +1 -1
- package/src/tools/ToolController.ts +3 -3
- package/src/types.ts +16 -1
@@ -1,6 +1,6 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
2
|
import { ToolType } from '../tools/ToolController';
|
3
|
-
import { EditorEventType } from '../types';
|
3
|
+
import { EditorEventType, StrokeDataPoint } from '../types';
|
4
4
|
|
5
5
|
import { coloris, init as colorisInit } from '@melloware/coloris';
|
6
6
|
import Color4 from '../Color4';
|
@@ -8,7 +8,16 @@ import Pen from '../tools/Pen';
|
|
8
8
|
import Eraser from '../tools/Eraser';
|
9
9
|
import BaseTool from '../tools/BaseTool';
|
10
10
|
import SelectionTool from '../tools/SelectionTool';
|
11
|
-
import {
|
11
|
+
import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder';
|
12
|
+
import { Vec2 } from '../geometry/Vec2';
|
13
|
+
import SVGRenderer from '../rendering/SVGRenderer';
|
14
|
+
import Viewport from '../Viewport';
|
15
|
+
import EventDispatcher from '../EventDispatcher';
|
16
|
+
import { ComponentBuilderFactory } from '../components/builders/types';
|
17
|
+
import { makeArrowBuilder } from '../components/builders/ArrowBuilder';
|
18
|
+
import { makeLineBuilder } from '../components/builders/LineBuilder';
|
19
|
+
import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../components/builders/RectangleBuilder';
|
20
|
+
import { defaultToolbarLocalization, ToolbarLocalization } from './localization';
|
12
21
|
|
13
22
|
const primaryForegroundFill = `
|
14
23
|
style='fill: var(--primary-foreground-color);'
|
@@ -18,6 +27,8 @@ const primaryForegroundStrokeFill = `
|
|
18
27
|
`;
|
19
28
|
|
20
29
|
const toolbarCSSPrefix = 'toolbar-';
|
30
|
+
const svgNamespace = 'http://www.w3.org/2000/svg';
|
31
|
+
|
21
32
|
abstract class ToolbarWidget {
|
22
33
|
protected readonly container: HTMLElement;
|
23
34
|
private button: HTMLElement;
|
@@ -163,7 +174,7 @@ abstract class ToolbarWidget {
|
|
163
174
|
}
|
164
175
|
|
165
176
|
private createDropdownIcon(): Element {
|
166
|
-
const icon = document.createElementNS(
|
177
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
167
178
|
icon.innerHTML = `
|
168
179
|
<g>
|
169
180
|
<path
|
@@ -183,9 +194,7 @@ class EraserWidget extends ToolbarWidget {
|
|
183
194
|
return this.localizationTable.eraser;
|
184
195
|
}
|
185
196
|
protected createIcon(): Element {
|
186
|
-
const icon = document.createElementNS(
|
187
|
-
'http://www.w3.org/2000/svg', 'svg'
|
188
|
-
);
|
197
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
189
198
|
|
190
199
|
// Draw an eraser-like shape
|
191
200
|
icon.innerHTML = `
|
@@ -220,7 +229,7 @@ class SelectionWidget extends ToolbarWidget {
|
|
220
229
|
}
|
221
230
|
|
222
231
|
protected createIcon(): Element {
|
223
|
-
const icon = document.createElementNS(
|
232
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
224
233
|
|
225
234
|
// Draw a cursor-like shape
|
226
235
|
icon.innerHTML = `
|
@@ -270,7 +279,7 @@ class TouchDrawingWidget extends ToolbarWidget {
|
|
270
279
|
}
|
271
280
|
|
272
281
|
protected createIcon(): Element {
|
273
|
-
const icon = document.createElementNS(
|
282
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
274
283
|
|
275
284
|
// Draw a cursor-like shape
|
276
285
|
icon.innerHTML = `
|
@@ -307,7 +316,7 @@ class PenWidget extends ToolbarWidget {
|
|
307
316
|
private updateInputs: ()=> void = () => {};
|
308
317
|
|
309
318
|
public constructor(
|
310
|
-
editor: Editor, private tool: Pen, localization: ToolbarLocalization
|
319
|
+
editor: Editor, private tool: Pen, localization: ToolbarLocalization, private penTypes: PenTypeRecord[]
|
311
320
|
) {
|
312
321
|
super(editor, tool, localization);
|
313
322
|
|
@@ -328,13 +337,7 @@ class PenWidget extends ToolbarWidget {
|
|
328
337
|
return this.targetTool.description;
|
329
338
|
}
|
330
339
|
|
331
|
-
|
332
|
-
// We need to use createElementNS to embed an SVG element in HTML.
|
333
|
-
// See http://zhangwenli.com/blog/2017/07/26/createelementns/
|
334
|
-
const icon = document.createElementNS(
|
335
|
-
'http://www.w3.org/2000/svg', 'svg'
|
336
|
-
);
|
337
|
-
|
340
|
+
private makePenIcon(elem: SVGSVGElement) {
|
338
341
|
// Use a square-root scale to prevent the pen's tip from overflowing.
|
339
342
|
const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 2);
|
340
343
|
const color = this.tool.getColor();
|
@@ -342,7 +345,7 @@ class PenWidget extends ToolbarWidget {
|
|
342
345
|
// Draw a pen-like shape
|
343
346
|
const primaryStrokeTipPath = `M14,63 L${50 - scale},95 L${50 + scale},90 L88,60 Z`;
|
344
347
|
const backgroundStrokeTipPath = `M14,63 L${50 - scale},85 L${50 + scale},83 L88,60 Z`;
|
345
|
-
|
348
|
+
elem.innerHTML = `
|
346
349
|
<defs>
|
347
350
|
<pattern
|
348
351
|
id='checkerboard'
|
@@ -375,8 +378,50 @@ class PenWidget extends ToolbarWidget {
|
|
375
378
|
/>
|
376
379
|
</g>
|
377
380
|
`;
|
381
|
+
}
|
382
|
+
|
383
|
+
// Draws an icon with the pen.
|
384
|
+
private makeDrawnIcon(icon: SVGSVGElement) {
|
385
|
+
const strokeFactory = this.tool.getStrokeFactory();
|
386
|
+
|
387
|
+
const toolThickness = this.tool.getThickness();
|
388
|
+
|
389
|
+
const nowTime = (new Date()).getTime();
|
390
|
+
const startPoint: StrokeDataPoint = {
|
391
|
+
pos: Vec2.of(10, 10),
|
392
|
+
width: toolThickness / 5,
|
393
|
+
color: this.tool.getColor(),
|
394
|
+
time: nowTime - 100,
|
395
|
+
};
|
396
|
+
const endPoint: StrokeDataPoint = {
|
397
|
+
pos: Vec2.of(90, 90),
|
398
|
+
width: toolThickness / 5,
|
399
|
+
color: this.tool.getColor(),
|
400
|
+
time: nowTime,
|
401
|
+
};
|
402
|
+
|
403
|
+
const builder = strokeFactory(startPoint, this.editor.viewport);
|
404
|
+
builder.addPoint(endPoint);
|
405
|
+
|
406
|
+
const viewport = new Viewport(new EventDispatcher());
|
407
|
+
viewport.updateScreenSize(Vec2.of(100, 100));
|
408
|
+
const renderer = new SVGRenderer(icon, viewport);
|
409
|
+
builder.preview(renderer);
|
410
|
+
}
|
411
|
+
|
412
|
+
protected createIcon(): Element {
|
413
|
+
// We need to use createElementNS to embed an SVG element in HTML.
|
414
|
+
// See http://zhangwenli.com/blog/2017/07/26/createelementns/
|
415
|
+
const icon = document.createElementNS(svgNamespace, 'svg');
|
378
416
|
icon.setAttribute('viewBox', '0 0 100 100');
|
379
417
|
|
418
|
+
const strokeFactory = this.tool.getStrokeFactory();
|
419
|
+
if (strokeFactory === makeFreehandLineBuilder) {
|
420
|
+
this.makePenIcon(icon);
|
421
|
+
} else {
|
422
|
+
this.makeDrawnIcon(icon);
|
423
|
+
}
|
424
|
+
|
380
425
|
return icon;
|
381
426
|
}
|
382
427
|
|
@@ -384,15 +429,23 @@ class PenWidget extends ToolbarWidget {
|
|
384
429
|
protected fillDropdown(dropdown: HTMLElement): boolean {
|
385
430
|
const container = document.createElement('div');
|
386
431
|
|
387
|
-
// Thickness: Value of the input is squared to allow for finer control/larger values.
|
388
432
|
const thicknessRow = document.createElement('div');
|
433
|
+
const objectTypeRow = document.createElement('div');
|
434
|
+
|
435
|
+
// Thickness: Value of the input is squared to allow for finer control/larger values.
|
389
436
|
const thicknessLabel = document.createElement('label');
|
390
437
|
const thicknessInput = document.createElement('input');
|
438
|
+
const objectSelectLabel = document.createElement('label');
|
439
|
+
const objectTypeSelect = document.createElement('select');
|
391
440
|
|
441
|
+
// Give inputs IDs so we can label them with a <label for=...>Label text</label>
|
392
442
|
thicknessInput.id = `${toolbarCSSPrefix}thicknessInput${PenWidget.idCounter++}`;
|
443
|
+
objectTypeSelect.id = `${toolbarCSSPrefix}builderSelect${PenWidget.idCounter++}`;
|
393
444
|
|
394
445
|
thicknessLabel.innerText = this.localizationTable.thicknessLabel;
|
395
446
|
thicknessLabel.setAttribute('for', thicknessInput.id);
|
447
|
+
objectSelectLabel.innerText = this.localizationTable.selectObjectType;
|
448
|
+
objectSelectLabel.setAttribute('for', objectTypeSelect.id);
|
396
449
|
|
397
450
|
thicknessInput.type = 'range';
|
398
451
|
thicknessInput.min = '1';
|
@@ -404,6 +457,18 @@ class PenWidget extends ToolbarWidget {
|
|
404
457
|
thicknessRow.appendChild(thicknessLabel);
|
405
458
|
thicknessRow.appendChild(thicknessInput);
|
406
459
|
|
460
|
+
objectTypeSelect.oninput = () => {
|
461
|
+
const penTypeIdx = parseInt(objectTypeSelect.value);
|
462
|
+
if (penTypeIdx < 0 || penTypeIdx >= this.penTypes.length) {
|
463
|
+
console.error('Invalid pen type index', penTypeIdx);
|
464
|
+
return;
|
465
|
+
}
|
466
|
+
|
467
|
+
this.tool.setStrokeFactory(this.penTypes[penTypeIdx].factory);
|
468
|
+
};
|
469
|
+
objectTypeRow.appendChild(objectSelectLabel);
|
470
|
+
objectTypeRow.appendChild(objectTypeSelect);
|
471
|
+
|
407
472
|
const colorRow = document.createElement('div');
|
408
473
|
const colorLabel = document.createElement('label');
|
409
474
|
const colorInput = document.createElement('input');
|
@@ -417,6 +482,18 @@ class PenWidget extends ToolbarWidget {
|
|
417
482
|
colorInput.oninput = () => {
|
418
483
|
this.tool.setColor(Color4.fromHex(colorInput.value));
|
419
484
|
};
|
485
|
+
colorInput.addEventListener('open', () => {
|
486
|
+
this.editor.notifier.dispatch(EditorEventType.ColorPickerToggled, {
|
487
|
+
kind: EditorEventType.ColorPickerToggled,
|
488
|
+
open: true,
|
489
|
+
});
|
490
|
+
});
|
491
|
+
colorInput.addEventListener('close', () => {
|
492
|
+
this.editor.notifier.dispatch(EditorEventType.ColorPickerToggled, {
|
493
|
+
kind: EditorEventType.ColorPickerToggled,
|
494
|
+
open: false,
|
495
|
+
});
|
496
|
+
});
|
420
497
|
|
421
498
|
colorRow.appendChild(colorLabel);
|
422
499
|
colorRow.appendChild(colorInput);
|
@@ -424,45 +501,80 @@ class PenWidget extends ToolbarWidget {
|
|
424
501
|
this.updateInputs = () => {
|
425
502
|
colorInput.value = this.tool.getColor().toHexString();
|
426
503
|
thicknessInput.value = Math.sqrt(this.tool.getThickness()).toString();
|
504
|
+
|
505
|
+
objectTypeSelect.replaceChildren();
|
506
|
+
for (let i = 0; i < this.penTypes.length; i ++) {
|
507
|
+
const penType = this.penTypes[i];
|
508
|
+
const option = document.createElement('option');
|
509
|
+
option.value = i.toString();
|
510
|
+
option.innerText = penType.name;
|
511
|
+
|
512
|
+
objectTypeSelect.appendChild(option);
|
513
|
+
|
514
|
+
if (penType.factory === this.tool.getStrokeFactory()) {
|
515
|
+
objectTypeSelect.value = i.toString();
|
516
|
+
}
|
517
|
+
}
|
427
518
|
};
|
428
519
|
this.updateInputs();
|
429
520
|
|
430
|
-
container.replaceChildren(colorRow, thicknessRow);
|
521
|
+
container.replaceChildren(colorRow, thicknessRow, objectTypeRow);
|
431
522
|
dropdown.replaceChildren(container);
|
432
523
|
return true;
|
433
524
|
}
|
434
525
|
}
|
435
526
|
|
527
|
+
interface PenTypeRecord {
|
528
|
+
name: string;
|
529
|
+
factory: ComponentBuilderFactory;
|
530
|
+
}
|
531
|
+
|
436
532
|
export default class HTMLToolbar {
|
437
533
|
private container: HTMLElement;
|
438
|
-
|
439
|
-
public static defaultLocalization: ToolbarLocalization = {
|
440
|
-
pen: 'Pen',
|
441
|
-
eraser: 'Eraser',
|
442
|
-
select: 'Select',
|
443
|
-
touchDrawing: 'Touch Drawing',
|
444
|
-
thicknessLabel: 'Thickness: ',
|
445
|
-
colorLabel: 'Color: ',
|
446
|
-
resizeImageToSelection: 'Resize image to selection',
|
447
|
-
undo: 'Undo',
|
448
|
-
redo: 'Redo',
|
449
|
-
|
450
|
-
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
451
|
-
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
452
|
-
};
|
534
|
+
private penTypes: PenTypeRecord[];
|
453
535
|
|
454
536
|
public constructor(
|
455
537
|
private editor: Editor, parent: HTMLElement,
|
456
|
-
private localizationTable: ToolbarLocalization =
|
538
|
+
private localizationTable: ToolbarLocalization = defaultToolbarLocalization,
|
457
539
|
) {
|
458
540
|
this.container = document.createElement('div');
|
459
541
|
this.container.classList.add(`${toolbarCSSPrefix}root`);
|
460
542
|
this.container.setAttribute('role', 'toolbar');
|
461
|
-
this.addElements();
|
462
543
|
parent.appendChild(this.container);
|
463
544
|
|
464
|
-
// Initialize color choosers
|
465
545
|
colorisInit();
|
546
|
+
this.setupColorPickers();
|
547
|
+
|
548
|
+
// Default pen types
|
549
|
+
this.penTypes = [
|
550
|
+
{
|
551
|
+
name: localizationTable.freehandPen,
|
552
|
+
factory: makeFreehandLineBuilder,
|
553
|
+
},
|
554
|
+
{
|
555
|
+
name: localizationTable.arrowPen,
|
556
|
+
factory: makeArrowBuilder,
|
557
|
+
},
|
558
|
+
{
|
559
|
+
name: localizationTable.linePen,
|
560
|
+
factory: makeLineBuilder,
|
561
|
+
},
|
562
|
+
{
|
563
|
+
name: localizationTable.filledRectanglePen,
|
564
|
+
factory: makeFilledRectangleBuilder,
|
565
|
+
},
|
566
|
+
{
|
567
|
+
name: localizationTable.outlinedRectanglePen,
|
568
|
+
factory: makeOutlinedRectangleBuilder,
|
569
|
+
},
|
570
|
+
];
|
571
|
+
}
|
572
|
+
|
573
|
+
public setupColorPickers() {
|
574
|
+
const closePickerOverlay = document.createElement('div');
|
575
|
+
closePickerOverlay.className = `${toolbarCSSPrefix}closeColorPickerOverlay`;
|
576
|
+
this.editor.createHTMLOverlay(closePickerOverlay);
|
577
|
+
|
466
578
|
coloris({
|
467
579
|
el: '.coloris_input',
|
468
580
|
format: 'hex',
|
@@ -479,6 +591,16 @@ export default class HTMLToolbar {
|
|
479
591
|
Color4.white.toHexString(),
|
480
592
|
],
|
481
593
|
});
|
594
|
+
|
595
|
+
this.editor.notifier.on(EditorEventType.ColorPickerToggled, event => {
|
596
|
+
if (event.kind !== EditorEventType.ColorPickerToggled) {
|
597
|
+
return;
|
598
|
+
}
|
599
|
+
|
600
|
+
// Show/hide the overlay. Making the overlay visible gives users a surface to click
|
601
|
+
// on that shows/hides the color picker.
|
602
|
+
closePickerOverlay.style.display = event.open ? 'block' : 'none';
|
603
|
+
});
|
482
604
|
}
|
483
605
|
|
484
606
|
public addActionButton(text: string, command: ()=> void, parent?: Element) {
|
@@ -515,14 +637,16 @@ export default class HTMLToolbar {
|
|
515
637
|
});
|
516
638
|
}
|
517
639
|
|
518
|
-
|
640
|
+
public addDefaultToolWidgets() {
|
519
641
|
const toolController = this.editor.toolController;
|
520
642
|
for (const tool of toolController.getMatchingTools(ToolType.Pen)) {
|
521
643
|
if (!(tool instanceof Pen)) {
|
522
644
|
throw new Error('All `Pen` tools must have kind === ToolType.Pen');
|
523
645
|
}
|
524
646
|
|
525
|
-
const widget = new PenWidget(
|
647
|
+
const widget = new PenWidget(
|
648
|
+
this.editor, tool, this.localizationTable, this.penTypes,
|
649
|
+
);
|
526
650
|
widget.addTo(this.container);
|
527
651
|
}
|
528
652
|
|
@@ -546,6 +670,10 @@ export default class HTMLToolbar {
|
|
546
670
|
(new TouchDrawingWidget(this.editor, tool, this.localizationTable)).addTo(this.container);
|
547
671
|
}
|
548
672
|
|
673
|
+
this.setupColorPickers();
|
674
|
+
}
|
675
|
+
|
676
|
+
public addDefaultActionButtons() {
|
549
677
|
this.addUndoRedoButtons();
|
550
678
|
}
|
551
679
|
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
export interface ToolbarLocalization {
|
4
|
+
outlinedRectanglePen: string;
|
5
|
+
filledRectanglePen: string;
|
6
|
+
linePen: string;
|
7
|
+
arrowPen: string;
|
8
|
+
freehandPen: string;
|
9
|
+
selectObjectType: string;
|
10
|
+
colorLabel: string;
|
11
|
+
pen: string;
|
12
|
+
eraser: string;
|
13
|
+
select: string;
|
14
|
+
touchDrawing: string;
|
15
|
+
thicknessLabel: string;
|
16
|
+
resizeImageToSelection: string;
|
17
|
+
undo: string;
|
18
|
+
redo: string;
|
19
|
+
|
20
|
+
dropdownShown: (toolName: string)=>string;
|
21
|
+
dropdownHidden: (toolName: string)=>string;
|
22
|
+
}
|
23
|
+
|
24
|
+
export const defaultToolbarLocalization: ToolbarLocalization = {
|
25
|
+
pen: 'Pen',
|
26
|
+
eraser: 'Eraser',
|
27
|
+
select: 'Select',
|
28
|
+
touchDrawing: 'Touch Drawing',
|
29
|
+
thicknessLabel: 'Thickness: ',
|
30
|
+
colorLabel: 'Color: ',
|
31
|
+
resizeImageToSelection: 'Resize image to selection',
|
32
|
+
undo: 'Undo',
|
33
|
+
redo: 'Redo',
|
34
|
+
selectObjectType: 'Object type: ',
|
35
|
+
|
36
|
+
freehandPen: 'Freehand',
|
37
|
+
arrowPen: 'Arrow',
|
38
|
+
linePen: 'Line',
|
39
|
+
outlinedRectanglePen: 'Outlined rectangle',
|
40
|
+
filledRectanglePen: 'Filled rectangle',
|
41
|
+
|
42
|
+
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
43
|
+
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
44
|
+
};
|
package/src/toolbar/toolbar.css
CHANGED
@@ -100,6 +100,18 @@
|
|
100
100
|
flex-direction: row;
|
101
101
|
}
|
102
102
|
|
103
|
+
.toolbar-closeColorPickerOverlay {
|
104
|
+
display: none;
|
105
|
+
position: fixed;
|
106
|
+
top: 0;
|
107
|
+
left: 0;
|
108
|
+
bottom: 0;
|
109
|
+
right: 0;
|
110
|
+
|
111
|
+
background-color: var(--primary-background-color);
|
112
|
+
opacity: 0.3;
|
113
|
+
}
|
114
|
+
|
103
115
|
/* Make color selection buttons fill their containing label */
|
104
116
|
.toolbar-dropdown .clr-field button {
|
105
117
|
width: 100%;
|
package/src/toolbar/types.ts
CHANGED
@@ -2,19 +2,3 @@ export enum ToolbarButtonType {
|
|
2
2
|
ToggleButton,
|
3
3
|
ActionButton,
|
4
4
|
}
|
5
|
-
|
6
|
-
|
7
|
-
export interface ToolbarLocalization {
|
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
|
-
|
18
|
-
dropdownShown: (toolName: string)=>string;
|
19
|
-
dropdownHidden: (toolName: string)=>string;
|
20
|
-
}
|
package/src/tools/Pen.ts
CHANGED
@@ -1,49 +1,60 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import Editor from '../Editor';
|
3
3
|
import EditorImage from '../EditorImage';
|
4
|
-
import { Vec2 } from '../geometry/Vec2';
|
5
4
|
import Pointer, { PointerDevice } from '../Pointer';
|
6
|
-
import
|
7
|
-
import { EditorEventType, PointerEvt } from '../types';
|
5
|
+
import { makeFreehandLineBuilder } from '../components/builders/FreehandLineBuilder';
|
6
|
+
import { EditorEventType, PointerEvt, StrokeDataPoint } from '../types';
|
8
7
|
import BaseTool from './BaseTool';
|
9
8
|
import { ToolType } from './ToolController';
|
9
|
+
import { ComponentBuilder, ComponentBuilderFactory } from '../components/builders/types';
|
10
|
+
|
11
|
+
interface PenStyle {
|
12
|
+
color: Color4;
|
13
|
+
thickness: number;
|
14
|
+
}
|
10
15
|
|
11
16
|
export default class Pen extends BaseTool {
|
12
|
-
private builder:
|
17
|
+
private builder: ComponentBuilder|null = null;
|
18
|
+
private builderFactory: ComponentBuilderFactory = makeFreehandLineBuilder;
|
19
|
+
private lastPoint: StrokeDataPoint|null = null;
|
20
|
+
|
13
21
|
public readonly kind: ToolType = ToolType.Pen;
|
14
22
|
|
15
23
|
public constructor(
|
16
24
|
private editor: Editor,
|
17
25
|
description: string,
|
18
|
-
private
|
19
|
-
private thickness: number = 16.0,
|
26
|
+
private style: PenStyle,
|
20
27
|
) {
|
21
28
|
super(editor.notifier, description);
|
22
29
|
}
|
23
30
|
|
24
31
|
private getPressureMultiplier() {
|
25
|
-
return 1 / this.editor.viewport.getScaleFactor() * this.thickness;
|
32
|
+
return 1 / this.editor.viewport.getScaleFactor() * this.style.thickness;
|
26
33
|
}
|
27
34
|
|
28
|
-
private getStrokePoint(pointer: Pointer) {
|
35
|
+
private getStrokePoint(pointer: Pointer): StrokeDataPoint {
|
29
36
|
const minPressure = 0.3;
|
30
37
|
const pressure = Math.max(pointer.pressure ?? 1.0, minPressure);
|
31
38
|
return {
|
32
39
|
pos: pointer.canvasPos,
|
33
40
|
width: pressure * this.getPressureMultiplier(),
|
34
|
-
color: this.color,
|
41
|
+
color: this.style.color,
|
35
42
|
time: pointer.timeStamp,
|
36
43
|
};
|
37
44
|
}
|
38
45
|
|
39
|
-
private
|
46
|
+
private previewStroke() {
|
47
|
+
this.editor.clearWetInk();
|
48
|
+
this.builder?.preview(this.editor.display.getWetInkRenderer());
|
49
|
+
}
|
50
|
+
|
51
|
+
private addPointToStroke(point: StrokeDataPoint) {
|
40
52
|
if (!this.builder) {
|
41
53
|
throw new Error('No stroke is currently being generated.');
|
42
54
|
}
|
43
|
-
this.builder.addPoint(
|
44
|
-
|
45
|
-
this.
|
46
|
-
this.editor.drawWetInk(...this.builder.preview());
|
55
|
+
this.builder.addPoint(point);
|
56
|
+
this.lastPoint = point;
|
57
|
+
this.previewStroke();
|
47
58
|
}
|
48
59
|
|
49
60
|
public onPointerDown({ current, allPointers }: PointerEvt): boolean {
|
@@ -52,15 +63,7 @@ export default class Pen extends BaseTool {
|
|
52
63
|
}
|
53
64
|
|
54
65
|
if (allPointers.length === 1 || current.device === PointerDevice.Pen) {
|
55
|
-
|
56
|
-
// less than ± 2 px from the curve.
|
57
|
-
const canvasTransform = this.editor.viewport.screenToCanvasTransform;
|
58
|
-
const maxSmoothingDist = canvasTransform.transformVec3(Vec2.unitX).magnitude() * 7;
|
59
|
-
const minSmoothingDist = canvasTransform.transformVec3(Vec2.unitX).magnitude() * 2;
|
60
|
-
|
61
|
-
this.builder = new StrokeBuilder(
|
62
|
-
this.getStrokePoint(current), minSmoothingDist, maxSmoothingDist
|
63
|
-
);
|
66
|
+
this.builder = this.builderFactory(this.getStrokePoint(current), this.editor.viewport);
|
64
67
|
return true;
|
65
68
|
}
|
66
69
|
|
@@ -68,7 +71,7 @@ export default class Pen extends BaseTool {
|
|
68
71
|
}
|
69
72
|
|
70
73
|
public onPointerMove({ current }: PointerEvt): void {
|
71
|
-
this.addPointToStroke(current);
|
74
|
+
this.addPointToStroke(this.getStrokePoint(current));
|
72
75
|
}
|
73
76
|
|
74
77
|
public onPointerUp({ current }: PointerEvt): void {
|
@@ -76,12 +79,17 @@ export default class Pen extends BaseTool {
|
|
76
79
|
return;
|
77
80
|
}
|
78
81
|
|
79
|
-
|
82
|
+
// onPointerUp events can have zero pressure. Use the last pressure instead.
|
83
|
+
const currentPoint = this.getStrokePoint(current);
|
84
|
+
const strokePoint = {
|
85
|
+
...currentPoint,
|
86
|
+
width: this.lastPoint?.width ?? currentPoint.width,
|
87
|
+
};
|
88
|
+
|
89
|
+
this.addPointToStroke(strokePoint);
|
80
90
|
if (this.builder && current.isPrimary) {
|
81
91
|
const stroke = this.builder.build();
|
82
|
-
|
83
|
-
this.editor.clearWetInk();
|
84
|
-
this.editor.drawWetInk(...this.builder.preview());
|
92
|
+
this.previewStroke();
|
85
93
|
|
86
94
|
const canFlatten = true;
|
87
95
|
const action = new EditorImage.AddElementCommand(stroke, canFlatten);
|
@@ -103,19 +111,33 @@ export default class Pen extends BaseTool {
|
|
103
111
|
}
|
104
112
|
|
105
113
|
public setColor(color: Color4): void {
|
106
|
-
if (color.toHexString() !== this.color.toHexString()) {
|
107
|
-
this.
|
114
|
+
if (color.toHexString() !== this.style.color.toHexString()) {
|
115
|
+
this.style = {
|
116
|
+
...this.style,
|
117
|
+
color,
|
118
|
+
};
|
108
119
|
this.noteUpdated();
|
109
120
|
}
|
110
121
|
}
|
111
122
|
|
112
123
|
public setThickness(thickness: number) {
|
113
|
-
if (thickness !== this.thickness) {
|
114
|
-
this.
|
124
|
+
if (thickness !== this.style.thickness) {
|
125
|
+
this.style = {
|
126
|
+
...this.style,
|
127
|
+
thickness,
|
128
|
+
};
|
129
|
+
this.noteUpdated();
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
public setStrokeFactory(factory: ComponentBuilderFactory) {
|
134
|
+
if (factory !== this.builderFactory) {
|
135
|
+
this.builderFactory = factory;
|
115
136
|
this.noteUpdated();
|
116
137
|
}
|
117
138
|
}
|
118
139
|
|
119
|
-
public getThickness() { return this.thickness; }
|
120
|
-
public getColor() { return this.color; }
|
140
|
+
public getThickness() { return this.style.thickness; }
|
141
|
+
public getColor() { return this.style.color; }
|
142
|
+
public getStrokeFactory() { return this.builderFactory; }
|
121
143
|
}
|
@@ -15,7 +15,7 @@ const getSelectionTool = (editor: Editor): SelectionTool => {
|
|
15
15
|
return editor.toolController.getMatchingTools(ToolType.Selection)[0] as SelectionTool;
|
16
16
|
};
|
17
17
|
|
18
|
-
const createEditor = () => new Editor(document.body, RenderingMode.DummyRenderer);
|
18
|
+
const createEditor = () => new Editor(document.body, { renderingMode: RenderingMode.DummyRenderer });
|
19
19
|
|
20
20
|
const createSquareStroke = () => {
|
21
21
|
const testStroke = new Stroke([
|
@@ -24,17 +24,17 @@ export default class ToolController {
|
|
24
24
|
public constructor(editor: Editor, localization: ToolLocalization) {
|
25
25
|
const primaryToolEnabledGroup = new ToolEnabledGroup();
|
26
26
|
const touchPanZoom = new PanZoom(editor, PanZoomMode.OneFingerGestures, localization.touchPanTool);
|
27
|
-
const primaryPenTool = new Pen(editor, localization.penTool(1));
|
27
|
+
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 16 });
|
28
28
|
const primaryTools = [
|
29
29
|
new SelectionTool(editor, localization.selectionTool),
|
30
30
|
new Eraser(editor, localization.eraserTool),
|
31
31
|
|
32
32
|
// Three pens
|
33
33
|
primaryPenTool,
|
34
|
-
new Pen(editor, localization.penTool(2), Color4.clay, 8),
|
34
|
+
new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 8 }),
|
35
35
|
|
36
36
|
// Highlighter-like pen with width=64
|
37
|
-
new Pen(editor, localization.penTool(3), Color4.ofRGBA(1, 1, 0, 0.5), 64),
|
37
|
+
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 64 }),
|
38
38
|
];
|
39
39
|
this.tools = [
|
40
40
|
touchPanZoom,
|