1/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "SkScalar.h"
9#include "SkTime.h"
10
11#ifndef SkAnimTimer_DEFINED
12#define SkAnimTimer_DEFINED
13
14/**
15 *  Class to track a "timer". It supports 3 states: stopped, paused, running.
16 *
17 *  The caller must call updateTime() to resync with the clock (typically just before
18 *  using the timer). Forcing the caller to do this ensures that the timer's return values
19 *  are consistent if called repeatedly, as they only reflect the time since the last
20 *  calle to updateTimer().
21 */
22class SkAnimTimer {
23public:
24    enum State {
25        kStopped_State,
26        kPaused_State,
27        kRunning_State
28    };
29
30    /**
31     *  Class begins in the "stopped" state.
32     */
33    SkAnimTimer() : fBaseTimeNanos(0), fCurrTimeNanos(0), fState(kStopped_State) {}
34
35    SkAnimTimer(double base, double curr, State state)
36        : fBaseTimeNanos(base)
37        , fCurrTimeNanos(curr)
38        , fState(state) {}
39
40    bool isStopped() const { return kStopped_State == fState; }
41    bool isRunning() const { return kRunning_State == fState; }
42    bool isPaused() const { return kPaused_State == fState; }
43
44    /**
45     *  Stops the timer, and resets it, such that the next call to run or togglePauseResume
46     *  will begin at time 0.
47     */
48    void stop() {
49        this->setState(kStopped_State);
50    }
51
52    /**
53     *  If the timer is paused or stopped, it will resume (or start if it was stopped).
54     */
55    void run() {
56        this->setState(kRunning_State);
57    }
58
59    /**
60     *  If the timer is stopped, this has no effect, else it toggles between paused and running.
61     */
62    void togglePauseResume() {
63        if (kRunning_State == fState) {
64            this->setState(kPaused_State);
65        } else {
66            this->setState(kRunning_State);
67        }
68    }
69
70    /**
71     *  Call this each time you want to sample the clock for the timer. This is NOT done
72     *  automatically, so that repeated calls to msec() or secs() will always return the
73     *  same value.
74     *
75     *  This may safely be called with the timer in any state.
76     */
77    void updateTime() {
78        if (kRunning_State == fState) {
79            fCurrTimeNanos = SkTime::GetNSecs();
80        }
81    }
82
83    /**
84     *  Return the time in milliseconds the timer has been in the running state.
85     *  Returns 0 if the timer is stopped. Behavior is undefined if the timer
86     *  has been running longer than SK_MSecMax.
87     */
88    SkMSec msec() const {
89        const double msec = (fCurrTimeNanos - fBaseTimeNanos) * 1e-6;
90        SkASSERT(SK_MSecMax >= msec);
91        return static_cast<SkMSec>(msec);
92    }
93
94    /**
95     *  Return the time in seconds the timer has been in the running state.
96     *  Returns 0 if the timer is stopped.
97     */
98    double secs() const { return (fCurrTimeNanos - fBaseTimeNanos) * 1e-9; }
99
100    /**
101     *  Return the time in seconds the timer has been in the running state,
102     *  scaled by "speed" and (if not zero) mod by period.
103     *  Returns 0 if the timer is stopped.
104     */
105    SkScalar scaled(SkScalar speed, SkScalar period = 0) const {
106        double value = this->secs() * speed;
107        if (period) {
108            value = ::fmod(value, SkScalarToDouble(period));
109        }
110        return SkDoubleToScalar(value);
111    }
112
113    /**
114     * Transitions from ends->mid->ends linearly over period seconds. The phase specifies a phase
115     * shift in seconds.
116     */
117    SkScalar pingPong(SkScalar period, SkScalar phase, SkScalar ends, SkScalar mid) const {
118        return PingPong(this->secs(), period, phase, ends, mid);
119    }
120
121    /** Helper for computing a ping-pong value without a SkAnimTimer object. */
122    static SkScalar PingPong(double t, SkScalar period, SkScalar phase, SkScalar ends,
123                             SkScalar mid) {
124        double value = ::fmod(t + phase, period);
125        double half = period / 2.0;
126        double diff = ::fabs(value - half);
127        return SkDoubleToScalar(ends + (1.0 - diff / half) * (mid - ends));
128    }
129
130private:
131    double  fBaseTimeNanos;
132    double  fCurrTimeNanos;
133    State   fState;
134
135    void setState(State newState) {
136        switch (newState) {
137            case kStopped_State:
138                fBaseTimeNanos = fCurrTimeNanos = 0;
139                fState = kStopped_State;
140                break;
141            case kPaused_State:
142                if (kRunning_State == fState) {
143                    fState = kPaused_State;
144                } // else stay stopped or paused
145                break;
146            case kRunning_State:
147                switch (fState) {
148                    case kStopped_State:
149                        fBaseTimeNanos = fCurrTimeNanos = SkTime::GetNSecs();
150                        break;
151                    case kPaused_State: {// they want "resume"
152                        double now = SkTime::GetNSecs();
153                        fBaseTimeNanos += now - fCurrTimeNanos;
154                        fCurrTimeNanos = now;
155                    } break;
156                    case kRunning_State:
157                        break;
158                }
159                fState = kRunning_State;
160                break;
161        }
162    }
163};
164
165#endif
166