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///
42/// See the [interactive components demo](https://freyaui.dev/demo).
43#[derive(Clone, PartialEq)]
44pub struct ProgressBar {
45    pub(crate) theme: Option<ProgressBarThemePartial>,
46    width: Size,
47    show_progress: bool,
48    progress: f32,
49    key: DiffKey,
50}
51
52impl KeyExt for ProgressBar {
53    fn key_mut(&mut self) -> &mut DiffKey {
54        &mut self.key
55    }
56}
57
58impl ProgressBar {
59    pub fn new(progress: f32) -> Self {
60        Self {
61            width: Size::fill(),
62            theme: None,
63            show_progress: true,
64            progress,
65            key: DiffKey::None,
66        }
67    }
68
69    pub fn width(mut self, width: impl Into<Size>) -> Self {
70        self.width = width.into();
71        self
72    }
73
74    /// Shows the progress bar percentage label
75    ///
76    /// This is set to true by default
77    pub fn show_progress(mut self, show_progress: bool) -> Self {
78        self.show_progress = show_progress;
79        self
80    }
81}
82
83impl Component for ProgressBar {
84    fn render(&self) -> impl IntoElement {
85        let progressbar_theme = get_theme!(&self.theme, ProgressBarThemePreference, "progressbar");
86
87        let progress = use_reactive(&self.progress.clamp(0., 100.));
88        let animation = use_animation_transition(progress, |from, to| {
89            AnimNum::new(from, to)
90                .time(500)
91                .ease(Ease::Out)
92                .function(Function::Expo)
93        });
94
95        rect()
96            .a11y_alt(format!("Progress {:.2}%", self.progress))
97            .a11y_focusable(true)
98            .a11y_role(AccessibilityRole::ProgressIndicator)
99            .horizontal()
100            .width(self.width.clone())
101            .height(Size::px(progressbar_theme.height))
102            .corner_radius(99.)
103            .overflow(Overflow::Clip)
104            .background(progressbar_theme.background)
105            .border(
106                Border::new()
107                    .width(1.)
108                    .alignment(BorderAlignment::Outer)
109                    .fill(progressbar_theme.background),
110            )
111            .font_size(13.)
112            .child(
113                rect()
114                    .horizontal()
115                    .width(Size::percent(animation.read().value()))
116                    .cross_align(Alignment::Center)
117                    .height(Size::fill())
118                    .corner_radius(99.)
119                    .background(progressbar_theme.progress_background)
120                    .maybe(self.show_progress, |el| {
121                        el.child(
122                            label()
123                                .width(Size::fill())
124                                .color(progressbar_theme.color)
125                                .text_align(TextAlign::Center)
126                                .text(format!("{:.2}%", self.progress))
127                                .max_lines(1),
128                        )
129                    }),
130            )
131    }
132
133    fn render_key(&self) -> DiffKey {
134        self.key.clone().or(self.default_key())
135    }
136}