Skip to main content

freya_components/
overflowed_content.rs

1use std::time::Duration;
2
3use freya_animation::prelude::{
4    AnimDirection,
5    AnimNum,
6    Ease,
7    Function,
8    use_animation,
9};
10use freya_core::prelude::*;
11use torin::{
12    node::Node,
13    prelude::Area,
14    size::Size,
15};
16
17/// The direction in which [`OverflowedContent`] scrolls.
18#[derive(Clone, PartialEq, Default)]
19pub enum OverflowedContentDirection {
20    /// Content enters from the right edge and scrolls to the left.
21    #[default]
22    RightToLeft,
23    /// Content starts at the left edge and scrolls to the right.
24    LeftToRight,
25}
26
27/// Where the [`OverflowedContent`] animation starts from.
28#[derive(Clone, PartialEq, Default)]
29pub enum OverflowedContentStart {
30    /// Content starts off-screen and enters from the edge.
31    #[default]
32    Edge,
33    /// Content starts visible at its natural position.
34    Visible,
35}
36
37/// Animate the content of a container when the content overflows.
38///
39/// This is primarily targeted to text that can't be fully shown in small layouts.
40///
41/// # Example
42///
43/// ```rust
44/// # use freya::prelude::*;
45/// fn app() -> impl IntoElement {
46///     Button::new().child(
47///         OverflowedContent::new().width(Size::px(100.)).child(
48///             label()
49///                 .text("Freya is a cross-platform GUI library for Rust")
50///                 .max_lines(1),
51///         ),
52///     )
53/// }
54/// ```
55#[derive(Clone, PartialEq)]
56pub struct OverflowedContent {
57    children: Vec<Element>,
58    layout: LayoutData,
59    duration: Duration,
60    direction: OverflowedContentDirection,
61    start: OverflowedContentStart,
62    key: DiffKey,
63}
64
65impl LayoutExt for OverflowedContent {
66    fn get_layout(&mut self) -> &mut LayoutData {
67        &mut self.layout
68    }
69}
70
71impl ContainerSizeExt for OverflowedContent {}
72
73impl Default for OverflowedContent {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79impl ChildrenExt for OverflowedContent {
80    fn get_children(&mut self) -> &mut Vec<Element> {
81        &mut self.children
82    }
83}
84
85impl KeyExt for OverflowedContent {
86    fn write_key(&mut self) -> &mut DiffKey {
87        &mut self.key
88    }
89}
90
91impl OverflowedContent {
92    pub fn new() -> Self {
93        Self {
94            children: Vec::new(),
95            layout: Node {
96                width: Size::fill(),
97                height: Size::Inner,
98                ..Default::default()
99            }
100            .into(),
101            duration: Duration::from_secs(4),
102            direction: OverflowedContentDirection::default(),
103            start: OverflowedContentStart::default(),
104            key: DiffKey::None,
105        }
106    }
107
108    pub fn width(mut self, width: impl Into<Size>) -> Self {
109        self.layout.width = width.into();
110        self
111    }
112
113    pub fn height(mut self, height: impl Into<Size>) -> Self {
114        self.layout.height = height.into();
115        self
116    }
117
118    pub fn duration(mut self, duration: Duration) -> Self {
119        self.duration = duration;
120        self
121    }
122
123    pub fn direction(mut self, direction: OverflowedContentDirection) -> Self {
124        self.direction = direction;
125        self
126    }
127
128    pub fn right_to_left(self) -> Self {
129        self.direction(OverflowedContentDirection::RightToLeft)
130    }
131
132    pub fn left_to_right(self) -> Self {
133        self.direction(OverflowedContentDirection::LeftToRight)
134    }
135
136    pub fn start(mut self, start: OverflowedContentStart) -> Self {
137        self.start = start;
138        self
139    }
140
141    pub fn start_edge(self) -> Self {
142        self.start(OverflowedContentStart::Edge)
143    }
144
145    pub fn start_visible(self) -> Self {
146        self.start(OverflowedContentStart::Visible)
147    }
148}
149
150impl Component for OverflowedContent {
151    fn render(&self) -> impl IntoElement {
152        let mut content_area = use_state(Area::default);
153        let mut container_area = use_state(Area::default);
154
155        let container_width = container_area.read().width();
156        let content_width = content_area.read().width();
157        let does_overflow = content_width > container_width;
158
159        let duration = self.duration;
160
161        let animation = use_animation(move |_| {
162            AnimNum::new(0., 100.)
163                .duration(duration)
164                .ease(Ease::InOut)
165                .function(Function::Linear)
166        });
167
168        let is_running = *animation.is_running().read();
169        let has_run = *animation.has_run_yet().read();
170
171        use_side_effect_with_deps(
172            &(does_overflow, is_running, has_run),
173            move |&(does_overflow, is_running, has_run)| {
174                if does_overflow && (!has_run || !is_running) {
175                    animation.run(AnimDirection::Forward);
176                }
177            },
178        );
179
180        let progress = animation.get().value();
181        let is_first_cycle =
182            *animation.runs().read() <= 1 && self.start == OverflowedContentStart::Visible;
183
184        let offset_x = if does_overflow {
185            match (&self.direction, is_first_cycle) {
186                (OverflowedContentDirection::RightToLeft, false) => {
187                    container_width - (content_width + container_width) * progress / 100.
188                }
189                (OverflowedContentDirection::RightToLeft, true) => {
190                    -(content_width * progress / 100.)
191                }
192                (OverflowedContentDirection::LeftToRight, false) => {
193                    (content_width + container_width) * progress / 100. - content_width
194                }
195                (OverflowedContentDirection::LeftToRight, true) => {
196                    container_width * progress / 100.
197                }
198            }
199        } else {
200            0.
201        };
202
203        rect()
204            .width(self.layout.width.clone())
205            .height(self.layout.height.clone())
206            .overflow(Overflow::Clip)
207            .on_sized(move |e: Event<SizedEventData>| container_area.set(e.area))
208            .child(
209                rect()
210                    .offset_x(offset_x)
211                    .on_sized(move |e: Event<SizedEventData>| content_area.set(e.area))
212                    .children(self.children.clone()),
213            )
214    }
215
216    fn render_key(&self) -> DiffKey {
217        self.key.clone().or(self.default_key())
218    }
219}