@leafer-in/linker 2.3.0
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 +40 -0
- package/src/Linker.ts +355 -0
- package/src/data/LinkerData.ts +11 -0
- package/src/decorator.ts +18 -0
- package/src/helper/LinkerHelper.ts +207 -0
- package/src/helper/drawPoints.ts +283 -0
- package/src/index.ts +10 -0
- package/types/index.d.ts +33 -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-in/linker
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leafer-in/linker",
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "@leafer-in/linker",
|
|
5
|
+
"author": "Chao (Leafer) Wan",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/linker.esm.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
"import": "./dist/linker.esm.js",
|
|
11
|
+
"require": "./dist/linker.cjs",
|
|
12
|
+
"types": "./types/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"types": "types/index.d.ts",
|
|
15
|
+
"unpkg": "dist/linker.js",
|
|
16
|
+
"jsdelivr": "dist/linker.js",
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"types",
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/leaferjs/leafer-in.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/leaferjs/leafer-in/tree/main/packages/linker",
|
|
27
|
+
"bugs": "https://github.com/leaferjs/leafer-in/issues",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"leafer linker",
|
|
30
|
+
"leafer-linker",
|
|
31
|
+
"leafer-in",
|
|
32
|
+
"linker",
|
|
33
|
+
"leafer-ui",
|
|
34
|
+
"leaferjs"
|
|
35
|
+
],
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@leafer-ui/draw": "^2.0.0",
|
|
38
|
+
"@leafer-ui/interface": "^2.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/Linker.ts
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { ILinker, ILinkerInputData, ILinkerData, ILinkerPointData, ILinkerPointOptions, ILinkerComputedPointData, ILinkerPointType, IPointData, IUI, IDirection4, ILeaf, IPointsCurve, IPointsCurveData } from '@leafer-ui/interface'
|
|
2
|
+
import { registerUI, dataProcessor, PathCommandDataHelper, pathType, isString, BoundsEvent, Line, PointHelper, BoundsHelper, LeafHelper, isObject } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
import { LinkerData } from './data/LinkerData'
|
|
5
|
+
import { LinkerHelper } from './helper/LinkerHelper'
|
|
6
|
+
import { linkerPointType } from './decorator'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const { moveTo, bezierCurveTo, drawPoints } = PathCommandDataHelper
|
|
10
|
+
const { isEmpty, getPoint } = BoundsHelper
|
|
11
|
+
const { getDistance } = PointHelper
|
|
12
|
+
const { getCtrlPoint, getDirectionForFromPoint, isOppositeDirection } = LinkerHelper
|
|
13
|
+
const { max, min, abs } = Math
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@registerUI()
|
|
17
|
+
export class Linker extends Line implements ILinker {
|
|
18
|
+
|
|
19
|
+
public get __tag() { return 'Linker' }
|
|
20
|
+
|
|
21
|
+
@dataProcessor(LinkerData)
|
|
22
|
+
declare public __: ILinkerData
|
|
23
|
+
|
|
24
|
+
@linkerPointType()
|
|
25
|
+
public startPoint?: ILinkerPointData
|
|
26
|
+
|
|
27
|
+
@linkerPointType()
|
|
28
|
+
public endPoint?: ILinkerPointData
|
|
29
|
+
|
|
30
|
+
@pathType({ type: 'C', value: 0.5 } as IPointsCurveData)
|
|
31
|
+
declare public curve?: IPointsCurve
|
|
32
|
+
|
|
33
|
+
public startNode: IUI
|
|
34
|
+
public endNode: IUI
|
|
35
|
+
|
|
36
|
+
public startData: ILinkerComputedPointData
|
|
37
|
+
public endData: ILinkerComputedPointData
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
constructor(data?: ILinkerInputData) {
|
|
41
|
+
super(data)
|
|
42
|
+
this.onStartNodeChange = this.onStartNodeChange.bind(this)
|
|
43
|
+
this.onEndNodeChange = this.onEndNodeChange.bind(this)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public __updatePath(): void {
|
|
47
|
+
|
|
48
|
+
const layout = this.__layout
|
|
49
|
+
|
|
50
|
+
const { startNode, endNode } = this
|
|
51
|
+
|
|
52
|
+
if (layout.linkerChanged) {
|
|
53
|
+
this.updateLinkerNode('start')
|
|
54
|
+
this.updateLinkerNode('end')
|
|
55
|
+
|
|
56
|
+
this.updateLinkerPoint('start')
|
|
57
|
+
this.updateLinkerPoint('end')
|
|
58
|
+
|
|
59
|
+
if (startNode !== this.startNode) this.listenLinkerNode(this.startNode, startNode, 'start')
|
|
60
|
+
if (endNode !== this.endNode) this.listenLinkerNode(this.endNode, endNode, 'end')
|
|
61
|
+
|
|
62
|
+
layout.linkerChanged = false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.updateLinkerPath()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public listenLinkerNode(newNode: IUI, oldNode: IUI, type: ILinkerPointType): void {
|
|
69
|
+
if (oldNode) {
|
|
70
|
+
oldNode.off(BoundsEvent.WORLD, type === 'end' ? this.onEndNodeChange : this.onStartNodeChange)
|
|
71
|
+
const list = type === 'end' ? oldNode.endLinker : oldNode.startLinker
|
|
72
|
+
if (list) {
|
|
73
|
+
const index = list.indexOf(this)
|
|
74
|
+
if (index > -1) list.splice(index, 1)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (newNode) {
|
|
78
|
+
newNode.on(BoundsEvent.WORLD, type === 'end' ? this.onEndNodeChange : this.onStartNodeChange)
|
|
79
|
+
const list = type === 'end' ? (newNode.endLinker || (newNode.endLinker = [])) : (newNode.startLinker || (newNode.startLinker = []))
|
|
80
|
+
list.push(this)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public onStartNodeChange(): void {
|
|
85
|
+
this.onLinkerNodeChange('start')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public onEndNodeChange(): void {
|
|
89
|
+
this.onLinkerNodeChange('end')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public onLinkerNodeChange(type: ILinkerPointType): void {
|
|
93
|
+
if (this.leaferIsCreated) {
|
|
94
|
+
const { layouter, zoomLayer } = this.leafer
|
|
95
|
+
if (layouter.__updatedList.has(zoomLayer)) return // 节流,画布缩放平移不用更新
|
|
96
|
+
layouter.addExtra(this, true)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const { startData, endData } = this
|
|
100
|
+
if (type == 'start') {
|
|
101
|
+
this.updateLinkerPoint('start')
|
|
102
|
+
if (endData && endData.auto) this.updateLinkerPoint('end')
|
|
103
|
+
} else {
|
|
104
|
+
if (startData && startData.auto) this.updateLinkerPoint('start')
|
|
105
|
+
this.updateLinkerPoint('end')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.__layout.boxChange()
|
|
109
|
+
LeafHelper.updateBounds(this)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public updateLinkerNode(type: ILinkerPointType) {
|
|
113
|
+
const { startPoint, endPoint } = this
|
|
114
|
+
const nowPoint = type === 'end' ? endPoint : startPoint
|
|
115
|
+
if (nowPoint) {
|
|
116
|
+
const { id } = nowPoint
|
|
117
|
+
const ui = id && (isString(id) ? this.app.findId(id) : id)
|
|
118
|
+
type === 'end' ? this.endNode = ui : this.startNode = ui
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public getLinkerNodeCenter(type: ILinkerPointType): IPointData {
|
|
123
|
+
const { startPoint, endPoint, startNode, endNode, startData, endData } = this
|
|
124
|
+
const node = type === 'end' ? endNode : startNode
|
|
125
|
+
const data = type === 'end' ? (endData || endPoint) : (startData || startPoint)
|
|
126
|
+
if (node) {
|
|
127
|
+
const { worldRenderBounds } = node
|
|
128
|
+
if (isEmpty(worldRenderBounds)) LeafHelper.updateBounds(node) // 防止线条位于元素底部,先于元素布局
|
|
129
|
+
return getPoint(node.worldRenderBounds, 'center')
|
|
130
|
+
} else {
|
|
131
|
+
return data && isObject(data.point) && this.getWorldPointByLocal(data.point)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public updateLinkerPoint(type: ILinkerPointType) {
|
|
136
|
+
const { startPoint, endPoint } = this
|
|
137
|
+
const nowPoint = type === 'end' ? endPoint : startPoint
|
|
138
|
+
if (nowPoint) {
|
|
139
|
+
const data = {} as ILinkerComputedPointData
|
|
140
|
+
const { id, offset } = nowPoint
|
|
141
|
+
let { direction, point } = nowPoint
|
|
142
|
+
|
|
143
|
+
if (direction === 'center' || (!direction && id && !point)) {
|
|
144
|
+
const start = this.getLinkerNodeCenter('start')
|
|
145
|
+
const end = this.getLinkerNodeCenter('end')
|
|
146
|
+
const from = type === 'end' ? end : start
|
|
147
|
+
const to = type === 'end' ? start : end
|
|
148
|
+
if (from && to) direction = getDirectionForFromPoint(from, to)
|
|
149
|
+
data.auto = true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if (!direction && isString(point)) direction = point as any as IDirection4
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
data.direction = direction
|
|
157
|
+
|
|
158
|
+
const ui = type === 'end' ? this.endNode : this.startNode
|
|
159
|
+
|
|
160
|
+
let p: IPointData
|
|
161
|
+
|
|
162
|
+
if (ui) {
|
|
163
|
+
if (!point) point = direction
|
|
164
|
+
if (point) {
|
|
165
|
+
const { boxBounds } = ui
|
|
166
|
+
|
|
167
|
+
if (isEmpty(boxBounds)) LeafHelper.updateBounds(ui) // 防止线条位于元素底部,先于元素布局
|
|
168
|
+
p = getPoint(boxBounds, point, true)
|
|
169
|
+
ui.getWorldPointByBox(p, this, false, true)
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
if (isObject(point)) p = point
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (p && offset) {
|
|
176
|
+
const o = { ...offset } as IPointData
|
|
177
|
+
if (!offset.x) o.x = 0
|
|
178
|
+
if (!offset.y) o.y = 0
|
|
179
|
+
|
|
180
|
+
if (ui) ui.getWorldPoint(o, this, true, true)
|
|
181
|
+
PointHelper.move(p, o)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
data.point = p
|
|
185
|
+
|
|
186
|
+
type === 'end' ? this.endData = p && data : this.startData = p && data
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
public updateLinkerPath() {
|
|
193
|
+
const data = this.__
|
|
194
|
+
const path: number[] = data.path = []
|
|
195
|
+
|
|
196
|
+
const hasPoints = data.points && data.points.length
|
|
197
|
+
|
|
198
|
+
const { startData, endData, startNode, endNode } = this
|
|
199
|
+
if (!startData || !endData) {
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const startPoint = startData.point, endPoint = endData.point
|
|
204
|
+
const startDirection = startData.direction, endDirection = endData.direction
|
|
205
|
+
|
|
206
|
+
const half: IPointData = {
|
|
207
|
+
x: abs((startPoint.x - endPoint.x) / 2),
|
|
208
|
+
y: abs((startPoint.y - endPoint.y) / 2)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
const arrowWidth = this.__.strokeWidth * 8
|
|
213
|
+
|
|
214
|
+
let minCtrl: number
|
|
215
|
+
|
|
216
|
+
if (this.__.__useArrow) {
|
|
217
|
+
const ratio = min(getDistance(startPoint, endPoint) / (arrowWidth * 25), 1) // 控制add 手柄的长度与两点间的距离有关
|
|
218
|
+
minCtrl = max(arrowWidth * ratio * 12, arrowWidth * 3,) // 控制手柄最少不能低于3个箭头的宽度
|
|
219
|
+
} else {
|
|
220
|
+
minCtrl = arrowWidth * 2
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (endDirection) {
|
|
224
|
+
|
|
225
|
+
if (isOppositeDirection(startDirection, endDirection) && startDirection === getDirectionForFromPoint(startPoint, endPoint)) // 正好处于相反的位置, 不需要太长的控制手柄
|
|
226
|
+
{
|
|
227
|
+
minCtrl = arrowWidth * 2
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
half.x = max(minCtrl, half.x)
|
|
233
|
+
half.y = max(minCtrl, half.y)
|
|
234
|
+
|
|
235
|
+
if (hasPoints) {
|
|
236
|
+
minCtrl *= 0.6
|
|
237
|
+
const points = PointHelper.toNumberPoints(data.points)
|
|
238
|
+
drawPoints(path, [startPoint.x, startPoint.y, ...points, endPoint.x, endPoint.y], data.curve, false, data)
|
|
239
|
+
|
|
240
|
+
if (startDirection) {
|
|
241
|
+
const handleX = path[4]
|
|
242
|
+
const handleY = path[5]
|
|
243
|
+
|
|
244
|
+
half.x = max(minCtrl, abs(handleX - startPoint.x))
|
|
245
|
+
half.y = max(minCtrl, abs(handleY - startPoint.y))
|
|
246
|
+
const startCtrlPoint: IPointData = getCtrlPoint(this, startNode, startPoint, startDirection, half)
|
|
247
|
+
path[4] = startCtrlPoint.x
|
|
248
|
+
path[5] = startCtrlPoint.y
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const len = path.length
|
|
252
|
+
if (endDirection) {
|
|
253
|
+
|
|
254
|
+
const handleX = path[len - 4]
|
|
255
|
+
const handleY = path[len - 3]
|
|
256
|
+
|
|
257
|
+
half.x = max(minCtrl, abs(handleX - endPoint.x))
|
|
258
|
+
half.y = max(minCtrl, abs(handleY - endPoint.y))
|
|
259
|
+
const endCtrlPoint: IPointData = getCtrlPoint(this, endNode, endPoint, endDirection, half)
|
|
260
|
+
path[len - 4] = endCtrlPoint.x
|
|
261
|
+
path[len - 3] = endCtrlPoint.y
|
|
262
|
+
} else {
|
|
263
|
+
path[len - 4] = endPoint.x
|
|
264
|
+
path[len - 3] = endPoint.y
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
} else {
|
|
268
|
+
|
|
269
|
+
const startCtrlPoint: IPointData = startDirection ? getCtrlPoint(this, startNode, startPoint, startDirection, half) : startPoint
|
|
270
|
+
const endCtrlPoint: IPointData = endDirection ? getCtrlPoint(this, endNode, endPoint, endDirection, half) : endPoint
|
|
271
|
+
|
|
272
|
+
moveTo(path, startPoint.x, startPoint.y)
|
|
273
|
+
bezierCurveTo(path, startCtrlPoint.x, startCtrlPoint.y, endCtrlPoint.x, endCtrlPoint.y, endPoint.x, endPoint.y)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
public createStartPoint(worldPoint: IPointData, node?: ILeaf, options?: ILinkerPointOptions): ILinkerPointData {
|
|
280
|
+
const data = this.createLinkerPoint(worldPoint, node, options)
|
|
281
|
+
this.startPoint = data
|
|
282
|
+
return data
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public createEndPoint(worldPoint: IPointData, node?: ILeaf, options?: ILinkerPointOptions): ILinkerPointData {
|
|
286
|
+
const data = this.createLinkerPoint(worldPoint, node, options)
|
|
287
|
+
this.endPoint = data
|
|
288
|
+
return data
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public createLinkerPoint(worldPoint: IPointData, node?: ILeaf, options?: ILinkerPointOptions): ILinkerPointData {
|
|
292
|
+
const data: ILinkerPointData = {}
|
|
293
|
+
if (node && node.id) {
|
|
294
|
+
const point = node.getBoxPoint(worldPoint)
|
|
295
|
+
const { boxBounds } = node
|
|
296
|
+
|
|
297
|
+
const mode = options && options.mode
|
|
298
|
+
|
|
299
|
+
const edge = mode === 'edge' || !mode
|
|
300
|
+
const four = mode === 'four'
|
|
301
|
+
const auto = mode === 'auto'
|
|
302
|
+
const free = mode === 'free'
|
|
303
|
+
|
|
304
|
+
if (!auto) {
|
|
305
|
+
|
|
306
|
+
if (four) {
|
|
307
|
+
data.point = LinkerHelper.getDirection(point, boxBounds, four)
|
|
308
|
+
} else {
|
|
309
|
+
data.direction = LinkerHelper.getDirection(point, boxBounds, edge)
|
|
310
|
+
data.point = LinkerHelper.getPointDirection(point, boxBounds, edge) || data.direction
|
|
311
|
+
if (data.point === data.direction) delete data.direction
|
|
312
|
+
|
|
313
|
+
if (free || edge) {
|
|
314
|
+
const snapPoint = BoundsHelper.getPoint(boxBounds, data.point)
|
|
315
|
+
|
|
316
|
+
const offsetX = point.x - snapPoint.x
|
|
317
|
+
const offsetY = point.y - snapPoint.y
|
|
318
|
+
|
|
319
|
+
const snapRadius = (options && options.snapRadius) || 10
|
|
320
|
+
|
|
321
|
+
if (Math.abs(offsetX) > snapRadius || Math.abs(offsetY) > snapRadius) {
|
|
322
|
+
const offset = data.offset = {
|
|
323
|
+
x: Math.abs(offsetX) > snapRadius ? offsetX : 0,
|
|
324
|
+
y: Math.abs(offsetY) > snapRadius ? offsetY : 0
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (edge && (offset.x || offset.y)) {
|
|
328
|
+
switch (data.direction || data.point) {
|
|
329
|
+
case 'top':
|
|
330
|
+
case 'bottom':
|
|
331
|
+
offset.y = 0; break
|
|
332
|
+
case 'right':
|
|
333
|
+
case 'left':
|
|
334
|
+
offset.x = 0; break
|
|
335
|
+
default:
|
|
336
|
+
if (offset.x > offset.y) offset.y = 0
|
|
337
|
+
else offset.x = 0
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
data.id = node.id
|
|
348
|
+
} else {
|
|
349
|
+
data.point = this.getLocalPoint(worldPoint)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return data
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ILinkerData } from "@leafer-ui/interface"
|
|
2
|
+
import { LineData } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class LinkerData extends LineData implements ILinkerData {
|
|
6
|
+
|
|
7
|
+
public get __usePathBox(): boolean {
|
|
8
|
+
return ((this as ILinkerData).path || (this as ILinkerData).__pathInputed) as any as boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
}
|
package/src/decorator.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IValue } from '@leafer-ui/interface'
|
|
2
|
+
import { decorateLeafAttr, attr, doBoundsType, doStrokeType } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function linkerPointType(defaultValue?: IValue) {
|
|
6
|
+
return decorateLeafAttr(defaultValue, (key: string) => attr({
|
|
7
|
+
set(value: IValue) {
|
|
8
|
+
if (this.__setAttr(key, value)) {
|
|
9
|
+
const layout = this.__layout
|
|
10
|
+
if (!layout.linkerChanged) {
|
|
11
|
+
doBoundsType(this)
|
|
12
|
+
doStrokeType(this)
|
|
13
|
+
layout.linkerChanged = true
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}))
|
|
18
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { IBoundsData, IDirection, IDirection4, ILinker, IPointData, IUI } from '@leafer-ui/interface'
|
|
2
|
+
import { AroundHelper, BoundsHelper, Direction9, PointHelper } from '@leafer-ui/draw'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const { directionData } = AroundHelper
|
|
6
|
+
|
|
7
|
+
const directionNames: IDirection[] = [
|
|
8
|
+
'top-left',
|
|
9
|
+
'top',
|
|
10
|
+
'top-right',
|
|
11
|
+
'right',
|
|
12
|
+
'bottom-right',
|
|
13
|
+
'bottom',
|
|
14
|
+
'bottom-left',
|
|
15
|
+
'left',
|
|
16
|
+
'center'
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
export const LinkerHelper = {
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 根据箭头方向获取控制点
|
|
23
|
+
*/
|
|
24
|
+
getCtrlPoint(linker: ILinker, node: IUI, point: IPointData, direction: IDirection, ctrl: IPointData, offsetAngle?: number): IPointData {
|
|
25
|
+
|
|
26
|
+
if (node) {
|
|
27
|
+
point = node.getBoxPoint(point, linker)
|
|
28
|
+
// half 需要 / startNode.scale
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let ctrlPoint
|
|
32
|
+
switch (direction) {
|
|
33
|
+
case 'left':
|
|
34
|
+
case 'top-left':
|
|
35
|
+
case 'bottom-left':
|
|
36
|
+
ctrlPoint = {
|
|
37
|
+
x: point.x - ctrl.x,
|
|
38
|
+
y: point.y
|
|
39
|
+
}
|
|
40
|
+
break
|
|
41
|
+
case 'top':
|
|
42
|
+
ctrlPoint = {
|
|
43
|
+
x: point.x,
|
|
44
|
+
y: point.y - ctrl.y
|
|
45
|
+
}
|
|
46
|
+
break
|
|
47
|
+
case 'right':
|
|
48
|
+
case 'top-right':
|
|
49
|
+
case 'bottom-right':
|
|
50
|
+
ctrlPoint = {
|
|
51
|
+
x: point.x + ctrl.x,
|
|
52
|
+
y: point.y
|
|
53
|
+
}
|
|
54
|
+
break
|
|
55
|
+
case 'bottom':
|
|
56
|
+
ctrlPoint = {
|
|
57
|
+
x: point.x,
|
|
58
|
+
y: point.y + ctrl.y
|
|
59
|
+
}
|
|
60
|
+
break
|
|
61
|
+
default:
|
|
62
|
+
ctrlPoint = {
|
|
63
|
+
x: point.x,
|
|
64
|
+
y: point.y
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (offsetAngle) { // 旋转角度
|
|
68
|
+
offsetAngle = offsetAngle / 180 * Math.PI
|
|
69
|
+
ctrlPoint = {
|
|
70
|
+
x: (ctrlPoint.x - point.x) * Math.cos(offsetAngle) - (ctrlPoint.y - point.y) * Math.sin(offsetAngle) + point.x,
|
|
71
|
+
y: (ctrlPoint.x - point.x) * Math.sin(offsetAngle) + (ctrlPoint.y - point.y) * Math.cos(offsetAngle) + point.y
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (node) node.getWorldPointByBox(ctrlPoint, linker, undefined, true)
|
|
76
|
+
|
|
77
|
+
return ctrlPoint
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// 是否为相反方向
|
|
81
|
+
isOppositeDirection(from: IDirection, to: IDirection): boolean {
|
|
82
|
+
switch (from) {
|
|
83
|
+
case 'left':
|
|
84
|
+
return to == 'right'
|
|
85
|
+
case 'top':
|
|
86
|
+
return to == 'bottom'
|
|
87
|
+
case 'right':
|
|
88
|
+
return to == 'left'
|
|
89
|
+
case 'bottom':
|
|
90
|
+
return to == 'top'
|
|
91
|
+
case 'center':
|
|
92
|
+
return to == 'center'
|
|
93
|
+
default:
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 获取to 相对于 from 的方向
|
|
100
|
+
* @param from
|
|
101
|
+
* @param to
|
|
102
|
+
* @returns
|
|
103
|
+
*/
|
|
104
|
+
getDirectionForFromPoint(from: IPointData, to: IPointData): IDirection4 {
|
|
105
|
+
const a = from.x - to.x
|
|
106
|
+
const b = from.y - to.y
|
|
107
|
+
|
|
108
|
+
let direction: IDirection
|
|
109
|
+
let angle = (180 * Math.atan2(b, a)) / Math.PI
|
|
110
|
+
|
|
111
|
+
if (angle < 0) angle = 360 + angle
|
|
112
|
+
|
|
113
|
+
if (angle > 315 || angle < 45) {
|
|
114
|
+
direction = 'left'
|
|
115
|
+
} else if (angle > 45 && angle < 135) {
|
|
116
|
+
direction = 'top'
|
|
117
|
+
} else if (angle > 135 && angle < 225) {
|
|
118
|
+
direction = 'right'
|
|
119
|
+
} else {
|
|
120
|
+
direction = 'bottom'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return direction
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
getRotateDirection(direction: IDirection, rotation: number, totalDirection = 8): IDirection {
|
|
127
|
+
let directionIndex = Direction9[direction]
|
|
128
|
+
directionIndex = (directionIndex + Math.round(rotation / (360 / totalDirection))) % totalDirection
|
|
129
|
+
if (directionIndex < 0) directionIndex += totalDirection
|
|
130
|
+
return directionNames[directionIndex]
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
getDirection,
|
|
134
|
+
|
|
135
|
+
getPointDirection,
|
|
136
|
+
|
|
137
|
+
autoRotatePoint(point: IPointData, origin: IPointData, ui: IUI): void {
|
|
138
|
+
const { rotation, scaleX, scaleY } = ui
|
|
139
|
+
const oneFlip = scaleX * scaleY < 0
|
|
140
|
+
if (rotation || oneFlip) PointHelper.rotate(point, oneFlip ? -rotation : rotation, origin)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
function getPointDirection(boxPoint: IPointData, bounds: IBoundsData, eight?: boolean): IDirection {
|
|
148
|
+
const { width, height } = bounds
|
|
149
|
+
const hitRadiusX = width / 4
|
|
150
|
+
const hitRadiusY = height / 4
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < directionData.length; i++) {
|
|
153
|
+
const unit = directionData[i]
|
|
154
|
+
// 百分比坐标转像素
|
|
155
|
+
const px = unit.x * width
|
|
156
|
+
const py = unit.y * height
|
|
157
|
+
|
|
158
|
+
const dx = boxPoint.x - px
|
|
159
|
+
const dy = boxPoint.y - py
|
|
160
|
+
|
|
161
|
+
if (Math.abs(dx) <= hitRadiusX && Math.abs(dy) <= hitRadiusY) {
|
|
162
|
+
if (eight) {
|
|
163
|
+
if (directionNames[i] !== 'center') return directionNames[i]
|
|
164
|
+
} else return directionNames[i]
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return undefined
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
function getDirection(boxPoint: IPointData, bounds: IBoundsData, four?: boolean): IDirection4 | 'center' {
|
|
173
|
+
const { width, height } = bounds
|
|
174
|
+
const center = BoundsHelper.getPoint(bounds, 'center', true)
|
|
175
|
+
// 将元素沿着对角线切成4块,匹配对应上下左右四个方向
|
|
176
|
+
// 转为以中心为原点的坐标
|
|
177
|
+
const dx = boxPoint.x - center.x
|
|
178
|
+
const dy = boxPoint.y - center.y
|
|
179
|
+
|
|
180
|
+
if (!four) {
|
|
181
|
+
// 中心点
|
|
182
|
+
if (Math.abs(dx) < width / 4 && Math.abs(dy) < height / 4) return 'center'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ----------------------------------------
|
|
186
|
+
// 将矩形按比例缩放到正方形空间
|
|
187
|
+
//
|
|
188
|
+
// 等价于比较:
|
|
189
|
+
// |dx| / width 和 |dy| / height
|
|
190
|
+
//
|
|
191
|
+
// 但为了避免除法,改为:
|
|
192
|
+
// |dx| * height 和 |dy| * width
|
|
193
|
+
//
|
|
194
|
+
// 这样可以严格按矩形的两条对角线划分区域
|
|
195
|
+
// ----------------------------------------
|
|
196
|
+
|
|
197
|
+
const ax = Math.abs(dx) * height
|
|
198
|
+
const ay = Math.abs(dy) * width
|
|
199
|
+
|
|
200
|
+
// 左右区域
|
|
201
|
+
if (ax > ay) {
|
|
202
|
+
return dx > 0 ? 'right' : 'left'
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 上下区域
|
|
206
|
+
return dy > 0 ? 'bottom' : 'top'
|
|
207
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { IPointData, IPathCommandData } from '@leafer-ui/interface'
|
|
2
|
+
|
|
3
|
+
import { PathCommandMap, PointHelper } from '@leafer-ui/core'
|
|
4
|
+
|
|
5
|
+
const { toNumberPoints } = PointHelper
|
|
6
|
+
const { M, L, C, Z } = PathCommandMap
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 使用角平分线自动生成平滑 Bezier 曲线
|
|
11
|
+
*
|
|
12
|
+
* 算法特点:
|
|
13
|
+
*
|
|
14
|
+
* 1. Handle 方向:
|
|
15
|
+
* 使用前后方向的角平分线
|
|
16
|
+
*
|
|
17
|
+
* 2. Handle 长度:
|
|
18
|
+
* 左右分别根据相邻边长度计算
|
|
19
|
+
*
|
|
20
|
+
* in = AB * curve
|
|
21
|
+
* out = BC * curve
|
|
22
|
+
*
|
|
23
|
+
* 3. 自动曲率控制:
|
|
24
|
+
*
|
|
25
|
+
* - 接近平角:
|
|
26
|
+
* 降低 handle,避免直线中间产生鼓包
|
|
27
|
+
*
|
|
28
|
+
* - 接近尖角:
|
|
29
|
+
* 降低 handle,避免曲线过冲
|
|
30
|
+
*
|
|
31
|
+
* 4. 性能:
|
|
32
|
+
*
|
|
33
|
+
* - O(n)
|
|
34
|
+
* - 无对象创建
|
|
35
|
+
* - 无临时数组
|
|
36
|
+
* - 适合大规模路径
|
|
37
|
+
*
|
|
38
|
+
*
|
|
39
|
+
* curve:
|
|
40
|
+
*
|
|
41
|
+
* 0 直线
|
|
42
|
+
* 0.5 推荐
|
|
43
|
+
* 1 最大平滑
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 使用角平分线法线生成自动 Bezier 曲线
|
|
48
|
+
*
|
|
49
|
+
* 算法:
|
|
50
|
+
*
|
|
51
|
+
* 1. 通过 BA / BC 求角平分方向
|
|
52
|
+
* 2. 旋转 90° 得到曲线切线方向
|
|
53
|
+
* 3. 左右 handle 长度分别根据两侧距离计算
|
|
54
|
+
*
|
|
55
|
+
* handle:
|
|
56
|
+
*
|
|
57
|
+
* C2
|
|
58
|
+
* |
|
|
59
|
+
* |
|
|
60
|
+
* A ---- B ---- C
|
|
61
|
+
* |
|
|
62
|
+
* |
|
|
63
|
+
* C1
|
|
64
|
+
*
|
|
65
|
+
*
|
|
66
|
+
* curve:
|
|
67
|
+
*
|
|
68
|
+
* 0 直线
|
|
69
|
+
* 0.5 推荐
|
|
70
|
+
* 1 最大曲率
|
|
71
|
+
*/
|
|
72
|
+
export function drawPoints(data: IPathCommandData, originPoints: number[] | IPointData[], curve?: boolean | number, close?: boolean): void {
|
|
73
|
+
const points = toNumberPoints(originPoints)
|
|
74
|
+
const count = points.length >> 1
|
|
75
|
+
|
|
76
|
+
if (count < 2) return
|
|
77
|
+
|
|
78
|
+
data.push(M, points[0], points[1])
|
|
79
|
+
|
|
80
|
+
if (!curve || count < 3) {
|
|
81
|
+
|
|
82
|
+
for (let i = 1; i < count; i++) {
|
|
83
|
+
|
|
84
|
+
data.push(L, points[i * 2], points[i * 2 + 1])
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (close) data.push(Z)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let tension = curve === true ? 0.5 : curve as number
|
|
92
|
+
|
|
93
|
+
if (tension < 0) tension = 0
|
|
94
|
+
else if (tension > 1) tension = 1
|
|
95
|
+
|
|
96
|
+
const end = close ? count : count - 1
|
|
97
|
+
|
|
98
|
+
let lastOutX = points[0]
|
|
99
|
+
let lastOutY = points[1]
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 非闭合路径第一个点:
|
|
103
|
+
* 使用第一条边方向作为初始切线
|
|
104
|
+
*/
|
|
105
|
+
{
|
|
106
|
+
|
|
107
|
+
const dx = points[2] - points[0]
|
|
108
|
+
const dy = points[3] - points[1]
|
|
109
|
+
|
|
110
|
+
const len = Math.sqrt(dx * dx + dy * dy)
|
|
111
|
+
|
|
112
|
+
if (len) {
|
|
113
|
+
lastOutX = points[0] + dx / len * len * tension
|
|
114
|
+
lastOutY = points[1] + dy / len * len * tension
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < end; i++) {
|
|
120
|
+
const index = i + 1 === count ? 0 : i + 1
|
|
121
|
+
|
|
122
|
+
const bx = points[index * 2]
|
|
123
|
+
const by = points[index * 2 + 1]
|
|
124
|
+
|
|
125
|
+
const prev = index === 0 ? count - 1 : index - 1
|
|
126
|
+
const next = index + 1 === count ? 0 : index + 1
|
|
127
|
+
|
|
128
|
+
const ax = points[prev * 2]
|
|
129
|
+
const ay = points[prev * 2 + 1]
|
|
130
|
+
|
|
131
|
+
const cx = points[next * 2]
|
|
132
|
+
const cy = points[next * 2 + 1]
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* BA
|
|
136
|
+
*/
|
|
137
|
+
let bax = ax - bx
|
|
138
|
+
let bay = ay - by
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* BC
|
|
142
|
+
*/
|
|
143
|
+
let bcx = cx - bx
|
|
144
|
+
let bcy = cy - by
|
|
145
|
+
|
|
146
|
+
const ab = Math.sqrt(bax * bax + bay * bay)
|
|
147
|
+
const bc = Math.sqrt(bcx * bcx + bcy * bcy)
|
|
148
|
+
|
|
149
|
+
let inX = bx
|
|
150
|
+
let inY = by
|
|
151
|
+
|
|
152
|
+
let outX = bx
|
|
153
|
+
let outY = by
|
|
154
|
+
|
|
155
|
+
if (ab || bc) {
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 计算角平分方向
|
|
159
|
+
*/
|
|
160
|
+
let ux = 0
|
|
161
|
+
let uy = 0
|
|
162
|
+
|
|
163
|
+
if (ab) {
|
|
164
|
+
ux += bax / ab
|
|
165
|
+
uy += bay / ab
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (bc) {
|
|
169
|
+
ux += bcx / bc
|
|
170
|
+
uy += bcy / bc
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 角平分线长度
|
|
175
|
+
*/
|
|
176
|
+
let uLen = Math.sqrt(ux * ux + uy * uy)
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 180度:
|
|
180
|
+
* 两个方向抵消
|
|
181
|
+
*
|
|
182
|
+
* 使用 BC 方向
|
|
183
|
+
*/
|
|
184
|
+
if (!uLen) {
|
|
185
|
+
|
|
186
|
+
if (bc) {
|
|
187
|
+
ux = bcx / bc
|
|
188
|
+
uy = bcy / bc
|
|
189
|
+
|
|
190
|
+
} else {
|
|
191
|
+
ux = bax / ab
|
|
192
|
+
uy = bay / ab
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
uLen = 1
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
ux /= uLen
|
|
200
|
+
uy /= uLen
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 角平分线旋转90度
|
|
204
|
+
*
|
|
205
|
+
* 得到曲线切线方向
|
|
206
|
+
*/
|
|
207
|
+
let tx = -uy
|
|
208
|
+
let ty = ux
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 保证方向朝向下一点
|
|
212
|
+
*/
|
|
213
|
+
if (bc) {
|
|
214
|
+
if (tx * bcx + ty * bcy < 0) {
|
|
215
|
+
tx = -tx
|
|
216
|
+
ty = -ty
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 计算夹角
|
|
222
|
+
*/
|
|
223
|
+
let cos = 0
|
|
224
|
+
|
|
225
|
+
if (ab && bc) {
|
|
226
|
+
cos = (bax * bcx + bay * bcy) / (ab * bc)
|
|
227
|
+
if (cos > 1) cos = 1
|
|
228
|
+
else if (cos < -1) cos = -1
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* 曲率限制:
|
|
233
|
+
*
|
|
234
|
+
* 平角降低
|
|
235
|
+
* 尖角降低
|
|
236
|
+
*/
|
|
237
|
+
let angleFactor = Math.sqrt(Math.sqrt(Math.max(0, 1 - cos * cos)))
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* 保留少量曲率,
|
|
241
|
+
* 避免完全退化
|
|
242
|
+
*/
|
|
243
|
+
if (angleFactor < 0.15) angleFactor = 0.15
|
|
244
|
+
|
|
245
|
+
let inLength = ab * tension * angleFactor
|
|
246
|
+
let outLength = bc * tension * angleFactor
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 防止 handle 过长
|
|
250
|
+
*/
|
|
251
|
+
const maxLength = Math.min(ab || bc, bc || ab) * 0.5
|
|
252
|
+
|
|
253
|
+
if (inLength > maxLength) inLength = maxLength
|
|
254
|
+
if (outLength > maxLength) outLength = maxLength
|
|
255
|
+
|
|
256
|
+
inX = bx - tx * inLength
|
|
257
|
+
inY = by - ty * inLength
|
|
258
|
+
|
|
259
|
+
outX = bx + tx * outLength
|
|
260
|
+
outY = by + ty * outLength
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
data.push(C,
|
|
264
|
+
|
|
265
|
+
// 上一个节点 handle out
|
|
266
|
+
lastOutX,
|
|
267
|
+
lastOutY,
|
|
268
|
+
|
|
269
|
+
// 当前节点 handle in
|
|
270
|
+
inX,
|
|
271
|
+
inY,
|
|
272
|
+
|
|
273
|
+
// 当前节点
|
|
274
|
+
bx,
|
|
275
|
+
by
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
lastOutX = outX
|
|
279
|
+
lastOutY = outY
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (close) data.push(Z)
|
|
283
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { Linker } from './Linker'
|
|
2
|
+
export { LinkerData } from './data/LinkerData'
|
|
3
|
+
|
|
4
|
+
import { Plugin, registerPointsCurve } from '@leafer-ui/draw'
|
|
5
|
+
import { drawPoints } from './helper/drawPoints'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Plugin.add('linker')
|
|
9
|
+
|
|
10
|
+
registerPointsCurve('C', drawPoints)
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ILinker, ILinkerData, ILinkerPointData, IPointsCurve, IUI, ILinkerComputedPointData, ILinkerInputData, ILinkerPointType, IPointData, ILeaf, ILinkerPointOptions } from '@leafer-ui/interface';
|
|
2
|
+
import { Line, LineData } from '@leafer-ui/draw';
|
|
3
|
+
|
|
4
|
+
declare class Linker extends Line implements ILinker {
|
|
5
|
+
get __tag(): string;
|
|
6
|
+
__: ILinkerData;
|
|
7
|
+
startPoint?: ILinkerPointData;
|
|
8
|
+
endPoint?: ILinkerPointData;
|
|
9
|
+
curve?: IPointsCurve;
|
|
10
|
+
startNode: IUI;
|
|
11
|
+
endNode: IUI;
|
|
12
|
+
startData: ILinkerComputedPointData;
|
|
13
|
+
endData: ILinkerComputedPointData;
|
|
14
|
+
constructor(data?: ILinkerInputData);
|
|
15
|
+
__updatePath(): void;
|
|
16
|
+
listenLinkerNode(newNode: IUI, oldNode: IUI, type: ILinkerPointType): void;
|
|
17
|
+
onStartNodeChange(): void;
|
|
18
|
+
onEndNodeChange(): void;
|
|
19
|
+
onLinkerNodeChange(type: ILinkerPointType): void;
|
|
20
|
+
updateLinkerNode(type: ILinkerPointType): void;
|
|
21
|
+
getLinkerNodeCenter(type: ILinkerPointType): IPointData;
|
|
22
|
+
updateLinkerPoint(type: ILinkerPointType): void;
|
|
23
|
+
updateLinkerPath(): void;
|
|
24
|
+
createStartPoint(worldPoint: IPointData, node?: ILeaf, options?: ILinkerPointOptions): ILinkerPointData;
|
|
25
|
+
createEndPoint(worldPoint: IPointData, node?: ILeaf, options?: ILinkerPointOptions): ILinkerPointData;
|
|
26
|
+
createLinkerPoint(worldPoint: IPointData, node?: ILeaf, options?: ILinkerPointOptions): ILinkerPointData;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class LinkerData extends LineData implements ILinkerData {
|
|
30
|
+
get __usePathBox(): boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { Linker, LinkerData };
|