Adds lines, improves transforms
This commit is contained in:
parent
b50045c9b7
commit
b8d3b35b07
14 changed files with 260 additions and 125 deletions
|
@ -27,7 +27,7 @@ export default function Bounds() {
|
||||||
height={height}
|
height={height}
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
/>
|
/>
|
||||||
{width * zoom > 8 && (
|
{width * zoom > 8 && height * zoom > 8 && (
|
||||||
<>
|
<>
|
||||||
<EdgeHorizontal
|
<EdgeHorizontal
|
||||||
x={minX + p}
|
x={minX + p}
|
||||||
|
|
|
@ -7,13 +7,12 @@ import styled from "styles"
|
||||||
function Shape({ id }: { id: string }) {
|
function Shape({ id }: { id: string }) {
|
||||||
const rGroup = useRef<SVGGElement>(null)
|
const rGroup = useRef<SVGGElement>(null)
|
||||||
|
|
||||||
const shape = useSelector(
|
|
||||||
({ data: { currentPageId, document } }) =>
|
|
||||||
document.pages[currentPageId].shapes[id]
|
|
||||||
)
|
|
||||||
|
|
||||||
const isSelected = useSelector((state) => state.values.selectedIds.has(id))
|
const isSelected = useSelector((state) => state.values.selectedIds.has(id))
|
||||||
|
|
||||||
|
const shape = useSelector(
|
||||||
|
({ data }) => data.document.pages[data.currentPageId].shapes[id]
|
||||||
|
)
|
||||||
|
|
||||||
const handlePointerDown = useCallback(
|
const handlePointerDown = useCallback(
|
||||||
(e: React.PointerEvent) => {
|
(e: React.PointerEvent) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
@ -33,12 +32,12 @@ function Shape({ id }: { id: string }) {
|
||||||
)
|
)
|
||||||
|
|
||||||
const handlePointerEnter = useCallback(
|
const handlePointerEnter = useCallback(
|
||||||
(e: React.PointerEvent) => state.send("HOVERED_SHAPE", { id }),
|
() => state.send("HOVERED_SHAPE", { id }),
|
||||||
[id]
|
[id]
|
||||||
)
|
)
|
||||||
|
|
||||||
const handlePointerLeave = useCallback(
|
const handlePointerLeave = useCallback(
|
||||||
(e: React.PointerEvent) => state.send("UNHOVERED_SHAPE", { id }),
|
() => state.send("UNHOVERED_SHAPE", { id }),
|
||||||
[id]
|
[id]
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
|
|
19
lib/shapes/base-shape.tsx
Normal file
19
lib/shapes/base-shape.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Bounds, Shape } from "types"
|
||||||
|
|
||||||
|
export default interface ShapeUtil<K extends Shape> {
|
||||||
|
create(props: Partial<K>): K
|
||||||
|
getBounds(this: ShapeUtil<K>, shape: K): Bounds
|
||||||
|
hitTest(this: ShapeUtil<K>, shape: K, test: number[]): boolean
|
||||||
|
hitTestBounds(this: ShapeUtil<K>, shape: K, bounds: Bounds): boolean
|
||||||
|
rotate(this: ShapeUtil<K>, shape: K): K
|
||||||
|
translate(this: ShapeUtil<K>, shape: K, delta: number[]): K
|
||||||
|
scale(this: ShapeUtil<K>, shape: K, scale: number): K
|
||||||
|
stretch(this: ShapeUtil<K>, shape: K, scaleX: number, scaleY: number): K
|
||||||
|
render(this: ShapeUtil<K>, shape: K): JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createShape<T extends Shape>(
|
||||||
|
shape: ShapeUtil<T>
|
||||||
|
): ShapeUtil<T> {
|
||||||
|
return shape
|
||||||
|
}
|
|
@ -88,8 +88,12 @@ const circle = createShape<CircleShape>({
|
||||||
},
|
},
|
||||||
|
|
||||||
transform(shape, bounds) {
|
transform(shape, bounds) {
|
||||||
shape.point = [bounds.minX, bounds.minY]
|
// shape.point = [bounds.minX, bounds.minY]
|
||||||
shape.radius = Math.min(bounds.width, bounds.height) / 2
|
shape.radius = Math.min(bounds.width, bounds.height) / 2
|
||||||
|
shape.point = [
|
||||||
|
bounds.minX + bounds.width / 2 - shape.radius,
|
||||||
|
bounds.minY + bounds.height / 2 - shape.radius,
|
||||||
|
]
|
||||||
|
|
||||||
return shape
|
return shape
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,7 +41,14 @@ export interface ShapeUtility<K extends Shape> {
|
||||||
translate(this: ShapeUtility<K>, shape: K, delta: number[]): K
|
translate(this: ShapeUtility<K>, shape: K, delta: number[]): K
|
||||||
|
|
||||||
// Transform to fit a new bounding box.
|
// Transform to fit a new bounding box.
|
||||||
transform(this: ShapeUtility<K>, shape: K, bounds: Bounds): K
|
transform(
|
||||||
|
this: ShapeUtility<K>,
|
||||||
|
shape: K,
|
||||||
|
bounds: Bounds & { isFlippedX: boolean; isFlippedY: boolean },
|
||||||
|
initialShape: K,
|
||||||
|
initialShapeBounds: BoundsSnapshot,
|
||||||
|
initialBounds: Bounds
|
||||||
|
): K
|
||||||
|
|
||||||
// Apply a scale to a shape.
|
// Apply a scale to a shape.
|
||||||
scale(this: ShapeUtility<K>, shape: K, scale: number): K
|
scale(this: ShapeUtility<K>, shape: K, scale: number): K
|
||||||
|
|
|
@ -16,15 +16,23 @@ const line = createShape<LineShape>({
|
||||||
parentId: "page0",
|
parentId: "page0",
|
||||||
childIndex: 0,
|
childIndex: 0,
|
||||||
point: [0, 0],
|
point: [0, 0],
|
||||||
vector: [0, 0],
|
direction: [0, 0],
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
style: {},
|
style: {},
|
||||||
...props,
|
...props,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render({ id }) {
|
render({ id, direction }) {
|
||||||
return <circle id={id} cx={4} cy={4} r={4} />
|
const [x1, y1] = vec.add([0, 0], vec.mul(direction, 100000))
|
||||||
|
const [x2, y2] = vec.sub([0, 0], vec.mul(direction, 100000))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g id={id}>
|
||||||
|
<line x1={x1} y1={y1} x2={x2} y2={y2} />
|
||||||
|
<circle cx={0} cy={0} r={4} />
|
||||||
|
</g>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
getBounds(shape) {
|
getBounds(shape) {
|
||||||
|
@ -38,11 +46,11 @@ const line = createShape<LineShape>({
|
||||||
|
|
||||||
const bounds = {
|
const bounds = {
|
||||||
minX: x,
|
minX: x,
|
||||||
maxX: x + 8,
|
maxX: x + 1,
|
||||||
minY: y,
|
minY: y,
|
||||||
maxY: y + 8,
|
maxY: y + 1,
|
||||||
width: 8,
|
width: 1,
|
||||||
height: 8,
|
height: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.boundsCache.set(shape, bounds)
|
this.boundsCache.set(shape, bounds)
|
||||||
|
@ -55,11 +63,7 @@ const line = createShape<LineShape>({
|
||||||
},
|
},
|
||||||
|
|
||||||
hitTestBounds(this, shape, brushBounds) {
|
hitTestBounds(this, shape, brushBounds) {
|
||||||
const shapeBounds = this.getBounds(shape)
|
return true
|
||||||
return (
|
|
||||||
boundsContained(shapeBounds, brushBounds) ||
|
|
||||||
intersectCircleBounds(shape.point, 4, brushBounds).length > 0
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
rotate(shape) {
|
rotate(shape) {
|
||||||
|
@ -80,6 +84,8 @@ const line = createShape<LineShape>({
|
||||||
},
|
},
|
||||||
|
|
||||||
transform(shape, bounds) {
|
transform(shape, bounds) {
|
||||||
|
shape.point = [bounds.minX, bounds.minY]
|
||||||
|
|
||||||
return shape
|
return shape
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -90,15 +90,20 @@ const polyline = createShape<PolylineShape>({
|
||||||
return shape
|
return shape
|
||||||
},
|
},
|
||||||
|
|
||||||
transform(shape, bounds) {
|
transform(shape, bounds, initialShape, initialShapeBounds) {
|
||||||
const currentBounds = this.getBounds(shape)
|
shape.points = shape.points.map((_, i) => {
|
||||||
|
const [x, y] = initialShape.points[i]
|
||||||
|
|
||||||
const scaleX = bounds.width / currentBounds.width
|
return [
|
||||||
const scaleY = bounds.height / currentBounds.height
|
bounds.width *
|
||||||
|
(bounds.isFlippedX
|
||||||
shape.points = shape.points.map((point) => {
|
? 1 - x / initialShapeBounds.width
|
||||||
let pt = vec.mulV(point, [scaleX, scaleY])
|
: x / initialShapeBounds.width),
|
||||||
return pt
|
bounds.height *
|
||||||
|
(bounds.isFlippedY
|
||||||
|
? 1 - y / initialShapeBounds.height
|
||||||
|
: y / initialShapeBounds.height),
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
shape.point = [bounds.minX, bounds.minY]
|
shape.point = [bounds.minX, bounds.minY]
|
||||||
|
|
|
@ -16,7 +16,7 @@ const ray = createShape<RayShape>({
|
||||||
parentId: "page0",
|
parentId: "page0",
|
||||||
childIndex: 0,
|
childIndex: 0,
|
||||||
point: [0, 0],
|
point: [0, 0],
|
||||||
vector: [0, 0],
|
direction: [0, 0],
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
style: {},
|
style: {},
|
||||||
...props,
|
...props,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import translate from "./translate-command"
|
import translate from "./translate-command"
|
||||||
|
import transform from "./transform-command"
|
||||||
|
|
||||||
const commands = { translate }
|
const commands = { translate, transform }
|
||||||
|
|
||||||
export default commands
|
export default commands
|
||||||
|
|
67
state/commands/transform-command.ts
Normal file
67
state/commands/transform-command.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import Command from "./command"
|
||||||
|
import history from "../history"
|
||||||
|
import { Data } from "types"
|
||||||
|
import { TransformSnapshot } from "state/sessions/transform-session"
|
||||||
|
import { getShapeUtils } from "lib/shapes"
|
||||||
|
|
||||||
|
export default function translateCommand(
|
||||||
|
data: Data,
|
||||||
|
before: TransformSnapshot,
|
||||||
|
after: TransformSnapshot
|
||||||
|
) {
|
||||||
|
history.execute(
|
||||||
|
data,
|
||||||
|
new Command({
|
||||||
|
name: "translate_shapes",
|
||||||
|
category: "canvas",
|
||||||
|
do(data) {
|
||||||
|
const { shapeBounds, initialBounds, currentPageId, selectedIds } = after
|
||||||
|
const { shapes } = data.document.pages[currentPageId]
|
||||||
|
|
||||||
|
selectedIds.forEach((id) => {
|
||||||
|
const { initialShape, initialShapeBounds } = shapeBounds[id]
|
||||||
|
const shape = shapes[id]
|
||||||
|
|
||||||
|
getShapeUtils(shape).transform(
|
||||||
|
shape,
|
||||||
|
{
|
||||||
|
...initialShapeBounds,
|
||||||
|
isFlippedX: false,
|
||||||
|
isFlippedY: false,
|
||||||
|
},
|
||||||
|
initialShape,
|
||||||
|
initialShapeBounds,
|
||||||
|
initialBounds
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
undo(data) {
|
||||||
|
const {
|
||||||
|
shapeBounds,
|
||||||
|
initialBounds,
|
||||||
|
currentPageId,
|
||||||
|
selectedIds,
|
||||||
|
} = before
|
||||||
|
|
||||||
|
const { shapes } = data.document.pages[currentPageId]
|
||||||
|
|
||||||
|
selectedIds.forEach((id) => {
|
||||||
|
const { initialShape, initialShapeBounds } = shapeBounds[id]
|
||||||
|
const shape = shapes[id]
|
||||||
|
|
||||||
|
getShapeUtils(shape).transform(
|
||||||
|
shape,
|
||||||
|
{
|
||||||
|
...initialShapeBounds,
|
||||||
|
isFlippedX: false,
|
||||||
|
isFlippedY: false,
|
||||||
|
},
|
||||||
|
initialShape,
|
||||||
|
initialShapeBounds,
|
||||||
|
initialBounds
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ export const defaultDocument: Data["document"] = {
|
||||||
childIndex: 3,
|
childIndex: 3,
|
||||||
point: [500, 100],
|
point: [500, 100],
|
||||||
style: {
|
style: {
|
||||||
fill: "#aaa",
|
fill: "#AAA",
|
||||||
stroke: "#777",
|
stroke: "#777",
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
},
|
},
|
||||||
|
@ -27,7 +27,7 @@ export const defaultDocument: Data["document"] = {
|
||||||
point: [100, 100],
|
point: [100, 100],
|
||||||
radius: 50,
|
radius: 50,
|
||||||
style: {
|
style: {
|
||||||
fill: "#aaa",
|
fill: "#AAA",
|
||||||
stroke: "#777",
|
stroke: "#777",
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
},
|
},
|
||||||
|
@ -40,7 +40,7 @@ export const defaultDocument: Data["document"] = {
|
||||||
radiusX: 50,
|
radiusX: 50,
|
||||||
radiusY: 30,
|
radiusY: 30,
|
||||||
style: {
|
style: {
|
||||||
fill: "#aaa",
|
fill: "#AAA",
|
||||||
stroke: "#777",
|
stroke: "#777",
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
},
|
},
|
||||||
|
@ -70,7 +70,19 @@ export const defaultDocument: Data["document"] = {
|
||||||
point: [300, 300],
|
point: [300, 300],
|
||||||
size: [200, 200],
|
size: [200, 200],
|
||||||
style: {
|
style: {
|
||||||
fill: "#aaa",
|
fill: "#AAA",
|
||||||
|
stroke: "#777",
|
||||||
|
strokeWidth: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
shape6: shapeUtils[ShapeType.Line].create({
|
||||||
|
id: "shape6",
|
||||||
|
name: "Shape 6",
|
||||||
|
childIndex: 1,
|
||||||
|
point: [400, 400],
|
||||||
|
direction: [0.2, 0.2],
|
||||||
|
style: {
|
||||||
|
fill: "#AAA",
|
||||||
stroke: "#777",
|
stroke: "#777",
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { current } from "immer"
|
import { current } from "immer"
|
||||||
import { ShapeUtil, Bounds, Data, Shapes } from "types"
|
import { ShapeUtil, Bounds, Data, Shapes } from "types"
|
||||||
import BaseSession from "./base-session"
|
import BaseSession from "./base-session"
|
||||||
import shapes from "lib/shapes"
|
import shapes, { getShapeUtils } from "lib/shapes"
|
||||||
import { getBoundsFromPoints } from "utils/utils"
|
import { getBoundsFromPoints } from "utils/utils"
|
||||||
import * as vec from "utils/vec"
|
import * as vec from "utils/vec"
|
||||||
|
|
||||||
|
@ -68,9 +68,7 @@ export default class BrushSession extends BaseSession {
|
||||||
.map((shape) => ({
|
.map((shape) => ({
|
||||||
id: shape.id,
|
id: shape.id,
|
||||||
test: (brushBounds: Bounds): boolean =>
|
test: (brushBounds: Bounds): boolean =>
|
||||||
(shapes[shape.type] as ShapeUtil<
|
getShapeUtils(shape).hitTestBounds(shape, brushBounds),
|
||||||
Shapes[typeof shape.type]
|
|
||||||
>).hitTestBounds(shape, brushBounds),
|
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { Data, TransformEdge, TransformCorner, Bounds } from "types"
|
import {
|
||||||
|
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"
|
||||||
|
@ -11,7 +17,6 @@ export default class TransformSession extends BaseSession {
|
||||||
transformType: TransformEdge | TransformCorner
|
transformType: TransformEdge | TransformCorner
|
||||||
origin: number[]
|
origin: number[]
|
||||||
snapshot: TransformSnapshot
|
snapshot: TransformSnapshot
|
||||||
currentBounds: Bounds
|
|
||||||
corners: {
|
corners: {
|
||||||
a: number[]
|
a: number[]
|
||||||
b: number[]
|
b: number[]
|
||||||
|
@ -29,8 +34,6 @@ export default class TransformSession extends BaseSession {
|
||||||
|
|
||||||
const { minX, minY, maxX, maxY } = this.snapshot.initialBounds
|
const { minX, minY, maxX, maxY } = this.snapshot.initialBounds
|
||||||
|
|
||||||
this.currentBounds = { ...this.snapshot.initialBounds }
|
|
||||||
|
|
||||||
this.corners = {
|
this.corners = {
|
||||||
a: [minX, minY],
|
a: [minX, minY],
|
||||||
b: [maxX, maxY],
|
b: [maxX, maxY],
|
||||||
|
@ -38,130 +41,144 @@ export default class TransformSession extends BaseSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data: Data, point: number[]) {
|
update(data: Data, point: number[]) {
|
||||||
const { shapeBounds, currentPageId, selectedIds } = this.snapshot
|
|
||||||
const {
|
const {
|
||||||
document: { pages },
|
shapeBounds,
|
||||||
} = data
|
initialBounds,
|
||||||
|
currentPageId,
|
||||||
|
selectedIds,
|
||||||
|
} = this.snapshot
|
||||||
|
|
||||||
|
const { shapes } = data.document.pages[currentPageId]
|
||||||
|
|
||||||
let [x, y] = point
|
let [x, y] = point
|
||||||
const { corners, transformType } = this
|
|
||||||
|
const {
|
||||||
|
corners: { a, b },
|
||||||
|
transformType,
|
||||||
|
} = this
|
||||||
|
|
||||||
// Edge Transform
|
// Edge Transform
|
||||||
|
|
||||||
switch (transformType) {
|
switch (transformType) {
|
||||||
case TransformEdge.Top: {
|
case TransformEdge.Top: {
|
||||||
corners.a[1] = y
|
a[1] = y
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case TransformEdge.Right: {
|
case TransformEdge.Right: {
|
||||||
corners.b[0] = x
|
b[0] = x
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case TransformEdge.Bottom: {
|
case TransformEdge.Bottom: {
|
||||||
corners.b[1] = y
|
b[1] = y
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case TransformEdge.Left: {
|
case TransformEdge.Left: {
|
||||||
corners.a[0] = x
|
a[0] = x
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case TransformCorner.TopLeft: {
|
case TransformCorner.TopLeft: {
|
||||||
corners.a[1] = y
|
a[1] = y
|
||||||
corners.a[0] = x
|
a[0] = x
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case TransformCorner.TopRight: {
|
case TransformCorner.TopRight: {
|
||||||
corners.b[0] = x
|
b[0] = x
|
||||||
corners.a[1] = y
|
a[1] = y
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case TransformCorner.BottomRight: {
|
case TransformCorner.BottomRight: {
|
||||||
corners.b[1] = y
|
b[1] = y
|
||||||
corners.b[0] = x
|
b[0] = x
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case TransformCorner.BottomLeft: {
|
case TransformCorner.BottomLeft: {
|
||||||
corners.a[0] = x
|
a[0] = x
|
||||||
corners.b[1] = y
|
b[1] = y
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate new common (externior) bounding box
|
||||||
const newBounds = {
|
const newBounds = {
|
||||||
minX: Math.min(corners.a[0], corners.b[0]),
|
minX: Math.min(a[0], b[0]),
|
||||||
minY: Math.min(corners.a[1], corners.b[1]),
|
minY: Math.min(a[1], b[1]),
|
||||||
maxX: Math.max(corners.a[0], corners.b[0]),
|
maxX: Math.max(a[0], b[0]),
|
||||||
maxY: Math.max(corners.a[1], corners.b[1]),
|
maxY: Math.max(a[1], b[1]),
|
||||||
width: Math.abs(corners.b[0] - corners.a[0]),
|
width: Math.abs(b[0] - a[0]),
|
||||||
height: Math.abs(corners.b[1] - corners.a[1]),
|
height: Math.abs(b[1] - a[1]),
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFlippedX = corners.b[0] - corners.a[0] < 0
|
const isFlippedX = b[0] < a[0]
|
||||||
const isFlippedY = corners.b[1] - corners.a[1] < 0
|
const isFlippedY = b[1] < a[1]
|
||||||
|
|
||||||
// const dx = newBounds.minX - currentBounds.minX
|
// Now work backward to calculate a new bounding box for each of the shapes.
|
||||||
// const dy = newBounds.minY - currentBounds.minY
|
|
||||||
// const scaleX = newBounds.width / currentBounds.width
|
|
||||||
// const scaleY = newBounds.height / currentBounds.height
|
|
||||||
|
|
||||||
this.currentBounds = newBounds
|
|
||||||
|
|
||||||
selectedIds.forEach((id) => {
|
selectedIds.forEach((id) => {
|
||||||
const { nx, nmx, nw, ny, nmy, nh } = shapeBounds[id]
|
const { initialShape, initialShapeBounds } = shapeBounds[id]
|
||||||
|
const { nx, nmx, nw, ny, nmy, nh } = initialShapeBounds
|
||||||
|
const shape = shapes[id]
|
||||||
|
|
||||||
const minX = newBounds.minX + (isFlippedX ? nmx : nx) * newBounds.width
|
const minX = newBounds.minX + (isFlippedX ? nmx : nx) * newBounds.width
|
||||||
const minY = newBounds.minY + (isFlippedY ? nmy : ny) * newBounds.height
|
const minY = newBounds.minY + (isFlippedY ? nmy : ny) * newBounds.height
|
||||||
const width = nw * newBounds.width
|
const width = nw * newBounds.width
|
||||||
const height = nh * newBounds.height
|
const height = nh * newBounds.height
|
||||||
|
|
||||||
const shape = pages[currentPageId].shapes[id]
|
const newShapeBounds = {
|
||||||
|
|
||||||
getShapeUtils(shape).transform(shape, {
|
|
||||||
minX,
|
minX,
|
||||||
minY,
|
minY,
|
||||||
maxX: minX + width,
|
maxX: minX + width,
|
||||||
maxY: minY + height,
|
maxY: minY + height,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
isFlippedX,
|
||||||
|
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,
|
||||||
|
initialShape,
|
||||||
|
initialShapeBounds,
|
||||||
|
initialBounds
|
||||||
|
)
|
||||||
})
|
})
|
||||||
// utils.stretch(shape, scaleX, scaleY)
|
|
||||||
})
|
|
||||||
|
|
||||||
// switch (this.transformHandle) {
|
|
||||||
// case TransformEdge.Top:
|
|
||||||
// case TransformEdge.Left:
|
|
||||||
// case TransformEdge.Right:
|
|
||||||
// case TransformEdge.Bottom: {
|
|
||||||
// for (let id in shapeBounds) {
|
|
||||||
// const { ny, nmy, nh } = shapeBounds[id]
|
|
||||||
// const minY = v.my + (v.y1 < v.y0 ? nmy : ny) * v.mh
|
|
||||||
// const height = nh * v.mh
|
|
||||||
|
|
||||||
// const shape = pages[currentPageId].shapes[id]
|
|
||||||
|
|
||||||
// getShapeUtils(shape).transform(shape)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// case TransformCorner.TopLeft:
|
|
||||||
// case TransformCorner.TopRight:
|
|
||||||
// case TransformCorner.BottomLeft:
|
|
||||||
// case TransformCorner.BottomRight: {
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(data: Data) {
|
cancel(data: Data) {
|
||||||
const { currentPageId } = this.snapshot
|
const {
|
||||||
const { document } = data
|
shapeBounds,
|
||||||
|
initialBounds,
|
||||||
|
currentPageId,
|
||||||
|
selectedIds,
|
||||||
|
} = this.snapshot
|
||||||
|
|
||||||
// for (let id in shapes) {
|
const { shapes } = data.document.pages[currentPageId]
|
||||||
// Restore shape using original bounds
|
|
||||||
// document.pages[currentPageId].shapes[id]
|
selectedIds.forEach((id) => {
|
||||||
// }
|
const shape = shapes.shapes[id]
|
||||||
|
const { initialShape, initialShapeBounds } = shapeBounds[id]
|
||||||
|
|
||||||
|
getShapeUtils(shape).transform(
|
||||||
|
shape,
|
||||||
|
{
|
||||||
|
...initialShapeBounds,
|
||||||
|
isFlippedX: false,
|
||||||
|
isFlippedY: false,
|
||||||
|
},
|
||||||
|
initialShape,
|
||||||
|
initialShapeBounds,
|
||||||
|
initialBounds
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
complete(data: Data) {
|
complete(data: Data) {
|
||||||
// commands.translate(data, this.snapshot, getTransformSnapshot(data))
|
commands.transform(data, this.snapshot, getTransformSnapshot(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,21 +189,18 @@ export function getTransformSnapshot(data: Data) {
|
||||||
currentPageId,
|
currentPageId,
|
||||||
} = current(data)
|
} = current(data)
|
||||||
|
|
||||||
|
const pageShapes = pages[currentPageId].shapes
|
||||||
|
|
||||||
// A mapping of selected shapes and their bounds
|
// A mapping of selected shapes and their bounds
|
||||||
const shapesBounds = Object.fromEntries(
|
const shapesBounds = Object.fromEntries(
|
||||||
Array.from(selectedIds.values()).map((id) => {
|
Array.from(selectedIds.values()).map((id) => {
|
||||||
const shape = pages[currentPageId].shapes[id]
|
const shape = pageShapes[id]
|
||||||
return [shape.id, getShapeUtils(shape).getBounds(shape)]
|
return [shape.id, getShapeUtils(shape).getBounds(shape)]
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
// The common (exterior) bounds of the selected shapes
|
// The common (exterior) bounds of the selected shapes
|
||||||
const bounds = getCommonBounds(
|
const bounds = getCommonBounds(...Object.values(shapesBounds))
|
||||||
...Array.from(selectedIds.values()).map((id) => {
|
|
||||||
const shape = pages[currentPageId].shapes[id]
|
|
||||||
return getShapeUtils(shape).getBounds(shape)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -200,7 +214,9 @@ export function getTransformSnapshot(data: Data) {
|
||||||
return [
|
return [
|
||||||
id,
|
id,
|
||||||
{
|
{
|
||||||
...bounds,
|
initialShape: pageShapes[id],
|
||||||
|
initialShapeBounds: {
|
||||||
|
...shapesBounds[id],
|
||||||
nx: (minX - bounds.minX) / bounds.width,
|
nx: (minX - bounds.minX) / bounds.width,
|
||||||
ny: (minY - bounds.minY) / bounds.height,
|
ny: (minY - bounds.minY) / bounds.height,
|
||||||
nmx: 1 - (minX + width - bounds.minX) / bounds.width,
|
nmx: 1 - (minX + width - bounds.minX) / bounds.width,
|
||||||
|
@ -208,6 +224,7 @@ export function getTransformSnapshot(data: Data) {
|
||||||
nw: width / bounds.width,
|
nw: width / bounds.width,
|
||||||
nh: height / bounds.height,
|
nh: height / bounds.height,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|
4
types.ts
4
types.ts
|
@ -65,12 +65,12 @@ export interface EllipseShape extends BaseShape {
|
||||||
|
|
||||||
export interface LineShape extends BaseShape {
|
export interface LineShape extends BaseShape {
|
||||||
type: ShapeType.Line
|
type: ShapeType.Line
|
||||||
vector: number[]
|
direction: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RayShape extends BaseShape {
|
export interface RayShape extends BaseShape {
|
||||||
type: ShapeType.Ray
|
type: ShapeType.Ray
|
||||||
vector: number[]
|
direction: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PolylineShape extends BaseShape {
|
export interface PolylineShape extends BaseShape {
|
||||||
|
|
Loading…
Reference in a new issue