/
animation.go
247 lines (222 loc) · 5.84 KB
/
animation.go
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package wid
import (
"fmt"
"time"
"gioui.org/op"
)
// VisibilityAnimation holds the animation state for animations that transition between a
// "visible" and "invisible" state for a fixed duration of time.
type VisibilityAnimation struct {
// How long does the animation last
time.Duration
State VisibilityAnimationState
Started time.Time
}
// Revealed returns the fraction of the animated entity that should be revealed at the current
// time in the animation. This fraction is computed with linear interpolation.
//
// Revealed should be invoked during every frame that v.Animating() returns true.
//
// If the animation reaches its end this frame, Revealed will transition it to a non-animating
// state automatically.
//
// If the animation is in the process of animating, calling Revealed will automatically add
// an InvalidateOp to the provided Context to ensure that the next frame will be generated
// promptly.
func (v *VisibilityAnimation) Revealed(gtx C) float32 {
if v.Animating() {
op.InvalidateOp{}.Add(gtx.Ops)
}
if v.Duration == time.Duration(0) {
v.Duration = time.Second
}
progress := float32(gtx.Now.Sub(v.Started).Milliseconds()) / float32(v.Milliseconds())
if progress >= 1 {
if v.State == Appearing {
v.State = Visible
} else if v.State == Disappearing {
v.State = Invisible
}
}
switch v.State {
case Visible:
return 1
case Invisible:
return 0
case Appearing:
return progress
case Disappearing:
return 1 - progress
}
return progress
}
// Visible returns whether any part of the animated entity should be visible during the
// current animation frame.
func (v *VisibilityAnimation) Visible() bool {
return v.State != Invisible
}
// Animating returns whether the animation is either in the process of appearing or
// disappearing.
func (v *VisibilityAnimation) Animating() bool {
return v.State == Appearing || v.State == Disappearing
}
// Appear triggers the animation to begin becoming visible at the provided time. It is
// a no-op if the animation is already visible.
func (v *VisibilityAnimation) Appear(now time.Time) {
if !v.Visible() && !v.Animating() {
v.State = Appearing
v.Started = now
}
}
// Disappear triggers the animation to begin becoming invisible at the provided time.
// It is a no-op if the animation is already invisible.
func (v *VisibilityAnimation) Disappear(now time.Time) {
if v.Visible() {
if v.Animating() {
v.Duration = now.Sub(v.Started)
}
v.State = Disappearing
v.Started = now
}
}
// ToggleVisibility will make an invisible animation begin the process of becoming
// visible and a visible animation begin the process of disappearing.
func (v *VisibilityAnimation) ToggleVisibility(now time.Time) {
if v.Visible() {
v.Disappear(now)
} else {
v.Appear(now)
}
}
func (v *VisibilityAnimation) String(gtx C) string {
return fmt.Sprintf(
"State: %v, Revealed: %f, Duration: %v, Started: %v",
v.State,
v.Revealed(gtx),
v.Duration,
v.Started.Local(),
)
}
// VisibilityAnimationState represents possible states that a VisibilityAnimation can
// be in.
type VisibilityAnimationState int
// Visibility constants
const (
Visible VisibilityAnimationState = iota
Disappearing
Appearing
Invisible
)
func (v VisibilityAnimationState) String() string {
switch v {
case Visible:
return "visible"
case Disappearing:
return "disappearing"
case Appearing:
return "appearing"
case Invisible:
return "invisible"
default:
return "invalid VisibilityAnimationState"
}
}
// Progress is an animation primitive that tracks progress of time over a fixed
// duration as a float between [0, 1].
//
// Progress is reversible.
//
// Widgets map async UI events to state changes: stop, forward, reverse.
// Widgets then interpolate visual data based on progress value.
//
// Update method must be called every tick to HandleEvents the progress value.
type Progress struct {
progress float32
duration time.Duration
began time.Time
direction ProgressDirection
active bool
}
// ProgressDirection specifies how to HandleEvents progress every tick.
type ProgressDirection int
const (
// Forward progresses from 0 to 1.
Forward ProgressDirection = iota
// Reverse progresses from 1 to 0.
Reverse
)
// Progress reports the current progress as a float between [0, 1].
func (p *Progress) Progress() float32 {
if p.progress < 0.0 {
return 0.0
}
if p.progress > 1.0 {
return 1.0
}
return p.progress
}
// Absolute reports the absolute progress, ignoring direction.
func (p *Progress) Absolute() float32 {
if p.direction == Forward {
return p.Progress()
}
return 1 - p.Progress()
}
// Direction reports the current direction.
func (p *Progress) Direction() ProgressDirection {
return p.direction
}
// Started reports true if progression has started.
func (p *Progress) Started() bool {
return p.active
}
// Finished is true when animation is done
func (p *Progress) Finished() bool {
switch p.direction {
case Forward:
return p.progress >= 1.0
case Reverse:
return p.progress <= 0.0
}
return false
}
// Start the progress in the given direction over the given duration.
func (p *Progress) Start(began time.Time, direction ProgressDirection, duration time.Duration) {
if !p.active {
p.active = true
p.began = began
p.direction = direction
p.duration = duration
p.Update(began)
}
}
// Stop the progress.
func (p *Progress) Stop() {
p.active = false
}
// Update will do HandleEvents now
func (p *Progress) Update(now time.Time) {
if !p.Started() || p.Finished() {
p.Stop()
return
}
var (
elapsed = now.Sub(p.began).Milliseconds()
total = p.duration.Milliseconds()
)
switch p.direction {
case Forward:
p.progress = float32(elapsed) / float32(total)
case Reverse:
p.progress = 1 - float32(elapsed)/float32(total)
}
}
func (d ProgressDirection) String() string {
switch d {
case Forward:
return "forward"
case Reverse:
return "reverse"
}
return "unknown"
}