From 0c205d1377b0b640d221020f57949beef3ac14ab Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Wed, 19 May 2021 13:27:01 +0100 Subject: [PATCH] Improves rotated transforms, cleans up code --- lib/shapes/circle.tsx | 12 +- lib/shapes/index.tsx | 18 +- lib/shapes/rectangle.tsx | 136 ++---------- state/commands/transform-single.ts | 59 ++---- state/commands/transform.ts | 51 ++--- state/sessions/transform-session.ts | 227 ++++----------------- state/sessions/transform-single-session.ts | 188 +++-------------- state/state.ts | 2 +- utils/utils.ts | 140 ++++++++----- 9 files changed, 208 insertions(+), 625 deletions(-) diff --git a/lib/shapes/circle.tsx b/lib/shapes/circle.tsx index 3b8406ae0..92811928d 100644 --- a/lib/shapes/circle.tsx +++ b/lib/shapes/circle.tsx @@ -5,7 +5,7 @@ import { createShape } from "./index" import { boundsContained } from "utils/bounds" import { intersectCircleBounds } from "utils/intersections" import { pointInCircle } from "utils/hitTests" -import { translateBounds } from "utils/utils" +import { getTransformAnchor, translateBounds } from "utils/utils" const circle = createShape({ boundsCache: new WeakMap([]), @@ -94,7 +94,9 @@ const circle = createShape({ return shape }, - transform(shape, bounds, { anchor }) { + transform(shape, bounds, { type, initialShape, scaleX, scaleY }) { + const anchor = getTransformAnchor(type, scaleX < 0, scaleY < 0) + // Set the new corner or position depending on the anchor switch (anchor) { case TransformCorner.TopLeft: { @@ -112,7 +114,11 @@ const circle = createShape({ } case TransformCorner.BottomRight: { shape.radius = Math.min(bounds.width, bounds.height) / 2 - shape.point = [bounds.minX, bounds.minY] + shape.point = [ + bounds.maxX - shape.radius * 2, + bounds.maxY - shape.radius * 2, + ] + break break } case TransformCorner.BottomLeft: { diff --git a/lib/shapes/index.tsx b/lib/shapes/index.tsx index eb7cb3919..bbccca952 100644 --- a/lib/shapes/index.tsx +++ b/lib/shapes/index.tsx @@ -61,14 +61,9 @@ export interface ShapeUtility { bounds: Bounds, info: { type: TransformEdge | TransformCorner - boundsRotation: number initialShape: K - initialShapeBounds: BoundsSnapshot - initialBounds: Bounds - isFlippedX: boolean - isFlippedY: boolean - isSingle: boolean - anchor: TransformEdge | TransformCorner + scaleX: number + scaleY: number } ): K @@ -78,14 +73,9 @@ export interface ShapeUtility { bounds: Bounds, info: { type: TransformEdge | TransformCorner - boundsRotation: number initialShape: K - initialShapeBounds: BoundsSnapshot - initialBounds: Bounds - isFlippedX: boolean - isFlippedY: boolean - isSingle: boolean - anchor: TransformEdge | TransformCorner + scaleX: number + scaleY: number } ): K diff --git a/lib/shapes/rectangle.tsx b/lib/shapes/rectangle.tsx index f13b209b0..d70bbb7e4 100644 --- a/lib/shapes/rectangle.tsx +++ b/lib/shapes/rectangle.tsx @@ -99,142 +99,34 @@ const rectangle = createShape({ return shape }, - transform( - shape, - shapeBounds, - { initialShape, isSingle, initialShapeBounds, isFlippedX, isFlippedY } - ) { - if (shape.rotation === 0 || isSingle) { - shape.size = [shapeBounds.width, shapeBounds.height] - shape.point = [shapeBounds.minX, shapeBounds.minY] + transform(shape, bounds, { initialShape, scaleX, scaleY }) { + if (shape.rotation === 0) { + shape.size = [bounds.width, bounds.height] + shape.point = [bounds.minX, bounds.minY] } else { + // Center shape in resized bounds shape.size = vec.mul( initialShape.size, - Math.min( - shapeBounds.width / initialShapeBounds.width, - shapeBounds.height / initialShapeBounds.height - ) + Math.min(Math.abs(scaleX), Math.abs(scaleY)) ) - const newCenter = [ - shapeBounds.minX + shapeBounds.width / 2, - shapeBounds.minY + shapeBounds.height / 2, - ] - - shape.point = vec.sub(newCenter, vec.div(shape.size, 2)) + shape.point = vec.sub( + vec.med([bounds.minX, bounds.minY], [bounds.maxX, bounds.maxY]), + vec.div(shape.size, 2) + ) } - // Rotation for flipped shapes - + // Set rotation for flipped shapes shape.rotation = initialShape.rotation - - if (isFlippedX) { - shape.rotation *= -1 - } - - if (isFlippedY) { - shape.rotation *= -1 - } + if (scaleX < 0) shape.rotation *= -1 + if (scaleY < 0) shape.rotation *= -1 return shape }, - transformSingle( - shape, - bounds, - { initialShape, initialShapeBounds, anchor, isFlippedY, isFlippedX } - ) { + transformSingle(shape, bounds) { shape.size = [bounds.width, bounds.height] shape.point = [bounds.minX, bounds.minY] - - // const prevCorners = getRotatedCorners( - // initialShapeBounds, - // initialShape.rotation - // ) - - // let currCorners = getRotatedCorners(this.getBounds(shape), shape.rotation) - - // if (isFlippedX) { - // let t = currCorners[3] - // currCorners[3] = currCorners[2] - // currCorners[2] = t - - // t = currCorners[0] - // currCorners[0] = currCorners[1] - // currCorners[1] = t - // } - - // if (isFlippedY) { - // let t = currCorners[3] - // currCorners[3] = currCorners[0] - // currCorners[0] = t - - // t = currCorners[2] - // currCorners[2] = currCorners[1] - // currCorners[1] = t - // } - - // switch (anchor) { - // case TransformCorner.TopLeft: { - // shape.point = vec.sub( - // shape.point, - // vec.sub(currCorners[2], prevCorners[2]) - // ) - // break - // } - // case TransformCorner.TopRight: { - // shape.point = vec.sub( - // shape.point, - // vec.sub(currCorners[3], prevCorners[3]) - // ) - // break - // } - // case TransformCorner.BottomRight: { - // shape.point = vec.sub( - // shape.point, - // vec.sub(currCorners[0], prevCorners[0]) - // ) - // break - // } - // case TransformCorner.BottomLeft: { - // shape.point = vec.sub( - // shape.point, - // vec.sub(currCorners[1], prevCorners[1]) - // ) - // break - // } - // case TransformEdge.Top: { - // shape.point = vec.sub( - // shape.point, - // vec.sub(currCorners[3], prevCorners[3]) - // ) - // break - // } - // case TransformEdge.Right: { - // shape.point = vec.sub( - // shape.point, - // vec.sub(currCorners[3], prevCorners[3]) - // ) - // break - // } - // case TransformEdge.Bottom: { - // shape.point = vec.sub( - // shape.point, - // vec.sub(currCorners[0], prevCorners[0]) - // ) - // break - // } - // case TransformEdge.Left: { - // shape.point = vec.sub( - // shape.point, - // vec.sub(currCorners[2], prevCorners[2]) - // ) - // break - // } - // } - - // console.log(shape.point, shape.size) - return shape }, diff --git a/state/commands/transform-single.ts b/state/commands/transform-single.ts index d7852d1aa..217cda628 100644 --- a/state/commands/transform-single.ts +++ b/state/commands/transform-single.ts @@ -8,63 +8,38 @@ export default function transformSingleCommand( data: Data, before: TransformSingleSnapshot, after: TransformSingleSnapshot, - anchor: TransformCorner | TransformEdge + scaleX: number, + scaleY: number ) { history.execute( data, new Command({ - name: "translate_shapes", + name: "transform_single_shape", category: "canvas", do(data) { - const { + const { id, currentPageId, type, initialShape, initialShapeBounds } = + after + + const shape = data.document.pages[currentPageId].shapes[id] + + getShapeUtils(shape).transformSingle(shape, initialShapeBounds, { type, initialShape, - initialShapeBounds, - currentPageId, - id, - boundsRotation, - } = after - - const { shapes } = data.document.pages[currentPageId] - - const shape = shapes[id] - - getShapeUtils(shape).transform(shape, initialShapeBounds, { - type, - initialShape, - initialShapeBounds, - initialBounds: initialShapeBounds, - boundsRotation, - isFlippedX: false, - isFlippedY: false, - isSingle: false, - anchor, + scaleX, + scaleY, }) }, undo(data) { - const { - type, - initialShape, - initialShapeBounds, - currentPageId, - id, - boundsRotation, - } = before + const { id, currentPageId, type, initialShape, initialShapeBounds } = + before - const { shapes } = data.document.pages[currentPageId] - - const shape = shapes[id] + const shape = data.document.pages[currentPageId].shapes[id] getShapeUtils(shape).transform(shape, initialShapeBounds, { type, - initialShape, - initialShapeBounds, - initialBounds: initialShapeBounds, - boundsRotation, - isFlippedX: false, - isFlippedY: false, - isSingle: false, - anchor, + initialShape: after.initialShape, + scaleX: 1, + scaleY: 1, }) }, }) diff --git a/state/commands/transform.ts b/state/commands/transform.ts index df840d60f..fb3c195f7 100644 --- a/state/commands/transform.ts +++ b/state/commands/transform.ts @@ -8,7 +8,8 @@ export default function transformCommand( data: Data, before: TransformSnapshot, after: TransformSnapshot, - anchor: TransformCorner | TransformEdge + scaleX: number, + scaleY: number ) { history.execute( data, @@ -16,60 +17,32 @@ export default function transformCommand( name: "translate_shapes", category: "canvas", do(data) { - const { - type, - shapeBounds, - initialBounds, - currentPageId, - selectedIds, - boundsRotation, - } = after - - const { shapes } = data.document.pages[currentPageId] + const { type, currentPageId, selectedIds } = after selectedIds.forEach((id) => { - const { initialShape, initialShapeBounds } = shapeBounds[id] - const shape = shapes[id] + const { initialShape, initialShapeBounds } = after.shapeBounds[id] + const shape = data.document.pages[currentPageId].shapes[id] getShapeUtils(shape).transform(shape, initialShapeBounds, { type, initialShape, - initialShapeBounds, - initialBounds, - boundsRotation, - isFlippedX: false, - isFlippedY: false, - isSingle: false, - anchor, + scaleX: 1, + scaleY: 1, }) }) }, undo(data) { - const { - type, - shapeBounds, - initialBounds, - currentPageId, - selectedIds, - boundsRotation, - } = before - - const { shapes } = data.document.pages[currentPageId] + const { type, currentPageId, selectedIds } = before selectedIds.forEach((id) => { - const { initialShape, initialShapeBounds } = shapeBounds[id] - const shape = shapes[id] + const { initialShape, initialShapeBounds } = before.shapeBounds[id] + const shape = data.document.pages[currentPageId].shapes[id] getShapeUtils(shape).transform(shape, initialShapeBounds, { type, initialShape, - initialShapeBounds, - initialBounds, - boundsRotation, - isFlippedX: false, - isFlippedY: false, - isSingle: false, - anchor: type, + scaleX: 1, + scaleY: 1, }) }) }, diff --git a/state/sessions/transform-session.ts b/state/sessions/transform-session.ts index 67c7fe8a1..48d968f65 100644 --- a/state/sessions/transform-session.ts +++ b/state/sessions/transform-session.ts @@ -1,28 +1,21 @@ -import { - Data, - TransformEdge, - TransformCorner, - Bounds, - BoundsSnapshot, -} from "types" +import { Data, TransformEdge, TransformCorner } from "types" import * as vec from "utils/vec" import BaseSession from "./base-session" import commands from "state/commands" import { current } from "immer" import { getShapeUtils } from "lib/shapes" -import { getCommonBounds, getTransformAnchor } from "utils/utils" +import { + getCommonBounds, + getRelativeTransformedBoundingBox, + getTransformedBoundingBox, +} from "utils/utils" export default class TransformSession extends BaseSession { - delta = [0, 0] - isFlippedX = false - isFlippedY = false + scaleX = 1 + scaleY = 1 transformType: TransformEdge | TransformCorner origin: number[] snapshot: TransformSnapshot - corners: { - a: number[] - b: number[] - } constructor( data: Data, @@ -32,196 +25,62 @@ export default class TransformSession extends BaseSession { super(data) this.origin = point this.transformType = transformType - - // if (data.selectedIds.size === 1) { - // const shape = - // data.document.pages[data.currentPageId].shapes[ - // Array.from(data.selectedIds.values())[0] - // ] - - // if (shape.rotation > 0) { - - // } - // } - this.snapshot = getTransformSnapshot(data, transformType) - - const { minX, minY, maxX, maxY } = this.snapshot.initialBounds - - this.corners = { - a: [minX, minY], - b: [maxX, maxY], - } } update(data: Data, point: number[]) { - const { - corners: { a, b }, - transformType, - } = this + const { transformType } = this - const { - boundsRotation, - shapeBounds, + const { currentPageId, selectedIds, shapeBounds, initialBounds } = + this.snapshot + + const newBoundingBox = getTransformedBoundingBox( initialBounds, - currentPageId, - selectedIds, - } = this.snapshot + transformType, + vec.vec(this.origin, point), + data.boundsRotation + ) - const { shapes } = data.document.pages[currentPageId] - - let delta = vec.vec(this.origin, point) - - // if (isSingle) { - // const center = [ - // initialBounds.minX + initialBounds.width / 2, - // initialBounds.minY + initialBounds.height / 2, - // ] - - // const rotation = shapes[Array.from(selectedIds.values())[0]].rotation - - // const rotatedOrigin = vec.rotWith(this.origin, center, -rotation) - // const rotatedPoint = vec.rotWith(point, center, -rotation) - - // delta = vec.vec(rotatedOrigin, rotatedPoint) - // } - - /* - Transforms - - Corners a and b are the original top-left and bottom-right corners of the - bounding box. Depending on what the user is dragging, change one or both - points. To keep things smooth, calculate based by adding the delta (the - vector between the current point and its original point) to the original - bounding box values. - */ - - switch (transformType) { - case TransformEdge.Top: { - a[1] = initialBounds.minY + delta[1] - break - } - case TransformEdge.Right: { - b[0] = initialBounds.maxX + delta[0] - break - } - case TransformEdge.Bottom: { - b[1] = initialBounds.maxY + delta[1] - break - } - case TransformEdge.Left: { - a[0] = initialBounds.minX + delta[0] - break - } - case TransformCorner.TopLeft: { - a[0] = initialBounds.minX + delta[0] - a[1] = initialBounds.minY + delta[1] - break - } - case TransformCorner.TopRight: { - a[1] = initialBounds.minY + delta[1] - b[0] = initialBounds.maxX + delta[0] - break - } - case TransformCorner.BottomRight: { - b[0] = initialBounds.maxX + delta[0] - b[1] = initialBounds.maxY + delta[1] - break - } - case TransformCorner.BottomLeft: { - a[0] = initialBounds.minX + delta[0] - b[1] = initialBounds.maxY + delta[1] - break - } - } - - // Calculate new common (externior) bounding box - const newBounds = { - minX: Math.min(a[0], b[0]), - minY: Math.min(a[1], b[1]), - maxX: Math.max(a[0], b[0]), - maxY: Math.max(a[1], b[1]), - width: Math.abs(b[0] - a[0]), - height: Math.abs(b[1] - a[1]), - } - - this.isFlippedX = b[0] < a[0] - this.isFlippedY = b[1] < a[1] + this.scaleX = newBoundingBox.scaleX + this.scaleY = newBoundingBox.scaleY // Now work backward to calculate a new bounding box for each of the shapes. selectedIds.forEach((id) => { const { initialShape, initialShapeBounds } = shapeBounds[id] - const { nx, nmx, nw, ny, nmy, nh } = initialShapeBounds - const shape = shapes[id] - const minX = - newBounds.minX + (this.isFlippedX ? nmx : nx) * newBounds.width - const minY = - newBounds.minY + (this.isFlippedY ? nmy : ny) * newBounds.height - const width = nw * newBounds.width - const height = nh * newBounds.height + const newShapeBounds = getRelativeTransformedBoundingBox( + newBoundingBox, + initialBounds, + initialShapeBounds, + this.scaleX < 0, + this.scaleY < 0 + ) - const newShapeBounds = { - minX, - minY, - maxX: minX + width, - maxY: minY + height, - width, - height, - isFlippedX: this.isFlippedX, - isFlippedY: this.isFlippedY, - } - - // Pass the new data to the shape's transform utility for mutation. - // Most shapes should be able to transform using only the bounding box, - // however some shapes (e.g. those with internal points) will need more - // data here too. + const shape = data.document.pages[currentPageId].shapes[id] getShapeUtils(shape).transform(shape, newShapeBounds, { type: this.transformType, initialShape, - initialShapeBounds, - initialBounds, - boundsRotation, - isFlippedX: this.isFlippedX, - isFlippedY: this.isFlippedY, - isSingle: false, - anchor: getTransformAnchor( - this.transformType, - this.isFlippedX, - this.isFlippedY - ), + scaleX: this.scaleX, + scaleY: this.scaleY, }) }) } cancel(data: Data) { - const { - shapeBounds, - boundsRotation, - initialBounds, - currentPageId, - selectedIds, - } = this.snapshot - - const { shapes } = data.document.pages[currentPageId] + const { currentPageId, selectedIds, shapeBounds } = this.snapshot selectedIds.forEach((id) => { - const shape = shapes[id] + const shape = data.document.pages[currentPageId].shapes[id] const { initialShape, initialShapeBounds } = shapeBounds[id] getShapeUtils(shape).transform(shape, initialShapeBounds, { type: this.transformType, initialShape, - initialShapeBounds, - initialBounds, - boundsRotation, - isFlippedX: false, - isFlippedY: false, - isSingle: false, - anchor: getTransformAnchor(this.transformType, false, false), + scaleX: 1, + scaleY: 1, }) }) } @@ -231,7 +90,8 @@ export default class TransformSession extends BaseSession { data, this.snapshot, getTransformSnapshot(data, this.transformType), - getTransformAnchor(this.transformType, false, false) + this.scaleX, + this.scaleY ) } } @@ -244,7 +104,6 @@ export function getTransformSnapshot( document: { pages }, selectedIds, currentPageId, - boundsRotation, } = current(data) const pageShapes = pages[currentPageId].shapes @@ -263,27 +122,17 @@ export function getTransformSnapshot( // Return a mapping of shapes to bounds together with the relative // positions of the shape's bounds within the common bounds shape. return { - currentPageId, type: transformType, - initialBounds: bounds, - boundsRotation, + currentPageId, selectedIds: new Set(selectedIds), + initialBounds: bounds, shapeBounds: Object.fromEntries( Array.from(selectedIds.values()).map((id) => { - const { minX, minY, width, height } = shapesBounds[id] return [ id, { initialShape: pageShapes[id], - initialShapeBounds: { - ...shapesBounds[id], - nx: (minX - bounds.minX) / bounds.width, - ny: (minY - bounds.minY) / bounds.height, - nmx: 1 - (minX + width - bounds.minX) / bounds.width, - nmy: 1 - (minY + height - bounds.minY) / bounds.height, - nw: width / bounds.width, - nh: height / bounds.height, - }, + initialShapeBounds: shapesBounds[id], }, ] }) diff --git a/state/sessions/transform-single-session.ts b/state/sessions/transform-single-session.ts index 563d17874..e737640a8 100644 --- a/state/sessions/transform-single-session.ts +++ b/state/sessions/transform-single-session.ts @@ -13,17 +13,11 @@ import { export default class TransformSingleSession extends BaseSession { delta = [0, 0] - isFlippedX = false - isFlippedY = false + scaleX = 1 + scaleY = 1 transformType: TransformEdge | TransformCorner - origin: number[] - center: number[] snapshot: TransformSingleSnapshot - corners: { - a: number[] - b: number[] - } - rotatedCorners: number[][] + origin: number[] constructor( data: Data, @@ -33,168 +27,49 @@ export default class TransformSingleSession extends BaseSession { super(data) this.origin = point this.transformType = transformType - this.snapshot = getTransformSingleSnapshot(data, transformType) - - const { minX, minY, maxX, maxY } = this.snapshot.initialShapeBounds - - this.center = [(minX + maxX) / 2, (minY + maxY) / 2] - - this.corners = { - a: [minX, minY], - b: [maxX, maxY], - } - - this.rotatedCorners = getRotatedCorners( - this.snapshot.initialShapeBounds, - this.snapshot.initialShape.rotation - ) } update(data: Data, point: number[]) { - const { - corners: { a, b }, - transformType, - } = this + const { transformType } = this - const { - boundsRotation, - initialShapeBounds, - currentPageId, - initialShape, - id, - } = this.snapshot + const { initialShapeBounds, currentPageId, initialShape, id } = + this.snapshot - const { shapes } = data.document.pages[currentPageId] - - const shape = shapes[id] - const rotation = shape.rotation - - // 1. Create a new bounding box. - // Counter rotate the delta and apply this to the original bounding box. - - const delta = vec.vec(this.origin, point) - - /* - Transforms - - Corners a and b are the original top-left and bottom-right corners of the - bounding box. Depending on what the user is dragging, change one or both - points. To keep things smooth, calculate based by adding the delta (the - vector between the current point and its original point) to the original - bounding box values. - */ + const shape = data.document.pages[currentPageId].shapes[id] const newBoundingBox = getTransformedBoundingBox( initialShapeBounds, transformType, - delta, + vec.vec(this.origin, point), shape.rotation ) - // console.log(newBoundingBox) - - switch (transformType) { - case TransformEdge.Top: { - a[1] = initialShapeBounds.minY + delta[1] - break - } - case TransformEdge.Right: { - b[0] = initialShapeBounds.maxX + delta[0] - break - } - case TransformEdge.Bottom: { - b[1] = initialShapeBounds.maxY + delta[1] - break - } - case TransformEdge.Left: { - a[0] = initialShapeBounds.minX + delta[0] - break - } - case TransformCorner.TopLeft: { - a[0] = initialShapeBounds.minX + delta[0] - a[1] = initialShapeBounds.minY + delta[1] - break - } - case TransformCorner.TopRight: { - a[1] = initialShapeBounds.minY + delta[1] - b[0] = initialShapeBounds.maxX + delta[0] - break - } - case TransformCorner.BottomRight: { - b[0] = initialShapeBounds.maxX + delta[0] - b[1] = initialShapeBounds.maxY + delta[1] - break - } - case TransformCorner.BottomLeft: { - a[0] = initialShapeBounds.minX + delta[0] - b[1] = initialShapeBounds.maxY + delta[1] - break - } - } - - // Calculate new common (externior) bounding box - const newBounds = { - minX: Math.min(a[0], b[0]), - minY: Math.min(a[1], b[1]), - maxX: Math.max(a[0], b[0]), - maxY: Math.max(a[1], b[1]), - width: Math.abs(b[0] - a[0]), - height: Math.abs(b[1] - a[1]), - } - - this.isFlippedX = b[0] < a[0] - this.isFlippedY = b[1] < a[1] - - const anchor = this.transformType - - // Pass the new data to the shape's transform utility for mutation. - // Most shapes should be able to transform using only the bounding box, - // however some shapes (e.g. those with internal points) will need more - // data here too. + this.scaleX = newBoundingBox.scaleX + this.scaleY = newBoundingBox.scaleY getShapeUtils(shape).transformSingle(shape, newBoundingBox, { - type: this.transformType, initialShape, - initialShapeBounds, - initialBounds: initialShapeBounds, - boundsRotation, - isFlippedX: this.isFlippedX, - isFlippedY: this.isFlippedY, - isSingle: true, - anchor, + type: this.transformType, + scaleX: this.scaleX, + scaleY: this.scaleY, }) } cancel(data: Data) { - const { - id, - boundsRotation, - initialShape, - initialShapeBounds, - currentPageId, - isSingle, - } = this.snapshot + const { id, initialShape, initialShapeBounds, currentPageId } = + this.snapshot const { shapes } = data.document.pages[currentPageId] - // selectedIds.forEach((id) => { - // const shape = shapes[id] + const shape = shapes[id] - // const { initialShape, initialShapeBounds } = shapeBounds[id] - - // getShapeUtils(shape).transform(shape, initialShapeBounds, { - // type: this.transformType, - // initialShape, - // initialShapeBounds, - // initialBounds, - // boundsRotation, - // isFlippedX: false, - // isFlippedY: false, - // isSingle, - // anchor: getTransformAnchor(this.transformType, false, false), - // }) - // }) + getShapeUtils(shape).transform(shape, initialShapeBounds, { + initialShape, + type: this.transformType, + scaleX: this.scaleX, + scaleY: this.scaleY, + }) } complete(data: Data) { @@ -202,7 +77,8 @@ export default class TransformSingleSession extends BaseSession { data, this.snapshot, getTransformSingleSnapshot(data, this.transformType), - getTransformAnchor(this.transformType, false, false) + this.scaleX, + this.scaleY ) } } @@ -217,10 +93,8 @@ export function getTransformSingleSnapshot( currentPageId, } = current(data) - const pageShapes = pages[currentPageId].shapes - const id = Array.from(selectedIds)[0] - const shape = pageShapes[id] + const shape = pages[currentPageId].shapes[id] const bounds = getShapeUtils(shape).getBounds(shape) return { @@ -228,17 +102,7 @@ export function getTransformSingleSnapshot( currentPageId, type: transformType, initialShape: shape, - initialShapeBounds: { - ...bounds, - nx: 0, - ny: 0, - nmx: 1, - nmy: 1, - nw: 1, - nh: 1, - }, - boundsRotation: shape.rotation, - isSingle: true, + initialShapeBounds: bounds, } } diff --git a/state/state.ts b/state/state.ts index 59006b29a..e9b127872 100644 --- a/state/state.ts +++ b/state/state.ts @@ -541,7 +541,7 @@ const state = createState({ ) }, startDrawTransformSession(data, payload: PointerInfo) { - session = new Sessions.TransformSession( + session = new Sessions.TransformSingleSession( data, TransformCorner.BottomRight, screenToWorld(payload.point, data) diff --git a/utils/utils.ts b/utils/utils.ts index 7b750e235..e950be8ae 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -915,6 +915,8 @@ export function getTransformAnchor( anchor = TransformCorner.TopRight } else if (isFlippedY) { anchor = TransformCorner.BottomLeft + } else { + anchor = TransformCorner.BottomRight } break } @@ -925,6 +927,8 @@ export function getTransformAnchor( anchor = TransformCorner.TopLeft } else if (isFlippedY) { anchor = TransformCorner.BottomRight + } else { + anchor = TransformCorner.BottomLeft } break } @@ -935,6 +939,8 @@ export function getTransformAnchor( anchor = TransformCorner.BottomLeft } else if (isFlippedY) { anchor = TransformCorner.TopRight + } else { + anchor = TransformCorner.TopLeft } break } @@ -945,6 +951,8 @@ export function getTransformAnchor( anchor = TransformCorner.BottomRight } else if (isFlippedY) { anchor = TransformCorner.TopLeft + } else { + anchor = TransformCorner.TopRight } break } @@ -1052,54 +1060,34 @@ export function getTransformedBoundingBox( const [dx, dy] = vec.rot(delta, -rotation) // Depending on the dragging handle (an edge or corner of - // the bounding box), find the anchor corner and use the delta - // to adjust the result's corners. - - let anchor: TransformCorner | TransformEdge + // the bounding box), use the delta to adjust the result's corners. switch (handle) { - case TransformEdge.Top: { - anchor = TransformCorner.BottomRight - by0 += dy - break - } - case TransformEdge.Right: { - anchor = TransformCorner.TopLeft - bx1 += dx - break - } - case TransformEdge.Bottom: { - anchor = TransformCorner.TopLeft - by1 += dy - break - } - case TransformEdge.Left: { - anchor = TransformCorner.BottomRight - bx0 += dx - break - } - case TransformCorner.TopLeft: { - anchor = TransformCorner.BottomRight - bx0 += dx - by0 += dy - break - } + case TransformEdge.Top: + case TransformCorner.TopLeft: case TransformCorner.TopRight: { - anchor = TransformCorner.BottomLeft - bx1 += dx by0 += dy break } + case TransformEdge.Bottom: + case TransformCorner.BottomLeft: case TransformCorner.BottomRight: { - anchor = TransformCorner.TopLeft - bx1 += dx by1 += dy break } + } + + switch (handle) { + case TransformEdge.Left: + case TransformCorner.TopLeft: case TransformCorner.BottomLeft: { - anchor = TransformCorner.TopRight bx0 += dx - by1 += dy + break + } + case TransformEdge.Right: + case TransformCorner.TopRight: + case TransformCorner.BottomRight: { + bx1 += dx break } } @@ -1115,35 +1103,39 @@ export function getTransformedBoundingBox( const c0 = vec.med([ax0, ay0], [ax1, ay1]) const c1 = vec.med([bx0, by0], [bx1, by1]) - switch (anchor) { - case TransformCorner.TopLeft: { - cv = vec.sub( - vec.rotWith([bx0, by0], c1, rotation), - vec.rotWith([ax0, ay0], c0, rotation) - ) - break - } - case TransformCorner.TopRight: { - cv = vec.sub( - vec.rotWith([bx1, by0], c1, rotation), - vec.rotWith([ax1, ay0], c0, rotation) - ) - break - } - case TransformCorner.BottomRight: { + switch (handle) { + case TransformCorner.TopLeft: + case TransformEdge.Top: + case TransformEdge.Left: { cv = vec.sub( vec.rotWith([bx1, by1], c1, rotation), vec.rotWith([ax1, ay1], c0, rotation) ) break } - case TransformCorner.BottomLeft: { + case TransformCorner.TopRight: { cv = vec.sub( vec.rotWith([bx0, by1], c1, rotation), vec.rotWith([ax0, ay1], c0, rotation) ) break } + case TransformCorner.BottomRight: + case TransformEdge.Bottom: + case TransformEdge.Right: { + cv = vec.sub( + vec.rotWith([bx0, by0], c1, rotation), + vec.rotWith([ax0, ay0], c0, rotation) + ) + break + } + case TransformCorner.BottomLeft: { + cv = vec.sub( + vec.rotWith([bx1, by0], c1, rotation), + vec.rotWith([ax1, ay0], c0, rotation) + ) + break + } } ;[bx0, by0] = vec.sub([bx0, by0], cv) @@ -1153,6 +1145,9 @@ export function getTransformedBoundingBox( // If the axes are flipped (e.g. if the right edge has been dragged // left past the initial left edge) then swap points on that axis. + let scaleX = (bx1 - bx0) / (ax1 - ax0) + let scaleY = (by1 - by0) / (ay1 - ay0) + if (bx1 < bx0) { ;[bx1, bx0] = [bx0, bx1] } @@ -1168,5 +1163,44 @@ export function getTransformedBoundingBox( maxY: by1, width: bx1 - bx0, height: by1 - by0, + scaleX, + scaleY, + } +} + +export function getRelativeTransformedBoundingBox( + bounds: Bounds, + initialBounds: Bounds, + initialShapeBounds: Bounds, + isFlippedX: boolean, + isFlippedY: boolean +) { + const minX = + bounds.minX + + bounds.width * + ((isFlippedX + ? initialBounds.maxX - initialShapeBounds.maxX + : initialShapeBounds.minX - initialBounds.minX) / + initialBounds.width) + + const minY = + bounds.minY + + bounds.height * + ((isFlippedY + ? initialBounds.maxY - initialShapeBounds.maxY + : initialShapeBounds.minY - initialBounds.minY) / + initialBounds.height) + + const width = (initialShapeBounds.width / initialBounds.width) * bounds.width + const height = + (initialShapeBounds.height / initialBounds.height) * bounds.height + + return { + minX, + minY, + maxX: minX + width, + maxY: minY + height, + width, + height, } }