improves arrow rotation
This commit is contained in:
parent
554d55a9e3
commit
81141e7bb5
9 changed files with 179 additions and 86 deletions
|
@ -30,7 +30,6 @@ export default function Handles() {
|
|||
{Object.values(shape.handles).map((handle) => (
|
||||
<Handle
|
||||
key={handle.id}
|
||||
shapeId={shape.id}
|
||||
id={handle.id}
|
||||
point={vec.add(handle.point, shape.point)}
|
||||
/>
|
||||
|
@ -39,15 +38,7 @@ export default function Handles() {
|
|||
)
|
||||
}
|
||||
|
||||
function Handle({
|
||||
shapeId,
|
||||
id,
|
||||
point,
|
||||
}: {
|
||||
shapeId: string
|
||||
id: string
|
||||
point: number[]
|
||||
}) {
|
||||
function Handle({ id, point }: { id: string; point: number[] }) {
|
||||
const rGroup = useRef<SVGGElement>(null)
|
||||
const events = useHandleEvents(id, rGroup)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as vec from 'utils/vec'
|
|||
import * as svg from 'utils/svg'
|
||||
import {
|
||||
ArrowShape,
|
||||
Bounds,
|
||||
ColorStyle,
|
||||
DashStyle,
|
||||
ShapeHandle,
|
||||
|
@ -10,7 +11,13 @@ import {
|
|||
SizeStyle,
|
||||
} from 'types'
|
||||
import { registerShapeUtils } from './index'
|
||||
import { circleFromThreePoints, clamp, isAngleBetween } from 'utils/utils'
|
||||
import {
|
||||
circleFromThreePoints,
|
||||
clamp,
|
||||
getBoundsCenter,
|
||||
isAngleBetween,
|
||||
rotateBounds,
|
||||
} from 'utils/utils'
|
||||
import { pointInBounds } from 'utils/bounds'
|
||||
import {
|
||||
intersectArcBounds,
|
||||
|
@ -170,25 +177,27 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
)
|
||||
},
|
||||
|
||||
rotateBy(shape, delta) {
|
||||
const { start, end, bend } = shape.handles
|
||||
const mp = vec.med(start.point, end.point)
|
||||
start.point = vec.rotWith(start.point, mp, delta)
|
||||
end.point = vec.rotWith(end.point, mp, delta)
|
||||
bend.point = vec.rotWith(bend.point, mp, delta)
|
||||
|
||||
this.onHandleChange(shape, shape.handles)
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
rotateTo(shape, rotation, delta) {
|
||||
const { start, end, bend } = shape.handles
|
||||
// const mp = vec.med(start.point, end.point)
|
||||
// start.point = vec.rotWith(start.point, mp, delta)
|
||||
// end.point = vec.rotWith(end.point, mp, delta)
|
||||
// bend.point = vec.rotWith(bend.point, mp, delta)
|
||||
// this.onHandleChange(shape, shape.handles)
|
||||
const mp = vec.med(start.point, end.point)
|
||||
start.point = vec.rotWith(start.point, mp, delta)
|
||||
end.point = vec.rotWith(end.point, mp, delta)
|
||||
bend.point = vec.rotWith(bend.point, mp, delta)
|
||||
|
||||
// const bounds = this.getBounds(shape)
|
||||
this.onHandleChange(shape, shape.handles)
|
||||
|
||||
// const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
|
||||
|
||||
// this.translateTo(shape, vec.add(shape.point, offset))
|
||||
|
||||
// start.point = vec.sub(start.point, offset)
|
||||
// end.point = vec.sub(end.point, offset)
|
||||
// bend.point = vec.sub(bend.point, offset)
|
||||
|
||||
shape.rotation = rotation
|
||||
return this
|
||||
},
|
||||
|
||||
|
@ -202,11 +211,16 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
},
|
||||
|
||||
getRotatedBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
this.boundsCache.set(shape, getBoundsFromPoints(shape.points))
|
||||
}
|
||||
const { start, end } = shape.handles
|
||||
return translateBounds(
|
||||
getBoundsFromPoints([start.point, end.point], shape.rotation),
|
||||
shape.point
|
||||
)
|
||||
},
|
||||
|
||||
return translateBounds(this.boundsCache.get(shape), shape.point)
|
||||
getCenter(shape) {
|
||||
const { start, end } = shape.handles
|
||||
return vec.add(shape.point, vec.med(start.point, end.point))
|
||||
},
|
||||
|
||||
hitTest(shape, point) {
|
||||
|
@ -281,6 +295,9 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
},
|
||||
|
||||
onHandleChange(shape, handles) {
|
||||
// const oldBounds = this.getRotatedBounds(shape)
|
||||
// const prevCenter = getBoundsCenter(oldBounds)
|
||||
|
||||
for (let id in handles) {
|
||||
const handle = handles[id]
|
||||
|
||||
|
@ -313,6 +330,27 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
|
||||
shape.handles.bend.point = getBendPoint(shape)
|
||||
|
||||
// const newBounds = this.getRotatedBounds(shape)
|
||||
// const newCenter = getBoundsCenter(newBounds)
|
||||
|
||||
// shape.point = vec.add(shape.point, vec.neg(vec.sub(newCenter, prevCenter)))
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
onSessionComplete(shape) {
|
||||
const bounds = this.getBounds(shape)
|
||||
|
||||
const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
|
||||
|
||||
this.translateTo(shape, vec.add(shape.point, offset))
|
||||
|
||||
const { start, end, bend } = shape.handles
|
||||
|
||||
start.point = vec.sub(start.point, offset)
|
||||
end.point = vec.sub(end.point, offset)
|
||||
bend.point = vec.sub(bend.point, offset)
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
|
@ -360,3 +398,44 @@ function getBendPoint(shape: ArrowShape) {
|
|||
? midPoint
|
||||
: vec.add(midPoint, vec.mul(vec.per(u), bendDist))
|
||||
}
|
||||
|
||||
function getResizeOffset(a: Bounds, b: Bounds) {
|
||||
const { minX: x0, minY: y0, width: w0, height: h0 } = a
|
||||
const { minX: x1, minY: y1, width: w1, height: h1 } = b
|
||||
|
||||
let delta: number[]
|
||||
|
||||
if (h0 === h1 && w0 !== w1) {
|
||||
if (x0 !== x1) {
|
||||
// moving left edge, pin right edge
|
||||
delta = vec.sub([x1, y1 + h1 / 2], [x0, y0 + h0 / 2])
|
||||
} else {
|
||||
// moving right edge, pin left edge
|
||||
delta = vec.sub([x1 + w1, y1 + h1 / 2], [x0 + w0, y0 + h0 / 2])
|
||||
}
|
||||
} else if (h0 !== h1 && w0 === w1) {
|
||||
if (y0 !== y1) {
|
||||
// moving top edge, pin bottom edge
|
||||
delta = vec.sub([x1 + w1 / 2, y1], [x0 + w0 / 2, y0])
|
||||
} else {
|
||||
// moving bottom edge, pin top edge
|
||||
delta = vec.sub([x1 + w1 / 2, y1 + h1], [x0 + w0 / 2, y0 + h0])
|
||||
}
|
||||
} else if (x0 !== x1) {
|
||||
if (y0 !== y1) {
|
||||
// moving top left, pin bottom right
|
||||
delta = vec.sub([x1, y1], [x0, y0])
|
||||
} else {
|
||||
// moving bottom left, pin top right
|
||||
delta = vec.sub([x1, y1 + h1], [x0, y0 + h0])
|
||||
}
|
||||
} else if (y0 !== y1) {
|
||||
// moving top right, pin bottom left
|
||||
delta = vec.sub([x1 + w1, y1], [x0 + w0, y0])
|
||||
} else {
|
||||
// moving bottom right, pin top left
|
||||
delta = vec.sub([x1 + w1, y1 + h1], [x0 + w0, y0 + h0])
|
||||
}
|
||||
|
||||
return delta
|
||||
}
|
||||
|
|
|
@ -147,6 +147,9 @@ export interface ShapeUtility<K extends Shape> {
|
|||
handle: Partial<K['handles']>
|
||||
): ShapeUtility<K>
|
||||
|
||||
// Clean up changes when a session ends.
|
||||
onSessionComplete(this: ShapeUtility<K>, shape: Mutable<K>): ShapeUtility<K>
|
||||
|
||||
// Render a shape to JSX.
|
||||
render(this: ShapeUtility<K>, shape: K): JSX.Element
|
||||
|
||||
|
@ -258,6 +261,10 @@ function getDefaultShapeUtil<T extends Shape>(): ShapeUtility<T> {
|
|||
return this
|
||||
},
|
||||
|
||||
onSessionComplete() {
|
||||
return this
|
||||
},
|
||||
|
||||
getBounds(shape) {
|
||||
const [x, y] = shape.point
|
||||
return {
|
||||
|
|
|
@ -24,26 +24,27 @@ export default function handleCommand(
|
|||
const page = getPage(data, currentPageId)
|
||||
const shape = page.shapes[initialShape.id]
|
||||
|
||||
getShapeUtils(shape).onHandleChange(shape, initialShape.handles)
|
||||
getShapeUtils(shape)
|
||||
.onHandleChange(shape, initialShape.handles)
|
||||
.onSessionComplete(shape)
|
||||
|
||||
const bounds = getShapeUtils(shape).getBounds(shape)
|
||||
// const bounds = getShapeUtils(shape).getBounds(shape)
|
||||
|
||||
const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
|
||||
// const offset = vec.sub([bounds.minX, bounds.minY], shape.point)
|
||||
|
||||
getShapeUtils(shape).translateTo(shape, vec.add(shape.point, offset))
|
||||
// getShapeUtils(shape).translateTo(shape, vec.add(shape.point, offset))
|
||||
|
||||
const { start, end, bend } = page.shapes[initialShape.id].handles
|
||||
// const { start, end, bend } = page.shapes[initialShape.id].handles
|
||||
|
||||
start.point = vec.sub(start.point, offset)
|
||||
end.point = vec.sub(end.point, offset)
|
||||
bend.point = vec.sub(bend.point, offset)
|
||||
// start.point = vec.sub(start.point, offset)
|
||||
// end.point = vec.sub(end.point, offset)
|
||||
// bend.point = vec.sub(bend.point, offset)
|
||||
},
|
||||
undo(data) {
|
||||
const { initialShape, currentPageId } = before
|
||||
|
||||
const shape = getPage(data, currentPageId).shapes[initialShape.id]
|
||||
|
||||
getShapeUtils(shape).onHandleChange(shape, initialShape.handles)
|
||||
const page = getPage(data, currentPageId)
|
||||
page.shapes[initialShape.id] = initialShape
|
||||
},
|
||||
})
|
||||
)
|
||||
|
|
|
@ -20,10 +20,10 @@ export default function rotateCommand(
|
|||
|
||||
for (let { id, point, rotation } of after.initialShapes) {
|
||||
const shape = shapes[id]
|
||||
const utils = getShapeUtils(shape)
|
||||
utils
|
||||
.rotateTo(shape, rotation, rotation - shape.rotation)
|
||||
getShapeUtils(shape)
|
||||
.rotateBy(shape, rotation - shape.rotation)
|
||||
.translateTo(shape, point)
|
||||
.onSessionComplete(shape)
|
||||
}
|
||||
|
||||
data.boundsRotation = after.boundsRotation
|
||||
|
@ -33,10 +33,10 @@ export default function rotateCommand(
|
|||
|
||||
for (let { id, point, rotation } of before.initialShapes) {
|
||||
const shape = shapes[id]
|
||||
const utils = getShapeUtils(shape)
|
||||
utils
|
||||
.rotateTo(shape, rotation, rotation - shape.rotation)
|
||||
getShapeUtils(shape)
|
||||
.rotateBy(shape, rotation - shape.rotation)
|
||||
.translateTo(shape, point)
|
||||
.onSessionComplete(shape)
|
||||
}
|
||||
|
||||
data.boundsRotation = before.boundsRotation
|
||||
|
|
|
@ -115,31 +115,31 @@ export const defaultDocument: Data['document'] = {
|
|||
// },
|
||||
// }),
|
||||
// Groups Testing
|
||||
shapeA: shapeUtils[ShapeType.Rectangle].create({
|
||||
id: 'shapeA',
|
||||
name: 'Shape A',
|
||||
childIndex: 1,
|
||||
point: [0, 0],
|
||||
size: [200, 200],
|
||||
parentId: 'groupA',
|
||||
}),
|
||||
shapeB: shapeUtils[ShapeType.Rectangle].create({
|
||||
id: 'shapeB',
|
||||
name: 'Shape B',
|
||||
childIndex: 2,
|
||||
point: [220, 100],
|
||||
size: [200, 200],
|
||||
parentId: 'groupA',
|
||||
}),
|
||||
groupA: shapeUtils[ShapeType.Group].create({
|
||||
id: 'groupA',
|
||||
name: 'Group A',
|
||||
childIndex: 2,
|
||||
point: [0, 0],
|
||||
size: [420, 300],
|
||||
parentId: 'page1',
|
||||
children: ['shapeA', 'shapeB'],
|
||||
}),
|
||||
// shapeA: shapeUtils[ShapeType.Rectangle].create({
|
||||
// id: 'shapeA',
|
||||
// name: 'Shape A',
|
||||
// childIndex: 1,
|
||||
// point: [0, 0],
|
||||
// size: [200, 200],
|
||||
// parentId: 'groupA',
|
||||
// }),
|
||||
// shapeB: shapeUtils[ShapeType.Rectangle].create({
|
||||
// id: 'shapeB',
|
||||
// name: 'Shape B',
|
||||
// childIndex: 2,
|
||||
// point: [220, 100],
|
||||
// size: [200, 200],
|
||||
// parentId: 'groupA',
|
||||
// }),
|
||||
// groupA: shapeUtils[ShapeType.Group].create({
|
||||
// id: 'groupA',
|
||||
// name: 'Group A',
|
||||
// childIndex: 2,
|
||||
// point: [0, 0],
|
||||
// size: [420, 300],
|
||||
// parentId: 'page1',
|
||||
// children: ['shapeA', 'shapeB'],
|
||||
// }),
|
||||
},
|
||||
},
|
||||
page2: {
|
||||
|
|
|
@ -33,17 +33,21 @@ export default class HandleSession extends BaseSession {
|
|||
|
||||
const handles = initialShape.handles
|
||||
|
||||
// rotate the delta ?
|
||||
// rotate the handle ?
|
||||
// rotate the shape around the previous center point
|
||||
|
||||
getShapeUtils(shape).onHandleChange(shape, {
|
||||
[handleId]: {
|
||||
...handles[handleId],
|
||||
point: vec.add(handles[handleId].point, delta),
|
||||
point: vec.add(handles[handleId].point, delta), // vec.rot(delta, shape.rotation)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
cancel(data: Data) {
|
||||
const { currentPageId, handleId, initialShape } = this.snapshot
|
||||
const shape = getPage(data, currentPageId).shapes[initialShape.id]
|
||||
getPage(data, currentPageId).shapes[initialShape.id] = initialShape
|
||||
}
|
||||
|
||||
complete(data: Data) {
|
||||
|
|
|
@ -925,7 +925,7 @@ const state = createState({
|
|||
payload: PointerInfo & { target: Corner | Edge }
|
||||
) {
|
||||
const point = screenToWorld(inputs.pointer.origin, data)
|
||||
// session = new Sessions.TransformSession(data, payload.target, point)
|
||||
session = new Sessions.TransformSession(data, payload.target, point)
|
||||
session =
|
||||
data.selectedIds.size === 1
|
||||
? new Sessions.TransformSingleSession(data, payload.target, point)
|
||||
|
@ -1442,9 +1442,16 @@ const state = createState({
|
|||
return bounds
|
||||
}
|
||||
|
||||
const uniqueSelectedShapeIds: string[] = Array.from(
|
||||
new Set(
|
||||
Array.from(selectedIds.values()).flatMap((id) =>
|
||||
getDocumentBranch(data, id)
|
||||
)
|
||||
).values()
|
||||
)
|
||||
|
||||
const commonBounds = getCommonBounds(
|
||||
...shapes
|
||||
.flatMap((shape) => getDocumentBranch(data, shape.id))
|
||||
...uniqueSelectedShapeIds
|
||||
.map((id) => page.shapes[id])
|
||||
.filter((shape) => shape.type !== ShapeType.Group)
|
||||
.map((shape) => {
|
||||
|
|
22
todo.md
22
todo.md
|
@ -1,17 +1,21 @@
|
|||
# Todo
|
||||
|
||||
## Done
|
||||
## Groups
|
||||
|
||||
- fix select indicator placement for arrow
|
||||
- fix drift when moving children of rotated group
|
||||
|
||||
## Todo
|
||||
## Select
|
||||
|
||||
- Restore select highlight, fix for children of rotated groups
|
||||
- Transforming on rotated shapes
|
||||
- Fix bounding box for rotated shapes
|
||||
- Allow single-selected groups to transform their children correctly
|
||||
|
||||
# Transforms
|
||||
|
||||
- (merge transform-session and transform-single-session)
|
||||
- fix drift when moving children of rotated group
|
||||
- shift dragging arrow handles should lock to directions
|
||||
- arrow rotation with handles
|
||||
- Allow single-selected groups to transform their children correctly
|
||||
- fix ellipse when scaleX < 0 or scaleY < 0
|
||||
- Transforming on rotated shapes
|
||||
|
||||
## Arrows
|
||||
|
||||
- shift dragging arrow handles should lock to directions
|
||||
- fix undo/redo on rotated arrows
|
||||
|
|
Loading…
Reference in a new issue