Align selected anchor points relative to each other in vector edit mode. The standard alignment buttons in the position panel now operate on selected vertices when 2 or more are selected, enabling precise vector path editing workflows.
150 lines
4.1 KiB
TypeScript
150 lines
4.1 KiB
TypeScript
import { hitTestHandle } from './geometry'
|
|
|
|
import type { DragResize, HandlePosition } from './types'
|
|
import { cloneVectorNetwork } from '@open-pencil/core'
|
|
import type { Rect, SceneNode } from '@open-pencil/core'
|
|
import type { Editor } from '@open-pencil/core/editor'
|
|
|
|
export function constrainToAspectRatio(
|
|
handle: HandlePosition,
|
|
origRect: Rect,
|
|
width: number,
|
|
height: number,
|
|
dx: number,
|
|
dy: number
|
|
): Rect {
|
|
let x = handle.includes('w') ? origRect.x + origRect.width - Math.abs(width) : origRect.x
|
|
const isTop = handle === 'nw' || handle === 'n' || handle === 'ne'
|
|
let y = isTop ? origRect.y + origRect.height - Math.abs(height) : origRect.y
|
|
const aspect = origRect.width / origRect.height
|
|
|
|
if (handle === 'n' || handle === 's') {
|
|
width = Math.abs(height) * aspect
|
|
x = origRect.x + (origRect.width - width) / 2
|
|
} else if (handle === 'e' || handle === 'w') {
|
|
height = Math.abs(width) / aspect
|
|
y = origRect.y + (origRect.height - height) / 2
|
|
} else if (Math.abs(dx) > Math.abs(dy)) {
|
|
height = (Math.abs(width) / aspect) * Math.sign(height || 1)
|
|
if (isTop) y = origRect.y + origRect.height - Math.abs(height)
|
|
} else {
|
|
width = Math.abs(height) * aspect * Math.sign(width || 1)
|
|
if (handle.includes('w')) x = origRect.x + origRect.width - Math.abs(width)
|
|
}
|
|
|
|
return { x, y, width, height }
|
|
}
|
|
|
|
export function applyResize(
|
|
d: DragResize,
|
|
cx: number,
|
|
cy: number,
|
|
constrain: boolean,
|
|
editor: Editor
|
|
) {
|
|
const { handle, origRect } = d
|
|
let { x, y, width, height } = origRect
|
|
const dx = cx - d.startX
|
|
const dy = cy - d.startY
|
|
|
|
const moveLeft = handle.includes('w')
|
|
const moveRight = handle.includes('e')
|
|
const moveTop = handle === 'nw' || handle === 'n' || handle === 'ne'
|
|
const moveBottom = handle === 'sw' || handle === 's' || handle === 'se'
|
|
|
|
if (moveRight) width = origRect.width + dx
|
|
if (moveLeft) {
|
|
x = origRect.x + dx
|
|
width = origRect.width - dx
|
|
}
|
|
if (moveBottom) height = origRect.height + dy
|
|
if (moveTop) {
|
|
y = origRect.y + dy
|
|
height = origRect.height - dy
|
|
}
|
|
|
|
if (constrain && origRect.width > 0 && origRect.height > 0) {
|
|
;({ x, y, width, height } = constrainToAspectRatio(handle, origRect, width, height, dx, dy))
|
|
}
|
|
|
|
if (width < 0) {
|
|
x += width
|
|
width = -width
|
|
}
|
|
if (height < 0) {
|
|
y += height
|
|
height = -height
|
|
}
|
|
|
|
const newW = Math.round(Math.max(1, width))
|
|
const newH = Math.round(Math.max(1, height))
|
|
const changes: Partial<SceneNode> = {
|
|
x: Math.round(x),
|
|
y: Math.round(y),
|
|
width: newW,
|
|
height: newH
|
|
}
|
|
|
|
// Scale vectorNetwork vertices + tangents proportionally
|
|
if (d.origVectorNetwork && origRect.width > 0 && origRect.height > 0) {
|
|
const sx = newW / origRect.width
|
|
const sy = newH / origRect.height
|
|
if (sx !== 1 || sy !== 1) {
|
|
const vn = d.origVectorNetwork
|
|
changes.vectorNetwork = {
|
|
vertices: vn.vertices.map((v) => ({
|
|
...v,
|
|
x: v.x * sx,
|
|
y: v.y * sy
|
|
})),
|
|
segments: vn.segments.map((s) => ({
|
|
...s,
|
|
tangentStart: { x: s.tangentStart.x * sx, y: s.tangentStart.y * sy },
|
|
tangentEnd: { x: s.tangentEnd.x * sx, y: s.tangentEnd.y * sy }
|
|
})),
|
|
regions: vn.regions
|
|
}
|
|
}
|
|
}
|
|
|
|
editor.updateNode(d.nodeId, changes)
|
|
}
|
|
|
|
export function tryStartResize(
|
|
sx: number,
|
|
sy: number,
|
|
cx: number,
|
|
cy: number,
|
|
editor: Editor
|
|
): DragResize | null {
|
|
for (const id of editor.state.selectedIds) {
|
|
const node = editor.graph.getNode(id)
|
|
if (!node || node.locked) continue
|
|
const abs = editor.graph.getAbsolutePosition(id)
|
|
const handle = hitTestHandle(
|
|
sx,
|
|
sy,
|
|
abs.x,
|
|
abs.y,
|
|
node.width,
|
|
node.height,
|
|
editor.state.zoom,
|
|
editor.state.panX,
|
|
editor.state.panY,
|
|
node.rotation
|
|
)
|
|
if (handle) {
|
|
return {
|
|
type: 'resize',
|
|
handle,
|
|
startX: cx,
|
|
startY: cy,
|
|
origRect: { x: node.x, y: node.y, width: node.width, height: node.height },
|
|
nodeId: id,
|
|
origVectorNetwork: node.vectorNetwork ? cloneVectorNetwork(node.vectorNetwork) : null
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|