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
5import 'box.dart';
6import 'object.dart';
7
8export 'package:flutter/foundation.dart' show debugPrint;
9
10// Any changes to this file should be reflected in the debugAssertAllRenderVarsUnset()
11// function below.
12
13const 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].
24bool debugPaintSizeEnabled = false;
25
26/// Causes each RenderBox to paint a line at each of its baselines.
27bool debugPaintBaselinesEnabled = false;
28
29/// Causes each Layer to paint a box around its bounds.
30bool 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].
38bool 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.
46bool debugRepaintRainbowEnabled = false;
47
48/// Overlay a rotating set of colors when repainting text in debug mode.
49bool 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.
58HSVColor 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.
66bool debugPrintMarkNeedsLayoutStacks = false;
67
68/// Log the call stacks that mark render objects as needing paint.
69bool 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].
89bool 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.
96bool 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.
122bool 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.
151bool 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.
172bool 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.
193bool debugEnhancePaintTimelineArguments = false;
194
195/// Signature for [debugOnProfilePaint] implementations.
196typedef 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.
212ProfilePaintCallback? 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.
224bool 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.
235bool 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.
249bool debugDisableOpacityLayers = false;
250
251void _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.
274void 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.)
299bool 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.
331bool 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