Merge pull request #2281 from Hixie/gradient

Convert LinearGradient and RadialGradient to fractional units
diff --git a/examples/layers/widgets/gestures.dart b/examples/layers/widgets/gestures.dart
index 6534567..0b456d8 100644
--- a/examples/layers/widgets/gestures.dart
+++ b/examples/layers/widgets/gestures.dart
@@ -29,12 +29,16 @@
     Point center = (size.center(Point.origin).toOffset() * zoom + offset).toPoint();
     double radius = size.width / 2.0 * zoom;
     Gradient gradient = new RadialGradient(
-      center: center, radius: radius,
       colors: forward ? <Color>[swatch[50], swatch[900]]
                       : <Color>[swatch[900], swatch[50]]
     );
     Paint paint = new Paint()
-      ..shader = gradient.createShader();
+      ..shader = gradient.createShader(new Rect.fromLTWH(
+        center.x - radius,
+        center.y - radius,
+        radius * 2.0,
+        radius * 2.0
+      ));
     canvas.drawCircle(center, radius, paint);
   }
 
diff --git a/examples/material_gallery/lib/demo/weathers_demo.dart b/examples/material_gallery/lib/demo/weathers_demo.dart
index 2613678..942255e 100644
--- a/examples/material_gallery/lib/demo/weathers_demo.dart
+++ b/examples/material_gallery/lib/demo/weathers_demo.dart
@@ -276,14 +276,15 @@
   void paint(Canvas canvas) {
     applyTransformForPivot(canvas);
 
+    Rect rect = Point.origin & size;
     Paint gradientPaint = new Paint()..shader = new LinearGradient(
-      begin: Point.origin,
-      end: new Point(0.0, size.height),
+      begin: const Offset(0.0, 0.0),
+      end: const Offset(0.0, 1.0),
       colors: <Color>[colorTop, colorBottom],
       stops: <double>[0.0, 1.0]
-    ).createShader();
+    ).createShader(rect);
 
-    canvas.drawRect(new Rect.fromLTWH(0.0, 0.0, size.width, size.height), gradientPaint);
+    canvas.drawRect(rect, gradientPaint);
   }
 }
 
diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart
index 9f5cf28..dce01de 100644
--- a/packages/flutter/lib/src/painting/box_painter.dart
+++ b/packages/flutter/lib/src/painting/box_painter.dart
@@ -235,27 +235,43 @@
   String toString() => 'BoxShadow($color, $offset, $blurRadius, $spreadRadius)';
 }
 
+// TODO(ianh): We should probably expose something that does this on Rect.
+// https://github.com/flutter/flutter/issues/2318
+Point _offsetToPoint(Offset offset, Rect rect) {
+  return new Point(rect.left + offset.dx * rect.width, rect.top + offset.dy * rect.height);
+}
+
 /// A 2D gradient.
 abstract class Gradient {
   const Gradient();
-  Shader createShader();
+  Shader createShader(Rect rect);
 }
 
 /// A 2D linear gradient.
 class LinearGradient extends Gradient {
   const LinearGradient({
-    this.begin,
-    this.end,
+    this.begin: const Offset(0.0, 0.5),
+    this.end: const Offset(1.0, 0.5),
     this.colors,
     this.stops,
     this.tileMode: TileMode.clamp
   });
 
-  /// The point at which stop 0.0 of the gradient is placed.
-  final Point begin;
+  /// The offset from coordinate (0.0,0.0) at which stop 0.0 of the
+  /// gradient is placed, in a coordinate space that maps the top left
+  /// of the paint box at (0.0,0.0) and the bottom right at (1.0,1.0).
+  ///
+  /// For example, a begin offset of (0.0,0.5) is half way down the
+  /// left side of the box.
+  final Offset begin;
 
-  /// The point at which stop 1.0 of the gradient is placed.
-  final Point end;
+  /// The offset from coordinate (0.0,0.0) at which stop 1.0 of the
+  /// gradient is placed, in a coordinate space that maps the top left
+  /// of the paint box at (0.0,0.0) and the bottom right at (1.0,1.0).
+  ///
+  /// For example, an end offset of (1.0,0.5) is half way down the
+  /// right side of the box.
+  final Offset end;
 
   /// The colors the gradient should obtain at each of the stops.
   ///
@@ -271,8 +287,11 @@
   /// How this gradient should tile the plane.
   final TileMode tileMode;
 
-  Shader createShader() {
-    return new ui.Gradient.linear(<Point>[begin, end], this.colors, this.stops, this.tileMode);
+  Shader createShader(Rect rect) {
+    return new ui.Gradient.linear(
+      <Point>[_offsetToPoint(begin, rect), _offsetToPoint(end, rect)],
+      colors, stops, tileMode
+    );
   }
 
   bool operator ==(dynamic other) {
@@ -316,17 +335,26 @@
 /// A 2D radial gradient.
 class RadialGradient extends Gradient {
   const RadialGradient({
-    this.center,
-    this.radius,
+    this.center: const Offset(0.5, 0.5),
+    this.radius: 0.5,
     this.colors,
     this.stops,
     this.tileMode: TileMode.clamp
   });
 
-  /// The center of the gradient.
-  final Point center;
+  /// The center of the gradient, as an offset into the unit square
+  /// describing the gradient which will be mapped onto the paint box.
+  ///
+  /// For example, an offset of (0.5,0.5) will place the radial
+  /// gradient in the center of the box.
+  final Offset center;
 
-  /// The radius at which stop 1.0 is placed.
+  /// The radius of the gradient, as a fraction of the shortest side
+  /// of the paint box.
+  ///
+  /// For example, if a radial gradient is painted on a box that is
+  /// 100.0 pixels wide and 200.0 pixels tall, then a radius of 1.0
+  /// will place the 1.0 stop at 100.0 pixels from the [center].
   final double radius;
 
   /// The colors the gradient should obtain at each of the stops.
@@ -345,8 +373,12 @@
   /// How this gradient should tile the plane.
   final TileMode tileMode;
 
-  Shader createShader() {
-    return new ui.Gradient.radial(center, radius, colors, stops, tileMode);
+  Shader createShader(Rect rect) {
+    return new ui.Gradient.radial(
+      _offsetToPoint(center, rect),
+      radius * rect.shortestSide,
+      colors, stops, tileMode
+    );
   }
 
   bool operator ==(dynamic other) {
@@ -903,15 +935,23 @@
   final BoxDecoration _decoration;
 
   Paint _cachedBackgroundPaint;
-  Paint get _backgroundPaint {
-    if (_cachedBackgroundPaint == null) {
+  Rect _rectForCachedBackgroundPaint;
+  Paint _getBackgroundPaint(Rect rect) {
+    assert(rect != null);
+    if (_cachedBackgroundPaint == null ||
+        (_decoration.gradient == null && _rectForCachedBackgroundPaint != null) ||
+        (_decoration.gradient != null && _rectForCachedBackgroundPaint != rect)) {
       Paint paint = new Paint();
 
       if (_decoration.backgroundColor != null)
         paint.color = _decoration.backgroundColor;
 
-      if (_decoration.gradient != null)
-        paint.shader = _decoration.gradient.createShader();
+      if (_decoration.gradient != null) {
+        paint.shader = _decoration.gradient.createShader(rect);
+        _rectForCachedBackgroundPaint = rect;
+      } else {
+        _rectForCachedBackgroundPaint = null;
+      }
 
       _cachedBackgroundPaint = paint;
     }
@@ -971,7 +1011,7 @@
 
   void _paintBackgroundColor(Canvas canvas, Rect rect) {
     if (_decoration.backgroundColor != null || _decoration.gradient != null)
-      _paintBox(canvas, rect, _backgroundPaint);
+      _paintBox(canvas, rect, _getBackgroundPaint(rect));
   }
 
   void _paintBackgroundImage(Canvas canvas, Rect rect) {
diff --git a/packages/flutter/test/rendering/box_test.dart b/packages/flutter/test/rendering/box_test.dart
index 0fcda75..7c42c4c 100644
--- a/packages/flutter/test/rendering/box_test.dart
+++ b/packages/flutter/test/rendering/box_test.dart
@@ -14,7 +14,7 @@
       decoration: new BoxDecoration(
         backgroundColor: const Color(0xFF00FF00),
         gradient: new RadialGradient(
-          center: Point.origin, radius: 500.0,
+          center: Offset.zero, radius: 1.8,
           colors: <Color>[Colors.yellow[500], Colors.blue[500]]),
         boxShadow: elevationToShadow[3])
     );
diff --git a/packages/flutter/test/widget/shader_mask_test.dart b/packages/flutter/test/widget/shader_mask_test.dart
index 72a1df7..b65b308 100644
--- a/packages/flutter/test/widget/shader_mask_test.dart
+++ b/packages/flutter/test/widget/shader_mask_test.dart
@@ -10,12 +10,12 @@
 
 Shader createShader(Rect bounds) {
   return new LinearGradient(
-      begin: bounds.topLeft,
-      end: bounds.bottomLeft,
+      begin: const Offset(0.0, 0.0),
+      end: const Offset(0.0, 1.0),
       colors: <Color>[const Color(0x00FFFFFF), const Color(0xFFFFFFFF)],
       stops: <double>[0.1, 0.35]
   )
-  .createShader();
+  .createShader(bounds);
 }