@leafer-in/editor 1.0.0-beta.15
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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/editor.esm.js +510 -0
- package/dist/editor.esm.min.js +1 -0
- package/dist/editor.js +520 -0
- package/dist/editor.min.js +1 -0
- package/package.json +39 -0
- package/src/Editor.ts +232 -0
- package/src/cursor.ts +57 -0
- package/src/event/EditorResizeEvent.ts +34 -0
- package/src/event/EditorRotateEvent.ts +23 -0
- package/src/index.ts +5 -0
- package/src/resize.ts +87 -0
- package/src/tool/LineTool.ts +88 -0
- package/src/tool/RectTool.ts +139 -0
- package/types/index.d.ts +68 -0
package/src/Editor.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { IGroupInputData, IPolygon, IUI, IEventListenerId } from '@leafer-ui/interface'
|
|
2
|
+
import { IEditor, IEditorConfig, IEditorTool, IDirection8 } from '@leafer-in/interface'
|
|
3
|
+
|
|
4
|
+
import { Group, Rect, Polygon, DragEvent, PointHelper, PointerEvent, KeyEvent, RotateEvent, DataHelper, MathHelper, RenderEvent } from '@leafer-ui/core'
|
|
5
|
+
|
|
6
|
+
import { getResizeData } from './resize'
|
|
7
|
+
import { updateCursor } from './cursor'
|
|
8
|
+
|
|
9
|
+
import { LineTool } from './tool/LineTool'
|
|
10
|
+
import { RectTool } from './tool/RectTool'
|
|
11
|
+
|
|
12
|
+
import { EditorResizeEvent } from './event/EditorResizeEvent'
|
|
13
|
+
import { EditorRotateEvent } from './event/EditorRotateEvent'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export class Editor extends Group implements IEditor {
|
|
17
|
+
|
|
18
|
+
public config: IEditorConfig = {
|
|
19
|
+
type: 'pc',
|
|
20
|
+
stroke: '#836DFF',
|
|
21
|
+
pointFill: '#FFFFFF',
|
|
22
|
+
pointSize: 10,
|
|
23
|
+
pointRadius: 10,
|
|
24
|
+
rotateGap: 90,
|
|
25
|
+
hideOnMove: false,
|
|
26
|
+
moveCursor: 'move',
|
|
27
|
+
resizeType: 'auto',
|
|
28
|
+
resizeCursor: ['nwse-resize', 'ns-resize', 'nesw-resize', 'ew-resize', 'nwse-resize', 'ns-resize', 'nesw-resize', 'ew-resize'],
|
|
29
|
+
rotateCursor: ['ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize', 'nw-resize', 'n-resize'],
|
|
30
|
+
resizeable: true,
|
|
31
|
+
rotateable: true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public resizePoints: IUI[] = [] // topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
|
|
35
|
+
public rotatePoints: IUI[] = [] // topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
|
|
36
|
+
public resizeLines: IUI[] = [] // top, right, bottom, left
|
|
37
|
+
|
|
38
|
+
public targetRect: IUI = new Rect({ hitFill: 'all', hitRadius: 5 })
|
|
39
|
+
public rect: IPolygon = new Polygon({ hittable: false, strokeAlign: 'center' })
|
|
40
|
+
public circle: IUI = new Rect({ around: 'center', hitRadius: 10 }) // rotate point
|
|
41
|
+
|
|
42
|
+
public tool: IEditorTool
|
|
43
|
+
|
|
44
|
+
private _target: IUI
|
|
45
|
+
public get target(): IUI { return this._target }
|
|
46
|
+
public set target(value: IUI) {
|
|
47
|
+
this.__removeTargetEvents()
|
|
48
|
+
this.visible = !!value
|
|
49
|
+
|
|
50
|
+
this._target = value
|
|
51
|
+
if (value) this.onTarget()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public enterPoint: IUI
|
|
55
|
+
|
|
56
|
+
protected __eventIds: IEventListenerId[] = []
|
|
57
|
+
protected __targetEventIds: IEventListenerId[] = []
|
|
58
|
+
|
|
59
|
+
constructor(userConfig?: IEditorConfig, data?: IGroupInputData) {
|
|
60
|
+
super(data)
|
|
61
|
+
if (userConfig) this.config = DataHelper.default(userConfig, this.config)
|
|
62
|
+
this.init()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
protected init() {
|
|
66
|
+
let rotatePoint: IUI, resizeLine: IUI, resizePoint: IUI
|
|
67
|
+
const { resizePoints, rotatePoints, resizeLines } = this
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < 8; i++) {
|
|
70
|
+
rotatePoint = new Rect({ around: 'center', width: 30, height: 30, hitRadius: 10, hitFill: "all" })
|
|
71
|
+
rotatePoints.push(rotatePoint)
|
|
72
|
+
this.__listenPointEvents(rotatePoint, 'rotate', i)
|
|
73
|
+
|
|
74
|
+
if (i % 2) {
|
|
75
|
+
resizeLine = new Rect({ around: 'center', width: 10, height: 10, hitFill: "all" })
|
|
76
|
+
resizeLines.push(resizeLine)
|
|
77
|
+
this.__listenPointEvents(resizeLine, 'resize', i)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
resizePoint = new Rect({ around: 'center', hitRadius: 5 })
|
|
81
|
+
resizePoints.push(resizePoint)
|
|
82
|
+
this.__listenPointEvents(resizePoint, 'resize', i)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.__listenPointEvents(this.circle, 'rotate', 1)
|
|
86
|
+
this.addMany(...rotatePoints, this.targetRect, this.rect, this.circle, ...resizeLines, ...resizePoints)
|
|
87
|
+
|
|
88
|
+
this.__listenEvents()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
protected onTarget(): void {
|
|
93
|
+
this.tool = this.getTool(this.target)
|
|
94
|
+
this.waitLeafer(() => {
|
|
95
|
+
this.update()
|
|
96
|
+
this.updateMoveCursor()
|
|
97
|
+
this.__listenTargetEvents()
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public getTool(value: IUI): IEditorTool {
|
|
102
|
+
return (value.tag === 'Line' && value.resizeable) ? LineTool : RectTool
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public update(): void {
|
|
106
|
+
if (!this.target) return
|
|
107
|
+
this.tool.update(this)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
protected onDrag(e: DragEvent): void {
|
|
112
|
+
const { resizeable, rotateable } = this.config
|
|
113
|
+
if (e.metaKey || e.ctrlKey || !resizeable) {
|
|
114
|
+
if (rotateable) this.onRotate(e)
|
|
115
|
+
} else {
|
|
116
|
+
this.onResize(e)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
protected onMove(e: DragEvent): void {
|
|
121
|
+
const { target } = this
|
|
122
|
+
const { x, y } = e.getLocalMove(target)
|
|
123
|
+
if (e.shiftKey) {
|
|
124
|
+
if (Math.abs(x) > Math.abs(y)) {
|
|
125
|
+
target.x += x
|
|
126
|
+
} else {
|
|
127
|
+
target.y += y
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
target.x += x
|
|
131
|
+
target.y += y
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected onRotate(e: DragEvent | RotateEvent): void {
|
|
136
|
+
const { target } = this
|
|
137
|
+
const { rotateGap } = this.config
|
|
138
|
+
const { x, y, width, height } = target.boxBounds
|
|
139
|
+
const origin = { x: x + width / 2, y: y + height / 2 }
|
|
140
|
+
|
|
141
|
+
let rotation: number
|
|
142
|
+
|
|
143
|
+
if (e instanceof RotateEvent) {
|
|
144
|
+
rotation = e.rotation
|
|
145
|
+
} else {
|
|
146
|
+
const point = e
|
|
147
|
+
const last = { x: point.x - e.moveX, y: point.y - e.moveY }
|
|
148
|
+
rotation = PointHelper.getChangeAngle(last, target.getWorldPoint(origin), point)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
rotation = MathHelper.getGapRotation(target.rotation + rotation, rotateGap) - target.rotation
|
|
152
|
+
|
|
153
|
+
const event = new EditorRotateEvent(EditorRotateEvent.ROTATE, { editor: this, target, origin, rotation })
|
|
154
|
+
|
|
155
|
+
this.tool.rotate(event)
|
|
156
|
+
target.emitEvent(event)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public onResize(e: DragEvent): void {
|
|
160
|
+
const { target } = this
|
|
161
|
+
const { __direction } = e.current.__
|
|
162
|
+
|
|
163
|
+
let { resizeType, around, lockRatio } = this.config
|
|
164
|
+
|
|
165
|
+
if (e.shiftKey) lockRatio = true
|
|
166
|
+
if (e.altKey && !around) around = 'center'
|
|
167
|
+
|
|
168
|
+
if (resizeType === 'auto') resizeType = target.resizeable ? 'size' : 'scale'
|
|
169
|
+
const data = getResizeData(target.boxBounds, __direction, e.getInnerMove(this.targetRect), lockRatio, around)
|
|
170
|
+
|
|
171
|
+
const event = new EditorResizeEvent(EditorResizeEvent.RESIZE, { ...data, target, editor: this, dragEvent: e, resizeType })
|
|
172
|
+
|
|
173
|
+
this.tool.resize(event)
|
|
174
|
+
target.emitEvent(event)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
public updateMoveCursor(): void {
|
|
179
|
+
this.targetRect.cursor = this.config.moveCursor
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
protected __listenEvents(): void {
|
|
184
|
+
this.__eventIds = [
|
|
185
|
+
this.targetRect.on_(DragEvent.START, () => { this.opacity = this.config.hideOnMove ? 0 : 1 }),
|
|
186
|
+
this.targetRect.on_(DragEvent.DRAG, this.onMove, this),
|
|
187
|
+
this.targetRect.on_(DragEvent.END, () => { this.opacity = 1 }),
|
|
188
|
+
this.targetRect.on_(PointerEvent.ENTER, this.updateMoveCursor, this)
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
protected __removeListenEvents(): void {
|
|
193
|
+
this.targetRect.off_(this.__eventIds)
|
|
194
|
+
this.__eventIds.length = 0
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
protected __listenPointEvents(point: IUI, type: 'rotate' | 'resize', direction: IDirection8): void {
|
|
199
|
+
point.__.__direction = direction
|
|
200
|
+
const resize = point.__.__isResizePoint = type === 'resize'
|
|
201
|
+
point.on_(DragEvent.DRAG, resize ? this.onDrag : this.onRotate, this) // i % 2 ? this.onSkew :
|
|
202
|
+
point.on_(PointerEvent.LEAVE, () => this.enterPoint = null)
|
|
203
|
+
point.on_(PointerEvent.ENTER, (e) => { this.enterPoint = point; updateCursor(this, e) })
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
protected __listenTargetEvents(): void {
|
|
208
|
+
if (this.target) {
|
|
209
|
+
const { leafer } = this.target
|
|
210
|
+
this.__targetEventIds = [
|
|
211
|
+
leafer.on_(RenderEvent.START, this.update, this),
|
|
212
|
+
leafer.on_([KeyEvent.HOLD, KeyEvent.UP], (e) => { updateCursor(this, e) })
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
protected __removeTargetEvents(): void {
|
|
218
|
+
if (this.__targetEventIds.length) {
|
|
219
|
+
const { leafer } = this.target
|
|
220
|
+
if (leafer) leafer.off_(this.__targetEventIds)
|
|
221
|
+
this.__targetEventIds.length = 0
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
public destroy(): void {
|
|
227
|
+
this.__removeListenEvents()
|
|
228
|
+
this._target = null
|
|
229
|
+
super.destroy()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
}
|
package/src/cursor.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ICursorType, IUIEvent } from '@leafer-ui/interface'
|
|
2
|
+
import { IDirection8, IEditor } from '@leafer-in/interface'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const { topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left } = IDirection8
|
|
6
|
+
|
|
7
|
+
export function updateCursor(editor: IEditor, e: IUIEvent): void {
|
|
8
|
+
|
|
9
|
+
const point = editor.enterPoint
|
|
10
|
+
if (!point || !editor.target || !editor.visible) return
|
|
11
|
+
|
|
12
|
+
let { rotation } = editor
|
|
13
|
+
let { resizeCursor, rotateCursor, resizeable } = editor.config
|
|
14
|
+
const mirror = editor.tool.getMirrorData(editor)
|
|
15
|
+
const { __direction, __isResizePoint } = point.__
|
|
16
|
+
|
|
17
|
+
editor.enterPoint = point
|
|
18
|
+
|
|
19
|
+
if (__isResizePoint && (e.metaKey || e.ctrlKey || !resizeable)) resizeCursor = rotateCursor
|
|
20
|
+
|
|
21
|
+
if (mirror.x || mirror.y) {
|
|
22
|
+
mirrorCursors(resizeCursor = [...resizeCursor], mirror.x, mirror.y)
|
|
23
|
+
mirrorCursors(rotateCursor = [...rotateCursor], mirror.y, mirror.x)
|
|
24
|
+
if (mirror.x + mirror.y === 1) rotation = -rotation
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let index = (__direction + Math.round(rotation / 45)) % 8
|
|
28
|
+
if (index < 0) index += 8
|
|
29
|
+
|
|
30
|
+
point.cursor = __isResizePoint ? resizeCursor[index] : rotateCursor[index]
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
export function mirrorCursors(mirror: ICursorType[], mirrorX: number, mirrorY: number): void {
|
|
36
|
+
|
|
37
|
+
if (mirrorX) {
|
|
38
|
+
const topCursor = mirror[top], topLeftCursor = mirror[topLeft], topRightCursor = mirror[topRight]
|
|
39
|
+
mirror[top] = mirror[bottom]
|
|
40
|
+
mirror[topLeft] = mirror[bottomLeft]
|
|
41
|
+
mirror[topRight] = mirror[bottomRight]
|
|
42
|
+
mirror[bottom] = topCursor
|
|
43
|
+
mirror[bottomLeft] = topLeftCursor
|
|
44
|
+
mirror[bottomRight] = topRightCursor
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (mirrorY) {
|
|
48
|
+
const leftCursor = mirror[left], topLeftCursor = mirror[topLeft], bottomLeftCursor = mirror[bottomLeft]
|
|
49
|
+
mirror[left] = mirror[right]
|
|
50
|
+
mirror[topLeft] = mirror[topRight]
|
|
51
|
+
mirror[bottomLeft] = mirror[bottomRight]
|
|
52
|
+
mirror[right] = leftCursor
|
|
53
|
+
mirror[topRight] = topLeftCursor
|
|
54
|
+
mirror[bottomRight] = bottomLeftCursor
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { IUI, IResizeType, IBoundsData, IPointData, IAround, IDragEvent } from '@leafer-ui/interface'
|
|
2
|
+
import { IEditor, IDirection8, IEditorResizeEvent } from '@leafer-in/interface'
|
|
3
|
+
|
|
4
|
+
import { Event } from '@leafer-ui/core'
|
|
5
|
+
|
|
6
|
+
export class EditorResizeEvent extends Event implements IEditorResizeEvent {
|
|
7
|
+
|
|
8
|
+
static RESIZE = 'editor.resize'
|
|
9
|
+
|
|
10
|
+
declare readonly target: IUI
|
|
11
|
+
readonly editor: IEditor
|
|
12
|
+
|
|
13
|
+
readonly resizeType: IResizeType
|
|
14
|
+
readonly lockRatio: boolean
|
|
15
|
+
readonly around: IAround
|
|
16
|
+
|
|
17
|
+
readonly dragEvent: IDragEvent
|
|
18
|
+
readonly direction: IDirection8
|
|
19
|
+
|
|
20
|
+
// from old to bounds
|
|
21
|
+
readonly bounds: IBoundsData
|
|
22
|
+
readonly old: IBoundsData
|
|
23
|
+
|
|
24
|
+
// scaleOf(origin, scaleX, scaleY)
|
|
25
|
+
readonly origin: IPointData
|
|
26
|
+
readonly scaleX: number
|
|
27
|
+
readonly scaleY: number
|
|
28
|
+
|
|
29
|
+
constructor(type: string, data?: IEditorResizeEvent) {
|
|
30
|
+
super(type)
|
|
31
|
+
if (data) Object.assign(this, data)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IUI, IPointData } from '@leafer-ui/interface'
|
|
2
|
+
import { IEditor, IEditorRotateEvent } from '@leafer-in/interface'
|
|
3
|
+
|
|
4
|
+
import { Event } from '@leafer-ui/core'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class EditorRotateEvent extends Event implements IEditorRotateEvent {
|
|
8
|
+
|
|
9
|
+
static ROTATE = 'editor.rotate'
|
|
10
|
+
|
|
11
|
+
declare readonly target: IUI
|
|
12
|
+
readonly editor: IEditor
|
|
13
|
+
|
|
14
|
+
// rotateOf(origin, rotation)
|
|
15
|
+
readonly origin: IPointData
|
|
16
|
+
readonly rotation: number
|
|
17
|
+
|
|
18
|
+
constructor(type: string, data?: IEditorRotateEvent) {
|
|
19
|
+
super(type)
|
|
20
|
+
if (data) Object.assign(this, data)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
package/src/index.ts
ADDED
package/src/resize.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { IBoundsData, IPointData, IMatrixData, IAround } from '@leafer-ui/interface'
|
|
2
|
+
import { IEditorResizeEvent, IDirection8 } from '@leafer-in/interface'
|
|
3
|
+
|
|
4
|
+
import { MatrixHelper } from '@leafer-ui/core'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const { scaleOfOuter, reset } = MatrixHelper
|
|
8
|
+
const { topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left } = IDirection8
|
|
9
|
+
const matrix = {} as IMatrixData
|
|
10
|
+
|
|
11
|
+
export function getResizeData(old: IBoundsData, direction: IDirection8, move: IPointData, lockRatio: boolean, around: IAround): IEditorResizeEvent {
|
|
12
|
+
|
|
13
|
+
if (around) {
|
|
14
|
+
move.x *= 2
|
|
15
|
+
move.y *= 2
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let origin: IPointData, scaleX: number = 1, scaleY: number = 1
|
|
19
|
+
const { x, y, width, height } = old
|
|
20
|
+
|
|
21
|
+
const topScale = (-move.y + height) / height
|
|
22
|
+
const rightScale = (move.x + width) / width
|
|
23
|
+
const bottomScale = (move.y + height) / height
|
|
24
|
+
const leftScale = (-move.x + width) / width
|
|
25
|
+
|
|
26
|
+
switch (direction) {
|
|
27
|
+
case top:
|
|
28
|
+
scaleY = topScale
|
|
29
|
+
if (lockRatio) scaleX = scaleY
|
|
30
|
+
origin = { x: x + width / 2, y: y + height }
|
|
31
|
+
break
|
|
32
|
+
case right:
|
|
33
|
+
scaleX = rightScale
|
|
34
|
+
if (lockRatio) scaleY = scaleX
|
|
35
|
+
origin = { x, y: y + height / 2 }
|
|
36
|
+
break
|
|
37
|
+
case bottom:
|
|
38
|
+
scaleY = bottomScale
|
|
39
|
+
if (lockRatio) scaleX = scaleY
|
|
40
|
+
origin = { x: x + width / 2, y }
|
|
41
|
+
break
|
|
42
|
+
case left:
|
|
43
|
+
scaleX = leftScale
|
|
44
|
+
if (lockRatio) scaleY = scaleX
|
|
45
|
+
origin = { x: x + width, y: y + height / 2 }
|
|
46
|
+
break
|
|
47
|
+
case topLeft:
|
|
48
|
+
scaleY = topScale
|
|
49
|
+
scaleX = leftScale
|
|
50
|
+
if (lockRatio) scaleX = scaleY
|
|
51
|
+
origin = { x: x + width, y: y + height }
|
|
52
|
+
break
|
|
53
|
+
case topRight:
|
|
54
|
+
scaleY = topScale
|
|
55
|
+
scaleX = rightScale
|
|
56
|
+
if (lockRatio) scaleX = scaleY
|
|
57
|
+
origin = { x, y: y + height }
|
|
58
|
+
break
|
|
59
|
+
case bottomRight:
|
|
60
|
+
scaleY = bottomScale
|
|
61
|
+
scaleX = rightScale
|
|
62
|
+
if (lockRatio) scaleX = scaleY
|
|
63
|
+
origin = { x, y }
|
|
64
|
+
break
|
|
65
|
+
case bottomLeft:
|
|
66
|
+
scaleY = bottomScale
|
|
67
|
+
scaleX = leftScale
|
|
68
|
+
if (lockRatio) scaleX = scaleY
|
|
69
|
+
origin = { x: x + width, y }
|
|
70
|
+
break
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (around) {
|
|
74
|
+
if (typeof around === 'object') {
|
|
75
|
+
origin = { x: x + width / around.x, y: y + height / around.y }
|
|
76
|
+
} else {
|
|
77
|
+
origin = { x: x + width / 2, y: y + height / 2 }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
reset(matrix)
|
|
82
|
+
scaleOfOuter(matrix, origin, scaleX, scaleY)
|
|
83
|
+
const bounds = { x: old.x + matrix.e, y: old.y + matrix.f, width: width * scaleX, height: height * scaleY }
|
|
84
|
+
return { bounds, old, origin, scaleX, scaleY, direction, lockRatio, around, }
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { IDirection8, IEditor, IEditorTool, IEditorResizeEvent, IEditorRotateEvent, ILine, IPointData } from '@leafer-in/interface'
|
|
2
|
+
|
|
3
|
+
import { RectTool } from './RectTool'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const { left, right } = IDirection8
|
|
7
|
+
|
|
8
|
+
export const LineTool: IEditorTool = {
|
|
9
|
+
|
|
10
|
+
name: 'LineTool',
|
|
11
|
+
|
|
12
|
+
getMirrorData(_editor: IEditor): IPointData {
|
|
13
|
+
return {
|
|
14
|
+
x: 0,
|
|
15
|
+
y: 0
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
resize(e: IEditorResizeEvent): void {
|
|
20
|
+
const { direction, dragEvent, lockRatio, around } = e
|
|
21
|
+
const target = e.target as ILine
|
|
22
|
+
|
|
23
|
+
const fromPoint = { x: 0, y: 0 }
|
|
24
|
+
const { toPoint } = target
|
|
25
|
+
|
|
26
|
+
target.rotation = 0
|
|
27
|
+
|
|
28
|
+
let { x, y } = dragEvent.getInnerMove(target)
|
|
29
|
+
|
|
30
|
+
if (lockRatio) {
|
|
31
|
+
if (Math.abs(x) > Math.abs(y)) {
|
|
32
|
+
y = 0
|
|
33
|
+
} else {
|
|
34
|
+
x = 0
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (direction === left) {
|
|
39
|
+
|
|
40
|
+
fromPoint.x += x
|
|
41
|
+
fromPoint.y += y
|
|
42
|
+
|
|
43
|
+
if (around) {
|
|
44
|
+
toPoint.x -= x
|
|
45
|
+
toPoint.y -= y
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
} else {
|
|
49
|
+
|
|
50
|
+
if (around) {
|
|
51
|
+
fromPoint.x -= x
|
|
52
|
+
fromPoint.y -= y
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
toPoint.x += x
|
|
56
|
+
toPoint.y += y
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
target.getLocalPointByInner(fromPoint, null, null, true)
|
|
61
|
+
target.getLocalPointByInner(toPoint, null, null, true)
|
|
62
|
+
target.x = fromPoint.x
|
|
63
|
+
target.y = fromPoint.y
|
|
64
|
+
|
|
65
|
+
target.getInnerPointByLocal(toPoint, null, null, true)
|
|
66
|
+
target.toPoint = toPoint
|
|
67
|
+
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
rotate(e: IEditorRotateEvent): void {
|
|
71
|
+
RectTool.rotate(e)
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
update(editor: IEditor) {
|
|
75
|
+
|
|
76
|
+
const { rotatePoints, circle, resizeLines, resizePoints } = editor
|
|
77
|
+
RectTool.update(editor)
|
|
78
|
+
|
|
79
|
+
for (let i = 0; i < 8; i++) {
|
|
80
|
+
if (i < 4) resizeLines[i].visible = false
|
|
81
|
+
resizePoints[i].visible = rotatePoints[i].visible = i === left || i === right
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
circle.visible = false
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { IUI, IUIInputData, IPointData } from '@leafer-ui/interface'
|
|
2
|
+
import { IEditor, IEditorResizeEvent, IEditorRotateEvent, IEditorTool } from '@leafer-in/interface'
|
|
3
|
+
|
|
4
|
+
import { Bounds, Matrix } from '@leafer-ui/core'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const RectTool: IEditorTool = {
|
|
8
|
+
|
|
9
|
+
name: 'RectTool',
|
|
10
|
+
|
|
11
|
+
getMirrorData(editor: IEditor): IPointData {
|
|
12
|
+
const { scaleX, scaleY } = editor.target
|
|
13
|
+
return {
|
|
14
|
+
x: scaleX < 0 ? 1 : 0, // 1 = mirrorX
|
|
15
|
+
y: scaleY < 0 ? 1 : 0
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
resize(e: IEditorResizeEvent): void {
|
|
20
|
+
const { target, bounds, resizeType, old } = e
|
|
21
|
+
const { x, y, width, height } = bounds
|
|
22
|
+
const point = { x: x - old.x, y: y - old.y }
|
|
23
|
+
|
|
24
|
+
target.innerToWorld(point, null, true, target.parent)
|
|
25
|
+
target.x += point.x
|
|
26
|
+
target.y += point.y
|
|
27
|
+
|
|
28
|
+
if (resizeType === 'scale') {
|
|
29
|
+
target.scaleX *= width / old.width
|
|
30
|
+
target.scaleY *= height / old.height
|
|
31
|
+
} else {
|
|
32
|
+
if (width < 0) {
|
|
33
|
+
target.width = -width
|
|
34
|
+
target.scaleX *= -1
|
|
35
|
+
} else {
|
|
36
|
+
if (target.width !== width) target.width = width
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (height < 0) {
|
|
40
|
+
target.height = -height
|
|
41
|
+
target.scaleY *= -1
|
|
42
|
+
} else {
|
|
43
|
+
if (target.height !== height) target.height = height // Text auto height
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
rotate(e: IEditorRotateEvent): void {
|
|
51
|
+
const { target, rotation, origin } = e
|
|
52
|
+
target.rotateOf(origin, rotation)
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
update(editor: IEditor) {
|
|
56
|
+
const { target, config, rotatePoints, targetRect, rect, circle, resizeLines, resizePoints } = editor
|
|
57
|
+
const { type, resizeable, rotateable, stroke, pointFill, pointSize, pointRadius } = config
|
|
58
|
+
|
|
59
|
+
const defaultStyle = { fill: pointFill, stroke, width: pointSize, height: pointSize, cornerRadius: pointRadius }
|
|
60
|
+
const pointStyles = config.point instanceof Array ? config.point : [config.point || defaultStyle]
|
|
61
|
+
|
|
62
|
+
const box = new Bounds(target.boxBounds)
|
|
63
|
+
const w = target.worldTransform, pw = editor.parent.worldTransform
|
|
64
|
+
|
|
65
|
+
const matrix = new Matrix(w)
|
|
66
|
+
matrix.divide(pw)
|
|
67
|
+
const worldX = matrix.e, worldY = matrix.f
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
let { scaleX, scaleY, rotation, skewX, skewY } = w
|
|
71
|
+
scaleX /= pw.scaleX, scaleY /= pw.scaleY, rotation -= pw.rotation, skewX -= pw.skewX, skewY -= pw.skewY
|
|
72
|
+
|
|
73
|
+
const { x, y, width, height } = box.scale(scaleX, scaleY) // maybe width / height < 0
|
|
74
|
+
|
|
75
|
+
editor.set({ x: worldX, y: worldY, rotation, skewX, skewY })
|
|
76
|
+
targetRect.set({ x, y, width: box.width / scaleX, height: box.height / scaleY, scaleX, scaleY, visible: true })
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
const points: IPointData[] = [ // topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
|
|
80
|
+
{ x, y },
|
|
81
|
+
{ x: x + width / 2, y },
|
|
82
|
+
{ x: x + width, y },
|
|
83
|
+
{ x: x + width, y: y + height / 2 },
|
|
84
|
+
{ x: x + width, y: y + height },
|
|
85
|
+
{ x: x + width / 2, y: y + height },
|
|
86
|
+
{ x, y: y + height },
|
|
87
|
+
{ x, y: y + height / 2 }
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
const rectPoints: number[] = []
|
|
91
|
+
let point: IPointData, style: IUIInputData, rotateP: IUI, resizeP: IUI, resizeL: IUI
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < 8; i++) {
|
|
94
|
+
point = points[i]
|
|
95
|
+
style = pointStyles[i % pointStyles.length]
|
|
96
|
+
|
|
97
|
+
resizeP = resizePoints[i]
|
|
98
|
+
resizeL = resizeLines[Math.floor(i / 2)]
|
|
99
|
+
rotateP = rotatePoints[i]
|
|
100
|
+
|
|
101
|
+
resizeP.set(style)
|
|
102
|
+
resizeP.x = rotateP.x = resizeL.x = point.x
|
|
103
|
+
resizeP.y = rotateP.y = resizeL.y = point.y
|
|
104
|
+
|
|
105
|
+
resizeP.visible = resizeL.visible = resizeable || rotateable
|
|
106
|
+
rotateP.visible = rotateable && resizeable
|
|
107
|
+
|
|
108
|
+
if (i % 2) { // top, right, bottom, left
|
|
109
|
+
if (((i + 1) / 2) % 2) { // top, bottom
|
|
110
|
+
resizeL.width = Math.abs(width)
|
|
111
|
+
rotateP.width = Math.max(10, Math.abs(width) - 30) // skew
|
|
112
|
+
} else {
|
|
113
|
+
resizeL.height = Math.abs(height)
|
|
114
|
+
rotateP.height = Math.max(10, Math.abs(height) - 30) // skew
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
resizeP.rotation = 90
|
|
118
|
+
resizeP.visible = type === 'mobile'
|
|
119
|
+
|
|
120
|
+
} else {
|
|
121
|
+
rectPoints.push(point.x, point.y)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
style = config.rotatePoint || style
|
|
128
|
+
|
|
129
|
+
circle.set(style)
|
|
130
|
+
circle.x = x + width / 2
|
|
131
|
+
if (!style.y) circle.y = y - (10 + (resizeP.height + circle.height) / 2) * (this.getMirrorData(editor).y ? -1 : 1)
|
|
132
|
+
circle.visible = rotateable && type === 'mobile'
|
|
133
|
+
|
|
134
|
+
rect.set(config.rect || { stroke })
|
|
135
|
+
rect.points = rectPoints
|
|
136
|
+
rect.visible = true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
}
|