js-draw 0.10.0 → 0.10.2
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 +9 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.d.ts +2 -2
- package/dist/src/EditorImage.d.ts +1 -1
- package/dist/src/EventDispatcher.d.ts +1 -1
- package/dist/src/SVGLoader.d.ts +2 -2
- package/dist/src/UndoRedoHistory.d.ts +2 -2
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/commands/SerializableCommand.d.ts +1 -1
- package/dist/src/components/AbstractComponent.d.ts +4 -3
- package/dist/src/components/AbstractComponent.js +6 -0
- package/dist/src/components/ImageComponent.d.ts +1 -0
- package/dist/src/components/ImageComponent.js +4 -0
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +1 -1
- package/dist/src/components/Stroke.d.ts +2 -0
- package/dist/src/components/Stroke.js +5 -0
- package/dist/src/components/TextComponent.d.ts +1 -0
- package/dist/src/components/TextComponent.js +3 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +4 -3
- package/dist/src/components/builders/PressureSensitiveFreehandLineBuilder.js +1 -1
- package/dist/src/components/builders/types.d.ts +1 -1
- package/dist/src/components/util/StrokeSmoother.d.ts +1 -1
- package/dist/src/components/util/StrokeSmoother.js +11 -7
- package/dist/src/math/Mat33.d.ts +3 -1
- package/dist/src/math/Mat33.js +7 -0
- package/dist/src/math/Path.d.ts +1 -1
- package/dist/src/math/Path.js +3 -0
- package/dist/src/math/Vec2.d.ts +2 -2
- package/dist/src/rendering/Display.js +5 -2
- package/dist/src/rendering/caching/RenderingCache.js +5 -1
- package/dist/src/rendering/caching/RenderingCacheNode.js +5 -2
- package/dist/src/rendering/caching/testUtils.d.ts +1 -1
- package/dist/src/rendering/caching/testUtils.js +1 -1
- package/dist/src/rendering/caching/types.d.ts +4 -4
- package/dist/src/toolbar/IconProvider.d.ts +3 -2
- package/dist/src/toolbar/IconProvider.js +23 -3
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/toolbar/makeColorInput.d.ts +2 -2
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/PenToolWidget.js +2 -1
- package/dist/src/tools/BaseTool.js +4 -4
- package/dist/src/tools/PanZoom.js +13 -3
- package/dist/src/tools/PipetteTool.d.ts +1 -1
- package/dist/src/tools/SelectionTool/SelectionHandle.d.ts +3 -3
- package/dist/src/tools/ToolbarShortcutHandler.d.ts +1 -1
- package/dist/src/types.d.ts +8 -8
- package/package.json +1 -1
- package/src/components/AbstractComponent.ts +8 -0
- package/src/components/ImageComponent.ts +5 -0
- package/src/components/Stroke.ts +11 -0
- package/src/components/TextComponent.ts +4 -0
- package/src/components/builders/FreehandLineBuilder.ts +4 -3
- package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +1 -1
- package/src/components/util/StrokeSmoother.ts +14 -11
- package/src/math/Mat33.ts +9 -0
- package/src/math/Path.ts +4 -0
- package/src/rendering/Display.ts +7 -2
- package/src/rendering/caching/RenderingCache.ts +10 -2
- package/src/rendering/caching/RenderingCacheNode.ts +6 -2
- package/src/rendering/caching/testUtils.ts +2 -2
- package/src/rendering/caching/types.ts +2 -2
- package/src/toolbar/IconProvider.ts +28 -3
- package/src/toolbar/localization.ts +3 -0
- package/src/toolbar/widgets/PenToolWidget.ts +3 -1
- package/src/tools/PanZoom.ts +16 -3
- package/.firebase/hosting.ZG9jcw.cache +0 -338
@@ -2,7 +2,7 @@ import Color4 from '../Color4';
|
|
2
2
|
import Editor from '../Editor';
|
3
3
|
import { PointerEvt } from '../types';
|
4
4
|
import BaseTool from './BaseTool';
|
5
|
-
|
5
|
+
type ColorListener = (color: Color4 | null) => void;
|
6
6
|
export default class PipetteTool extends BaseTool {
|
7
7
|
private editor;
|
8
8
|
private colorPreviewListener;
|
@@ -6,9 +6,9 @@ export declare enum HandleShape {
|
|
6
6
|
Square = 1
|
7
7
|
}
|
8
8
|
export declare const handleSize = 30;
|
9
|
-
export
|
10
|
-
export
|
11
|
-
export
|
9
|
+
export type DragStartCallback = (startPoint: Point2) => void;
|
10
|
+
export type DragUpdateCallback = (canvasPoint: Point2) => void;
|
11
|
+
export type DragEndCallback = () => void;
|
12
12
|
export default class SelectionHandle {
|
13
13
|
readonly shape: HandleShape;
|
14
14
|
private readonly parentSide;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
2
|
import { KeyPressEvent } from '../types';
|
3
3
|
import BaseTool from './BaseTool';
|
4
|
-
|
4
|
+
type KeyPressListener = (event: KeyPressEvent) => boolean;
|
5
5
|
export default class ToolbarShortcutHandler extends BaseTool {
|
6
6
|
private listeners;
|
7
7
|
constructor(editor: Editor);
|
package/dist/src/types.d.ts
CHANGED
@@ -68,9 +68,9 @@ export interface PointerMoveEvt extends PointerEvtBase {
|
|
68
68
|
export interface PointerUpEvt extends PointerEvtBase {
|
69
69
|
readonly kind: InputEvtType.PointerUpEvt;
|
70
70
|
}
|
71
|
-
export
|
72
|
-
export
|
73
|
-
export
|
71
|
+
export type PointerEvt = PointerDownEvt | PointerMoveEvt | PointerUpEvt;
|
72
|
+
export type InputEvt = KeyPressEvent | KeyUpEvent | WheelEvt | GestureCancelEvt | PointerEvt | CopyEvent | PasteEvent;
|
73
|
+
export type EditorNotifier = EventDispatcher<EditorEventType, EditorEventDataType>;
|
74
74
|
export declare enum EditorEventType {
|
75
75
|
ToolEnabled = 0,
|
76
76
|
ToolDisabled = 1,
|
@@ -85,7 +85,7 @@ export declare enum EditorEventType {
|
|
85
85
|
ColorPickerColorSelected = 10,
|
86
86
|
ToolbarDropdownShown = 11
|
87
87
|
}
|
88
|
-
|
88
|
+
type EditorToolEventType = EditorEventType.ToolEnabled | EditorEventType.ToolDisabled | EditorEventType.ToolUpdated;
|
89
89
|
export interface EditorToolEvent {
|
90
90
|
readonly kind: EditorToolEventType;
|
91
91
|
readonly tool: BaseTool;
|
@@ -128,10 +128,10 @@ export interface ToolbarDropdownShownEvent {
|
|
128
128
|
readonly kind: EditorEventType.ToolbarDropdownShown;
|
129
129
|
readonly parentWidget: BaseWidget;
|
130
130
|
}
|
131
|
-
export
|
132
|
-
export
|
133
|
-
export
|
134
|
-
export
|
131
|
+
export type EditorEventDataType = EditorToolEvent | EditorObjectEvent | EditorViewportChangedEvent | DisplayResizedEvent | EditorUndoStackUpdated | CommandDoneEvent | CommandUndoneEvent | ColorPickerToggled | ColorPickerColorSelected | ToolbarDropdownShownEvent;
|
132
|
+
export type OnProgressListener = (amountProcessed: number, totalToProcess: number) => Promise<void> | null;
|
133
|
+
export type ComponentAddedListener = (component: AbstractComponent) => void;
|
134
|
+
export type OnDetermineExportRectListener = (exportRect: Rect2) => void;
|
135
135
|
export interface ImageLoader {
|
136
136
|
start(onAddComponent: ComponentAddedListener, onProgressListener: OnProgressListener, onDetermineExportRect?: OnDetermineExportRectListener): Promise<void>;
|
137
137
|
}
|
package/package.json
CHANGED
@@ -70,6 +70,7 @@ export default abstract class AbstractComponent {
|
|
70
70
|
public getZIndex(): number {
|
71
71
|
return this.zIndex;
|
72
72
|
}
|
73
|
+
|
73
74
|
public getBBox(): Rect2 {
|
74
75
|
return this.contentBBox;
|
75
76
|
}
|
@@ -94,6 +95,13 @@ export default abstract class AbstractComponent {
|
|
94
95
|
return true;
|
95
96
|
}
|
96
97
|
|
98
|
+
// @returns an approximation of the proportional time it takes to render this component.
|
99
|
+
// This is intended to be a rough estimate, but, for example, a stroke with two points sould have
|
100
|
+
// a renderingWeight approximately twice that of a stroke with one point.
|
101
|
+
public getProportionalRenderingTime(): number {
|
102
|
+
return 1;
|
103
|
+
}
|
104
|
+
|
97
105
|
private static transformElementCommandId = 'transform-element';
|
98
106
|
|
99
107
|
private static UnresolvedTransformElementCommand = class extends SerializableCommand {
|
@@ -88,6 +88,11 @@ export default class ImageComponent extends AbstractComponent {
|
|
88
88
|
canvas.drawImage(this.image);
|
89
89
|
}
|
90
90
|
|
91
|
+
public getProportionalRenderingTime(): number {
|
92
|
+
// Estimate: Equivalent to a stroke with 10 segments.
|
93
|
+
return 10;
|
94
|
+
}
|
95
|
+
|
91
96
|
public intersects(lineSegment: LineSegment2): boolean {
|
92
97
|
const rect = this.getImageRect();
|
93
98
|
const edges = rect.getEdges().map(edge => edge.transformedBy(this.image.transform));
|
package/src/components/Stroke.ts
CHANGED
@@ -15,11 +15,16 @@ export default class Stroke extends AbstractComponent {
|
|
15
15
|
private parts: StrokePart[];
|
16
16
|
protected contentBBox: Rect2;
|
17
17
|
|
18
|
+
// See `getProportionalRenderingTime`
|
19
|
+
private approximateRenderingTime: number;
|
20
|
+
|
18
21
|
// Creates a `Stroke` from the given `parts`.
|
19
22
|
public constructor(parts: RenderablePathSpec[]) {
|
20
23
|
super('stroke');
|
21
24
|
|
25
|
+
this.approximateRenderingTime = 0;
|
22
26
|
this.parts = [];
|
27
|
+
|
23
28
|
for (const section of parts) {
|
24
29
|
const path = Path.fromRenderable(section);
|
25
30
|
const pathBBox = this.bboxForPart(path.bbox, section.style);
|
@@ -38,6 +43,8 @@ export default class Stroke extends AbstractComponent {
|
|
38
43
|
style: section.style,
|
39
44
|
commands: path.parts,
|
40
45
|
});
|
46
|
+
|
47
|
+
this.approximateRenderingTime += path.parts.length;
|
41
48
|
}
|
42
49
|
this.contentBBox ??= Rect2.empty;
|
43
50
|
}
|
@@ -71,6 +78,10 @@ export default class Stroke extends AbstractComponent {
|
|
71
78
|
canvas.endObject(this.getLoadSaveData());
|
72
79
|
}
|
73
80
|
|
81
|
+
public getProportionalRenderingTime(): number {
|
82
|
+
return this.approximateRenderingTime;
|
83
|
+
}
|
84
|
+
|
74
85
|
// Grows the bounding box for a given stroke part based on that part's style.
|
75
86
|
private bboxForPart(origBBox: Rect2, style: RenderingStyle) {
|
76
87
|
if (!style.stroke) {
|
@@ -121,6 +121,10 @@ export default class TextComponent extends AbstractComponent {
|
|
121
121
|
canvas.endObject(this.getLoadSaveData());
|
122
122
|
}
|
123
123
|
|
124
|
+
public getProportionalRenderingTime(): number {
|
125
|
+
return this.textObjects.length;
|
126
|
+
}
|
127
|
+
|
124
128
|
public intersects(lineSegment: LineSegment2): boolean {
|
125
129
|
|
126
130
|
// Convert canvas space to internal space.
|
@@ -55,7 +55,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
55
55
|
fill: Color4.transparent,
|
56
56
|
stroke: {
|
57
57
|
color: this.startPoint.color,
|
58
|
-
width: this.roundDistance(this.averageWidth
|
58
|
+
width: this.roundDistance(this.averageWidth),
|
59
59
|
}
|
60
60
|
};
|
61
61
|
}
|
@@ -108,7 +108,7 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
108
108
|
}
|
109
109
|
|
110
110
|
private getMinFit(): number {
|
111
|
-
let minFit = Math.min(this.minFitAllowed, this.averageWidth /
|
111
|
+
let minFit = Math.min(this.minFitAllowed, this.averageWidth / 3);
|
112
112
|
|
113
113
|
if (minFit < 1e-10) {
|
114
114
|
minFit = this.minFitAllowed;
|
@@ -135,7 +135,8 @@ export default class FreehandLineBuilder implements ComponentBuilder {
|
|
135
135
|
return [];
|
136
136
|
}
|
137
137
|
|
138
|
-
|
138
|
+
// Make the circle small -- because of the stroke style, we'll be drawing a stroke around it.
|
139
|
+
const width = Viewport.roundPoint(this.averageWidth / 10, Math.min(this.minFitAllowed, this.averageWidth / 10));
|
139
140
|
const center = this.roundPoint(this.startPoint.pos);
|
140
141
|
|
141
142
|
// Start on the right, cycle clockwise:
|
@@ -290,7 +290,7 @@ export default class PressureSensitiveFreehandLineBuilder implements ComponentBu
|
|
290
290
|
return;
|
291
291
|
}
|
292
292
|
|
293
|
-
const width = Viewport.roundPoint(this.startPoint.width /
|
293
|
+
const width = Viewport.roundPoint(this.startPoint.width / 2.2, Math.min(this.minFitAllowed, this.startPoint.width / 4));
|
294
294
|
const center = this.roundPoint(this.startPoint.pos);
|
295
295
|
|
296
296
|
// Start on the right, cycle clockwise:
|
@@ -98,6 +98,8 @@ export class StrokeSmoother {
|
|
98
98
|
this.buffer[this.buffer.length - 2], lastPoint,
|
99
99
|
];
|
100
100
|
this.currentCurve = null;
|
101
|
+
|
102
|
+
this.isFirstSegment = false;
|
101
103
|
}
|
102
104
|
|
103
105
|
// Returns [upper curve, connector, lower curve]
|
@@ -161,29 +163,30 @@ export class StrokeSmoother {
|
|
161
163
|
this.lastPoint = newPoint;
|
162
164
|
|
163
165
|
this.buffer.push(newPoint.pos);
|
164
|
-
const pointRadius = newPoint.width
|
166
|
+
const pointRadius = newPoint.width;
|
165
167
|
const prevEndWidth = this.curveEndWidth;
|
166
168
|
this.curveEndWidth = pointRadius;
|
167
169
|
|
168
|
-
if (this.isFirstSegment) {
|
169
|
-
// The start of a curve often lacks accurate pressure information. Update it.
|
170
|
-
this.curveStartWidth = (this.curveStartWidth + pointRadius) / 2;
|
171
|
-
}
|
172
|
-
|
173
170
|
// recompute bbox
|
174
171
|
this.bbox = this.bbox.grownToPoint(newPoint.pos, pointRadius);
|
175
172
|
|
173
|
+
// If the last curve just ended or it's the first curve,
|
176
174
|
if (this.currentCurve === null) {
|
177
175
|
const p1 = lastPoint.pos;
|
178
176
|
const p2 = lastPoint.pos.plus(this.lastExitingVec ?? Vec2.unitX);
|
179
177
|
const p3 = newPoint.pos;
|
180
178
|
|
181
179
|
// Quadratic Bézier curve
|
182
|
-
this.currentCurve = new Bezier(
|
183
|
-
p1.xy, p2.xy, p3.xy
|
184
|
-
);
|
185
|
-
this.curveStartWidth = lastPoint.width / 2;
|
180
|
+
this.currentCurve = new Bezier(p1.xy, p2.xy, p3.xy);
|
186
181
|
console.assert(!isNaN(p1.magnitude()) && !isNaN(p2.magnitude()) && !isNaN(p3.magnitude()), 'Expected !NaN');
|
182
|
+
|
183
|
+
if (this.isFirstSegment) {
|
184
|
+
// The start of a curve often lacks accurate pressure information. Update it.
|
185
|
+
this.curveStartWidth = (this.curveStartWidth + pointRadius) / 2;
|
186
|
+
}
|
187
|
+
else {
|
188
|
+
this.curveStartWidth = prevEndWidth;
|
189
|
+
}
|
187
190
|
}
|
188
191
|
|
189
192
|
// If there isn't an entering vector (e.g. because this.isFirstCurve), approximate it.
|
@@ -271,7 +274,7 @@ export class StrokeSmoother {
|
|
271
274
|
return true;
|
272
275
|
};
|
273
276
|
|
274
|
-
if (this.buffer.length > 3 && this.approxCurrentCurveLength() > this.curveStartWidth) {
|
277
|
+
if (this.buffer.length > 3 && this.approxCurrentCurveLength() > this.curveStartWidth / 2) {
|
275
278
|
if (!curveMatchesPoints(this.currentCurve)) {
|
276
279
|
// Use a curve that better fits the points
|
277
280
|
this.currentCurve = prevCurve;
|
package/src/math/Mat33.ts
CHANGED
@@ -219,6 +219,15 @@ export default class Mat33 {
|
|
219
219
|
);
|
220
220
|
}
|
221
221
|
|
222
|
+
/** @returns true iff this is the identity matrix. */
|
223
|
+
public isIdentity(): boolean {
|
224
|
+
if (this === Mat33.identity) {
|
225
|
+
return true;
|
226
|
+
}
|
227
|
+
|
228
|
+
return this.eq(Mat33.identity);
|
229
|
+
}
|
230
|
+
|
222
231
|
/** Returns true iff this = other ± fuzz */
|
223
232
|
public eq(other: Mat33, fuzz: number = 0): boolean {
|
224
233
|
for (let i = 0; i < 3; i++) {
|
package/src/math/Path.ts
CHANGED
package/src/rendering/Display.ts
CHANGED
@@ -78,8 +78,13 @@ export default class Display {
|
|
78
78
|
blockResolution: cacheBlockResolution,
|
79
79
|
cacheSize: 600 * 600 * 4 * 90,
|
80
80
|
maxScale: 1.4,
|
81
|
-
|
82
|
-
|
81
|
+
|
82
|
+
// Require about 20 strokes with 4 parts each to cache an image in one of the
|
83
|
+
// parts of the cache grid.
|
84
|
+
minProportionalRenderTimePerCache: 20 * 4,
|
85
|
+
|
86
|
+
// Require about 105 strokes with 4 parts each to use the cache at all.
|
87
|
+
minProportionalRenderTimeToUseCache: 105 * 4,
|
83
88
|
});
|
84
89
|
|
85
90
|
this.editor.notifier.on(EditorEventType.DisplayResized, event => {
|
@@ -48,8 +48,16 @@ export default class RenderingCache {
|
|
48
48
|
|
49
49
|
this.rootNode = this.rootNode!.smallestChildContaining(visibleRect) ?? this.rootNode;
|
50
50
|
|
51
|
-
const visibleLeaves = image.getLeavesIntersectingRegion(
|
52
|
-
|
51
|
+
const visibleLeaves = image.getLeavesIntersectingRegion(
|
52
|
+
viewport.visibleRect, rect => screenRenderer.isTooSmallToRender(rect)
|
53
|
+
);
|
54
|
+
|
55
|
+
let approxVisibleRenderTime = 0;
|
56
|
+
for (const leaf of visibleLeaves) {
|
57
|
+
approxVisibleRenderTime += leaf.getContent()!.getProportionalRenderingTime();
|
58
|
+
}
|
59
|
+
|
60
|
+
if (approxVisibleRenderTime > this.sharedState.props.minProportionalRenderTimeToUseCache) {
|
53
61
|
this.rootNode!.renderItems(screenRenderer, [ image ], viewport);
|
54
62
|
} else {
|
55
63
|
image.render(screenRenderer, visibleRect);
|
@@ -250,9 +250,13 @@ export default class RenderingCacheNode {
|
|
250
250
|
return;
|
251
251
|
}
|
252
252
|
|
253
|
+
let leafApproxRenderTime = 0;
|
254
|
+
for (const leaf of leavesByIds) {
|
255
|
+
leafApproxRenderTime += leaf.getContent()!.getProportionalRenderingTime();
|
256
|
+
}
|
257
|
+
|
253
258
|
// Is it worth it to render the items?
|
254
|
-
|
255
|
-
if (leavesByIds.length > this.cacheState.props.minComponentsPerCache) {
|
259
|
+
if (leafApproxRenderTime > this.cacheState.props.minProportionalRenderTimePerCache) {
|
256
260
|
let fullRerenderNeeded = true;
|
257
261
|
if (!this.cachedRenderer) {
|
258
262
|
this.cachedRenderer = this.cacheState.recordManager.allocCanvas(
|
@@ -23,8 +23,8 @@ export const createCache = (onRenderAlloc?: RenderAllocCallback, cacheOptions?:
|
|
23
23
|
blockResolution: Vec2.of(500, 500),
|
24
24
|
cacheSize: 500 * 10 * 4,
|
25
25
|
maxScale: 2,
|
26
|
-
|
27
|
-
|
26
|
+
minProportionalRenderTimePerCache: 0,
|
27
|
+
minProportionalRenderTimeToUseCache: 0,
|
28
28
|
...cacheOptions
|
29
29
|
});
|
30
30
|
|
@@ -20,11 +20,11 @@ export interface CacheProps {
|
|
20
20
|
maxScale: number;
|
21
21
|
|
22
22
|
// Minimum component count to cache, rather than just re-render each time.
|
23
|
-
|
23
|
+
minProportionalRenderTimePerCache: number;
|
24
24
|
|
25
25
|
// Minimum number of strokes/etc. to use the cache to render, isntead of
|
26
26
|
// rendering directly.
|
27
|
-
|
27
|
+
minProportionalRenderTimeToUseCache: number;
|
28
28
|
}
|
29
29
|
|
30
30
|
export interface CacheState {
|
@@ -347,7 +347,7 @@ export default class IconProvider {
|
|
347
347
|
return icon;
|
348
348
|
}
|
349
349
|
|
350
|
-
public makePenIcon(tipThickness: number, color: string|Color4): IconType {
|
350
|
+
public makePenIcon(tipThickness: number, color: string|Color4, roundedTip?: boolean): IconType {
|
351
351
|
if (color instanceof Color4) {
|
352
352
|
color = color.toHexString();
|
353
353
|
}
|
@@ -358,8 +358,20 @@ export default class IconProvider {
|
|
358
358
|
const halfThickness = tipThickness / 2;
|
359
359
|
|
360
360
|
// Draw a pen-like shape
|
361
|
-
const
|
362
|
-
const
|
361
|
+
const penTipLeft = 50 - halfThickness;
|
362
|
+
const penTipRight = 50 + halfThickness;
|
363
|
+
|
364
|
+
let tipCenterPrimaryPath = `L${penTipLeft},95 L${penTipRight},90`;
|
365
|
+
let tipCenterBackgroundPath = `L${penTipLeft},85 L${penTipRight},83`;
|
366
|
+
|
367
|
+
if (roundedTip) {
|
368
|
+
tipCenterPrimaryPath = `L${penTipLeft},95 q${halfThickness},10 ${2 * halfThickness},-5`;
|
369
|
+
tipCenterBackgroundPath = `L${penTipLeft},87 q${halfThickness},10 ${2 * halfThickness},-3`;
|
370
|
+
}
|
371
|
+
|
372
|
+
const primaryStrokeTipPath = `M14,63 ${tipCenterPrimaryPath} L88,60 Z`;
|
373
|
+
const backgroundStrokeTipPath = `M14,63 ${tipCenterBackgroundPath} L88,60 Z`;
|
374
|
+
|
363
375
|
icon.innerHTML = `
|
364
376
|
<defs>
|
365
377
|
${checkerboardPatternDef}
|
@@ -490,6 +502,19 @@ export default class IconProvider {
|
|
490
502
|
M 10,25 10,90 70,90 70,60 40,60 40,25 10,25 z
|
491
503
|
`);
|
492
504
|
}
|
505
|
+
|
506
|
+
public makePasteIcon(): IconType {
|
507
|
+
const icon = this.makeIconFromPath(`
|
508
|
+
M 50 0 L 50 5 L 35 5 L 40 24.75 L 20 25 L 20 100 L 85 100 L 100 90 L 100 24 L 75.1 24.3 L 80 5 L 65 5 L 65 0 L 50 0 z
|
509
|
+
M 10 15 L 10 115 L 110 115 L 110 15 L 85 15 L 83 20 L 105 20 L 105 110 L 15 110 L 15 20 L 32 20 L 30 15 L 10 15 z
|
510
|
+
M 25 35 L 90 35 L 90 40 L 25 40 L 25 35 z
|
511
|
+
M 25 45 L 90 45 L 90 50 L 25 50 L 25 45 z
|
512
|
+
M 25 55 L 85 55 L 85 60 L 25 60 L 25 55 z
|
513
|
+
M 25 65 L 90 65 L 90 70 L 25 70 L 25 65 z
|
514
|
+
`);
|
515
|
+
icon.setAttribute('viewBox', '0 0 120 120');
|
516
|
+
return icon;
|
517
|
+
}
|
493
518
|
|
494
519
|
public makeDeleteSelectionIcon(): IconType {
|
495
520
|
const strokeWidth = '5px';
|
@@ -28,6 +28,7 @@ export interface ToolbarLocalization {
|
|
28
28
|
zoom: string;
|
29
29
|
resetView: string;
|
30
30
|
selectionToolKeyboardShortcuts: string;
|
31
|
+
paste: string;
|
31
32
|
|
32
33
|
dropdownShown: (toolName: string)=> string;
|
33
34
|
dropdownHidden: (toolName: string)=> string;
|
@@ -66,6 +67,8 @@ export const defaultToolbarLocalization: ToolbarLocalization = {
|
|
66
67
|
filledRectanglePen: 'Filled rectangle',
|
67
68
|
lockRotation: 'Lock rotation',
|
68
69
|
|
70
|
+
paste: 'Paste',
|
71
|
+
|
69
72
|
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
70
73
|
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
71
74
|
zoomLevel: (zoomPercent: number) => `Zoom: ${zoomPercent}%`,
|
@@ -122,7 +122,9 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
122
122
|
// Use a square-root scale to prevent the pen's tip from overflowing.
|
123
123
|
const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 4);
|
124
124
|
const color = this.tool.getColor();
|
125
|
-
|
125
|
+
const roundedTip = strokeFactory === makeFreehandLineBuilder;
|
126
|
+
|
127
|
+
return this.editor.icons.makePenIcon(scale, color.toHexString(), roundedTip);
|
126
128
|
} else {
|
127
129
|
const strokeFactory = this.tool.getStrokeFactory();
|
128
130
|
return this.editor.icons.makeIconFromFactory(this.tool, strokeFactory);
|
package/src/tools/PanZoom.ts
CHANGED
@@ -143,15 +143,22 @@ export default class PanZoom extends BaseTool {
|
|
143
143
|
private updateVelocity(currentCenter: Point2) {
|
144
144
|
const deltaPos = currentCenter.minus(this.lastScreenCenter);
|
145
145
|
const deltaTime = ((new Date()).getTime() - this.lastTimestamp) / 1000;
|
146
|
-
const currentVelocity = deltaPos.times(1 / deltaTime);
|
147
|
-
let smoothedVelocity = currentVelocity;
|
148
146
|
|
147
|
+
// We divide by deltaTime. Don't divide by zero.
|
149
148
|
if (deltaTime === 0) {
|
150
149
|
return;
|
151
150
|
}
|
152
151
|
|
152
|
+
// Ignore duplicate events, unless there has been enough time between them.
|
153
|
+
if (deltaPos.magnitude() === 0 && deltaTime < 0.1) {
|
154
|
+
return;
|
155
|
+
}
|
156
|
+
|
157
|
+
const currentVelocity = deltaPos.times(1 / deltaTime);
|
158
|
+
let smoothedVelocity = currentVelocity;
|
159
|
+
|
153
160
|
if (this.velocity) {
|
154
|
-
smoothedVelocity = this.velocity.lerp(
|
161
|
+
smoothedVelocity = this.velocity.lerp(currentVelocity, 0.5);
|
155
162
|
}
|
156
163
|
|
157
164
|
this.velocity = smoothedVelocity;
|
@@ -230,6 +237,12 @@ export default class PanZoom extends BaseTool {
|
|
230
237
|
event.current.device === PointerDevice.Touch && event.allPointers.length === 1;
|
231
238
|
|
232
239
|
if (shouldInertialScroll && this.velocity !== null) {
|
240
|
+
// If the user drags the screen, then stops, then lifts the pointer,
|
241
|
+
// we want the final velocity to reflect the stop at the end (so the velocity
|
242
|
+
// should be near zero). Handle this:
|
243
|
+
this.updateVelocity(event.current.screenPos);
|
244
|
+
|
245
|
+
// Cancel any ongoing inertial scrolling.
|
233
246
|
this.inertialScroller?.stop();
|
234
247
|
|
235
248
|
this.inertialScroller = new InertialScroller(this.velocity, (scrollDelta: Vec2) => {
|