Merge pull request #1657 from Hixie/debugging

Reduce latency of low-volume debugPrint output
diff --git a/sky/engine/core/painting/Offset.dart b/sky/engine/core/painting/Offset.dart
index 744e532..88771ef 100644
--- a/sky/engine/core/painting/Offset.dart
+++ b/sky/engine/core/painting/Offset.dart
@@ -61,5 +61,5 @@
   /// Compares two Offsets for equality.
   bool operator ==(dynamic other) => other is Offset && super == other;
 
-  String toString() => "Offset($dx, $dy)";
+  String toString() => "Offset(${dx.toStringAsFixed(1)}, ${dy.toStringAsFixed(1)})";
 }
diff --git a/sky/engine/core/painting/Point.dart b/sky/engine/core/painting/Point.dart
index 3ba60c1..68e6b37 100644
--- a/sky/engine/core/painting/Point.dart
+++ b/sky/engine/core/painting/Point.dart
@@ -36,5 +36,5 @@
     return result;
   }
 
-  String toString() => "Point($x, $y)";
+  String toString() => "Point(${x.toStringAsFixed(1)}, ${y.toStringAsFixed(1)})";
 }
diff --git a/sky/engine/core/painting/Rect.dart b/sky/engine/core/painting/Rect.dart
index 0a45764..42d1329 100644
--- a/sky/engine/core/painting/Rect.dart
+++ b/sky/engine/core/painting/Rect.dart
@@ -142,5 +142,5 @@
 
   int get hashCode => _value.fold(373, (value, item) => (37 * value + item.hashCode));
 
-  String toString() => "Rect.fromLTRB($left, $top, $right, $bottom)";
+  String toString() => "Rect.fromLTRB(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)})";
 }
diff --git a/sky/engine/core/painting/Size.dart b/sky/engine/core/painting/Size.dart
index 94a0d15..7af64da 100644
--- a/sky/engine/core/painting/Size.dart
+++ b/sky/engine/core/painting/Size.dart
@@ -61,5 +61,5 @@
   /// Compares two Sizes for equality.
   bool operator ==(dynamic other) => other is Size && super == other;
 
-  String toString() => "Size($width, $height)";
+  String toString() => "Size(${width.toStringAsFixed(1)}, ${height.toStringAsFixed(1)})";
 }
diff --git a/sky/packages/sky/lib/src/material/icon.dart b/sky/packages/sky/lib/src/material/icon.dart
index 8c25d35..46b46ab 100644
--- a/sky/packages/sky/lib/src/material/icon.dart
+++ b/sky/packages/sky/lib/src/material/icon.dart
@@ -23,6 +23,8 @@
   }
 
   int get hashCode => color.hashCode;
+
+  String toString() => '$color';
 }
 
 class IconTheme extends InheritedWidget {
@@ -45,6 +47,10 @@
 
   bool updateShouldNotify(IconTheme old) => data != old.data;
 
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('$data');
+  }
 }
 
 AssetBundle _initIconBundle() {
@@ -63,7 +69,10 @@
     this.type: '',
     this.color,
     this.colorFilter
-  }) : super(key: key);
+  }) : super(key: key) {
+    assert(size != null);
+    assert(type != null);
+  }
 
   final int size;
   final String type;
@@ -108,4 +117,10 @@
       colorFilter: colorFilter
     );
   }
+
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('$type');
+    description.add('size: $size');
+  }
 }
diff --git a/sky/packages/sky/lib/src/material/icon_button.dart b/sky/packages/sky/lib/src/material/icon_button.dart
index f629806..4ea19a3 100644
--- a/sky/packages/sky/lib/src/material/icon_button.dart
+++ b/sky/packages/sky/lib/src/material/icon_button.dart
@@ -36,4 +36,9 @@
       )
     );
   }
+
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('$icon');
+  }
 }
diff --git a/sky/packages/sky/lib/src/material/material_button.dart b/sky/packages/sky/lib/src/material/material_button.dart
index bf5380d..1eb4591 100644
--- a/sky/packages/sky/lib/src/material/material_button.dart
+++ b/sky/packages/sky/lib/src/material/material_button.dart
@@ -47,6 +47,12 @@
   final bool enabled;
   final ButtonColor textColor;
   final GestureTapCallback onPressed;
+
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    if (!enabled)
+      description.add('disabled');
+  }
 }
 
 abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
diff --git a/sky/packages/sky/lib/src/material/progress_indicator.dart b/sky/packages/sky/lib/src/material/progress_indicator.dart
index 73052ec..e688a44 100644
--- a/sky/packages/sky/lib/src/material/progress_indicator.dart
+++ b/sky/packages/sky/lib/src/material/progress_indicator.dart
@@ -14,15 +14,15 @@
 const double _kMinCircularProgressIndicatorSize = 15.0;
 const double _kCircularProgressIndicatorStrokeWidth = 3.0;
 
+// TODO(hansmuller) implement the support for buffer indicator
+
 abstract class ProgressIndicator extends StatefulComponent {
   ProgressIndicator({
     Key key,
-    this.value,
-    this.bufferValue
+    this.value
   }) : super(key: key);
 
   final double value; // Null for non-determinate progress indicator.
-  final double bufferValue; // TODO(hansmuller) implement the support for this.
 
   Color _getBackgroundColor(BuildContext context) => Theme.of(context).primarySwatch[200];
   Color _getValueColor(BuildContext context) => Theme.of(context).primaryColor;
@@ -31,6 +31,11 @@
   Widget _buildIndicator(BuildContext context, double performanceValue);
 
   _ProgressIndicatorState createState() => new _ProgressIndicatorState();
+
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('${(value.clamp(0.0, 1.0) * 100.0).toStringAsFixed(1)}%');
+  }
 }
 
 class _ProgressIndicatorState extends State<ProgressIndicator> {
@@ -72,9 +77,8 @@
 class LinearProgressIndicator extends ProgressIndicator {
   LinearProgressIndicator({
     Key key,
-    double value,
-    double bufferValue
-  }) : super(key: key, value: value, bufferValue: bufferValue);
+    double value
+  }) : super(key: key, value: value);
 
   void _paint(BuildContext context, double performanceValue, Canvas canvas, Size size) {
     Paint paint = new Paint()
@@ -120,9 +124,8 @@
 
   CircularProgressIndicator({
     Key key,
-    double value,
-    double bufferValue
-  }) : super(key: key, value: value, bufferValue: bufferValue);
+    double value
+  }) : super(key: key, value: value);
 
   void _paint(BuildContext context, double performanceValue, Canvas canvas, Size size) {
     Paint paint = new Paint()
diff --git a/sky/packages/sky/lib/src/material/tabs.dart b/sky/packages/sky/lib/src/material/tabs.dart
index 00025c8..3a9d40e 100644
--- a/sky/packages/sky/lib/src/material/tabs.dart
+++ b/sky/packages/sky/lib/src/material/tabs.dart
@@ -281,6 +281,16 @@
 
   final String text;
   final String icon;
+
+  String toString() {
+    if (text != null && icon != null)
+      return '"$text" ($icon)';
+    if (text != null)
+      return '"$text"';
+    if (icon != null)
+      return '$icon';
+    return 'EMPTY TAB LABEL';
+  }
 }
 
 class Tab extends StatelessComponent {
@@ -345,6 +355,11 @@
       child: centeredLabel
     );
   }
+
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('$label');
+  }
 }
 
 class _TabsScrollBehavior extends BoundedBehavior {
diff --git a/sky/packages/sky/lib/src/material/theme.dart b/sky/packages/sky/lib/src/material/theme.dart
index aeff56f..7d9233a 100644
--- a/sky/packages/sky/lib/src/material/theme.dart
+++ b/sky/packages/sky/lib/src/material/theme.dart
@@ -28,4 +28,9 @@
   }
 
   bool updateShouldNotify(Theme old) => data != old.data;
+
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('$data');
+  }
 }
diff --git a/sky/packages/sky/lib/src/material/theme_data.dart b/sky/packages/sky/lib/src/material/theme_data.dart
index 7566fd4..3b789d0 100644
--- a/sky/packages/sky/lib/src/material/theme_data.dart
+++ b/sky/packages/sky/lib/src/material/theme_data.dart
@@ -124,4 +124,6 @@
     value = 37 * value + accentColorBrightness.hashCode;
     return value;
   }
+
+  String toString() => '$primaryColor $brightness etc...';
 }
diff --git a/sky/packages/sky/lib/src/material/title.dart b/sky/packages/sky/lib/src/material/title.dart
index deca5d1..293320c 100644
--- a/sky/packages/sky/lib/src/material/title.dart
+++ b/sky/packages/sky/lib/src/material/title.dart
@@ -17,4 +17,9 @@
     updateTaskDescription(title, Theme.of(context).primaryColor);
     return child;
   }
+
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    description.add('"$title"');
+  }
 }
diff --git a/sky/packages/sky/lib/src/rendering/box.dart b/sky/packages/sky/lib/src/rendering/box.dart
index b26a99a..2341e81 100644
--- a/sky/packages/sky/lib/src/rendering/box.dart
+++ b/sky/packages/sky/lib/src/rendering/box.dart
@@ -254,7 +254,21 @@
     return value;
   }
 
-  String toString() => "BoxConstraints($minWidth<=w<=$maxWidth, $minHeight<=h<=$maxHeight)";
+  String toString() {
+    if (minWidth == double.INFINITY && minHeight == double.INFINITY)
+      return 'BoxConstraints(biggest)';
+    if (minWidth == 0 && maxWidth == double.INFINITY &&
+        minHeight == 0 && maxHeight == double.INFINITY)
+      return 'BoxConstraints(unconstrained)';
+    String describe(double min, double max, String dim) {
+      if (min == max)
+        return '$dim=${min.toStringAsFixed(1)}';
+      return '${min.toStringAsFixed(1)}<=$dim<=${max.toStringAsFixed(1)}';
+    }
+    final String width = describe(minWidth, maxWidth, 'w');
+    final String height = describe(minHeight, maxHeight, 'h');
+    return 'BoxConstraints($width, $height)';
+  }
 }
 
 /// A hit test entry used by [RenderBox]
diff --git a/sky/packages/sky/lib/src/rendering/stack.dart b/sky/packages/sky/lib/src/rendering/stack.dart
index c6ba3f6..54b329b 100644
--- a/sky/packages/sky/lib/src/rendering/stack.dart
+++ b/sky/packages/sky/lib/src/rendering/stack.dart
@@ -42,8 +42,8 @@
     return new RelativeRect.fromLTRB(
       rect.left - container.left,
       rect.top - container.top,
-      container.right - rect.left + rect.width,
-      container.bottom - rect.top + rect.height
+      container.right - rect.right,
+      container.bottom - rect.bottom
     );
   }
 
@@ -132,7 +132,7 @@
     return value;
   }
 
-  String toString() => "RelativeRect.fromLTRB($left, $top, $right, $bottom)";
+  String toString() => "RelativeRect.fromLTRB(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)})";
 }
 
 /// Parent data for use with [RenderStack]
diff --git a/sky/packages/sky/lib/src/rendering/view.dart b/sky/packages/sky/lib/src/rendering/view.dart
index 7b0d354..a9b739b 100644
--- a/sky/packages/sky/lib/src/rendering/view.dart
+++ b/sky/packages/sky/lib/src/rendering/view.dart
@@ -23,6 +23,8 @@
 
   /// The orientation of the output surface (aspirational)
   final int orientation;
+
+  String toString() => '$size';
 }
 
 /// The root of the render tree
@@ -53,7 +55,7 @@
   ViewConstraints get rootConstraints => _rootConstraints;
   ViewConstraints _rootConstraints;
   void set rootConstraints(ViewConstraints value) {
-    if (_rootConstraints == value)
+    if (rootConstraints == value)
       return;
     _rootConstraints = value;
     markNeedsLayout();
@@ -80,12 +82,12 @@
   }
 
   void performLayout() {
-    if (_rootConstraints.orientation != _orientation) {
+    if (rootConstraints.orientation != _orientation) {
       if (_orientation != null && child != null)
-        child.rotate(oldAngle: _orientation, newAngle: _rootConstraints.orientation, time: timeForRotation);
-      _orientation = _rootConstraints.orientation;
+        child.rotate(oldAngle: _orientation, newAngle: rootConstraints.orientation, time: timeForRotation);
+      _orientation = rootConstraints.orientation;
     }
-    _size = _rootConstraints.size;
+    _size = rootConstraints.size;
     assert(!_size.isInfinite);
 
     if (child != null)
@@ -127,4 +129,7 @@
   }
 
   Rect get paintBounds => Point.origin & size;
+
+  String debugDescribeSettings(String prefix) => '${prefix}view width: ${ui.view.width} (in device pixels)\n${prefix}view height: ${ui.view.height} (in device pixels)\n${prefix}device pixel ratio: ${ui.view.devicePixelRatio} (device pixels per logical pixel)\n${prefix}root constraints: $rootConstraints (in logical pixels)\n';
+  // call to ${super.debugDescribeSettings(prefix)} is omitted because the root superclasses don't include any interesting information for this class
 }
diff --git a/sky/packages/sky/lib/src/widgets/animated_container.dart b/sky/packages/sky/lib/src/widgets/animated_container.dart
index 307fdd4..8017dba 100644
--- a/sky/packages/sky/lib/src/widgets/animated_container.dart
+++ b/sky/packages/sky/lib/src/widgets/animated_container.dart
@@ -124,7 +124,6 @@
   void _updateAllVariables() {
     setState(() {
       _updateVariable(_constraints);
-      _updateVariable(_constraints);
       _updateVariable(_decoration);
       _updateVariable(_foregroundDecoration);
       _updateVariable(_margin);
@@ -224,4 +223,24 @@
       height: _height?.value
     );
   }
+
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    if (_constraints != null)
+      description.add('has constraints');
+    if (_decoration != null)
+      description.add('has background');
+    if (_foregroundDecoration != null)
+      description.add('has foreground');
+    if (_margin != null)
+      description.add('has margin');
+    if (_padding != null)
+      description.add('has padding');
+    if (_transform != null)
+      description.add('has transform');
+    if (_width != null)
+      description.add('has width');
+    if (_height != null)
+      description.add('has height');
+  }
 }
diff --git a/sky/packages/sky/lib/src/widgets/basic.dart b/sky/packages/sky/lib/src/widgets/basic.dart
index 776e33b..ac64a03 100644
--- a/sky/packages/sky/lib/src/widgets/basic.dart
+++ b/sky/packages/sky/lib/src/widgets/basic.dart
@@ -314,7 +314,7 @@
 
   void debugFillDescription(List<String> description) {
     super.debugFillDescription(description);
-    description.add('constraints: $constraints');
+    description.add('$constraints');
   }
 }
 
@@ -549,6 +549,26 @@
     return current;
   }
 
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    if (constraints != null)
+      description.add('$constraints');
+    if (decoration != null)
+      description.add('has background');
+    if (foregroundDecoration != null)
+      description.add('has foreground');
+    if (margin != null)
+      description.add('margin: $margin');
+    if (padding != null)
+      description.add('padding: $padding');
+    if (transform != null)
+      description.add('has transform');
+    if (width != null)
+      description.add('width: $width');
+    if (height != null)
+      description.add('height: $height');
+  }
+
 }
 
 
@@ -672,8 +692,11 @@
       needsLayout = true;
     }
 
-    if (needsLayout)
-      renderObject.markNeedsLayout();
+    if (needsLayout) {
+      AbstractNode targetParent = renderObject.parent;
+      if (targetParent is RenderObject)
+        targetParent.markNeedsLayout();
+    }
   }
 
   void debugFillDescription(List<String> description) {
@@ -768,7 +791,9 @@
     final FlexParentData parentData = renderObject.parentData;
     if (parentData.flex != flex) {
       parentData.flex = flex;
-      renderObject.markNeedsLayout();
+      AbstractNode targetParent = renderObject.parent;
+      if (targetParent is RenderObject)
+        targetParent.markNeedsLayout();
     }
   }
 
diff --git a/sky/packages/sky/lib/src/widgets/focus.dart b/sky/packages/sky/lib/src/widgets/focus.dart
index 5f2f129..c5592b0 100644
--- a/sky/packages/sky/lib/src/widgets/focus.dart
+++ b/sky/packages/sky/lib/src/widgets/focus.dart
@@ -22,8 +22,8 @@
     Widget child
   }) : super(key: key, child: child);
 
-  final bool scopeFocused;
   final FocusState focusState;
+  final bool scopeFocused;
 
   // These are mutable because we implicitly change them when they're null in
   // certain cases, basically pretending retroactively that we were constructed
@@ -61,6 +61,15 @@
     return false;
   }
 
+  void debugFillDescription(List<String> description) {
+    super.debugFillDescription(description);
+    if (scopeFocused)
+      description.add('this scope has focus');
+    if (focusedScope != null)
+      description.add('focused subscope: $focusedScope');
+    if (focusedWidget != null)
+      description.add('focused widget: $focusedWidget');
+  }
 }
 
 class Focus extends StatefulComponent {
diff --git a/sky/packages/sky/lib/src/widgets/transitions.dart b/sky/packages/sky/lib/src/widgets/transitions.dart
index bd2f7da..c696131 100644
--- a/sky/packages/sky/lib/src/widgets/transitions.dart
+++ b/sky/packages/sky/lib/src/widgets/transitions.dart
@@ -4,11 +4,13 @@
 
 import 'package:flutter/animation.dart';
 import 'package:vector_math/vector_math_64.dart' show Matrix4;
+import 'package:flutter/rendering.dart';
 
 import 'basic.dart';
 import 'framework.dart';
 
 export 'package:flutter/animation.dart' show AnimationDirection;
+export 'package:flutter/rendering.dart' show RelativeRect;
 
 abstract class TransitionComponent extends StatefulComponent {
   TransitionComponent({
@@ -150,6 +152,47 @@
   }
 }
 
+/// An animated variable containing a RelativeRectangle
+///
+/// This class specializes the interpolation of AnimatedValue<RelativeRect> to
+/// be appropriate for rectangles that are described in terms of offsets from
+/// other rectangles.
+class AnimatedRelativeRectValue extends AnimatedValue<RelativeRect> {
+  AnimatedRelativeRectValue(RelativeRect begin, { RelativeRect end, Curve curve, Curve reverseCurve })
+    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
+
+  RelativeRect lerp(double t) => RelativeRect.lerp(begin, end, t);
+}
+
+/// Animated version of [Positioned].
+/// Only works if it's the child of a [Stack].
+class PositionedTransition extends TransitionWithChild {
+  PositionedTransition({
+    Key key,
+    this.rect,
+    PerformanceView performance,
+    Widget child
+  }) : super(key: key,
+             performance: performance,
+             child: child) {
+    assert(rect != null);
+  }
+
+  final AnimatedRelativeRectValue rect;
+
+  Widget buildWithChild(BuildContext context, Widget child) {
+    performance.updateVariable(rect);
+    return new Positioned(
+      top: rect.value.top,
+      right: rect.value.right,
+      bottom: rect.value.bottom,
+      left: rect.value.left,
+      child: child
+    );
+  }
+}
+
+
 typedef Widget BuilderFunction(BuildContext context);
 
 class BuilderTransition extends TransitionComponent {
diff --git a/sky/unit/test/widget/positioned_test.dart b/sky/unit/test/widget/positioned_test.dart
new file mode 100644
index 0000000..7adecc8
--- /dev/null
+++ b/sky/unit/test/widget/positioned_test.dart
@@ -0,0 +1,74 @@
+import 'package:flutter/animation.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/widgets.dart';
+import 'package:test/test.dart';
+
+import 'widget_tester.dart';
+
+void main() {
+
+  test('Can animate position data', () {
+    testWidgets((WidgetTester tester) {
+
+      final AnimatedRelativeRectValue rect = new AnimatedRelativeRectValue(
+        new RelativeRect.fromRect(
+          new Rect.fromLTRB(10.0, 20.0, 20.0, 30.0),
+          new Rect.fromLTRB(0.0, 10.0, 100.0, 110.0)
+        ),
+        end: new RelativeRect.fromRect(
+          new Rect.fromLTRB(80.0, 90.0, 90.0, 100.0),
+          new Rect.fromLTRB(0.0, 10.0, 100.0, 110.0)
+        ),
+        curve: linear
+      );
+      final Performance performance = new Performance(
+        duration: const Duration(seconds: 10)
+      );
+      final List<Size> sizes = <Size>[];
+      final List<Point> positions = <Point>[];
+      final GlobalKey key = new GlobalKey();
+
+      void recordMetrics() {
+        RenderBox box = key.currentContext.findRenderObject();
+        BoxParentData boxParentData = box.parentData;
+        sizes.add(box.size);
+        positions.add(boxParentData.position);
+      }
+
+      tester.pumpWidget(
+        new Center(
+          child: new Container(
+            height: 100.0,
+            width: 100.0,
+            child: new Stack(<Widget>[
+              new PositionedTransition(
+                rect: rect,
+                performance: performance,
+                child: new Container(
+                  key: key
+                )
+              )
+            ])
+          )
+        )
+      ); // t=0
+      recordMetrics();
+      performance.play();
+      tester.pump(); // t=0 again
+      recordMetrics();
+      tester.pump(const Duration(seconds: 1)); // t=1
+      recordMetrics();
+      tester.pump(const Duration(seconds: 1)); // t=2
+      recordMetrics();
+      tester.pump(const Duration(seconds: 3)); // t=5
+      recordMetrics();
+      tester.pump(const Duration(seconds: 5)); // t=10
+      recordMetrics();
+
+      expect(sizes, equals([const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0)]));
+      expect(positions, equals([const Point(10.0, 10.0), const Point(10.0, 10.0), const Point(17.0, 17.0), const Point(24.0, 24.0), const Point(45.0, 45.0), const Point(80.0, 80.0)]));
+
+    });
+  });
+
+}