diff --git a/CHANGELOG.md b/CHANGELOG.md index 42deab2a6553..4297dd807c89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [ ## Unreleased +* [Progress bar](https://github.com/emilk/egui/pull/519) ## 0.13.0 - 2021-06-24 - Better panels, plots and new visual style diff --git a/egui/src/widgets/mod.rs b/egui/src/widgets/mod.rs index 2330019a348f..532920db1484 100644 --- a/egui/src/widgets/mod.rs +++ b/egui/src/widgets/mod.rs @@ -13,6 +13,7 @@ mod hyperlink; mod image; mod label; pub mod plot; +mod progress_bar; mod selected_label; mod separator; mod slider; @@ -20,6 +21,7 @@ pub(crate) mod text_edit; pub use hyperlink::*; pub use label::*; +pub use progress_bar::ProgressBar; pub use selected_label::*; pub use separator::*; pub use {button::*, drag_value::DragValue, image::Image, slider::*, text_edit::*}; diff --git a/egui/src/widgets/progress_bar.rs b/egui/src/widgets/progress_bar.rs new file mode 100644 index 000000000000..01e1cc953b79 --- /dev/null +++ b/egui/src/widgets/progress_bar.rs @@ -0,0 +1,141 @@ +use crate::*; + +enum ProgressBarText { + Custom(String), + Percentage, +} + +/// A simple progress bar. +pub struct ProgressBar { + progress: f32, + desired_width: Option, + text: Option, + animate: bool, +} + +impl ProgressBar { + /// Progress in the `[0, 1]` range, where `1` means "completed". + pub fn new(progress: f32) -> Self { + Self { + progress: progress.clamp(0.0, 1.0), + desired_width: None, + text: None, + animate: false, + } + } + + /// The desired width of the bar. Will use all horizonal space if not set. + pub fn desired_width(mut self, desired_width: f32) -> Self { + self.desired_width = Some(desired_width); + self + } + + /// A custom text to display on the progress bar. + #[allow(clippy::needless_pass_by_value)] + pub fn text(mut self, text: impl ToString) -> Self { + self.text = Some(ProgressBarText::Custom(text.to_string())); + self + } + + /// Show the progress in percent on the progress bar. + pub fn show_percentage(mut self) -> Self { + self.text = Some(ProgressBarText::Percentage); + self + } + + /// Whether to display a loading animation when progress `< 1`. + /// Note that this require the UI to be redrawn. + /// Defaults to `false`. + pub fn animate(mut self, animate: bool) -> Self { + self.animate = animate; + self + } +} + +impl Widget for ProgressBar { + fn ui(self, ui: &mut Ui) -> Response { + let ProgressBar { + progress, + desired_width, + text, + mut animate, + } = self; + + animate &= progress < 1.0; + + let desired_width = desired_width.unwrap_or(ui.available_size_before_wrap().x); + let height = ui.spacing().interact_size.y; + let (outer_rect, response) = + ui.allocate_exact_size(vec2(desired_width, height), Sense::hover()); + let visuals = ui.style().visuals.clone(); + let corner_radius = outer_rect.height() / 2.0; + ui.painter().rect( + outer_rect, + corner_radius, + visuals.extreme_bg_color, + Stroke::none(), + ); + let inner_rect = Rect::from_min_size( + outer_rect.min, + vec2( + (outer_rect.width() * progress).at_least(outer_rect.height()), + outer_rect.height(), + ), + ); + + let (dark, bright) = (0.7, 1.0); + let color_factor = if animate { + ui.ctx().request_repaint(); + lerp(dark..=bright, ui.input().time.cos().abs()) + } else { + bright + }; + + ui.painter().rect( + inner_rect, + corner_radius, + Color32::from(Rgba::from(visuals.selection.bg_fill) * color_factor as f32), + Stroke::none(), + ); + + if animate { + let n_points = 20; + let start_angle = ui.input().time as f64 * 360f64.to_radians(); + let end_angle = start_angle + 240f64.to_radians() * ui.input().time.sin(); + let circle_radius = corner_radius - 2.0; + let points: Vec = (0..n_points) + .map(|i| { + let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64); + let (sin, cos) = angle.sin_cos(); + inner_rect.right_center() + + circle_radius * vec2(cos as f32, sin as f32) + + vec2(-corner_radius, 0.0) + }) + .collect(); + ui.painter().add(Shape::Path { + points, + closed: false, + fill: Color32::TRANSPARENT, + stroke: Stroke::new(2.0, visuals.faint_bg_color), + }); + } + + if let Some(text_kind) = text { + let text = match text_kind { + ProgressBarText::Custom(string) => string, + ProgressBarText::Percentage => format!("{}%", (progress * 100.0) as usize), + }; + ui.painter().sub_region(outer_rect).text( + outer_rect.left_center() + vec2(ui.spacing().item_spacing.x, 0.0), + Align2::LEFT_CENTER, + text, + TextStyle::Button, + visuals + .override_text_color + .unwrap_or(visuals.selection.stroke.color), + ); + } + + response + } +} diff --git a/egui_demo_lib/src/apps/demo/widget_gallery.rs b/egui_demo_lib/src/apps/demo/widget_gallery.rs index e19b6bc4fad4..93139010d9ed 100644 --- a/egui_demo_lib/src/apps/demo/widget_gallery.rs +++ b/egui_demo_lib/src/apps/demo/widget_gallery.rs @@ -16,6 +16,7 @@ pub struct WidgetGallery { scalar: f32, string: String, color: egui::Color32, + animate_progress_bar: bool, } impl Default for WidgetGallery { @@ -28,6 +29,7 @@ impl Default for WidgetGallery { scalar: 42.0, string: Default::default(), color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5), + animate_progress_bar: false, } } } @@ -95,6 +97,7 @@ impl WidgetGallery { scalar, string, color, + animate_progress_bar, } = self; ui.add(doc_link_label("Label", "label,heading")); @@ -157,6 +160,17 @@ impl WidgetGallery { ui.add(egui::Slider::new(scalar, 0.0..=360.0).suffix("°")); ui.end_row(); + ui.add(doc_link_label("ProgressBar", "ProgressBar")); + let progress = *scalar / 360.0; + let progress_bar = egui::ProgressBar::new(progress) + .show_percentage() + .animate(*animate_progress_bar); + *animate_progress_bar = ui + .add(progress_bar) + .on_hover_text("The progress bar can be animated!") + .hovered(); + ui.end_row(); + ui.add(doc_link_label("DragValue", "DragValue")); ui.add(egui::DragValue::new(scalar).speed(1.0)); ui.end_row();