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
package/dist/src/Editor.d.ts
CHANGED
@@ -29,8 +29,8 @@ import Pointer from './Pointer';
|
|
29
29
|
import Rect2 from './math/Rect2';
|
30
30
|
import { EditorLocalization } from './localization';
|
31
31
|
import IconProvider from './toolbar/IconProvider';
|
32
|
-
|
33
|
-
|
32
|
+
type HTMLPointerEventType = 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel';
|
33
|
+
type HTMLPointerEventFilter = (eventName: HTMLPointerEventType, event: PointerEvent) => boolean;
|
34
34
|
export interface EditorSettings {
|
35
35
|
/** Defaults to `RenderingMode.CanvasRenderer` */
|
36
36
|
renderingMode: RenderingMode;
|
@@ -27,7 +27,7 @@ export default class EditorImage {
|
|
27
27
|
static addElement(elem: AbstractComponent, applyByFlattening?: boolean): SerializableCommand;
|
28
28
|
private static AddElementCommand;
|
29
29
|
}
|
30
|
-
|
30
|
+
type TooSmallToRenderCheck = (rect: Rect2) => boolean;
|
31
31
|
/** Part of the Editor's image. @internal */
|
32
32
|
export declare class ImageNode {
|
33
33
|
private parent;
|
@@ -15,7 +15,7 @@
|
|
15
15
|
*
|
16
16
|
* @packageDocumentation
|
17
17
|
*/
|
18
|
-
|
18
|
+
type CallbackHandler<EventType> = (data: EventType) => void;
|
19
19
|
export default class EventDispatcher<EventKeyType extends string | symbol | number, EventMessageType> {
|
20
20
|
private listeners;
|
21
21
|
constructor();
|
package/dist/src/SVGLoader.d.ts
CHANGED
@@ -3,8 +3,8 @@ import { ComponentAddedListener, ImageLoader, OnDetermineExportRectListener, OnP
|
|
3
3
|
export declare const defaultSVGViewRect: Rect2;
|
4
4
|
export declare const svgAttributesDataKey = "svgAttrs";
|
5
5
|
export declare const svgStyleAttributesDataKey = "svgStyleAttrs";
|
6
|
-
export
|
7
|
-
export
|
6
|
+
export type SVGLoaderUnknownAttribute = [string, string];
|
7
|
+
export type SVGLoaderUnknownStyleAttribute = {
|
8
8
|
key: string;
|
9
9
|
value: string;
|
10
10
|
priority?: string;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import Editor from './Editor';
|
2
2
|
import Command from './commands/Command';
|
3
|
-
|
4
|
-
|
3
|
+
type AnnounceRedoCallback = (command: Command) => void;
|
4
|
+
type AnnounceUndoCallback = (command: Command) => void;
|
5
5
|
declare class UndoRedoHistory {
|
6
6
|
private readonly editor;
|
7
7
|
private announceRedoCallback;
|
package/dist/src/Viewport.d.ts
CHANGED
@@ -4,7 +4,7 @@ import Rect2 from './math/Rect2';
|
|
4
4
|
import { Point2, Vec2 } from './math/Vec2';
|
5
5
|
import { StrokeDataPoint } from './types';
|
6
6
|
import { EditorNotifier } from './types';
|
7
|
-
|
7
|
+
type PointDataType<T extends Point2 | StrokeDataPoint | number> = T extends Point2 ? Point2 : number;
|
8
8
|
export declare abstract class ViewportTransform extends Command {
|
9
9
|
abstract readonly transform: Mat33;
|
10
10
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import Editor from '../Editor';
|
2
2
|
import Command from './Command';
|
3
|
-
export
|
3
|
+
export type DeserializationCallback = (data: Record<string, any> | any[], editor: Editor) => SerializableCommand;
|
4
4
|
export default abstract class SerializableCommand extends Command {
|
5
5
|
private commandTypeId;
|
6
6
|
constructor(commandTypeId: string);
|
@@ -4,9 +4,9 @@ import Mat33 from '../math/Mat33';
|
|
4
4
|
import Rect2 from '../math/Rect2';
|
5
5
|
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
6
6
|
import { ImageComponentLocalization } from './localization';
|
7
|
-
export
|
8
|
-
export
|
9
|
-
export
|
7
|
+
export type LoadSaveData = (string[] | Record<symbol, string | number>);
|
8
|
+
export type LoadSaveDataTable = Record<string, Array<LoadSaveData>>;
|
9
|
+
export type DeserializeCallback = (data: string) => AbstractComponent;
|
10
10
|
export default abstract class AbstractComponent {
|
11
11
|
private readonly componentKind;
|
12
12
|
protected lastChangedTime: number;
|
@@ -29,6 +29,7 @@ export default abstract class AbstractComponent {
|
|
29
29
|
protected abstract applyTransformation(affineTransfm: Mat33): void;
|
30
30
|
transformBy(affineTransfm: Mat33): SerializableCommand;
|
31
31
|
isSelectable(): boolean;
|
32
|
+
getProportionalRenderingTime(): number;
|
32
33
|
private static transformElementCommandId;
|
33
34
|
private static UnresolvedTransformElementCommand;
|
34
35
|
private static TransformElementCommand;
|
@@ -52,6 +52,12 @@ export default class AbstractComponent {
|
|
52
52
|
isSelectable() {
|
53
53
|
return true;
|
54
54
|
}
|
55
|
+
// @returns an approximation of the proportional time it takes to render this component.
|
56
|
+
// This is intended to be a rough estimate, but, for example, a stroke with two points sould have
|
57
|
+
// a renderingWeight approximately twice that of a stroke with one point.
|
58
|
+
getProportionalRenderingTime() {
|
59
|
+
return 1;
|
60
|
+
}
|
55
61
|
// Returns a copy of this component.
|
56
62
|
clone() {
|
57
63
|
const clone = this.createClone();
|
@@ -12,6 +12,7 @@ export default class ImageComponent extends AbstractComponent {
|
|
12
12
|
private recomputeBBox;
|
13
13
|
static fromImage(elem: HTMLImageElement, transform: Mat33): Promise<ImageComponent>;
|
14
14
|
render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
|
15
|
+
getProportionalRenderingTime(): number;
|
15
16
|
intersects(lineSegment: LineSegment2): boolean;
|
16
17
|
protected serializeToJSON(): {
|
17
18
|
src: string;
|
@@ -80,6 +80,10 @@ export default class ImageComponent extends AbstractComponent {
|
|
80
80
|
render(canvas, _visibleRect) {
|
81
81
|
canvas.drawImage(this.image);
|
82
82
|
}
|
83
|
+
getProportionalRenderingTime() {
|
84
|
+
// Estimate: Equivalent to a stroke with 10 segments.
|
85
|
+
return 10;
|
86
|
+
}
|
83
87
|
intersects(lineSegment) {
|
84
88
|
const rect = this.getImageRect();
|
85
89
|
const edges = rect.getEdges().map(edge => edge.transformedBy(this.image.transform));
|
@@ -4,7 +4,7 @@ import Rect2 from '../math/Rect2';
|
|
4
4
|
import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
5
5
|
import AbstractComponent from './AbstractComponent';
|
6
6
|
import { ImageComponentLocalization } from './localization';
|
7
|
-
|
7
|
+
type GlobalAttrsList = Array<[string, string | null]>;
|
8
8
|
export default class SVGGlobalAttributesObject extends AbstractComponent {
|
9
9
|
private readonly attrs;
|
10
10
|
protected contentBBox: Rect2;
|
@@ -8,9 +8,11 @@ import { ImageComponentLocalization } from './localization';
|
|
8
8
|
export default class Stroke extends AbstractComponent {
|
9
9
|
private parts;
|
10
10
|
protected contentBBox: Rect2;
|
11
|
+
private approximateRenderingTime;
|
11
12
|
constructor(parts: RenderablePathSpec[]);
|
12
13
|
intersects(line: LineSegment2): boolean;
|
13
14
|
render(canvas: AbstractRenderer, visibleRect?: Rect2): void;
|
15
|
+
getProportionalRenderingTime(): number;
|
14
16
|
private bboxForPart;
|
15
17
|
protected applyTransformation(affineTransfm: Mat33): void;
|
16
18
|
getPath(): Path;
|
@@ -7,6 +7,7 @@ export default class Stroke extends AbstractComponent {
|
|
7
7
|
constructor(parts) {
|
8
8
|
var _a;
|
9
9
|
super('stroke');
|
10
|
+
this.approximateRenderingTime = 0;
|
10
11
|
this.parts = [];
|
11
12
|
for (const section of parts) {
|
12
13
|
const path = Path.fromRenderable(section);
|
@@ -24,6 +25,7 @@ export default class Stroke extends AbstractComponent {
|
|
24
25
|
style: section.style,
|
25
26
|
commands: path.parts,
|
26
27
|
});
|
28
|
+
this.approximateRenderingTime += path.parts.length;
|
27
29
|
}
|
28
30
|
(_a = this.contentBBox) !== null && _a !== void 0 ? _a : (this.contentBBox = Rect2.empty);
|
29
31
|
}
|
@@ -53,6 +55,9 @@ export default class Stroke extends AbstractComponent {
|
|
53
55
|
}
|
54
56
|
canvas.endObject(this.getLoadSaveData());
|
55
57
|
}
|
58
|
+
getProportionalRenderingTime() {
|
59
|
+
return this.approximateRenderingTime;
|
60
|
+
}
|
56
61
|
// Grows the bounding box for a given stroke part based on that part's style.
|
57
62
|
bboxForPart(origBBox, style) {
|
58
63
|
if (!style.stroke) {
|
@@ -26,6 +26,7 @@ export default class TextComponent extends AbstractComponent {
|
|
26
26
|
private recomputeBBox;
|
27
27
|
private renderInternal;
|
28
28
|
render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
|
29
|
+
getProportionalRenderingTime(): number;
|
29
30
|
intersects(lineSegment: LineSegment2): boolean;
|
30
31
|
getBaselinePos(): import("../lib").Vec3;
|
31
32
|
getTextStyle(): TextStyle;
|
@@ -91,6 +91,9 @@ export default class TextComponent extends AbstractComponent {
|
|
91
91
|
this.renderInternal(canvas);
|
92
92
|
canvas.endObject(this.getLoadSaveData());
|
93
93
|
}
|
94
|
+
getProportionalRenderingTime() {
|
95
|
+
return this.textObjects.length;
|
96
|
+
}
|
94
97
|
intersects(lineSegment) {
|
95
98
|
// Convert canvas space to internal space.
|
96
99
|
const invTransform = this.transform.inverse();
|
@@ -33,7 +33,7 @@ export default class FreehandLineBuilder {
|
|
33
33
|
fill: Color4.transparent,
|
34
34
|
stroke: {
|
35
35
|
color: this.startPoint.color,
|
36
|
-
width: this.roundDistance(this.averageWidth
|
36
|
+
width: this.roundDistance(this.averageWidth),
|
37
37
|
}
|
38
38
|
};
|
39
39
|
}
|
@@ -77,7 +77,7 @@ export default class FreehandLineBuilder {
|
|
77
77
|
return this.previewStroke();
|
78
78
|
}
|
79
79
|
getMinFit() {
|
80
|
-
let minFit = Math.min(this.minFitAllowed, this.averageWidth /
|
80
|
+
let minFit = Math.min(this.minFitAllowed, this.averageWidth / 3);
|
81
81
|
if (minFit < 1e-10) {
|
82
82
|
minFit = this.minFitAllowed;
|
83
83
|
}
|
@@ -98,7 +98,8 @@ export default class FreehandLineBuilder {
|
|
98
98
|
if (!this.isFirstSegment) {
|
99
99
|
return [];
|
100
100
|
}
|
101
|
-
|
101
|
+
// Make the circle small -- because of the stroke style, we'll be drawing a stroke around it.
|
102
|
+
const width = Viewport.roundPoint(this.averageWidth / 10, Math.min(this.minFitAllowed, this.averageWidth / 10));
|
102
103
|
const center = this.roundPoint(this.startPoint.pos);
|
103
104
|
// Start on the right, cycle clockwise:
|
104
105
|
// |
|
@@ -209,7 +209,7 @@ export default class PressureSensitiveFreehandLineBuilder {
|
|
209
209
|
if (!this.isFirstSegment) {
|
210
210
|
return;
|
211
211
|
}
|
212
|
-
const width = Viewport.roundPoint(this.startPoint.width /
|
212
|
+
const width = Viewport.roundPoint(this.startPoint.width / 2.2, Math.min(this.minFitAllowed, this.startPoint.width / 4));
|
213
213
|
const center = this.roundPoint(this.startPoint.pos);
|
214
214
|
// Start on the right, cycle clockwise:
|
215
215
|
// |
|
@@ -9,4 +9,4 @@ export interface ComponentBuilder {
|
|
9
9
|
preview(renderer: AbstractRenderer): void;
|
10
10
|
addPoint(point: StrokeDataPoint): void;
|
11
11
|
}
|
12
|
-
export
|
12
|
+
export type ComponentBuilderFactory = (startPoint: StrokeDataPoint, viewport: Viewport) => ComponentBuilder;
|
@@ -8,7 +8,7 @@ export interface Curve {
|
|
8
8
|
endWidth: number;
|
9
9
|
endPoint: Vec2;
|
10
10
|
}
|
11
|
-
|
11
|
+
type OnCurveAddedCallback = (curve: Curve | null) => void;
|
12
12
|
export declare class StrokeSmoother {
|
13
13
|
private startPoint;
|
14
14
|
private minFitAllowed;
|
@@ -60,6 +60,7 @@ export class StrokeSmoother {
|
|
60
60
|
this.buffer[this.buffer.length - 2], lastPoint,
|
61
61
|
];
|
62
62
|
this.currentCurve = null;
|
63
|
+
this.isFirstSegment = false;
|
63
64
|
}
|
64
65
|
// Returns [upper curve, connector, lower curve]
|
65
66
|
currentSegmentToPath() {
|
@@ -113,23 +114,26 @@ export class StrokeSmoother {
|
|
113
114
|
const lastPoint = (_a = this.lastPoint) !== null && _a !== void 0 ? _a : newPoint;
|
114
115
|
this.lastPoint = newPoint;
|
115
116
|
this.buffer.push(newPoint.pos);
|
116
|
-
const pointRadius = newPoint.width
|
117
|
+
const pointRadius = newPoint.width;
|
117
118
|
const prevEndWidth = this.curveEndWidth;
|
118
119
|
this.curveEndWidth = pointRadius;
|
119
|
-
if (this.isFirstSegment) {
|
120
|
-
// The start of a curve often lacks accurate pressure information. Update it.
|
121
|
-
this.curveStartWidth = (this.curveStartWidth + pointRadius) / 2;
|
122
|
-
}
|
123
120
|
// recompute bbox
|
124
121
|
this.bbox = this.bbox.grownToPoint(newPoint.pos, pointRadius);
|
122
|
+
// If the last curve just ended or it's the first curve,
|
125
123
|
if (this.currentCurve === null) {
|
126
124
|
const p1 = lastPoint.pos;
|
127
125
|
const p2 = lastPoint.pos.plus((_b = this.lastExitingVec) !== null && _b !== void 0 ? _b : Vec2.unitX);
|
128
126
|
const p3 = newPoint.pos;
|
129
127
|
// Quadratic Bézier curve
|
130
128
|
this.currentCurve = new Bezier(p1.xy, p2.xy, p3.xy);
|
131
|
-
this.curveStartWidth = lastPoint.width / 2;
|
132
129
|
console.assert(!isNaN(p1.magnitude()) && !isNaN(p2.magnitude()) && !isNaN(p3.magnitude()), 'Expected !NaN');
|
130
|
+
if (this.isFirstSegment) {
|
131
|
+
// The start of a curve often lacks accurate pressure information. Update it.
|
132
|
+
this.curveStartWidth = (this.curveStartWidth + pointRadius) / 2;
|
133
|
+
}
|
134
|
+
else {
|
135
|
+
this.curveStartWidth = prevEndWidth;
|
136
|
+
}
|
133
137
|
}
|
134
138
|
// If there isn't an entering vector (e.g. because this.isFirstCurve), approximate it.
|
135
139
|
let enteringVec = this.lastExitingVec;
|
@@ -190,7 +194,7 @@ export class StrokeSmoother {
|
|
190
194
|
}
|
191
195
|
return true;
|
192
196
|
};
|
193
|
-
if (this.buffer.length > 3 && this.approxCurrentCurveLength() > this.curveStartWidth) {
|
197
|
+
if (this.buffer.length > 3 && this.approxCurrentCurveLength() > this.curveStartWidth / 2) {
|
194
198
|
if (!curveMatchesPoints(this.currentCurve)) {
|
195
199
|
// Use a curve that better fits the points
|
196
200
|
this.currentCurve = prevCurve;
|
package/dist/src/math/Mat33.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Point2, Vec2 } from './Vec2';
|
2
2
|
import Vec3 from './Vec3';
|
3
|
-
export
|
3
|
+
export type Mat33Array = [
|
4
4
|
number,
|
5
5
|
number,
|
6
6
|
number,
|
@@ -69,6 +69,8 @@ export default class Mat33 {
|
|
69
69
|
* This is the standard way of transforming vectors in ℝ³.
|
70
70
|
*/
|
71
71
|
transformVec3(other: Vec3): Vec3;
|
72
|
+
/** @returns true iff this is the identity matrix. */
|
73
|
+
isIdentity(): boolean;
|
72
74
|
/** Returns true iff this = other ± fuzz */
|
73
75
|
eq(other: Mat33, fuzz?: number): boolean;
|
74
76
|
toString(): string;
|
package/dist/src/math/Mat33.js
CHANGED
@@ -153,6 +153,13 @@ export default class Mat33 {
|
|
153
153
|
transformVec3(other) {
|
154
154
|
return Vec3.of(this.rows[0].dot(other), this.rows[1].dot(other), this.rows[2].dot(other));
|
155
155
|
}
|
156
|
+
/** @returns true iff this is the identity matrix. */
|
157
|
+
isIdentity() {
|
158
|
+
if (this === Mat33.identity) {
|
159
|
+
return true;
|
160
|
+
}
|
161
|
+
return this.eq(Mat33.identity);
|
162
|
+
}
|
156
163
|
/** Returns true iff this = other ± fuzz */
|
157
164
|
eq(other, fuzz = 0) {
|
158
165
|
for (let i = 0; i < 3; i++) {
|
package/dist/src/math/Path.d.ts
CHANGED
@@ -30,7 +30,7 @@ export interface MoveToPathCommand {
|
|
30
30
|
kind: PathCommandType.MoveTo;
|
31
31
|
point: Point2;
|
32
32
|
}
|
33
|
-
export
|
33
|
+
export type PathCommand = CubicBezierPathCommand | LinePathCommand | QuadraticBezierPathCommand | MoveToPathCommand;
|
34
34
|
interface IntersectionResult {
|
35
35
|
curve: LineSegment2 | Bezier;
|
36
36
|
parameterValue: number;
|
package/dist/src/math/Path.js
CHANGED
@@ -179,6 +179,9 @@ export default class Path {
|
|
179
179
|
return new Path(startPoint, newParts);
|
180
180
|
}
|
181
181
|
transformedBy(affineTransfm) {
|
182
|
+
if (affineTransfm.isIdentity()) {
|
183
|
+
return this;
|
184
|
+
}
|
182
185
|
return this.mapPoints(point => affineTransfm.transformVec2(point));
|
183
186
|
}
|
184
187
|
// Creates a new path by joining [other] to the end of this path
|
package/dist/src/math/Vec2.d.ts
CHANGED
@@ -73,8 +73,11 @@ export default class Display {
|
|
73
73
|
blockResolution: cacheBlockResolution,
|
74
74
|
cacheSize: 600 * 600 * 4 * 90,
|
75
75
|
maxScale: 1.4,
|
76
|
-
|
77
|
-
|
76
|
+
// Require about 20 strokes with 4 parts each to cache an image in one of the
|
77
|
+
// parts of the cache grid.
|
78
|
+
minProportionalRenderTimePerCache: 20 * 4,
|
79
|
+
// Require about 105 strokes with 4 parts each to use the cache at all.
|
80
|
+
minProportionalRenderTimeToUseCache: 105 * 4,
|
78
81
|
});
|
79
82
|
this.editor.notifier.on(EditorEventType.DisplayResized, event => {
|
80
83
|
var _a;
|
@@ -31,7 +31,11 @@ export default class RenderingCache {
|
|
31
31
|
}
|
32
32
|
this.rootNode = (_a = this.rootNode.smallestChildContaining(visibleRect)) !== null && _a !== void 0 ? _a : this.rootNode;
|
33
33
|
const visibleLeaves = image.getLeavesIntersectingRegion(viewport.visibleRect, rect => screenRenderer.isTooSmallToRender(rect));
|
34
|
-
|
34
|
+
let approxVisibleRenderTime = 0;
|
35
|
+
for (const leaf of visibleLeaves) {
|
36
|
+
approxVisibleRenderTime += leaf.getContent().getProportionalRenderingTime();
|
37
|
+
}
|
38
|
+
if (approxVisibleRenderTime > this.sharedState.props.minProportionalRenderTimeToUseCache) {
|
35
39
|
this.rootNode.renderItems(screenRenderer, [image], viewport);
|
36
40
|
}
|
37
41
|
else {
|
@@ -197,9 +197,12 @@ export default class RenderingCacheNode {
|
|
197
197
|
}
|
198
198
|
return;
|
199
199
|
}
|
200
|
+
let leafApproxRenderTime = 0;
|
201
|
+
for (const leaf of leavesByIds) {
|
202
|
+
leafApproxRenderTime += leaf.getContent().getProportionalRenderingTime();
|
203
|
+
}
|
200
204
|
// Is it worth it to render the items?
|
201
|
-
|
202
|
-
if (leavesByIds.length > this.cacheState.props.minComponentsPerCache) {
|
205
|
+
if (leafApproxRenderTime > this.cacheState.props.minProportionalRenderTimePerCache) {
|
203
206
|
let fullRerenderNeeded = true;
|
204
207
|
if (!this.cachedRenderer) {
|
205
208
|
this.cachedRenderer = this.cacheState.recordManager.allocCanvas(this.region, () => this.onRegionDealloc());
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import DummyRenderer from '../renderers/DummyRenderer';
|
2
2
|
import RenderingCache from './RenderingCache';
|
3
3
|
import { CacheProps } from './types';
|
4
|
-
|
4
|
+
type RenderAllocCallback = (renderer: DummyRenderer) => void;
|
5
5
|
export declare const createCache: (onRenderAlloc?: RenderAllocCallback, cacheOptions?: Partial<CacheProps>) => {
|
6
6
|
cache: RenderingCache;
|
7
7
|
editor: import("../../Editor").Editor;
|
@@ -12,7 +12,7 @@ export const createCache = (onRenderAlloc, cacheOptions) => {
|
|
12
12
|
},
|
13
13
|
isOfCorrectType(renderer) {
|
14
14
|
return renderer instanceof DummyRenderer;
|
15
|
-
}, blockResolution: Vec2.of(500, 500), cacheSize: 500 * 10 * 4, maxScale: 2,
|
15
|
+
}, blockResolution: Vec2.of(500, 500), cacheSize: 500 * 10 * 4, maxScale: 2, minProportionalRenderTimePerCache: 0, minProportionalRenderTimeToUseCache: 0 }, cacheOptions));
|
16
16
|
return {
|
17
17
|
cache,
|
18
18
|
editor
|
@@ -1,16 +1,16 @@
|
|
1
1
|
import { Vec2 } from '../../math/Vec2';
|
2
2
|
import AbstractRenderer from '../renderers/AbstractRenderer';
|
3
3
|
import { CacheRecordManager } from './CacheRecordManager';
|
4
|
-
export
|
5
|
-
export
|
4
|
+
export type CacheAddress = number;
|
5
|
+
export type BeforeDeallocCallback = () => void;
|
6
6
|
export interface CacheProps {
|
7
7
|
createRenderer(): AbstractRenderer;
|
8
8
|
isOfCorrectType(renderer: AbstractRenderer): boolean;
|
9
9
|
blockResolution: Vec2;
|
10
10
|
cacheSize: number;
|
11
11
|
maxScale: number;
|
12
|
-
|
13
|
-
|
12
|
+
minProportionalRenderTimePerCache: number;
|
13
|
+
minProportionalRenderTimeToUseCache: number;
|
14
14
|
}
|
15
15
|
export interface CacheState {
|
16
16
|
currentRenderingCycle: number;
|
@@ -2,7 +2,7 @@ import Color4 from '../Color4';
|
|
2
2
|
import { ComponentBuilderFactory } from '../components/builders/types';
|
3
3
|
import { TextStyle } from '../components/TextComponent';
|
4
4
|
import Pen from '../tools/Pen';
|
5
|
-
|
5
|
+
type IconType = SVGSVGElement | HTMLImageElement;
|
6
6
|
export default class IconProvider {
|
7
7
|
makeUndoIcon(): IconType;
|
8
8
|
makeRedoIcon(mirror?: boolean): IconType;
|
@@ -20,11 +20,12 @@ export default class IconProvider {
|
|
20
20
|
makeZoomIcon(): IconType;
|
21
21
|
makeRotationLockIcon(): IconType;
|
22
22
|
makeTextIcon(textStyle: TextStyle): IconType;
|
23
|
-
makePenIcon(tipThickness: number, color: string | Color4): IconType;
|
23
|
+
makePenIcon(tipThickness: number, color: string | Color4, roundedTip?: boolean): IconType;
|
24
24
|
makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType;
|
25
25
|
makePipetteIcon(color?: Color4): IconType;
|
26
26
|
makeResizeViewportIcon(): IconType;
|
27
27
|
makeDuplicateSelectionIcon(): IconType;
|
28
|
+
makePasteIcon(): IconType;
|
28
29
|
makeDeleteSelectionIcon(): IconType;
|
29
30
|
makeSaveIcon(): IconType;
|
30
31
|
}
|
@@ -302,7 +302,7 @@ export default class IconProvider {
|
|
302
302
|
icon.appendChild(textNode);
|
303
303
|
return icon;
|
304
304
|
}
|
305
|
-
makePenIcon(tipThickness, color) {
|
305
|
+
makePenIcon(tipThickness, color, roundedTip) {
|
306
306
|
if (color instanceof Color4) {
|
307
307
|
color = color.toHexString();
|
308
308
|
}
|
@@ -310,8 +310,16 @@ export default class IconProvider {
|
|
310
310
|
icon.setAttribute('viewBox', '0 0 100 100');
|
311
311
|
const halfThickness = tipThickness / 2;
|
312
312
|
// Draw a pen-like shape
|
313
|
-
const
|
314
|
-
const
|
313
|
+
const penTipLeft = 50 - halfThickness;
|
314
|
+
const penTipRight = 50 + halfThickness;
|
315
|
+
let tipCenterPrimaryPath = `L${penTipLeft},95 L${penTipRight},90`;
|
316
|
+
let tipCenterBackgroundPath = `L${penTipLeft},85 L${penTipRight},83`;
|
317
|
+
if (roundedTip) {
|
318
|
+
tipCenterPrimaryPath = `L${penTipLeft},95 q${halfThickness},10 ${2 * halfThickness},-5`;
|
319
|
+
tipCenterBackgroundPath = `L${penTipLeft},87 q${halfThickness},10 ${2 * halfThickness},-3`;
|
320
|
+
}
|
321
|
+
const primaryStrokeTipPath = `M14,63 ${tipCenterPrimaryPath} L88,60 Z`;
|
322
|
+
const backgroundStrokeTipPath = `M14,63 ${tipCenterBackgroundPath} L88,60 Z`;
|
315
323
|
icon.innerHTML = `
|
316
324
|
<defs>
|
317
325
|
${checkerboardPatternDef}
|
@@ -425,6 +433,18 @@ export default class IconProvider {
|
|
425
433
|
M 10,25 10,90 70,90 70,60 40,60 40,25 10,25 z
|
426
434
|
`);
|
427
435
|
}
|
436
|
+
makePasteIcon() {
|
437
|
+
const icon = this.makeIconFromPath(`
|
438
|
+
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
|
439
|
+
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
|
440
|
+
M 25 35 L 90 35 L 90 40 L 25 40 L 25 35 z
|
441
|
+
M 25 45 L 90 45 L 90 50 L 25 50 L 25 45 z
|
442
|
+
M 25 55 L 85 55 L 85 60 L 25 60 L 25 55 z
|
443
|
+
M 25 65 L 90 65 L 90 70 L 25 70 L 25 65 z
|
444
|
+
`);
|
445
|
+
icon.setAttribute('viewBox', '0 0 120 120');
|
446
|
+
return icon;
|
447
|
+
}
|
428
448
|
makeDeleteSelectionIcon() {
|
429
449
|
const strokeWidth = '5px';
|
430
450
|
const strokeColor = 'var(--icon-color)';
|
@@ -26,6 +26,7 @@ export interface ToolbarLocalization {
|
|
26
26
|
zoom: string;
|
27
27
|
resetView: string;
|
28
28
|
selectionToolKeyboardShortcuts: string;
|
29
|
+
paste: string;
|
29
30
|
dropdownShown: (toolName: string) => string;
|
30
31
|
dropdownHidden: (toolName: string) => string;
|
31
32
|
zoomLevel: (zoomPercentage: number) => string;
|
@@ -26,6 +26,7 @@ export const defaultToolbarLocalization = {
|
|
26
26
|
outlinedRectanglePen: 'Outlined rectangle',
|
27
27
|
filledRectanglePen: 'Filled rectangle',
|
28
28
|
lockRotation: 'Lock rotation',
|
29
|
+
paste: 'Paste',
|
29
30
|
dropdownShown: (toolName) => `Dropdown for ${toolName} shown`,
|
30
31
|
dropdownHidden: (toolName) => `Dropdown for ${toolName} hidden`,
|
31
32
|
zoomLevel: (zoomPercent) => `Zoom: ${zoomPercent}%`,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import Editor from '../Editor';
|
3
|
-
|
4
|
-
|
3
|
+
type OnColorChangeListener = (color: Color4) => void;
|
4
|
+
type SetColorCallback = (color: Color4 | string) => void;
|
5
5
|
export declare const makeColorInput: (editor: Editor, onColorChange: OnColorChangeListener) => [HTMLInputElement, HTMLElement, SetColorCallback];
|
6
6
|
export default makeColorInput;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
2
|
import { KeyPressEvent } from '../../types';
|
3
3
|
import { ToolbarLocalization } from '../localization';
|
4
|
-
export
|
4
|
+
export type SavedToolbuttonState = Record<string, any>;
|
5
5
|
export default abstract class BaseWidget {
|
6
6
|
#private;
|
7
7
|
protected editor: Editor;
|
@@ -87,7 +87,8 @@ export default class PenToolWidget extends BaseToolWidget {
|
|
87
87
|
// Use a square-root scale to prevent the pen's tip from overflowing.
|
88
88
|
const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 4);
|
89
89
|
const color = this.tool.getColor();
|
90
|
-
|
90
|
+
const roundedTip = strokeFactory === makeFreehandLineBuilder;
|
91
|
+
return this.editor.icons.makePenIcon(scale, color.toHexString(), roundedTip);
|
91
92
|
}
|
92
93
|
else {
|
93
94
|
const strokeFactory = this.tool.getStrokeFactory();
|
@@ -1,15 +1,15 @@
|
|
1
1
|
import { EditorEventType } from '../types';
|
2
2
|
export default class BaseTool {
|
3
|
+
onPointerDown(_event) { return false; }
|
4
|
+
onPointerMove(_event) { }
|
5
|
+
onPointerUp(_event) { }
|
6
|
+
onGestureCancel() { }
|
3
7
|
constructor(notifier, description) {
|
4
8
|
this.notifier = notifier;
|
5
9
|
this.description = description;
|
6
10
|
this.enabled = true;
|
7
11
|
this.group = null;
|
8
12
|
}
|
9
|
-
onPointerDown(_event) { return false; }
|
10
|
-
onPointerMove(_event) { }
|
11
|
-
onPointerUp(_event) { }
|
12
|
-
onGestureCancel() { }
|
13
13
|
onWheel(_event) {
|
14
14
|
return false;
|
15
15
|
}
|
@@ -115,13 +115,18 @@ export default class PanZoom extends BaseTool {
|
|
115
115
|
updateVelocity(currentCenter) {
|
116
116
|
const deltaPos = currentCenter.minus(this.lastScreenCenter);
|
117
117
|
const deltaTime = ((new Date()).getTime() - this.lastTimestamp) / 1000;
|
118
|
-
|
119
|
-
let smoothedVelocity = currentVelocity;
|
118
|
+
// We divide by deltaTime. Don't divide by zero.
|
120
119
|
if (deltaTime === 0) {
|
121
120
|
return;
|
122
121
|
}
|
122
|
+
// Ignore duplicate events, unless there has been enough time between them.
|
123
|
+
if (deltaPos.magnitude() === 0 && deltaTime < 0.1) {
|
124
|
+
return;
|
125
|
+
}
|
126
|
+
const currentVelocity = deltaPos.times(1 / deltaTime);
|
127
|
+
let smoothedVelocity = currentVelocity;
|
123
128
|
if (this.velocity) {
|
124
|
-
smoothedVelocity = this.velocity.lerp(
|
129
|
+
smoothedVelocity = this.velocity.lerp(currentVelocity, 0.5);
|
125
130
|
}
|
126
131
|
this.velocity = smoothedVelocity;
|
127
132
|
}
|
@@ -181,6 +186,11 @@ export default class PanZoom extends BaseTool {
|
|
181
186
|
};
|
182
187
|
const shouldInertialScroll = event.current.device === PointerDevice.Touch && event.allPointers.length === 1;
|
183
188
|
if (shouldInertialScroll && this.velocity !== null) {
|
189
|
+
// If the user drags the screen, then stops, then lifts the pointer,
|
190
|
+
// we want the final velocity to reflect the stop at the end (so the velocity
|
191
|
+
// should be near zero). Handle this:
|
192
|
+
this.updateVelocity(event.current.screenPos);
|
193
|
+
// Cancel any ongoing inertial scrolling.
|
184
194
|
(_a = this.inertialScroller) === null || _a === void 0 ? void 0 : _a.stop();
|
185
195
|
this.inertialScroller = new InertialScroller(this.velocity, (scrollDelta) => {
|
186
196
|
if (!this.transform) {
|