@leafer/renderer 1.0.0-alpha.21 → 1.0.0-alpha.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leafer/renderer",
3
- "version": "1.0.0-alpha.21",
3
+ "version": "1.0.0-alpha.30",
4
4
  "description": "@leafer/renderer",
5
5
  "author": "Chao (Leafer) Wan",
6
6
  "license": "MIT",
@@ -19,13 +19,13 @@
19
19
  "leaferjs"
20
20
  ],
21
21
  "dependencies": {
22
- "@leafer/event": "1.0.0-alpha.21",
23
- "@leafer/math": "1.0.0-alpha.21",
24
- "@leafer/data": "1.0.0-alpha.21",
25
- "@leafer/platform": "1.0.0-alpha.21",
26
- "@leafer/debug": "1.0.0-alpha.21"
22
+ "@leafer/event": "1.0.0-alpha.30",
23
+ "@leafer/math": "1.0.0-alpha.30",
24
+ "@leafer/data": "1.0.0-alpha.30",
25
+ "@leafer/platform": "1.0.0-alpha.30",
26
+ "@leafer/debug": "1.0.0-alpha.30"
27
27
  },
28
28
  "devDependencies": {
29
- "@leafer/interface": "1.0.0-alpha.21"
29
+ "@leafer/interface": "1.0.0-alpha.30"
30
30
  }
31
31
  }
package/src/Renderer.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ILeaf, ILeaferCanvas, IRenderer, IRendererConfig, IEventListenerId, IBounds, IFunction } from '@leafer/interface'
1
+ import { ILeaf, ILeaferCanvas, IRenderer, IRendererConfig, IEventListenerId, IBounds, IFunction, IRenderOptions } from '@leafer/interface'
2
2
  import { LayoutEvent, RenderEvent, ResizeEvent } from '@leafer/event'
3
3
  import { Bounds } from '@leafer/math'
4
4
  import { DataHelper } from '@leafer/data'
@@ -19,6 +19,9 @@ export class Renderer implements IRenderer {
19
19
  public times: number = 0
20
20
 
21
21
  public running: boolean
22
+ public rendering: boolean
23
+
24
+ public waitAgain: boolean
22
25
  public changed: boolean
23
26
 
24
27
  public config: IRendererConfig = {
@@ -26,9 +29,15 @@ export class Renderer implements IRenderer {
26
29
  maxFPS: 60
27
30
  }
28
31
 
32
+ protected renderBounds: IBounds
33
+ protected renderOptions: IRenderOptions
34
+ protected totalBounds: IBounds
35
+
29
36
  protected __eventIds: IEventListenerId[]
30
37
 
31
- constructor(target: ILeaf, canvas: ILeaferCanvas, userConfig: IRendererConfig) {
38
+ protected get needFill(): boolean { return !!(!this.canvas.allowBackgroundColor && this.config.fill) }
39
+
40
+ constructor(target: ILeaf, canvas: ILeaferCanvas, userConfig?: IRendererConfig) {
32
41
  this.target = target
33
42
  this.canvas = canvas
34
43
  if (userConfig) this.config = DataHelper.default(userConfig, this.config)
@@ -53,126 +62,151 @@ export class Renderer implements IRenderer {
53
62
  }
54
63
 
55
64
  public render(callback?: IFunction): void {
65
+ if (!(this.running)) return
66
+
56
67
  const { target } = this
57
68
  this.times = 0
69
+ this.totalBounds = new Bounds()
58
70
 
59
- debug.log(target.innerId, '--->')
71
+ debug.log(target.innerName, '--->')
60
72
 
61
- target.emit(RenderEvent.START)
62
- this.renderOnce(callback)
63
- target.emit(RenderEvent.RENDER)
64
- target.emit(RenderEvent.END)
73
+ try {
74
+ this.emitRender(RenderEvent.START)
75
+ this.renderOnce(callback)
76
+ this.emitRender(RenderEvent.END, this.totalBounds)
77
+ } catch (e) {
78
+ debug.error(e)
79
+ }
65
80
 
66
- debug.log(target.innerId, '---|')
81
+ debug.log('-------------|')
82
+ }
83
+
84
+ public renderAgain(): void {
85
+ if (this.rendering) {
86
+ this.waitAgain = true
87
+ } else {
88
+ this.renderOnce()
89
+ }
67
90
  }
68
91
 
69
92
  public renderOnce(callback?: IFunction): void {
70
- const { target } = this
93
+ if (this.rendering) return debug.warn('rendering')
94
+ if (this.times > 3) return debug.warn('render max times')
71
95
 
72
96
  this.times++
73
97
  this.totalTimes++
98
+
99
+ this.rendering = true
74
100
  this.changed = false
101
+ this.renderBounds = new Bounds()
102
+ this.renderOptions = {}
75
103
 
76
104
  if (callback) {
77
-
78
- target.emit(RenderEvent.BEFORE_ONCE)
79
-
105
+ this.emitRender(RenderEvent.BEFORE)
80
106
  callback()
81
-
82
107
  } else {
83
-
84
108
  this.requestLayout()
85
109
 
86
- target.emit(RenderEvent.BEFORE_ONCE)
110
+ this.emitRender(RenderEvent.BEFORE)
87
111
 
88
112
  if (this.config.usePartRender && this.totalTimes > 1) {
89
113
  this.partRender()
90
114
  } else {
91
115
  this.fullRender()
92
116
  }
93
-
94
117
  }
95
118
 
96
- target.emit(RenderEvent.ONCE)
97
- target.emit(RenderEvent.AFTER_ONCE)
98
-
119
+ this.emitRender(RenderEvent.RENDER, this.renderBounds, this.renderOptions)
120
+ this.emitRender(RenderEvent.AFTER, this.renderBounds, this.renderOptions)
99
121
 
100
122
  this.updateBlocks = null
123
+ this.rendering = false
101
124
 
102
- this.__checkAgain()
125
+ if (this.waitAgain) {
126
+ this.waitAgain = false
127
+ this.renderOnce()
128
+ }
103
129
  }
104
130
 
105
131
  public partRender(): void {
106
132
  const { canvas, updateBlocks: list } = this
133
+ if (!list) return debug.warn('PartRender: need update attr')
107
134
 
108
- if (!list) {
109
- debug.warn('PartRender: layoutedBlocks is empty')
110
- this.fullRender(canvas.bounds)
111
- return
112
- }
113
-
114
- if (list.some(block => block.includes(this.target.__world))) {
115
- this.mergeBlocks()
116
- this.clipRender(this.updateBlocks[0], true)
117
- } else {
118
- list.forEach(block => {
119
- if (canvas.bounds.hit(block) && !block.isEmpty()) this.clipRender(block.getIntersect(canvas.bounds))
120
- })
121
- }
135
+ if (list.some(block => block.includes(this.target.__world))) this.mergeBlocks()
136
+ list.forEach(block => { if (canvas.bounds.hit(block) && !block.isEmpty()) this.clipRender(block) })
122
137
  }
123
138
 
124
- public clipRender(bounds: IBounds, fullMode?: boolean): void {
139
+ public clipRender(block: IBounds): void {
125
140
  const t = Run.start('PartRender')
126
- const { canvas, target } = this
141
+ const { canvas } = this
127
142
 
128
- bounds.spread(1 + 1 / this.canvas.pixelRatio)
129
- bounds.ceil()
143
+ const bounds = block.getIntersect(canvas.bounds)
144
+ const includes = block.includes(this.target.__world)
145
+ const realBounds = new Bounds().copy(bounds)
130
146
 
131
147
  canvas.save()
132
- canvas.clearWorld(bounds, true)
133
- if (Debug.showRepaint) canvas.strokeWorld(bounds, 'red')
134
- canvas.clipWorld(bounds, true)
135
- target.__render(canvas, fullMode ? {} : { bounds })
148
+
149
+ if (includes && !Debug.showRepaint) {
150
+ canvas.clear()
151
+ } else {
152
+ bounds.spread(1 + 1 / this.canvas.pixelRatio).ceil()
153
+ canvas.clearWorld(bounds, true)
154
+ canvas.clipWorld(bounds, true)
155
+ }
156
+
157
+ this.__render(bounds, realBounds)
136
158
  canvas.restore()
137
159
 
138
160
  Run.end(t)
139
161
  }
140
162
 
141
- public fullRender(bounds?: IBounds): void {
142
- const { canvas, target } = this
143
- Renderer.fullRender(target, canvas, bounds)
144
- }
145
-
146
- static fullRender(target: ILeaf, canvas: ILeaferCanvas, bounds?: IBounds): void {
163
+ public fullRender(): void {
147
164
  const t = Run.start('FullRender')
148
- if (!bounds) bounds = canvas.bounds
165
+ const { canvas } = this
149
166
 
150
167
  canvas.save()
151
168
  canvas.clear()
152
- target.__render(canvas, canvas.bounds.includes(target.__world) ? {} : { bounds })
169
+ this.__render(canvas.bounds)
153
170
  canvas.restore()
154
171
 
155
172
  Run.end(t)
156
173
  }
157
174
 
175
+ protected __render(bounds: IBounds, realBounds?: IBounds): void {
176
+ const options: IRenderOptions = bounds?.includes(this.target.__world) ? {} : { bounds }
177
+
178
+ if (this.needFill) this.canvas.fillWorld(bounds, this.config.fill)
179
+ if (Debug.showRepaint) this.canvas.strokeWorld(bounds, 'red')
180
+
181
+ this.target.__render(this.canvas, options)
182
+
183
+ this.renderBounds = realBounds || bounds
184
+ this.renderOptions = options
185
+ this.totalBounds.isEmpty() ? this.totalBounds = this.renderBounds : this.totalBounds.add(this.renderBounds)
186
+
187
+ if (Debug.showHitView) this.renderHitView(options)
188
+ if (Debug.showBoundsView) this.renderBoundsView(options)
189
+ }
190
+
191
+ public renderHitView(_options: IRenderOptions): void { }
192
+
193
+ public renderBoundsView(_options: IRenderOptions): void { }
194
+
158
195
  public addBlock(block: IBounds): void {
159
196
  if (!this.updateBlocks) this.updateBlocks = []
160
197
  this.updateBlocks.push(block)
161
198
  }
162
199
 
163
200
  public mergeBlocks(): void {
164
- const { updateBlocks } = this
165
- if (updateBlocks) {
201
+ const { updateBlocks: list } = this
202
+ if (list) {
166
203
  const bounds = new Bounds()
167
- bounds.setByList(updateBlocks)
168
- this.updateBlocks = [bounds]
204
+ bounds.setByList(list)
205
+ list.length = 0
206
+ list.push(bounds)
169
207
  }
170
208
  }
171
209
 
172
- protected __checkAgain(): void {
173
- if (this.changed && this.times < 3) this.target.emit(RenderEvent.AGAIN)
174
- }
175
-
176
210
  protected __requestRender(): void {
177
211
  const startTime = Date.now()
178
212
  Platform.requestRender(() => {
@@ -184,18 +218,34 @@ export class Renderer implements IRenderer {
184
218
  }
185
219
 
186
220
  protected __onResize(e: ResizeEvent): void {
221
+ if (this.canvas.unreal) return
187
222
  if (e.bigger || !e.samePixelRatio) {
188
223
  const { width, height } = e.old
189
224
  const bounds = new Bounds(0, 0, width, height)
190
- if (!bounds.includes(this.target.__world)) {
191
- this.target.__updateAttr('fill')
192
- this.update()
225
+ if (!bounds.includes(this.target.__world) || this.needFill || !e.samePixelRatio) {
226
+ this.addBlock(this.canvas.bounds)
227
+ this.target.forceUpdate('blendMode')
193
228
  }
194
229
  }
195
230
  }
196
231
 
197
232
  protected __onLayoutEnd(event: LayoutEvent): void {
198
- event.data.map(item => this.addBlock(item.updatedBounds))
233
+ if (event.data) event.data.map(item => {
234
+ let empty: boolean
235
+ if (item.updatedList) item.updatedList.list.some(leaf => {
236
+ empty = (!leaf.__world.width || !leaf.__world.height)
237
+ if (empty) {
238
+ debug.warn(leaf.innerName, ': none bounds')
239
+ empty = (!leaf.isBranch || leaf.isBranchLeaf) // render object
240
+ }
241
+ return empty
242
+ })
243
+ this.addBlock(empty ? this.canvas.bounds : item.updatedBounds)
244
+ })
245
+ }
246
+
247
+ protected emitRender(type: string, bounds?: IBounds, options?: IRenderOptions): void {
248
+ this.target.emitEvent(new RenderEvent(type, this.times, bounds, options))
199
249
  }
200
250
 
201
251
  protected __listenEvents(): void {
@@ -203,7 +253,7 @@ export class Renderer implements IRenderer {
203
253
  this.__eventIds = [
204
254
  target.on__(RenderEvent.REQUEST, this.update, this),
205
255
  target.on__(LayoutEvent.END, this.__onLayoutEnd, this),
206
- target.on__(RenderEvent.AGAIN, this.renderOnce, this),
256
+ target.on__(RenderEvent.AGAIN, this.renderAgain, this),
207
257
  target.on__(ResizeEvent.RESIZE, this.__onResize, this)
208
258
  ]
209
259
  }
@@ -214,6 +264,7 @@ export class Renderer implements IRenderer {
214
264
 
215
265
  public destroy(): void {
216
266
  if (this.target) {
267
+ this.stop()
217
268
  this.__removeListenEvents()
218
269
  this.target = null
219
270
  this.canvas = null
@@ -0,0 +1,49 @@
1
+ import { ILeaf, ILeaferCanvas, IRenderOptions } from '@leafer/interface'
2
+
3
+
4
+ const colorList: string[] = []
5
+ for (let i = 0; i < 360; i++) {
6
+ colorList.push(Math.round(Math.random() * 360) + '')
7
+ }
8
+
9
+ export function renderHitView(target: ILeaf, canvas: ILeaferCanvas, options: IRenderOptions, hasMask?: boolean): void {
10
+ if (target.hittable) {
11
+ if (target.isBranch) {
12
+ renderBranchHitView(target, canvas, options)
13
+ } else {
14
+ renderLeafHitView(target, canvas, options, hasMask)
15
+ }
16
+ }
17
+ }
18
+
19
+ export function renderBranchHitView(branch: ILeaf, canvas: ILeaferCanvas, options: IRenderOptions): void {
20
+ const { children } = branch
21
+ let target: ILeaf
22
+ for (let i = 0, len = children.length; i < len; i++) {
23
+ target = children[i]
24
+ if (target.hittable) {
25
+ if (target.isBranch) {
26
+ renderBranchHitView(target, canvas, options)
27
+ } else {
28
+ renderLeafHitView(target, canvas, options, target.__hasMask)
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ function renderLeafHitView(leaf: ILeaf, canvas: ILeaferCanvas, options: IRenderOptions, hasMask: boolean): void {
35
+ if (leaf.__worldOpacity) {
36
+ const { isMask, __strokeOuterWidth: strokeWidth } = leaf.__
37
+ if (hasMask && !isMask) return
38
+
39
+ canvas.setWorld(leaf.__world, options.matrix)
40
+ canvas.opacity = 0.5
41
+
42
+ leaf.__drawHitPath(canvas)
43
+
44
+ canvas.fillStyle = canvas.strokeStyle = 'hsl(' + colorList[leaf.innerId % 360] + ',50%, 50%)'
45
+ canvas.fill(leaf.__.windingRule)
46
+ canvas.strokeWidth = strokeWidth ? strokeWidth * 2 : 1
47
+ canvas.stroke()
48
+ }
49
+ }