js-draw 1.7.2 → 1.9.0
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/LICENSE +1 -1
- package/dist/Editor.css +1 -0
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/EventDispatcher.js +2 -1
- package/dist/cjs/SVGLoader.d.ts +1 -1
- package/dist/cjs/SVGLoader.js +11 -2
- package/dist/cjs/Viewport.d.ts +2 -0
- package/dist/cjs/Viewport.js +2 -0
- package/dist/cjs/components/AbstractComponent.d.ts +9 -0
- package/dist/cjs/components/AbstractComponent.js +11 -0
- package/dist/cjs/components/Stroke.d.ts +3 -0
- package/dist/cjs/components/Stroke.js +55 -1
- package/dist/cjs/image/EditorImage.d.ts +20 -0
- package/dist/cjs/image/EditorImage.js +48 -3
- package/dist/cjs/rendering/Display.d.ts +9 -0
- package/dist/cjs/rendering/Display.js +39 -5
- package/dist/cjs/rendering/RenderablePathSpec.d.ts +20 -2
- package/dist/cjs/rendering/RenderablePathSpec.js +72 -9
- package/dist/cjs/rendering/caching/CacheRecord.js +5 -3
- package/dist/cjs/rendering/caching/CacheRecordManager.js +3 -4
- package/dist/cjs/rendering/caching/RenderingCache.d.ts +1 -0
- package/dist/cjs/rendering/caching/RenderingCache.js +4 -0
- package/dist/cjs/rendering/caching/RenderingCacheNode.js +19 -11
- package/dist/cjs/rendering/caching/types.d.ts +1 -0
- package/dist/cjs/rendering/renderers/AbstractRenderer.d.ts +10 -0
- package/dist/cjs/rendering/renderers/AbstractRenderer.js +12 -3
- package/dist/cjs/rendering/renderers/CanvasRenderer.js +5 -2
- package/dist/cjs/rendering/renderers/SVGRenderer.d.ts +0 -10
- package/dist/cjs/rendering/renderers/SVGRenderer.js +0 -14
- package/dist/cjs/testing/startPinchGesture.d.ts +14 -0
- package/dist/cjs/testing/startPinchGesture.js +41 -0
- package/dist/cjs/tools/PanZoom.d.ts +4 -0
- package/dist/cjs/tools/PanZoom.js +21 -2
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/EventDispatcher.mjs +2 -1
- package/dist/mjs/SVGLoader.d.ts +1 -1
- package/dist/mjs/SVGLoader.mjs +11 -2
- package/dist/mjs/Viewport.d.ts +2 -0
- package/dist/mjs/Viewport.mjs +2 -0
- package/dist/mjs/components/AbstractComponent.d.ts +9 -0
- package/dist/mjs/components/AbstractComponent.mjs +11 -0
- package/dist/mjs/components/Stroke.d.ts +3 -0
- package/dist/mjs/components/Stroke.mjs +56 -2
- package/dist/mjs/image/EditorImage.d.ts +20 -0
- package/dist/mjs/image/EditorImage.mjs +46 -2
- package/dist/mjs/rendering/Display.d.ts +9 -0
- package/dist/mjs/rendering/Display.mjs +39 -5
- package/dist/mjs/rendering/RenderablePathSpec.d.ts +20 -2
- package/dist/mjs/rendering/RenderablePathSpec.mjs +71 -9
- package/dist/mjs/rendering/caching/CacheRecord.mjs +5 -3
- package/dist/mjs/rendering/caching/CacheRecordManager.mjs +3 -4
- package/dist/mjs/rendering/caching/RenderingCache.d.ts +1 -0
- package/dist/mjs/rendering/caching/RenderingCache.mjs +4 -0
- package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +20 -12
- package/dist/mjs/rendering/caching/types.d.ts +1 -0
- package/dist/mjs/rendering/renderers/AbstractRenderer.d.ts +10 -0
- package/dist/mjs/rendering/renderers/AbstractRenderer.mjs +12 -3
- package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +5 -2
- package/dist/mjs/rendering/renderers/SVGRenderer.d.ts +0 -10
- package/dist/mjs/rendering/renderers/SVGRenderer.mjs +0 -14
- package/dist/mjs/testing/startPinchGesture.d.ts +14 -0
- package/dist/mjs/testing/startPinchGesture.mjs +36 -0
- package/dist/mjs/tools/PanZoom.d.ts +4 -0
- package/dist/mjs/tools/PanZoom.mjs +21 -2
- package/dist/mjs/version.mjs +1 -1
- package/package.json +4 -4
- package/src/Editor.scss +2 -0
@@ -1,5 +1,4 @@
|
|
1
1
|
import CacheRecord from './CacheRecord.mjs';
|
2
|
-
const debugMode = false;
|
3
2
|
export class CacheRecordManager {
|
4
3
|
constructor(cacheProps) {
|
5
4
|
// Fixed-size array: Cache blocks are assigned indicies into [cachedCanvases].
|
@@ -16,19 +15,19 @@ export class CacheRecordManager {
|
|
16
15
|
const record = new CacheRecord(onDealloc, this.cacheState);
|
17
16
|
record.setRenderingRegion(drawTo);
|
18
17
|
this.cacheRecords.push(record);
|
19
|
-
if (debugMode) {
|
18
|
+
if (this.cacheState.debugMode) {
|
20
19
|
console.log('[Cache] Cache spaces used: ', this.cacheRecords.length, ' of ', this.maxCanvases);
|
21
20
|
}
|
22
21
|
return record;
|
23
22
|
}
|
24
23
|
else {
|
25
24
|
const lru = this.getLeastRecentlyUsedRecord();
|
26
|
-
if (debugMode) {
|
25
|
+
if (this.cacheState.debugMode) {
|
27
26
|
console.log('[Cache] Re-alloc. Times allocated: ', lru.allocCount, '\nLast used cycle: ', lru.getLastUsedCycle(), '\nCurrent cycle: ', this.cacheState.currentRenderingCycle);
|
28
27
|
}
|
29
28
|
lru.realloc(onDealloc);
|
30
29
|
lru.setRenderingRegion(drawTo);
|
31
|
-
if (debugMode) {
|
30
|
+
if (this.cacheState.debugMode) {
|
32
31
|
console.log('[Cache] Now re-alloc\'d. Last used cycle: ', lru.getLastUsedCycle());
|
33
32
|
console.assert(lru['cacheState'] === this.cacheState, '[Cache] Unequal cache states! cacheState should be a shared object!');
|
34
33
|
}
|
@@ -8,6 +8,7 @@ export default class RenderingCache {
|
|
8
8
|
props: cacheProps,
|
9
9
|
currentRenderingCycle: 0,
|
10
10
|
recordManager: this.recordManager,
|
11
|
+
debugMode: false,
|
11
12
|
};
|
12
13
|
this.recordManager.setSharedState(this.sharedState);
|
13
14
|
}
|
@@ -44,4 +45,7 @@ export default class RenderingCache {
|
|
44
45
|
getDebugInfo() {
|
45
46
|
return this.recordManager.getDebugInfo();
|
46
47
|
}
|
48
|
+
setIsDebugMode(debugMode) {
|
49
|
+
this.sharedState.debugMode = debugMode;
|
50
|
+
}
|
47
51
|
}
|
@@ -1,10 +1,8 @@
|
|
1
1
|
// A cache record with sub-nodes.
|
2
|
-
import { sortLeavesByZIndex } from '../../image/EditorImage.mjs';
|
2
|
+
import { computeFirstIndexToRender, sortLeavesByZIndex } from '../../image/EditorImage.mjs';
|
3
3
|
import { Rect2, Color4 } from '@js-draw/math';
|
4
4
|
// 3x3 divisions for each node.
|
5
5
|
const cacheDivisionSize = 3;
|
6
|
-
// True: Show rendering updates.
|
7
|
-
const debugMode = false;
|
8
6
|
export default class RenderingCacheNode {
|
9
7
|
constructor(region, cacheState) {
|
10
8
|
this.region = region;
|
@@ -161,8 +159,8 @@ export default class RenderingCacheNode {
|
|
161
159
|
items.forEach(item => item.render(screenRenderer, viewport.visibleRect));
|
162
160
|
return;
|
163
161
|
}
|
164
|
-
if (debugMode) {
|
165
|
-
screenRenderer.drawRect(this.region,
|
162
|
+
if (this.cacheState.debugMode) {
|
163
|
+
screenRenderer.drawRect(this.region, viewport.getSizeOfPixelOnCanvas(), { fill: Color4.yellow });
|
166
164
|
}
|
167
165
|
// Could we render direclty from [this] or do we need to recurse?
|
168
166
|
const couldRender = this.renderingWouldBeHighEnoughResolution(viewport);
|
@@ -197,7 +195,9 @@ export default class RenderingCacheNode {
|
|
197
195
|
}
|
198
196
|
let leafApproxRenderTime = 0;
|
199
197
|
for (const leaf of leavesByIds) {
|
200
|
-
|
198
|
+
if (!tooSmallToRender(leaf.getBBox())) {
|
199
|
+
leafApproxRenderTime += leaf.getContent().getProportionalRenderingTime();
|
200
|
+
}
|
201
201
|
}
|
202
202
|
// Is it worth it to render the items?
|
203
203
|
if (leafApproxRenderTime > this.cacheState.props.minProportionalRenderTimePerCache) {
|
@@ -233,26 +233,30 @@ export default class RenderingCacheNode {
|
|
233
233
|
this.renderedMaxZIndex = zIndex;
|
234
234
|
}
|
235
235
|
}
|
236
|
-
if (debugMode) {
|
237
|
-
|
236
|
+
if (this.cacheState.debugMode) {
|
237
|
+
// Clay for adding new elements
|
238
|
+
screenRenderer.drawRect(this.region, 2 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.clay });
|
238
239
|
}
|
239
240
|
}
|
240
241
|
}
|
241
|
-
else if (debugMode) {
|
242
|
+
else if (this.cacheState.debugMode) {
|
242
243
|
console.log('Decided on a full re-render. Reason: At least one of the following is false:', '\n leafIds.length > this.renderedIds.length: ', leafIds.length > this.renderedIds.length, '\n this.allRenderedIdsIn(leafIds): ', this.allRenderedIdsIn(leafIds), '\n this.renderedMaxZIndex !== null: ', this.renderedMaxZIndex !== null, '\n\nthis.rerenderedIds: ', this.renderedIds, ', leafIds: ', leafIds);
|
243
244
|
}
|
244
245
|
if (fullRerenderNeeded) {
|
245
246
|
thisRenderer = this.cachedRenderer.startRender();
|
246
247
|
thisRenderer.clear();
|
247
248
|
this.renderedMaxZIndex = null;
|
248
|
-
|
249
|
+
const startIndex = computeFirstIndexToRender(leaves, this.region);
|
250
|
+
for (let i = startIndex; i < leaves.length; i++) {
|
251
|
+
const leaf = leaves[i];
|
249
252
|
const content = leaf.getContent();
|
250
253
|
this.renderedMaxZIndex ??= content.getZIndex();
|
251
254
|
this.renderedMaxZIndex = Math.max(this.renderedMaxZIndex, content.getZIndex());
|
252
255
|
leaf.render(thisRenderer, this.region);
|
253
256
|
}
|
254
|
-
if (debugMode) {
|
255
|
-
|
257
|
+
if (this.cacheState.debugMode) {
|
258
|
+
// Red for full rerender
|
259
|
+
screenRenderer.drawRect(this.region, 3 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.red });
|
256
260
|
}
|
257
261
|
}
|
258
262
|
this.renderedIds = leafIds;
|
@@ -269,6 +273,10 @@ export default class RenderingCacheNode {
|
|
269
273
|
leaf.render(screenRenderer, this.region.intersection(viewport.visibleRect));
|
270
274
|
}
|
271
275
|
screenRenderer.endObject();
|
276
|
+
if (this.cacheState.debugMode) {
|
277
|
+
// Green for no cache needed render
|
278
|
+
screenRenderer.drawRect(this.region, 2 * viewport.getSizeOfPixelOnCanvas(), { fill: Color4.green });
|
279
|
+
}
|
272
280
|
}
|
273
281
|
}
|
274
282
|
else {
|
@@ -20,6 +20,11 @@ export default abstract class AbstractRenderer {
|
|
20
20
|
private selfTransform;
|
21
21
|
private transformStack;
|
22
22
|
protected constructor(viewport: Viewport);
|
23
|
+
/**
|
24
|
+
* this.canvasToScreen, etc. should be used instead of the corresponding
|
25
|
+
* methods on `Viewport`, because the viewport may not accurately reflect
|
26
|
+
* what is rendered.
|
27
|
+
*/
|
23
28
|
protected getViewport(): Viewport;
|
24
29
|
abstract displaySize(): Vec2;
|
25
30
|
abstract clear(): void;
|
@@ -73,5 +78,10 @@ export default abstract class AbstractRenderer {
|
|
73
78
|
getCanvasToScreenTransform(): Mat33;
|
74
79
|
canvasToScreen(vec: Vec2): Vec2;
|
75
80
|
getSizeOfCanvasPixelOnScreen(): number;
|
81
|
+
private visibleRectOverride;
|
82
|
+
/**
|
83
|
+
* @internal
|
84
|
+
*/
|
85
|
+
overrideVisibleRect(rect: Rect2 | null): void;
|
76
86
|
getVisibleRect(): Rect2;
|
77
87
|
}
|
@@ -15,8 +15,11 @@ export default class AbstractRenderer {
|
|
15
15
|
this.objectLevel = 0;
|
16
16
|
this.currentPaths = null;
|
17
17
|
}
|
18
|
-
|
19
|
-
|
18
|
+
/**
|
19
|
+
* this.canvasToScreen, etc. should be used instead of the corresponding
|
20
|
+
* methods on `Viewport`, because the viewport may not accurately reflect
|
21
|
+
* what is rendered.
|
22
|
+
*/
|
20
23
|
getViewport() { return this.viewport; }
|
21
24
|
setDraftMode(_draftMode) { }
|
22
25
|
flushPath() {
|
@@ -158,12 +161,18 @@ export default class AbstractRenderer {
|
|
158
161
|
getSizeOfCanvasPixelOnScreen() {
|
159
162
|
return this.getCanvasToScreenTransform().transformVec3(Vec2.unitX).length();
|
160
163
|
}
|
164
|
+
/**
|
165
|
+
* @internal
|
166
|
+
*/
|
167
|
+
overrideVisibleRect(rect) {
|
168
|
+
this.visibleRectOverride = rect;
|
169
|
+
}
|
161
170
|
// Returns the region in canvas space that is visible within the viewport this
|
162
171
|
// canvas is rendering to.
|
163
172
|
//
|
164
173
|
// Note that in some cases this might not be the same as the `visibleRect` given
|
165
174
|
// to components in their `render` method.
|
166
175
|
getVisibleRect() {
|
167
|
-
return this.viewport.visibleRect;
|
176
|
+
return this.visibleRectOverride ?? this.viewport.visibleRect;
|
168
177
|
}
|
169
178
|
}
|
@@ -77,7 +77,10 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
77
77
|
return Vec2.of(this.ctx.canvas.clientWidth, this.ctx.canvas.clientHeight);
|
78
78
|
}
|
79
79
|
clear() {
|
80
|
+
this.ctx.save();
|
81
|
+
this.ctx.resetTransform();
|
80
82
|
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
83
|
+
this.ctx.restore();
|
81
84
|
}
|
82
85
|
beginPath(startPoint) {
|
83
86
|
startPoint = this.canvasToScreen(startPoint);
|
@@ -144,7 +147,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
144
147
|
return;
|
145
148
|
}
|
146
149
|
// If part of a huge object, it might be worth trimming the path
|
147
|
-
const visibleRect = this.
|
150
|
+
const visibleRect = this.getVisibleRect();
|
148
151
|
if (this.currentObjectBBox?.containsRect(visibleRect)) {
|
149
152
|
// Try to trim/remove parts of the path outside of the bounding box.
|
150
153
|
path = visualEquivalent(path, visibleRect);
|
@@ -184,7 +187,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
184
187
|
if (!this.ignoringObject && clip) {
|
185
188
|
// Don't clip if it would only remove content already trimmed by
|
186
189
|
// the edge of the screen.
|
187
|
-
const clippedIsOutsideScreen = boundingBox.containsRect(this.
|
190
|
+
const clippedIsOutsideScreen = boundingBox.containsRect(this.getVisibleRect());
|
188
191
|
if (!clippedIsOutsideScreen) {
|
189
192
|
this.clipLevels.push(this.objectLevel);
|
190
193
|
this.ctx.save();
|
@@ -59,16 +59,6 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
59
59
|
drawPoints(...points: Point2[]): void;
|
60
60
|
drawSVGElem(elem: SVGElement): void;
|
61
61
|
isTooSmallToRender(_rect: Rect2): boolean;
|
62
|
-
private visibleRectOverride;
|
63
|
-
/**
|
64
|
-
* Overrides the visible region returned by `getVisibleRect`.
|
65
|
-
*
|
66
|
-
* This is useful when the `viewport`'s transform has been modified,
|
67
|
-
* for example, to compensate for storing part of the image's
|
68
|
-
* transformation in an SVG property.
|
69
|
-
*/
|
70
|
-
private overrideVisibleRect;
|
71
|
-
getVisibleRect(): Rect2;
|
72
62
|
/**
|
73
63
|
* Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
|
74
64
|
* and other metadata attributes set for the given `Viewport`.
|
@@ -36,7 +36,6 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
36
36
|
this.textContainer = null;
|
37
37
|
this.textContainerTransform = null;
|
38
38
|
this.textParentStyle = defaultTextStyle;
|
39
|
-
this.visibleRectOverride = null;
|
40
39
|
this.clear();
|
41
40
|
this.addStyleSheet();
|
42
41
|
}
|
@@ -339,19 +338,6 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
339
338
|
isTooSmallToRender(_rect) {
|
340
339
|
return false;
|
341
340
|
}
|
342
|
-
/**
|
343
|
-
* Overrides the visible region returned by `getVisibleRect`.
|
344
|
-
*
|
345
|
-
* This is useful when the `viewport`'s transform has been modified,
|
346
|
-
* for example, to compensate for storing part of the image's
|
347
|
-
* transformation in an SVG property.
|
348
|
-
*/
|
349
|
-
overrideVisibleRect(newRect) {
|
350
|
-
this.visibleRectOverride = newRect;
|
351
|
-
}
|
352
|
-
getVisibleRect() {
|
353
|
-
return this.visibleRectOverride ?? super.getVisibleRect();
|
354
|
-
}
|
355
341
|
/**
|
356
342
|
* Creates a new SVG element and `SVGRenerer` with `width`, `height`, `viewBox`,
|
357
343
|
* and other metadata attributes set for the given `Viewport`.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import type Editor from '../Editor';
|
2
|
+
import { Point2 } from '@js-draw/math';
|
3
|
+
/**
|
4
|
+
* Creates two pointers and sends the touch {@link InputEvtType.PointerDownEvt}s for them.
|
5
|
+
*
|
6
|
+
* Returns an object that allows continuing or ending the gesture.
|
7
|
+
*
|
8
|
+
* `initialRotation` should be in radians.
|
9
|
+
*/
|
10
|
+
declare const startPinchGesture: (editor: Editor, center: Point2, initialDistance: number, initialRotation: number) => {
|
11
|
+
update(center: Point2, distance: number, rotation: number): void;
|
12
|
+
end(): void;
|
13
|
+
};
|
14
|
+
export default startPinchGesture;
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import { Mat33, Vec2 } from '@js-draw/math';
|
2
|
+
import sendTouchEvent from './sendTouchEvent.mjs';
|
3
|
+
import { InputEvtType } from '../inputEvents.mjs';
|
4
|
+
/**
|
5
|
+
* Creates two pointers and sends the touch {@link InputEvtType.PointerDownEvt}s for them.
|
6
|
+
*
|
7
|
+
* Returns an object that allows continuing or ending the gesture.
|
8
|
+
*
|
9
|
+
* `initialRotation` should be in radians.
|
10
|
+
*/
|
11
|
+
const startPinchGesture = (editor, center, initialDistance, initialRotation) => {
|
12
|
+
const computeTouchPoints = (center, distance, rotation) => {
|
13
|
+
const halfDisplacement = Mat33.zRotation(rotation).transformVec2(Vec2.of(0, distance / 2));
|
14
|
+
const point1 = center.plus(halfDisplacement);
|
15
|
+
const point2 = center.minus(halfDisplacement);
|
16
|
+
return [point1, point2];
|
17
|
+
};
|
18
|
+
let [touchPoint1, touchPoint2] = computeTouchPoints(center, initialDistance, initialRotation);
|
19
|
+
let firstPointer = sendTouchEvent(editor, InputEvtType.PointerDownEvt, touchPoint1);
|
20
|
+
let secondPointer = sendTouchEvent(editor, InputEvtType.PointerDownEvt, touchPoint2, [firstPointer]);
|
21
|
+
return {
|
22
|
+
update(center, distance, rotation) {
|
23
|
+
const eventType = InputEvtType.PointerMoveEvt;
|
24
|
+
const [newPoint1, newPoint2] = computeTouchPoints(center, distance, rotation);
|
25
|
+
touchPoint1 = newPoint1;
|
26
|
+
touchPoint2 = newPoint2;
|
27
|
+
firstPointer = sendTouchEvent(editor, eventType, newPoint1, [secondPointer]);
|
28
|
+
secondPointer = sendTouchEvent(editor, eventType, newPoint2, [firstPointer]);
|
29
|
+
},
|
30
|
+
end() {
|
31
|
+
sendTouchEvent(editor, InputEvtType.PointerUpEvt, touchPoint1, [secondPointer]);
|
32
|
+
sendTouchEvent(editor, InputEvtType.PointerUpEvt, touchPoint2);
|
33
|
+
},
|
34
|
+
};
|
35
|
+
};
|
36
|
+
export default startPinchGesture;
|
@@ -21,6 +21,9 @@ export default class PanZoom extends BaseTool {
|
|
21
21
|
private editor;
|
22
22
|
private mode;
|
23
23
|
private transform;
|
24
|
+
private readonly initialRotationSnapAngle;
|
25
|
+
private readonly afterRotationStartSnapAngle;
|
26
|
+
private readonly pinchZoomStartThreshold;
|
24
27
|
private startDist;
|
25
28
|
private lastDist;
|
26
29
|
private lastScreenCenter;
|
@@ -29,6 +32,7 @@ export default class PanZoom extends BaseTool {
|
|
29
32
|
private initialTouchAngle;
|
30
33
|
private initialViewportRotation;
|
31
34
|
private isScaling;
|
35
|
+
private isRotating;
|
32
36
|
private inertialScroller;
|
33
37
|
private velocity;
|
34
38
|
constructor(editor: Editor, mode: PanZoomMode, description: string);
|
@@ -65,12 +65,19 @@ export default class PanZoom extends BaseTool {
|
|
65
65
|
this.editor = editor;
|
66
66
|
this.mode = mode;
|
67
67
|
this.transform = null;
|
68
|
+
// Constants
|
69
|
+
// initialRotationSnapAngle is larger than afterRotationStartSnapAngle to
|
70
|
+
// make it more difficult to start rotating (and easier to continue rotating).
|
71
|
+
this.initialRotationSnapAngle = 0.22; // radians
|
72
|
+
this.afterRotationStartSnapAngle = 0.07; // radians
|
73
|
+
this.pinchZoomStartThreshold = 1.08; // scale factor
|
68
74
|
this.lastPointerDownTimestamp = 0;
|
69
75
|
this.initialTouchAngle = 0;
|
70
76
|
this.initialViewportRotation = 0;
|
71
77
|
// Set to `true` only when scaling has started (if two fingers are down and have moved
|
72
78
|
// far enough).
|
73
79
|
this.isScaling = false;
|
80
|
+
this.isRotating = false;
|
74
81
|
this.inertialScroller = null;
|
75
82
|
this.velocity = null;
|
76
83
|
}
|
@@ -112,6 +119,9 @@ export default class PanZoom extends BaseTool {
|
|
112
119
|
this.initialTouchAngle = angle;
|
113
120
|
this.initialViewportRotation = this.editor.viewport.getRotationAngle();
|
114
121
|
this.isScaling = false;
|
122
|
+
// We're initially rotated if `initialViewportRotation` isn't near a multiple of pi/2.
|
123
|
+
// In other words, if sin(2 initialViewportRotation) is near zero.
|
124
|
+
this.isRotating = Math.abs(Math.sin(this.initialViewportRotation * 2)) > 1e-3;
|
115
125
|
handlingGesture = true;
|
116
126
|
}
|
117
127
|
else if (pointers.length === 1 && ((this.mode & PanZoomMode.OneFingerTouchGestures && allAreTouch)
|
@@ -168,7 +178,9 @@ export default class PanZoom extends BaseTool {
|
|
168
178
|
const roundedFullRotation = Math.round(fullRotation / snapToMultipleOf) * snapToMultipleOf;
|
169
179
|
// The maximum angle for which we snap the given angle to a multiple of
|
170
180
|
// `snapToMultipleOf`.
|
171
|
-
|
181
|
+
// Use a smaller snap angle if already rotated (to avoid pinch zoom gestures from
|
182
|
+
// starting rotation).
|
183
|
+
const maxSnapAngle = this.isRotating ? this.afterRotationStartSnapAngle : this.initialRotationSnapAngle;
|
172
184
|
// Snap the rotation
|
173
185
|
if (Math.abs(fullRotation - roundedFullRotation) < maxSnapAngle) {
|
174
186
|
fullRotation = roundedFullRotation;
|
@@ -191,6 +203,11 @@ export default class PanZoom extends BaseTool {
|
|
191
203
|
else {
|
192
204
|
deltaRotation = this.toSnappedRotationDelta(angle);
|
193
205
|
}
|
206
|
+
// If any rotation, make a note of this (affects rotation snap
|
207
|
+
// angles).
|
208
|
+
if (Math.abs(deltaRotation) > 1e-8) {
|
209
|
+
this.isRotating = true;
|
210
|
+
}
|
194
211
|
this.updateVelocity(screenCenter);
|
195
212
|
let scaleFactor = 1;
|
196
213
|
if (this.isScaling) {
|
@@ -199,7 +216,9 @@ export default class PanZoom extends BaseTool {
|
|
199
216
|
else {
|
200
217
|
const initialScaleFactor = dist / this.startDist;
|
201
218
|
// Only start scaling if scaling done so far exceeds some threshold.
|
202
|
-
|
219
|
+
const upperBound = this.pinchZoomStartThreshold;
|
220
|
+
const lowerBound = 1 / this.pinchZoomStartThreshold;
|
221
|
+
if (initialScaleFactor > upperBound || initialScaleFactor < lowerBound) {
|
203
222
|
scaleFactor = initialScaleFactor;
|
204
223
|
this.isScaling = true;
|
205
224
|
}
|
package/dist/mjs/version.mjs
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "js-draw",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.9.0",
|
4
4
|
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
|
5
5
|
"types": "./dist/mjs/lib.d.ts",
|
6
6
|
"main": "./dist/cjs/lib.js",
|
@@ -55,7 +55,7 @@
|
|
55
55
|
"license": "MIT",
|
56
56
|
"private": false,
|
57
57
|
"scripts": {
|
58
|
-
"dist-test": "
|
58
|
+
"dist-test": "cd dist-test/test_imports && npm install && npm run test",
|
59
59
|
"dist": "npm run build && npm run dist-test",
|
60
60
|
"build": "rm -rf ./dist/* && mkdir -p dist && build-tool build",
|
61
61
|
"watch": "rm -rf ./dist/* && mkdir -p dist && build-tool watch",
|
@@ -64,7 +64,7 @@
|
|
64
64
|
"postpack": "ts-node tools/copyREADME.ts revert"
|
65
65
|
},
|
66
66
|
"dependencies": {
|
67
|
-
"@js-draw/math": "^1.
|
67
|
+
"@js-draw/math": "^1.9.0",
|
68
68
|
"@melloware/coloris": "0.21.0"
|
69
69
|
},
|
70
70
|
"devDependencies": {
|
@@ -86,5 +86,5 @@
|
|
86
86
|
"freehand",
|
87
87
|
"svg"
|
88
88
|
],
|
89
|
-
"gitHead": "
|
89
|
+
"gitHead": "e824c37e9f216852cf096976e3a74fb4f177ead3"
|
90
90
|
}
|