Skip to main content

freya_components/
overflowed_content.rs

1use std::time::Duration;
2
3use dioxus::prelude::*;
4use freya_elements as dioxus_elements;
5use freya_hooks::{
6    use_animation,
7    use_node_signal,
8    AnimDirection,
9    AnimNum,
10    Ease,
11    Function,
12    OnFinish,
13};
14
15/// Animate the content of a container when the content overflows.
16///
17/// This is primarily targeted to text that can't be fully shown in small layouts.
18///
19/// # Example
20///
21/// ```no_run
22/// # use freya::prelude::*;
23/// fn app() -> Element {
24///     rsx!(
25///         Button {
26///             OverflowedContent {
27///                 width: "100",
28///                 rect {
29///                     direction: "horizontal",
30///                     cross_align: "center",
31///                     label {
32///                         "Freya is a cross-platform GUI library for Rust"
33///                     }
34///                 }
35///             }
36///         }
37///     )
38/// }
39/// ```
40#[component]
41pub fn OverflowedContent(
42    children: Element,
43    #[props(default = "100%".to_string())] width: String,
44    #[props(default = "auto".to_string())] height: String,
45    #[props(default = Duration::from_secs(4))] duration: Duration,
46) -> Element {
47    let (label_reference, label_size) = use_node_signal();
48    let (rect_reference, rect_size) = use_node_signal();
49
50    let rect_width = rect_size.read().area.width();
51    let label_width = label_size.read().area.width();
52    let does_overflow = label_width > rect_width;
53
54    let animation = use_animation(move |conf| {
55        conf.on_finish(OnFinish::Restart);
56
57        AnimNum::new(0., 100.)
58            .duration(duration)
59            .ease(Ease::InOut)
60            .function(Function::Linear)
61    });
62
63    use_effect(use_reactive!(|does_overflow| {
64        if does_overflow {
65            animation.run(AnimDirection::Forward);
66        }
67    }));
68
69    let progress = animation.get().read().read();
70    let offset_x = if does_overflow {
71        ((label_width + rect_width) * progress / 100.) - rect_width
72    } else {
73        0.
74    };
75
76    rsx!(
77        rect {
78            width,
79            height,
80            offset_x: "{-offset_x}",
81            overflow: "clip",
82            reference: rect_reference,
83            rect {
84                reference: label_reference,
85                max_lines: "1",
86                {children}
87            }
88        }
89    )
90}
91
92#[cfg(test)]
93mod test {
94    use std::time::Duration;
95
96    use freya::prelude::*;
97    use freya_testing::prelude::*;
98    use tokio::time::sleep;
99
100    #[tokio::test]
101    pub async fn overflowed_content() {
102        fn app() -> Element {
103            rsx!(
104                OverflowedContent {
105                    duration: Duration::from_millis(50),
106                    width: "50",
107                    label {
108                        "123456789123456789"
109                    }
110                }
111            )
112        }
113
114        let mut utils = launch_test(app);
115
116        // Disable event loop ticker
117        utils.config().event_loop_ticker = false;
118
119        let root = utils.root();
120        let label = root.get(0).get(0).get(0);
121
122        utils.wait_for_update().await;
123        utils.wait_for_update().await;
124        utils.wait_for_update().await;
125        assert_eq!(label.layout().unwrap().area.min_x(), 50.);
126
127        sleep(Duration::from_millis(5)).await;
128        utils.wait_for_update().await;
129        utils.wait_for_update().await;
130        assert_ne!(label.layout().unwrap().area.min_x(), 50.);
131    }
132}