performance.dart 12.7 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 7 8
import 'dart:ui' show VoidCallback, lerpDouble;

import 'package:newton/newton.dart';
Matt Perry's avatar
Matt Perry committed
9

10 11 12
import 'animated_value.dart';
import 'forces.dart';
import 'simulation_stepper.dart';
13

14
/// The status of an animation
15
enum PerformanceStatus {
16 17 18 19 20 21 22 23 24 25 26
  /// 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,
27 28
}

29
typedef void PerformanceStatusListener(PerformanceStatus status);
30

31
/// An interface that is implemented by [Performance] that exposes a
32 33 34
/// 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.
35
abstract class PerformanceView {
36 37
  const PerformanceView();

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

49
  /// The current status of this animation.
50 51
  PerformanceStatus get status;

52 53 54 55 56 57 58 59 60 61
  /// 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;

62 63 64 65
  /// 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;

66 67 68 69 70
  /// 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;
71 72
}

73 74 75 76 77 78 79 80
class AlwaysCompletePerformance extends PerformanceView {
  const AlwaysCompletePerformance();

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

  // this performance never changes state
81 82
  void addListener(VoidCallback listener) { }
  void removeListener(VoidCallback listener) { }
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
  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);
  }

103
  void addListener(VoidCallback listener) {
104 105
    masterPerformance.addListener(listener);
  }
106
  void removeListener(VoidCallback listener) {
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 150
    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;
    }
  }
}

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
class _RepeatingSimulation extends Simulation {
  _RepeatingSimulation(this.min, this.max, Duration period)
    : _periodInSeconds = period.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND {
    assert(_periodInSeconds > 0.0);
  }

  final double min;
  final double max;

  final double _periodInSeconds;

  double x(double timeInSeconds) {
    assert(timeInSeconds >= 0.0);
    final double t = (timeInSeconds / _periodInSeconds) % 1.0;
    return lerpDouble(min, max, t);
  }

  double dx(double timeInSeconds) => 1.0;

  bool isDone(double timeInSeconds) => false;
}
172

173
/// A timeline that can be reversed and used to update [Animatable]s.
174 175 176 177 178 179 180
///
/// 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.
181
class Performance extends PerformanceView {
182
  Performance({ this.duration, double progress, this.debugLabel }) {
Adam Barth's avatar
Adam Barth committed
183
    _timeline = new SimulationStepper(_tick);
Hixie's avatar
Hixie committed
184 185
    if (progress != null)
      _timeline.value = progress.clamp(0.0, 1.0);
186 187
  }

188 189 190 191
  /// A label that is used in the toString() output. Intended to aid with
  /// identifying performance instances in debug output.
  final String debugLabel;

192
  /// Returns a [PerformanceView] for this performance,
193
  /// so that a pointer to this object can be passed around without
194
  /// allowing users of that pointer to mutate the Performance state.
195
  PerformanceView get view => this;
196

197
  /// The length of time this performance should last
198 199
  Duration duration;

Adam Barth's avatar
Adam Barth committed
200
  SimulationStepper _timeline;
201
  AnimationDirection get direction => _direction;
202
  AnimationDirection _direction;
203
  AnimationDirection get curveDirection => _curveDirection;
204
  AnimationDirection _curveDirection;
205

206
  /// If non-null, animate with this timing instead of a linear timing
207 208
  AnimationTiming timing;

209 210 211
  /// The progress of this performance along the timeline
  ///
  /// Note: Setting this value stops the current animation.
212
  double get progress => _timeline.value.clamp(0.0, 1.0);
213 214
  void set progress(double t) {
    stop();
215
    _timeline.value = t.clamp(0.0, 1.0);
216
    _checkStatusChanged();
217 218
  }

219 220
  double get _curvedProgress {
    return timing != null ? timing.transform(progress, _curveDirection) : progress;
221 222
  }

223 224 225
  /// Whether this animation is currently animating in either the forward or reverse direction
  bool get isAnimating => _timeline.isAnimating;

226
  PerformanceStatus get status {
227
    if (!isAnimating && progress == 1.0)
228
      return PerformanceStatus.completed;
229
    if (!isAnimating && progress == 0.0)
230 231 232 233
      return PerformanceStatus.dismissed;
    return _direction == AnimationDirection.forward ?
        PerformanceStatus.forward :
        PerformanceStatus.reverse;
234 235
  }

236
  /// Update the given varaible according to the current progress of this performance
237
  void updateVariable(Animatable variable) {
238
    variable.setProgress(_curvedProgress, _curveDirection);
239 240
  }

241
  /// Start running this animation forwards (towards the end)
242
  Future forward() => play(AnimationDirection.forward);
243 244

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

247
  /// Start running this animation in the given direction
248
  Future play([AnimationDirection direction = AnimationDirection.forward]) {
249 250 251 252
    _direction = direction;
    return resume();
  }

253
  /// Start running this animation in the most recent direction
254
  Future resume() {
255
    return _animateTo(_direction == AnimationDirection.forward ? 1.0 : 0.0);
256
  }
257

258
  /// Stop running this animation
259
  void stop() {
260
    _timeline.stop();
261 262
  }

263 264 265 266 267
  /// 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.
268
  Future fling({double velocity: 1.0, Force force}) {
Matt Perry's avatar
Matt Perry committed
269 270
    if (force == null)
      force = kDefaultSpringForce;
271
    _direction = velocity < 0.0 ? AnimationDirection.reverse : AnimationDirection.forward;
Adam Barth's avatar
Adam Barth committed
272
    return _timeline.animateWith(force.release(progress, velocity));
273 274
  }

275 276 277 278 279 280
  Future repeat({ double min: 0.0, double max: 1.0, Duration period }) {
    if (period == null)
      period = duration;
    return _timeline.animateWith(new _RepeatingSimulation(min, max, period));
  }

281
  final List<VoidCallback> _listeners = new List<VoidCallback>();
282

283
  /// Calls the listener every time the progress of this performance changes
284
  void addListener(VoidCallback listener) {
285 286 287
    _listeners.add(listener);
  }

288
  /// Stop calling the listener every time the progress of this performance changes
289
  void removeListener(VoidCallback listener) {
290 291 292 293
    _listeners.remove(listener);
  }

  void _notifyListeners() {
294 295
    List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
    for (VoidCallback listener in localListeners)
296 297 298
      listener();
  }

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

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

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

311
  PerformanceStatus _lastStatus = PerformanceStatus.dismissed;
312
  void _checkStatusChanged() {
313
    PerformanceStatus currentStatus = status;
314
    if (currentStatus != _lastStatus) {
315 316
      List<PerformanceStatusListener> localListeners = new List<PerformanceStatusListener>.from(_statusListeners);
      for (PerformanceStatusListener listener in localListeners)
317 318 319 320 321
        listener(currentStatus);
    }
    _lastStatus = currentStatus;
  }

322 323
  void _updateCurveDirection() {
    if (status != _lastStatus) {
324
      if (_lastStatus == PerformanceStatus.dismissed || _lastStatus == PerformanceStatus.completed)
325
        _curveDirection = _direction;
326 327 328
    }
  }

Matt Perry's avatar
Matt Perry committed
329
  Future _animateTo(double target) {
330 331
    Duration remainingDuration = duration * (target - _timeline.value).abs();
    _timeline.stop();
332
    if (remainingDuration == Duration.ZERO)
Matt Perry's avatar
Matt Perry committed
333
      return new Future.value();
334
    return _timeline.animateTo(target, duration: remainingDuration);
335 336 337
  }

  void _tick(double t) {
338
    _updateCurveDirection();
Hixie's avatar
Hixie committed
339 340 341 342
    didTick(t);
  }

  void didTick(double t) {
343
    _notifyListeners();
344
    _checkStatusChanged();
345
  }
346 347 348 349 350 351

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

354
/// An animation performance with an animated variable with a concrete type
355 356
class ValuePerformance<T> extends Performance {
  ValuePerformance({ this.variable, Duration duration, double progress }) :
Hixie's avatar
Hixie committed
357
    super(duration: duration, progress: progress);
358

Hixie's avatar
Hixie committed
359
  AnimatedValue<T> variable;
360
  T get value => variable.value;
Hixie's avatar
Hixie committed
361 362 363 364 365 366

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