Skip to main content

ribir_core/animation/
animate.rs

1use 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  // Determines if lerp value in current frame.
24  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              // Stop the animate at the next frame of animate finished, to ensure draw the
63              // last frame of the animate.
64              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              // Forgets modifies because we only modifies the inner info.
83              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 the animate state modified, we need to update the restore value.
97          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  /// Advance the animation to the given time, you must start the animation
138  /// before calling this method, the `at` relative to the start time.
139  ///
140  /// ## Panics
141  ///
142  /// Panics if the animation is not running.
143  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        // the state may change during animate.
160        *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}