@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.
@@ -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,9 @@
1
+ import { Box } from '@leafer-ui/core'
2
+
3
+ import { IDirection8, IEditPoint, IEditPointType } from '@leafer-in/interface'
4
+
5
+
6
+ export class EditPoint extends Box implements IEditPoint {
7
+ public direction: IDirection8
8
+ public pointType: IEditPointType
9
+ }
@@ -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
+ }