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/// Inspector-visible properties of a [`ProgressBar`].
17#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
18#[derive(Clone, Debug)]
19pub struct ProgressBarProps {
20    /// Progress in `[0.0, 1.0]`.
21    pub value: f64,
22    pub show_text: bool,
23    pub font_size: f64,
24    pub fill_color: Option<Color>,
25}
26
27impl Default for ProgressBarProps {
28    fn default() -> Self {
29        Self {
30            value: 0.0,
31            show_text: true,
32            font_size: 11.0,
33            fill_color: None,
34        }
35    }
36}
37
38/// A horizontal progress bar. `value` is in `[0.0, 1.0]`.
39pub struct ProgressBar {
40    bounds: Rect,
41    children: Vec<Box<dyn Widget>>, // always empty
42    base: WidgetBase,
43    pub props: ProgressBarProps,
44    font: Arc<Font>,
45}
46
47impl ProgressBar {
48    pub fn new(value: f64, font: Arc<Font>) -> Self {
49        Self {
50            bounds: Rect::default(),
51            children: Vec::new(),
52            base: WidgetBase::new(),
53            props: ProgressBarProps {
54                value: value.clamp(0.0, 1.0),
55                ..ProgressBarProps::default()
56            },
57            font,
58        }
59    }
60
61    pub fn with_show_text(mut self, show: bool) -> Self {
62        self.props.show_text = show;
63        self
64    }
65    pub fn with_fill_color(mut self, color: Color) -> Self {
66        self.props.fill_color = Some(color);
67        self
68    }
69
70    pub fn with_margin(mut self, m: Insets) -> Self {
71        self.base.margin = m;
72        self
73    }
74    pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
75        self.base.h_anchor = h;
76        self
77    }
78    pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
79        self.base.v_anchor = v;
80        self
81    }
82    pub fn with_min_size(mut self, s: Size) -> Self {
83        self.base.min_size = s;
84        self
85    }
86    pub fn with_max_size(mut self, s: Size) -> Self {
87        self.base.max_size = s;
88        self
89    }
90
91    pub fn set_value(&mut self, v: f64) {
92        self.props.value = v.clamp(0.0, 1.0);
93    }
94
95    pub fn value(&self) -> f64 {
96        self.props.value
97    }
98}
99
100impl Widget for ProgressBar {
101    fn type_name(&self) -> &'static str {
102        "ProgressBar"
103    }
104    fn bounds(&self) -> Rect {
105        self.bounds
106    }
107    fn set_bounds(&mut self, b: Rect) {
108        self.bounds = b;
109    }
110    fn children(&self) -> &[Box<dyn Widget>] {
111        &self.children
112    }
113    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
114        &mut self.children
115    }
116
117    #[cfg(feature = "reflect")]
118    fn as_reflect(&self) -> Option<&dyn bevy_reflect::Reflect> {
119        Some(&self.props)
120    }
121    #[cfg(feature = "reflect")]
122    fn as_reflect_mut(&mut self) -> Option<&mut dyn bevy_reflect::Reflect> {
123        Some(&mut self.props)
124    }
125
126    fn margin(&self) -> Insets {
127        self.base.margin
128    }
129    fn widget_base(&self) -> Option<&WidgetBase> {
130        Some(&self.base)
131    }
132    fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
133        Some(&mut self.base)
134    }
135    fn h_anchor(&self) -> HAnchor {
136        self.base.h_anchor
137    }
138    fn v_anchor(&self) -> VAnchor {
139        self.base.v_anchor
140    }
141    fn min_size(&self) -> Size {
142        self.base.min_size
143    }
144    fn max_size(&self) -> Size {
145        self.base.max_size
146    }
147
148    fn layout(&mut self, available: Size) -> Size {
149        Size::new(available.width, WIDGET_H)
150    }
151
152    fn paint(&mut self, ctx: &mut dyn DrawCtx) {
153        let v = ctx.visuals();
154        let w = self.bounds.width;
155        let h = self.bounds.height;
156        let bar_y = (h - BAR_H) * 0.5;
157        let r = BAR_H * 0.5;
158
159        // Track
160        ctx.set_fill_color(v.track_bg);
161        ctx.begin_path();
162        ctx.rounded_rect(0.0, bar_y, w, BAR_H, r);
163        ctx.fill();
164
165        // Fill — use explicit fill_color if set, otherwise fall back to accent.
166        let fill_color = self.props.fill_color.unwrap_or(v.accent);
167        let fill_w = (w * self.props.value).max(0.0);
168        if fill_w >= 1.0 {
169            ctx.set_fill_color(fill_color);
170            ctx.begin_path();
171            ctx.rounded_rect(0.0, bar_y, fill_w, BAR_H, r);
172            ctx.fill();
173        }
174
175        // Percentage text centered over bar
176        if self.props.show_text {
177            let label = format!("{:.0}%", self.props.value * 100.0);
178            ctx.set_font(Arc::clone(&self.font));
179            ctx.set_font_size(self.props.font_size);
180            // Text color: always use theme text contrasted against the bar.
181            let mid = w * 0.5;
182            let text_color = if fill_w > mid {
183                Color::rgba(1.0, 1.0, 1.0, 0.9)
184            } else {
185                v.text_dim
186            };
187            ctx.set_fill_color(text_color);
188            if let Some(m) = ctx.measure_text(&label) {
189                let tx = (w - m.width) * 0.5;
190                let ty = bar_y + BAR_H * 0.5 - (m.ascent - m.descent) * 0.5;
191                ctx.fill_text(&label, tx, ty);
192            }
193        }
194    }
195
196    fn on_event(&mut self, _: &Event) -> EventResult {
197        EventResult::Ignored
198    }
199}