ribir_core/animation/
animate.rs1use crate::{prelude::*, ticker::FrameMsg, window::WindowId};
2#[simple_declare]
3pub struct Animate<S>
4where
5 S: AnimateState + 'static,
6{
7 #[declare(strict, default = transitions::LINEAR.of(ctx!()))]
8 pub transition: Box<dyn Transition>,
9 #[declare(strict)]
10 pub state: S,
11 pub from: S::Value,
12 #[declare(skip)]
13 running_info: Option<AnimateInfo<S::Value>>,
14 #[declare(skip, default = ctx!().window().id())]
15 window_id: WindowId,
16}
17
18pub(crate) struct AnimateInfo<V> {
19 from: V,
20 to: V,
21 start_at: Instant,
22 last_progress: AnimateProgress,
23 already_lerp: bool,
25 _tick_msg_guard: Option<Box<dyn Any>>,
26}
27
28impl<S, T> Animation for T
29where
30 S: AnimateState + 'static,
31 S::Value: Clone,
32 T: StateWriter<Value = Animate<S>>,
33{
34 fn run(&self) {
35 let mut animate_ref = self.write();
36 let this = &mut *animate_ref;
37 let wnd_id = this.window_id;
38 let new_to = this.state.get();
39
40 if let Some(AnimateInfo { from, to, last_progress, .. }) = &mut this.running_info {
41 *from = this
42 .state
43 .calc_lerp_value(from, to, last_progress.value());
44 *to = new_to;
45 } else if let Some(wnd) = AppCtx::get_window(wnd_id) {
46 drop(animate_ref);
47
48 let animate = self.clone_writer();
49 let this = &mut *self.write();
50 let tick_handle = wnd
51 .frame_ticker
52 .frame_tick_stream()
53 .subscribe(move |msg| {
54 match msg {
55 FrameMsg::NewFrame(time) => {
56 let p = animate
57 .read()
58 .running_info
59 .as_ref()
60 .unwrap()
61 .last_progress;
62 if matches!(p, AnimateProgress::Finish) {
65 let wnd = AppCtx::get_window(wnd_id).unwrap();
66 let animate = animate.clone_writer();
67 wnd
68 .frame_spawn(async move { animate.stop() })
69 .unwrap();
70 } else {
71 animate.shallow().advance_to(time);
72 }
73 }
74 FrameMsg::LayoutReady(_) => {}
75 FrameMsg::Finish(_) => {
76 let mut animate = animate.write();
77 let info = animate.running_info.as_mut().unwrap();
78 info.already_lerp = false;
79 let data_value = info.to.clone();
80 animate.state.set(data_value);
81
82 animate.forget_modifies();
84 }
85 }
86 })
87 .unsubscribe_when_dropped();
88
89 let animate = self.clone_writer();
90 let state_handle = this
91 .state
92 .animate_state_modifies()
93 .subscribe(move |_| {
94 let mut animate = animate.write();
95 let v = animate.state.get();
96 if let Some(info) = animate.running_info.as_mut() {
98 info.to = v;
99 }
100 animate.forget_modifies();
101 })
102 .unsubscribe_when_dropped();
103
104 this.running_info = Some(AnimateInfo {
105 from: this.from.clone(),
106 to: new_to,
107 start_at: Instant::now(),
108 last_progress: AnimateProgress::Dismissed,
109 _tick_msg_guard: Some(Box::new((tick_handle, state_handle))),
110 already_lerp: false,
111 });
112 wnd.inc_running_animate();
113 }
114 }
115
116 fn is_running(&self) -> bool { self.read().is_running() }
117
118 fn stop(&self) {
119 let mut this = self.silent();
120 if this.is_running() {
121 if let Some(wnd) = AppCtx::get_window(this.window_id) {
122 wnd.dec_running_animate();
123 this.running_info.take();
124 }
125 }
126 }
127
128 fn box_clone(&self) -> Box<dyn Animation> { Box::new(self.clone_writer()) }
129}
130
131impl<S> Animate<S>
132where
133 S: AnimateState + 'static,
134{
135 pub fn is_running(&self) -> bool { self.running_info.is_some() }
136
137 fn advance_to(&mut self, at: Instant) -> AnimateProgress {
144 let AnimateInfo { from, to, start_at, last_progress, already_lerp, .. } = self
145 .running_info
146 .as_mut()
147 .expect("This animation is not running.");
148
149 if *already_lerp {
150 return *last_progress;
151 }
152
153 let elapsed = at - *start_at;
154 let progress = self.transition.rate_of_change(elapsed);
155
156 match progress {
157 AnimateProgress::Between(rate) => {
158 let value = self.state.calc_lerp_value(from, to, rate);
159 *to = self.state.get();
161 self.state.set(value);
162 }
163 AnimateProgress::Dismissed => self.state.set(from.clone()),
164 AnimateProgress::Finish => {}
165 }
166
167 *last_progress = progress;
168 *already_lerp = true;
169
170 progress
171 }
172}
173
174impl<P> Drop for Animate<P>
175where
176 P: AnimateState + 'static,
177{
178 fn drop(&mut self) {
179 if self.running_info.is_some() {
180 if let Some(wnd) = AppCtx::get_window(self.window_id) {
181 wnd.dec_running_animate();
182 }
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::{reset_test_env, test_helper::TestWindow};
191
192 #[test]
193 fn fix_animate_circular_mut_borrow() {
194 reset_test_env!();
195
196 let w = fn_widget! {
197 let animate = @Animate {
198 transition: EasingTransition {
199 easing: easing::LINEAR,
200 duration: Duration::ZERO,
201 }.box_it(),
202 state: Stateful::new(1.),
203 from: 0.,
204 };
205 animate.run();
206 @Void {}
207 };
208
209 let mut wnd = TestWindow::new(w);
210 wnd.draw_frame();
211 }
212
213 #[test]
214 fn fix_write_state_during_animate_running() {
215 reset_test_env!();
216 let state = Stateful::new(0);
217 let c_state = state.clone_reader();
218 let w = fn_widget! {
219 let animate = @Animate {
220 transition: EasingTransition {
221 easing: easing::LINEAR,
222 duration: Duration::from_millis(1),
223 }.box_it(),
224 state: state.clone_writer(),
225 from: 100,
226 };
227
228 animate.run();
229
230 @Void { on_performed_layout: move |_| *$state.write() = 1 }
231 };
232
233 let mut wnd = TestWindow::new(w);
234 wnd.draw_frame();
235 assert_eq!(*c_state.read(), 1);
236 }
237}