Skip to main content

agg_gui/widgets/
progress_bar.rs

1//! `ProgressBar` — a read-only horizontal progress indicator.
2
3use std::sync::Arc;
4
5use crate::color::Color;
6use crate::draw_ctx::DrawCtx;
7use crate::event::{Event, EventResult};
8use crate::geometry::{Rect, Size};
9use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
10use crate::text::Font;
11use crate::widget::Widget;
12
13const BAR_H: f64 = 18.0;
14const WIDGET_H: f64 = 24.0;
15
16/// A horizontal progress bar. `value` is in `[0.0, 1.0]`.
17pub struct ProgressBar {
18    bounds: Rect,
19    children: Vec<Box<dyn Widget>>, // always empty
20    base: WidgetBase,
21    value: f64,
22    show_text: bool,
23    font: Arc<Font>,
24    font_size: f64,
25    fill_color: Option<Color>,
26}
27
28impl ProgressBar {
29    pub fn new(value: f64, font: Arc<Font>) -> Self {
30        Self {
31            bounds: Rect::default(),
32            children: Vec::new(),
33            base: WidgetBase::new(),
34            value: value.clamp(0.0, 1.0),
35            show_text: true,
36            font,
37            font_size: 11.0,
38            fill_color: None,
39        }
40    }
41
42    pub fn with_show_text(mut self, show: bool) -> Self {
43        self.show_text = show;
44        self
45    }
46    pub fn with_fill_color(mut self, color: Color) -> Self {
47        self.fill_color = Some(color);
48        self
49    }
50
51    pub fn with_margin(mut self, m: Insets) -> Self {
52        self.base.margin = m;
53        self
54    }
55    pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
56        self.base.h_anchor = h;
57        self
58    }
59    pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
60        self.base.v_anchor = v;
61        self
62    }
63    pub fn with_min_size(mut self, s: Size) -> Self {
64        self.base.min_size = s;
65        self
66    }
67    pub fn with_max_size(mut self, s: Size) -> Self {
68        self.base.max_size = s;
69        self
70    }
71
72    pub fn set_value(&mut self, v: f64) {
73        self.value = v.clamp(0.0, 1.0);
74    }
75
76    pub fn value(&self) -> f64 {
77        self.value
78    }
79}
80
81impl Widget for ProgressBar {
82    fn type_name(&self) -> &'static str {
83        "ProgressBar"
84    }
85    fn bounds(&self) -> Rect {
86        self.bounds
87    }
88    fn set_bounds(&mut self, b: Rect) {
89        self.bounds = b;
90    }
91    fn children(&self) -> &[Box<dyn Widget>] {
92        &self.children
93    }
94    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
95        &mut self.children
96    }
97
98    fn margin(&self) -> Insets {
99        self.base.margin
100    }
101    fn h_anchor(&self) -> HAnchor {
102        self.base.h_anchor
103    }
104    fn v_anchor(&self) -> VAnchor {
105        self.base.v_anchor
106    }
107    fn min_size(&self) -> Size {
108        self.base.min_size
109    }
110    fn max_size(&self) -> Size {
111        self.base.max_size
112    }
113
114    fn layout(&mut self, available: Size) -> Size {
115        Size::new(available.width, WIDGET_H)
116    }
117
118    fn paint(&mut self, ctx: &mut dyn DrawCtx) {
119        let v = ctx.visuals();
120        let w = self.bounds.width;
121        let h = self.bounds.height;
122        let bar_y = (h - BAR_H) * 0.5;
123        let r = BAR_H * 0.5;
124
125        // Track
126        ctx.set_fill_color(v.track_bg);
127        ctx.begin_path();
128        ctx.rounded_rect(0.0, bar_y, w, BAR_H, r);
129        ctx.fill();
130
131        // Fill — use explicit fill_color if set, otherwise fall back to accent.
132        let fill_color = self.fill_color.unwrap_or(v.accent);
133        let fill_w = (w * self.value).max(0.0);
134        if fill_w >= 1.0 {
135            ctx.set_fill_color(fill_color);
136            ctx.begin_path();
137            ctx.rounded_rect(0.0, bar_y, fill_w, BAR_H, r);
138            ctx.fill();
139        }
140
141        // Percentage text centered over bar
142        if self.show_text {
143            let label = format!("{:.0}%", self.value * 100.0);
144            ctx.set_font(Arc::clone(&self.font));
145            ctx.set_font_size(self.font_size);
146            // Text color: always use theme text contrasted against the bar.
147            let mid = w * 0.5;
148            let text_color = if fill_w > mid {
149                Color::rgba(1.0, 1.0, 1.0, 0.9)
150            } else {
151                v.text_dim
152            };
153            ctx.set_fill_color(text_color);
154            if let Some(m) = ctx.measure_text(&label) {
155                let tx = (w - m.width) * 0.5;
156                let ty = bar_y + BAR_H * 0.5 - (m.ascent - m.descent) * 0.5;
157                ctx.fill_text(&label, tx, ty);
158            }
159        }
160    }
161
162    fn on_event(&mut self, _: &Event) -> EventResult {
163        EventResult::Ignored
164    }
165}