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