irox_egui_extras/
progressbar.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// SPDX-License-Identifier: MIT
// Copyright ${YEAR} IROX Contributors
//

//!
//! Augmentations and tweaks for a better progress bar

use eframe::emath::lerp;
use eframe::epaint::text::TextWrapMode;
use egui::{
    Color32, NumExt, Rect, Response, Rgba, Sense, Stroke, TextStyle, Ui, Vec2, Widget, WidgetText,
};

///
/// A progress bar that supports both determinate (`[0->100]`) and indeterminate modes.
#[derive(Debug, Default, Clone)]
pub struct ProgressBar {
    /// Progress across the bar, in the range `[0->1]`
    pub progress: f32,
    /// Text to left align inside of the bar
    pub left_text: Option<String>,
    /// Text to center align inside of the bar
    pub center_text: Option<String>,
    /// Text to right align inside of the bar
    pub right_text: Option<String>,
    /// Request an immediate repaint of the bar
    pub animate: bool,
    /// The indicated progress is ignored, and the bar bounces back and forth
    pub is_indeterminate: bool,
    /// The desired draw width of the bar
    pub desired_width: Option<f32>,
    /// The desired draw height of the bar
    pub desired_height: Option<f32>,
}

impl ProgressBar {
    ///
    /// Creates a new progress bar with the specified determinate progress in the range `[0,1]`
    #[must_use]
    pub fn new(progress: f32) -> ProgressBar {
        ProgressBar {
            progress,
            ..Default::default()
        }
    }

    ///
    /// Creates a new progress bar that is indeterminate.  The little bar will bounce back and
    /// forth to indicate stuff is happening.
    #[must_use]
    pub fn indeterminate() -> ProgressBar {
        ProgressBar {
            is_indeterminate: true,
            ..Default::default()
        }
    }

    ///
    /// The same as [`Self::text_left`]
    #[must_use]
    pub fn text(self, text: String) -> ProgressBar {
        ProgressBar {
            left_text: Some(text),
            ..self
        }
    }

    ///
    /// Draws the provided text left aligned
    #[must_use]
    pub fn text_left(self, text: String) -> ProgressBar {
        self.text(text)
    }

    ///
    /// Draws the provided text center aligned
    #[must_use]
    pub fn text_center(self, text: String) -> ProgressBar {
        ProgressBar {
            center_text: Some(text),
            ..self
        }
    }

    ///
    /// Draws the provided text right aligned
    #[must_use]
    pub fn text_right(self, text: String) -> ProgressBar {
        ProgressBar {
            right_text: Some(text),
            ..self
        }
    }

    ///
    /// Sets the desired draw width of the bar
    #[must_use]
    pub fn desired_width(self, width: f32) -> ProgressBar {
        ProgressBar {
            desired_width: Some(width),
            ..self
        }
    }

    ///
    /// Sets the desired draw height of the bar
    #[must_use]
    pub fn desired_height(self, height: f32) -> ProgressBar {
        ProgressBar {
            desired_height: Some(height),
            ..self
        }
    }

    pub fn ui(self, ui: &mut Ui) -> Response {
        let ProgressBar {
            progress,
            desired_width,
            desired_height,
            left_text,
            center_text,
            right_text,
            animate,
            is_indeterminate,
        } = self;

        let animate = animate && progress < 1.0;

        let desired_width =
            desired_width.unwrap_or_else(|| ui.available_size_before_wrap().x.at_least(96.0));
        let height = desired_height.unwrap_or(ui.spacing().interact_size.y);
        let (outer_rect, response) =
            ui.allocate_exact_size(egui::vec2(desired_width, height), Sense::hover());

        if ui.is_rect_visible(response.rect) {
            if animate {
                ui.ctx().request_repaint();
            }

            let visuals = ui.style().visuals.clone();
            let rounding = outer_rect.height() / 2.0;
            let time = ui.input(|i| i.time).cos().abs() as f32;
            ui.painter()
                .rect(outer_rect, rounding, visuals.extreme_bg_color, Stroke::NONE);
            let inner_rect = if is_indeterminate {
                let max_x = outer_rect.width() * 0.8;
                let offset = lerp(0.0f32..=max_x, time);
                Rect::from_min_size(
                    egui::pos2(outer_rect.min.x + offset, outer_rect.min.y),
                    egui::vec2(outer_rect.width() * 0.2, outer_rect.height()),
                )
            } else {
                Rect::from_min_size(
                    outer_rect.min,
                    egui::vec2(
                        (outer_rect.width() * progress).at_least(outer_rect.height()),
                        outer_rect.height(),
                    ),
                )
            };

            let (dark, bright) = (0.7, 1.0);
            let color_factor = lerp(dark..=bright, time);

            ui.painter().rect(
                inner_rect,
                rounding,
                Color32::from(Rgba::from(visuals.selection.bg_fill) * color_factor),
                Stroke::NONE,
            );

            if let Some(text) = left_text {
                let text: WidgetText = text.into();

                let galley = text.into_galley(
                    ui,
                    Some(TextWrapMode::Truncate),
                    f32::INFINITY,
                    TextStyle::Button,
                );
                let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0)
                    + egui::vec2(ui.spacing().item_spacing.x, 0.0);
                let text_color = visuals
                    .override_text_color
                    .unwrap_or(visuals.selection.stroke.color);
                ui.painter().galley(text_pos, galley, text_color);
            }
            if let Some(text) = center_text {
                let text: WidgetText = text.into();

                let galley = text.into_galley(
                    ui,
                    Some(TextWrapMode::Truncate),
                    f32::INFINITY,
                    TextStyle::Button,
                );
                let size = galley.size();
                let text_pos = outer_rect.center() - Vec2::new(size.x / 2.0, size.y / 2.0);
                let text_color = visuals
                    .override_text_color
                    .unwrap_or(visuals.selection.stroke.color);
                ui.painter().galley(text_pos, galley, text_color);
            }

            if let Some(text) = right_text {
                let text: WidgetText = text.into();

                let galley = text.into_galley(
                    ui,
                    Some(TextWrapMode::Truncate),
                    f32::INFINITY,
                    TextStyle::Button,
                );
                let size = galley.size();
                let text_pos = outer_rect.right_center()
                    - Vec2::new(size.x, size.y / 2.0)
                    - egui::vec2(ui.spacing().item_spacing.x, 0.0);
                let text_color = visuals
                    .override_text_color
                    .unwrap_or(visuals.selection.stroke.color);
                ui.painter().galley(text_pos, galley, text_color);
            }
        }

        response
    }
}

impl Widget for ProgressBar {
    fn ui(self, ui: &mut Ui) -> Response {
        ProgressBar::ui(self, ui)
    }
}