freya_components/
overflowed_content.rs

1use std::time::Duration;
2
3use freya_animation::prelude::{
4    AnimDirection,
5    AnimNum,
6    Ease,
7    Function,
8    OnFinish,
9    use_animation,
10};
11use freya_core::prelude::*;
12use torin::{
13    prelude::Area,
14    size::Size,
15};
16
17/// Animate the content of a container when the content overflows.
18///
19/// This is primarily targeted to text that can't be fully shown in small layouts.
20///
21/// # Example
22///
23/// ```rust
24/// # use freya::prelude::*;
25/// fn app() -> impl IntoElement {
26///     Button::new().child(
27///         OverflowedContent::new().width(Size::px(100.)).child(
28///             label()
29///                 .text("Freya is a cross-platform GUI library for Rust")
30///                 .max_lines(1),
31///         ),
32///     )
33/// }
34/// ```
35#[derive(Clone, PartialEq)]
36pub struct OverflowedContent {
37    children: Vec<Element>,
38    width: Size,
39    height: Size,
40    duration: Duration,
41    key: DiffKey,
42}
43
44impl Default for OverflowedContent {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl ChildrenExt for OverflowedContent {
51    fn get_children(&mut self) -> &mut Vec<Element> {
52        &mut self.children
53    }
54}
55
56impl KeyExt for OverflowedContent {
57    fn write_key(&mut self) -> &mut DiffKey {
58        &mut self.key
59    }
60}
61
62impl OverflowedContent {
63    pub fn new() -> Self {
64        Self {
65            children: Vec::new(),
66            width: Size::fill(),
67            height: Size::auto(),
68            duration: Duration::from_secs(4),
69            key: DiffKey::None,
70        }
71    }
72
73    pub fn width(mut self, width: impl Into<Size>) -> Self {
74        self.width = width.into();
75        self
76    }
77
78    pub fn height(mut self, height: impl Into<Size>) -> Self {
79        self.height = height.into();
80        self
81    }
82
83    pub fn duration(mut self, duration: Duration) -> Self {
84        self.duration = duration;
85        self
86    }
87}
88
89impl Render for OverflowedContent {
90    fn render(&self) -> impl IntoElement {
91        let mut label_size = use_state(Area::default);
92        let mut rect_size = use_state(Area::default);
93
94        let rect_width = rect_size.read().width();
95        let label_width = label_size.read().width();
96        let does_overflow = label_width > rect_width;
97
98        let duration = self.duration;
99        let animation = use_animation(move |conf| {
100            conf.on_finish(OnFinish::restart());
101
102            AnimNum::new(0., 100.)
103                .duration(duration)
104                .ease(Ease::InOut)
105                .function(Function::Linear)
106        });
107
108        use_side_effect_with_deps(&does_overflow, move |does_overflow| {
109            if *does_overflow {
110                animation.run(AnimDirection::Forward);
111            }
112        });
113
114        let progress = animation.get().value();
115        let offset_x = if does_overflow {
116            ((label_width + rect_width) * progress / 100.) - rect_width
117        } else {
118            0.
119        };
120
121        rect()
122            .width(self.width.clone())
123            .height(self.height.clone())
124            .offset_x(-offset_x)
125            .overflow(Overflow::Clip)
126            .on_sized(move |e: Event<SizedEventData>| rect_size.set(e.area))
127            .child(
128                rect()
129                    .on_sized(move |e: Event<SizedEventData>| label_size.set(e.area))
130                    .children(self.children.clone()),
131            )
132    }
133
134    fn render_key(&self) -> DiffKey {
135        self.key.clone().or(self.default_key())
136    }
137}