@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,16 @@
|
|
|
1
|
+
import { IFunction, ILeaf, IObject } from '@leafer-ui/interface'
|
|
2
|
+
import { defineKey } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function targetAttr(fn: IFunction) {
|
|
6
|
+
return (target: ILeaf, key: string) => {
|
|
7
|
+
const privateKey = '_' + key
|
|
8
|
+
defineKey(target, key, {
|
|
9
|
+
get() { return (this as IObject)[privateKey] },
|
|
10
|
+
set(value: unknown) {
|
|
11
|
+
const old = (this as IObject)[privateKey]
|
|
12
|
+
if (old !== value) (this as IObject)[privateKey] = value, fn(this, old)
|
|
13
|
+
}
|
|
14
|
+
} as ThisType<ILeaf>)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { IRect, IEventListenerId, IBoundsData, IPointData, IKeyEvent, IGroup, IBox, IBoxInputData, IAlign } from '@leafer-ui/interface'
|
|
2
|
+
import { Group, Box, AroundHelper, Direction9 } from '@leafer-ui/draw'
|
|
3
|
+
import { DragEvent, PointerEvent, RotateEvent, ZoomEvent } from '@leafer-ui/core'
|
|
4
|
+
|
|
5
|
+
import { IEditBox, IEditor, IEditPoint, IEditPointType } from '@leafer-in/interface'
|
|
6
|
+
|
|
7
|
+
import { updateCursor, updateMoveCursor } from '../editor/cursor'
|
|
8
|
+
import { EditorEvent } from '../event/EditorEvent'
|
|
9
|
+
import { EditPoint } from './EditPoint'
|
|
10
|
+
import { EditDataHelper } from '../helper/EditDataHelper'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const fourDirection = ['top', 'right', 'bottom', 'left']
|
|
14
|
+
|
|
15
|
+
export class EditBox extends Group implements IEditBox {
|
|
16
|
+
|
|
17
|
+
public editor: IEditor
|
|
18
|
+
public dragging: boolean
|
|
19
|
+
public moving: boolean
|
|
20
|
+
|
|
21
|
+
public view: IGroup = new Group() // 放置默认编辑工具控制点
|
|
22
|
+
|
|
23
|
+
public rect: IBox = new Box({ name: 'rect', hitFill: 'all', hitStroke: 'none', strokeAlign: 'center', hitRadius: 5 }) // target rect
|
|
24
|
+
public circle: IEditPoint = new EditPoint({ name: 'circle', strokeAlign: 'center', around: 'center', cursor: 'crosshair', hitRadius: 5 }) // rotate point
|
|
25
|
+
|
|
26
|
+
public buttons: IGroup = new Group({ around: 'center', hitSelf: false })
|
|
27
|
+
|
|
28
|
+
public resizePoints: IEditPoint[] = [] // topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
|
|
29
|
+
public rotatePoints: IEditPoint[] = [] // topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
|
|
30
|
+
public resizeLines: IEditPoint[] = [] // top, right, bottom, left
|
|
31
|
+
|
|
32
|
+
// fliped
|
|
33
|
+
public get flipped(): boolean { return this.flippedX || this.flippedY }
|
|
34
|
+
public get flippedX(): boolean { return this.scaleX < 0 }
|
|
35
|
+
public get flippedY(): boolean { return this.scaleY < 0 }
|
|
36
|
+
public get flippedOne(): boolean { return this.scaleX * this.scaleY < 0 }
|
|
37
|
+
|
|
38
|
+
public enterPoint: IEditPoint
|
|
39
|
+
|
|
40
|
+
protected __eventIds: IEventListenerId[] = []
|
|
41
|
+
|
|
42
|
+
constructor(editor: IEditor) {
|
|
43
|
+
super()
|
|
44
|
+
this.editor = editor
|
|
45
|
+
this.visible = false
|
|
46
|
+
this.create()
|
|
47
|
+
this.rect.syncEventer = editor // rect的事件不会冒泡,需要手动传递给editor
|
|
48
|
+
this.__listenEvents()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public create() {
|
|
52
|
+
let rotatePoint: IEditPoint, resizeLine: IEditPoint, resizePoint: IEditPoint
|
|
53
|
+
const { view, resizePoints, rotatePoints, resizeLines, rect, circle, buttons } = this
|
|
54
|
+
const arounds: IAlign[] = ['bottom-right', 'bottom', 'bottom-left', 'left', 'top-left', 'top', 'top-right', 'right']
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < 8; i++) {
|
|
57
|
+
rotatePoint = new EditPoint({ name: 'rotate-point', around: arounds[i], width: 15, height: 15, hitFill: "all" })
|
|
58
|
+
rotatePoints.push(rotatePoint)
|
|
59
|
+
this.listenPointEvents(rotatePoint, 'rotate', i)
|
|
60
|
+
|
|
61
|
+
if (i % 2) {
|
|
62
|
+
resizeLine = new EditPoint({ name: 'resize-line', around: 'center', width: 10, height: 10, hitFill: "all" })
|
|
63
|
+
resizeLines.push(resizeLine)
|
|
64
|
+
this.listenPointEvents(resizeLine, 'resize', i)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
resizePoint = new EditPoint({ name: 'resize-point', hitRadius: 5 })
|
|
68
|
+
resizePoints.push(resizePoint)
|
|
69
|
+
this.listenPointEvents(resizePoint, 'resize', i)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
buttons.add(circle)
|
|
73
|
+
this.listenPointEvents(circle, 'rotate', 2)
|
|
74
|
+
|
|
75
|
+
view.addMany(...rotatePoints, rect, buttons, ...resizeLines, ...resizePoints)
|
|
76
|
+
this.add(view)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public load(): void {
|
|
80
|
+
const { mergeConfig, element, single } = this.editor
|
|
81
|
+
const { rect, circle, resizePoints } = this
|
|
82
|
+
const { stroke, strokeWidth, moveable } = mergeConfig
|
|
83
|
+
|
|
84
|
+
const pointsStyle = this.getPointsStyle()
|
|
85
|
+
const middlePointsStyle = this.getMiddlePointsStyle()
|
|
86
|
+
|
|
87
|
+
let resizeP: IRect
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < 8; i++) {
|
|
90
|
+
resizeP = resizePoints[i]
|
|
91
|
+
resizeP.set(this.getPointStyle((i % 2) ? middlePointsStyle[((i - 1) / 2) % middlePointsStyle.length] : pointsStyle[(i / 2) % pointsStyle.length]))
|
|
92
|
+
if (!(i % 2)) resizeP.rotation = (i / 2) * 90
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// rotate
|
|
96
|
+
circle.set(this.getPointStyle(mergeConfig.rotatePoint || pointsStyle[0]))
|
|
97
|
+
|
|
98
|
+
// rect
|
|
99
|
+
rect.set({ stroke, strokeWidth, ...(mergeConfig.rect || {}) })
|
|
100
|
+
rect.hittable = !single && moveable
|
|
101
|
+
|
|
102
|
+
// 编辑框作为底部虚拟元素, 在 onSelect 方法移除
|
|
103
|
+
element.syncEventer = (single && moveable) ? rect : null
|
|
104
|
+
this.app.interaction.bottomList = (single && moveable) ? [{ target: rect, proxy: element }] : null
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public update(bounds: IBoundsData): void {
|
|
109
|
+
this.visible = !this.editor.element.locked
|
|
110
|
+
|
|
111
|
+
if (this.view.worldOpacity) {
|
|
112
|
+
const { mergeConfig } = this.editor
|
|
113
|
+
const { width, height } = bounds
|
|
114
|
+
const { rect, circle, resizePoints, rotatePoints, resizeLines } = this
|
|
115
|
+
const { middlePoint, resizeable, rotateable, hideOnSmall } = mergeConfig
|
|
116
|
+
|
|
117
|
+
const smallSize = typeof hideOnSmall === 'number' ? hideOnSmall : 10
|
|
118
|
+
const showPoints = !(hideOnSmall && width < smallSize && height < smallSize)
|
|
119
|
+
|
|
120
|
+
let point = {} as IPointData, rotateP: IRect, resizeP: IRect, resizeL: IRect
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < 8; i++) {
|
|
123
|
+
|
|
124
|
+
AroundHelper.toPoint(AroundHelper.directionData[i], bounds, point)
|
|
125
|
+
resizeP = resizePoints[i]
|
|
126
|
+
rotateP = rotatePoints[i]
|
|
127
|
+
resizeL = resizeLines[Math.floor(i / 2)]
|
|
128
|
+
resizeP.set(point)
|
|
129
|
+
rotateP.set(point)
|
|
130
|
+
resizeL.set(point)
|
|
131
|
+
|
|
132
|
+
// visible
|
|
133
|
+
resizeP.visible = resizeL.visible = showPoints && !!(resizeable || rotateable)
|
|
134
|
+
rotateP.visible = showPoints && rotateable && resizeable && !mergeConfig.rotatePoint
|
|
135
|
+
|
|
136
|
+
if (i % 2) { // top, right, bottom, left
|
|
137
|
+
|
|
138
|
+
resizeP.visible = rotateP.visible = showPoints && !!middlePoint
|
|
139
|
+
|
|
140
|
+
if (((i + 1) / 2) % 2) { // top, bottom
|
|
141
|
+
resizeL.width = width
|
|
142
|
+
if (resizeP.width > width - 30) resizeP.visible = false
|
|
143
|
+
} else {
|
|
144
|
+
resizeL.height = height
|
|
145
|
+
resizeP.rotation = 90
|
|
146
|
+
if (resizeP.width > height - 30) resizeP.visible = false
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// rotate
|
|
153
|
+
circle.visible = showPoints && rotateable && !!mergeConfig.rotatePoint
|
|
154
|
+
|
|
155
|
+
// rect
|
|
156
|
+
if (rect.path) rect.path = null // line可能会变成path优先模式
|
|
157
|
+
rect.set({ ...bounds, visible: true })
|
|
158
|
+
|
|
159
|
+
// buttons
|
|
160
|
+
const buttonVisible = showPoints && (circle.visible || this.buttons.children.length > 1)
|
|
161
|
+
this.buttons.visible = buttonVisible
|
|
162
|
+
if (buttonVisible) this.layoutButtons()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
protected layoutButtons(): void {
|
|
167
|
+
const { buttons, resizePoints } = this
|
|
168
|
+
const { buttonsDirection, buttonsFixed, buttonsMargin, middlePoint } = this.editor.mergeConfig
|
|
169
|
+
|
|
170
|
+
const { flippedX, flippedY } = this
|
|
171
|
+
let index = fourDirection.indexOf(buttonsDirection)
|
|
172
|
+
if ((index % 2 && flippedX) || ((index + 1) % 2 && flippedY)) {
|
|
173
|
+
if (buttonsFixed) index = (index + 2) % 4 // flip x / y
|
|
174
|
+
}
|
|
175
|
+
const direction = buttonsFixed ? EditDataHelper.getRotateDirection(index, this.flippedOne ? this.rotation : -this.rotation, 4) : index
|
|
176
|
+
|
|
177
|
+
const point = resizePoints[direction * 2 + 1] // 4 map 8 direction
|
|
178
|
+
const useX = direction % 2 // left / right
|
|
179
|
+
const sign = (!direction || direction === 3) ? -1 : 1 // top / left = -1
|
|
180
|
+
|
|
181
|
+
const useWidth = index % 2 // left / right origin direction
|
|
182
|
+
const margin = (buttonsMargin + (useWidth ? ((middlePoint ? point.width : 0) + buttons.boxBounds.width) : ((middlePoint ? point.height : 0) + buttons.boxBounds.height)) / 2) * sign
|
|
183
|
+
|
|
184
|
+
if (useX) {
|
|
185
|
+
buttons.x = point.x + margin
|
|
186
|
+
buttons.y = point.y
|
|
187
|
+
} else {
|
|
188
|
+
buttons.x = point.x
|
|
189
|
+
buttons.y = point.y + margin
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (buttonsFixed) {
|
|
193
|
+
buttons.rotation = (direction - index) * 90
|
|
194
|
+
buttons.scaleX = flippedX ? -1 : 1
|
|
195
|
+
buttons.scaleY = flippedY ? -1 : 1
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public unload(): void {
|
|
201
|
+
this.visible = false
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
public getPointStyle(userStyle?: IBoxInputData): IBoxInputData {
|
|
206
|
+
const { stroke, strokeWidth, pointFill, pointSize, pointRadius } = this.editor.mergeConfig
|
|
207
|
+
const defaultStyle = { fill: pointFill, stroke, strokeWidth, around: 'center', strokeAlign: 'center', width: pointSize, height: pointSize, cornerRadius: pointRadius } as IBoxInputData
|
|
208
|
+
return userStyle ? Object.assign(defaultStyle, userStyle) : defaultStyle
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public getPointsStyle(): IBoxInputData[] {
|
|
212
|
+
const { point } = this.editor.mergeConfig
|
|
213
|
+
return point instanceof Array ? point : [point]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public getMiddlePointsStyle(): IBoxInputData[] {
|
|
217
|
+
const { middlePoint } = this.editor.mergeConfig
|
|
218
|
+
return middlePoint instanceof Array ? middlePoint : (middlePoint ? [middlePoint] : this.getPointsStyle())
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
protected onSelect(e: EditorEvent): void {
|
|
222
|
+
if (e.oldList.length === 1) {
|
|
223
|
+
e.oldList[0].syncEventer = null
|
|
224
|
+
if (this.app) this.app.interaction.bottomList = null
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// drag
|
|
229
|
+
|
|
230
|
+
protected onDragStart(e: DragEvent): void {
|
|
231
|
+
this.dragging = true
|
|
232
|
+
if (e.current.name === 'rect') {
|
|
233
|
+
const { editor } = this
|
|
234
|
+
this.moving = true
|
|
235
|
+
editor.dragStartPoint = { x: editor.element.x, y: editor.element.y }
|
|
236
|
+
editor.opacity = editor.mergeConfig.hideOnMove ? 0 : 1 // move
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
protected onDragEnd(e: DragEvent): void {
|
|
241
|
+
this.dragging = false
|
|
242
|
+
this.moving = false
|
|
243
|
+
if (e.current.name === 'rect') this.editor.opacity = 1 // move
|
|
244
|
+
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
protected onDrag(e: DragEvent): void {
|
|
248
|
+
const { editor } = this
|
|
249
|
+
const point = this.enterPoint = e.current as IEditPoint
|
|
250
|
+
if (point.pointType === 'rotate' || e.metaKey || e.ctrlKey || !editor.mergeConfig.resizeable) {
|
|
251
|
+
if (editor.mergeConfig.rotateable) editor.onRotate(e)
|
|
252
|
+
} else {
|
|
253
|
+
editor.onScale(e)
|
|
254
|
+
}
|
|
255
|
+
updateCursor(editor, e)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public onArrow(e: IKeyEvent): void {
|
|
259
|
+
if (this.editor.editing && this.editor.mergeConfig.keyEvent) {
|
|
260
|
+
const move = { x: 0, y: 0 }
|
|
261
|
+
const distance = e.shiftKey ? 10 : 1
|
|
262
|
+
switch (e.code) {
|
|
263
|
+
case 'ArrowDown':
|
|
264
|
+
move.y = distance
|
|
265
|
+
break
|
|
266
|
+
case 'ArrowUp':
|
|
267
|
+
move.y = -distance
|
|
268
|
+
break
|
|
269
|
+
case 'ArrowLeft':
|
|
270
|
+
move.x = -distance
|
|
271
|
+
break
|
|
272
|
+
case 'ArrowRight':
|
|
273
|
+
move.x = distance
|
|
274
|
+
}
|
|
275
|
+
this.editor.move(move)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
protected onDoubleTap(e: PointerEvent): void {
|
|
281
|
+
if (this.editor.mergeConfig.openInner === 'double') this.openInner(e)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
protected onLongPress(e: PointerEvent): void {
|
|
285
|
+
if (this.editor.mergeConfig.openInner === 'long') this.openInner(e)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
protected openInner(e: PointerEvent): void {
|
|
289
|
+
const { editor } = this
|
|
290
|
+
if (editor.single) {
|
|
291
|
+
const { element } = editor
|
|
292
|
+
if (element.isBranch) {
|
|
293
|
+
editor.openGroup(element as IGroup)
|
|
294
|
+
editor.target = editor.selector.findDeepOne(e)
|
|
295
|
+
} else {
|
|
296
|
+
editor.openInnerEditor()
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
public listenPointEvents(point: IEditPoint, type: IEditPointType, direction: Direction9): void {
|
|
303
|
+
const { editor } = this
|
|
304
|
+
point.direction = direction
|
|
305
|
+
point.pointType = type
|
|
306
|
+
point.on_(DragEvent.START, this.onDragStart, this)
|
|
307
|
+
point.on_(DragEvent.DRAG, this.onDrag, this)
|
|
308
|
+
point.on_(DragEvent.END, this.onDragEnd, this)
|
|
309
|
+
point.on_(PointerEvent.LEAVE, () => this.enterPoint = null)
|
|
310
|
+
if (point.name !== 'circle') point.on_(PointerEvent.ENTER, (e) => { this.enterPoint = point, updateCursor(editor, e) })
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
protected __listenEvents(): void {
|
|
314
|
+
const { rect, editor } = this
|
|
315
|
+
this.__eventIds = [
|
|
316
|
+
editor.on_(EditorEvent.SELECT, this.onSelect, this),
|
|
317
|
+
|
|
318
|
+
rect.on_(DragEvent.START, this.onDragStart, this),
|
|
319
|
+
rect.on_(DragEvent.DRAG, editor.onMove, editor),
|
|
320
|
+
rect.on_(DragEvent.END, this.onDragEnd, this),
|
|
321
|
+
|
|
322
|
+
rect.on_(ZoomEvent.BEFORE_ZOOM, editor.onScale, editor, true),
|
|
323
|
+
rect.on_(RotateEvent.BEFORE_ROTATE, editor.onRotate, editor, true),
|
|
324
|
+
|
|
325
|
+
rect.on_(PointerEvent.ENTER, () => updateMoveCursor(editor)),
|
|
326
|
+
rect.on_(PointerEvent.DOUBLE_TAP, this.onDoubleTap, this),
|
|
327
|
+
rect.on_(PointerEvent.LONG_PRESS, this.onLongPress, this)
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
protected __removeListenEvents(): void {
|
|
332
|
+
this.off_(this.__eventIds)
|
|
333
|
+
this.__eventIds.length = 0
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
public destroy(): void {
|
|
337
|
+
this.editor = null
|
|
338
|
+
this.__removeListenEvents()
|
|
339
|
+
super.destroy()
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ILeaferCanvas, IRenderOptions } from '@leafer-ui/interface'
|
|
2
|
+
import { UI } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
import { IEditor } from '@leafer-in/interface'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class EditMask extends UI {
|
|
8
|
+
|
|
9
|
+
public editor: IEditor
|
|
10
|
+
|
|
11
|
+
constructor(editor: IEditor) {
|
|
12
|
+
super()
|
|
13
|
+
this.editor = editor
|
|
14
|
+
this.hittable = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public __draw(canvas: ILeaferCanvas, options: IRenderOptions): void {
|
|
18
|
+
const { editor } = this
|
|
19
|
+
const { mask } = editor.mergeConfig
|
|
20
|
+
|
|
21
|
+
if (mask && editor.list.length) {
|
|
22
|
+
const { rect } = editor.editBox
|
|
23
|
+
const { width, height } = rect.__
|
|
24
|
+
|
|
25
|
+
canvas.resetTransform()
|
|
26
|
+
canvas.fillWorld(canvas.bounds, mask)
|
|
27
|
+
canvas.setWorld(rect.__world, options.matrix)
|
|
28
|
+
canvas.clearRect(0, 0, width, height)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public destroy(): void {
|
|
33
|
+
this.editor = null
|
|
34
|
+
super.destroy()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { IBounds, ILeaf, ILeafList, IUI, IEventListenerId, IPointerEvent } from '@leafer-ui/interface'
|
|
2
|
+
import { Bounds, LeafList, Group } from '@leafer-ui/draw'
|
|
3
|
+
import { PointerEvent, DragEvent, MoveEvent, ZoomEvent } from '@leafer-ui/core'
|
|
4
|
+
|
|
5
|
+
import { IEditSelect, IEditor, ISelectArea, IStroker } from '@leafer-in/interface'
|
|
6
|
+
|
|
7
|
+
import { Stroker } from './Stroker'
|
|
8
|
+
import { SelectArea } from './SelectArea'
|
|
9
|
+
import { EditSelectHelper } from '../helper/EditSelectHelper'
|
|
10
|
+
import { EditorEvent } from '../event/EditorEvent'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const { findOne } = EditSelectHelper
|
|
14
|
+
|
|
15
|
+
export class EditSelect extends Group implements IEditSelect {
|
|
16
|
+
|
|
17
|
+
public editor: IEditor
|
|
18
|
+
|
|
19
|
+
public get dragging(): boolean { return !!this.originList }
|
|
20
|
+
public get running(): boolean { const { editor } = this; return this.hittable && editor.visible && editor.hittable && editor.mergeConfig.selector }
|
|
21
|
+
public get isMoveMode(): boolean { return this.app && this.app.interaction.moveMode }
|
|
22
|
+
|
|
23
|
+
public hoverStroker: IStroker = new Stroker()
|
|
24
|
+
public targetStroker: IStroker = new Stroker()
|
|
25
|
+
|
|
26
|
+
public bounds: IBounds = new Bounds()
|
|
27
|
+
public selectArea: ISelectArea = new SelectArea()
|
|
28
|
+
|
|
29
|
+
protected originList: ILeafList
|
|
30
|
+
protected needRemoveItem: IUI
|
|
31
|
+
|
|
32
|
+
protected __eventIds: IEventListenerId[] = []
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
constructor(editor: IEditor) {
|
|
36
|
+
super()
|
|
37
|
+
this.editor = editor
|
|
38
|
+
this.addMany(this.targetStroker, this.hoverStroker, this.selectArea)
|
|
39
|
+
this.__listenEvents()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// hover / select
|
|
43
|
+
|
|
44
|
+
protected onHover(): void {
|
|
45
|
+
const { editor } = this
|
|
46
|
+
if (this.running && !this.dragging && !editor.dragging) {
|
|
47
|
+
const { stroke, strokeWidth, hover, hoverStyle } = editor.mergeConfig
|
|
48
|
+
this.hoverStroker.setTarget(hover ? this.editor.hoverTarget : null, { stroke, strokeWidth, ...(hoverStyle || {}) })
|
|
49
|
+
} else {
|
|
50
|
+
this.hoverStroker.target = null
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected onSelect(): void {
|
|
55
|
+
if (this.running) {
|
|
56
|
+
const { mergeConfig: config, list } = this.editor
|
|
57
|
+
const { stroke, strokeWidth } = config
|
|
58
|
+
this.targetStroker.setTarget(list, { stroke, strokeWidth: Math.max(1, strokeWidth / 2) })
|
|
59
|
+
this.hoverStroker.target = null
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public update(): void {
|
|
64
|
+
if (this.targetStroker.target) this.targetStroker.forceUpdate()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// move / down
|
|
68
|
+
|
|
69
|
+
protected onPointerMove(e: PointerEvent): void {
|
|
70
|
+
const { app, editor } = this
|
|
71
|
+
if (this.running && !this.isMoveMode && app.config.pointer.hover && !app.interaction.dragging) {
|
|
72
|
+
const find = this.findUI(e)
|
|
73
|
+
editor.hoverTarget = editor.hasItem(find) ? null : find
|
|
74
|
+
} if (this.isMoveMode) {
|
|
75
|
+
editor.hoverTarget = null // move.dragEmpty
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected onBeforeDown(e: PointerEvent): void {
|
|
80
|
+
const { select } = this.editor.mergeConfig
|
|
81
|
+
if (select === 'press') this.checkAndSelect(e)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected onTap(e: PointerEvent): void {
|
|
85
|
+
const { editor } = this
|
|
86
|
+
const { select } = editor.mergeConfig
|
|
87
|
+
if (select === 'tap') this.checkAndSelect(e)
|
|
88
|
+
|
|
89
|
+
if (this.needRemoveItem) {
|
|
90
|
+
editor.removeItem(this.needRemoveItem)
|
|
91
|
+
} else if (this.isMoveMode) {
|
|
92
|
+
editor.target = null // move.dragEmpty
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
protected checkAndSelect(e: PointerEvent): void { // pointer.down or tap
|
|
98
|
+
this.needRemoveItem = null
|
|
99
|
+
|
|
100
|
+
if (this.allowSelect(e)) {
|
|
101
|
+
const { editor } = this
|
|
102
|
+
const find = this.findUI(e)
|
|
103
|
+
|
|
104
|
+
if (find) {
|
|
105
|
+
if (this.isMultipleSelect(e)) {
|
|
106
|
+
if (editor.hasItem(find)) this.needRemoveItem = find // 等待tap事件再实际移除
|
|
107
|
+
else editor.addItem(find)
|
|
108
|
+
} else {
|
|
109
|
+
editor.target = find
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
} else if (this.allow(e.target)) {
|
|
113
|
+
|
|
114
|
+
if (!e.shiftKey) editor.target = null
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// drag
|
|
121
|
+
|
|
122
|
+
protected onDragStart(e: DragEvent): void {
|
|
123
|
+
if (this.allowDrag(e)) {
|
|
124
|
+
const { editor } = this
|
|
125
|
+
const { stroke, area } = editor.mergeConfig
|
|
126
|
+
const { x, y } = e.getInner(this)
|
|
127
|
+
|
|
128
|
+
this.bounds.set(x, y)
|
|
129
|
+
|
|
130
|
+
this.selectArea.setStyle({ visible: true, stroke, x, y }, area)
|
|
131
|
+
this.selectArea.setBounds(this.bounds.get())
|
|
132
|
+
|
|
133
|
+
this.originList = editor.leafList.clone()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
protected onDrag(e: DragEvent): void {
|
|
138
|
+
if (this.editor.dragging) {
|
|
139
|
+
this.onDragEnd()
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (this.dragging) {
|
|
144
|
+
const { editor } = this
|
|
145
|
+
const total = e.getInnerTotal(this)
|
|
146
|
+
|
|
147
|
+
const dragBounds = this.bounds.clone().unsign()
|
|
148
|
+
const list = new LeafList(editor.app.find(EditSelectHelper.findBounds, dragBounds))
|
|
149
|
+
|
|
150
|
+
this.bounds.width = total.x
|
|
151
|
+
this.bounds.height = total.y
|
|
152
|
+
|
|
153
|
+
this.selectArea.setBounds(dragBounds.get())
|
|
154
|
+
|
|
155
|
+
if (list.length) {
|
|
156
|
+
|
|
157
|
+
const selectList: ILeaf[] = []
|
|
158
|
+
|
|
159
|
+
this.originList.forEach(item => { if (!list.has(item)) selectList.push(item) })
|
|
160
|
+
list.forEach(item => { if (!this.originList.has(item)) selectList.push(item) })
|
|
161
|
+
|
|
162
|
+
if (selectList.length !== editor.list.length || editor.list.some((child, index) => child !== selectList[index])) {
|
|
163
|
+
editor.target = selectList as IUI[]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
} else {
|
|
167
|
+
|
|
168
|
+
editor.target = this.originList.list as IUI[]
|
|
169
|
+
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
protected onDragEnd(): void {
|
|
175
|
+
if (this.dragging) this.originList = null, this.selectArea.visible = false
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
protected onAutoMove(e: MoveEvent): void {
|
|
179
|
+
if (this.dragging) {
|
|
180
|
+
const { x, y } = e.getLocalMove(this)
|
|
181
|
+
this.bounds.x += x
|
|
182
|
+
this.bounds.y += y
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// helper
|
|
187
|
+
|
|
188
|
+
protected allow(target: ILeaf): boolean {
|
|
189
|
+
return target.leafer !== this.editor.leafer
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
protected allowDrag(e: DragEvent) {
|
|
193
|
+
if (this.running && this.editor.mergeConfig.boxSelect && !e.target.draggable) {
|
|
194
|
+
return (!this.editor.editing && this.allow(e.target)) || (e.shiftKey && !findOne(e.path))
|
|
195
|
+
} else {
|
|
196
|
+
return false
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected allowSelect(e: IPointerEvent): boolean {
|
|
201
|
+
return this.running && !this.isMoveMode && !e.middle
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public findDeepOne(e: PointerEvent): IUI {
|
|
205
|
+
const options = { exclude: new LeafList(this.editor.editBox.rect) }
|
|
206
|
+
return findOne(e.target.leafer.interaction.findPath(e, options)) as IUI
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public findUI(e: PointerEvent): IUI {
|
|
210
|
+
return this.isMultipleSelect(e) ? this.findDeepOne(e) : findOne(e.path)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public isMultipleSelect(e: IPointerEvent): boolean {
|
|
214
|
+
return e.shiftKey || this.editor.mergeConfig.continuousSelect
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
protected __listenEvents(): void {
|
|
218
|
+
const { editor } = this
|
|
219
|
+
editor.waitLeafer(() => {
|
|
220
|
+
|
|
221
|
+
const { app } = editor
|
|
222
|
+
app.selector.proxy = editor
|
|
223
|
+
|
|
224
|
+
this.__eventIds = [
|
|
225
|
+
editor.on_(EditorEvent.HOVER, this.onHover, this),
|
|
226
|
+
editor.on_(EditorEvent.SELECT, this.onSelect, this),
|
|
227
|
+
|
|
228
|
+
app.on_(PointerEvent.MOVE, this.onPointerMove, this),
|
|
229
|
+
app.on_(PointerEvent.BEFORE_DOWN, this.onBeforeDown, this),
|
|
230
|
+
app.on_(PointerEvent.TAP, this.onTap, this),
|
|
231
|
+
|
|
232
|
+
app.on_(DragEvent.START, this.onDragStart, this),
|
|
233
|
+
app.on_(DragEvent.DRAG, this.onDrag, this),
|
|
234
|
+
app.on_(DragEvent.END, this.onDragEnd, this),
|
|
235
|
+
|
|
236
|
+
app.on_(MoveEvent.MOVE, this.onAutoMove, this),
|
|
237
|
+
app.on_([ZoomEvent.ZOOM, MoveEvent.MOVE], () => { this.editor.hoverTarget = null }),
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
protected __removeListenEvents(): void {
|
|
244
|
+
if (this.__eventIds) {
|
|
245
|
+
this.off_(this.__eventIds)
|
|
246
|
+
this.__eventIds.length = 0
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public destroy(): void {
|
|
251
|
+
this.editor = this.originList = this.needRemoveItem = null
|
|
252
|
+
this.__removeListenEvents()
|
|
253
|
+
super.destroy()
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { IBoundsData, IGroupInputData, IRect, IRectInputData } from '@leafer-ui/interface'
|
|
2
|
+
import { Group, Rect } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
import { ISelectArea } from '@leafer-in/interface'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class SelectArea extends Group implements ISelectArea {
|
|
8
|
+
|
|
9
|
+
protected strokeArea: IRect = new Rect({ strokeAlign: 'center' })
|
|
10
|
+
protected fillArea: IRect = new Rect()
|
|
11
|
+
|
|
12
|
+
constructor(data?: IGroupInputData) {
|
|
13
|
+
super(data)
|
|
14
|
+
this.visible = this.hittable = false
|
|
15
|
+
this.addMany(this.fillArea, this.strokeArea)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public setStyle(style: IRectInputData, userStyle?: IRectInputData): void {
|
|
19
|
+
const { visible, stroke, strokeWidth } = style
|
|
20
|
+
this.visible = visible
|
|
21
|
+
this.strokeArea.reset({ stroke, strokeWidth, ...(userStyle || {}) })
|
|
22
|
+
this.fillArea.reset({ visible: userStyle ? false : true, fill: stroke, opacity: 0.2 })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public setBounds(bounds: IBoundsData): void {
|
|
26
|
+
this.strokeArea.set(bounds)
|
|
27
|
+
this.fillArea.set(bounds)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|