Improve debugging output

Teach dumpRenderTree() to draw actual trees.
Make the TextStyle output terser so it doesn't overflow the output.
Make debugDumpApp() say what mode we're in (checked vs release).
Hide lifecycle state from release mode dumps (since it's checked-only state).
This commit is contained in:
Hixie 2015-10-15 11:07:46 -07:00
parent 6e91cfa49a
commit 1f40d96fbf
7 changed files with 160 additions and 35 deletions

View file

@ -11,6 +11,7 @@ import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'stock_data.dart';

View file

@ -112,7 +112,7 @@ class StockHomeState extends State<StockHome> {
),
new DrawerItem(
icon: 'device/dvr',
onPressed: () { debugDumpApp(); },
onPressed: () { debugDumpApp(); debugDumpRenderTree(); },
child: new Text('Dump App to Console')
),
new DrawerDivider(),

View file

@ -252,25 +252,131 @@ class TextStyle {
List<String> result = <String>[];
if (color != null)
result.add('${prefix}color: $color');
// TODO(hansmuller): escape the fontFamily string.
if (fontFamily != null)
result.add('${prefix}fontFamily: "$fontFamily"');
result.add('${prefix}family: "$fontFamily"');
if (fontSize != null)
result.add('${prefix}fontSize: $fontSize');
if (fontWeight != null)
result.add('${prefix}fontWeight: $fontWeight');
if (fontStyle != null)
result.add('${prefix}fontStyle: $fontStyle');
if (textAlign != null)
result.add('${prefix}textAlign: $textAlign');
if (textBaseline != null)
result.add('${prefix}textBaseline: $textBaseline');
if (decoration != null)
result.add('${prefix}decoration: $decoration');
if (decorationColor != null)
result.add('${prefix}decorationColor: $decorationColor');
if (decorationStyle != null)
result.add('${prefix}decorationStyle: $decorationStyle');
result.add('${prefix}size: $fontSize');
if (fontWeight != null) {
switch (fontWeight) {
case FontWeight.w100:
result.add('${prefix}weight: 100');
break;
case FontWeight.w200:
result.add('${prefix}weight: 200');
break;
case FontWeight.w300:
result.add('${prefix}weight: 300');
break;
case FontWeight.w400:
result.add('${prefix}weight: 400');
break;
case FontWeight.w500:
result.add('${prefix}weight: 500');
break;
case FontWeight.w600:
result.add('${prefix}weight: 600');
break;
case FontWeight.w700:
result.add('${prefix}weight: 700');
break;
case FontWeight.w800:
result.add('${prefix}weight: 800');
break;
case FontWeight.w900:
result.add('${prefix}weight: 900');
break;
}
}
if (fontStyle != null) {
switch (fontStyle) {
case FontStyle.normal:
result.add('${prefix}style: normal');
break;
case FontStyle.italic:
result.add('${prefix}style: italic');
break;
}
}
if (textAlign != null) {
switch (textAlign) {
case TextAlign.left:
result.add('${prefix}align: left');
break;
case TextAlign.right:
result.add('${prefix}align: right');
break;
case TextAlign.center:
result.add('${prefix}align: center');
break;
}
}
if (textBaseline != null) {
switch (textBaseline) {
case TextBaseline.alphabetic:
result.add('${prefix}baseline: alphabetic');
break;
case TextBaseline.ideographic:
result.add('${prefix}baseline: ideographic');
break;
}
}
if (decoration != null || decorationColor != null || decorationStyle != null) {
String decorationDescription = '${prefix}decoration: ';
bool haveDecorationDescription = false;
if (decorationStyle != null) {
switch (decorationStyle) {
case TextDecorationStyle.solid:
decorationDescription += 'solid';
break;
case TextDecorationStyle.double:
decorationDescription += 'double';
break;
case TextDecorationStyle.dotted:
decorationDescription += 'dotted';
break;
case TextDecorationStyle.dashed:
decorationDescription += 'dashed';
break;
case TextDecorationStyle.wavy:
decorationDescription += 'wavy';
break;
}
haveDecorationDescription = true;
}
if (decorationColor != null) {
if (haveDecorationDescription)
decorationDescription += ' ';
decorationDescription += '$decorationColor';
haveDecorationDescription = true;
}
if (decoration != null) {
if (haveDecorationDescription)
decorationDescription += ' ';
bool multipleDecorations = false;
for (TextDecoration value in decoration) {
if (multipleDecorations)
decorationDescription += '+';
switch (value) {
case TextDecoration.none:
decorationDescription += 'none';
break;
case TextDecoration.underline:
decorationDescription += 'underline';
break;
case TextDecoration.overline:
decorationDescription += 'overline';
break;
case TextDecoration.lineThrough:
decorationDescription += 'line-through';
break;
}
multipleDecorations = true;
}
haveDecorationDescription = true;
}
assert(haveDecorationDescription);
result.add(decorationDescription);
}
if (result.isEmpty)
return '$prefix<no style specified>';
return result.join('\n');

View file

@ -1139,11 +1139,18 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
/// Returns a description of the tree rooted at this node.
/// If the prefix argument is provided, then every line in the output
/// will be prefixed by that string.
String toStringDeep([String prefix = '']) {
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
RenderObject debugPreviousActiveLayout = _debugActiveLayout;
_debugActiveLayout = null;
prefix += ' ';
String result = '$this\n${debugDescribeSettings(prefix)}${debugDescribeChildren(prefix)}';
String result = '$prefixLineOne$this\n';
final String childrenDescription = debugDescribeChildren(prefixOtherLines);
final String settingsPrefix = childrenDescription != '' ? '$prefixOtherLines \u2502 ' : '$prefixOtherLines ';
result += debugDescribeSettings(settingsPrefix);
if (childrenDescription != '')
result += '$prefixOtherLines \u2502\n';
else
result += '$prefixOtherLines\n';
result += childrenDescription;
_debugActiveLayout = debugPreviousActiveLayout;
return result;
}
@ -1198,7 +1205,7 @@ abstract class RenderObjectWithChildMixin<ChildType extends RenderObject> implem
}
String debugDescribeChildren(String prefix) {
if (child != null)
return '${prefix}child: ${child.toStringDeep(prefix)}';
return '${child.toStringDeep("$prefix \u2514\u2500child: ", "$prefix ")}';
return '';
}
}
@ -1442,13 +1449,19 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
String debugDescribeChildren(String prefix) {
String result = '';
int count = 1;
ChildType child = _firstChild;
while (child != null) {
result += '${prefix}child $count: ${child.toStringDeep(prefix)}';
count += 1;
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
if (_firstChild != null) {
ChildType child = _firstChild;
int count = 1;
while (child != _lastChild) {
result += '${child.toStringDeep("$prefix \u251C\u2500child $count: ", "$prefix \u2502")}';
count += 1;
final ParentDataType childParentData = child.parentData;
child = childParentData.nextSibling;
}
if (child != null) {
assert(child == _lastChild);
result += '${child.toStringDeep("$prefix \u2514\u2500child $count: ", "$prefix ")}';
}
}
return result;
}

View file

@ -883,6 +883,7 @@ class FractionalOffset {
value = 37 * value + y.hashCode;
return value;
}
String toString() => '$runtimeType($x, $y)';
}
/// Applies a transformation before painting its child
@ -1016,7 +1017,7 @@ class RenderTransform extends RenderProxyBox {
String debugDescribeSettings(String prefix) {
List<String> result = _transform.toString().split('\n').map((String s) => '$prefix $s\n').toList();
result.removeLast();
return '${super.debugDescribeSettings(prefix)}${prefix}transform matrix:\n${result.join()}\n${prefix}origin: $origin\n';
return '${super.debugDescribeSettings(prefix)}${prefix}transform matrix:\n${result.join()}\n${prefix}origin: $origin\nalignment: $alignment\n';
}
}

View file

@ -92,6 +92,9 @@ void runApp(Widget app) {
void debugDumpApp() {
assert(WidgetFlutterBinding.instance != null);
assert(WidgetFlutterBinding.instance.renderViewElement != null);
String mode = 'RELEASE MODE';
assert(() { mode = 'CHECKED MODE'; return true; });
print('${WidgetFlutterBinding.instance.runtimeType} - $mode');
WidgetFlutterBinding.instance.renderViewElement.toStringDeep().split('\n').forEach(print);
}

View file

@ -394,8 +394,11 @@ abstract class State<T extends StatefulComponent> {
void debugFillDescription(List<String> description) {
description.add('$hashCode');
if (_debugLifecycleState != _StateLifecycle.ready)
description.add('$_debugLifecycleState');
assert(() {
if (_debugLifecycleState != _StateLifecycle.ready)
description.add('$_debugLifecycleState');
return true;
});
if (_config == null)
description.add('no config');
if (_element == null)
@ -829,9 +832,7 @@ abstract class Element<T extends Widget> implements BuildContext {
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
String result = '$prefixLineOne$this\n';
List<Element> children = <Element>[];
visitChildren((Element child) {
children.add(child);
});
visitChildren(children.add);
if (children.length > 0) {
Element last = children.removeLast();
for (Element child in children)