| /* |
| * Copyright 2015 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "SkScalar.h" |
| #include "SkTime.h" |
| |
| #ifndef AnimTimer_DEFINED |
| #define AnimTimer_DEFINED |
| |
| /** |
| * Class to track a "timer". It supports 3 states: stopped, paused, and running. |
| * Playback speed is variable. |
| * |
| * The caller must call updateTime() to resync with the clock (typically just before |
| * using the timer). Forcing the caller to do this ensures that the timer's return values |
| * are consistent if called repeatedly, as they only reflect the time since the last |
| * calle to updateTimer(). |
| */ |
| class AnimTimer { |
| public: |
| enum State { kStopped_State, kPaused_State, kRunning_State }; |
| |
| /** |
| * Class begins in the "stopped" state. |
| */ |
| AnimTimer() : fPreviousNanos(0), fElapsedNanos(0), fSpeed(1), fState(kStopped_State) {} |
| |
| AnimTimer(double elapsed) |
| : fPreviousNanos(0), fElapsedNanos(elapsed), fSpeed(1), fState(kRunning_State) {} |
| |
| bool isStopped() const { return kStopped_State == fState; } |
| bool isRunning() const { return kRunning_State == fState; } |
| bool isPaused() const { return kPaused_State == fState; } |
| |
| /** |
| * Stops the timer, and resets it, such that the next call to run or togglePauseResume |
| * will begin at time 0. |
| */ |
| void stop() { this->setState(kStopped_State); } |
| |
| /** |
| * If the timer is paused or stopped, it will resume (or start if it was stopped). |
| */ |
| void run() { this->setState(kRunning_State); } |
| |
| /** |
| * Control the rate at which time advances. |
| */ |
| float getSpeed() const { return fSpeed; } |
| void setSpeed(float speed) { fSpeed = speed; } |
| |
| /** |
| * If the timer is stopped, start running, else it toggles between paused and running. |
| */ |
| void togglePauseResume() { |
| if (kRunning_State == fState) { |
| this->setState(kPaused_State); |
| } else { |
| this->setState(kRunning_State); |
| } |
| } |
| |
| /** |
| * Call this each time you want to sample the clock for the timer. This is NOT done |
| * automatically, so that repeated calls to msec() or secs() will always return the |
| * same value. |
| * |
| * This may safely be called with the timer in any state. |
| */ |
| void updateTime() { |
| if (kRunning_State == fState) { |
| double now = SkTime::GetNSecs(); |
| fElapsedNanos += (now - fPreviousNanos) * fSpeed; |
| fPreviousNanos = now; |
| } |
| } |
| |
| /** |
| * Return the time in milliseconds the timer has been in the running state. |
| * Returns 0 if the timer is stopped. Behavior is undefined if the timer |
| * has been running longer than SK_MSecMax. |
| */ |
| SkMSec msec() const { |
| const double msec = fElapsedNanos * 1e-6; |
| SkASSERT(SK_MSecMax >= msec); |
| return static_cast<SkMSec>(msec); |
| } |
| |
| /** |
| * Return the time in seconds the timer has been in the running state. |
| * Returns 0 if the timer is stopped. |
| */ |
| double secs() const { return fElapsedNanos * 1e-9; } |
| |
| /** |
| * Return the time in seconds the timer has been in the running state, |
| * scaled by "speed" and (if not zero) mod by period. |
| * Returns 0 if the timer is stopped. |
| */ |
| SkScalar scaled(SkScalar speed, SkScalar period = 0) const { |
| double value = this->secs() * speed; |
| if (period) { |
| value = ::fmod(value, SkScalarToDouble(period)); |
| } |
| return SkDoubleToScalar(value); |
| } |
| |
| /** |
| * Transitions from ends->mid->ends linearly over period seconds. The phase specifies a phase |
| * shift in seconds. |
| */ |
| SkScalar pingPong(SkScalar period, SkScalar phase, SkScalar ends, SkScalar mid) const { |
| return PingPong(this->secs(), period, phase, ends, mid); |
| } |
| |
| /** Helper for computing a ping-pong value without a AnimTimer object. */ |
| static SkScalar PingPong(double t, |
| SkScalar period, |
| SkScalar phase, |
| SkScalar ends, |
| SkScalar mid) { |
| double value = ::fmod(t + phase, period); |
| double half = period / 2.0; |
| double diff = ::fabs(value - half); |
| return SkDoubleToScalar(ends + (1.0 - diff / half) * (mid - ends)); |
| } |
| |
| private: |
| double fPreviousNanos; |
| double fElapsedNanos; |
| float fSpeed; |
| State fState; |
| |
| void setState(State newState) { |
| switch (newState) { |
| case kStopped_State: |
| fPreviousNanos = fElapsedNanos = 0; |
| fState = kStopped_State; |
| break; |
| case kPaused_State: |
| if (kRunning_State == fState) { |
| fState = kPaused_State; |
| } // else stay stopped or paused |
| break; |
| case kRunning_State: |
| switch (fState) { |
| case kStopped_State: |
| fPreviousNanos = SkTime::GetNSecs(); |
| fElapsedNanos = 0; |
| break; |
| case kPaused_State: // they want "resume" |
| fPreviousNanos = SkTime::GetNSecs(); |
| break; |
| case kRunning_State: break; |
| } |
| fState = kRunning_State; |
| break; |
| } |
| } |
| }; |
| |
| #endif |