import { Able, params, payload, PositionSchema, TransformData, Entity, PositionData } from '../../common'; import { Rectangle } from '@gedit/math'; import { Adsorber } from '../utils/adsorber'; export const DragablePayload = Symbol('DragablePayload'); export interface DragablePayload { startPos: PositionSchema endPos: PositionSchema isMoving: boolean isStart: boolean scale?: number // 当前比例 adsorbRefs?: Rectangle[] // 需要吸附的矩形 adsorbLines?: Adsorber.Line[], // 需要吸附的线 adsorbDisable?: boolean // 禁用吸附, 默认开启 } /** * 获取当前拖拽的选择框 * @param entities */ function geSelectorBounds(entities: Entity[]): Rectangle { const rects = entities .filter(e => e.getData(TransformData)) .map(e => e.getData(TransformData)!.bounds); return Rectangle.enlarge(rects); } function withScale(rect: Rectangle, scale: number, reverse: boolean = false): Rectangle { if (reverse) { return new Rectangle( rect.x / scale, rect.y / scale, rect.width / scale, rect.height / scale, ); } return new Rectangle( rect.x * scale, rect.y * scale, rect.width * scale, rect.height * scale ); } // 选择框开始拖拽的位置 const selectorStartDragPos = {x: 0, y: 0}; const adsorber = new Adsorber(); export class Dragable extends Able { static type = 'Dragable'; @payload(DragablePayload) payload: DragablePayload; protected startPosMap: Map = new Map(); @params(PositionData, TransformData) handle(position: PositionData, transform: TransformData, p: DragablePayload): void { const {isStart, isMoving, startPos, endPos} = p; const scale = p.scale ?? 1; if (isStart && !isMoving) { this.startPosMap.set(transform, { x: position.x, y: position.y, }); } else if (isMoving) { let delta: { x: number, y: number }; if (transform.parent) { const inverseStart = transform.parent.worldTransform.applyInverse(startPos); const inverseEnd = transform.parent.worldTransform.applyInverse(endPos); // 把位移投影到矩阵上 delta = { x: inverseEnd.x - inverseStart.x, y: inverseEnd.y - inverseStart.y }; } else { delta = { x: endPos.x - startPos.x, y: endPos.y - startPos.y }; } const start = this.startPosMap.get(transform)!; position.update({ x: start.x + delta.x / scale, y: start.y + delta.y / scale }); } else { this.startPosMap.clear(); } } /** * 处理拖拽时候吸附相关逻辑 * @param entities * @param p */ static globalBefore(entities: Entity[], p: DragablePayload): DragablePayload | undefined { if (entities.length === 0 || p.adsorbDisable) return undefined; const scale = p.scale ?? 1; const bounds = geSelectorBounds(entities); if (p.isStart && !p.isMoving) { // 缓存选择框位置 selectorStartDragPos.x = bounds.x * scale; selectorStartDragPos.y = bounds.y * scale; return { ...p, startPos: selectorStartDragPos, }; } else if (p.isMoving) { // 计算选择框新位置 bounds.x = selectorStartDragPos.x + p.endPos.x - p.startPos.x; bounds.y = selectorStartDragPos.y + p.endPos.y - p.startPos.y; bounds.width = bounds.width * scale; bounds.height = bounds.height * scale; const adsorbRefs: Rectangle[] = p.adsorbRefs || []; // 处理吸附逻辑, 对移动位置进行吸附偏移, 这里要全部都缩放计算 const endPos = adsorber.calculate(bounds, adsorbRefs.map(r => withScale(r, scale)), (p.adsorbLines || []).map(l => ({ ...l, pos: l.pos * scale }))); return { ...p, startPos: selectorStartDragPos, endPos, }; } else { selectorStartDragPos.x = 0; selectorStartDragPos.y = 0; adsorber.clearAbsorbedPos(); } return p; } }