Commit 78e18a5d authored by Matt Perry's avatar Matt Perry

When animating, use the same curve until it completes.

This ensures we don't run into discontinuities when reversing an
animation halfway through. I refactored AnimationValue to have knowledge
of the reverse curves and intervals.
parent 054ebbf3
......@@ -5,84 +5,96 @@
import "dart:sky";
import 'package:sky/animation/curves.dart';
import 'package:sky/animation/direction.dart';
import 'package:sky/base/lerp.dart';
export 'package:sky/animation/curves.dart' show Interval;
abstract class AnimatedVariable {
void setProgress(double t);
void setProgress(double t, Direction direction);
String toString();
}
class Interval {
final double start;
final double end;
abstract class CurvedVariable implements AnimatedVariable {
CurvedVariable({this.interval, this.reverseInterval, this.curve, this.reverseCurve});
Interval interval;
Interval reverseInterval;
Curve curve;
Curve reverseCurve;
double _transform(double t, Direction direction) {
Interval interval = _getInterval(direction);
if (interval != null)
t = interval.transform(t);
if (t == 1.0) // Or should we support inverse curves?
return t;
Curve curve = _getCurve(direction);
if (curve != null)
t = curve.transform(t);
return t;
}
double adjustTime(double t) {
return ((t - start) / (end - start)).clamp(0.0, 1.0);
Interval _getInterval(Direction direction) {
if (direction == Direction.forward || reverseInterval == null)
return interval;
return reverseInterval;
}
Interval(this.start, this.end) {
assert(start >= 0.0);
assert(start <= 1.0);
assert(end >= 0.0);
assert(end <= 1.0);
Curve _getCurve(Direction direction) {
if (direction == Direction.forward || reverseCurve == null)
return curve;
return reverseCurve;
}
}
class AnimatedValue<T extends dynamic> extends AnimatedVariable {
AnimatedValue(this.begin, { this.end, this.interval, this.curve: linear }) {
class AnimatedValue<T extends dynamic> extends CurvedVariable {
AnimatedValue(this.begin, { this.end, Interval interval, Curve curve, Curve reverseCurve })
: super(interval: interval, curve: curve, reverseCurve: reverseCurve) {
value = begin;
}
T value;
T begin;
T end;
Interval interval;
Curve curve;
void setProgress(double t) {
T lerp(double t) => begin + (end - begin) * t;
void setProgress(double t, Direction direction) {
if (end != null) {
double adjustedTime = interval == null ? t : interval.adjustTime(t);
if (adjustedTime == 1.0) {
value = end;
} else {
// TODO(mpcomplete): Reverse the timeline and curve.
value = begin + (end - begin) * curve.transform(adjustedTime);
}
t = _transform(t, direction);
value = (t == 1.0) ? end : lerp(t);
}
}
String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)';
}
class AnimatedList extends AnimatedVariable {
class AnimatedList extends CurvedVariable {
List<AnimatedVariable> variables;
Interval interval;
AnimatedList(this.variables, { this.interval });
AnimatedList(this.variables, { Interval interval, Curve curve, Curve reverseCurve })
: super(interval: interval, curve: curve, reverseCurve: reverseCurve);
void setProgress(double t) {
double adjustedTime = interval == null ? t : interval.adjustTime(t);
void setProgress(double t, Direction direction) {
double adjustedTime = _transform(t, direction);
for (AnimatedVariable variable in variables)
variable.setProgress(adjustedTime);
variable.setProgress(adjustedTime, direction);
}
String toString() => 'AnimatedList([$variables])';
}
class AnimatedColorValue extends AnimatedValue<Color> {
AnimatedColorValue(Color begin, { Color end, Curve curve: linear })
AnimatedColorValue(Color begin, { Color end, Curve curve })
: super(begin, end: end, curve: curve);
void setProgress(double t) {
value = lerpColor(begin, end, t);
}
Color lerp(double t) => lerpColor(begin, end, t);
}
class AnimatedRect extends AnimatedValue<Rect> {
AnimatedRect(Rect begin, { Rect end, Curve curve: linear })
AnimatedRect(Rect begin, { Rect end, Curve curve })
: super(begin, end: end, curve: curve);
void setProgress(double t) {
value = lerpRect(begin, end, t);
}
Rect lerp(double t) => lerpRect(begin, end, t);
}
......@@ -5,10 +5,11 @@
import 'dart:async';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/direction.dart';
import 'package:sky/animation/forces.dart';
import 'package:sky/animation/timeline.dart';
export 'package:sky/animation/forces.dart' show Direction;
export 'package:sky/animation/direction.dart' show Direction;
enum AnimationStatus {
dismissed, // stoped at 0
......@@ -39,6 +40,12 @@ class AnimationPerformance {
Direction _direction;
Direction get direction => _direction;
// This controls which curve we use for variables with different curves in
// the forward/reverse directions. Curve direction is only reset when we hit
// 0 or 1, to avoid discontinuities.
Direction _curveDirection;
Direction get curveDirection => _curveDirection;
// If non-null, animate with this force instead of a tween animation.
Force attachedForce;
......@@ -74,6 +81,10 @@ class AnimationPerformance {
AnimationStatus.reverse;
}
void updateVariable(AnimatedVariable variable) {
variable.setProgress(progress, curveDirection);
}
Future play([Direction direction = Direction.forward]) {
_direction = direction;
return resume();
......@@ -136,6 +147,13 @@ class AnimationPerformance {
_lastStatus = currentStatus;
}
void _updateCurveDirection() {
if (status != _lastStatus) {
if (_lastStatus == AnimationStatus.dismissed || _lastStatus == AnimationStatus.completed)
_curveDirection = direction;
}
}
Future _animateTo(double target) {
Duration remainingDuration = duration * (target - timeline.value).abs();
timeline.stop();
......@@ -145,8 +163,9 @@ class AnimationPerformance {
}
void _tick(double t) {
_updateCurveDirection();
if (variable != null)
variable.setProgress(t);
variable.setProgress(t, curveDirection);
_notifyListeners();
_checkStatusChanged();
}
......
......@@ -21,6 +21,22 @@ class Linear implements Curve {
}
}
class Interval implements Curve {
final double start;
final double end;
Interval(this.start, this.end) {
assert(start >= 0.0);
assert(start <= 1.0);
assert(end >= 0.0);
assert(end <= 1.0);
}
double transform(double t) {
return ((t - start) / (end - start)).clamp(0.0, 1.0);
}
}
class ParabolicFall implements Curve {
const ParabolicFall();
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
enum Direction {
forward,
reverse
}
......@@ -3,12 +3,7 @@
// found in the LICENSE file.
import 'package:newton/newton.dart';
// TODO(mpcomplete): This doesn't belong here.
enum Direction {
forward,
reverse
}
import 'package:sky/animation/direction.dart';
// Base class for creating Simulations for the animation Timeline.
abstract class Force {
......
......@@ -44,15 +44,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
void set value(bool value) {
if (value == _value) return;
_value = value;
// TODO(abarth): Setting the curve on the position means there's a
// discontinuity when we reverse the timeline.
if (value) {
_position.curve = easeIn;
_performance.play();
} else {
_position.curve = easeOut;
_performance.reverse();
}
performance.play(value ? Direction.forward : Direction.reverse);
}
ValueChanged _onChanged;
......@@ -63,7 +55,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
}
final AnimatedValue<double> _position =
new AnimatedValue<double>(0.0, end: 1.0);
new AnimatedValue<double>(0.0, end: 1.0, curve: easeIn, reverseCurve: easeOut);
AnimatedValue<double> get position => _position;
AnimationPerformance _performance;
......
......@@ -16,35 +16,23 @@ class AnimatedBoxConstraintsValue extends AnimatedValue<BoxConstraints> {
AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve: linear })
: super(begin, end: end, curve: curve);
void setProgress(double t) {
// TODO(abarth): We should lerp the BoxConstraints.
value = end;
}
BoxConstraints lerp(double t) => end;
}
class AnimatedBoxDecorationValue extends AnimatedValue<BoxDecoration> {
AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear })
: super(begin, end: end, curve: curve);
void setProgress(double t) {
if (t == 1.0) {
value = end;
return;
}
value = lerpBoxDecoration(begin, end, t);
}
BoxDecoration lerp(double t) => lerpBoxDecoration(begin, end, t);
}
class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> {
AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve: linear })
: super(begin, end: end, curve: curve);
void setProgress(double t) {
if (t == 1.0) {
value = end;
return;
}
value = new EdgeDims(
EdgeDims lerp(double t) {
return new EdgeDims(
lerpNum(begin.top, end.top, t),
lerpNum(begin.right, end.right, t),
lerpNum(begin.bottom, end.bottom, t),
......@@ -57,17 +45,13 @@ class AnimatedMatrix4Value extends AnimatedValue<Matrix4> {
AnimatedMatrix4Value(Matrix4 begin, { Matrix4 end, Curve curve: linear })
: super(begin, end: end, curve: curve);
void setProgress(double t) {
if (t == 1.0) {
value = end;
return;
}
Matrix4 lerp(double t) {
// TODO(mpcomplete): Animate the full matrix. Will animating the cells
// separately work?
Vector3 beginT = begin.getTranslation();
Vector3 endT = end.getTranslation();
Vector3 lerpT = beginT*(1.0-t) + endT*t;
value = new Matrix4.identity()..translate(lerpT);
return new Matrix4.identity()..translate(lerpT);
}
}
......
......@@ -7,7 +7,6 @@ import 'dart:async';
import 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/animation/forces.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/focus.dart';
import 'package:sky/widgets/transitions.dart';
......
......@@ -50,7 +50,6 @@ class PopupMenu extends AnimatedComponent {
AnimatedValue<double> _width;
AnimatedValue<double> _height;
List<AnimatedValue<double>> _itemOpacities;
AnimatedList _animationList;
AnimationPerformance _performance;
void initState() {
......@@ -101,8 +100,9 @@ class PopupMenu extends AnimatedComponent {
..add(_width)
..add(_height)
..addAll(_itemOpacities);
_animationList = new AnimatedList(variables);
_performance.variable = _animationList;
AnimatedList list = new AnimatedList(variables)
..reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd);
_performance.variable = list;
}
void _updateBoxPainter() {
......@@ -124,14 +124,12 @@ class PopupMenu extends AnimatedComponent {
void _open() {
_animationList.interval = null;
_performance.play();
if (navigator != null)
navigator.pushState(this, (_) => _close());
}
void _close() {
_animationList.interval = new Interval(0.0, _kMenuCloseIntervalEnd);
_performance.reverse();
}
......
......@@ -98,7 +98,7 @@ class SlideTransition extends TransitionBase {
}
Widget build() {
position.setProgress(performance.progress);
performance.updateVariable(position);
Matrix4 transform = new Matrix4.identity()
..translate(position.value.x, position.value.y);
return new Transform(transform: transform, child: child);
......@@ -131,7 +131,7 @@ class FadeTransition extends TransitionBase {
}
Widget build() {
opacity.setProgress(performance.progress);
performance.updateVariable(opacity);
return new Opacity(opacity: opacity.value, child: child);
}
}
......@@ -162,7 +162,7 @@ class ColorTransition extends TransitionBase {
}
Widget build() {
color.setProgress(performance.progress);
performance.updateVariable(color);
return new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: color.value),
child: child
......@@ -200,9 +200,9 @@ class SquashTransition extends TransitionBase {
Widget build() {
if (width != null)
width.setProgress(performance.progress);
performance.updateVariable(width);
if (height != null)
height.setProgress(performance.progress);
performance.updateVariable(height);
return new SizedBox(width: _maybe(width), height: _maybe(height), child: child);
}
}
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