1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'box.dart'; |
6 | import 'object.dart'; |
7 | |
8 | export 'package:flutter/foundation.dart' show debugPrint; |
9 | |
10 | // Any changes to this file should be reflected in the debugAssertAllRenderVarsUnset() |
11 | // function below. |
12 | |
13 | const HSVColor _kDebugDefaultRepaintColor = HSVColor.fromAHSV(0.4, 60.0, 1.0, 1.0); |
14 | |
15 | /// Causes each RenderBox to paint a box around its bounds, and some extra |
16 | /// boxes, such as [RenderPadding], to draw construction lines. |
17 | /// |
18 | /// The edges of the boxes are painted as a one-pixel-thick `const Color(0xFF00FFFF)` outline. |
19 | /// |
20 | /// Spacing is painted as a solid `const Color(0x90909090)` area. |
21 | /// |
22 | /// Padding is filled in solid `const Color(0x900090FF)`, with the inner edge |
23 | /// outlined in `const Color(0xFF0090FF)`, using [debugPaintPadding]. |
24 | bool debugPaintSizeEnabled = false; |
25 | |
26 | /// Causes each RenderBox to paint a line at each of its baselines. |
27 | bool debugPaintBaselinesEnabled = false; |
28 | |
29 | /// Causes each Layer to paint a box around its bounds. |
30 | bool debugPaintLayerBordersEnabled = false; |
31 | |
32 | /// Causes objects like [RenderPointerListener] to flash while they are being |
33 | /// tapped. This can be useful to see how large the hit box is, e.g. when |
34 | /// debugging buttons that are harder to hit than expected. |
35 | /// |
36 | /// For details on how to support this in your [RenderBox] subclass, see |
37 | /// [RenderBox.debugHandleEvent]. |
38 | bool debugPaintPointersEnabled = false; |
39 | |
40 | /// Overlay a rotating set of colors when repainting layers in debug mode. |
41 | /// |
42 | /// See also: |
43 | /// |
44 | /// * [RepaintBoundary], which can be used to contain repaints when unchanged |
45 | /// areas are being excessively repainted. |
46 | bool debugRepaintRainbowEnabled = false; |
47 | |
48 | /// Overlay a rotating set of colors when repainting text in debug mode. |
49 | bool debugRepaintTextRainbowEnabled = false; |
50 | |
51 | /// The current color to overlay when repainting a layer. |
52 | /// |
53 | /// This is used by painting debug code that implements |
54 | /// [debugRepaintRainbowEnabled] or [debugRepaintTextRainbowEnabled]. |
55 | /// |
56 | /// The value is incremented by [RenderView.compositeFrame] if either of those |
57 | /// flags is enabled. |
58 | HSVColor debugCurrentRepaintColor = _kDebugDefaultRepaintColor; |
59 | |
60 | /// Log the call stacks that mark render objects as needing layout. |
61 | /// |
62 | /// For sanity, this only logs the stack traces of cases where an object is |
63 | /// added to the list of nodes needing layout. This avoids printing multiple |
64 | /// redundant stack traces as a single [RenderObject.markNeedsLayout] call walks |
65 | /// up the tree. |
66 | bool debugPrintMarkNeedsLayoutStacks = false; |
67 | |
68 | /// Log the call stacks that mark render objects as needing paint. |
69 | bool debugPrintMarkNeedsPaintStacks = false; |
70 | |
71 | /// Log the dirty render objects that are laid out each frame. |
72 | /// |
73 | /// Combined with [debugPrintBeginFrameBanner], this allows you to distinguish |
74 | /// layouts triggered by the initial mounting of a render tree (e.g. in a call |
75 | /// to [runApp]) from the regular layouts triggered by the pipeline. |
76 | /// |
77 | /// Combined with [debugPrintMarkNeedsLayoutStacks], this lets you watch a |
78 | /// render object's dirty/clean lifecycle. |
79 | /// |
80 | /// See also: |
81 | /// |
82 | /// * [debugProfileLayoutsEnabled], which does something similar for layout |
83 | /// but using the timeline view. |
84 | /// * [debugProfilePaintsEnabled], which does something similar for painting |
85 | /// but using the timeline view. |
86 | /// * [debugPrintRebuildDirtyWidgets], which does something similar for widgets |
87 | /// being rebuilt. |
88 | /// * The discussion at [RendererBinding.drawFrame]. |
89 | bool debugPrintLayouts = false; |
90 | |
91 | /// Check the intrinsic sizes of each [RenderBox] during layout. |
92 | /// |
93 | /// By default this is turned off since these checks are expensive. If you are |
94 | /// implementing your own children of [RenderBox] with custom intrinsics, turn |
95 | /// this on in your unit tests for additional validations. |
96 | bool debugCheckIntrinsicSizes = false; |
97 | |
98 | /// Adds [Timeline] events for every [RenderObject] layout. |
99 | /// |
100 | /// The timing information this flag exposes is not representative of the actual |
101 | /// cost of layout, because the overhead of adding timeline events is |
102 | /// significant relative to the time each object takes to lay out. However, it |
103 | /// can expose unexpected layout behavior in the timeline. |
104 | /// |
105 | /// In debug builds, additional information is included in the trace (such as |
106 | /// the properties of render objects being laid out). Collecting this data is |
107 | /// expensive and further makes these traces non-representative of actual |
108 | /// performance. This data is omitted in profile builds. |
109 | /// |
110 | /// For more information about performance debugging in Flutter, see |
111 | /// <https://flutter.dev/docs/perf/rendering>. |
112 | /// |
113 | /// See also: |
114 | /// |
115 | /// * [debugPrintLayouts], which does something similar for layout but using |
116 | /// console output. |
117 | /// * [debugProfileBuildsEnabled], which does something similar for widgets |
118 | /// being rebuilt. |
119 | /// * [debugProfilePaintsEnabled], which does something similar for painting. |
120 | /// * [debugEnhanceLayoutTimelineArguments], which enhances the trace with |
121 | /// debugging information related to [RenderObject] layouts. |
122 | bool debugProfileLayoutsEnabled = false; |
123 | |
124 | /// Adds [Timeline] events for every [RenderObject] painted. |
125 | /// |
126 | /// The timing information this flag exposes is not representative of actual |
127 | /// paints, because the overhead of adding timeline events is significant |
128 | /// relative to the time each object takes to paint. However, it can expose |
129 | /// unexpected painting in the timeline. |
130 | /// |
131 | /// In debug builds, additional information is included in the trace (such as |
132 | /// the properties of render objects being painted). Collecting this data is |
133 | /// expensive and further makes these traces non-representative of actual |
134 | /// performance. This data is omitted in profile builds. |
135 | /// |
136 | /// For more information about performance debugging in Flutter, see |
137 | /// <https://flutter.dev/docs/perf/rendering>. |
138 | /// |
139 | /// See also: |
140 | /// |
141 | /// * [debugProfileBuildsEnabled], which does something similar for widgets |
142 | /// being rebuilt, and [debugPrintRebuildDirtyWidgets], its console |
143 | /// equivalent. |
144 | /// * [debugProfileLayoutsEnabled], which does something similar for layout, |
145 | /// and [debugPrintLayouts], its console equivalent. |
146 | /// * The discussion at [RendererBinding.drawFrame]. |
147 | /// * [RepaintBoundary], which can be used to contain repaints when unchanged |
148 | /// areas are being excessively repainted. |
149 | /// * [debugEnhancePaintTimelineArguments], which enhances the trace with |
150 | /// debugging information related to [RenderObject] paints. |
151 | bool debugProfilePaintsEnabled = false; |
152 | |
153 | /// Adds debugging information to [Timeline] events related to [RenderObject] |
154 | /// layouts. |
155 | /// |
156 | /// This flag will only add [Timeline] event arguments for debug builds. |
157 | /// Additional arguments will be added for the "LAYOUT" timeline event and for |
158 | /// all [RenderObject] layout [Timeline] events, which are the events that are |
159 | /// added when [debugProfileLayoutsEnabled] is true. The debugging information |
160 | /// that will be added in trace arguments includes stats around [RenderObject] |
161 | /// dirty states and [RenderObject] diagnostic information (i.e. [RenderObject] |
162 | /// properties). |
163 | /// |
164 | /// See also: |
165 | /// |
166 | /// * [debugProfileLayoutsEnabled], which adds [Timeline] events for every |
167 | /// [RenderObject] layout. |
168 | /// * [debugEnhancePaintTimelineArguments], which does something similar for |
169 | /// events related to [RenderObject] paints. |
170 | /// * [debugEnhanceBuildTimelineArguments], which does something similar for |
171 | /// events related to [Widget] builds. |
172 | bool debugEnhanceLayoutTimelineArguments = false; |
173 | |
174 | /// Adds debugging information to [Timeline] events related to [RenderObject] |
175 | /// paints. |
176 | /// |
177 | /// This flag will only add [Timeline] event arguments for debug builds. |
178 | /// Additional arguments will be added for the "PAINT" timeline event and for |
179 | /// all [RenderObject] paint [Timeline] events, which are the [Timeline] events |
180 | /// that are added when [debugProfilePaintsEnabled] is true. The debugging |
181 | /// information that will be added in trace arguments includes stats around |
182 | /// [RenderObject] dirty states and [RenderObject] diagnostic information |
183 | /// (i.e. [RenderObject] properties). |
184 | /// |
185 | /// See also: |
186 | /// |
187 | /// * [debugProfilePaintsEnabled], which adds [Timeline] events for every |
188 | /// [RenderObject] paint. |
189 | /// * [debugEnhanceLayoutTimelineArguments], which does something similar for |
190 | /// events related to [RenderObject] layouts. |
191 | /// * [debugEnhanceBuildTimelineArguments], which does something similar for |
192 | /// events related to [Widget] builds. |
193 | bool debugEnhancePaintTimelineArguments = false; |
194 | |
195 | /// Signature for [debugOnProfilePaint] implementations. |
196 | typedef ProfilePaintCallback = void Function(RenderObject renderObject); |
197 | |
198 | /// Callback invoked for every [RenderObject] painted each frame. |
199 | /// |
200 | /// This callback is only invoked in debug builds. |
201 | /// |
202 | /// See also: |
203 | /// |
204 | /// * [debugProfilePaintsEnabled], which does something similar but adds |
205 | /// [dart:developer.Timeline] events instead of invoking a callback. |
206 | /// * [debugOnRebuildDirtyWidget], which does something similar for widgets |
207 | /// being built. |
208 | /// * [WidgetInspectorService], which uses the [debugOnProfilePaint] |
209 | /// callback to generate aggregate profile statistics describing what paints |
210 | /// occurred when the `ext.flutter.inspector.trackRepaintWidgets` service |
211 | /// extension is enabled. |
212 | ProfilePaintCallback? debugOnProfilePaint; |
213 | |
214 | /// Setting to true will cause all clipping effects from the layer tree to be |
215 | /// ignored. |
216 | /// |
217 | /// Can be used to debug whether objects being clipped are painting excessively |
218 | /// in clipped areas. Can also be used to check whether excessive use of |
219 | /// clipping is affecting performance. |
220 | /// |
221 | /// This will not reduce the number of [Layer] objects created; the compositing |
222 | /// strategy is unaffected. It merely causes the clipping layers to be skipped |
223 | /// when building the scene. |
224 | bool debugDisableClipLayers = false; |
225 | |
226 | /// Setting to true will cause all physical modeling effects from the layer |
227 | /// tree, such as shadows from elevations, to be ignored. |
228 | /// |
229 | /// Can be used to check whether excessive use of physical models is affecting |
230 | /// performance. |
231 | /// |
232 | /// This will not reduce the number of [Layer] objects created; the compositing |
233 | /// strategy is unaffected. It merely causes the physical shape layers to be |
234 | /// skipped when building the scene. |
235 | bool debugDisablePhysicalShapeLayers = false; |
236 | |
237 | /// Setting to true will cause all opacity effects from the layer tree to be |
238 | /// ignored. |
239 | /// |
240 | /// An optimization to not paint the child at all when opacity is 0 will still |
241 | /// remain. |
242 | /// |
243 | /// Can be used to check whether excessive use of opacity effects is affecting |
244 | /// performance. |
245 | /// |
246 | /// This will not reduce the number of [Layer] objects created; the compositing |
247 | /// strategy is unaffected. It merely causes the opacity layers to be skipped |
248 | /// when building the scene. |
249 | bool debugDisableOpacityLayers = false; |
250 | |
251 | void _debugDrawDoubleRect(Canvas canvas, Rect outerRect, Rect innerRect, Color color) { |
252 | final Path path = Path() |
253 | ..fillType = PathFillType.evenOdd |
254 | ..addRect(outerRect) |
255 | ..addRect(innerRect); |
256 | final Paint paint = Paint() |
257 | ..color = color; |
258 | canvas.drawPath(path, paint); |
259 | } |
260 | |
261 | /// Paint a diagram showing the given area as padding. |
262 | /// |
263 | /// The `innerRect` argument represents the position of the child, if any. |
264 | /// |
265 | /// When `innerRect` is null, the method draws the entire `outerRect` in a |
266 | /// grayish color representing _spacing_. |
267 | /// |
268 | /// When `innerRect` is non-null, the method draws the padding region around the |
269 | /// `innerRect` in a tealish color, with a solid outline around the inner |
270 | /// region. |
271 | /// |
272 | /// This method is used by [RenderPadding.debugPaintSize] when |
273 | /// [debugPaintSizeEnabled] is true. |
274 | void debugPaintPadding(Canvas canvas, Rect outerRect, Rect? innerRect, { double outlineWidth = 2.0 }) { |
275 | assert(() { |
276 | if (innerRect != null && !innerRect.isEmpty) { |
277 | _debugDrawDoubleRect(canvas, outerRect, innerRect, const Color(0x900090FF)); |
278 | _debugDrawDoubleRect(canvas, innerRect.inflate(outlineWidth).intersect(outerRect), innerRect, const Color(0xFF0090FF)); |
279 | } else { |
280 | final Paint paint = Paint() |
281 | ..color = const Color(0x90909090); |
282 | canvas.drawRect(outerRect, paint); |
283 | } |
284 | return true; |
285 | }()); |
286 | } |
287 | |
288 | /// Returns true if none of the rendering library debug variables have been changed. |
289 | /// |
290 | /// This function is used by the test framework to ensure that debug variables |
291 | /// haven't been inadvertently changed. |
292 | /// |
293 | /// See [the rendering library](rendering/rendering-library.html) for a complete |
294 | /// list. |
295 | /// |
296 | /// The `debugCheckIntrinsicSizesOverride` argument can be provided to override |
297 | /// the expected value for [debugCheckIntrinsicSizes]. (This exists because the |
298 | /// test framework itself overrides this value in some cases.) |
299 | bool debugAssertAllRenderVarsUnset(String reason, { bool debugCheckIntrinsicSizesOverride = false }) { |
300 | assert(() { |
301 | if (debugPaintSizeEnabled || |
302 | debugPaintBaselinesEnabled || |
303 | debugPaintLayerBordersEnabled || |
304 | debugPaintPointersEnabled || |
305 | debugRepaintRainbowEnabled || |
306 | debugRepaintTextRainbowEnabled || |
307 | debugCurrentRepaintColor != _kDebugDefaultRepaintColor || |
308 | debugPrintMarkNeedsLayoutStacks || |
309 | debugPrintMarkNeedsPaintStacks || |
310 | debugPrintLayouts || |
311 | debugCheckIntrinsicSizes != debugCheckIntrinsicSizesOverride || |
312 | debugProfileLayoutsEnabled || |
313 | debugProfilePaintsEnabled || |
314 | debugOnProfilePaint != null || |
315 | debugDisableClipLayers || |
316 | debugDisablePhysicalShapeLayers || |
317 | debugDisableOpacityLayers) { |
318 | throw FlutterError(reason); |
319 | } |
320 | return true; |
321 | }()); |
322 | return true; |
323 | } |
324 | |
325 | /// Returns true if the given [Axis] is bounded within the given |
326 | /// [BoxConstraints] in both the main and cross axis, throwing an exception |
327 | /// otherwise. |
328 | /// |
329 | /// This is used by viewports during `performLayout` and `computeDryLayout` |
330 | /// because bounded constraints are required in order to layout their children. |
331 | bool debugCheckHasBoundedAxis(Axis axis, BoxConstraints constraints) { |
332 | assert(() { |
333 | if (!constraints.hasBoundedHeight || !constraints.hasBoundedWidth) { |
334 | switch (axis) { |
335 | case Axis.vertical: |
336 | if (!constraints.hasBoundedHeight) { |
337 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
338 | ErrorSummary('Vertical viewport was given unbounded height.' ), |
339 | ErrorDescription( |
340 | 'Viewports expand in the scrolling direction to fill their container. ' |
341 | 'In this case, a vertical viewport was given an unlimited amount of ' |
342 | 'vertical space in which to expand. This situation typically happens ' |
343 | 'when a scrollable widget is nested inside another scrollable widget.' , |
344 | ), |
345 | ErrorHint( |
346 | 'If this widget is always nested in a scrollable widget there ' |
347 | 'is no need to use a viewport because there will always be enough ' |
348 | 'vertical space for the children. In this case, consider using a ' |
349 | 'Column or Wrap instead. Otherwise, consider using a ' |
350 | 'CustomScrollView to concatenate arbitrary slivers into a ' |
351 | 'single scrollable.' , |
352 | ), |
353 | ]); |
354 | } |
355 | if (!constraints.hasBoundedWidth) { |
356 | throw FlutterError( |
357 | 'Vertical viewport was given unbounded width.\n' |
358 | 'Viewports expand in the cross axis to fill their container and ' |
359 | 'constrain their children to match their extent in the cross axis. ' |
360 | 'In this case, a vertical viewport was given an unlimited amount of ' |
361 | 'horizontal space in which to expand.' , |
362 | ); |
363 | } |
364 | case Axis.horizontal: |
365 | if (!constraints.hasBoundedWidth) { |
366 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
367 | ErrorSummary('Horizontal viewport was given unbounded width.' ), |
368 | ErrorDescription( |
369 | 'Viewports expand in the scrolling direction to fill their container. ' |
370 | 'In this case, a horizontal viewport was given an unlimited amount of ' |
371 | 'horizontal space in which to expand. This situation typically happens ' |
372 | 'when a scrollable widget is nested inside another scrollable widget.' , |
373 | ), |
374 | ErrorHint( |
375 | 'If this widget is always nested in a scrollable widget there ' |
376 | 'is no need to use a viewport because there will always be enough ' |
377 | 'horizontal space for the children. In this case, consider using a ' |
378 | 'Row or Wrap instead. Otherwise, consider using a ' |
379 | 'CustomScrollView to concatenate arbitrary slivers into a ' |
380 | 'single scrollable.' , |
381 | ), |
382 | ]); |
383 | } |
384 | if (!constraints.hasBoundedHeight) { |
385 | throw FlutterError( |
386 | 'Horizontal viewport was given unbounded height.\n' |
387 | 'Viewports expand in the cross axis to fill their container and ' |
388 | 'constrain their children to match their extent in the cross axis. ' |
389 | 'In this case, a horizontal viewport was given an unlimited amount of ' |
390 | 'vertical space in which to expand.' , |
391 | ); |
392 | } |
393 | } |
394 | } |
395 | return true; |
396 | }()); |
397 | return true; |
398 | } |
399 | |