1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use std::time::Duration;

use dioxus::prelude::*;
use freya_elements::elements as dioxus_elements;
use freya_hooks::{
    use_animation,
    use_node_signal,
    AnimDirection,
    AnimNum,
    Ease,
    Function,
    OnFinish,
};

/// Animate the content of a container when the content overflows.
///
/// This is primarily targeted to text that can't be fully shown in small layouts.
///
/// # Example
///
/// ```no_run
/// # use freya::prelude::*;
/// fn app() -> Element {
///     rsx!(
///         Button {
///             OverflowedContent {
///                 width: "100",
///                 rect {
///                     direction: "horizontal",
///                     cross_align: "center",
///                     label {
///                         "Freya is a cross-platform GUI library for Rust"
///                     }
///                 }
///             }
///         }
///     )
/// }
/// ```
#[component]
pub fn OverflowedContent(
    children: Element,
    #[props(default = "100%".to_string())] width: String,
    #[props(default = "auto".to_string())] height: String,
    #[props(default = Duration::from_secs(4))] duration: Duration,
) -> Element {
    let (label_reference, label_size) = use_node_signal();
    let (rect_reference, rect_size) = use_node_signal();

    let rect_width = rect_size.read().area.width();
    let label_width = label_size.read().area.width();
    let does_overflow = label_width > rect_width;

    let animations = use_animation(move |ctx| {
        ctx.on_finish(OnFinish::Restart);

        ctx.with(
            AnimNum::new(0., 100.)
                .duration(duration)
                .ease(Ease::InOut)
                .function(Function::Linear),
        )
    });

    use_effect(use_reactive!(|does_overflow| {
        if does_overflow {
            animations.run(AnimDirection::Forward);
        }
    }));

    let progress = animations.get();
    let progress = progress.read().as_f32();
    let offset_x = if does_overflow {
        ((label_width + rect_width) * progress / 100.) - rect_width
    } else {
        0.
    };

    rsx!(
        rect {
            width,
            height,
            offset_x: "{-offset_x}",
            overflow: "clip",
            reference: rect_reference,
            rect {
                reference: label_reference,
                max_lines: "1",
                {children}
            }
        }
    )
}

#[cfg(test)]
mod test {
    use std::time::Duration;

    use freya::prelude::*;
    use freya_testing::prelude::*;
    use tokio::time::sleep;

    #[tokio::test]
    pub async fn overflowed_content() {
        fn app() -> Element {
            rsx!(
                OverflowedContent {
                    duration: Duration::from_millis(50),
                    width: "50",
                    label {
                        "123456789123456789"
                    }
                }
            )
        }

        let mut utils = launch_test(app);

        let root = utils.root();
        let label = root.get(0).get(0).get(0);

        utils.wait_for_update().await;
        utils.wait_for_update().await;
        assert_eq!(label.layout().unwrap().area.min_x(), 50.);

        sleep(Duration::from_millis(50)).await;
        utils.wait_for_update().await;
        utils.wait_for_update().await;
        assert!(label.layout().unwrap().area.min_x() < 0.);

        sleep(Duration::from_millis(50)).await;
        utils.wait_for_update().await;
        utils.wait_for_update().await;
        utils.wait_for_update().await;
        assert_eq!(label.layout().unwrap().area.min_x(), 50.);
    }
}