Skip to main content

freya_components/
animated_position.rs

1use std::time::Duration;
2
3use dioxus::prelude::*;
4use freya_elements as dioxus_elements;
5use freya_hooks::{
6    use_animation_with_dependencies,
7    use_node_signal_with_prev,
8    AnimDirection,
9    AnimNum,
10    Ease,
11    Function,
12};
13
14#[component]
15pub fn AnimatedPosition(
16    children: Element,
17    width: String,
18    height: String,
19    #[props(default = Function::default())] function: Function,
20    #[props(default = Duration::from_millis(250))] duration: Duration,
21    #[props(default = Ease::default())] ease: Ease,
22) -> Element {
23    let mut render_element = use_signal(|| false);
24    let (reference, size, old_size) = use_node_signal_with_prev();
25
26    let animation = use_animation_with_dependencies(
27        &(function, duration, ease),
28        move |_conf, (function, duration, ease)| {
29            let old_size = old_size().unwrap_or_default();
30            let size = size().unwrap_or_default();
31            (
32                AnimNum::new(size.area.origin.x, old_size.area.origin.x)
33                    .duration(duration)
34                    .ease(ease)
35                    .function(function),
36                AnimNum::new(size.area.origin.y, old_size.area.origin.y)
37                    .duration(duration)
38                    .ease(ease)
39                    .function(function),
40            )
41        },
42    );
43
44    use_effect(move || {
45        if animation.is_running() {
46            render_element.set(true);
47        }
48    });
49
50    use_effect(move || {
51        let has_size = size.read().is_some();
52        let has_old_size = old_size.read().is_some();
53        if has_size && has_old_size {
54            animation.run(AnimDirection::Reverse);
55        } else if has_size {
56            render_element.set(true);
57        }
58    });
59
60    let (offset_x, offset_y) = &*animation.get().read_unchecked();
61    let offset_x = offset_x.read();
62    let offset_y = offset_y.read();
63
64    rsx!(
65        rect {
66            reference,
67            width: "{width}",
68            height: "{height}",
69            rect {
70                width: "0",
71                height: "0",
72                offset_x: "{offset_x}",
73                offset_y: "{offset_y}",
74                position: "global",
75                if render_element() {
76                    rect {
77                        width: "{size.read().as_ref().unwrap().area.width()}",
78                        height: "{size.read().as_ref().unwrap().area.height()}",
79                        {children}
80                    }
81                }
82            }
83        }
84    )
85}
86
87#[cfg(test)]
88mod test {
89    use std::time::Duration;
90
91    use freya::prelude::*;
92    use freya_testing::prelude::*;
93
94    #[tokio::test]
95    pub async fn animated_position() {
96        fn animated_position_app() -> Element {
97            let mut padding = use_signal(|| (100., 100.));
98
99            rsx!(
100                rect {
101                    padding: "{padding().0} {padding().1}",
102                    onclick: move |_| {
103                        padding.write().0 += 10.;
104                        padding.write().1 += 10.;
105                    },
106                    AnimatedPosition {
107                        width: "50",
108                        height: "50",
109                        function: Function::Linear
110                    }
111                }
112            )
113        }
114
115        let mut utils = launch_test(animated_position_app);
116
117        // Disable event loop ticker
118        utils.config().event_loop_ticker = false;
119
120        let root = utils.root();
121        utils.wait_for_update().await;
122        utils.wait_for_update().await;
123
124        let get_positions = || {
125            root.get(0)
126                .get(0)
127                .get(0)
128                .get(0)
129                .layout()
130                .unwrap()
131                .area
132                .origin
133        };
134
135        assert_eq!(get_positions().x, 100.);
136        assert_eq!(get_positions().y, 100.);
137
138        utils.click_cursor((5.0, 5.0)).await;
139        utils.wait_for_update().await;
140        utils.wait_for_update().await;
141        tokio::time::sleep(Duration::from_millis(125)).await;
142        utils.wait_for_update().await;
143        utils.wait_for_update().await;
144
145        assert!(get_positions().x < 106.);
146        assert!(get_positions().x > 105.);
147
148        assert!(get_positions().y < 106.);
149        assert!(get_positions().y > 105.);
150
151        utils.config().event_loop_ticker = true;
152
153        utils.wait_for_update().await;
154        tokio::time::sleep(Duration::from_millis(125)).await;
155        utils.wait_for_update().await;
156
157        assert_eq!(get_positions().x, 110.);
158    }
159}