Give Interval a Curve to apply between start and end

This patch simplifies AnimationTiming and all the AnimatedValue base classes.
Also, make PopupMenu a stateless component because it has no state.

Fixes #1168
diff --git a/examples/widgets/progress_indicator.dart b/examples/widgets/progress_indicator.dart
index e769784..b4b2b41 100644
--- a/examples/widgets/progress_indicator.dart
+++ b/examples/widgets/progress_indicator.dart
@@ -18,9 +18,8 @@
       ..variable = new AnimatedValue<double>(
         0.0,
         end: 1.0,
-        curve: ease,
-        reverseCurve: ease,
-        interval: new Interval(0.0, 0.9)
+        curve: new Interval(0.0, 0.9, curve: ease),
+        reverseCurve: ease
       );
     valueAnimation.addStatusListener((PerformanceStatus status) {
       if (status == PerformanceStatus.dismissed || status == PerformanceStatus.completed)
diff --git a/packages/flutter/lib/src/animation/animated_value.dart b/packages/flutter/lib/src/animation/animated_value.dart
index f42c4b8..9b4a8da 100644
--- a/packages/flutter/lib/src/animation/animated_value.dart
+++ b/packages/flutter/lib/src/animation/animated_value.dart
@@ -25,70 +25,43 @@
   String toString();
 }
 
-/// Used by [AnimationPerformance] to convert the timing of a performance to a different timescale.
+/// Used by [Performance] to convert the timing of a performance to a different timescale.
 /// For example, by setting different values for the interval and reverseInterval, a performance
 /// can be made to take longer in one direction that the other.
 class AnimationTiming {
-  AnimationTiming({
-    this.interval: const Interval(0.0, 1.0),
-    this.reverseInterval,
-    this.curve: linear,
-    this.reverseCurve
-  });
+  AnimationTiming({ this.curve, this.reverseCurve });
 
-  /// The interval during which this timing is active in the forward direction
-  Interval interval;
-
-  /// The interval during which this timing is active in the reverse direction
-  ///
-  /// If this field is null, the timing defaults to using [interval] in both directions.
-  Interval reverseInterval;
-
-  /// The curve that this timing applies to the animation clock in the forward direction
+  /// The curve to use in the forward direction
   Curve curve;
 
-  /// The curve that this timing applies to the animation clock in the reverse direction
+  /// The curve to use in the reverse direction
   ///
-  /// If this field is null, the timing defaults to using [curve] in both directions.
+  /// If this field is null, use [curve] in both directions.
   Curve reverseCurve;
 
   /// Applies this timing to the given animation clock value in the given direction
   double transform(double t, AnimationDirection direction) {
-    Interval interval = _getInterval(direction);
-    if (interval != null)
-      t = interval.transform(t);
-    assert(t >= 0.0 && t <= 1.0);
+    Curve activeCurve = _getActiveCurve(direction);
+    if (activeCurve == null)
+      return t;
     if (t == 0.0 || t == 1.0) {
-      assert(t == _applyCurve(t, direction).round().toDouble());
+      assert(activeCurve.transform(t).round() == t);
       return t;
     }
-    return _applyCurve(t, direction);
+    return activeCurve.transform(t);
   }
 
-  Interval _getInterval(AnimationDirection direction) {
-    if (direction == AnimationDirection.forward || reverseInterval == null)
-      return interval;
-    return reverseInterval;
-  }
-
-  Curve _getCurve(AnimationDirection direction) {
+  Curve _getActiveCurve(AnimationDirection direction) {
     if (direction == AnimationDirection.forward || reverseCurve == null)
       return curve;
     return reverseCurve;
   }
-
-  double _applyCurve(double t, AnimationDirection direction) {
-    Curve curve = _getCurve(direction);
-    if (curve == null)
-      return t;
-    return curve.transform(t);
-  }
 }
 
 /// An animated variable with a concrete type
 class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animatable {
-  AnimatedValue(this.begin, { this.end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
-    : super(interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve) {
+  AnimatedValue(this.begin, { this.end, Curve curve, Curve reverseCurve })
+    : super(curve: curve, reverseCurve: reverseCurve) {
     value = begin;
   }
 
@@ -125,8 +98,8 @@
 /// This class specializes the interpolation of AnimatedValue<Color> to be
 /// appropriate for colors.
 class AnimatedColorValue extends AnimatedValue<Color> {
-  AnimatedColorValue(Color begin, { Color end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
-    : super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve);
+  AnimatedColorValue(Color begin, { Color end, Curve curve, Curve reverseCurve })
+    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
 
   Color lerp(double t) => Color.lerp(begin, end, t);
 }
@@ -136,8 +109,8 @@
 /// This class specializes the interpolation of AnimatedValue<Rect> to be
 /// appropriate for rectangles.
 class AnimatedRectValue extends AnimatedValue<Rect> {
-  AnimatedRectValue(Rect begin, { Rect end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
-    : super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve);
+  AnimatedRectValue(Rect begin, { Rect end, Curve curve, Curve reverseCurve })
+    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
 
   Rect lerp(double t) => Rect.lerp(begin, end, t);
 }
diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart
index 31fb4a9..5b44cc4 100644
--- a/packages/flutter/lib/src/animation/curves.dart
+++ b/packages/flutter/lib/src/animation/curves.dart
@@ -27,9 +27,9 @@
   double transform(double t) => t;
 }
 
-/// A curve that is 0.0 until start, then linear from 0.0 to 1.0 at end, then 1.0
+/// A curve that is 0.0 until start, then curved from 0.0 to 1.0 at end, then 1.0
 class Interval implements Curve {
-  const Interval(this.start, this.end);
+  const Interval(this.start, this.end, { this.curve: linear });
 
   /// The smallest value for which this interval is 0.0
   final double start;
@@ -37,12 +37,18 @@
   /// The smallest value for which this interval is 1.0
   final double end;
 
+  /// The curve to apply between [start] and [end]
+  final Curve curve;
+
   double transform(double t) {
     assert(start >= 0.0);
     assert(start <= 1.0);
     assert(end >= 0.0);
     assert(end <= 1.0);
-    return ((t - start) / (end - start)).clamp(0.0, 1.0);
+    t = ((t - start) / (end - start)).clamp(0.0, 1.0);
+    if (t == 0.0 || t == 1.0)
+      return t;
+    return curve.transform(t);
   }
 }
 
diff --git a/packages/flutter/lib/src/widgets/animated_container.dart b/packages/flutter/lib/src/widgets/animated_container.dart
index 4d1f1d4..e9c56a1 100644
--- a/packages/flutter/lib/src/widgets/animated_container.dart
+++ b/packages/flutter/lib/src/widgets/animated_container.dart
@@ -9,29 +9,29 @@
 import 'package:vector_math/vector_math_64.dart';
 
 class AnimatedBoxConstraintsValue extends AnimatedValue<BoxConstraints> {
-  AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve: linear })
-    : super(begin, end: end, curve: curve);
+  AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve, Curve reverseCurve })
+    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
 
   BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t);
 }
 
 class AnimatedBoxDecorationValue extends AnimatedValue<BoxDecoration> {
-  AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear })
-    : super(begin, end: end, curve: curve);
+  AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve, Curve reverseCurve })
+    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
 
   BoxDecoration lerp(double t) => BoxDecoration.lerp(begin, end, t);
 }
 
 class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> {
-  AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve: linear })
-    : super(begin, end: end, curve: curve);
+  AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve, Curve reverseCurve })
+    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
 
   EdgeDims lerp(double t) => EdgeDims.lerp(begin, end, t);
 }
 
 class AnimatedMatrix4Value extends AnimatedValue<Matrix4> {
-  AnimatedMatrix4Value(Matrix4 begin, { Matrix4 end, Curve curve: linear })
-    : super(begin, end: end, curve: curve);
+  AnimatedMatrix4Value(Matrix4 begin, { Matrix4 end, Curve curve, Curve reverseCurve })
+    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
 
   Matrix4 lerp(double t) {
     // TODO(mpcomplete): Animate the full matrix. Will animating the cells
diff --git a/packages/flutter/lib/src/widgets/dismissable.dart b/packages/flutter/lib/src/widgets/dismissable.dart
index d019580..b9c11e2 100644
--- a/packages/flutter/lib/src/widgets/dismissable.dart
+++ b/packages/flutter/lib/src/widgets/dismissable.dart
@@ -12,7 +12,7 @@
 
 const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
 const Duration _kCardDismissResize = const Duration(milliseconds: 300);
-final Interval _kCardDismissResizeInterval = new Interval(0.4, 1.0);
+const Curve _kCardDismissResizeCurve = const Interval(0.4, 1.0, curve: ease);
 const double _kMinFlingVelocity = 700.0;
 const double _kMinFlingVelocityDelta = 400.0;
 const double _kFlingVelocityScale = 1.0 / 300.0;
@@ -226,8 +226,7 @@
       AnimatedValue<double> squashAxisExtent = new AnimatedValue<double>(
         _directionIsYAxis ? _size.width : _size.height,
         end: 0.0,
-        curve: ease,
-        interval: _kCardDismissResizeInterval
+        curve: _kCardDismissResizeCurve
       );
 
       return new SquashTransition(
diff --git a/packages/flutter/lib/src/widgets/popup_menu.dart b/packages/flutter/lib/src/widgets/popup_menu.dart
index 3812fb7..c6af624 100644
--- a/packages/flutter/lib/src/widgets/popup_menu.dart
+++ b/packages/flutter/lib/src/widgets/popup_menu.dart
@@ -29,7 +29,7 @@
 
 typedef List<PopupMenuItem> PopupMenuItemsBuilder(NavigatorState navigator);
 
-class PopupMenu extends StatefulComponent {
+class PopupMenu extends StatelessComponent {
   PopupMenu({
     Key key,
     this.items,
@@ -46,76 +46,46 @@
   final NavigatorState navigator;
   final PerformanceView performance;
 
-  PopupMenuState createState() => new PopupMenuState();
-}
-
-class PopupMenuState extends State<PopupMenu> {
-  void initState() {
-    super.initState();
-    config.performance.addListener(_performanceChanged);
-  }
-
-  void didUpdateConfig(PopupMenu oldConfig) {
-    if (config.performance != oldConfig.performance) {
-      oldConfig.performance.removeListener(_performanceChanged);
-      config.performance.addListener(_performanceChanged);
-    }
-  }
-
-  void dispose() {
-    config.performance.removeListener(_performanceChanged);
-    super.dispose();
-  }
-
-  void _performanceChanged() {
-    setState(() {
-      // the performance changed, and our state is tied up with the performance
-    });
-  }
-
-  BoxPainter _painter;
-
-  void _updateBoxPainter(BoxDecoration decoration) {
-    if (_painter == null || _painter.decoration != decoration)
-      _painter = new BoxPainter(decoration);
-  }
-
   Widget build(BuildContext context) {
-    _updateBoxPainter(new BoxDecoration(
+    final BoxPainter painter = new BoxPainter(new BoxDecoration(
       backgroundColor: Theme.of(context).canvasColor,
       borderRadius: 2.0,
-      boxShadow: shadows[config.level]
+      boxShadow: shadows[level]
     ));
-    double unit = 1.0 / (config.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
+
+    double unit = 1.0 / (items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
     List<Widget> children = [];
-    for (int i = 0; i < config.items.length; ++i) {
+
+    for (int i = 0; i < items.length; ++i) {
       double start = (i + 1) * unit;
       double end = (start + 1.5 * unit).clamp(0.0, 1.0);
       children.add(new FadeTransition(
-        performance: config.performance,
-        opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(start, end)),
+        performance: performance,
+        opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end)),
         child: new InkWell(
-          onTap: () { config.navigator.pop(config.items[i].value); },
-          child: config.items[i]
+          onTap: () { navigator.pop(items[i].value); },
+          child: items[i]
         ))
       );
     }
-    final width = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit));
-    final height = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit * config.items.length));
+
+    final width = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit));
+    final height = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit * items.length));
+
     return new FadeTransition(
-      performance: config.performance,
-      opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, 1.0 / 3.0)),
+      performance: performance,
+      opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)),
       child: new Container(
         margin: new EdgeDims.all(_kMenuMargin),
         child: new BuilderTransition(
-          performance: config.performance,
+          performance: performance,
           variables: [width, height],
           builder: (BuildContext context) {
             return new CustomPaint(
               callback: (sky.Canvas canvas, Size size) {
                 double widthValue = width.value * size.width;
                 double heightValue = height.value * size.height;
-                _painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
+                painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
               },
               child: new ConstrainedBox(
                 constraints: new BoxConstraints(
@@ -162,7 +132,7 @@
   Performance createPerformance() {
     Performance result = super.createPerformance();
     AnimationTiming timing = new AnimationTiming();
-    timing.reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd);
+    timing.reverseCurve = new Interval(0.0, _kMenuCloseIntervalEnd);
     result.timing = timing;
     return result;
   }