@leafer-in/editor 1.0.0-rc.3 → 1.0.0-rc.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/editor.cjs +1937 -0
- package/dist/editor.esm.js +1822 -422
- package/dist/editor.esm.min.js +1 -1
- package/dist/editor.js +1848 -428
- package/dist/editor.min.cjs +1 -0
- package/dist/editor.min.js +1 -1
- package/package.json +12 -9
- package/src/Editor.ts +396 -145
- package/src/config.ts +38 -0
- package/src/decorator/data.ts +16 -0
- package/src/display/EditBox.ts +342 -0
- package/src/display/EditMask.ts +37 -0
- package/src/display/EditPoint.ts +9 -0
- package/src/display/EditSelect.ts +255 -0
- package/src/display/SelectArea.ts +30 -0
- package/src/display/Stroker.ts +92 -0
- package/src/editor/cursor.ts +45 -0
- package/src/editor/simulate.ts +14 -0
- package/src/editor/target.ts +39 -0
- package/src/event/EditorEvent.ts +33 -0
- package/src/event/EditorGroupEvent.ts +23 -0
- package/src/event/EditorMoveEvent.ts +17 -0
- package/src/event/EditorRotateEvent.ts +4 -10
- package/src/event/EditorScaleEvent.ts +28 -0
- package/src/event/EditorSkewEvent.ts +18 -0
- package/src/event/InnerEditorEvent.ts +23 -0
- package/src/helper/EditDataHelper.ts +183 -0
- package/src/helper/EditSelectHelper.ts +34 -0
- package/src/helper/EditorHelper.ts +73 -0
- package/src/index.ts +50 -3
- package/src/svg.ts +54 -0
- package/src/tool/EditTool.ts +99 -0
- package/src/tool/EditToolCreator.ts +32 -0
- package/src/tool/InnerEditor.ts +68 -0
- package/src/tool/LineEditTool.ts +135 -0
- package/types/index.d.ts +293 -45
- package/src/cursor.ts +0 -57
- package/src/event/EditorResizeEvent.ts +0 -34
- package/src/resize.ts +0 -87
- package/src/tool/LineTool.ts +0 -88
- package/src/tool/RectTool.ts +0 -139
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { IUI, ILeaferCanvas, IRenderOptions, IRectInputData } from '@leafer-ui/interface'
|
|
2
|
+
import { Paint, UI, MatrixHelper } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
import { IStroker } from '@leafer-in/interface'
|
|
5
|
+
|
|
6
|
+
import { targetAttr } from '../decorator/data'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const matrix = MatrixHelper.get()
|
|
10
|
+
const { abs } = Math
|
|
11
|
+
const { copy, scale } = MatrixHelper
|
|
12
|
+
|
|
13
|
+
export class Stroker extends UI implements IStroker {
|
|
14
|
+
|
|
15
|
+
@targetAttr(onTarget)
|
|
16
|
+
public target: IUI | IUI[]
|
|
17
|
+
|
|
18
|
+
public list: IUI[] = []
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
super()
|
|
22
|
+
this.hittable = false
|
|
23
|
+
this.strokeAlign = 'center'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public setTarget(target: IUI | IUI[], style: IRectInputData): void {
|
|
27
|
+
this.set(style)
|
|
28
|
+
this.target = target
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public __draw(canvas: ILeaferCanvas, options: IRenderOptions): void {
|
|
32
|
+
const { list } = this
|
|
33
|
+
if (list.length) {
|
|
34
|
+
|
|
35
|
+
let leaf: IUI
|
|
36
|
+
const { stroke, strokeWidth, fill } = this.__
|
|
37
|
+
const { bounds } = options
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < list.length; i++) {
|
|
40
|
+
leaf = list[i]
|
|
41
|
+
if (bounds && bounds.hit(leaf.__world, options.matrix)) {
|
|
42
|
+
|
|
43
|
+
const aScaleX = abs(leaf.__world.scaleX), aScaleY = abs(leaf.__world.scaleY)
|
|
44
|
+
|
|
45
|
+
if (aScaleX !== aScaleY) { // need no scale stroke, use rect path
|
|
46
|
+
|
|
47
|
+
copy(matrix, leaf.__world)
|
|
48
|
+
scale(matrix, 1 / aScaleX, 1 / aScaleY)
|
|
49
|
+
|
|
50
|
+
canvas.setWorld(matrix, options.matrix)
|
|
51
|
+
canvas.beginPath()
|
|
52
|
+
this.__.strokeWidth = strokeWidth
|
|
53
|
+
|
|
54
|
+
const { x, y, width, height } = leaf.__layout.boxBounds
|
|
55
|
+
canvas.rect(x * aScaleX, y * aScaleY, width * aScaleX, height * aScaleY)
|
|
56
|
+
|
|
57
|
+
} else {
|
|
58
|
+
|
|
59
|
+
canvas.setWorld(leaf.__world, options.matrix)
|
|
60
|
+
canvas.beginPath()
|
|
61
|
+
|
|
62
|
+
if (leaf.__.__useArrow) {
|
|
63
|
+
leaf.__drawPath(canvas)
|
|
64
|
+
} else {
|
|
65
|
+
leaf.__.__pathForRender ? leaf.__drawRenderPath(canvas) : leaf.__drawPathByBox(canvas)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.__.strokeWidth = strokeWidth / abs(leaf.__world.scaleX)
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (stroke) typeof stroke === 'string' ? Paint.stroke(stroke, this, canvas) : Paint.strokes(stroke, this, canvas)
|
|
73
|
+
if (fill) typeof fill === 'string' ? Paint.fill(fill, this, canvas) : Paint.fills(fill, this, canvas)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
this.__.strokeWidth = strokeWidth
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public destroy(): void {
|
|
82
|
+
this.target = null
|
|
83
|
+
super.destroy()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function onTarget(stroker: Stroker): void {
|
|
89
|
+
const value = stroker.target
|
|
90
|
+
stroker.list = value ? (value instanceof Array ? value : [value]) : []
|
|
91
|
+
stroker.forceUpdate()
|
|
92
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { IObject, IUIEvent } from '@leafer-ui/interface'
|
|
2
|
+
|
|
3
|
+
import { IEditor } from '@leafer-in/interface'
|
|
4
|
+
import { MathHelper } from '@leafer-ui/draw'
|
|
5
|
+
|
|
6
|
+
import { EditDataHelper } from '../helper/EditDataHelper'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const cacheCursors: IObject = {}
|
|
10
|
+
|
|
11
|
+
export function updateCursor(editor: IEditor, e: IUIEvent): void {
|
|
12
|
+
const { editBox } = editor, point = editBox.enterPoint
|
|
13
|
+
if (!point || !editor.editing || !editBox.visible) return
|
|
14
|
+
if (point.name === 'circle') return // 独立旋转按钮
|
|
15
|
+
|
|
16
|
+
let { rotation } = editBox
|
|
17
|
+
const { resizeCursor, rotateCursor, skewCursor, resizeable, rotateable, skewable } = editor.mergeConfig
|
|
18
|
+
const { pointType } = point, { flippedX, flippedY } = editBox
|
|
19
|
+
|
|
20
|
+
let showResize = pointType === 'resize'
|
|
21
|
+
if (showResize && rotateable && (e.metaKey || e.ctrlKey || !resizeable)) showResize = false
|
|
22
|
+
const showSkew = skewable && !showResize && point.name === 'resize-line'
|
|
23
|
+
|
|
24
|
+
const cursor = showSkew ? skewCursor : (showResize ? resizeCursor : rotateCursor)
|
|
25
|
+
rotation += (EditDataHelper.getFlipDirection(point.direction, flippedX, flippedY) + 1) * 45
|
|
26
|
+
rotation = Math.round(MathHelper.formatRotation(rotation, true) / 2) * 2
|
|
27
|
+
|
|
28
|
+
const { url, x, y } = cursor
|
|
29
|
+
const key = url + rotation
|
|
30
|
+
|
|
31
|
+
if (cacheCursors[key]) {
|
|
32
|
+
point.cursor = cacheCursors[key]
|
|
33
|
+
} else {
|
|
34
|
+
cacheCursors[key] = point.cursor = { url: toDataURL(url, rotation), x, y }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function updateMoveCursor(editor: IEditor): void {
|
|
39
|
+
editor.editBox.rect.cursor = editor.mergeConfig.moveCursor
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
function toDataURL(svg: string, rotation: number): string {
|
|
44
|
+
return '"data:image/svg+xml,' + encodeURIComponent(svg.replace('{{rotation}}', rotation.toString())) + '"'
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IGroup, ILeaf } from '@leafer-ui/interface'
|
|
2
|
+
import { Bounds } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
import { IEditor } from '@leafer-in/interface'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function simulate(editor: IEditor) {
|
|
8
|
+
const { simulateTarget, leafList: targetList } = editor
|
|
9
|
+
const { x, y, width, height } = new Bounds().setListWithFn(targetList.list, (leaf: ILeaf) => leaf.worldBoxBounds)
|
|
10
|
+
|
|
11
|
+
const parent = simulateTarget.parent = targetList.list[0].leafer.zoomLayer as IGroup // follow zoomLayer zoom / move
|
|
12
|
+
const { scaleX, scaleY, e: worldX, f: worldY } = parent.__world
|
|
13
|
+
simulateTarget.reset({ x: (x - worldX) / scaleX, y: (y - worldY) / scaleY, width: width / scaleX, height: height / scaleY })
|
|
14
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { LeafList } from '@leafer-ui/draw'
|
|
2
|
+
|
|
3
|
+
import { IEditor, IUI } from '@leafer-in/interface'
|
|
4
|
+
|
|
5
|
+
import { simulate } from './simulate'
|
|
6
|
+
import { updateMoveCursor } from './cursor'
|
|
7
|
+
import { EditorEvent } from '../event/EditorEvent'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export function onTarget(editor: IEditor, oldValue: IUI | IUI[]): void {
|
|
11
|
+
const { target } = editor
|
|
12
|
+
if (target) {
|
|
13
|
+
editor.leafList = target instanceof LeafList ? target : new LeafList(target instanceof Array ? target : target as IUI)
|
|
14
|
+
} else {
|
|
15
|
+
editor.leafList.reset()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
editor.emitEvent(new EditorEvent(EditorEvent.SELECT, { editor, value: target, oldValue }))
|
|
19
|
+
editor.checkOpenedGroups()
|
|
20
|
+
|
|
21
|
+
if (editor.editing) {
|
|
22
|
+
editor.waitLeafer(() => {
|
|
23
|
+
if (editor.multiple) simulate(editor)
|
|
24
|
+
updateMoveCursor(editor)
|
|
25
|
+
editor.updateEditTool()
|
|
26
|
+
editor.update()
|
|
27
|
+
editor.listenTargetEvents()
|
|
28
|
+
})
|
|
29
|
+
} else {
|
|
30
|
+
editor.updateEditTool()
|
|
31
|
+
editor.removeTargetEvents()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export function onHover(editor: IEditor, oldValue: IUI): void {
|
|
37
|
+
editor.emitEvent(new EditorEvent(EditorEvent.HOVER, { editor, value: editor.hoverTarget, oldValue }))
|
|
38
|
+
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { IUI, IPointData } from '@leafer-ui/interface'
|
|
2
|
+
import { Event } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
import { IEditor, IEditorEvent } from '@leafer-in/interface'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
function toList(value: IUI | IUI[]): IUI[] {
|
|
8
|
+
return value ? (value instanceof Array ? value : [value]) : []
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class EditorEvent extends Event implements IEditorEvent {
|
|
12
|
+
|
|
13
|
+
static SELECT = 'editor.select'
|
|
14
|
+
static HOVER = 'editor.hover'
|
|
15
|
+
|
|
16
|
+
declare readonly target: IUI
|
|
17
|
+
readonly editor: IEditor
|
|
18
|
+
|
|
19
|
+
readonly value: IUI | IUI[]
|
|
20
|
+
readonly oldValue: IUI | IUI[]
|
|
21
|
+
|
|
22
|
+
get list() { return toList(this.value) }
|
|
23
|
+
get oldList() { return toList(this.oldValue) }
|
|
24
|
+
|
|
25
|
+
readonly worldOrigin: IPointData
|
|
26
|
+
declare readonly origin: IPointData
|
|
27
|
+
|
|
28
|
+
constructor(type: string, data?: IEditorEvent) {
|
|
29
|
+
super(type)
|
|
30
|
+
if (data) Object.assign(this, data)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IEditorGroupEvent } from '@leafer-in/interface'
|
|
2
|
+
|
|
3
|
+
import { EditorEvent } from './EditorEvent'
|
|
4
|
+
import { IGroup } from '@leafer-ui/interface'
|
|
5
|
+
import { } from '../tool/InnerEditor'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export class EditorGroupEvent extends EditorEvent implements IEditorGroupEvent {
|
|
9
|
+
|
|
10
|
+
static GROUP = 'editor.group'
|
|
11
|
+
static BEFORE_UNGROUP = 'editor.before_ungroup'
|
|
12
|
+
static UNGROUP = 'editor.ungroup'
|
|
13
|
+
|
|
14
|
+
static OPEN = 'editor.open_group'
|
|
15
|
+
static CLOSE = 'editor.close_group'
|
|
16
|
+
|
|
17
|
+
readonly editTarget: IGroup
|
|
18
|
+
|
|
19
|
+
constructor(type: string, data?: IEditorGroupEvent) {
|
|
20
|
+
super(type, data)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IEditorMoveEvent } from '@leafer-in/interface'
|
|
2
|
+
|
|
3
|
+
import { EditorEvent } from './EditorEvent'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class EditorMoveEvent extends EditorEvent implements IEditorMoveEvent {
|
|
7
|
+
|
|
8
|
+
static MOVE = 'editor.move'
|
|
9
|
+
|
|
10
|
+
readonly moveX: number
|
|
11
|
+
readonly moveY: number
|
|
12
|
+
|
|
13
|
+
constructor(type: string, data?: IEditorMoveEvent) {
|
|
14
|
+
super(type, data)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { IEditor, IEditorRotateEvent } from '@leafer-in/interface'
|
|
1
|
+
import { IEditorRotateEvent } from '@leafer-in/interface'
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { EditorEvent } from './EditorEvent'
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
export class EditorRotateEvent extends
|
|
6
|
+
export class EditorRotateEvent extends EditorEvent implements IEditorRotateEvent {
|
|
8
7
|
|
|
9
8
|
static ROTATE = 'editor.rotate'
|
|
10
9
|
|
|
11
|
-
declare readonly target: IUI
|
|
12
|
-
readonly editor: IEditor
|
|
13
|
-
|
|
14
10
|
// rotateOf(origin, rotation)
|
|
15
|
-
readonly origin: IPointData
|
|
16
11
|
readonly rotation: number
|
|
17
12
|
|
|
18
13
|
constructor(type: string, data?: IEditorRotateEvent) {
|
|
19
|
-
super(type)
|
|
20
|
-
if (data) Object.assign(this, data)
|
|
14
|
+
super(type, data)
|
|
21
15
|
}
|
|
22
16
|
|
|
23
17
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IAround, IDragEvent, IMatrixData } from '@leafer-ui/interface'
|
|
2
|
+
|
|
3
|
+
import { IEditorScaleEvent } from '@leafer-in/interface'
|
|
4
|
+
import { Direction9 } from '@leafer-ui/draw'
|
|
5
|
+
|
|
6
|
+
import { EditorEvent } from './EditorEvent'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export class EditorScaleEvent extends EditorEvent implements IEditorScaleEvent {
|
|
10
|
+
|
|
11
|
+
static SCALE = 'editor.scale'
|
|
12
|
+
|
|
13
|
+
// scaleOf(origin, scaleX, scaleY, resize)
|
|
14
|
+
readonly scaleX: number
|
|
15
|
+
readonly scaleY: number
|
|
16
|
+
readonly transform?: IMatrixData
|
|
17
|
+
|
|
18
|
+
readonly drag: IDragEvent
|
|
19
|
+
|
|
20
|
+
readonly direction: Direction9
|
|
21
|
+
readonly lockRatio: boolean
|
|
22
|
+
readonly around: IAround
|
|
23
|
+
|
|
24
|
+
constructor(type: string, data?: IEditorScaleEvent) {
|
|
25
|
+
super(type, data)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IEditorSkewEvent } from '@leafer-in/interface'
|
|
2
|
+
|
|
3
|
+
import { EditorEvent } from './EditorEvent'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class EditorSkewEvent extends EditorEvent implements IEditorSkewEvent {
|
|
7
|
+
|
|
8
|
+
static SKEW = 'editor.skew'
|
|
9
|
+
|
|
10
|
+
// skewOf(origin, skewX, skewY)
|
|
11
|
+
readonly skewX: number
|
|
12
|
+
readonly skewY: number
|
|
13
|
+
|
|
14
|
+
constructor(type: string, data?: IEditorSkewEvent) {
|
|
15
|
+
super(type, data)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IInnerEditorEvent, IInnerEditor } from '@leafer-in/interface'
|
|
2
|
+
|
|
3
|
+
import { EditorEvent } from './EditorEvent'
|
|
4
|
+
import { IUI } from '@leafer-ui/interface'
|
|
5
|
+
import { } from '../tool/InnerEditor'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export class InnerEditorEvent extends EditorEvent implements IInnerEditorEvent {
|
|
9
|
+
|
|
10
|
+
static BEFORE_OPEN = 'innerEditor.before_open'
|
|
11
|
+
static OPEN = 'innerEditor.open'
|
|
12
|
+
|
|
13
|
+
static BEFORE_CLOSE = 'innerEditor.before_close'
|
|
14
|
+
static CLOSE = 'innerEditor.close'
|
|
15
|
+
|
|
16
|
+
readonly editTarget: IUI
|
|
17
|
+
readonly innerEditor: IInnerEditor
|
|
18
|
+
|
|
19
|
+
constructor(type: string, data?: IInnerEditorEvent) {
|
|
20
|
+
super(type, data)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { IBoundsData, IPointData, IAround, IAlign } from '@leafer-ui/interface'
|
|
2
|
+
import { AroundHelper, PointHelper, Direction9 } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
import { IEditorScaleEvent, IEditorSkewEvent, IEditorRotateEvent } from '@leafer-in/interface'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const { topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left } = Direction9
|
|
8
|
+
const { toPoint } = AroundHelper
|
|
9
|
+
|
|
10
|
+
export const EditDataHelper = {
|
|
11
|
+
|
|
12
|
+
getScaleData(bounds: IBoundsData, direction: Direction9, pointMove: IPointData, lockRatio: boolean | 'corner', around: IAround): IEditorScaleEvent {
|
|
13
|
+
let align: IAlign, origin = {} as IPointData, scaleX: number = 1, scaleY: number = 1
|
|
14
|
+
const { width, height } = bounds
|
|
15
|
+
|
|
16
|
+
if (around) {
|
|
17
|
+
pointMove.x *= 2
|
|
18
|
+
pointMove.y *= 2
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 防止变为0
|
|
22
|
+
if (Math.abs(pointMove.x) === width) pointMove.x += 0.1
|
|
23
|
+
if (Math.abs(pointMove.y) === height) pointMove.y += 0.1
|
|
24
|
+
|
|
25
|
+
const topScale = (-pointMove.y + height) / height
|
|
26
|
+
const rightScale = (pointMove.x + width) / width
|
|
27
|
+
const bottomScale = (pointMove.y + height) / height
|
|
28
|
+
const leftScale = (-pointMove.x + width) / width
|
|
29
|
+
|
|
30
|
+
switch (direction) {
|
|
31
|
+
case top:
|
|
32
|
+
scaleY = topScale
|
|
33
|
+
align = 'bottom'
|
|
34
|
+
break
|
|
35
|
+
case right:
|
|
36
|
+
scaleX = rightScale
|
|
37
|
+
align = 'left'
|
|
38
|
+
break
|
|
39
|
+
case bottom:
|
|
40
|
+
scaleY = bottomScale
|
|
41
|
+
align = 'top'
|
|
42
|
+
break
|
|
43
|
+
case left:
|
|
44
|
+
scaleX = leftScale
|
|
45
|
+
align = 'right'
|
|
46
|
+
break
|
|
47
|
+
case topLeft:
|
|
48
|
+
scaleY = topScale
|
|
49
|
+
scaleX = leftScale
|
|
50
|
+
align = 'bottom-right'
|
|
51
|
+
break
|
|
52
|
+
case topRight:
|
|
53
|
+
scaleY = topScale
|
|
54
|
+
scaleX = rightScale
|
|
55
|
+
align = 'bottom-left'
|
|
56
|
+
break
|
|
57
|
+
case bottomRight:
|
|
58
|
+
scaleY = bottomScale
|
|
59
|
+
scaleX = rightScale
|
|
60
|
+
align = 'top-left'
|
|
61
|
+
break
|
|
62
|
+
case bottomLeft:
|
|
63
|
+
scaleY = bottomScale
|
|
64
|
+
scaleX = leftScale
|
|
65
|
+
align = 'top-right'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (lockRatio) {
|
|
69
|
+
const unlockSide = lockRatio === 'corner' && direction % 2
|
|
70
|
+
if (!unlockSide) {
|
|
71
|
+
const scale = Math.sqrt(Math.abs(scaleX * scaleY))
|
|
72
|
+
scaleX = scaleX < 0 ? -scale : scale
|
|
73
|
+
scaleY = scaleY < 0 ? -scale : scale
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
toPoint(around || align, bounds, origin)
|
|
78
|
+
|
|
79
|
+
return { origin, scaleX, scaleY, direction, lockRatio, around }
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
getRotateData(bounds: IBoundsData, direction: Direction9, current: IPointData, last: IPointData, around: IAround): IEditorRotateEvent {
|
|
83
|
+
let align: IAlign, origin = {} as IPointData
|
|
84
|
+
|
|
85
|
+
switch (direction) {
|
|
86
|
+
case topLeft:
|
|
87
|
+
align = 'bottom-right'
|
|
88
|
+
break
|
|
89
|
+
case topRight:
|
|
90
|
+
align = 'bottom-left'
|
|
91
|
+
break
|
|
92
|
+
case bottomRight:
|
|
93
|
+
align = 'top-left'
|
|
94
|
+
break
|
|
95
|
+
case bottomLeft:
|
|
96
|
+
align = 'top-right'
|
|
97
|
+
break
|
|
98
|
+
default:
|
|
99
|
+
align = 'center'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
toPoint(around || align, bounds, origin)
|
|
103
|
+
|
|
104
|
+
return { origin, rotation: PointHelper.getRotation(last, origin, current) }
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
getSkewData(bounds: IBoundsData, direction: Direction9, move: IPointData, around: IAround): IEditorSkewEvent {
|
|
108
|
+
let align: IAlign, origin = {} as IPointData, skewX = 0, skewY = 0
|
|
109
|
+
let last: IPointData
|
|
110
|
+
|
|
111
|
+
switch (direction) {
|
|
112
|
+
case top:
|
|
113
|
+
last = { x: 0.5, y: 0 }
|
|
114
|
+
align = 'bottom'
|
|
115
|
+
skewX = 1
|
|
116
|
+
break
|
|
117
|
+
case bottom:
|
|
118
|
+
last = { x: 0.5, y: 1 }
|
|
119
|
+
align = 'top'
|
|
120
|
+
skewX = 1
|
|
121
|
+
break
|
|
122
|
+
case left:
|
|
123
|
+
last = { x: 0, y: 0.5 }
|
|
124
|
+
align = 'right'
|
|
125
|
+
skewY = 1
|
|
126
|
+
break
|
|
127
|
+
case right:
|
|
128
|
+
last = { x: 1, y: 0.5 }
|
|
129
|
+
align = 'left'
|
|
130
|
+
skewY = 1
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { x, y, width, height } = bounds
|
|
134
|
+
|
|
135
|
+
last.x = x + last.x * width
|
|
136
|
+
last.y = y + last.y * height
|
|
137
|
+
|
|
138
|
+
toPoint(around || align, bounds, origin)
|
|
139
|
+
|
|
140
|
+
const rotation = PointHelper.getRotation(last, origin, { x: last.x + (skewX ? move.x : 0), y: last.y + (skewY ? move.y : 0) })
|
|
141
|
+
skewX ? skewX = -rotation : skewY = rotation
|
|
142
|
+
|
|
143
|
+
return { origin, skewX, skewY }
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
getAround(around: IAround, altKey: boolean): IAround {
|
|
148
|
+
return (altKey && !around) ? 'center' : around
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
getRotateDirection(direction: number, rotation: number, totalDirection = 8): number {
|
|
152
|
+
direction = (direction + Math.round(rotation / (360 / totalDirection))) % totalDirection
|
|
153
|
+
if (direction < 0) direction += totalDirection
|
|
154
|
+
return direction
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
getFlipDirection(direction: Direction9, flipedX: boolean, flipedY: boolean): Direction9 {
|
|
158
|
+
if (flipedX) {
|
|
159
|
+
switch (direction) {
|
|
160
|
+
case left: direction = right; break
|
|
161
|
+
case topLeft: direction = topRight; break
|
|
162
|
+
case bottomLeft: direction = bottomRight; break
|
|
163
|
+
case right: direction = left; break
|
|
164
|
+
case topRight: direction = topLeft; break
|
|
165
|
+
case bottomRight: direction = bottomLeft; break
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (flipedY) {
|
|
170
|
+
switch (direction) {
|
|
171
|
+
case top: direction = bottom; break
|
|
172
|
+
case topLeft: direction = bottomLeft; break
|
|
173
|
+
case topRight: direction = bottomRight; break
|
|
174
|
+
case bottom: direction = top; break
|
|
175
|
+
case bottomLeft: direction = topLeft; break
|
|
176
|
+
case bottomRight: direction = topRight; break
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return direction
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { IBounds, ILeafList, IUI } from '@leafer-ui/interface'
|
|
2
|
+
import { Answer } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
const { No, Yes, NoAndSkip, YesAndSkip } = Answer
|
|
5
|
+
|
|
6
|
+
export const EditSelectHelper = {
|
|
7
|
+
|
|
8
|
+
findOne(path: ILeafList): IUI {
|
|
9
|
+
return path.list.find((leaf) => leaf.editable) as IUI
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
findBounds(leaf: IUI, bounds: IBounds): Answer {
|
|
13
|
+
if (leaf.__.hittable && leaf.__.visible && !leaf.__.locked && bounds.hit(leaf.__world)) {
|
|
14
|
+
|
|
15
|
+
if (leaf.__.editable) {
|
|
16
|
+
if (leaf.isBranch && !leaf.__.hitChildren) {
|
|
17
|
+
return leaf.__.hitSelf ? YesAndSkip : NoAndSkip
|
|
18
|
+
} else if (leaf.isFrame) {
|
|
19
|
+
return bounds.includes(leaf.__layout.boxBounds, leaf.__world) ? YesAndSkip : No
|
|
20
|
+
} else {
|
|
21
|
+
if (bounds.hit(leaf.__layout.boxBounds, leaf.__world) && leaf.__.hitSelf) return Yes
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return No
|
|
26
|
+
|
|
27
|
+
} else {
|
|
28
|
+
|
|
29
|
+
return leaf.isBranch ? NoAndSkip : No
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { IGroup, IGroupInputData, ILeaf, IUI } from '@leafer-ui/interface'
|
|
2
|
+
import { Group, Matrix } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const order = (a: ILeaf, b: ILeaf) => a.parent.children.indexOf(a) - b.parent.children.indexOf(b)
|
|
6
|
+
const reverseOrder = (a: ILeaf, b: ILeaf) => b.parent.children.indexOf(b) - a.parent.children.indexOf(a)
|
|
7
|
+
|
|
8
|
+
export const EditorHelper = {
|
|
9
|
+
|
|
10
|
+
group(list: IUI[], element?: IUI, userGroup?: IGroup | IGroupInputData): IGroup {
|
|
11
|
+
list.sort(reverseOrder)
|
|
12
|
+
const { app, parent } = list[0]
|
|
13
|
+
|
|
14
|
+
let group: IGroup
|
|
15
|
+
if (userGroup && (userGroup as IGroup).add) {
|
|
16
|
+
group = userGroup as IGroup
|
|
17
|
+
} else {
|
|
18
|
+
group = new Group(userGroup)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
parent.addAt(group, parent.children.indexOf(list[0]))
|
|
22
|
+
list.sort(order)
|
|
23
|
+
|
|
24
|
+
const matrx = new Matrix(element.worldTransform)
|
|
25
|
+
matrx.divideParent(parent.worldTransform)
|
|
26
|
+
group.setTransform(matrx)
|
|
27
|
+
group.editable = true
|
|
28
|
+
group.hitChildren = false
|
|
29
|
+
|
|
30
|
+
app.lockLayout()
|
|
31
|
+
list.forEach(child => child.dropTo(group))
|
|
32
|
+
app.unlockLayout()
|
|
33
|
+
|
|
34
|
+
return group
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
ungroup(list: IUI[]): IUI[] {
|
|
38
|
+
const { app } = list[0]
|
|
39
|
+
const ungroupList: IUI[] = []
|
|
40
|
+
|
|
41
|
+
app.lockLayout()
|
|
42
|
+
list.forEach(leaf => {
|
|
43
|
+
if (leaf.isBranch) {
|
|
44
|
+
const { parent, children } = leaf
|
|
45
|
+
while (children.length) {
|
|
46
|
+
ungroupList.push(children[0])
|
|
47
|
+
children[0].dropTo(parent, parent.children.indexOf(leaf))
|
|
48
|
+
}
|
|
49
|
+
leaf.remove()
|
|
50
|
+
} else {
|
|
51
|
+
ungroupList.push(leaf)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
app.unlockLayout()
|
|
55
|
+
|
|
56
|
+
return ungroupList
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
toTop(list: IUI[]): void {
|
|
60
|
+
list.sort(order)
|
|
61
|
+
list.forEach(leaf => {
|
|
62
|
+
if (leaf.parent) leaf.parent.add(leaf)
|
|
63
|
+
})
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
toBottom(list: IUI[]): void {
|
|
67
|
+
list.sort(reverseOrder)
|
|
68
|
+
list.forEach(leaf => {
|
|
69
|
+
if (leaf.parent) leaf.parent.addAt(leaf, 0)
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|