Tldraw/examples/core-example-advanced/src/state/actions/handles/translateHandle.ts

110 wiersze
3.6 KiB
TypeScript

import type { Action, CustomBinding } from 'state/constants'
import { getShapeUtils, Shape } from 'shapes'
import { mutables } from 'state/mutables'
import { nanoid } from 'nanoid'
import { TLPointerInfo, Utils } from '@tldraw/core'
import type { ArrowShape } from 'shapes/arrow'
import Vec from '@tldraw/vec'
export const translateHandle: Action = (data, payload: TLPointerInfo) => {
const { initialPoint, snapshot, pointedHandleId } = mutables
if (!pointedHandleId) return
let delta = Vec.sub(mutables.currentPoint, initialPoint)
data.pageState.selectedIds.forEach((id) => {
const initialShape = snapshot.page.shapes[id] as ArrowShape
const shape = data.page.shapes[id] as ArrowShape
if (payload.shiftKey) {
const A = initialShape.handles[pointedHandleId === 'start' ? 'end' : 'start'].point
const B = initialShape.handles[pointedHandleId].point
const C = Vec.add(B, delta)
const angle = Vec.angle(A, C)
const adjusted = Vec.rotWith(C, A, Utils.snapAngleToSegments(angle, 24) - angle)
delta = Vec.add(delta, Vec.sub(adjusted, C))
}
const handlePoints = {
start: [...initialShape.handles.start.point],
end: [...initialShape.handles.end.point],
}
handlePoints[pointedHandleId] = Vec.add(handlePoints[pointedHandleId], delta)
// Create binding
const oppositeHandleId = pointedHandleId === 'start' ? 'end' : 'start'
const oppositeHandle = shape.handles[oppositeHandleId]
const handlePoint = Vec.add(handlePoints[pointedHandleId], initialShape.point)
let minDistance = Infinity
let toShape: Shape | undefined
const oppositeBindingTargetId = Object.values(data.page.bindings).find(
(binding) => binding.fromId === shape.id && binding.handleId === oppositeHandleId
)?.toId
if (!payload.metaKey) {
// Find colliding shape with center nearest to point
Object.values(data.page.shapes)
.filter(
(shape) =>
!data.pageState.selectedIds.includes(shape.id) && shape.id !== oppositeBindingTargetId
)
.forEach((potentialTarget) => {
const utils = getShapeUtils(potentialTarget)
if (!utils.canBind) return
const bounds = utils.getBounds(potentialTarget)
if (Utils.pointInBounds(handlePoint, bounds)) {
const dist = Vec.dist(handlePoint, utils.getCenter(potentialTarget))
if (dist < minDistance) {
minDistance = dist
toShape = potentialTarget
}
}
})
}
const oldBinding = Object.values(data.page.bindings).find(
(binding) => binding.fromId === shape.id && binding.handleId === pointedHandleId
)
// If we have a binding target
if (toShape) {
if (!oldBinding || oldBinding.toId !== toShape.id) {
if (oldBinding) {
delete data.page.bindings[oldBinding.id]
}
// Create a new binding between shape and toShape
const binding: CustomBinding = {
id: nanoid(),
fromId: shape.id,
toId: toShape.id,
handleId: pointedHandleId,
}
data.page.bindings[binding.id] = binding
}
// The `updateBoundShapes` action will take it from here.
return
}
// If we didn't find a toShape, clear out the old binding (if present)
if (oldBinding) {
delete data.page.bindings[oldBinding.id]
}
const offset = Utils.getCommonTopLeft([handlePoints.start, handlePoints.end])
shape.handles.start.point = Vec.sub(handlePoints.start, offset)
shape.handles.end.point = Vec.sub(handlePoints.end, offset)
shape.point = Vec.add(initialShape.point, offset)
})
}