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    bool isStopped() const { return kStopped_State == fState; }
36    bool isRunning() const { return kRunning_State == fState; }
37    bool isPaused() const { return kPaused_State == fState; }
38
39    /**
40     *  Stops the timer, and resets it, such that the next call to run or togglePauseResume
41     *  will begin at time 0.
42     */
43    void stop() {
44        this->setState(kStopped_State);
45    }
46
47    /**
48     *  If the timer is paused or stopped, it will resume (or start if it was stopped).
49     */
50    void run() {
51        this->setState(kRunning_State);
52    }
53
54    /**
55     *  If the timer is stopped, this has no effect, else it toggles between paused and running.
56     */
57    void togglePauseResume() {
58        if (kRunning_State == fState) {
59            this->setState(kPaused_State);
60        } else {
61            this->setState(kRunning_State);
62        }
63    }
64
65    /**
66     *  Call this each time you want to sample the clock for the timer. This is NOT done
67     *  automatically, so that repeated calls to msec() or secs() will always return the
68     *  same value.
69     *
70     *  This may safely be called with the timer in any state.
71     */
72    void updateTime() {
73        if (kRunning_State == fState) {
74            fCurrTimeNanos = SkTime::GetNSecs();
75        }
76    }
77
78    /**
79     *  Return the time in milliseconds the timer has been in the running state.
80     *  Returns 0 if the timer is stopped. Behavior is undefined if the timer
81     *  has been running longer than SK_MSecMax.
82     */
83    SkMSec msec() const {
84        const double msec = (fCurrTimeNanos - fBaseTimeNanos) * 1e-6;
85        SkASSERT(SK_MSecMax >= msec);
86        return static_cast<SkMSec>(msec);
87    }
88
89    /**
90     *  Return the time in seconds the timer has been in the running state.
91     *  Returns 0 if the timer is stopped.
92     */
93    double secs() const { return (fCurrTimeNanos - fBaseTimeNanos) * 1e-9; }
94
95    /**
96     *  Return the time in seconds the timer has been in the running state,
97     *  scaled by "speed" and (if not zero) mod by period.
98     *  Returns 0 if the timer is stopped.
99     */
100    SkScalar scaled(SkScalar speed, SkScalar period = 0) const {
101        double value = this->secs() * speed;
102        if (period) {
103            value = ::fmod(value, SkScalarToDouble(period));
104        }
105        return SkDoubleToScalar(value);
106    }
107
108    /**
109     * Transitions from ends->mid->ends linearly over period seconds. The phase specifies a phase
110     * shift in seconds.
111     */
112    SkScalar pingPong(SkScalar period, SkScalar phase, SkScalar ends, SkScalar mid) const {
113        return PingPong(this->secs(), period, phase, ends, mid);
114    }
115
116    /** Helper for computing a ping-pong value without a SkAnimTimer object. */
117    static SkScalar PingPong(double t, SkScalar period, SkScalar phase, SkScalar ends,
118                             SkScalar mid) {
119        double value = ::fmod(t + phase, period);
120        double half = period / 2.0;
121        double diff = ::fabs(value - half);
122        return SkDoubleToScalar(ends + (1.0 - diff / half) * (mid - ends));
123    }
124
125private:
126    double  fBaseTimeNanos;
127    double  fCurrTimeNanos;
128    State   fState;
129
130    void setState(State newState) {
131        switch (newState) {
132            case kStopped_State:
133                fBaseTimeNanos = fCurrTimeNanos = 0;
134                fState = kStopped_State;
135                break;
136            case kPaused_State:
137                if (kRunning_State == fState) {
138                    fState = kPaused_State;
139                } // else stay stopped or paused
140                break;
141            case kRunning_State:
142                switch (fState) {
143                    case kStopped_State:
144                        fBaseTimeNanos = fCurrTimeNanos = SkTime::GetNSecs();
145                        break;
146                    case kPaused_State: {// they want "resume"
147                        double now = SkTime::GetNSecs();
148                        fBaseTimeNanos += now - fCurrTimeNanos;
149                        fCurrTimeNanos = now;
150                    } break;
151                    case kRunning_State:
152                        break;
153                }
154                fState = kRunning_State;
155                break;
156        }
157    }
158};
159
160#endif
161