~whereswaldon/gio-x

ref: b0198a1b547035c76d814b60de9aec3fce6353a8 gio-x/component/animation.go -rw-r--r-- 5.5 KiB
b0198a1bChris Waldon component: document and refactor tooltip types 5 months ago
                                                                                
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
package component

import (
	"fmt"
	"time"

	"gioui.org/layout"
	"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 layout.Context to ensure that the next frame will be generated
// promptly.
func (v *VisibilityAnimation) Revealed(gtx layout.Context) 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 appearsing 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() && !v.Animating() {
		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 layout.Context) 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

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 reversable.
//
// 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 update the progress value.
type Progress struct {
	progress  float32
	duration  time.Duration
	began     time.Time
	direction ProgressDirection
	active    bool
}

// ProgressDirection specifies how to update 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
}

// 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
}

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
}

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"
}