From edbd49a4d4e17ada81056e2df889fe30297384ca Mon Sep 17 00:00:00 2001 From: Igor Zinken Date: Tue, 2 Feb 2021 21:06:19 +0100 Subject: [PATCH] Holding down shift while using the lasso tool will snap the line to 45 degree increments --- src/math/point-math.js | 27 ++++++++++++++++- .../canvas-elements/interaction-pane.js | 30 ++++++++++++------- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/math/point-math.js b/src/math/point-math.js index b8326bf..4cd76de 100644 --- a/src/math/point-math.js +++ b/src/math/point-math.js @@ -20,7 +20,7 @@ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const { pow, sqrt, atan2 } = Math; +const { pow, sqrt, atan2, round, cos, sin, PI } = Math; export const distanceBetween = ( point1, point2 ) => { return sqrt( pow( point2.x - point1.x, 2 ) + pow( point2.y - point1.y, 2 )); @@ -63,3 +63,28 @@ export const translatePoints = ( coordinateList, xTranslation = 0, yTranslation y: y + yTranslation, })); }; + +/** + * Calculates the coordinate relative to given x, y and sourcePoint for an incremental snap + * + * @param {Number} x coordinate to snap to + * @param {Number} y coordinate to snap to + * @param {{ x: Number, y: Number }} sourcePoint coordinate to snap from + * @param {{ left: Number, top: Number }} viewport in case coordinates are panned + * @param {Number=} steps defaults to 4 (45 degree increments) + */ +export const snapToAngle = ( x, y, sourcePoint, { left = 0, top = 0 } = {}, steps = 4 ) => { + const deltaX = ( x - sourcePoint.x ) + left; + const deltaY = ( y - sourcePoint.y ) + top; + let dist = sqrt( pow( deltaX, 2 ) + pow( deltaY, 2 )); + + const newAngle = atan2( deltaY, deltaX ); + const shiftedAngle = round( newAngle / PI * steps ) / steps * PI; + + dist *= cos( shiftedAngle - newAngle ); // closer to x, y + + return { + x: sourcePoint.x - left + dist * cos( shiftedAngle ), + y: sourcePoint.y - top + dist * sin( shiftedAngle ) + }; +}; diff --git a/src/rendering/canvas-elements/interaction-pane.js b/src/rendering/canvas-elements/interaction-pane.js index f9c7091..e0cfd99 100644 --- a/src/rendering/canvas-elements/interaction-pane.js +++ b/src/rendering/canvas-elements/interaction-pane.js @@ -25,11 +25,12 @@ import { sprite } from "zcanvas"; import { isInsideTransparentArea } from "@/utils/canvas-util"; import { enqueueState } from "@/factories/history-state-factory"; import { getCanvasInstance, getSpriteForLayer } from "@/factories/sprite-factory"; -import { isPointInRange } from "@/math/point-math"; +import { isPointInRange, snapToAngle } from "@/math/point-math"; import { rectangleToCoordinates } from "@/math/image-math"; import { isSelectionClosed } from "@/math/selection-math"; import ToolTypes from "@/definitions/tool-types"; import LayerSprite from "@/rendering/canvas-elements/layer-sprite"; +import KeyboardService from "@/services/keyboard-service"; export const MODE_PAN = 0; export const MODE_LAYER_SELECT = 1; @@ -184,17 +185,24 @@ class InteractionPane extends sprite { case MODE_SELECTION: if ( !this._selectionClosed ) { - const document = this.getActiveDocument(); + const document = this.getActiveDocument(); + const { selection } = document; // selection mode, set the click coordinate as the first point in the selection - const firstPoint = document.selection[ 0 ]; + const firstPoint = selection[ 0 ]; let storeHistory = false; - if ( firstPoint && isPointInRange( x, y, firstPoint.x, firstPoint.y, 5 / this.canvas.zoomFactor )) { - this._selectionClosed = true; - x = firstPoint.x; - y = firstPoint.y; - storeHistory = true; + if ( firstPoint ) { + if ( KeyboardService.hasShift() ) { + ({ x, y } = snapToAngle( x, y, selection[ selection.length - 1 ] )); + } + else if ( isPointInRange( x, y, firstPoint.x, firstPoint.y, 5 / this.canvas.zoomFactor )) { + // point was in range of start coordinate, snap and close selection + this._selectionClosed = true; + x = firstPoint.x; + y = firstPoint.y; + storeHistory = true; + } } - document.selection.push({ x, y }); + selection.push({ x, y }); if ( storeHistory ) { storeSelectionHistory( document ); } @@ -262,7 +270,9 @@ class InteractionPane extends sprite { // for lasso selections, draw line to current cursor position let currentPosition = null; if ( !this._isRectangleSelect && !this._selectionClosed ) { - currentPosition = { x: localPointerX, y: localPointerY }; + currentPosition = KeyboardService.hasShift() ? + snapToAngle( localPointerX, localPointerY, selection[ selection.length - 1 ], viewport ) + : { x: localPointerX, y: localPointerY }; } const { zoomFactor } = this.canvas;