-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathanimation.v
More file actions
208 lines (191 loc) · 5.76 KB
/
animation.v
File metadata and controls
208 lines (191 loc) · 5.76 KB
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
module gui
// animation.v defines the Animation interface and concrete types: Animate
// (one-shot or repeating callback) and BlinkCursorAnimation (cursor blink).
// The animation loop runs in a goroutine and fires refresh_layout or
// refresh_render_only depending on each animation's refresh_kind(). The
// blink cursor uses render_only; Animate (which changes app state) uses layout.
import time
const animation_cycle = 16 * time.millisecond
const animation_delay = 500 * time.millisecond
const blink_cursor_animation_id = '___blinky_cursor_animation___'
const blink_cursor_animation_delay = 600 * time.millisecond
enum AnimationRefreshKind as u8 {
none
render_only
layout
}
type AnimationCallback = fn (mut Window)
interface Animation {
id string
refresh_kind() AnimationRefreshKind
mut:
delay time.Duration
start time.Time
stopped bool
}
// Animate waits the specified delay duration and then executes the callback.
pub struct Animate implements Animation {
pub:
id string @[required]
callback fn (mut Animate, mut Window) @[required]
pub mut:
delay time.Duration = animation_delay
repeat bool
mut:
start time.Time
stopped bool
}
fn (_ Animate) refresh_kind() AnimationRefreshKind {
return .layout
}
struct BlinkCursorAnimation implements Animation {
pub:
id string = blink_cursor_animation_id
mut:
delay time.Duration = blink_cursor_animation_delay
start time.Time
stopped bool
}
fn (_ BlinkCursorAnimation) refresh_kind() AnimationRefreshKind {
return .render_only
}
// animation_add registers a new animation to the window's animation queue.
// If an animation with the same id already exists, it will be replaced.
// The animation's start time is set to the current time and will be processed
// in the animation loop.
pub fn (mut window Window) animation_add(mut animation Animation) {
if window.try_lock() {
defer { window.unlock() }
}
animation.start = time.now()
window.animations[animation.id] = animation
}
// has_animation returns true if an animation with the given id is
// currently active. Safe to call during view generation (no lock).
pub fn (window &Window) has_animation(id string) bool {
return id in window.animations
}
// remove_animation stops and removes an animation by id.
pub fn (mut window Window) remove_animation(id string) {
window.lock()
defer { window.unlock() }
window.animations.delete(id)
}
fn (mut window Window) animation_loop() {
// dt in seconds for spring physics
dt := f32(animation_cycle) / f32(time.second)
// Pre-allocate and reuse across ticks to avoid per-tick allocs.
mut deferred := []AnimationCallback{cap: 4}
mut stopped_ids := []string{cap: 4}
for {
time.sleep(animation_cycle)
mut refresh_kind := AnimationRefreshKind.none
array_clear(mut deferred)
array_clear(mut stopped_ids)
//--------------------------------------------
window.lock()
for _, mut animation in window.animations {
match mut animation {
Animate {
if update_animate(mut animation, mut window, mut deferred) {
refresh_kind = max_animation_refresh_kind(refresh_kind, animation.refresh_kind())
}
}
BlinkCursorAnimation {
if update_blink_cursor(mut animation, mut window) {
refresh_kind = max_animation_refresh_kind(refresh_kind, animation.refresh_kind())
}
}
TweenAnimation {
if update_tween(mut animation, mut window, mut deferred) {
refresh_kind = max_animation_refresh_kind(refresh_kind, animation.refresh_kind())
}
}
SpringAnimation {
if update_spring(mut animation, mut window, dt, mut deferred) {
refresh_kind = max_animation_refresh_kind(refresh_kind, animation.refresh_kind())
}
}
LayoutTransition {
if update_layout_transition(mut animation, mut window, mut deferred) {
refresh_kind = max_animation_refresh_kind(refresh_kind, animation.refresh_kind())
}
}
HeroTransition {
if update_hero_transition(mut animation, mut window, mut deferred) {
refresh_kind = max_animation_refresh_kind(refresh_kind, animation.refresh_kind())
}
}
KeyframeAnimation {
if update_keyframe(mut animation, mut window, mut deferred) {
refresh_kind = max_animation_refresh_kind(refresh_kind, animation.refresh_kind())
}
}
else {}
}
if animation.stopped {
stopped_ids << animation.id
}
}
for id in stopped_ids {
window.animations.delete(id)
}
window.unlock()
//--------------------------------------------
// Queue deferred callbacks to be executed on the main thread
for cb in deferred {
window.queue_command(cb)
}
match refresh_kind {
.render_only {
window.request_render_only()
}
.layout {
window.update_window()
}
.none {}
}
}
}
fn max_animation_refresh_kind(current AnimationRefreshKind, incoming AnimationRefreshKind) AnimationRefreshKind {
if current == .layout || incoming == .layout {
return .layout
}
if current == .render_only || incoming == .render_only {
return .render_only
}
return .none
}
fn update_animate(mut an Animate, mut w Window, mut deferred []AnimationCallback) bool {
if !an.stopped {
if time.since(an.start) > an.delay {
// Capture callback to call after lock release
callback := an.callback
deferred << fn [callback, mut an] (mut w Window) {
callback(mut an, mut w)
}
match an.repeat {
true { an.start = time.now() }
else { an.stopped = true }
}
return true
}
}
return false
}
fn update_blink_cursor(mut b BlinkCursorAnimation, mut w Window) bool {
if b.stopped {
return false
}
if time.since(b.start) > b.delay {
if w.view_state.cursor_on_sticky {
w.view_state.input_cursor_on = true
w.view_state.cursor_on_sticky = false
} else {
w.view_state.input_cursor_on = !w.view_state.input_cursor_on
}
b.start = time.now()
return true
}
return false
}