Commit 3a31f5f7 authored by Adam Barth's avatar Adam Barth

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
parent da685ec2
......@@ -18,9 +18,8 @@ class ProgressIndicatorAppState extends State<ProgressIndicatorApp> {
..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)
......
......@@ -25,70 +25,43 @@ abstract class Animatable {
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 @@ class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animat
/// 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 @@ class AnimatedColorValue extends AnimatedValue<Color> {
/// 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);
}
......@@ -27,9 +27,9 @@ class Linear implements Curve {
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 @@ class Interval implements Curve {
/// 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);
}
}
......
......@@ -9,29 +9,29 @@ import 'package:sky/src/widgets/framework.dart';
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
......
......@@ -12,7 +12,7 @@ import 'package:sky/src/widgets/gesture_detector.dart';
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 @@ class DismissableState extends State<Dismissable> {
AnimatedValue<double> squashAxisExtent = new AnimatedValue<double>(
_directionIsYAxis ? _size.width : _size.height,
end: 0.0,
curve: ease,
interval: _kCardDismissResizeInterval
curve: _kCardDismissResizeCurve
);
return new SquashTransition(
......
......@@ -29,7 +29,7 @@ const double _kMenuVerticalPadding = 8.0;
typedef List<PopupMenuItem> PopupMenuItemsBuilder(NavigatorState navigator);
class PopupMenu extends StatefulComponent {
class PopupMenu extends StatelessComponent {
PopupMenu({
Key key,
this.items,
......@@ -46,76 +46,46 @@ class PopupMenu extends StatefulComponent {
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 @@ class MenuRoute extends Route {
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;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment