js-draw 0.13.0 → 0.14.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.
@@ -90,11 +90,12 @@ class InertialScroller {
90
90
  export default class PanZoom extends BaseTool {
91
91
  private transform: ViewportTransform|null = null;
92
92
 
93
- private lastAngle: number;
94
93
  private lastDist: number;
95
94
  private lastScreenCenter: Point2;
96
95
  private lastTimestamp: number;
97
96
  private lastPointerDownTimestamp: number = 0;
97
+ private initialTouchAngle: number = 0;
98
+ private initialViewportRotation: number = 0;
98
99
 
99
100
  private inertialScroller: InertialScroller|null = null;
100
101
  private velocity: Vec2|null = null;
@@ -132,9 +133,11 @@ export default class PanZoom extends BaseTool {
132
133
 
133
134
  if (allAreTouch && pointers.length === 2 && this.mode & PanZoomMode.TwoFingerTouchGestures) {
134
135
  const { screenCenter, angle, dist } = this.computePinchData(pointers[0], pointers[1]);
135
- this.lastAngle = angle;
136
136
  this.lastDist = dist;
137
137
  this.lastScreenCenter = screenCenter;
138
+ this.initialTouchAngle = angle;
139
+ this.initialViewportRotation = this.editor.viewport.getRotationAngle();
140
+
138
141
  handlingGesture = true;
139
142
  } else if (pointers.length === 1 && (
140
143
  (this.mode & PanZoomMode.OneFingerTouchGestures && allAreTouch)
@@ -188,24 +191,51 @@ export default class PanZoom extends BaseTool {
188
191
  return delta;
189
192
  }
190
193
 
194
+ // Snaps `angle` to common desired rotations. For example, if `touchAngle` corresponds
195
+ // to a viewport rotation of 90.1 degrees, this function returns a rotation delta that,
196
+ // when applied to the viewport, rotates the viewport to 90.0 degrees.
197
+ //
198
+ // Returns a snapped rotation delta that, when applied to the viewport, rotates the viewport,
199
+ // from its position on the last touchDown event, by `touchAngle - initialTouchAngle`.
200
+ private toSnappedRotationDelta(touchAngle: number) {
201
+ const deltaAngle = touchAngle - this.initialTouchAngle;
202
+ let fullRotation = deltaAngle + this.initialViewportRotation;
203
+
204
+ const snapToMultipleOf = Math.PI / 2;
205
+ const roundedFullRotation = Math.round(fullRotation / snapToMultipleOf) * snapToMultipleOf;
206
+
207
+ // The maximum angle for which we snap the given angle to a multiple of
208
+ // `snapToMultipleOf`.
209
+ const maxSnapAngle = 0.07;
210
+
211
+ // Snap the rotation
212
+ if (Math.abs(fullRotation - roundedFullRotation) < maxSnapAngle) {
213
+ fullRotation = roundedFullRotation;
214
+ }
215
+
216
+ return fullRotation - this.editor.viewport.getRotationAngle();
217
+ }
218
+
191
219
  private handleTwoFingerMove(allPointers: Pointer[]) {
192
220
  const { screenCenter, canvasCenter, angle, dist } = this.computePinchData(allPointers[0], allPointers[1]);
193
221
 
194
222
  const delta = this.getCenterDelta(screenCenter);
195
- let rotation = angle - this.lastAngle;
223
+ let deltaRotation;
196
224
 
197
225
  if (this.isRotationLocked()) {
198
- rotation = 0;
226
+ deltaRotation = 0;
227
+ } else {
228
+ deltaRotation = this.toSnappedRotationDelta(angle);
199
229
  }
200
230
 
231
+
201
232
  this.updateVelocity(screenCenter);
202
233
 
203
234
  const transformUpdate = Mat33.translation(delta)
204
235
  .rightMul(Mat33.scaling2D(dist / this.lastDist, canvasCenter))
205
- .rightMul(Mat33.zRotation(rotation, canvasCenter));
236
+ .rightMul(Mat33.zRotation(deltaRotation, canvasCenter));
206
237
  this.lastScreenCenter = screenCenter;
207
238
  this.lastDist = dist;
208
- this.lastAngle = angle;
209
239
  this.transform = Viewport.transformBy(
210
240
  this.transform!.transform.rightMul(transformUpdate)
211
241
  );
@@ -121,6 +121,21 @@ export default class Selection {
121
121
  return this.originalRegion.transformedBoundingBox(scaleAndTranslateMat);
122
122
  }
123
123
 
124
+ /**
125
+ * Computes and returns the bounding box of the selection without
126
+ * any additional padding. Computes directly from the elements that are selected.
127
+ * @internal
128
+ */
129
+ public computeTightBoundingBox() {
130
+ const bbox = this.selectedElems.reduce((
131
+ accumulator: Rect2|null, elem: AbstractComponent
132
+ ): Rect2 => {
133
+ return (accumulator ?? elem.getBBox()).union(elem.getBBox());
134
+ }, null);
135
+
136
+ return bbox ?? Rect2.empty;
137
+ }
138
+
124
139
  public get regionRotation(): number {
125
140
  return this.transform.transformVec3(Vec2.unitX).angle();
126
141
  }
@@ -328,11 +343,7 @@ export default class Selection {
328
343
  // Recompute this' region from the selected elements.
329
344
  // Returns false if the selection is empty.
330
345
  public recomputeRegion(): boolean {
331
- const newRegion = this.selectedElems.reduce((
332
- accumulator: Rect2|null, elem: AbstractComponent
333
- ): Rect2 => {
334
- return (accumulator ?? elem.getBBox()).union(elem.getBBox());
335
- }, null);
346
+ const newRegion = this.computeTightBoundingBox();
336
347
 
337
348
  if (!newRegion) {
338
349
  this.cancelSelection();
@@ -62,12 +62,13 @@ export default class SelectionTool extends BaseTool {
62
62
  private snapSelectionToGrid() {
63
63
  if (!this.selectionBox) throw new Error('No selection to snap!');
64
64
 
65
- const topLeftOfBBox = this.selectionBox.region.topLeft;
66
- const snapDistance =
67
- this.editor.viewport.snapToGrid(topLeftOfBBox).minus(topLeftOfBBox);
65
+ // Snap the top left corner of what we have selected.
66
+ const topLeftOfBBox = this.selectionBox.computeTightBoundingBox().topLeft;
67
+ const snappedTopLeft = this.editor.viewport.snapToGrid(topLeftOfBBox);
68
+ const snapDelta = snappedTopLeft.minus(topLeftOfBBox);
68
69
 
69
70
  const oldTransform = this.selectionBox.getTransform();
70
- this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(snapDistance)));
71
+ this.selectionBox.setTransform(oldTransform.rightMul(Mat33.translation(snapDelta)));
71
72
  this.selectionBox.finalizeTransform();
72
73
  }
73
74
 
@@ -62,7 +62,7 @@ export class ResizeTransformer {
62
62
  // long decimal representations => large file sizes.
63
63
  scale = scale.map(component => Viewport.roundScaleRatio(component, 2));
64
64
 
65
- if (scale.x > 0 && scale.y > 0) {
65
+ if (scale.x !== 0 && scale.y !== 0) {
66
66
  const origin = this.editor.viewport.roundPoint(this.selection.preTransformRegion.topLeft);
67
67
  this.selection.setTransform(Mat33.scaling2D(scale, origin));
68
68
  }