Skip to main content

freya_components/
progressbar.rs

1use freya_animation::{
2    easing::Function,
3    hook::{
4        Ease,
5        use_animation_transition,
6    },
7    prelude::AnimNum,
8};
9use freya_core::prelude::*;
10use torin::{
11    prelude::Alignment,
12    size::Size,
13};
14
15use crate::{
16    define_theme,
17    get_theme,
18};
19
20define_theme! {
21    %[component]
22    pub ProgressBar {
23        %[fields]
24        color: Color,
25        background: Color,
26        progress_background: Color,
27        height: f32,
28    }
29}
30
31/// ProgressBar component.
32///
33/// # Example
34///
35/// ```rust
36/// # use freya::prelude::*;
37/// fn app() -> impl IntoElement {
38///     ProgressBar::new(50.)
39/// }
40///
41/// # use freya_testing::prelude::*;
42/// # launch_doc(|| {
43/// #   rect().padding(8.).center().expanded().child(app())
44/// # }, "./images/gallery_progressbar.png").render();
45/// ```
46///
47/// # Preview
48/// ![Progressbar Preview][progressbar]
49#[cfg_attr(feature = "docs",
50    doc = embed_doc_image::embed_image!("progressbar", "images/gallery_progressbar.png")
51)]
52#[derive(Clone, PartialEq)]
53pub struct ProgressBar {
54    pub(crate) theme: Option<ProgressBarThemePartial>,
55    width: Size,
56    show_progress: bool,
57    progress: f32,
58    key: DiffKey,
59}
60
61impl KeyExt for ProgressBar {
62    fn write_key(&mut self) -> &mut DiffKey {
63        &mut self.key
64    }
65}
66
67impl ProgressBar {
68    pub fn new(progress: impl Into<f32>) -> Self {
69        Self {
70            width: Size::fill(),
71            theme: None,
72            show_progress: true,
73            progress: progress.into(),
74            key: DiffKey::None,
75        }
76    }
77
78    pub fn width(mut self, width: impl Into<Size>) -> Self {
79        self.width = width.into();
80        self
81    }
82
83    /// Shows the progress bar percentage label
84    ///
85    /// This is set to true by default
86    pub fn show_progress(mut self, show_progress: bool) -> Self {
87        self.show_progress = show_progress;
88        self
89    }
90}
91
92impl Component for ProgressBar {
93    fn render(&self) -> impl IntoElement {
94        let progressbar_theme = get_theme!(&self.theme, ProgressBarThemePreference, "progressbar");
95
96        let progress = use_reactive(&self.progress.clamp(0., 100.));
97        let animation = use_animation_transition(progress, |from, to| {
98            AnimNum::new(from, to)
99                .time(500)
100                .ease(Ease::Out)
101                .function(Function::Expo)
102        });
103
104        rect()
105            .a11y_alt(format!("Progress {}%", progress()))
106            .a11y_focusable(true)
107            .a11y_role(AccessibilityRole::ProgressIndicator)
108            .horizontal()
109            .width(self.width.clone())
110            .height(Size::px(progressbar_theme.height))
111            .corner_radius(99.)
112            .overflow(Overflow::Clip)
113            .background(progressbar_theme.background)
114            .border(
115                Border::new()
116                    .width(1.)
117                    .alignment(BorderAlignment::Outer)
118                    .fill(progressbar_theme.background),
119            )
120            .font_size(13.)
121            .child(
122                rect()
123                    .horizontal()
124                    .width(Size::percent(&*animation.read()))
125                    .cross_align(Alignment::Center)
126                    .height(Size::fill())
127                    .corner_radius(99.)
128                    .background(progressbar_theme.progress_background)
129                    .maybe(self.show_progress, |el| {
130                        el.child(
131                            label()
132                                .width(Size::fill())
133                                .color(progressbar_theme.color)
134                                .text_align(TextAlign::Center)
135                                .text(format!("{}%", self.progress))
136                                .max_lines(1),
137                        )
138                    }),
139            )
140    }
141
142    fn render_key(&self) -> DiffKey {
143        self.key.clone().or(self.default_key())
144    }
145}