Skip to main content

matrix_gui/widgets/
bar.rs

1//! Progress bar widget for displaying values.
2//!
3//! This module provides a horizontal progress bar widget that displays
4//! a value within a range. The bar shows filled and unfilled portions
5//! with customizable colors and optional border.
6
7use crate::prelude::*;
8
9/// Horizontal progress bar widget.
10///
11/// This widget displays a horizontal progress bar showing a value within
12/// a specified range. The bar is divided into filled and unfilled portions
13/// with customizable colors and optional border.
14///
15/// # Type Parameters
16///
17/// * `'a` - The lifetime of the region reference
18/// * `ID` - The widget ID type implementing [`WidgetId`]
19/// * `COL` - The pixel color type implementing [`PixelColor`]
20pub struct Bar<'a, ID, COL: PixelColor> {
21    /// The region defining the bar's position and size.
22    region: &'a Region<ID>,
23    /// Optional padding around the bar content.
24    padding: Option<Size>,
25    /// Color for the filled portion of the bar.
26    color_filled: OptionColor<COL>,
27    /// Color for the unfilled portion of the bar.
28    color_unfilled: OptionColor<COL>,
29    /// Optional border color.
30    border: Option<COL>,
31    /// The filled value (proportional to total).
32    filled_value: u16,
33    /// The unfilled value (proportional to total).
34    unfilled_value: u16,
35}
36
37impl<'a, ID: WidgetId, COL: PixelColor> Bar<'a, ID, COL> {
38    pub const fn new(
39        region: &'a Region<ID>,
40        min_value: i16,
41        max_value: i16,
42        cur_value: i16,
43    ) -> Bar<'a, ID, COL> {
44        Bar {
45            region,
46            padding: None,
47            color_filled: OptionColor::none(),
48            color_unfilled: OptionColor::none(),
49            border: None,
50            filled_value: if cur_value >= min_value {
51                (cur_value - min_value) as u16
52            } else {
53                0
54            },
55            unfilled_value: if cur_value <= max_value {
56                (max_value - cur_value) as u16
57            } else {
58                0
59            },
60        }
61    }
62
63    pub const fn with_padding(mut self, padding: Size) -> Self {
64        self.padding = Some(padding);
65        self
66    }
67
68    pub const fn with_filled_color(mut self, color_filled: COL) -> Self {
69        self.color_filled.set_color(color_filled);
70        self
71    }
72
73    pub const fn with_unfilled_color(mut self, color_unfilled: COL) -> Self {
74        self.color_unfilled.set_color(color_unfilled);
75        self
76    }
77
78    pub const fn with_border_color(mut self, border_color: COL) -> Self {
79        self.border = Some(border_color);
80        self
81    }
82}
83
84impl<DRAW: DrawTarget<Color = COL>, ID: WidgetId, COL: PixelColor> Widget<DRAW, COL>
85    for Bar<'_, ID, COL>
86{
87    fn draw(&mut self, ui: &mut Ui<DRAW, COL>) -> GuiResult<Response> {
88        let widget_state = ui.get_widget_state(self.region.id())?;
89        if widget_state.compare_set(RenderStatus::Rendered) {
90            return Ok(Response::Idle);
91        }
92
93        let bar_width = self.region.width();
94        let bar_height = self.region.height();
95
96        let area = self.region.rectangle();
97
98        let padding = if let Some(padding) = self.padding {
99            padding
100        } else {
101            ui.style().default_padding
102        };
103        let top_left = area.top_left + Point::new(padding.width as i32, padding.height as i32);
104        let indic_width = bar_width - 2 * padding.width;
105        let indic_height = bar_height - 2 * padding.height;
106        let filled_len = indic_width * self.filled_value as u32
107            / (self.filled_value + self.unfilled_value) as u32;
108        let unfilled_len = indic_width - filled_len;
109
110        ui.clear_area(&area)?;
111
112        if filled_len > 0 {
113            let filled_size = Size::new(filled_len, indic_height);
114            let filled_style = PrimitiveStyle::with_fill(self.color_filled.text_color(ui.style()));
115            let filled_area = Rectangle::new(top_left, filled_size).into_styled(filled_style);
116            ui.draw(&filled_area)?;
117        }
118        if unfilled_len > 0 {
119            let unfilled_size = Size::new(unfilled_len, indic_height);
120            let unfilled_style =
121                PrimitiveStyle::with_fill(self.color_unfilled.background_color(ui.style()));
122            let unfilled_area =
123                Rectangle::new(top_left + Point::new(filled_len as i32, 0), unfilled_size)
124                    .into_styled(unfilled_style);
125            ui.draw(&unfilled_area)?;
126        }
127        if let Some(border_color) = self.border {
128            let border_style =
129                PrimitiveStyle::with_stroke(border_color, ui.style().border_width as u32);
130            let border_area = Rectangle::new(top_left, Size::new(indic_width, indic_height))
131                .into_styled(border_style);
132            ui.draw(&border_area)?;
133        }
134
135        Ok(Response::Idle)
136    }
137}