@@ -11,9 +11,10 @@ import type {NativeEvent, ReactProfilerData} from '../types';
11
11
import type { Interaction , MouseMoveInteraction , Rect , Size } from '../view-base' ;
12
12
13
13
import {
14
+ durationToWidth ,
14
15
positioningScaleFactor ,
15
- timestampToPosition ,
16
16
positionToTimestamp ,
17
+ timestampToPosition ,
17
18
} from './utils/positioning' ;
18
19
import {
19
20
View ,
@@ -24,28 +25,77 @@ import {
24
25
} from '../view-base' ;
25
26
import {
26
27
COLORS ,
27
- EVENT_ROW_PADDING ,
28
- EVENT_DIAMETER ,
28
+ TEXT_PADDING ,
29
+ NATIVE_EVENT_HEIGHT ,
30
+ FONT_SIZE ,
29
31
BORDER_SIZE ,
30
32
} from './constants' ;
31
33
32
- const EVENT_ROW_HEIGHT_FIXED =
33
- EVENT_ROW_PADDING + EVENT_DIAMETER + EVENT_ROW_PADDING ;
34
+ const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE ;
35
+
36
+ // TODO (scheduling profiler) Make this a reusable util
37
+ const cachedFlamechartTextWidths = new Map ( ) ;
38
+ const trimFlamechartText = (
39
+ context : CanvasRenderingContext2D ,
40
+ text : string ,
41
+ width : number ,
42
+ ) => {
43
+ for ( let i = text . length - 1 ; i >= 0 ; i -- ) {
44
+ const trimmedText = i === text . length - 1 ? text : text . substr ( 0 , i ) + '…' ;
45
+
46
+ let measuredWidth = cachedFlamechartTextWidths . get ( trimmedText ) ;
47
+ if ( measuredWidth == null ) {
48
+ measuredWidth = context . measureText ( trimmedText ) . width ;
49
+ cachedFlamechartTextWidths . set ( trimmedText , measuredWidth ) ;
50
+ }
51
+
52
+ if ( measuredWidth <= width ) {
53
+ return trimmedText ;
54
+ }
55
+ }
56
+
57
+ return null ;
58
+ } ;
34
59
35
60
export class NativeEventsView extends View {
36
- _profilerData : ReactProfilerData ;
61
+ _depthToNativeEvent : Map < number , NativeEvent [ ] > ;
62
+ _hoveredEvent: NativeEvent | null = null ;
37
63
_intrinsicSize: Size ;
64
+ _maxDepth: number = 0 ;
65
+ _profilerData: ReactProfilerData ;
38
66
39
- _hoveredEvent : NativeEvent | null = null ;
40
67
onHover: ( ( event : NativeEvent | null ) => void ) | null = null ;
41
68
42
69
constructor ( surface : Surface , frame : Rect , profilerData : ReactProfilerData ) {
43
70
super ( surface , frame ) ;
71
+
44
72
this . _profilerData = profilerData ;
45
73
74
+ this . _performPreflightComputations ( ) ;
75
+ console . log ( this . _depthToNativeEvent ) ;
76
+ }
77
+
78
+ _performPreflightComputations ( ) {
79
+ this . _depthToNativeEvent = new Map ( ) ;
80
+
81
+ const { duration, nativeEvents} = this . _profilerData ;
82
+
83
+ nativeEvents . forEach ( event => {
84
+ const depth = event . depth ;
85
+
86
+ this . _maxDepth = Math . max ( this . _maxDepth , depth ) ;
87
+
88
+ if ( ! this . _depthToNativeEvent . has ( depth ) ) {
89
+ this . _depthToNativeEvent . set ( depth , [ event ] ) ;
90
+ } else {
91
+ // $FlowFixMe This is unnecessary.
92
+ this . _depthToNativeEvent . get ( depth ) . push ( event ) ;
93
+ }
94
+ } ) ;
95
+
46
96
this . _intrinsicSize = {
47
- width : this . _profilerData . duration ,
48
- height : EVENT_ROW_HEIGHT_FIXED ,
97
+ width : duration ,
98
+ height : ( this . _maxDepth + 1 ) * ROW_WITH_BORDER_HEIGHT ,
49
99
} ;
50
100
}
51
101
@@ -73,7 +123,9 @@ export class NativeEventsView extends View {
73
123
showHoverHighlight : boolean ,
74
124
) {
75
125
const { frame} = this ;
76
- const { duration, timestamp} = event ;
126
+ const { depth, duration, highlight, timestamp, type} = event ;
127
+
128
+ baseY += depth * ROW_WITH_BORDER_HEIGHT ;
77
129
78
130
const xStart = timestampToPosition ( timestamp , scaleFactor , frame ) ;
79
131
const xStop = timestampToPosition ( timestamp + duration , scaleFactor , frame ) ;
@@ -82,25 +134,60 @@ export class NativeEventsView extends View {
82
134
x : xStart ,
83
135
y : baseY ,
84
136
} ,
85
- size : { width : xStop - xStart , height : EVENT_DIAMETER } ,
137
+ size : { width : xStop - xStart , height : NATIVE_EVENT_HEIGHT } ,
86
138
} ;
87
139
if ( ! rectIntersectsRect ( eventRect , rect ) ) {
88
140
return ; // Not in view
89
141
}
90
142
91
- const fillStyle = showHoverHighlight
92
- ? COLORS . NATIVE_EVENT_HOVER
93
- : COLORS . NATIVE_EVENT ;
143
+ const width = durationToWidth ( duration , scaleFactor ) ;
144
+ if ( width < 1 ) {
145
+ return ; // Too small to render at this zoom level
146
+ }
94
147
95
148
const drawableRect = intersectionOfRects ( eventRect , rect ) ;
96
149
context . beginPath ( ) ;
97
- context . fillStyle = fillStyle ;
150
+ if ( highlight ) {
151
+ context . fillStyle = showHoverHighlight
152
+ ? COLORS . NATIVE_EVENT_WARNING_HOVER
153
+ : COLORS . NATIVE_EVENT_WARNING ;
154
+ } else {
155
+ context . fillStyle = showHoverHighlight
156
+ ? COLORS . NATIVE_EVENT_HOVER
157
+ : COLORS . NATIVE_EVENT ;
158
+ }
98
159
context . fillRect (
99
160
drawableRect . origin . x ,
100
161
drawableRect . origin . y ,
101
162
drawableRect . size . width ,
102
163
drawableRect . size . height ,
103
164
) ;
165
+
166
+ // Render event type label
167
+ context . textAlign = 'left' ;
168
+ context . textBaseline = 'middle' ;
169
+ context . font = `${ FONT_SIZE } px sans-serif` ;
170
+
171
+ if ( width > TEXT_PADDING * 2 ) {
172
+ const x = Math . floor ( timestampToPosition ( timestamp , scaleFactor , frame ) ) ;
173
+ const trimmedName = trimFlamechartText (
174
+ context ,
175
+ type ,
176
+ width - TEXT_PADDING * 2 + ( x < 0 ? x : 0 ) ,
177
+ ) ;
178
+
179
+ if ( trimmedName !== null ) {
180
+ context . fillStyle = highlight
181
+ ? COLORS . NATIVE_EVENT_WARNING_TEXT
182
+ : COLORS . TEXT_COLOR ;
183
+
184
+ context . fillText (
185
+ trimmedName ,
186
+ eventRect . origin . x + TEXT_PADDING - ( x < 0 ? x : 0 ) ,
187
+ eventRect . origin . y + NATIVE_EVENT_HEIGHT / 2 ,
188
+ ) ;
189
+ }
190
+ }
104
191
}
105
192
106
193
draw ( context : CanvasRenderingContext2D ) {
@@ -111,7 +198,7 @@ export class NativeEventsView extends View {
111
198
visibleArea,
112
199
} = this ;
113
200
114
- context . fillStyle = COLORS . BACKGROUND ;
201
+ context . fillStyle = COLORS . PRIORITY_BACKGROUND ;
115
202
context . fillRect (
116
203
visibleArea . origin . x ,
117
204
visibleArea . origin . y ,
@@ -120,57 +207,43 @@ export class NativeEventsView extends View {
120
207
) ;
121
208
122
209
// Draw events
123
- const baseY = frame . origin . y + EVENT_ROW_PADDING ;
124
210
const scaleFactor = positioningScaleFactor (
125
211
this . _intrinsicSize . width ,
126
212
frame ,
127
213
) ;
128
214
129
215
nativeEvents . forEach ( event => {
130
- if ( event === _hoveredEvent ) {
131
- // Draw the highlighted items on top so they stand out.
132
- // This is helpful if there are multiple (overlapping) items close to each other.
133
- this . _drawSingleNativeEvent (
134
- context ,
135
- visibleArea ,
136
- event ,
137
- baseY ,
138
- scaleFactor ,
139
- true ,
140
- ) ;
141
- } else {
142
- this . _drawSingleNativeEvent (
143
- context ,
144
- visibleArea ,
145
- event ,
146
- baseY ,
147
- scaleFactor ,
148
- false ,
149
- ) ;
150
- }
216
+ this . _drawSingleNativeEvent (
217
+ context ,
218
+ visibleArea ,
219
+ event ,
220
+ frame . origin . y ,
221
+ scaleFactor ,
222
+ event === _hoveredEvent ,
223
+ ) ;
151
224
} ) ;
152
225
153
- // Render bottom border .
154
- // Propose border rect, check if intersects with `rect`, draw intersection.
155
- const borderFrame : Rect = {
156
- origin : {
157
- x : frame . origin . x ,
158
- y : frame . origin . y + EVENT_ROW_HEIGHT_FIXED - BORDER_SIZE ,
159
- } ,
160
- size : {
161
- width : frame . size . width ,
162
- height : BORDER_SIZE ,
163
- } ,
164
- } ;
165
- if ( rectIntersectsRect ( borderFrame , visibleArea ) ) {
166
- const borderDrawableRect = intersectionOfRects ( borderFrame , visibleArea ) ;
167
- context . fillStyle = COLORS . PRIORITY_BORDER ;
168
- context . fillRect (
169
- borderDrawableRect . origin . x ,
170
- borderDrawableRect . origin . y ,
171
- borderDrawableRect . size . width ,
172
- borderDrawableRect . size . height ,
173
- ) ;
226
+ // Render bottom borders .
227
+ for ( let i = 0 ; i <= this . _maxDepth ; i ++ ) {
228
+ const borderFrame : Rect = {
229
+ origin : {
230
+ x : frame . origin . x ,
231
+ y : frame . origin . y + NATIVE_EVENT_HEIGHT ,
232
+ } ,
233
+ size : {
234
+ width : frame . size . width ,
235
+ height : BORDER_SIZE ,
236
+ } ,
237
+ } ;
238
+ if ( rectIntersectsRect ( borderFrame , visibleArea ) ) {
239
+ context . fillStyle = COLORS . PRIORITY_BORDER ;
240
+ context . fillRect (
241
+ visibleArea . origin . x ,
242
+ frame . origin . y + ( i + 1 ) * ROW_WITH_BORDER_HEIGHT - BORDER_SIZE ,
243
+ visibleArea . size . width ,
244
+ BORDER_SIZE ,
245
+ ) ;
246
+ }
174
247
}
175
248
}
176
249
@@ -189,25 +262,26 @@ export class NativeEventsView extends View {
189
262
return ;
190
263
}
191
264
192
- const { nativeEvents} = this . _profilerData ;
193
-
194
265
const scaleFactor = positioningScaleFactor ( _intrinsicSize . width , frame ) ;
195
266
const hoverTimestamp = positionToTimestamp ( location . x , scaleFactor , frame ) ;
196
267
197
- // Find the event being hovered over.
198
- //
199
- // Because data ranges may overlap, we want to find the last intersecting item.
200
- // This will always be the one on "top" (the one the user is hovering over).
201
- for ( let index = nativeEvents . length - 1 ; index >= 0 ; index -- ) {
202
- const nativeEvent = nativeEvents [ index ] ;
203
- const { duration, timestamp} = nativeEvent ;
204
-
205
- if (
206
- hoverTimestamp >= timestamp &&
207
- hoverTimestamp <= timestamp + duration
208
- ) {
209
- onHover ( nativeEvent ) ;
210
- return ;
268
+ const adjustedCanvasMouseY = location . y - frame . origin . y ;
269
+ const depth = Math . floor ( adjustedCanvasMouseY / ROW_WITH_BORDER_HEIGHT ) ;
270
+ const nativeEventsAtDepth = this . _depthToNativeEvent . get ( depth ) ;
271
+
272
+ if ( nativeEventsAtDepth ) {
273
+ // Find the event being hovered over.
274
+ for ( let index = nativeEventsAtDepth . length - 1 ; index >= 0 ; index -- ) {
275
+ const nativeEvent = nativeEventsAtDepth [ index ] ;
276
+ const { duration, timestamp} = nativeEvent ;
277
+
278
+ if (
279
+ hoverTimestamp >= timestamp &&
280
+ hoverTimestamp <= timestamp + duration
281
+ ) {
282
+ onHover ( nativeEvent ) ;
283
+ return ;
284
+ }
211
285
}
212
286
}
213
287
0 commit comments