tldraw/state/sessions/transform-single-session.ts
2021-05-19 10:35:00 +01:00

247 lines
6.1 KiB
TypeScript

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 {
getTransformedBoundingBox,
getCommonBounds,
getRotatedCorners,
getTransformAnchor,
} from "utils/utils"
export default class TransformSingleSession extends BaseSession {
delta = [0, 0]
isFlippedX = false
isFlippedY = false
transformType: TransformEdge | TransformCorner
origin: number[]
center: number[]
snapshot: TransformSingleSnapshot
corners: {
a: number[]
b: number[]
}
rotatedCorners: number[][]
constructor(
data: Data,
transformType: TransformCorner | TransformEdge,
point: number[]
) {
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 {
boundsRotation,
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 newBoundingBox = getTransformedBoundingBox(
initialShapeBounds,
transformType,
delta,
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.
getShapeUtils(shape).transformSingle(shape, newBoundingBox, {
type: this.transformType,
initialShape,
initialShapeBounds,
initialBounds: initialShapeBounds,
boundsRotation,
isFlippedX: this.isFlippedX,
isFlippedY: this.isFlippedY,
isSingle: true,
anchor,
})
}
cancel(data: Data) {
const {
id,
boundsRotation,
initialShape,
initialShapeBounds,
currentPageId,
isSingle,
} = this.snapshot
const { shapes } = data.document.pages[currentPageId]
// selectedIds.forEach((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),
// })
// })
}
complete(data: Data) {
commands.transformSingle(
data,
this.snapshot,
getTransformSingleSnapshot(data, this.transformType),
getTransformAnchor(this.transformType, false, false)
)
}
}
export function getTransformSingleSnapshot(
data: Data,
transformType: TransformEdge | TransformCorner
) {
const {
document: { pages },
selectedIds,
currentPageId,
} = current(data)
const pageShapes = pages[currentPageId].shapes
const id = Array.from(selectedIds)[0]
const shape = pageShapes[id]
const bounds = getShapeUtils(shape).getBounds(shape)
return {
id,
currentPageId,
type: transformType,
initialShape: shape,
initialShapeBounds: {
...bounds,
nx: 0,
ny: 0,
nmx: 1,
nmy: 1,
nw: 1,
nh: 1,
},
boundsRotation: shape.rotation,
isSingle: true,
}
}
export type TransformSingleSnapshot = ReturnType<
typeof getTransformSingleSnapshot
>