ferroui_base/animation/
page_slide.rs1use 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
15#[repr(i32)]
16pub enum SlideAxis {
17 #[default]
18 Horizontal = 0,
19 Vertical = 1,
20}
21
22pub 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 pub fn new() -> Self {
41 Self::with_duration(TimeSpan::ZERO, SlideAxis::Horizontal)
42 }
43
44 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 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 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 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 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 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 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}