Improves rotated transforms, cleans up code
This commit is contained in:
parent
c3740cacdd
commit
0c205d1377
9 changed files with 208 additions and 625 deletions
|
@ -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<CircleShape>({
|
||||
boundsCache: new WeakMap([]),
|
||||
|
@ -94,7 +94,9 @@ const circle = createShape<CircleShape>({
|
|||
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<CircleShape>({
|
|||
}
|
||||
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: {
|
||||
|
|
|
@ -61,14 +61,9 @@ export interface ShapeUtility<K extends Shape> {
|
|||
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<K extends Shape> {
|
|||
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
|
||||
|
||||
|
|
|
@ -99,142 +99,34 @@ const rectangle = createShape<RectangleShape>({
|
|||
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
|
||||
},
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
},
|
||||
|
|
|
@ -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],
|
||||
},
|
||||
]
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
140
utils/utils.ts
140
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,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue