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 {}
72impl ContainerPositionExt for OverflowedContent {}
73
74impl Default for OverflowedContent {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80impl ChildrenExt for OverflowedContent {
81    fn get_children(&mut self) -> &mut Vec<Element> {
82        &mut self.children
83    }
84}
85
86impl KeyExt for OverflowedContent {
87    fn write_key(&mut self) -> &mut DiffKey {
88        &mut self.key
89    }
90}
91
92impl OverflowedContent {
93    pub fn new() -> Self {
94        Self {
95            children: Vec::new(),
96            layout: Node {
97                width: Size::fill(),
98                height: Size::Inner,
99                ..Default::default()
100            }
101            .into(),
102            duration: Duration::from_secs(4),
103            direction: OverflowedContentDirection::default(),
104            start: OverflowedContentStart::default(),
105            key: DiffKey::None,
106        }
107    }
108
109    pub fn width(mut self, width: impl Into<Size>) -> Self {
110        self.layout.width = width.into();
111        self
112    }
113
114    pub fn height(mut self, height: impl Into<Size>) -> Self {
115        self.layout.height = height.into();
116        self
117    }
118
119    pub fn duration(mut self, duration: Duration) -> Self {
120        self.duration = duration;
121        self
122    }
123
124    pub fn direction(mut self, direction: OverflowedContentDirection) -> Self {
125        self.direction = direction;
126        self
127    }
128
129    pub fn right_to_left(self) -> Self {
130        self.direction(OverflowedContentDirection::RightToLeft)
131    }
132
133    pub fn left_to_right(self) -> Self {
134        self.direction(OverflowedContentDirection::LeftToRight)
135    }
136
137    pub fn start(mut self, start: OverflowedContentStart) -> Self {
138        self.start = start;
139        self
140    }
141
142    pub fn start_edge(self) -> Self {
143        self.start(OverflowedContentStart::Edge)
144    }
145
146    pub fn start_visible(self) -> Self {
147        self.start(OverflowedContentStart::Visible)
148    }
149}
150
151impl Component for OverflowedContent {
152    fn render(&self) -> impl IntoElement {
153        let mut content_area = use_state(Area::default);
154        let mut container_area = use_state(Area::default);
155
156        let container_width = container_area.read().width();
157        let content_width = content_area.read().width();
158        let does_overflow = content_width > container_width;
159
160        let duration = self.duration;
161
162        let animation = use_animation(move |_| {
163            AnimNum::new(0., 100.)
164                .duration(duration)
165                .ease(Ease::InOut)
166                .function(Function::Linear)
167        });
168
169        let is_running = *animation.is_running().read();
170        let has_run = *animation.has_run_yet().read();
171
172        use_side_effect_with_deps(
173            &(does_overflow, is_running, has_run),
174            move |&(does_overflow, is_running, has_run)| {
175                if does_overflow && (!has_run || !is_running) {
176                    animation.run(AnimDirection::Forward);
177                }
178            },
179        );
180
181        let progress = animation.get().value();
182        let is_first_cycle =
183            *animation.runs().read() <= 1 && self.start == OverflowedContentStart::Visible;
184
185        let offset_x = if does_overflow {
186            match (&self.direction, is_first_cycle) {
187                (OverflowedContentDirection::RightToLeft, false) => {
188                    container_width - (content_width + container_width) * progress / 100.
189                }
190                (OverflowedContentDirection::RightToLeft, true) => {
191                    -(content_width * progress / 100.)
192                }
193                (OverflowedContentDirection::LeftToRight, false) => {
194                    (content_width + container_width) * progress / 100. - content_width
195                }
196                (OverflowedContentDirection::LeftToRight, true) => {
197                    container_width * progress / 100.
198                }
199            }
200        } else {
201            0.
202        };
203
204        rect()
205            .width(self.layout.width.clone())
206            .height(self.layout.height.clone())
207            .overflow(Overflow::Clip)
208            .on_sized(move |e: Event<SizedEventData>| container_area.set(e.area))
209            .child(
210                rect()
211                    .offset_x(offset_x)
212                    .on_sized(move |e: Event<SizedEventData>| content_area.set(e.area))
213                    .children(self.children.clone()),
214            )
215    }
216
217    fn render_key(&self) -> DiffKey {
218        self.key.clone().or(self.default_key())
219    }
220}