// 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. import 'dart:async'; import 'package:newton/newton.dart'; import 'package:sky/src/animation/scheduler.dart'; const double _kSecondsPerMillisecond = 1000.0; // TODO(abarth): Change from double to Duration. typedef _TickerCallback(double timeStamp); /// Calls its callback once per animation frame class Ticker { /// Constructs a ticker that will call onTick once per frame while running Ticker(_TickerCallback onTick) : _onTick = onTick; final _TickerCallback _onTick; Completer _completer; int _animationId; /// Start calling onTick once per animation frame /// /// The returned future resolves once the ticker stops ticking. Future start() { assert(!isTicking); _completer = new Completer(); _scheduleTick(); return _completer.future; } /// Stop calling onTick /// /// Causes the future returned by [start] to resolve. void stop() { if (!isTicking) return; if (_animationId != null) { scheduler.cancelAnimationFrame(_animationId); _animationId = null; } // We take the _completer into a local variable so that !isTicking // when we actually complete the future (isTicking uses _completer // to determine its state). Completer localCompleter = _completer; _completer = null; assert(!isTicking); localCompleter.complete(); } /// Whether this ticker has scheduled a call to onTick bool get isTicking => _completer != null; void _tick(double timeStamp) { assert(isTicking); assert(_animationId != null); _animationId = null; _onTick(timeStamp); // The onTick callback may have scheduled another tick already. if (isTicking && _animationId == null) _scheduleTick(); } void _scheduleTick() { assert(isTicking); assert(_animationId == null); _animationId = scheduler.requestAnimationFrame(_tick); } } /// Ticks a simulation once per frame class AnimatedSimulation { AnimatedSimulation(Function onTick) : _onTick = onTick { _ticker = new Ticker(_tick); } final Function _onTick; Ticker _ticker; Simulation _simulation; double _startTime; double _value = 0.0; /// The current value of the simulation double get value => _value; void set value(double newValue) { assert(!_ticker.isTicking); _value = newValue; _onTick(_value); } /// Start ticking the given simulation once per frame /// /// Returns a future that resolves when the simulation stops ticking. Future start(Simulation simulation) { assert(simulation != null); assert(!_ticker.isTicking); _simulation = simulation; _startTime = null; _value = simulation.x(0.0); return _ticker.start(); } /// Stop ticking the current simulation void stop() { _simulation = null; _startTime = null; _ticker.stop(); } /// Whether this object is currently ticking a simulation bool get isAnimating => _ticker.isTicking; void _tick(double timeStamp) { if (_startTime == null) _startTime = timeStamp; double timeInSeconds = (timeStamp - _startTime) / _kSecondsPerMillisecond; _value = _simulation.x(timeInSeconds); final bool isLastTick = _simulation.isDone(timeInSeconds); if (isLastTick) stop(); _onTick(_value); } }