Skip to main content

ferroui_base/animation/
page_slide.rs

1use crate::animation::easings::Easing;
2use crate::animation::{
3    Animation, Cue, FillMode, IPageTransition, IProgressPageTransition, KeyFrame, PageTransitionItem, TimeSpan,
4};
5use crate::animation::i_page_transition::{start_async, when_all};
6use crate::media::{ITransform, TranslateTransform};
7use crate::styling::Setter;
8use crate::threading::{CancellationToken, DispatcherTask};
9use crate::{Ref, Visual};
10use std::cell::{Cell, RefCell};
11use std::rc::Rc;
12
13/// The axis on which a [`PageSlide`] should occur.
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
15#[repr(i32)]
16pub enum SlideAxis {
17    #[default]
18    Horizontal = 0,
19    Vertical = 1,
20}
21
22/// Transitions between two pages by sliding them horizontally or
23/// vertically.
24pub struct PageSlide {
25    duration: Cell<TimeSpan>,
26    orientation: Cell<SlideAxis>,
27    slide_in_easing: RefCell<Easing>,
28    slide_out_easing: RefCell<Easing>,
29    fill_mode: Cell<FillMode>,
30}
31
32impl Default for PageSlide {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl PageSlide {
39    /// Creates a horizontal slide with a zero duration.
40    pub fn new() -> Self {
41        Self::with_duration(TimeSpan::ZERO, SlideAxis::Horizontal)
42    }
43
44    /// Creates a slide with the given duration and axis.
45    pub fn with_duration(duration: TimeSpan, orientation: SlideAxis) -> Self {
46        Self {
47            duration: Cell::new(duration),
48            orientation: Cell::new(orientation),
49            slide_in_easing: RefCell::new(Easing::default()),
50            slide_out_easing: RefCell::new(Easing::default()),
51            fill_mode: Cell::new(FillMode::Forward),
52        }
53    }
54
55    /// The duration of the animation.
56    pub fn duration(&self) -> TimeSpan {
57        self.duration.get()
58    }
59
60    pub fn set_duration(&self, value: TimeSpan) {
61        self.duration.set(value)
62    }
63
64    /// The direction of the animation.
65    pub fn orientation(&self) -> SlideAxis {
66        self.orientation.get()
67    }
68
69    pub fn set_orientation(&self, value: SlideAxis) {
70        self.orientation.set(value)
71    }
72
73    /// The easing used for the page that slides in.
74    pub fn slide_in_easing(&self) -> Easing {
75        self.slide_in_easing.borrow().clone()
76    }
77
78    pub fn set_slide_in_easing(&self, value: impl Into<Easing>) {
79        *self.slide_in_easing.borrow_mut() = value.into();
80    }
81
82    /// The easing used for the page that slides out.
83    pub fn slide_out_easing(&self) -> Easing {
84        self.slide_out_easing.borrow().clone()
85    }
86
87    pub fn set_slide_out_easing(&self, value: impl Into<Easing>) {
88        *self.slide_out_easing.borrow_mut() = value.into();
89    }
90
91    /// The fill mode.
92    pub fn fill_mode(&self) -> FillMode {
93        self.fill_mode.get()
94    }
95
96    pub fn set_fill_mode(&self, value: FillMode) {
97        self.fill_mode.set(value)
98    }
99
100    /// The common visual parent of the two controls. Panics when the
101    /// controls have different parents or no parent.
102    pub fn get_visual_parent(from: Option<&Ref<Visual>>, to: Option<&Ref<Visual>>) -> Ref<Visual> {
103        let p1 = from.or(to).expect("a page").visual_parent();
104        let p2 = to.or(from).expect("a page").visual_parent();
105
106        if let (Some(p1), Some(p2)) = (&p1, &p2) {
107            if p1 != p2 {
108                panic!("Controls for PageSlide must have same parent.");
109            }
110        }
111
112        match p1 {
113            Some(p1) => p1,
114            None => panic!("Cannot determine visual parent."),
115        }
116    }
117
118    fn translate_transform(visual: &Ref<Visual>) -> Ref<TranslateTransform> {
119        let existing = visual
120            .render_transform()
121            .and_then(|transform| transform.as_object().and_then(|o| o.to_ref().cast::<TranslateTransform>()));
122        match existing {
123            Some(transform) => transform,
124            None => {
125                let transform = TranslateTransform::new();
126                let handle: Rc<dyn ITransform> = (&transform).into();
127                visual.set_render_transform(Some(handle));
128                transform
129            }
130        }
131    }
132}
133
134impl IPageTransition for PageSlide {
135    fn start(
136        &self,
137        from: Option<&Ref<Visual>>,
138        to: Option<&Ref<Visual>>,
139        forward: bool,
140        cancellation_token: CancellationToken,
141    ) -> DispatcherTask<()> {
142        let horizontal = self.orientation.get() == SlideAxis::Horizontal;
143        let fill_mode = self.fill_mode.get();
144        let duration = self.duration.get();
145        let slide_in_easing = self.slide_in_easing();
146        let slide_out_easing = self.slide_out_easing();
147        let from = from.cloned();
148        let to = to.cloned();
149
150        start_async(async move {
151            if cancellation_token.is_cancellation_requested() {
152                return;
153            }
154
155            let mut tasks = Vec::new();
156            let parent = Self::get_visual_parent(from.as_ref(), to.as_ref());
157            let distance = if horizontal { parent.bounds().width } else { parent.bounds().height };
158            let translate_property =
159                if horizontal { TranslateTransform::x_property() } else { TranslateTransform::y_property() };
160
161            let slide = |easing: Easing, start: f64, end: f64| {
162                let animation = Animation::new();
163                animation.set_fill_mode(fill_mode);
164                animation.set_easing(easing);
165                animation
166                    .children()
167                    .add(KeyFrame::with_cue(Cue::new(0.0), [Setter::new(translate_property, start) as _]));
168                animation
169                    .children()
170                    .add(KeyFrame::with_cue(Cue::new(1.0), [Setter::new(translate_property, end) as _]));
171                animation.set_duration(duration);
172                animation
173            };
174
175            if let Some(from) = &from {
176                let animation = slide(slide_out_easing, 0.0, if forward { -distance } else { distance });
177                tasks.push(animation.run_async_with_clock(from, None, cancellation_token.clone()));
178            }
179
180            if let Some(to) = &to {
181                to.set_is_visible(true);
182                let animation = slide(slide_in_easing, if forward { distance } else { -distance }, 0.0);
183                tasks.push(animation.run_async_with_clock(to, None, cancellation_token.clone()));
184            }
185
186            when_all(tasks).await;
187
188            if cancellation_token.is_cancellation_requested() {
189                return;
190            }
191
192            if let Some(from) = &from {
193                from.set_is_visible(false);
194                if fill_mode != FillMode::None {
195                    from.set_render_transform(None);
196                }
197            }
198
199            if let Some(to) = &to {
200                if fill_mode != FillMode::None {
201                    to.set_render_transform(None);
202                }
203            }
204        })
205    }
206
207    fn as_progress_page_transition(&self) -> Option<&dyn IProgressPageTransition> {
208        Some(self)
209    }
210
211    fn as_page_slide(&self) -> Option<&PageSlide> {
212        Some(self)
213    }
214}
215
216impl IProgressPageTransition for PageSlide {
217    fn update(
218        &self,
219        progress: f64,
220        from: Option<&Ref<Visual>>,
221        to: Option<&Ref<Visual>>,
222        forward: bool,
223        page_length: f64,
224        visible_items: &[PageTransitionItem],
225    ) {
226        if !visible_items.is_empty() {
227            return;
228        }
229
230        if from.is_none() && to.is_none() {
231            return;
232        }
233
234        let parent = Self::get_visual_parent(from, to);
235        let horizontal = self.orientation.get() == SlideAxis::Horizontal;
236        let distance = if page_length > 0.0 {
237            page_length
238        } else if horizontal {
239            parent.bounds().width
240        } else {
241            parent.bounds().height
242        };
243        let offset = distance * progress;
244
245        if let Some(from) = from {
246            let ft = Self::translate_transform(from);
247            if horizontal {
248                ft.set_x(if forward { -offset } else { offset });
249            } else {
250                ft.set_y(if forward { -offset } else { offset });
251            }
252        }
253
254        if let Some(to) = to {
255            to.set_is_visible(true);
256            let tt = Self::translate_transform(to);
257            if horizontal {
258                tt.set_x(if forward { distance - offset } else { -(distance - offset) });
259            } else {
260                tt.set_y(if forward { distance - offset } else { -(distance - offset) });
261            }
262        }
263    }
264
265    fn reset(&self, visual: &Ref<Visual>) {
266        visual.set_render_transform(None);
267    }
268}