aboutsummaryrefslogtreecommitdiffhomepage
path: root/gm/SkAnimTimer.h
blob: 86ce7acd4bb43fe099cc6fa91ea32ddafb39677e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*
 * 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 SkAnimTimer_DEFINED
#define SkAnimTimer_DEFINED

/**
 *  Class to track a "timer". It supports 3 states: stopped, paused, running.
 *
 *  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 SkAnimTimer {
public:
    enum State {
        kStopped_State,
        kPaused_State,
        kRunning_State
    };

    /**
     *  Class begins in the "stopped" state.
     */
    SkAnimTimer() : fBaseTimeNanos(0), fCurrTimeNanos(0), fState(kStopped_State) {}

    SkAnimTimer(double base, double curr, State state)
        : fBaseTimeNanos(base)
        , fCurrTimeNanos(curr)
        , fState(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);
    }

    /**
     *  If the timer is stopped, this has no effect, 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) {
            fCurrTimeNanos = SkTime::GetNSecs();
        }
    }

    /**
     *  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 = (fCurrTimeNanos - fBaseTimeNanos) * 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 (fCurrTimeNanos - fBaseTimeNanos) * 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 SkAnimTimer 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  fBaseTimeNanos;
    double  fCurrTimeNanos;
    State   fState;

    void setState(State newState) {
        switch (newState) {
            case kStopped_State:
                fBaseTimeNanos = fCurrTimeNanos = 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:
                        fBaseTimeNanos = fCurrTimeNanos = SkTime::GetNSecs();
                        break;
                    case kPaused_State: {// they want "resume"
                        double now = SkTime::GetNSecs();
                        fBaseTimeNanos += now - fCurrTimeNanos;
                        fCurrTimeNanos = now;
                    } break;
                    case kRunning_State:
                        break;
                }
                fState = kRunning_State;
                break;
        }
    }
};

#endif