performance.dart 11.9 KB
Newer Older
1 2 3 4
// 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.

Matt Perry's avatar
Matt Perry committed
5
import 'dart:async';
6
import 'dart:ui' show VoidCallback;
Matt Perry's avatar
Matt Perry committed
7

8 9 10
import 'animated_value.dart';
import 'forces.dart';
import 'simulation_stepper.dart';
11

12
/// The status of an animation
13
enum PerformanceStatus {
14 15 16 17 18 19 20 21 22 23 24
  /// The animation is stopped at the beginning
  dismissed,

  /// The animation is running from beginning to end
  forward,

  /// The animation is running backwards, from end to beginning
  reverse,

  /// The animation is stopped at the end
  completed,
25 26
}

27
typedef void PerformanceStatusListener(PerformanceStatus status);
28

29
/// An interface that is implemented by [Performance] that exposes a
30 31 32
/// read-only view of the underlying performance. This is used by classes that
/// want to watch a performance but should not be able to change the
/// performance's state.
33
abstract class PerformanceView {
34 35
  const PerformanceView();

36
  /// Update the given variable according to the current progress of the performance
37
  void updateVariable(Animatable variable);
38
  /// Calls the listener every time the progress of the performance changes
39
  void addListener(VoidCallback listener);
40
  /// Stop calling the listener every time the progress of the performance changes
41
  void removeListener(VoidCallback listener);
42
  /// Calls listener every time the status of the performance changes
43
  void addStatusListener(PerformanceStatusListener listener);
44
  /// Stops calling the listener every time the status of the performance changes
45
  void removeStatusListener(PerformanceStatusListener listener);
46

47
  /// The current status of this animation.
48 49
  PerformanceStatus get status;

50 51 52 53 54 55 56 57 58 59
  /// The current direction of the animation.
  AnimationDirection get direction;

  /// The direction used to select the current curve.
  ///
  /// The curve direction is only reset when we hit the beginning or the end of
  /// the timeline to avoid discontinuities in the value of any variables this
  /// performance is used to animate.
  AnimationDirection get curveDirection;

60 61 62 63
  /// The current progress of this animation (a value from 0.0 to 1.0).
  /// This is the value that is used to update any variables when using updateVariable().
  double get progress;

64 65 66 67 68
  /// Whether this animation is stopped at the beginning
  bool get isDismissed => status == PerformanceStatus.dismissed;

  /// Whether this animation is stopped at the end
  bool get isCompleted => status == PerformanceStatus.completed;
69 70
}

71 72 73 74 75 76 77 78
class AlwaysCompletePerformance extends PerformanceView {
  const AlwaysCompletePerformance();

  void updateVariable(Animatable variable) {
    variable.setProgress(1.0, AnimationDirection.forward);
  }

  // this performance never changes state
79 80
  void addListener(VoidCallback listener) { }
  void removeListener(VoidCallback listener) { }
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  void addStatusListener(PerformanceStatusListener listener) { }
  void removeStatusListener(PerformanceStatusListener listener) { }
  PerformanceStatus get status => PerformanceStatus.completed;
  AnimationDirection get direction => AnimationDirection.forward;
  AnimationDirection get curveDirection => AnimationDirection.forward;
  double get progress => 1.0;
}
const AlwaysCompletePerformance alwaysCompletePerformance = const AlwaysCompletePerformance();

class ReversePerformance extends PerformanceView {
  ReversePerformance(this.masterPerformance) {
    masterPerformance.addStatusListener(_statusChangeHandler);
  }

  final PerformanceView masterPerformance;

  void updateVariable(Animatable variable) {
    variable.setProgress(progress, curveDirection);
  }

101
  void addListener(VoidCallback listener) {
102 103
    masterPerformance.addListener(listener);
  }
104
  void removeListener(VoidCallback listener) {
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
    masterPerformance.removeListener(listener);
  }

  final List<PerformanceStatusListener> _statusListeners = new List<PerformanceStatusListener>();

  /// Calls listener every time the status of this performance changes
  void addStatusListener(PerformanceStatusListener listener) {
    _statusListeners.add(listener);
  }

  /// Stops calling the listener every time the status of this performance changes
  void removeStatusListener(PerformanceStatusListener listener) {
    _statusListeners.remove(listener);
  }

  void _statusChangeHandler(PerformanceStatus status) {
    status = _reverseStatus(status);
    List<PerformanceStatusListener> localListeners = new List<PerformanceStatusListener>.from(_statusListeners);
    for (PerformanceStatusListener listener in localListeners)
      listener(status);
  }

  PerformanceStatus get status => _reverseStatus(masterPerformance.status);
  AnimationDirection get direction => _reverseDirection(masterPerformance.direction);
  AnimationDirection get curveDirection => _reverseDirection(masterPerformance.curveDirection);
  double get progress => 1.0 - masterPerformance.progress;

  PerformanceStatus _reverseStatus(PerformanceStatus status) {
    switch (status) {
      case PerformanceStatus.forward: return PerformanceStatus.reverse;
      case PerformanceStatus.reverse: return PerformanceStatus.forward;
      case PerformanceStatus.completed: return PerformanceStatus.dismissed;
      case PerformanceStatus.dismissed: return PerformanceStatus.completed;
    }
  }

  AnimationDirection _reverseDirection(AnimationDirection direction) {
    switch (direction) {
      case AnimationDirection.forward: return AnimationDirection.reverse;
      case AnimationDirection.reverse: return AnimationDirection.forward;
    }
  }
}


150
/// A timeline that can be reversed and used to update [Animatable]s.
151 152 153 154 155 156 157
///
/// For example, a performance may handle an animation of a menu opening by
/// sliding and fading in (changing Y value and opacity) over .5 seconds. The
/// performance can move forwards (present) or backwards (dismiss). A consumer
/// may also take direct control of the timeline by manipulating [progress], or
/// [fling] the timeline causing a physics-based simulation to take over the
/// progression.
158
class Performance extends PerformanceView {
159
  Performance({ this.duration, double progress, this.debugLabel }) {
Adam Barth's avatar
Adam Barth committed
160
    _timeline = new SimulationStepper(_tick);
Hixie's avatar
Hixie committed
161 162
    if (progress != null)
      _timeline.value = progress.clamp(0.0, 1.0);
163 164
  }

165 166 167 168
  /// A label that is used in the toString() output. Intended to aid with
  /// identifying performance instances in debug output.
  final String debugLabel;

169
  /// Returns a [PerformanceView] for this performance,
170 171
  /// so that a pointer to this object can be passed around without
  /// allowing users of that pointer to mutate the AnimationPerformance state.
172
  PerformanceView get view => this;
173

174
  /// The length of time this performance should last
175 176
  Duration duration;

Adam Barth's avatar
Adam Barth committed
177
  SimulationStepper _timeline;
178
  AnimationDirection get direction => _direction;
179
  AnimationDirection _direction;
180
  AnimationDirection get curveDirection => _curveDirection;
181
  AnimationDirection _curveDirection;
182

183
  /// If non-null, animate with this timing instead of a linear timing
184 185
  AnimationTiming timing;

186 187 188
  /// The progress of this performance along the timeline
  ///
  /// Note: Setting this value stops the current animation.
189
  double get progress => _timeline.value.clamp(0.0, 1.0);
190 191
  void set progress(double t) {
    stop();
192
    _timeline.value = t.clamp(0.0, 1.0);
193
    _checkStatusChanged();
194 195
  }

196 197
  double get _curvedProgress {
    return timing != null ? timing.transform(progress, _curveDirection) : progress;
198 199
  }

200 201 202
  /// Whether this animation is currently animating in either the forward or reverse direction
  bool get isAnimating => _timeline.isAnimating;

203
  PerformanceStatus get status {
204
    if (!isAnimating && progress == 1.0)
205
      return PerformanceStatus.completed;
206
    if (!isAnimating && progress == 0.0)
207 208 209 210
      return PerformanceStatus.dismissed;
    return _direction == AnimationDirection.forward ?
        PerformanceStatus.forward :
        PerformanceStatus.reverse;
211 212
  }

213
  /// Update the given varaible according to the current progress of this performance
214
  void updateVariable(Animatable variable) {
215
    variable.setProgress(_curvedProgress, _curveDirection);
216 217
  }

218
  /// Start running this animation forwards (towards the end)
219
  Future forward() => play(AnimationDirection.forward);
220 221

  /// Start running this animation in reverse (towards the beginning)
222
  Future reverse() => play(AnimationDirection.reverse);
223

224
  /// Start running this animation in the given direction
225
  Future play([AnimationDirection direction = AnimationDirection.forward]) {
226 227 228 229
    _direction = direction;
    return resume();
  }

230
  /// Start running this animation in the most recent direction
231
  Future resume() {
232
    return _animateTo(_direction == AnimationDirection.forward ? 1.0 : 0.0);
233
  }
234

235
  /// Stop running this animation
236
  void stop() {
237
    _timeline.stop();
238 239
  }

240 241 242 243 244
  /// Start running this animation according to the given physical parameters
  ///
  /// Flings the timeline with an optional force (defaults to a critically
  /// damped spring) and initial velocity. If velocity is positive, the
  /// animation will complete, otherwise it will dismiss.
245
  Future fling({double velocity: 1.0, Force force}) {
Matt Perry's avatar
Matt Perry committed
246 247
    if (force == null)
      force = kDefaultSpringForce;
248
    _direction = velocity < 0.0 ? AnimationDirection.reverse : AnimationDirection.forward;
Adam Barth's avatar
Adam Barth committed
249
    return _timeline.animateWith(force.release(progress, velocity));
250 251
  }

252
  final List<VoidCallback> _listeners = new List<VoidCallback>();
253

254
  /// Calls the listener every time the progress of this performance changes
255
  void addListener(VoidCallback listener) {
256 257 258
    _listeners.add(listener);
  }

259
  /// Stop calling the listener every time the progress of this performance changes
260
  void removeListener(VoidCallback listener) {
261 262 263 264
    _listeners.remove(listener);
  }

  void _notifyListeners() {
265 266
    List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
    for (VoidCallback listener in localListeners)
267 268 269
      listener();
  }

270
  final List<PerformanceStatusListener> _statusListeners = new List<PerformanceStatusListener>();
271

272
  /// Calls listener every time the status of this performance changes
273
  void addStatusListener(PerformanceStatusListener listener) {
274 275 276
    _statusListeners.add(listener);
  }

277
  /// Stops calling the listener every time the status of this performance changes
278
  void removeStatusListener(PerformanceStatusListener listener) {
279 280 281
    _statusListeners.remove(listener);
  }

282
  PerformanceStatus _lastStatus = PerformanceStatus.dismissed;
283
  void _checkStatusChanged() {
284
    PerformanceStatus currentStatus = status;
285
    if (currentStatus != _lastStatus) {
286 287
      List<PerformanceStatusListener> localListeners = new List<PerformanceStatusListener>.from(_statusListeners);
      for (PerformanceStatusListener listener in localListeners)
288 289 290 291 292
        listener(currentStatus);
    }
    _lastStatus = currentStatus;
  }

293 294
  void _updateCurveDirection() {
    if (status != _lastStatus) {
295
      if (_lastStatus == PerformanceStatus.dismissed || _lastStatus == PerformanceStatus.completed)
296
        _curveDirection = _direction;
297 298 299
    }
  }

Matt Perry's avatar
Matt Perry committed
300
  Future _animateTo(double target) {
301 302
    Duration remainingDuration = duration * (target - _timeline.value).abs();
    _timeline.stop();
303
    if (remainingDuration == Duration.ZERO)
Matt Perry's avatar
Matt Perry committed
304
      return new Future.value();
305
    return _timeline.animateTo(target, duration: remainingDuration);
306 307 308
  }

  void _tick(double t) {
309
    _updateCurveDirection();
Hixie's avatar
Hixie committed
310 311 312 313
    didTick(t);
  }

  void didTick(double t) {
314
    _notifyListeners();
315
    _checkStatusChanged();
316
  }
317 318 319 320 321 322

  String toString() {
    if (debugLabel != null)
      return '$runtimeType at $progress for $debugLabel';
    return '$runtimeType at $progress';
  }
323
}
324

325
/// An animation performance with an animated variable with a concrete type
326 327
class ValuePerformance<T> extends Performance {
  ValuePerformance({ this.variable, Duration duration, double progress }) :
Hixie's avatar
Hixie committed
328
    super(duration: duration, progress: progress);
329

Hixie's avatar
Hixie committed
330
  AnimatedValue<T> variable;
331
  T get value => variable.value;
Hixie's avatar
Hixie committed
332 333 334 335 336 337

  void didTick(double t) {
    if (variable != null)
      variable.setProgress(_curvedProgress, _curveDirection);
    super.didTick(t);
  }
338
}