Skip to main content

egui_components/
progress.rs

1//! `Progress` — a horizontal progress bar (determinate or indeterminate).
2//!
3//! ```ignore
4//! ui.add(sc::Progress::new(0.6));            // 60%
5//! ui.add(sc::Progress::indeterminate());     // animated sweep
6//! ```
7
8use egui::{vec2, Color32, Response, Sense, Ui, Widget};
9use egui_components_theme::Theme;
10
11use crate::common::Variant;
12
13pub struct Progress {
14    value: f32,
15    indeterminate: bool,
16    width: Option<f32>,
17    height: f32,
18    variant: Variant,
19}
20
21impl Progress {
22    pub fn new(value: f32) -> Self {
23        Self {
24            value: value.clamp(0.0, 1.0),
25            indeterminate: false,
26            width: None,
27            height: 8.0,
28            variant: Variant::Primary,
29        }
30    }
31
32    pub fn indeterminate() -> Self {
33        Self {
34            value: 0.0,
35            indeterminate: true,
36            width: None,
37            height: 8.0,
38            variant: Variant::Primary,
39        }
40    }
41
42    pub fn width(mut self, w: f32) -> Self {
43        self.width = Some(w);
44        self
45    }
46    pub fn height(mut self, h: f32) -> Self {
47        self.height = h;
48        self
49    }
50    pub fn variant(mut self, v: Variant) -> Self {
51        self.variant = v;
52        self
53    }
54}
55
56impl Widget for Progress {
57    fn ui(self, ui: &mut Ui) -> Response {
58        let theme = Theme::get(ui.ctx());
59        let c = theme.colors;
60        let width = self.width.unwrap_or_else(|| ui.available_width().min(280.0));
61        let (rect, response) = ui.allocate_exact_size(vec2(width, self.height), Sense::hover());
62
63        if ui.is_rect_visible(rect) {
64            let radius = egui::CornerRadius::same((self.height * 0.5) as u8);
65            let painter = ui.painter();
66            painter.rect_filled(rect, radius, c.muted_background);
67
68            let fill = fill_color(&c, self.variant);
69            if self.indeterminate {
70                // A segment sweeping left-to-right on a loop.
71                let t = (ui.input(|i| i.time) % 1.4) as f32 / 1.4;
72                let seg_w = width * 0.35;
73                let travel = width + seg_w;
74                let x = rect.left() - seg_w + travel * t;
75                let seg = egui::Rect::from_min_max(
76                    egui::pos2(x.max(rect.left()), rect.top()),
77                    egui::pos2((x + seg_w).min(rect.right()), rect.bottom()),
78                );
79                if seg.width() > 0.0 {
80                    painter.rect_filled(seg, radius, fill);
81                }
82                ui.ctx().request_repaint();
83            } else if self.value > 0.0 {
84                let filled = egui::Rect::from_min_size(
85                    rect.min,
86                    vec2(width * self.value, self.height),
87                );
88                painter.rect_filled(filled, radius, fill);
89            }
90        }
91
92        response
93    }
94}
95
96fn fill_color(c: &egui_components_theme::ThemeColor, v: Variant) -> Color32 {
97    match v {
98        Variant::Success => c.success_background,
99        Variant::Warning => c.warning_background,
100        Variant::Danger => c.danger_background,
101        Variant::Info => c.info_background,
102        _ => c.primary_background,
103    }
104}