Skip to content

Commit

Permalink
When animating, use the same curve until it completes.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mpcomplete committed Aug 4, 2015
1 parent 7b0856d commit d3817ff
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 89 deletions.
86 changes: 49 additions & 37 deletions sky/packages/sky/lib/animation/animated_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
23 changes: 21 additions & 2 deletions sky/packages/sky/lib/animation/animation_performance.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -145,8 +163,9 @@ class AnimationPerformance {
}

void _tick(double t) {
_updateCurveDirection();
if (variable != null)
variable.setProgress(t);
variable.setProgress(t, curveDirection);
_notifyListeners();
_checkStatusChanged();
}
Expand Down
16 changes: 16 additions & 0 deletions sky/packages/sky/lib/animation/curves.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
8 changes: 8 additions & 0 deletions sky/packages/sky/lib/animation/direction.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// 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
}
7 changes: 1 addition & 6 deletions sky/packages/sky/lib/animation/forces.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 2 additions & 10 deletions sky/packages/sky/lib/rendering/toggleable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
30 changes: 7 additions & 23 deletions sky/packages/sky/lib/widgets/animated_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
// TODO(abarth): We should lerp the BoxConstraints.
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),
Expand All @@ -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);
}
}

Expand Down
1 change: 0 additions & 1 deletion sky/packages/sky/lib/widgets/navigator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
8 changes: 3 additions & 5 deletions sky/packages/sky/lib/widgets/popup_menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ class PopupMenu extends AnimatedComponent {
AnimatedValue<double> _width;
AnimatedValue<double> _height;
List<AnimatedValue<double>> _itemOpacities;
AnimatedList _animationList;
AnimationPerformance _performance;

void initState() {
Expand Down Expand Up @@ -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() {
Expand All @@ -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();
}

Expand Down
Loading

0 comments on commit d3817ff

Please sign in to comment.