@leafer-ui/interaction 1.0.0-rc.10
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 +1 -0
- package/package.json +31 -0
- package/src/Cursor.ts +16 -0
- package/src/Dragger.ts +252 -0
- package/src/HitCanvasManager.ts +45 -0
- package/src/Interaction.ts +456 -0
- package/src/InteractionHelper.ts +111 -0
- package/src/MultiTouchHelper.ts +22 -0
- package/src/Transformer.ts +108 -0
- package/src/config.ts +25 -0
- package/src/emit.ts +64 -0
- package/src/index.ts +5 -0
- package/types/index.d.ts +163 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present, Chao (Leafer) Wan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @leafer-ui/interaction
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leafer-ui/interaction",
|
|
3
|
+
"version": "1.0.0-rc.10",
|
|
4
|
+
"description": "@leafer-ui/interaction",
|
|
5
|
+
"author": "Chao (Leafer) Wan",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "src/index.ts",
|
|
8
|
+
"types": "types/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"types",
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/leaferjs/ui.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/leaferjs/ui/tree/main/packages/interaction/interaction",
|
|
19
|
+
"bugs": "https://github.com/leaferjs/ui/issues",
|
|
20
|
+
"keywords": [
|
|
21
|
+
"leafer-ui",
|
|
22
|
+
"leaferjs"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@leafer/core": "1.0.0-rc.10",
|
|
26
|
+
"@leafer-ui/event": "1.0.0-rc.10"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@leafer/interface": "1.0.0-rc.10"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/Cursor.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ICursorTypeMap, ICursorType } from '@leafer/interface'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export class Cursor {
|
|
5
|
+
|
|
6
|
+
static custom: ICursorTypeMap = {}
|
|
7
|
+
|
|
8
|
+
static set(name: string, value: ICursorType | ICursorType[]): void {
|
|
9
|
+
this.custom[name] = value
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static get(name: string): ICursorType | ICursorType[] {
|
|
13
|
+
return this.custom[name]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
package/src/Dragger.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { IPointerEvent, IDragEvent, ILeaf, ILeafList, ITimer, IFunction } from '@leafer/interface'
|
|
2
|
+
import { BoundsHelper, PointHelper, LeafHelper, LeafList } from '@leafer/core'
|
|
3
|
+
|
|
4
|
+
import { MoveEvent, DragEvent, DropEvent, PointerButton } from '@leafer-ui/event'
|
|
5
|
+
|
|
6
|
+
import { InteractionHelper } from './InteractionHelper'
|
|
7
|
+
import { InteractionBase } from './Interaction'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const emptyList = new LeafList()
|
|
11
|
+
const { getDragEventData, getDropEventData, getSwipeEventData } = InteractionHelper
|
|
12
|
+
|
|
13
|
+
export class Dragger {
|
|
14
|
+
|
|
15
|
+
protected interaction: InteractionBase
|
|
16
|
+
|
|
17
|
+
public moving: boolean
|
|
18
|
+
public dragging: boolean
|
|
19
|
+
|
|
20
|
+
public dragData: IDragEvent
|
|
21
|
+
protected downData: IPointerEvent
|
|
22
|
+
|
|
23
|
+
public dragableList: ILeafList
|
|
24
|
+
protected dragOverPath: ILeafList
|
|
25
|
+
protected dragEnterPath: ILeafList
|
|
26
|
+
|
|
27
|
+
protected autoMoveTimer: ITimer
|
|
28
|
+
|
|
29
|
+
protected canAnimate: boolean
|
|
30
|
+
protected animateWait: IFunction
|
|
31
|
+
|
|
32
|
+
constructor(interaction: InteractionBase) {
|
|
33
|
+
this.interaction = interaction
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public setDragData(data: IPointerEvent): void { // pointer down
|
|
37
|
+
if (this.animateWait) this.dragEndReal()
|
|
38
|
+
this.downData = this.interaction.downData
|
|
39
|
+
this.dragData = getDragEventData(data, data, data)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public getList(): ILeafList {
|
|
43
|
+
const { proxy } = this.interaction.selector
|
|
44
|
+
return this.dragging && (!proxy || !proxy.list.length) ? (DragEvent.list || this.dragableList || emptyList) : emptyList
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public checkDrag(data: IPointerEvent, canDrag: boolean): void {
|
|
48
|
+
const { interaction } = this
|
|
49
|
+
|
|
50
|
+
if (this.moving && data.buttons < 1) {
|
|
51
|
+
this.canAnimate = false // 防止dragEnd动画
|
|
52
|
+
interaction.pointerCancel() // 按住中键/右键拖出页面后的up事件接收不到
|
|
53
|
+
return
|
|
54
|
+
} else {
|
|
55
|
+
this.canAnimate = true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!this.moving && canDrag) {
|
|
59
|
+
if (this.moving = interaction.moveMode || interaction.isHoldRightKey) interaction.emit(MoveEvent.START, this.dragData)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!this.moving) {
|
|
63
|
+
this.dragStart(data, canDrag)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.drag(data)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public dragStart(data: IPointerEvent, canDrag: boolean): void {
|
|
70
|
+
if (!this.dragging) {
|
|
71
|
+
this.dragging = canDrag && PointerButton.left(data)
|
|
72
|
+
if (this.dragging) {
|
|
73
|
+
this.interaction.emit(DragEvent.START, this.dragData)
|
|
74
|
+
this.getDragableList(this.dragData.path)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected getDragableList(path: ILeafList): void {
|
|
80
|
+
let leaf: ILeaf
|
|
81
|
+
for (let i = 0, len = path.length; i < len; i++) {
|
|
82
|
+
leaf = path.list[i]
|
|
83
|
+
if ((leaf.__.draggable || leaf.__.editable) && leaf.__.hitSelf && !leaf.__.locked) {
|
|
84
|
+
this.dragableList = new LeafList(leaf)
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected drag(data: IPointerEvent): void {
|
|
91
|
+
const { interaction, dragData, downData } = this
|
|
92
|
+
const { path, throughPath } = downData
|
|
93
|
+
this.dragData = getDragEventData(downData, dragData, data)
|
|
94
|
+
if (throughPath) this.dragData.throughPath = throughPath
|
|
95
|
+
this.dragData.path = path
|
|
96
|
+
|
|
97
|
+
if (this.moving) {
|
|
98
|
+
interaction.emit(MoveEvent.BEFORE_MOVE, this.dragData)
|
|
99
|
+
interaction.emit(MoveEvent.MOVE, this.dragData)
|
|
100
|
+
} else if (this.dragging) {
|
|
101
|
+
this.dragReal()
|
|
102
|
+
interaction.emit(DragEvent.BEFORE_DRAG, this.dragData)
|
|
103
|
+
interaction.emit(DragEvent.DRAG, this.dragData)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected dragReal(): void {
|
|
108
|
+
const { running } = this.interaction
|
|
109
|
+
const list = this.getList()
|
|
110
|
+
if (list.length && running) {
|
|
111
|
+
const { moveX, moveY } = this.dragData
|
|
112
|
+
list.forEach(leaf => LeafHelper.moveWorld(leaf, moveX, moveY))
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public dragOverOrOut(data: IPointerEvent): void {
|
|
117
|
+
const { interaction } = this
|
|
118
|
+
const { dragOverPath } = this
|
|
119
|
+
const { path } = data
|
|
120
|
+
|
|
121
|
+
if (dragOverPath) {
|
|
122
|
+
if (path.indexAt(0) !== dragOverPath.indexAt(0)) {
|
|
123
|
+
interaction.emit(DragEvent.OUT, data, dragOverPath)
|
|
124
|
+
interaction.emit(DragEvent.OVER, data, path)
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
interaction.emit(DragEvent.OVER, data, path)
|
|
128
|
+
}
|
|
129
|
+
this.dragOverPath = path
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public dragEnterOrLeave(data: IPointerEvent): void {
|
|
133
|
+
const { interaction } = this
|
|
134
|
+
const { dragEnterPath } = this
|
|
135
|
+
const { path } = data
|
|
136
|
+
|
|
137
|
+
interaction.emit(DragEvent.LEAVE, data, dragEnterPath, path)
|
|
138
|
+
interaction.emit(DragEvent.ENTER, data, path, dragEnterPath)
|
|
139
|
+
this.dragEnterPath = path
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public dragEnd(data: IPointerEvent, speed?: number): void {
|
|
143
|
+
if (!this.dragData) return
|
|
144
|
+
|
|
145
|
+
const { moveX, moveY } = this.dragData
|
|
146
|
+
if (this.canAnimate && this.moving && (Math.abs(moveX) > 1 || Math.abs(moveY) > 1)) {
|
|
147
|
+
data = { ...data }
|
|
148
|
+
speed = (speed || (data.pointerType === 'touch' ? 2 : 1)) * 0.9
|
|
149
|
+
PointHelper.move(data, moveX * speed, moveY * speed)
|
|
150
|
+
|
|
151
|
+
this.drag(data)
|
|
152
|
+
this.animate(() => { this.dragEnd(data, 1) })
|
|
153
|
+
|
|
154
|
+
} else {
|
|
155
|
+
this.dragEndReal(data)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected dragEndReal(data?: IPointerEvent): void {
|
|
160
|
+
const { interaction, downData, dragData } = this
|
|
161
|
+
if (!data) data = dragData
|
|
162
|
+
const { path, throughPath } = downData
|
|
163
|
+
const endDragData = getDragEventData(downData, data, data)
|
|
164
|
+
if (throughPath) endDragData.throughPath = throughPath
|
|
165
|
+
endDragData.path = path
|
|
166
|
+
|
|
167
|
+
if (this.moving) interaction.emit(MoveEvent.END, endDragData)
|
|
168
|
+
if (this.dragging) {
|
|
169
|
+
interaction.emit(DragEvent.END, endDragData)
|
|
170
|
+
|
|
171
|
+
this.swipe(data, endDragData)
|
|
172
|
+
this.drop(data)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.autoMoveCancel()
|
|
176
|
+
this.dragReset()
|
|
177
|
+
this.animate(null, 'off')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
protected animate(func?: IFunction, off?: 'off'): void { // dragEnd animation
|
|
181
|
+
const animateWait = func || this.animateWait
|
|
182
|
+
if (animateWait) this.interaction.target.nextRender(animateWait, off)
|
|
183
|
+
this.animateWait = func
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
protected swipe(data: IPointerEvent, endDragData: IDragEvent): void {
|
|
188
|
+
const { interaction } = this
|
|
189
|
+
const { downData } = interaction
|
|
190
|
+
if (PointHelper.getDistance(downData, data) > interaction.config.pointer.swipeDistance) {
|
|
191
|
+
const swipeData = getSwipeEventData(downData, this.dragData, endDragData)
|
|
192
|
+
this.interaction.emit(swipeData.type, swipeData)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
protected drop(data: IPointerEvent): void {
|
|
197
|
+
const dropData = getDropEventData(data, this.getList(), DragEvent.data)
|
|
198
|
+
dropData.path = this.dragEnterPath
|
|
199
|
+
this.interaction.emit(DropEvent.DROP, dropData)
|
|
200
|
+
this.interaction.emit(DragEvent.LEAVE, data, this.dragEnterPath)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
protected dragReset(): void {
|
|
204
|
+
DragEvent.list = DragEvent.data = this.dragableList = this.dragData = this.downData = this.dragOverPath = this.dragEnterPath = null
|
|
205
|
+
this.dragging = this.moving = false
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
public checkDragOut(data: IPointerEvent): void {
|
|
210
|
+
const { interaction } = this
|
|
211
|
+
this.autoMoveCancel()
|
|
212
|
+
if (this.dragging && !interaction.shrinkCanvasBounds.hitPoint(data)) this.autoMoveOnDragOut(data)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
protected autoMoveOnDragOut(data: IPointerEvent): void {
|
|
217
|
+
const { interaction, downData } = this
|
|
218
|
+
const { autoDistance, dragOut } = interaction.config.move
|
|
219
|
+
if (!dragOut || !autoDistance) return
|
|
220
|
+
|
|
221
|
+
const bounds = interaction.shrinkCanvasBounds
|
|
222
|
+
const { x, y } = bounds
|
|
223
|
+
const right = BoundsHelper.maxX(bounds)
|
|
224
|
+
const bottom = BoundsHelper.maxY(bounds)
|
|
225
|
+
|
|
226
|
+
const moveX = data.x < x ? autoDistance : (right < data.x ? -autoDistance : 0)
|
|
227
|
+
const moveY = data.y < y ? autoDistance : (bottom < data.y ? -autoDistance : 0)
|
|
228
|
+
let totalX = 0, totalY = 0
|
|
229
|
+
|
|
230
|
+
this.autoMoveTimer = setInterval(() => {
|
|
231
|
+
totalX += moveX
|
|
232
|
+
totalY += moveY
|
|
233
|
+
|
|
234
|
+
PointHelper.move(downData, moveX, moveY)
|
|
235
|
+
PointHelper.move(this.dragData, moveX, moveY)
|
|
236
|
+
|
|
237
|
+
interaction.move({ ...data, moveX, moveY, totalX, totalY })
|
|
238
|
+
interaction.pointerMoveReal(data)
|
|
239
|
+
}, 10)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
protected autoMoveCancel(): void {
|
|
243
|
+
if (this.autoMoveTimer) {
|
|
244
|
+
clearInterval(this.autoMoveTimer)
|
|
245
|
+
this.autoMoveTimer = 0
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
public destroy(): void {
|
|
250
|
+
this.dragReset()
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { IScreenSizeData, IHitCanvasManager, ILeaf, IHitCanvas, ILeafList } from '@leafer/interface'
|
|
2
|
+
import { CanvasManager, LeafList, Creator } from '@leafer/core'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class HitCanvasManager extends CanvasManager implements IHitCanvasManager {
|
|
6
|
+
|
|
7
|
+
protected pathTypeList: ILeafList = new LeafList()
|
|
8
|
+
protected imageTypeList: ILeafList = new LeafList()
|
|
9
|
+
|
|
10
|
+
public getImageType(leaf: ILeaf, size: IScreenSizeData): IHitCanvas {
|
|
11
|
+
this.imageTypeList.add(leaf)
|
|
12
|
+
return Creator.hitCanvas(size)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public getPathType(leaf: ILeaf): IHitCanvas {
|
|
16
|
+
this.pathTypeList.add(leaf)
|
|
17
|
+
return Creator.hitCanvas()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public clearImageType(): void {
|
|
21
|
+
this.__clearLeafList(this.imageTypeList)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public clearPathType(): void {
|
|
25
|
+
this.__clearLeafList(this.pathTypeList)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected __clearLeafList(leafList: ILeafList): void {
|
|
29
|
+
if (leafList.length) {
|
|
30
|
+
leafList.forEach(leaf => {
|
|
31
|
+
if (leaf.__hitCanvas) {
|
|
32
|
+
leaf.__hitCanvas.destroy()
|
|
33
|
+
leaf.__hitCanvas = null
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
leafList.reset()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public clear(): void {
|
|
41
|
+
this.clearPathType()
|
|
42
|
+
this.clearImageType()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { IUIEvent, IPointerEvent, ILeaf, IInteraction, IInteractionConfig, ILeafList, IMoveEvent, IZoomEvent, IRotateEvent, ISelector, IBounds, IEventListenerId, IInteractionCanvas, ITimer, IKeepTouchData, IKeyEvent, IPickOptions, ICursorType, IBooleanMap } from '@leafer/interface'
|
|
2
|
+
import { LeaferEvent, ResizeEvent, LeafList, Bounds, PointHelper, DataHelper } from '@leafer/core'
|
|
3
|
+
|
|
4
|
+
import { PointerEvent, DropEvent, KeyEvent, PointerButton, Keyboard } from '@leafer-ui/event'
|
|
5
|
+
|
|
6
|
+
import { Transformer } from './Transformer'
|
|
7
|
+
import { Dragger } from './Dragger'
|
|
8
|
+
import { emit } from './emit'
|
|
9
|
+
import { InteractionHelper } from './InteractionHelper'
|
|
10
|
+
import { MultiTouchHelper } from './MultiTouchHelper'
|
|
11
|
+
import { config } from './config'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const { pathHasEventType, getMoveEventData, getZoomEventData, getRotateEventData } = InteractionHelper
|
|
15
|
+
export class InteractionBase implements IInteraction {
|
|
16
|
+
|
|
17
|
+
public target: ILeaf
|
|
18
|
+
public canvas: IInteractionCanvas
|
|
19
|
+
public selector: ISelector
|
|
20
|
+
|
|
21
|
+
public running: boolean
|
|
22
|
+
|
|
23
|
+
public get dragging(): boolean { return this.dragger.dragging }
|
|
24
|
+
public get isDragEmpty(): boolean { return this.config.move.dragEmpty && (this.hoverData && (this.hoverData.path.list[0] as ILeaf).isLeafer) && (!this.downData || (this.downData.path.list[0] as ILeaf).isLeafer) }
|
|
25
|
+
public get isHoldRightKey(): boolean { return this.config.move.holdRightKey && this.downData && PointerButton.right(this.downData) }
|
|
26
|
+
public get moveMode(): boolean { return this.config.move.drag || (this.config.move.holdSpaceKey && Keyboard.isHoldSpaceKey()) || (this.downData && ((this.config.move.holdMiddleKey && PointerButton.middle(this.downData)) || (this.isHoldRightKey && this.dragger.moving))) || this.isDragEmpty }
|
|
27
|
+
|
|
28
|
+
public config: IInteractionConfig = config
|
|
29
|
+
|
|
30
|
+
public cursor: ICursorType | ICursorType[]
|
|
31
|
+
public get hitRadius(): number { return this.config.pointer.hitRadius }
|
|
32
|
+
|
|
33
|
+
public shrinkCanvasBounds: IBounds
|
|
34
|
+
|
|
35
|
+
public downData: IPointerEvent
|
|
36
|
+
protected oldDownData?: IPointerEvent // 通过updateDownData强制更新下来的数据
|
|
37
|
+
public hoverData: IPointerEvent
|
|
38
|
+
|
|
39
|
+
public downTime: number
|
|
40
|
+
protected overPath: LeafList
|
|
41
|
+
protected enterPath: LeafList
|
|
42
|
+
|
|
43
|
+
protected waitMenuTap: boolean
|
|
44
|
+
protected waitTap: boolean
|
|
45
|
+
protected longPressTimer: ITimer
|
|
46
|
+
protected longPressed: boolean
|
|
47
|
+
protected tapCount = 0
|
|
48
|
+
protected tapTimer: ITimer
|
|
49
|
+
|
|
50
|
+
protected dragger: Dragger
|
|
51
|
+
protected transformer: Transformer
|
|
52
|
+
|
|
53
|
+
protected __eventIds: IEventListenerId[]
|
|
54
|
+
protected defaultPath: ILeafList
|
|
55
|
+
|
|
56
|
+
protected downKeyMap: IBooleanMap = {}
|
|
57
|
+
|
|
58
|
+
constructor(target: ILeaf, canvas: IInteractionCanvas, selector: ISelector, userConfig?: IInteractionConfig) {
|
|
59
|
+
this.target = target
|
|
60
|
+
this.canvas = canvas
|
|
61
|
+
this.selector = selector
|
|
62
|
+
this.defaultPath = new LeafList(target)
|
|
63
|
+
|
|
64
|
+
this.transformer = new Transformer(this)
|
|
65
|
+
this.dragger = new Dragger(this)
|
|
66
|
+
|
|
67
|
+
if (userConfig) this.config = DataHelper.default(userConfig, this.config)
|
|
68
|
+
this.__listenEvents()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
public start(): void {
|
|
73
|
+
this.running = true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public stop(): void {
|
|
77
|
+
this.running = false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
public receive(_event: any): void { }
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
public pointerDown(data?: IPointerEvent, useDefaultPath?: boolean): void {
|
|
85
|
+
if (!data) data = this.hoverData
|
|
86
|
+
if (!data) return
|
|
87
|
+
PointerButton.defaultLeft(data)
|
|
88
|
+
|
|
89
|
+
this.updateDownData(data)
|
|
90
|
+
if (useDefaultPath) data.path = this.defaultPath
|
|
91
|
+
|
|
92
|
+
this.emit(PointerEvent.BEFORE_DOWN, data)
|
|
93
|
+
this.emit(PointerEvent.DOWN, data)
|
|
94
|
+
|
|
95
|
+
this.downTime = Date.now()
|
|
96
|
+
|
|
97
|
+
this.dragger.setDragData(data)
|
|
98
|
+
|
|
99
|
+
if (PointerButton.left(data)) {
|
|
100
|
+
this.tapWait()
|
|
101
|
+
this.longPressWait(data)
|
|
102
|
+
} else if (PointerButton.right(data)) {
|
|
103
|
+
this.waitMenuTap = true
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.updateCursor(data)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public pointerMove(data?: IPointerEvent): void {
|
|
110
|
+
if (!data) data = this.hoverData
|
|
111
|
+
if (!data) return
|
|
112
|
+
if (this.downData) PointerButton.defaultLeft(data)
|
|
113
|
+
|
|
114
|
+
const hit = this.canvas.bounds.hitPoint(data)
|
|
115
|
+
if (hit || this.downData) {
|
|
116
|
+
if (hit && !this.downData && PointerButton.left(data)) this.pointerDown(data, true) // 从外部拖拽内容进入,需要先模拟down事件
|
|
117
|
+
this.pointerMoveReal(data)
|
|
118
|
+
this.dragger.checkDragOut(data)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public pointerMoveReal(data: IPointerEvent): void {
|
|
123
|
+
this.emit(PointerEvent.BEFORE_MOVE, data, this.defaultPath)
|
|
124
|
+
|
|
125
|
+
if (this.downData) {
|
|
126
|
+
const canDrag = PointHelper.getDistance(this.downData, data) > this.config.pointer.dragDistance
|
|
127
|
+
if (canDrag) {
|
|
128
|
+
if (this.waitTap) this.pointerWaitCancel()
|
|
129
|
+
this.waitMenuTap = false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.dragger.checkDrag(data, canDrag)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!this.dragger.moving) {
|
|
136
|
+
this.updateHoverData(data)
|
|
137
|
+
this.emit(PointerEvent.MOVE, data)
|
|
138
|
+
|
|
139
|
+
this.pointerOverOrOut(data)
|
|
140
|
+
this.pointerEnterOrLeave(data)
|
|
141
|
+
if (this.dragger.dragging) {
|
|
142
|
+
this.dragger.dragOverOrOut(data)
|
|
143
|
+
this.dragger.dragEnterOrLeave(data)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.updateCursor(this.downData || data)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public pointerUp(data?: IPointerEvent): void {
|
|
151
|
+
if (!data) data = this.downData
|
|
152
|
+
if (!this.downData) return
|
|
153
|
+
PointerButton.defaultLeft(data)
|
|
154
|
+
|
|
155
|
+
this.findPath(data)
|
|
156
|
+
|
|
157
|
+
this.emit(PointerEvent.BEFORE_UP, data)
|
|
158
|
+
this.emit(PointerEvent.UP, data)
|
|
159
|
+
if (this.oldDownData) this.emit(PointerEvent.UP, this.oldDownData, undefined, data.path) // oldDownPath必须触发up
|
|
160
|
+
this.emit(PointerEvent.UP, this.downData, undefined, data.path) // downPath必须触发up
|
|
161
|
+
|
|
162
|
+
this.touchLeave(data)
|
|
163
|
+
|
|
164
|
+
this.tap(data)
|
|
165
|
+
this.menuTap(data)
|
|
166
|
+
|
|
167
|
+
this.dragger.dragEnd(data)
|
|
168
|
+
|
|
169
|
+
this.downData = this.oldDownData = null
|
|
170
|
+
|
|
171
|
+
this.updateCursor(data)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public pointerCancel(): void {
|
|
175
|
+
this.pointerUp(this.dragger.dragData)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
public multiTouch(data: IUIEvent, list: IKeepTouchData[]): void {
|
|
180
|
+
const { move, angle, scale, center } = MultiTouchHelper.getData(list)
|
|
181
|
+
this.rotate(getRotateEventData(center, angle, data))
|
|
182
|
+
this.zoom(getZoomEventData(center, scale, data))
|
|
183
|
+
this.move(getMoveEventData(center, move, data))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// context menu
|
|
187
|
+
|
|
188
|
+
public menu(data: IPointerEvent): void {
|
|
189
|
+
this.findPath(data)
|
|
190
|
+
this.emit(PointerEvent.MENU, data)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public menuTap(data: IPointerEvent): void {
|
|
194
|
+
if (this.waitMenuTap) this.emit(PointerEvent.MENU_TAP, data)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// window transform
|
|
198
|
+
|
|
199
|
+
public move(data: IMoveEvent): void {
|
|
200
|
+
this.transformer.move(data)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public zoom(data: IZoomEvent): void {
|
|
204
|
+
this.transformer.zoom(data)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public rotate(data: IRotateEvent): void {
|
|
208
|
+
this.transformer.rotate(data)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public transformEnd(): void {
|
|
212
|
+
this.transformer.transformEnd()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
// key
|
|
217
|
+
|
|
218
|
+
public keyDown(data: IKeyEvent): void {
|
|
219
|
+
const { code } = data
|
|
220
|
+
if (!this.downKeyMap[code]) {
|
|
221
|
+
this.downKeyMap[code] = true
|
|
222
|
+
Keyboard.setDownCode(code)
|
|
223
|
+
|
|
224
|
+
this.emit(KeyEvent.HOLD, data, this.defaultPath)
|
|
225
|
+
if (this.moveMode) this.updateCursor()
|
|
226
|
+
}
|
|
227
|
+
this.emit(KeyEvent.DOWN, data, this.defaultPath)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public keyUp(data: IKeyEvent): void {
|
|
231
|
+
const { code } = data
|
|
232
|
+
this.downKeyMap[code] = false
|
|
233
|
+
Keyboard.setUpCode(code)
|
|
234
|
+
|
|
235
|
+
this.emit(KeyEvent.UP, data, this.defaultPath)
|
|
236
|
+
if (this.cursor === 'grab') this.updateCursor()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
// helper
|
|
241
|
+
protected pointerOverOrOut(data: IPointerEvent): void {
|
|
242
|
+
if (this.dragger.moving) return
|
|
243
|
+
if (this.dragging && !this.config.pointer.dragHover) return
|
|
244
|
+
|
|
245
|
+
const { path } = data
|
|
246
|
+
if (this.overPath) {
|
|
247
|
+
if (path.indexAt(0) !== this.overPath.indexAt(0)) {
|
|
248
|
+
this.emit(PointerEvent.OUT, data, this.overPath)
|
|
249
|
+
this.emit(PointerEvent.OVER, data, path)
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
this.emit(PointerEvent.OVER, data, path)
|
|
253
|
+
}
|
|
254
|
+
this.overPath = path
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
protected pointerEnterOrLeave(data: IPointerEvent): void {
|
|
258
|
+
if (this.dragger.moving) return
|
|
259
|
+
if (this.dragging && !this.config.pointer.dragHover) return
|
|
260
|
+
|
|
261
|
+
const { path } = data
|
|
262
|
+
this.emit(PointerEvent.LEAVE, data, this.enterPath, path)
|
|
263
|
+
this.emit(PointerEvent.ENTER, data, path, this.enterPath)
|
|
264
|
+
this.enterPath = path
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
protected touchLeave(data: IPointerEvent): void {
|
|
268
|
+
if (data.pointerType === 'touch') {
|
|
269
|
+
if (this.enterPath) {
|
|
270
|
+
this.emit(PointerEvent.LEAVE, data)
|
|
271
|
+
if (this.dragger.dragging) this.emit(DropEvent.LEAVE, data)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
protected tap(data: IPointerEvent): void {
|
|
277
|
+
const { pointer } = this.config
|
|
278
|
+
|
|
279
|
+
const longTap = this.longTap(data)
|
|
280
|
+
if (!pointer.tapMore && longTap) return
|
|
281
|
+
|
|
282
|
+
if (!this.waitTap) return
|
|
283
|
+
if (pointer.tapMore) this.emitTap(data)
|
|
284
|
+
|
|
285
|
+
const useTime = Date.now() - this.downTime
|
|
286
|
+
|
|
287
|
+
const hasDouble = [PointerEvent.DOUBLE_TAP, PointerEvent.DOUBLE_CLICK].some(type => pathHasEventType(data.path, type))
|
|
288
|
+
|
|
289
|
+
if (useTime < pointer.tapTime + 50 && hasDouble) {
|
|
290
|
+
|
|
291
|
+
this.tapCount++
|
|
292
|
+
if (this.tapCount === 2) {
|
|
293
|
+
this.tapWaitCancel()
|
|
294
|
+
this.emitDoubleTap(data)
|
|
295
|
+
} else {
|
|
296
|
+
clearTimeout(this.tapTimer)
|
|
297
|
+
this.tapTimer = setTimeout(() => {
|
|
298
|
+
if (!pointer.tapMore) {
|
|
299
|
+
this.tapWaitCancel()
|
|
300
|
+
this.emitTap(data)
|
|
301
|
+
}
|
|
302
|
+
}, pointer.tapTime)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
} else {
|
|
306
|
+
|
|
307
|
+
if (!pointer.tapMore) {
|
|
308
|
+
this.tapWaitCancel()
|
|
309
|
+
this.emitTap(data)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
// update
|
|
316
|
+
public findPath(data: IPointerEvent, options?: IPickOptions): ILeafList {
|
|
317
|
+
const { hitRadius, through } = this.config.pointer
|
|
318
|
+
const find = this.selector.getByPoint(data, hitRadius, options || { through })
|
|
319
|
+
if (find.throughPath) data.throughPath = find.throughPath
|
|
320
|
+
data.path = find.path
|
|
321
|
+
return find.path
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public isDrag(leaf: ILeaf): boolean {
|
|
325
|
+
return this.dragger.getList().has(leaf)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
public updateDownData(data?: IPointerEvent, options?: IPickOptions): void {
|
|
329
|
+
const { downData } = this
|
|
330
|
+
if (!data && downData) data = { ...downData }
|
|
331
|
+
if (!data) return
|
|
332
|
+
this.oldDownData = downData
|
|
333
|
+
this.findPath(data, options)
|
|
334
|
+
this.downData = data
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
public updateHoverData(data?: IPointerEvent): void {
|
|
338
|
+
if (!data) data = this.hoverData
|
|
339
|
+
if (!data) return
|
|
340
|
+
this.findPath(data, { exclude: this.dragger.getList(), name: PointerEvent.MOVE })
|
|
341
|
+
this.hoverData = data
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public updateCursor(data?: IPointerEvent): void {
|
|
345
|
+
if (this.config.cursor.stop) return
|
|
346
|
+
|
|
347
|
+
if (!data) {
|
|
348
|
+
this.updateHoverData()
|
|
349
|
+
data = this.downData || this.hoverData
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (this.dragger.moving) {
|
|
353
|
+
return this.setCursor('grabbing')
|
|
354
|
+
} else if (this.moveMode) {
|
|
355
|
+
return this.setCursor(this.downData ? 'grabbing' : 'grab')
|
|
356
|
+
} else if (!data) return
|
|
357
|
+
|
|
358
|
+
let leaf: ILeaf
|
|
359
|
+
let cursor: ICursorType | ICursorType[]
|
|
360
|
+
|
|
361
|
+
const { path } = data
|
|
362
|
+
for (let i = 0, len = path.length; i < len; i++) {
|
|
363
|
+
leaf = path.list[i]
|
|
364
|
+
cursor = leaf.cursor
|
|
365
|
+
if (cursor) break
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
this.setCursor(cursor)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
public setCursor(cursor: ICursorType | ICursorType[]): void {
|
|
372
|
+
this.cursor = cursor
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
protected emitTap(data: IPointerEvent) {
|
|
376
|
+
this.emit(PointerEvent.TAP, data)
|
|
377
|
+
this.emit(PointerEvent.CLICK, data)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
protected emitDoubleTap(data: IPointerEvent) {
|
|
381
|
+
this.emit(PointerEvent.DOUBLE_TAP, data)
|
|
382
|
+
this.emit(PointerEvent.DOUBLE_CLICK, data)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
public pointerWaitCancel(): void {
|
|
386
|
+
this.tapWaitCancel()
|
|
387
|
+
this.longPressWaitCancel()
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
protected tapWait(): void {
|
|
391
|
+
clearTimeout(this.tapTimer)
|
|
392
|
+
this.waitTap = true
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
protected tapWaitCancel(): void {
|
|
396
|
+
clearTimeout(this.tapTimer)
|
|
397
|
+
this.waitTap = false
|
|
398
|
+
this.tapCount = 0
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
protected longPressWait(data: IPointerEvent): void {
|
|
402
|
+
clearTimeout(this.longPressTimer)
|
|
403
|
+
this.longPressTimer = setTimeout(() => {
|
|
404
|
+
this.longPressed = true
|
|
405
|
+
this.emit(PointerEvent.LONG_PRESS, data)
|
|
406
|
+
}, this.config.pointer.longPressTime)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
protected longTap(data: IPointerEvent): boolean {
|
|
410
|
+
let longTap
|
|
411
|
+
if (this.longPressed) {
|
|
412
|
+
this.emit(PointerEvent.LONG_TAP, data)
|
|
413
|
+
if (pathHasEventType(data.path, PointerEvent.LONG_TAP)) longTap = true
|
|
414
|
+
}
|
|
415
|
+
this.longPressWaitCancel()
|
|
416
|
+
return longTap
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
protected longPressWaitCancel(): void {
|
|
420
|
+
clearTimeout(this.longPressTimer)
|
|
421
|
+
this.longPressed = false
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
protected __onResize(): void {
|
|
425
|
+
this.shrinkCanvasBounds = new Bounds(this.canvas.bounds)
|
|
426
|
+
this.shrinkCanvasBounds.spread(-2)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
protected __listenEvents(): void {
|
|
430
|
+
const { target } = this
|
|
431
|
+
this.__eventIds = [target.on_(ResizeEvent.RESIZE, this.__onResize, this)]
|
|
432
|
+
target.once(LeaferEvent.READY, () => this.__onResize())
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
protected __removeListenEvents(): void {
|
|
436
|
+
this.target.off_(this.__eventIds)
|
|
437
|
+
this.__eventIds.length = 0
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
public emit(type: string, data: IUIEvent, path?: ILeafList, excludePath?: ILeafList): void {
|
|
442
|
+
if (this.running) emit(type, data, path, excludePath)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
public destroy(): void {
|
|
447
|
+
if (this.__eventIds.length) {
|
|
448
|
+
this.stop()
|
|
449
|
+
this.__removeListenEvents()
|
|
450
|
+
this.dragger.destroy()
|
|
451
|
+
this.transformer.destroy()
|
|
452
|
+
this.downData = this.overPath = this.enterPath = null
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { IEvent, IPointerEvent, IMoveEvent, IZoomEvent, IRotateEvent, IDragEvent, ISwipeEvent, IUIEvent, IPointData, ILeafList, IDropEvent, IObject } from '@leafer/interface'
|
|
2
|
+
import { PointHelper, LeafList } from '@leafer/core'
|
|
3
|
+
|
|
4
|
+
import { SwipeEvent } from '@leafer-ui/event'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const InteractionHelper = {
|
|
8
|
+
|
|
9
|
+
getMoveEventData(center: IPointData, move: IPointData, event: IEvent): IMoveEvent {
|
|
10
|
+
return {
|
|
11
|
+
...event,
|
|
12
|
+
x: center.x,
|
|
13
|
+
y: center.y,
|
|
14
|
+
moveX: move.x,
|
|
15
|
+
moveY: move.y
|
|
16
|
+
} as IMoveEvent
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
getRotateEventData(center: IPointData, angle: number, event: IEvent): IRotateEvent {
|
|
20
|
+
return {
|
|
21
|
+
...event,
|
|
22
|
+
x: center.x,
|
|
23
|
+
y: center.y,
|
|
24
|
+
rotation: angle
|
|
25
|
+
} as IRotateEvent
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
getZoomEventData(center: IPointData, scale: number, event: IEvent): IZoomEvent {
|
|
29
|
+
return {
|
|
30
|
+
...event,
|
|
31
|
+
x: center.x,
|
|
32
|
+
y: center.y,
|
|
33
|
+
scale,
|
|
34
|
+
} as IZoomEvent
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
getDragEventData(startPoint: IPointData, lastPoint: IPointData, event: IPointerEvent): IDragEvent {
|
|
38
|
+
return {
|
|
39
|
+
...event,
|
|
40
|
+
x: event.x,
|
|
41
|
+
y: event.y,
|
|
42
|
+
moveX: event.x - lastPoint.x,
|
|
43
|
+
moveY: event.y - lastPoint.y,
|
|
44
|
+
totalX: event.x - startPoint.x,
|
|
45
|
+
totalY: event.y - startPoint.y,
|
|
46
|
+
} as IDragEvent
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
getDropEventData(event: IPointerEvent, list: ILeafList, data: IObject): IDropEvent {
|
|
50
|
+
return {
|
|
51
|
+
...event,
|
|
52
|
+
list,
|
|
53
|
+
data
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
getSwipeDirection(angle: number): string {
|
|
58
|
+
if (angle < -45 && angle > -135) {
|
|
59
|
+
return SwipeEvent.UP
|
|
60
|
+
} else if (angle > 45 && angle < 135) {
|
|
61
|
+
return SwipeEvent.DOWN
|
|
62
|
+
} else if (angle <= 45 && angle >= -45) {
|
|
63
|
+
return SwipeEvent.RIGHT
|
|
64
|
+
} else {
|
|
65
|
+
return SwipeEvent.LEFT
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
getSwipeEventData(startPoint: IPointData, lastDragData: IDragEvent, event: IPointerEvent): ISwipeEvent {
|
|
70
|
+
return {
|
|
71
|
+
...event,
|
|
72
|
+
moveX: lastDragData.moveX,
|
|
73
|
+
moveY: lastDragData.moveY,
|
|
74
|
+
totalX: event.x - startPoint.x,
|
|
75
|
+
totalY: event.y - startPoint.y,
|
|
76
|
+
type: I.getSwipeDirection(PointHelper.getAngle(startPoint, event)),
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
getBase(e: IObject): IUIEvent {
|
|
82
|
+
const pointerUpButtons = e.button === 1 ? 4 : e.button // 0: left, 1: middle, 2: right
|
|
83
|
+
return {
|
|
84
|
+
altKey: e.altKey,
|
|
85
|
+
ctrlKey: e.ctrlKey,
|
|
86
|
+
shiftKey: e.shiftKey,
|
|
87
|
+
metaKey: e.metaKey,
|
|
88
|
+
buttons: e.buttons === undefined ? 1 : (e.buttons === 0 ? pointerUpButtons : e.buttons), // touchEvent no button and buttons, set default
|
|
89
|
+
origin: e
|
|
90
|
+
} as IUIEvent
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
pathHasEventType(path: ILeafList, type: string): boolean {
|
|
94
|
+
const { list } = path
|
|
95
|
+
for (let i = 0, len = list.length; i < len; i++) {
|
|
96
|
+
if (list[i].hasEvent(type)) return true
|
|
97
|
+
}
|
|
98
|
+
return false
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
filterPathByEventType(path: ILeafList, type: string): ILeafList {
|
|
102
|
+
const find = new LeafList()
|
|
103
|
+
const { list } = path
|
|
104
|
+
for (let i = 0, len = list.length; i < len; i++) {
|
|
105
|
+
if (list[i].hasEvent(type)) find.add(list[i])
|
|
106
|
+
}
|
|
107
|
+
return find
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const I = InteractionHelper
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { IMultiTouchData, IKeepTouchData } from '@leafer/interface'
|
|
2
|
+
import { PointHelper } from '@leafer/core'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export const MultiTouchHelper = {
|
|
6
|
+
|
|
7
|
+
getData(list: IKeepTouchData[]): IMultiTouchData {
|
|
8
|
+
const a = list[0]
|
|
9
|
+
const b = list[1]
|
|
10
|
+
const lastCenter = PointHelper.getCenter(a.from, b.from)
|
|
11
|
+
const center = PointHelper.getCenter(a.to, b.to)
|
|
12
|
+
const move = { x: center.x - lastCenter.x, y: center.y - lastCenter.y }
|
|
13
|
+
|
|
14
|
+
const lastDistance = PointHelper.getDistance(a.from, b.from)
|
|
15
|
+
const distance = PointHelper.getDistance(a.to, b.to)
|
|
16
|
+
const scale = distance / lastDistance
|
|
17
|
+
|
|
18
|
+
const angle = PointHelper.getRotation(a.from, b.from, a.to, b.to)
|
|
19
|
+
|
|
20
|
+
return { move, scale, angle, center }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { IMoveEvent, IZoomEvent, IRotateEvent, ITimer } from '@leafer/interface'
|
|
2
|
+
import { MoveEvent, ZoomEvent, RotateEvent } from '@leafer-ui/event'
|
|
3
|
+
|
|
4
|
+
import { InteractionBase } from './Interaction'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class Transformer {
|
|
8
|
+
|
|
9
|
+
protected interaction: InteractionBase
|
|
10
|
+
protected moveData: IMoveEvent
|
|
11
|
+
protected zoomData: IZoomEvent
|
|
12
|
+
protected rotateData: IRotateEvent
|
|
13
|
+
protected transformTimer: ITimer
|
|
14
|
+
|
|
15
|
+
constructor(interaction: InteractionBase) {
|
|
16
|
+
this.interaction = interaction
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public move(data: IMoveEvent): void {
|
|
20
|
+
const { interaction } = this
|
|
21
|
+
|
|
22
|
+
if (!this.moveData) {
|
|
23
|
+
const { path } = interaction.selector.getByPoint(data, interaction.hitRadius)
|
|
24
|
+
data.path = path
|
|
25
|
+
this.moveData = { ...data, moveX: 0, moveY: 0 }
|
|
26
|
+
interaction.emit(MoveEvent.START, this.moveData)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
data.path = this.moveData.path
|
|
30
|
+
interaction.emit(MoveEvent.BEFORE_MOVE, data)
|
|
31
|
+
interaction.emit(MoveEvent.MOVE, data)
|
|
32
|
+
|
|
33
|
+
this.transformEndWait()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public zoom(data: IZoomEvent): void {
|
|
37
|
+
const { interaction } = this
|
|
38
|
+
|
|
39
|
+
if (!this.zoomData) {
|
|
40
|
+
const { path } = interaction.selector.getByPoint(data, interaction.hitRadius)
|
|
41
|
+
data.path = path
|
|
42
|
+
this.zoomData = { ...data, scale: 1 }
|
|
43
|
+
interaction.emit(ZoomEvent.START, this.zoomData)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
data.path = this.zoomData.path
|
|
47
|
+
interaction.emit(ZoomEvent.BEFORE_ZOOM, data)
|
|
48
|
+
interaction.emit(ZoomEvent.ZOOM, data)
|
|
49
|
+
|
|
50
|
+
this.transformEndWait()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public rotate(data: IRotateEvent): void {
|
|
54
|
+
const { interaction } = this
|
|
55
|
+
|
|
56
|
+
if (!this.rotateData) {
|
|
57
|
+
const { path } = interaction.selector.getByPoint(data, interaction.hitRadius)
|
|
58
|
+
data.path = path
|
|
59
|
+
this.rotateData = { ...data, rotation: 0 }
|
|
60
|
+
interaction.emit(RotateEvent.START, this.rotateData)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
data.path = this.rotateData.path
|
|
64
|
+
interaction.emit(RotateEvent.BEFORE_ROTATE, data)
|
|
65
|
+
interaction.emit(RotateEvent.ROTATE, data)
|
|
66
|
+
|
|
67
|
+
this.transformEndWait()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
protected transformEndWait(): void {
|
|
72
|
+
clearTimeout(this.transformTimer)
|
|
73
|
+
this.transformTimer = setTimeout(() => {
|
|
74
|
+
this.transformEnd()
|
|
75
|
+
}, this.interaction.config.pointer.transformTime)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public transformEnd(): void {
|
|
79
|
+
this.moveEnd()
|
|
80
|
+
this.zoomEnd()
|
|
81
|
+
this.rotateEnd()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected moveEnd(): void {
|
|
85
|
+
if (this.moveData) {
|
|
86
|
+
this.interaction.emit(MoveEvent.END, this.moveData)
|
|
87
|
+
this.moveData = null
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
protected zoomEnd(): void {
|
|
92
|
+
if (this.zoomData) {
|
|
93
|
+
this.interaction.emit(ZoomEvent.END, this.zoomData)
|
|
94
|
+
this.zoomData = null
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
protected rotateEnd(): void {
|
|
99
|
+
if (this.rotateData) {
|
|
100
|
+
this.interaction.emit(RotateEvent.END, this.rotateData)
|
|
101
|
+
this.rotateData = null
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public destroy(): void {
|
|
106
|
+
this.zoomData = this.moveData = this.rotateData = null
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IInteractionConfig } from '@leafer/interface'
|
|
2
|
+
|
|
3
|
+
export const config: IInteractionConfig = {
|
|
4
|
+
wheel: {
|
|
5
|
+
zoomMode: false,
|
|
6
|
+
zoomSpeed: 0.5,
|
|
7
|
+
moveSpeed: 0.5,
|
|
8
|
+
rotateSpeed: 0.5,
|
|
9
|
+
delta: { x: 80 / 4, y: 8.0 }, // 基准速度(会影响zoomSpeed),可根据不同系统、浏览器细化定制
|
|
10
|
+
preventDefault: true
|
|
11
|
+
},
|
|
12
|
+
pointer: {
|
|
13
|
+
hitRadius: 5,
|
|
14
|
+
through: false,
|
|
15
|
+
tapTime: 120,
|
|
16
|
+
longPressTime: 800,
|
|
17
|
+
transformTime: 500,
|
|
18
|
+
dragHover: true,
|
|
19
|
+
dragDistance: 2,
|
|
20
|
+
swipeDistance: 20,
|
|
21
|
+
ignoreMove: false,
|
|
22
|
+
preventDefaultMenu: true
|
|
23
|
+
},
|
|
24
|
+
cursor: {}
|
|
25
|
+
}
|
package/src/emit.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { IUIEvent, ILeaf, ILeafList } from '@leafer/interface'
|
|
2
|
+
import { EventCreator, Debug } from '@leafer/core'
|
|
3
|
+
|
|
4
|
+
const debug = Debug.get('emit')
|
|
5
|
+
|
|
6
|
+
export function emit(type: string, data: IUIEvent, path?: ILeafList, excludePath?: ILeafList): void {
|
|
7
|
+
if (!path && !data.path) return
|
|
8
|
+
|
|
9
|
+
let leaf: ILeaf
|
|
10
|
+
data.type = type
|
|
11
|
+
if (path) {
|
|
12
|
+
data = { ...data, path }
|
|
13
|
+
} else {
|
|
14
|
+
path = data.path
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
data.target = path.indexAt(0)
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
|
|
21
|
+
// capture
|
|
22
|
+
for (let i = path.length - 1; i > -1; i--) {
|
|
23
|
+
leaf = path.list[i]
|
|
24
|
+
if (emitEvent(leaf, type, data, true, excludePath)) return
|
|
25
|
+
if (leaf.isApp) emitAppChildren(leaf, type, data, true, excludePath) // other leafer
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// bubble
|
|
29
|
+
for (let i = 0, len = path.length; i < len; i++) {
|
|
30
|
+
leaf = path.list[i]
|
|
31
|
+
if (leaf.isApp) emitAppChildren(leaf, type, data, false, excludePath) // other leafer
|
|
32
|
+
if (emitEvent(leaf, type, data, false, excludePath)) return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
} catch (e) {
|
|
36
|
+
debug.error(e)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const allowTypes = ['move', 'zoom', 'rotate', 'key']
|
|
41
|
+
function emitAppChildren(leaf: ILeaf, type: string, data: IUIEvent, capture?: boolean, excludePath?: ILeafList): void {
|
|
42
|
+
if (allowTypes.some(name => type.startsWith(name)) && leaf.__.hitChildren && !exclude(leaf, excludePath)) {
|
|
43
|
+
let child: ILeaf
|
|
44
|
+
for (let i = 0, len = leaf.children.length; i < len; i++) {
|
|
45
|
+
child = leaf.children[i]
|
|
46
|
+
if (!data.path.has(child) && child.__.hittable) emitEvent(child, type, data, capture, excludePath) // other leafer
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function emitEvent(leaf: ILeaf, type: string, data: IUIEvent, capture?: boolean, excludePath?: ILeafList): boolean {
|
|
52
|
+
if (leaf.destroyed) return true
|
|
53
|
+
if (leaf.__.hitSelf && leaf.hasEvent(type, capture) && !exclude(leaf, excludePath)) {
|
|
54
|
+
data.phase = capture ? 1 : ((leaf === data.target) ? 2 : 3)
|
|
55
|
+
const event = EventCreator.get(type, data)
|
|
56
|
+
leaf.emitEvent(event, capture)
|
|
57
|
+
if (event.isStop) return true
|
|
58
|
+
}
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function exclude(leaf: ILeaf, excludePath?: ILeafList): boolean {
|
|
63
|
+
return excludePath && excludePath.has(leaf)
|
|
64
|
+
}
|
package/src/index.ts
ADDED
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { IMoveEvent, IZoomEvent, IRotateEvent, ITimer, IDragEvent, IPointerEvent, ILeafList, IFunction, IInteraction, ILeaf, IInteractionCanvas, ISelector, IInteractionConfig, ICursorType, IBounds, IEventListenerId, IBooleanMap, IUIEvent, IKeepTouchData, IKeyEvent, IPickOptions, IPointData, IEvent, IObject, IDropEvent, ISwipeEvent, IMultiTouchData, ICursorTypeMap, IHitCanvasManager, IScreenSizeData, IHitCanvas } from '@leafer/interface';
|
|
2
|
+
import { LeafList, CanvasManager } from '@leafer/core';
|
|
3
|
+
|
|
4
|
+
declare class Transformer {
|
|
5
|
+
protected interaction: InteractionBase;
|
|
6
|
+
protected moveData: IMoveEvent;
|
|
7
|
+
protected zoomData: IZoomEvent;
|
|
8
|
+
protected rotateData: IRotateEvent;
|
|
9
|
+
protected transformTimer: ITimer;
|
|
10
|
+
constructor(interaction: InteractionBase);
|
|
11
|
+
move(data: IMoveEvent): void;
|
|
12
|
+
zoom(data: IZoomEvent): void;
|
|
13
|
+
rotate(data: IRotateEvent): void;
|
|
14
|
+
protected transformEndWait(): void;
|
|
15
|
+
transformEnd(): void;
|
|
16
|
+
protected moveEnd(): void;
|
|
17
|
+
protected zoomEnd(): void;
|
|
18
|
+
protected rotateEnd(): void;
|
|
19
|
+
destroy(): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare class Dragger {
|
|
23
|
+
protected interaction: InteractionBase;
|
|
24
|
+
moving: boolean;
|
|
25
|
+
dragging: boolean;
|
|
26
|
+
dragData: IDragEvent;
|
|
27
|
+
protected downData: IPointerEvent;
|
|
28
|
+
dragableList: ILeafList;
|
|
29
|
+
protected dragOverPath: ILeafList;
|
|
30
|
+
protected dragEnterPath: ILeafList;
|
|
31
|
+
protected autoMoveTimer: ITimer;
|
|
32
|
+
protected canAnimate: boolean;
|
|
33
|
+
protected animateWait: IFunction;
|
|
34
|
+
constructor(interaction: InteractionBase);
|
|
35
|
+
setDragData(data: IPointerEvent): void;
|
|
36
|
+
getList(): ILeafList;
|
|
37
|
+
checkDrag(data: IPointerEvent, canDrag: boolean): void;
|
|
38
|
+
dragStart(data: IPointerEvent, canDrag: boolean): void;
|
|
39
|
+
protected getDragableList(path: ILeafList): void;
|
|
40
|
+
protected drag(data: IPointerEvent): void;
|
|
41
|
+
protected dragReal(): void;
|
|
42
|
+
dragOverOrOut(data: IPointerEvent): void;
|
|
43
|
+
dragEnterOrLeave(data: IPointerEvent): void;
|
|
44
|
+
dragEnd(data: IPointerEvent, speed?: number): void;
|
|
45
|
+
protected dragEndReal(data?: IPointerEvent): void;
|
|
46
|
+
protected animate(func?: IFunction, off?: 'off'): void;
|
|
47
|
+
protected swipe(data: IPointerEvent, endDragData: IDragEvent): void;
|
|
48
|
+
protected drop(data: IPointerEvent): void;
|
|
49
|
+
protected dragReset(): void;
|
|
50
|
+
checkDragOut(data: IPointerEvent): void;
|
|
51
|
+
protected autoMoveOnDragOut(data: IPointerEvent): void;
|
|
52
|
+
protected autoMoveCancel(): void;
|
|
53
|
+
destroy(): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare class InteractionBase implements IInteraction {
|
|
57
|
+
target: ILeaf;
|
|
58
|
+
canvas: IInteractionCanvas;
|
|
59
|
+
selector: ISelector;
|
|
60
|
+
running: boolean;
|
|
61
|
+
get dragging(): boolean;
|
|
62
|
+
get isDragEmpty(): boolean;
|
|
63
|
+
get isHoldRightKey(): boolean;
|
|
64
|
+
get moveMode(): boolean;
|
|
65
|
+
config: IInteractionConfig;
|
|
66
|
+
cursor: ICursorType | ICursorType[];
|
|
67
|
+
get hitRadius(): number;
|
|
68
|
+
shrinkCanvasBounds: IBounds;
|
|
69
|
+
downData: IPointerEvent;
|
|
70
|
+
protected oldDownData?: IPointerEvent;
|
|
71
|
+
hoverData: IPointerEvent;
|
|
72
|
+
downTime: number;
|
|
73
|
+
protected overPath: LeafList;
|
|
74
|
+
protected enterPath: LeafList;
|
|
75
|
+
protected waitMenuTap: boolean;
|
|
76
|
+
protected waitTap: boolean;
|
|
77
|
+
protected longPressTimer: ITimer;
|
|
78
|
+
protected longPressed: boolean;
|
|
79
|
+
protected tapCount: number;
|
|
80
|
+
protected tapTimer: ITimer;
|
|
81
|
+
protected dragger: Dragger;
|
|
82
|
+
protected transformer: Transformer;
|
|
83
|
+
protected __eventIds: IEventListenerId[];
|
|
84
|
+
protected defaultPath: ILeafList;
|
|
85
|
+
protected downKeyMap: IBooleanMap;
|
|
86
|
+
constructor(target: ILeaf, canvas: IInteractionCanvas, selector: ISelector, userConfig?: IInteractionConfig);
|
|
87
|
+
start(): void;
|
|
88
|
+
stop(): void;
|
|
89
|
+
receive(_event: any): void;
|
|
90
|
+
pointerDown(data?: IPointerEvent, useDefaultPath?: boolean): void;
|
|
91
|
+
pointerMove(data?: IPointerEvent): void;
|
|
92
|
+
pointerMoveReal(data: IPointerEvent): void;
|
|
93
|
+
pointerUp(data?: IPointerEvent): void;
|
|
94
|
+
pointerCancel(): void;
|
|
95
|
+
multiTouch(data: IUIEvent, list: IKeepTouchData[]): void;
|
|
96
|
+
menu(data: IPointerEvent): void;
|
|
97
|
+
menuTap(data: IPointerEvent): void;
|
|
98
|
+
move(data: IMoveEvent): void;
|
|
99
|
+
zoom(data: IZoomEvent): void;
|
|
100
|
+
rotate(data: IRotateEvent): void;
|
|
101
|
+
transformEnd(): void;
|
|
102
|
+
keyDown(data: IKeyEvent): void;
|
|
103
|
+
keyUp(data: IKeyEvent): void;
|
|
104
|
+
protected pointerOverOrOut(data: IPointerEvent): void;
|
|
105
|
+
protected pointerEnterOrLeave(data: IPointerEvent): void;
|
|
106
|
+
protected touchLeave(data: IPointerEvent): void;
|
|
107
|
+
protected tap(data: IPointerEvent): void;
|
|
108
|
+
findPath(data: IPointerEvent, options?: IPickOptions): ILeafList;
|
|
109
|
+
isDrag(leaf: ILeaf): boolean;
|
|
110
|
+
updateDownData(data?: IPointerEvent, options?: IPickOptions): void;
|
|
111
|
+
updateHoverData(data?: IPointerEvent): void;
|
|
112
|
+
updateCursor(data?: IPointerEvent): void;
|
|
113
|
+
setCursor(cursor: ICursorType | ICursorType[]): void;
|
|
114
|
+
protected emitTap(data: IPointerEvent): void;
|
|
115
|
+
protected emitDoubleTap(data: IPointerEvent): void;
|
|
116
|
+
pointerWaitCancel(): void;
|
|
117
|
+
protected tapWait(): void;
|
|
118
|
+
protected tapWaitCancel(): void;
|
|
119
|
+
protected longPressWait(data: IPointerEvent): void;
|
|
120
|
+
protected longTap(data: IPointerEvent): boolean;
|
|
121
|
+
protected longPressWaitCancel(): void;
|
|
122
|
+
protected __onResize(): void;
|
|
123
|
+
protected __listenEvents(): void;
|
|
124
|
+
protected __removeListenEvents(): void;
|
|
125
|
+
emit(type: string, data: IUIEvent, path?: ILeafList, excludePath?: ILeafList): void;
|
|
126
|
+
destroy(): void;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
declare const InteractionHelper: {
|
|
130
|
+
getMoveEventData(center: IPointData, move: IPointData, event: IEvent): IMoveEvent;
|
|
131
|
+
getRotateEventData(center: IPointData, angle: number, event: IEvent): IRotateEvent;
|
|
132
|
+
getZoomEventData(center: IPointData, scale: number, event: IEvent): IZoomEvent;
|
|
133
|
+
getDragEventData(startPoint: IPointData, lastPoint: IPointData, event: IPointerEvent): IDragEvent;
|
|
134
|
+
getDropEventData(event: IPointerEvent, list: ILeafList, data: IObject): IDropEvent;
|
|
135
|
+
getSwipeDirection(angle: number): string;
|
|
136
|
+
getSwipeEventData(startPoint: IPointData, lastDragData: IDragEvent, event: IPointerEvent): ISwipeEvent;
|
|
137
|
+
getBase(e: IObject): IUIEvent;
|
|
138
|
+
pathHasEventType(path: ILeafList, type: string): boolean;
|
|
139
|
+
filterPathByEventType(path: ILeafList, type: string): ILeafList;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
declare const MultiTouchHelper: {
|
|
143
|
+
getData(list: IKeepTouchData[]): IMultiTouchData;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
declare class Cursor {
|
|
147
|
+
static custom: ICursorTypeMap;
|
|
148
|
+
static set(name: string, value: ICursorType | ICursorType[]): void;
|
|
149
|
+
static get(name: string): ICursorType | ICursorType[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
declare class HitCanvasManager extends CanvasManager implements IHitCanvasManager {
|
|
153
|
+
protected pathTypeList: ILeafList;
|
|
154
|
+
protected imageTypeList: ILeafList;
|
|
155
|
+
getImageType(leaf: ILeaf, size: IScreenSizeData): IHitCanvas;
|
|
156
|
+
getPathType(leaf: ILeaf): IHitCanvas;
|
|
157
|
+
clearImageType(): void;
|
|
158
|
+
clearPathType(): void;
|
|
159
|
+
protected __clearLeafList(leafList: ILeafList): void;
|
|
160
|
+
clear(): void;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { Cursor, HitCanvasManager, InteractionBase, InteractionHelper, MultiTouchHelper };
|