@leafer-in/editor 1.0.0-rc.2 → 1.0.0-rc.21

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