Improves rotated transforms, cleans up code

This commit is contained in:
Steve Ruiz 2021-05-19 13:27:01 +01:00
parent c3740cacdd
commit 0c205d1377
9 changed files with 208 additions and 625 deletions

View file

@ -5,7 +5,7 @@ import { createShape } from "./index"
import { boundsContained } from "utils/bounds" import { boundsContained } from "utils/bounds"
import { intersectCircleBounds } from "utils/intersections" import { intersectCircleBounds } from "utils/intersections"
import { pointInCircle } from "utils/hitTests" import { pointInCircle } from "utils/hitTests"
import { translateBounds } from "utils/utils" import { getTransformAnchor, translateBounds } from "utils/utils"
const circle = createShape<CircleShape>({ const circle = createShape<CircleShape>({
boundsCache: new WeakMap([]), boundsCache: new WeakMap([]),
@ -94,7 +94,9 @@ const circle = createShape<CircleShape>({
return shape 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 // Set the new corner or position depending on the anchor
switch (anchor) { switch (anchor) {
case TransformCorner.TopLeft: { case TransformCorner.TopLeft: {
@ -112,7 +114,11 @@ const circle = createShape<CircleShape>({
} }
case TransformCorner.BottomRight: { case TransformCorner.BottomRight: {
shape.radius = Math.min(bounds.width, bounds.height) / 2 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 break
} }
case TransformCorner.BottomLeft: { case TransformCorner.BottomLeft: {

View file

@ -61,14 +61,9 @@ export interface ShapeUtility<K extends Shape> {
bounds: Bounds, bounds: Bounds,
info: { info: {
type: TransformEdge | TransformCorner type: TransformEdge | TransformCorner
boundsRotation: number
initialShape: K initialShape: K
initialShapeBounds: BoundsSnapshot scaleX: number
initialBounds: Bounds scaleY: number
isFlippedX: boolean
isFlippedY: boolean
isSingle: boolean
anchor: TransformEdge | TransformCorner
} }
): K ): K
@ -78,14 +73,9 @@ export interface ShapeUtility<K extends Shape> {
bounds: Bounds, bounds: Bounds,
info: { info: {
type: TransformEdge | TransformCorner type: TransformEdge | TransformCorner
boundsRotation: number
initialShape: K initialShape: K
initialShapeBounds: BoundsSnapshot scaleX: number
initialBounds: Bounds scaleY: number
isFlippedX: boolean
isFlippedY: boolean
isSingle: boolean
anchor: TransformEdge | TransformCorner
} }
): K ): K

View file

@ -99,142 +99,34 @@ const rectangle = createShape<RectangleShape>({
return shape return shape
}, },
transform( transform(shape, bounds, { initialShape, scaleX, scaleY }) {
shape, if (shape.rotation === 0) {
shapeBounds, shape.size = [bounds.width, bounds.height]
{ initialShape, isSingle, initialShapeBounds, isFlippedX, isFlippedY } shape.point = [bounds.minX, bounds.minY]
) {
if (shape.rotation === 0 || isSingle) {
shape.size = [shapeBounds.width, shapeBounds.height]
shape.point = [shapeBounds.minX, shapeBounds.minY]
} else { } else {
// Center shape in resized bounds
shape.size = vec.mul( shape.size = vec.mul(
initialShape.size, initialShape.size,
Math.min( Math.min(Math.abs(scaleX), Math.abs(scaleY))
shapeBounds.width / initialShapeBounds.width,
shapeBounds.height / initialShapeBounds.height
)
) )
const newCenter = [ shape.point = vec.sub(
shapeBounds.minX + shapeBounds.width / 2, vec.med([bounds.minX, bounds.minY], [bounds.maxX, bounds.maxY]),
shapeBounds.minY + shapeBounds.height / 2, vec.div(shape.size, 2)
] )
shape.point = vec.sub(newCenter, vec.div(shape.size, 2))
} }
// Rotation for flipped shapes // Set rotation for flipped shapes
shape.rotation = initialShape.rotation shape.rotation = initialShape.rotation
if (scaleX < 0) shape.rotation *= -1
if (isFlippedX) { if (scaleY < 0) shape.rotation *= -1
shape.rotation *= -1
}
if (isFlippedY) {
shape.rotation *= -1
}
return shape return shape
}, },
transformSingle( transformSingle(shape, bounds) {
shape,
bounds,
{ initialShape, initialShapeBounds, anchor, isFlippedY, isFlippedX }
) {
shape.size = [bounds.width, bounds.height] shape.size = [bounds.width, bounds.height]
shape.point = [bounds.minX, bounds.minY] 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 return shape
}, },

View file

@ -8,63 +8,38 @@ export default function transformSingleCommand(
data: Data, data: Data,
before: TransformSingleSnapshot, before: TransformSingleSnapshot,
after: TransformSingleSnapshot, after: TransformSingleSnapshot,
anchor: TransformCorner | TransformEdge scaleX: number,
scaleY: number
) { ) {
history.execute( history.execute(
data, data,
new Command({ new Command({
name: "translate_shapes", name: "transform_single_shape",
category: "canvas", category: "canvas",
do(data) { do(data) {
const { const { id, currentPageId, type, initialShape, initialShapeBounds } =
after
const shape = data.document.pages[currentPageId].shapes[id]
getShapeUtils(shape).transformSingle(shape, initialShapeBounds, {
type, type,
initialShape, initialShape,
initialShapeBounds, scaleX,
currentPageId, scaleY,
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,
}) })
}, },
undo(data) { undo(data) {
const { const { id, currentPageId, type, initialShape, initialShapeBounds } =
type, before
initialShape,
initialShapeBounds,
currentPageId,
id,
boundsRotation,
} = before
const { shapes } = data.document.pages[currentPageId] const shape = data.document.pages[currentPageId].shapes[id]
const shape = shapes[id]
getShapeUtils(shape).transform(shape, initialShapeBounds, { getShapeUtils(shape).transform(shape, initialShapeBounds, {
type, type,
initialShape, initialShape: after.initialShape,
initialShapeBounds, scaleX: 1,
initialBounds: initialShapeBounds, scaleY: 1,
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle: false,
anchor,
}) })
}, },
}) })

View file

@ -8,7 +8,8 @@ export default function transformCommand(
data: Data, data: Data,
before: TransformSnapshot, before: TransformSnapshot,
after: TransformSnapshot, after: TransformSnapshot,
anchor: TransformCorner | TransformEdge scaleX: number,
scaleY: number
) { ) {
history.execute( history.execute(
data, data,
@ -16,60 +17,32 @@ export default function transformCommand(
name: "translate_shapes", name: "translate_shapes",
category: "canvas", category: "canvas",
do(data) { do(data) {
const { const { type, currentPageId, selectedIds } = after
type,
shapeBounds,
initialBounds,
currentPageId,
selectedIds,
boundsRotation,
} = after
const { shapes } = data.document.pages[currentPageId]
selectedIds.forEach((id) => { selectedIds.forEach((id) => {
const { initialShape, initialShapeBounds } = shapeBounds[id] const { initialShape, initialShapeBounds } = after.shapeBounds[id]
const shape = shapes[id] const shape = data.document.pages[currentPageId].shapes[id]
getShapeUtils(shape).transform(shape, initialShapeBounds, { getShapeUtils(shape).transform(shape, initialShapeBounds, {
type, type,
initialShape, initialShape,
initialShapeBounds, scaleX: 1,
initialBounds, scaleY: 1,
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle: false,
anchor,
}) })
}) })
}, },
undo(data) { undo(data) {
const { const { type, currentPageId, selectedIds } = before
type,
shapeBounds,
initialBounds,
currentPageId,
selectedIds,
boundsRotation,
} = before
const { shapes } = data.document.pages[currentPageId]
selectedIds.forEach((id) => { selectedIds.forEach((id) => {
const { initialShape, initialShapeBounds } = shapeBounds[id] const { initialShape, initialShapeBounds } = before.shapeBounds[id]
const shape = shapes[id] const shape = data.document.pages[currentPageId].shapes[id]
getShapeUtils(shape).transform(shape, initialShapeBounds, { getShapeUtils(shape).transform(shape, initialShapeBounds, {
type, type,
initialShape, initialShape,
initialShapeBounds, scaleX: 1,
initialBounds, scaleY: 1,
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle: false,
anchor: type,
}) })
}) })
}, },

View file

@ -1,28 +1,21 @@
import { import { Data, TransformEdge, TransformCorner } from "types"
Data,
TransformEdge,
TransformCorner,
Bounds,
BoundsSnapshot,
} from "types"
import * as vec from "utils/vec" import * as vec from "utils/vec"
import BaseSession from "./base-session" import BaseSession from "./base-session"
import commands from "state/commands" import commands from "state/commands"
import { current } from "immer" import { current } from "immer"
import { getShapeUtils } from "lib/shapes" import { getShapeUtils } from "lib/shapes"
import { getCommonBounds, getTransformAnchor } from "utils/utils" import {
getCommonBounds,
getRelativeTransformedBoundingBox,
getTransformedBoundingBox,
} from "utils/utils"
export default class TransformSession extends BaseSession { export default class TransformSession extends BaseSession {
delta = [0, 0] scaleX = 1
isFlippedX = false scaleY = 1
isFlippedY = false
transformType: TransformEdge | TransformCorner transformType: TransformEdge | TransformCorner
origin: number[] origin: number[]
snapshot: TransformSnapshot snapshot: TransformSnapshot
corners: {
a: number[]
b: number[]
}
constructor( constructor(
data: Data, data: Data,
@ -32,196 +25,62 @@ export default class TransformSession extends BaseSession {
super(data) super(data)
this.origin = point this.origin = point
this.transformType = transformType 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) 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[]) { update(data: Data, point: number[]) {
const { const { transformType } = this
corners: { a, b },
transformType,
} = this
const { const { currentPageId, selectedIds, shapeBounds, initialBounds } =
boundsRotation, this.snapshot
shapeBounds,
const newBoundingBox = getTransformedBoundingBox(
initialBounds, initialBounds,
currentPageId, transformType,
selectedIds, vec.vec(this.origin, point),
} = this.snapshot data.boundsRotation
)
const { shapes } = data.document.pages[currentPageId] this.scaleX = newBoundingBox.scaleX
this.scaleY = newBoundingBox.scaleY
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]
// Now work backward to calculate a new bounding box for each of the shapes. // Now work backward to calculate a new bounding box for each of the shapes.
selectedIds.forEach((id) => { selectedIds.forEach((id) => {
const { initialShape, initialShapeBounds } = shapeBounds[id] const { initialShape, initialShapeBounds } = shapeBounds[id]
const { nx, nmx, nw, ny, nmy, nh } = initialShapeBounds
const shape = shapes[id]
const minX = const newShapeBounds = getRelativeTransformedBoundingBox(
newBounds.minX + (this.isFlippedX ? nmx : nx) * newBounds.width newBoundingBox,
const minY = initialBounds,
newBounds.minY + (this.isFlippedY ? nmy : ny) * newBounds.height initialShapeBounds,
const width = nw * newBounds.width this.scaleX < 0,
const height = nh * newBounds.height this.scaleY < 0
)
const newShapeBounds = { const shape = data.document.pages[currentPageId].shapes[id]
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.
getShapeUtils(shape).transform(shape, newShapeBounds, { getShapeUtils(shape).transform(shape, newShapeBounds, {
type: this.transformType, type: this.transformType,
initialShape, initialShape,
initialShapeBounds, scaleX: this.scaleX,
initialBounds, scaleY: this.scaleY,
boundsRotation,
isFlippedX: this.isFlippedX,
isFlippedY: this.isFlippedY,
isSingle: false,
anchor: getTransformAnchor(
this.transformType,
this.isFlippedX,
this.isFlippedY
),
}) })
}) })
} }
cancel(data: Data) { cancel(data: Data) {
const { const { currentPageId, selectedIds, shapeBounds } = this.snapshot
shapeBounds,
boundsRotation,
initialBounds,
currentPageId,
selectedIds,
} = this.snapshot
const { shapes } = data.document.pages[currentPageId]
selectedIds.forEach((id) => { selectedIds.forEach((id) => {
const shape = shapes[id] const shape = data.document.pages[currentPageId].shapes[id]
const { initialShape, initialShapeBounds } = shapeBounds[id] const { initialShape, initialShapeBounds } = shapeBounds[id]
getShapeUtils(shape).transform(shape, initialShapeBounds, { getShapeUtils(shape).transform(shape, initialShapeBounds, {
type: this.transformType, type: this.transformType,
initialShape, initialShape,
initialShapeBounds, scaleX: 1,
initialBounds, scaleY: 1,
boundsRotation,
isFlippedX: false,
isFlippedY: false,
isSingle: false,
anchor: getTransformAnchor(this.transformType, false, false),
}) })
}) })
} }
@ -231,7 +90,8 @@ export default class TransformSession extends BaseSession {
data, data,
this.snapshot, this.snapshot,
getTransformSnapshot(data, this.transformType), getTransformSnapshot(data, this.transformType),
getTransformAnchor(this.transformType, false, false) this.scaleX,
this.scaleY
) )
} }
} }
@ -244,7 +104,6 @@ export function getTransformSnapshot(
document: { pages }, document: { pages },
selectedIds, selectedIds,
currentPageId, currentPageId,
boundsRotation,
} = current(data) } = current(data)
const pageShapes = pages[currentPageId].shapes const pageShapes = pages[currentPageId].shapes
@ -263,27 +122,17 @@ export function getTransformSnapshot(
// Return a mapping of shapes to bounds together with the relative // Return a mapping of shapes to bounds together with the relative
// positions of the shape's bounds within the common bounds shape. // positions of the shape's bounds within the common bounds shape.
return { return {
currentPageId,
type: transformType, type: transformType,
initialBounds: bounds, currentPageId,
boundsRotation,
selectedIds: new Set(selectedIds), selectedIds: new Set(selectedIds),
initialBounds: bounds,
shapeBounds: Object.fromEntries( shapeBounds: Object.fromEntries(
Array.from(selectedIds.values()).map((id) => { Array.from(selectedIds.values()).map((id) => {
const { minX, minY, width, height } = shapesBounds[id]
return [ return [
id, id,
{ {
initialShape: pageShapes[id], initialShape: pageShapes[id],
initialShapeBounds: { initialShapeBounds: shapesBounds[id],
...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,
},
}, },
] ]
}) })

View file

@ -13,17 +13,11 @@ import {
export default class TransformSingleSession extends BaseSession { export default class TransformSingleSession extends BaseSession {
delta = [0, 0] delta = [0, 0]
isFlippedX = false scaleX = 1
isFlippedY = false scaleY = 1
transformType: TransformEdge | TransformCorner transformType: TransformEdge | TransformCorner
origin: number[]
center: number[]
snapshot: TransformSingleSnapshot snapshot: TransformSingleSnapshot
corners: { origin: number[]
a: number[]
b: number[]
}
rotatedCorners: number[][]
constructor( constructor(
data: Data, data: Data,
@ -33,168 +27,49 @@ export default class TransformSingleSession extends BaseSession {
super(data) super(data)
this.origin = point this.origin = point
this.transformType = transformType this.transformType = transformType
this.snapshot = getTransformSingleSnapshot(data, 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[]) { update(data: Data, point: number[]) {
const { const { transformType } = this
corners: { a, b },
transformType,
} = this
const { const { initialShapeBounds, currentPageId, initialShape, id } =
boundsRotation, this.snapshot
initialShapeBounds,
currentPageId,
initialShape,
id,
} = this.snapshot
const { shapes } = data.document.pages[currentPageId] const shape = data.document.pages[currentPageId].shapes[id]
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 newBoundingBox = getTransformedBoundingBox( const newBoundingBox = getTransformedBoundingBox(
initialShapeBounds, initialShapeBounds,
transformType, transformType,
delta, vec.vec(this.origin, point),
shape.rotation shape.rotation
) )
// console.log(newBoundingBox) this.scaleX = newBoundingBox.scaleX
this.scaleY = newBoundingBox.scaleY
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.
getShapeUtils(shape).transformSingle(shape, newBoundingBox, { getShapeUtils(shape).transformSingle(shape, newBoundingBox, {
type: this.transformType,
initialShape, initialShape,
initialShapeBounds, type: this.transformType,
initialBounds: initialShapeBounds, scaleX: this.scaleX,
boundsRotation, scaleY: this.scaleY,
isFlippedX: this.isFlippedX,
isFlippedY: this.isFlippedY,
isSingle: true,
anchor,
}) })
} }
cancel(data: Data) { cancel(data: Data) {
const { const { id, initialShape, initialShapeBounds, currentPageId } =
id, this.snapshot
boundsRotation,
initialShape,
initialShapeBounds,
currentPageId,
isSingle,
} = this.snapshot
const { shapes } = data.document.pages[currentPageId] 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, {
initialShape,
// getShapeUtils(shape).transform(shape, initialShapeBounds, { type: this.transformType,
// type: this.transformType, scaleX: this.scaleX,
// initialShape, scaleY: this.scaleY,
// initialShapeBounds, })
// initialBounds,
// boundsRotation,
// isFlippedX: false,
// isFlippedY: false,
// isSingle,
// anchor: getTransformAnchor(this.transformType, false, false),
// })
// })
} }
complete(data: Data) { complete(data: Data) {
@ -202,7 +77,8 @@ export default class TransformSingleSession extends BaseSession {
data, data,
this.snapshot, this.snapshot,
getTransformSingleSnapshot(data, this.transformType), getTransformSingleSnapshot(data, this.transformType),
getTransformAnchor(this.transformType, false, false) this.scaleX,
this.scaleY
) )
} }
} }
@ -217,10 +93,8 @@ export function getTransformSingleSnapshot(
currentPageId, currentPageId,
} = current(data) } = current(data)
const pageShapes = pages[currentPageId].shapes
const id = Array.from(selectedIds)[0] const id = Array.from(selectedIds)[0]
const shape = pageShapes[id] const shape = pages[currentPageId].shapes[id]
const bounds = getShapeUtils(shape).getBounds(shape) const bounds = getShapeUtils(shape).getBounds(shape)
return { return {
@ -228,17 +102,7 @@ export function getTransformSingleSnapshot(
currentPageId, currentPageId,
type: transformType, type: transformType,
initialShape: shape, initialShape: shape,
initialShapeBounds: { initialShapeBounds: bounds,
...bounds,
nx: 0,
ny: 0,
nmx: 1,
nmy: 1,
nw: 1,
nh: 1,
},
boundsRotation: shape.rotation,
isSingle: true,
} }
} }

View file

@ -541,7 +541,7 @@ const state = createState({
) )
}, },
startDrawTransformSession(data, payload: PointerInfo) { startDrawTransformSession(data, payload: PointerInfo) {
session = new Sessions.TransformSession( session = new Sessions.TransformSingleSession(
data, data,
TransformCorner.BottomRight, TransformCorner.BottomRight,
screenToWorld(payload.point, data) screenToWorld(payload.point, data)

View file

@ -915,6 +915,8 @@ export function getTransformAnchor(
anchor = TransformCorner.TopRight anchor = TransformCorner.TopRight
} else if (isFlippedY) { } else if (isFlippedY) {
anchor = TransformCorner.BottomLeft anchor = TransformCorner.BottomLeft
} else {
anchor = TransformCorner.BottomRight
} }
break break
} }
@ -925,6 +927,8 @@ export function getTransformAnchor(
anchor = TransformCorner.TopLeft anchor = TransformCorner.TopLeft
} else if (isFlippedY) { } else if (isFlippedY) {
anchor = TransformCorner.BottomRight anchor = TransformCorner.BottomRight
} else {
anchor = TransformCorner.BottomLeft
} }
break break
} }
@ -935,6 +939,8 @@ export function getTransformAnchor(
anchor = TransformCorner.BottomLeft anchor = TransformCorner.BottomLeft
} else if (isFlippedY) { } else if (isFlippedY) {
anchor = TransformCorner.TopRight anchor = TransformCorner.TopRight
} else {
anchor = TransformCorner.TopLeft
} }
break break
} }
@ -945,6 +951,8 @@ export function getTransformAnchor(
anchor = TransformCorner.BottomRight anchor = TransformCorner.BottomRight
} else if (isFlippedY) { } else if (isFlippedY) {
anchor = TransformCorner.TopLeft anchor = TransformCorner.TopLeft
} else {
anchor = TransformCorner.TopRight
} }
break break
} }
@ -1052,54 +1060,34 @@ export function getTransformedBoundingBox(
const [dx, dy] = vec.rot(delta, -rotation) const [dx, dy] = vec.rot(delta, -rotation)
// Depending on the dragging handle (an edge or corner of // Depending on the dragging handle (an edge or corner of
// the bounding box), find the anchor corner and use the delta // the bounding box), use the delta to adjust the result's corners.
// to adjust the result's corners.
let anchor: TransformCorner | TransformEdge
switch (handle) { switch (handle) {
case TransformEdge.Top: { case TransformEdge.Top:
anchor = TransformCorner.BottomRight case TransformCorner.TopLeft:
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 TransformCorner.TopRight: { case TransformCorner.TopRight: {
anchor = TransformCorner.BottomLeft
bx1 += dx
by0 += dy by0 += dy
break break
} }
case TransformEdge.Bottom:
case TransformCorner.BottomLeft:
case TransformCorner.BottomRight: { case TransformCorner.BottomRight: {
anchor = TransformCorner.TopLeft
bx1 += dx
by1 += dy by1 += dy
break break
} }
}
switch (handle) {
case TransformEdge.Left:
case TransformCorner.TopLeft:
case TransformCorner.BottomLeft: { case TransformCorner.BottomLeft: {
anchor = TransformCorner.TopRight
bx0 += dx bx0 += dx
by1 += dy break
}
case TransformEdge.Right:
case TransformCorner.TopRight:
case TransformCorner.BottomRight: {
bx1 += dx
break break
} }
} }
@ -1115,35 +1103,39 @@ export function getTransformedBoundingBox(
const c0 = vec.med([ax0, ay0], [ax1, ay1]) const c0 = vec.med([ax0, ay0], [ax1, ay1])
const c1 = vec.med([bx0, by0], [bx1, by1]) const c1 = vec.med([bx0, by0], [bx1, by1])
switch (anchor) { switch (handle) {
case TransformCorner.TopLeft: { case TransformCorner.TopLeft:
cv = vec.sub( case TransformEdge.Top:
vec.rotWith([bx0, by0], c1, rotation), case TransformEdge.Left: {
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: {
cv = vec.sub( cv = vec.sub(
vec.rotWith([bx1, by1], c1, rotation), vec.rotWith([bx1, by1], c1, rotation),
vec.rotWith([ax1, ay1], c0, rotation) vec.rotWith([ax1, ay1], c0, rotation)
) )
break break
} }
case TransformCorner.BottomLeft: { case TransformCorner.TopRight: {
cv = vec.sub( cv = vec.sub(
vec.rotWith([bx0, by1], c1, rotation), vec.rotWith([bx0, by1], c1, rotation),
vec.rotWith([ax0, ay1], c0, rotation) vec.rotWith([ax0, ay1], c0, rotation)
) )
break 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) ;[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 // 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. // 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) { if (bx1 < bx0) {
;[bx1, bx0] = [bx0, bx1] ;[bx1, bx0] = [bx0, bx1]
} }
@ -1168,5 +1163,44 @@ export function getTransformedBoundingBox(
maxY: by1, maxY: by1,
width: bx1 - bx0, width: bx1 - bx0,
height: by1 - by0, 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,
} }
} }