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.
- package/CHANGELOG.md +7 -0
- package/dist/src/Editor.d.ts +1 -1
- package/dist/src/Editor.js +7 -2
- package/dist/src/EditorImage.d.ts +2 -0
- package/dist/src/EditorImage.js +29 -5
- package/dist/src/Pointer.js +1 -1
- package/dist/src/Viewport.d.ts +1 -1
- 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} +27 -9
- 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 +32 -0
- package/dist/src/geometry/Vec3.d.ts +2 -0
- package/dist/src/geometry/Vec3.js +13 -0
- package/dist/src/rendering/AbstractRenderer.js +3 -25
- package/dist/src/toolbar/HTMLToolbar.d.ts +4 -1
- package/dist/src/toolbar/HTMLToolbar.js +143 -16
- package/dist/src/toolbar/types.d.ts +6 -0
- package/dist/src/tools/Pen.d.ts +13 -3
- package/dist/src/tools/Pen.js +37 -28
- package/dist/src/tools/ToolController.js +3 -3
- package/dist/src/types.d.ts +14 -2
- package/dist/src/types.js +1 -0
- package/package.json +4 -3
- package/src/Editor.ts +9 -2
- package/src/EditorImage.ts +31 -3
- package/src/Pointer.ts +1 -1
- package/src/Viewport.ts +1 -1
- 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} +36 -18
- 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.ts +43 -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/rendering/AbstractRenderer.ts +3 -32
- package/src/{editorStyles.js → styles.js} +0 -0
- package/src/toolbar/HTMLToolbar.ts +172 -22
- package/src/toolbar/toolbar.css +12 -0
- package/src/toolbar/types.ts +6 -0
- package/src/tools/Pen.ts +56 -34
- package/src/tools/ToolController.ts +3 -3
- 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
|
69
|
-
|
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
|
-
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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;
|
package/dist/src/tools/Pen.d.ts
CHANGED
@@ -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
|
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,
|
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 {};
|
package/dist/src/tools/Pen.js
CHANGED
@@ -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
|
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,
|
8
|
+
constructor(editor, description, style) {
|
11
9
|
super(editor.notifier, description);
|
12
10
|
this.editor = editor;
|
13
|
-
this.
|
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
|
-
|
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(
|
37
|
-
this.
|
38
|
-
this.
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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,
|
package/dist/src/types.d.ts
CHANGED
@@ -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
|
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
|
+
"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/
|
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": "
|
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
|
-
|
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() {
|