freya_components/
animated_position.rs1use 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 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}