@leafer-in/editor 1.0.0-rc.2 → 1.0.0-rc.20
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.esm.js +1409 -389
- package/dist/editor.esm.min.js +1 -1
- package/dist/editor.js +1422 -392
- package/dist/editor.min.js +1 -1
- package/package.json +5 -6
- package/src/Editor.ts +239 -149
- package/src/config.ts +37 -0
- package/src/decorator/data.ts +16 -0
- package/src/display/EditBox.ts +275 -0
- package/src/display/EditPoint.ts +9 -0
- package/src/display/EditSelect.ts +249 -0
- package/src/display/SelectArea.ts +30 -0
- package/src/display/Stroker.ts +95 -0
- package/src/editor/cursor.ts +45 -0
- package/src/editor/simulate.ts +14 -0
- package/src/editor/target.ts +37 -0
- package/src/event/EditorEvent.ts +33 -0
- package/src/event/EditorMoveEvent.ts +17 -0
- package/src/event/EditorRotateEvent.ts +4 -10
- package/src/event/EditorScaleEvent.ts +27 -0
- package/src/event/EditorSkewEvent.ts +18 -0
- package/src/helper/EditDataHelper.ts +182 -0
- package/src/helper/EditSelectHelper.ts +34 -0
- package/src/helper/EditorHelper.ts +73 -0
- package/src/index.ts +28 -3
- package/src/svg.ts +54 -0
- package/src/tool/EditTool.ts +75 -0
- package/src/tool/{LineTool.ts → LineEditTool.ts} +19 -25
- package/src/tool/index.ts +21 -0
- package/types/index.d.ts +199 -43
- package/src/cursor.ts +0 -57
- package/src/event/EditorResizeEvent.ts +0 -34
- package/src/resize.ts +0 -87
- package/src/tool/RectTool.ts +0 -139
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { IRect, IAround, IEventListenerId, IBoundsData, IRectInputData, IPointData, IKeyEvent, IGroup, IBox } from '@leafer-ui/interface'
|
|
2
|
+
import { Group, DragEvent, PointerEvent, Box, AroundHelper } from '@leafer-ui/core'
|
|
3
|
+
|
|
4
|
+
import { IEditBox, IEditor, IDirection8, IEditPoint, IEditPointType } from '@leafer-in/interface'
|
|
5
|
+
|
|
6
|
+
import { updateCursor, updateMoveCursor } from '../editor/cursor'
|
|
7
|
+
import { EditorEvent } from '../event/EditorEvent'
|
|
8
|
+
import { EditPoint } from './EditPoint'
|
|
9
|
+
import { EditDataHelper } from '../helper/EditDataHelper'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const fourDirection = ['top', 'right', 'bottom', 'left']
|
|
13
|
+
|
|
14
|
+
export class EditBox extends Group implements IEditBox {
|
|
15
|
+
|
|
16
|
+
public editor: IEditor
|
|
17
|
+
public dragging: boolean
|
|
18
|
+
public moving: boolean
|
|
19
|
+
|
|
20
|
+
public rect: IBox = new Box({ name: 'rect', hitFill: 'all', hitStroke: 'none', strokeAlign: 'center', hitRadius: 5 }) // target rect
|
|
21
|
+
public circle: IEditPoint = new EditPoint({ name: 'circle', strokeAlign: 'center', around: 'center', cursor: 'crosshair', hitRadius: 5 }) // rotate point
|
|
22
|
+
|
|
23
|
+
public buttons: IGroup = new Group({ around: 'center', hitSelf: false })
|
|
24
|
+
|
|
25
|
+
public resizePoints: IEditPoint[] = [] // topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
|
|
26
|
+
public rotatePoints: IEditPoint[] = [] // topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left
|
|
27
|
+
public resizeLines: IEditPoint[] = [] // top, right, bottom, left
|
|
28
|
+
|
|
29
|
+
// fliped
|
|
30
|
+
public get flipped(): boolean { return this.flippedX || this.flippedY }
|
|
31
|
+
public get flippedX(): boolean { return this.scaleX < 0 }
|
|
32
|
+
public get flippedY(): boolean { return this.scaleY < 0 }
|
|
33
|
+
public get flippedOne(): boolean { return this.scaleX * this.scaleY < 0 }
|
|
34
|
+
|
|
35
|
+
public enterPoint: IEditPoint
|
|
36
|
+
|
|
37
|
+
protected __eventIds: IEventListenerId[] = []
|
|
38
|
+
|
|
39
|
+
constructor(editor: IEditor) {
|
|
40
|
+
super()
|
|
41
|
+
this.editor = editor
|
|
42
|
+
this.visible = false
|
|
43
|
+
this.create()
|
|
44
|
+
this.__listenEvents()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public create() {
|
|
48
|
+
let rotatePoint: IEditPoint, resizeLine: IEditPoint, resizePoint: IEditPoint
|
|
49
|
+
const { resizePoints, rotatePoints, resizeLines, rect, circle, buttons } = this
|
|
50
|
+
const arounds: IAround[] = [{ x: 1, y: 1 }, { x: 0.5, y: 1 }, { x: 0, y: 1 }, { x: 0, y: 0.5 }, { x: 0, y: 0 }, { x: 0.5, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 0.5 }]
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < 8; i++) {
|
|
53
|
+
rotatePoint = new EditPoint({ name: 'rotate-point', around: arounds[i], width: 15, height: 15, hitFill: "all" })
|
|
54
|
+
rotatePoints.push(rotatePoint)
|
|
55
|
+
this.listenPointEvents(rotatePoint, 'rotate', i)
|
|
56
|
+
|
|
57
|
+
if (i % 2) {
|
|
58
|
+
resizeLine = new EditPoint({ name: 'resize-line', around: 'center', width: 10, height: 10, hitFill: "all" })
|
|
59
|
+
resizeLines.push(resizeLine)
|
|
60
|
+
this.listenPointEvents(resizeLine, 'resize', i)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
resizePoint = new EditPoint({ name: 'resize-point', around: 'center', strokeAlign: 'center', hitRadius: 5 })
|
|
64
|
+
resizePoints.push(resizePoint)
|
|
65
|
+
this.listenPointEvents(resizePoint, 'resize', i)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
buttons.add(circle)
|
|
69
|
+
this.listenPointEvents(circle, 'rotate', 2)
|
|
70
|
+
|
|
71
|
+
this.addMany(...rotatePoints, rect, buttons, ...resizeLines, ...resizePoints)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// update
|
|
75
|
+
|
|
76
|
+
public update(bounds: IBoundsData): void {
|
|
77
|
+
const { config, list } = this.editor
|
|
78
|
+
const { width, height } = bounds
|
|
79
|
+
const { rect, circle, resizePoints, rotatePoints, resizeLines } = this
|
|
80
|
+
const { middlePoint, resizeable, rotateable, stroke, strokeWidth, hideOnSmall } = config
|
|
81
|
+
|
|
82
|
+
const pointsStyle = this.getPointsStyle()
|
|
83
|
+
const middlePointsStyle = this.getMiddlePointsStyle()
|
|
84
|
+
const smallSize = typeof hideOnSmall === 'number' ? hideOnSmall : 10
|
|
85
|
+
const showPoints = !(hideOnSmall && width < smallSize && height < smallSize)
|
|
86
|
+
|
|
87
|
+
this.visible = list[0] && !list[0].locked // check locked
|
|
88
|
+
|
|
89
|
+
let point = {} as IPointData, style: IRectInputData, rotateP: IRect, resizeP: IRect, resizeL: IRect
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < 8; i++) {
|
|
92
|
+
|
|
93
|
+
AroundHelper.toPoint(AroundHelper.directionData[i], bounds, point)
|
|
94
|
+
style = this.getPointStyle((i % 2) ? middlePointsStyle[((i - 1) / 2) % middlePointsStyle.length] : pointsStyle[(i / 2) % pointsStyle.length])
|
|
95
|
+
resizeP = resizePoints[i], rotateP = rotatePoints[i], resizeL = resizeLines[Math.floor(i / 2)]
|
|
96
|
+
resizeP.set(style)
|
|
97
|
+
resizeP.set(point), rotateP.set(point), resizeL.set(point)
|
|
98
|
+
|
|
99
|
+
// visible
|
|
100
|
+
resizeP.visible = resizeL.visible = showPoints && (resizeable || rotateable)
|
|
101
|
+
rotateP.visible = showPoints && rotateable && resizeable && !config.rotatePoint
|
|
102
|
+
|
|
103
|
+
if (i % 2) { // top, right, bottom, left
|
|
104
|
+
|
|
105
|
+
resizeP.visible = rotateP.visible = showPoints && !!middlePoint
|
|
106
|
+
|
|
107
|
+
if (((i + 1) / 2) % 2) { // top, bottom
|
|
108
|
+
resizeL.width = width
|
|
109
|
+
if (resizeP.width > width - 30) resizeP.visible = false
|
|
110
|
+
} else {
|
|
111
|
+
resizeL.height = height
|
|
112
|
+
resizeP.rotation = 90
|
|
113
|
+
if (resizeP.width > height - 30) resizeP.visible = false
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
resizeP.rotation = (i / 2) * 90
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// rotate
|
|
122
|
+
circle.visible = showPoints && rotateable && !!config.rotatePoint
|
|
123
|
+
circle.set(this.getPointStyle(config.rotatePoint || pointsStyle[0]))
|
|
124
|
+
|
|
125
|
+
// rect
|
|
126
|
+
rect.set({ stroke, strokeWidth, ...(config.rect || {}) })
|
|
127
|
+
rect.set({ ...bounds, visible: true })
|
|
128
|
+
rect.hittable = config.moveable
|
|
129
|
+
|
|
130
|
+
// buttons
|
|
131
|
+
this.buttons.visible = showPoints
|
|
132
|
+
this.layoutButtons()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected layoutButtons(): void {
|
|
136
|
+
const { buttons, resizePoints } = this
|
|
137
|
+
const { buttonsDirection, buttonsFixed, buttonsMargin, middlePoint } = this.editor.config
|
|
138
|
+
|
|
139
|
+
const { flippedX, flippedY } = this
|
|
140
|
+
let index = fourDirection.indexOf(buttonsDirection)
|
|
141
|
+
if ((index % 2 && flippedX) || ((index + 1) % 2 && flippedY)) {
|
|
142
|
+
if (buttonsFixed) index = (index + 2) % 4 // flip x / y
|
|
143
|
+
}
|
|
144
|
+
const direction = buttonsFixed ? EditDataHelper.getRotateDirection(index, this.flippedOne ? this.rotation : -this.rotation, 4) : index
|
|
145
|
+
|
|
146
|
+
const point = resizePoints[direction * 2 + 1] // 4 map 8 direction
|
|
147
|
+
const useX = direction % 2 // left / right
|
|
148
|
+
const sign = (!direction || direction === 3) ? -1 : 1 // top / left = -1
|
|
149
|
+
|
|
150
|
+
const useWidth = index % 2 // left / right origin direction
|
|
151
|
+
const margin = (buttonsMargin + (useWidth ? ((middlePoint ? point.width : 0) + buttons.boxBounds.width) : ((middlePoint ? point.height : 0) + buttons.boxBounds.height)) / 2) * sign
|
|
152
|
+
|
|
153
|
+
if (useX) {
|
|
154
|
+
buttons.x = point.x + margin
|
|
155
|
+
buttons.y = point.y
|
|
156
|
+
} else {
|
|
157
|
+
buttons.x = point.x
|
|
158
|
+
buttons.y = point.y + margin
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (buttonsFixed) {
|
|
162
|
+
buttons.rotation = (direction - index) * 90
|
|
163
|
+
buttons.scaleX = flippedX ? -1 : 1
|
|
164
|
+
buttons.scaleY = flippedY ? -1 : 1
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public getPointStyle(userStyle?: IRectInputData): IRectInputData {
|
|
170
|
+
const { stroke, strokeWidth, pointFill, pointSize, pointRadius } = this.editor.config
|
|
171
|
+
const defaultStyle = { fill: pointFill, stroke, strokeWidth, width: pointSize, height: pointSize, cornerRadius: pointRadius }
|
|
172
|
+
return userStyle ? Object.assign(defaultStyle, userStyle) : defaultStyle
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public getPointsStyle(): IRectInputData[] {
|
|
176
|
+
const { point } = this.editor.config
|
|
177
|
+
return point instanceof Array ? point : [point]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public getMiddlePointsStyle(): IRectInputData[] {
|
|
181
|
+
const { middlePoint } = this.editor.config
|
|
182
|
+
return middlePoint instanceof Array ? middlePoint : (middlePoint ? [middlePoint] : this.getPointsStyle())
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// drag
|
|
186
|
+
|
|
187
|
+
protected onDragStart(e: DragEvent): void {
|
|
188
|
+
this.dragging = true
|
|
189
|
+
if (e.target.name === 'rect') {
|
|
190
|
+
this.moving = true
|
|
191
|
+
this.editor.opacity = this.editor.config.hideOnMove ? 0 : 1 // move
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
protected onDragEnd(e: DragEvent): void {
|
|
196
|
+
this.dragging = false
|
|
197
|
+
this.moving = false
|
|
198
|
+
if (e.target.name === 'rect') this.editor.opacity = 1 // move
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
protected onDrag(e: DragEvent): void {
|
|
203
|
+
const { editor } = this
|
|
204
|
+
const point = this.enterPoint = e.current as IEditPoint
|
|
205
|
+
if (point.pointType === 'rotate' || e.metaKey || e.ctrlKey || !editor.config.resizeable) {
|
|
206
|
+
if (editor.config.rotateable) editor.onRotate(e)
|
|
207
|
+
} else {
|
|
208
|
+
editor.onScale(e)
|
|
209
|
+
}
|
|
210
|
+
updateCursor(editor, e)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public onArrow(e: IKeyEvent): void {
|
|
214
|
+
if (this.editor.hasTarget && this.editor.config.keyEvent) {
|
|
215
|
+
const move = { x: 0, y: 0 }
|
|
216
|
+
const distance = e.shiftKey ? 10 : 1
|
|
217
|
+
switch (e.code) {
|
|
218
|
+
case 'ArrowDown':
|
|
219
|
+
move.y = distance
|
|
220
|
+
break
|
|
221
|
+
case 'ArrowUp':
|
|
222
|
+
move.y = -distance
|
|
223
|
+
break
|
|
224
|
+
case 'ArrowLeft':
|
|
225
|
+
move.x = -distance
|
|
226
|
+
break
|
|
227
|
+
case 'ArrowRight':
|
|
228
|
+
move.x = distance
|
|
229
|
+
}
|
|
230
|
+
if (move.x || move.y) this.editor.move(move.x, move.y)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
protected onDoubleClick(): void {
|
|
235
|
+
const { editor } = this
|
|
236
|
+
if (editor.single && editor.element.isBranch) {
|
|
237
|
+
//list[0].hitChildren = true
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
public listenPointEvents(point: IEditPoint, type: IEditPointType, direction: IDirection8): void {
|
|
242
|
+
const { editor } = this
|
|
243
|
+
point.direction = direction
|
|
244
|
+
point.pointType = type
|
|
245
|
+
point.on_(DragEvent.START, this.onDragStart, this)
|
|
246
|
+
point.on_(DragEvent.DRAG, this.onDrag, this)
|
|
247
|
+
point.on_(DragEvent.END, this.onDragEnd, this)
|
|
248
|
+
point.on_(PointerEvent.LEAVE, () => this.enterPoint = null)
|
|
249
|
+
if (point.name !== 'circle') point.on_(PointerEvent.ENTER, (e) => { this.enterPoint = point, updateCursor(editor, e) })
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
protected __listenEvents(): void {
|
|
253
|
+
const { rect, editor } = this
|
|
254
|
+
this.__eventIds = [
|
|
255
|
+
editor.on_(EditorEvent.SELECT, () => { this.visible = editor.hasTarget }),
|
|
256
|
+
rect.on_(DragEvent.START, this.onDragStart, this),
|
|
257
|
+
rect.on_(DragEvent.DRAG, editor.onMove, editor),
|
|
258
|
+
rect.on_(DragEvent.END, this.onDragEnd, this),
|
|
259
|
+
rect.on_(PointerEvent.ENTER, () => updateMoveCursor(editor)),
|
|
260
|
+
rect.on_(PointerEvent.DOUBLE_CLICK, this.onDoubleClick, this)
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
protected __removeListenEvents(): void {
|
|
265
|
+
this.off_(this.__eventIds)
|
|
266
|
+
this.__eventIds.length = 0
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
public destroy(): void {
|
|
270
|
+
this.editor = null
|
|
271
|
+
this.__removeListenEvents()
|
|
272
|
+
super.destroy()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { IBounds, ILeaf, ILeafList, IUI, IEventListenerId } from '@leafer-ui/interface'
|
|
2
|
+
import { Bounds, PointerEvent, DragEvent, MoveEvent, LeafList, Group, ZoomEvent } from '@leafer-ui/core'
|
|
3
|
+
|
|
4
|
+
import { IEditSelect, IEditor, ISelectArea, IStroker } from '@leafer-in/interface'
|
|
5
|
+
|
|
6
|
+
import { Stroker } from './Stroker'
|
|
7
|
+
import { SelectArea } from './SelectArea'
|
|
8
|
+
import { EditSelectHelper } from '../helper/EditSelectHelper'
|
|
9
|
+
import { EditorEvent } from '../event/EditorEvent'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const { findOne } = EditSelectHelper
|
|
13
|
+
|
|
14
|
+
export class EditSelect extends Group implements IEditSelect {
|
|
15
|
+
|
|
16
|
+
public editor: IEditor
|
|
17
|
+
|
|
18
|
+
public get dragging(): boolean { return !!this.originList }
|
|
19
|
+
public get running(): boolean { return this.editor.hittable && this.editor.config.selector }
|
|
20
|
+
public get isMoveMode(): boolean { return this.app && this.app.interaction.moveMode }
|
|
21
|
+
|
|
22
|
+
public hoverStroker: IStroker = new Stroker()
|
|
23
|
+
public targetStroker: IStroker = new Stroker()
|
|
24
|
+
|
|
25
|
+
public bounds: IBounds = new Bounds()
|
|
26
|
+
public selectArea: ISelectArea = new SelectArea()
|
|
27
|
+
|
|
28
|
+
protected originList: ILeafList
|
|
29
|
+
protected lastDownLeaf: IUI
|
|
30
|
+
|
|
31
|
+
protected __eventIds: IEventListenerId[] = []
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
constructor(editor: IEditor) {
|
|
35
|
+
super()
|
|
36
|
+
this.editor = editor
|
|
37
|
+
this.addMany(this.targetStroker, this.hoverStroker, this.selectArea)
|
|
38
|
+
this.__listenEvents()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// hover / select
|
|
42
|
+
|
|
43
|
+
protected onHover(): void {
|
|
44
|
+
const { editor } = this
|
|
45
|
+
if (this.running && !this.dragging && !editor.dragging) {
|
|
46
|
+
const { stroke, strokeWidth, hover } = editor.config
|
|
47
|
+
this.hoverStroker.setTarget(hover ? this.editor.hoverTarget : null, { stroke, strokeWidth })
|
|
48
|
+
} else {
|
|
49
|
+
this.hoverStroker.target = null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected onSelect(): void {
|
|
54
|
+
if (this.running) {
|
|
55
|
+
const { config, list } = this.editor
|
|
56
|
+
const { stroke, strokeWidth } = config
|
|
57
|
+
this.targetStroker.setTarget(list, { stroke, strokeWidth: Math.max(1, strokeWidth / 2) })
|
|
58
|
+
this.hoverStroker.target = null
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public update(): void {
|
|
63
|
+
if (this.running) this.targetStroker.forceUpdate()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// move / down
|
|
67
|
+
|
|
68
|
+
protected onPointerMove(e: PointerEvent): void {
|
|
69
|
+
const { app, editor } = this
|
|
70
|
+
if (this.running && !this.isMoveMode && app.config.pointer.hover && !app.interaction.dragging) {
|
|
71
|
+
const find = e.shiftKey ? this.findDeepOne(e) : findOne(e.path)
|
|
72
|
+
editor.hoverTarget = editor.hasItem(find) ? null : find
|
|
73
|
+
} if (this.isMoveMode) {
|
|
74
|
+
editor.hoverTarget = null // move.dragEmpty
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected onBeforeDown(e: PointerEvent): void {
|
|
79
|
+
const { select } = this.editor.config
|
|
80
|
+
if (select === 'press') this.checkAndSelect(e, true)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
protected onTap(e: PointerEvent): void {
|
|
84
|
+
const { editor } = this
|
|
85
|
+
const { select, continuousSelect } = editor.config
|
|
86
|
+
if (select === 'tap') this.checkAndSelect(e)
|
|
87
|
+
|
|
88
|
+
if (this.running && (e.shiftKey || continuousSelect) && !e.middle && !this.lastDownLeaf) {
|
|
89
|
+
const find = this.findDeepOne(e)
|
|
90
|
+
if (find) editor.shiftItem(find)
|
|
91
|
+
else if (!e.shiftKey && continuousSelect) editor.target = null
|
|
92
|
+
} else if (this.isMoveMode) {
|
|
93
|
+
editor.target = null // move.dragEmpty
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.lastDownLeaf = null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected checkAndSelect(e: PointerEvent, isDownType?: boolean): void { // pointer.down or tap
|
|
100
|
+
if (this.running && !this.isMoveMode && !e.middle) {
|
|
101
|
+
const { editor } = this
|
|
102
|
+
const find = this.lastDownLeaf = findOne(e.path)
|
|
103
|
+
|
|
104
|
+
if (find) {
|
|
105
|
+
|
|
106
|
+
if (e.shiftKey || editor.config.continuousSelect) {
|
|
107
|
+
editor.shiftItem(find)
|
|
108
|
+
} else {
|
|
109
|
+
editor.target = find
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// change down data
|
|
113
|
+
if (isDownType) {
|
|
114
|
+
editor.updateLayout()
|
|
115
|
+
if (!find.locked) this.app.interaction.updateDownData(e, { findList: [editor.editBox.rect] }, editor.config.dualEvent)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
} else if (this.allow(e.target)) {
|
|
119
|
+
|
|
120
|
+
if (!e.shiftKey) editor.target = null
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// drag
|
|
127
|
+
|
|
128
|
+
protected onDragStart(e: DragEvent): void {
|
|
129
|
+
if (this.running && this.allowDrag(e)) {
|
|
130
|
+
const { editor } = this
|
|
131
|
+
const { stroke, strokeWidth, area } = editor.config
|
|
132
|
+
const { x, y } = e.getInner(this)
|
|
133
|
+
|
|
134
|
+
this.bounds.set(x, y)
|
|
135
|
+
|
|
136
|
+
this.selectArea.setStyle({ visible: true, stroke, strokeWidth, x, y }, area)
|
|
137
|
+
this.selectArea.setBounds(this.bounds.get())
|
|
138
|
+
|
|
139
|
+
this.originList = editor.leafList.clone()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected onDrag(e: DragEvent): void {
|
|
144
|
+
if (this.editor.dragging) {
|
|
145
|
+
this.onDragEnd()
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (this.dragging) {
|
|
150
|
+
const { editor } = this
|
|
151
|
+
const total = e.getInnerTotal(this)
|
|
152
|
+
|
|
153
|
+
const dragBounds = this.bounds.clone().unsign()
|
|
154
|
+
const list = new LeafList(editor.app.find(EditSelectHelper.findBounds, dragBounds))
|
|
155
|
+
|
|
156
|
+
this.bounds.width = total.x
|
|
157
|
+
this.bounds.height = total.y
|
|
158
|
+
|
|
159
|
+
this.selectArea.setBounds(dragBounds.get())
|
|
160
|
+
|
|
161
|
+
if (list.length) {
|
|
162
|
+
|
|
163
|
+
const selectList: ILeaf[] = []
|
|
164
|
+
|
|
165
|
+
this.originList.forEach(item => { if (!list.has(item)) selectList.push(item) })
|
|
166
|
+
list.forEach(item => { if (!this.originList.has(item)) selectList.push(item) })
|
|
167
|
+
|
|
168
|
+
if (selectList.length !== editor.list.length || editor.list.some((child, index) => child !== selectList[index])) {
|
|
169
|
+
editor.target = selectList as IUI[]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
} else {
|
|
173
|
+
|
|
174
|
+
editor.target = this.originList.list as IUI[]
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
protected onDragEnd(): void {
|
|
181
|
+
if (this.dragging) this.originList = null, this.selectArea.visible = false
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
protected onAutoMove(e: MoveEvent): void {
|
|
185
|
+
if (this.dragging) {
|
|
186
|
+
const { x, y } = e.getLocalMove(this)
|
|
187
|
+
this.bounds.x += x
|
|
188
|
+
this.bounds.y += y
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// helper
|
|
193
|
+
|
|
194
|
+
protected allow(target: ILeaf): boolean {
|
|
195
|
+
return target.leafer !== this.editor.leafer
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
protected allowDrag(e: DragEvent) {
|
|
199
|
+
if (this.editor.config.boxSelect && !e.target.draggable) {
|
|
200
|
+
return (!this.editor.hasTarget && this.allow(e.target)) || (e.shiftKey && !findOne(e.path))
|
|
201
|
+
} else {
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
protected findDeepOne(e: PointerEvent): IUI {
|
|
207
|
+
const options = { exclude: new LeafList(this.editor.editBox.rect) }
|
|
208
|
+
return findOne(e.target.leafer.interaction.findPath(e, options)) as IUI
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
protected __listenEvents(): void {
|
|
212
|
+
const { editor } = this
|
|
213
|
+
editor.waitLeafer(() => {
|
|
214
|
+
|
|
215
|
+
const { app } = editor
|
|
216
|
+
app.selector.proxy = editor
|
|
217
|
+
|
|
218
|
+
this.__eventIds = [
|
|
219
|
+
editor.on_(EditorEvent.HOVER, this.onHover, this),
|
|
220
|
+
editor.on_(EditorEvent.SELECT, this.onSelect, this),
|
|
221
|
+
|
|
222
|
+
app.on_(PointerEvent.MOVE, this.onPointerMove, this),
|
|
223
|
+
app.on_(PointerEvent.BEFORE_DOWN, this.onBeforeDown, this),
|
|
224
|
+
app.on_(PointerEvent.TAP, this.onTap, this),
|
|
225
|
+
|
|
226
|
+
app.on_(DragEvent.START, this.onDragStart, this),
|
|
227
|
+
app.on_(DragEvent.DRAG, this.onDrag, this),
|
|
228
|
+
app.on_(DragEvent.END, this.onDragEnd, this),
|
|
229
|
+
|
|
230
|
+
app.on_(MoveEvent.MOVE, this.onAutoMove, this),
|
|
231
|
+
app.on_([ZoomEvent.ZOOM, MoveEvent.MOVE], () => { this.editor.hoverTarget = null }),
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
protected __removeListenEvents(): void {
|
|
238
|
+
if (this.__eventIds) {
|
|
239
|
+
this.off_(this.__eventIds)
|
|
240
|
+
this.__eventIds.length = 0
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public destroy(): void {
|
|
245
|
+
this.editor = this.originList = this.lastDownLeaf = null
|
|
246
|
+
this.__removeListenEvents()
|
|
247
|
+
super.destroy()
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { IBoundsData, IGroupInputData, IRect, IRectInputData } from '@leafer-ui/interface'
|
|
2
|
+
import { Group, Rect } from '@leafer-ui/core'
|
|
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
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { IUI, ILeaferCanvas, IRenderOptions, IRectInputData } from '@leafer-ui/interface'
|
|
2
|
+
import { Paint, UI, MatrixHelper } from '@leafer-ui/core'
|
|
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
|
+
const { stroke, strokeWidth } = style
|
|
28
|
+
this.set({ stroke, strokeWidth })
|
|
29
|
+
this.target = target
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public __draw(canvas: ILeaferCanvas, options: IRenderOptions): void {
|
|
33
|
+
const { list } = this
|
|
34
|
+
if (list.length) {
|
|
35
|
+
|
|
36
|
+
let leaf: IUI
|
|
37
|
+
const { stroke, strokeWidth } = this.__
|
|
38
|
+
const { bounds } = options
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < list.length; i++) {
|
|
41
|
+
leaf = list[i]
|
|
42
|
+
if (bounds && bounds.hit(leaf.__world, options.matrix)) {
|
|
43
|
+
|
|
44
|
+
let drewPath: boolean
|
|
45
|
+
|
|
46
|
+
if (leaf.__.editSize === 'scale') {
|
|
47
|
+
const aScaleX = abs(leaf.__world.scaleX), aScaleY = abs(leaf.__world.scaleY)
|
|
48
|
+
if (aScaleX !== aScaleY) { // need no scale stroke
|
|
49
|
+
copy(matrix, leaf.__world)
|
|
50
|
+
scale(matrix, 1 / aScaleX, 1 / aScaleY)
|
|
51
|
+
|
|
52
|
+
canvas.setWorld(matrix, options.matrix)
|
|
53
|
+
canvas.beginPath()
|
|
54
|
+
this.__.strokeWidth = strokeWidth
|
|
55
|
+
|
|
56
|
+
const { x, y, width, height } = leaf.__layout.boxBounds
|
|
57
|
+
canvas.rect(x * aScaleX, y * aScaleY, width * aScaleX, height * aScaleY)
|
|
58
|
+
|
|
59
|
+
drewPath = true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!drewPath) {
|
|
64
|
+
canvas.setWorld(leaf.__world, options.matrix)
|
|
65
|
+
canvas.beginPath()
|
|
66
|
+
|
|
67
|
+
if (leaf.__.__useArrow) {
|
|
68
|
+
leaf.__drawPath(canvas)
|
|
69
|
+
} else {
|
|
70
|
+
leaf.__.__pathForRender ? leaf.__drawRenderPath(canvas) : leaf.__drawPathByBox(canvas)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.__.strokeWidth = strokeWidth / abs(leaf.__world.scaleX)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
typeof stroke === 'string' ? Paint.stroke(stroke, this, canvas) : Paint.strokes(stroke, this, canvas)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.__.strokeWidth = strokeWidth
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public destroy(): void {
|
|
85
|
+
this.target = null
|
|
86
|
+
super.destroy()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function onTarget(stroker: Stroker): void {
|
|
92
|
+
const value = stroker.target
|
|
93
|
+
stroker.list = value ? (value instanceof Array ? value : [value]) : []
|
|
94
|
+
stroker.forceUpdate()
|
|
95
|
+
}
|