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