@leafer-in/editor
Version:
298 lines (212 loc) • 11.8 kB
text/typescript
import { IEvent, IPointData, IAlign, IAxis, IFunction, IMatrix, IUI } from '@leafer-ui/interface'
import { MathHelper, PointHelper, Matrix, LeafHelper, AroundHelper, isObject, isString, isNumber } from '@leafer-ui/draw'
import { DragEvent, RotateEvent, ZoomEvent, MoveEvent } from '@leafer-ui/core'
import { IEditBox, IEditPoint, IEditTool, IEditorScaleEvent, ISimulateElement, IEditorMoveEvent, IEditorRotateEvent, IEditorSkewEvent } from '@leafer-in/interface'
import { EditorMoveEvent } from '../event/EditorMoveEvent'
import { EditorScaleEvent } from '../event/EditorScaleEvent'
import { EditorRotateEvent } from '../event/EditorRotateEvent'
import { EditorSkewEvent } from '../event/EditorSkewEvent'
import { EditDataHelper } from '../helper/EditDataHelper'
import { ITransformTool } from '@leafer-ui/interface'
export class TransformTool implements ITransformTool { // Editor use
public editBox: IEditBox
public editTool?: IEditTool // 可能不存在值
// operate
public onMove(e: DragEvent | MoveEvent): void {
const { target, mergeConfig, dragStartData } = this.editBox
let move: IPointData, { dragLimitAnimate } = mergeConfig
const isMoveEnd = e.type === MoveEvent.END || e.type === DragEvent.END
const axisDrag = isString(target.draggable)
const checkLimitMove = !dragLimitAnimate || isMoveEnd || axisDrag
const total = { x: e.totalX, y: e.totalY }
if (e instanceof MoveEvent) {
PointHelper.move(total, target.getWorldPointByLocal(dragStartData.totalOffset, null, true))
}
if (e.shiftKey) {
if (Math.abs(total.x) > Math.abs(total.y)) total.y = 0
else total.x = 0
}
move = DragEvent.getValidMove(target, dragStartData.point, total, checkLimitMove)
if (move.x || move.y) {
if (dragLimitAnimate && !axisDrag && isMoveEnd) LeafHelper.animateMove(this as unknown as IUI, move, isNumber(dragLimitAnimate) ? dragLimitAnimate : 0.3) // 是否进行动画
else this.move(move)
}
}
public onScale(e: DragEvent | ZoomEvent): void {
const { target, mergeConfig, single, dragStartData } = this.editBox
let { around, lockRatio, flipable, editSize } = mergeConfig, totalMove: IPointData | number
if (e instanceof ZoomEvent) {
if (!around) around = target.getBoxPoint(e)
totalMove = e.totalScale
} else {
totalMove = e.getInnerTotal(target)
}
const { direction } = e.current as IEditPoint
if (e.shiftKey || target.lockRatio) lockRatio = true
const data = EditDataHelper.getScaleData(target, dragStartData.bounds, direction, totalMove, lockRatio, EditDataHelper.getAround(around, e.altKey), flipable, !single || editSize === 'scale')
const targetX = target.x, targetY = target.y
if (e instanceof DragEvent && this.editTool && this.editTool.onScaleWithDrag) {
data.drag = e
this.scaleWithDrag(data)
} else {
this.scaleOf(data.origin, data.scaleX, data.scaleY)
}
PointHelper.move(dragStartData.totalOffset, target.x - targetX, target.y - targetY)
}
public onRotate(e: DragEvent | RotateEvent): void {
const { target, mergeConfig, dragStartData } = this.editBox
const { around, rotateAround, rotateGap, diagonalRotateKey } = mergeConfig
const { direction } = e.current as IEditPoint
let origin: IPointData, rotation: number
if (e instanceof RotateEvent) {
rotation = e.rotation
origin = rotateAround ? AroundHelper.getPoint(rotateAround, target.boxBounds) : target.getBoxPoint(e)
} else {
const isDiagonalRotate = diagonalRotateKey ? e.isHoldKeys(diagonalRotateKey) : e.shiftKey // 对角旋转
const data = EditDataHelper.getRotateData(target, direction, e, dragStartData, isDiagonalRotate ? null : (rotateAround || target.around || target.origin || around || 'center'))
rotation = dragStartData.rotation + data.rotation - target.rotation
origin = data.origin
}
rotation = MathHelper.float(MathHelper.getGapRotation(rotation, rotateGap, target.rotation), 2)
if (!rotation) return
const targetX = target.x, targetY = target.y
this.rotateOf(origin, rotation)
PointHelper.move(dragStartData.totalOffset, target.x - targetX, target.y - targetY)
}
public onSkew(e: DragEvent): void {
const { target, mergeConfig } = this.editBox
const { around } = mergeConfig
const { origin, skewX, skewY } = EditDataHelper.getSkewData(target.boxBounds, (e.current as IEditPoint).direction, e.getInnerMove(target), EditDataHelper.getAround(around, e.altKey))
if (!skewX && !skewY) return
this.skewOf(origin, skewX, skewY)
}
// transform
public move(x: number | IPointData, y = 0): void {
if (!this.checkTransform('moveable')) return
if (isObject(x)) y = x.y, x = x.x
const { target, mergeConfig, single, editor } = this.editBox
const { beforeMove } = mergeConfig
if (beforeMove) {
const check = beforeMove({ target, x, y })
if (isObject(check)) x = check.x, y = check.y
else if (check === false) return
}
const world = target.getWorldPointByLocal({ x, y }, null, true)
if (!single) (target as ISimulateElement).safeChange(() => target.move(x, y))
const data: IEditorMoveEvent = { target, editor, moveX: world.x, moveY: world.y }
this.emitEvent(new EditorMoveEvent(EditorMoveEvent.BEFORE_MOVE, data))
const event = new EditorMoveEvent(EditorMoveEvent.MOVE, data)
this.doMove(event)
this.emitEvent(event)
}
public scaleWithDrag(data: IEditorScaleEvent): void {
if (!this.checkTransform('resizeable')) return
const { target, mergeConfig, editor } = this.editBox
const { beforeScale } = mergeConfig
if (beforeScale) {
const { origin, scaleX, scaleY, drag } = data
const check = beforeScale({ target, drag, origin, scaleX, scaleY })
if (check === false) return
}
data = { ...data, target, editor, worldOrigin: target.getWorldPoint(data.origin) }
this.emitEvent(new EditorScaleEvent(EditorScaleEvent.BEFORE_SCALE, data))
const event = new EditorScaleEvent(EditorScaleEvent.SCALE, data)
this.editTool.onScaleWithDrag(event)
this.emitEvent(event)
}
public scaleOf(origin: IPointData | IAlign, scaleX: number, scaleY = scaleX, _resize?: boolean): void {
if (!this.checkTransform('resizeable')) return
const { target, mergeConfig, single, editor } = this.editBox
const { beforeScale } = mergeConfig
if (beforeScale) {
const check = beforeScale({ target, origin, scaleX, scaleY })
if (isObject(check)) scaleX = check.scaleX, scaleY = check.scaleY
else if (check === false) return
}
const worldOrigin = this.getWorldOrigin(origin)
const transform = !single && this.getChangedTransform(() => (target as ISimulateElement).safeChange(() => target.scaleOf(origin, scaleX, scaleY)))
const data: IEditorScaleEvent = { target, editor, worldOrigin, scaleX, scaleY, transform }
this.emitEvent(new EditorScaleEvent(EditorScaleEvent.BEFORE_SCALE, data))
const event = new EditorScaleEvent(EditorScaleEvent.SCALE, data)
this.doScale(event)
this.emitEvent(event)
}
public flip(axis: IAxis): void {
if (!this.checkTransform('resizeable')) return
const { target, single, editor } = this.editBox
const worldOrigin = this.getWorldOrigin('center')
const transform = !single ? this.getChangedTransform(() => (target as ISimulateElement).safeChange(() => target.flip(axis))) : new Matrix(LeafHelper.getFlipTransform(target, axis))
const data: IEditorScaleEvent = { target, editor, worldOrigin, scaleX: axis === 'x' ? -1 : 1, scaleY: axis === 'y' ? -1 : 1, transform }
this.emitEvent(new EditorScaleEvent(EditorScaleEvent.BEFORE_SCALE, data))
const event = new EditorScaleEvent(EditorScaleEvent.SCALE, data)
this.doScale(event)
this.emitEvent(event)
}
public rotateOf(origin: IPointData | IAlign, rotation: number): void {
if (!this.checkTransform('rotateable')) return
const { target, mergeConfig, single, editor } = this.editBox
const { beforeRotate } = mergeConfig
if (beforeRotate) {
const check = beforeRotate({ target, origin, rotation })
if (isNumber(check)) rotation = check
else if (check === false) return
}
const worldOrigin = this.getWorldOrigin(origin)
const transform = !single && this.getChangedTransform(() => (target as ISimulateElement).safeChange(() => target.rotateOf(origin, rotation)))
const data: IEditorRotateEvent = { target, editor, worldOrigin, rotation, transform }
this.emitEvent(new EditorRotateEvent(EditorRotateEvent.BEFORE_ROTATE, data))
const event = new EditorRotateEvent(EditorRotateEvent.ROTATE, data)
this.doRotate(event)
this.emitEvent(event)
}
public skewOf(origin: IPointData | IAlign, skewX: number, skewY = 0, _resize?: boolean): void {
if (!this.checkTransform('skewable')) return
const { target, mergeConfig, single, editor } = this.editBox
const { beforeSkew } = mergeConfig
if (beforeSkew) {
const check = beforeSkew({ target, origin, skewX, skewY })
if (isObject(check)) skewX = check.skewX, skewY = check.skewY
else if (check === false) return
}
const worldOrigin = this.getWorldOrigin(origin)
const transform = !single && this.getChangedTransform(() => (target as ISimulateElement).safeChange(() => target.skewOf(origin, skewX, skewY)))
const data: IEditorSkewEvent = { target, editor, worldOrigin, skewX, skewY, transform }
this.emitEvent(new EditorSkewEvent(EditorSkewEvent.BEFORE_SKEW, data))
const event = new EditorSkewEvent(EditorSkewEvent.SKEW, data)
this.doSkew(event)
this.emitEvent(event)
}
// do
protected doMove(event: IEditorMoveEvent) {
this.editTool.onMove(event)
}
protected doScale(event: IEditorScaleEvent): void {
this.editTool.onScale(event)
}
protected doRotate(event: IEditorRotateEvent): void {
this.editTool.onRotate(event)
}
protected doSkew(event: IEditorSkewEvent): void {
this.editTool.onSkew(event)
}
// helper
public checkTransform(type: 'moveable' | 'resizeable' | 'rotateable' | 'skewable'): boolean {
const { target, mergeConfig } = this.editBox
return target && !target.locked && mergeConfig[type] as boolean
}
protected getWorldOrigin(origin: IPointData | IAlign): IPointData {
const { target } = this.editBox
return target.getWorldPoint(LeafHelper.getInnerOrigin(target, origin))
}
protected getChangedTransform(func: IFunction): IMatrix {
const { target, single } = this.editBox
if (!single && !(target as ISimulateElement).canChange) return (target as ISimulateElement).changedTransform
const oldMatrix = new Matrix(target.worldTransform)
func()
return new Matrix(target.worldTransform).divide(oldMatrix) // world change transform
}
// need rewrite
public emitEvent(event?: IEvent, capture?: boolean): void {
this.editBox.editor.emitEvent(event, capture)
}
}