Skip to content

Commit 89cea7a

Browse files
EmbersArcemilk
andauthored
Progress bar (#519)
* add progress bar * update changelog * apply suggestions * disable animation by default and tweak colors * allow toggling the animation by clicking * Update egui/src/widgets/progress_bar.rs Co-authored-by: Emil Ernerfeldt <[email protected]> * Update egui/src/widgets/progress_bar.rs Co-authored-by: Emil Ernerfeldt <[email protected]> * Update egui/src/widgets/progress_bar.rs Co-authored-by: Emil Ernerfeldt <[email protected]> * address review comments Co-authored-by: Emil Ernerfeldt <[email protected]>
1 parent 52e3663 commit 89cea7a

File tree

4 files changed

+158
-0
lines changed

4 files changed

+158
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [
66

77

88
## Unreleased
9+
* [Progress bar](https://github.com/emilk/egui/pull/519)
910

1011

1112
## 0.13.1 - 2021-06-28 - Plot fixes

egui/src/widgets/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ mod hyperlink;
1313
mod image;
1414
mod label;
1515
pub mod plot;
16+
mod progress_bar;
1617
mod selected_label;
1718
mod separator;
1819
mod slider;
1920
pub(crate) mod text_edit;
2021

2122
pub use hyperlink::*;
2223
pub use label::*;
24+
pub use progress_bar::ProgressBar;
2325
pub use selected_label::*;
2426
pub use separator::*;
2527
pub use {button::*, drag_value::DragValue, image::Image, slider::*, text_edit::*};

egui/src/widgets/progress_bar.rs

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use crate::*;
2+
3+
enum ProgressBarText {
4+
Custom(String),
5+
Percentage,
6+
}
7+
8+
/// A simple progress bar.
9+
pub struct ProgressBar {
10+
progress: f32,
11+
desired_width: Option<f32>,
12+
text: Option<ProgressBarText>,
13+
animate: bool,
14+
}
15+
16+
impl ProgressBar {
17+
/// Progress in the `[0, 1]` range, where `1` means "completed".
18+
pub fn new(progress: f32) -> Self {
19+
Self {
20+
progress: progress.clamp(0.0, 1.0),
21+
desired_width: None,
22+
text: None,
23+
animate: false,
24+
}
25+
}
26+
27+
/// The desired width of the bar. Will use all horizonal space if not set.
28+
pub fn desired_width(mut self, desired_width: f32) -> Self {
29+
self.desired_width = Some(desired_width);
30+
self
31+
}
32+
33+
/// A custom text to display on the progress bar.
34+
#[allow(clippy::needless_pass_by_value)]
35+
pub fn text(mut self, text: impl ToString) -> Self {
36+
self.text = Some(ProgressBarText::Custom(text.to_string()));
37+
self
38+
}
39+
40+
/// Show the progress in percent on the progress bar.
41+
pub fn show_percentage(mut self) -> Self {
42+
self.text = Some(ProgressBarText::Percentage);
43+
self
44+
}
45+
46+
/// Whether to display a loading animation when progress `< 1`.
47+
/// Note that this require the UI to be redrawn.
48+
/// Defaults to `false`.
49+
pub fn animate(mut self, animate: bool) -> Self {
50+
self.animate = animate;
51+
self
52+
}
53+
}
54+
55+
impl Widget for ProgressBar {
56+
fn ui(self, ui: &mut Ui) -> Response {
57+
let ProgressBar {
58+
progress,
59+
desired_width,
60+
text,
61+
mut animate,
62+
} = self;
63+
64+
animate &= progress < 1.0;
65+
66+
let desired_width = desired_width.unwrap_or(ui.available_size_before_wrap().x);
67+
let height = ui.spacing().interact_size.y;
68+
let (outer_rect, response) =
69+
ui.allocate_exact_size(vec2(desired_width, height), Sense::hover());
70+
let visuals = ui.style().visuals.clone();
71+
let corner_radius = outer_rect.height() / 2.0;
72+
ui.painter().rect(
73+
outer_rect,
74+
corner_radius,
75+
visuals.extreme_bg_color,
76+
Stroke::none(),
77+
);
78+
let inner_rect = Rect::from_min_size(
79+
outer_rect.min,
80+
vec2(
81+
(outer_rect.width() * progress).at_least(outer_rect.height()),
82+
outer_rect.height(),
83+
),
84+
);
85+
86+
let (dark, bright) = (0.7, 1.0);
87+
let color_factor = if animate {
88+
ui.ctx().request_repaint();
89+
lerp(dark..=bright, ui.input().time.cos().abs())
90+
} else {
91+
bright
92+
};
93+
94+
ui.painter().rect(
95+
inner_rect,
96+
corner_radius,
97+
Color32::from(Rgba::from(visuals.selection.bg_fill) * color_factor as f32),
98+
Stroke::none(),
99+
);
100+
101+
if animate {
102+
let n_points = 20;
103+
let start_angle = ui.input().time as f64 * 360f64.to_radians();
104+
let end_angle = start_angle + 240f64.to_radians() * ui.input().time.sin();
105+
let circle_radius = corner_radius - 2.0;
106+
let points: Vec<Pos2> = (0..n_points)
107+
.map(|i| {
108+
let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64);
109+
let (sin, cos) = angle.sin_cos();
110+
inner_rect.right_center()
111+
+ circle_radius * vec2(cos as f32, sin as f32)
112+
+ vec2(-corner_radius, 0.0)
113+
})
114+
.collect();
115+
ui.painter().add(Shape::Path {
116+
points,
117+
closed: false,
118+
fill: Color32::TRANSPARENT,
119+
stroke: Stroke::new(2.0, visuals.faint_bg_color),
120+
});
121+
}
122+
123+
if let Some(text_kind) = text {
124+
let text = match text_kind {
125+
ProgressBarText::Custom(string) => string,
126+
ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize),
127+
};
128+
ui.painter().sub_region(outer_rect).text(
129+
outer_rect.left_center() + vec2(ui.spacing().item_spacing.x, 0.0),
130+
Align2::LEFT_CENTER,
131+
text,
132+
TextStyle::Button,
133+
visuals
134+
.override_text_color
135+
.unwrap_or(visuals.selection.stroke.color),
136+
);
137+
}
138+
139+
response
140+
}
141+
}

egui_demo_lib/src/apps/demo/widget_gallery.rs

+14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub struct WidgetGallery {
1616
scalar: f32,
1717
string: String,
1818
color: egui::Color32,
19+
animate_progress_bar: bool,
1920
}
2021

2122
impl Default for WidgetGallery {
@@ -28,6 +29,7 @@ impl Default for WidgetGallery {
2829
scalar: 42.0,
2930
string: Default::default(),
3031
color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5),
32+
animate_progress_bar: false,
3133
}
3234
}
3335
}
@@ -95,6 +97,7 @@ impl WidgetGallery {
9597
scalar,
9698
string,
9799
color,
100+
animate_progress_bar,
98101
} = self;
99102

100103
ui.add(doc_link_label("Label", "label,heading"));
@@ -157,6 +160,17 @@ impl WidgetGallery {
157160
ui.add(egui::Slider::new(scalar, 0.0..=360.0).suffix("°"));
158161
ui.end_row();
159162

163+
ui.add(doc_link_label("ProgressBar", "ProgressBar"));
164+
let progress = *scalar / 360.0;
165+
let progress_bar = egui::ProgressBar::new(progress)
166+
.show_percentage()
167+
.animate(*animate_progress_bar);
168+
*animate_progress_bar = ui
169+
.add(progress_bar)
170+
.on_hover_text("The progress bar can be animated!")
171+
.hovered();
172+
ui.end_row();
173+
160174
ui.add(doc_link_label("DragValue", "DragValue"));
161175
ui.add(egui::DragValue::new(scalar).speed(1.0));
162176
ui.end_row();

0 commit comments

Comments
 (0)