Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to customize progress bar rounding #2881

Merged
merged 4 commits into from
Jan 7, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions crates/egui/src/widgets/progress_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct ProgressBar {
text: Option<ProgressBarText>,
fill: Option<Color32>,
animate: bool,
rounding: Option<Rounding>,
}

impl ProgressBar {
Expand All @@ -28,6 +29,7 @@ impl ProgressBar {
text: None,
fill: None,
animate: false,
rounding: None,
}
}

Expand Down Expand Up @@ -69,11 +71,26 @@ impl ProgressBar {
/// Whether to display a loading animation when progress `< 1`.
/// Note that this will cause the UI to be redrawn.
/// Defaults to `false`.
///
/// If [`Self::rounding`] and [`Self::animate`] are used simultaneously, the animation is not
/// rendered, since it requires a perfect circle to render correctly. However, the UI is still
/// redrawn.
#[inline]
pub fn animate(mut self, animate: bool) -> Self {
YgorSouza marked this conversation as resolved.
Show resolved Hide resolved
self.animate = animate;
self
}

/// Set the rounding of the progress bar.
///
/// If [`Self::rounding`] and [`Self::animate`] are used simultaneously, the animation is not
/// rendered, since it requires a perfect circle to render correctly. However, the UI is still
/// redrawn.
#[inline]
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
YgorSouza marked this conversation as resolved.
Show resolved Hide resolved
self.rounding = Some(rounding.into());
self
}
}

impl Widget for ProgressBar {
Expand All @@ -85,6 +102,7 @@ impl Widget for ProgressBar {
text,
fill,
animate,
rounding,
} = self;

let animate = animate && progress < 1.0;
Expand All @@ -101,16 +119,15 @@ impl Widget for ProgressBar {
}

let visuals = ui.style().visuals.clone();
let rounding = outer_rect.height() / 2.0;
let is_custom_rounding = rounding.is_some();
let corner_radius = outer_rect.height() / 2.0;
let rounding = rounding.unwrap_or_else(|| corner_radius.into());
ui.painter()
.rect(outer_rect, rounding, 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 min_width = 2.0 * rounding.sw.at_least(rounding.nw).at_most(corner_radius);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why only look at sw and nw here? Wouldn't it make sense to use something like rounding.max() instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this min_width is meant to make the rounding look right when the progress bar is empty, I figured only the left side mattered. But maybe it would be better to just use max(). I'll do a quick test and commit it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So there is no method Rounding::max currently. Should I add one?

It doesn't seem to be necessary, though. The problem this min_width avoids is that the filled rect is flattened when it is too small, and then it ends up appearing outside the background rect, like this:

image

Actually, if there was a way to avoid this overflow without setting a minimum width, I think a lot of people would prefer that (see for example #3999). But I don't know if it's possible.

let filled_width = (outer_rect.width() * progress).at_least(min_width);
let inner_rect =
Rect::from_min_size(outer_rect.min, vec2(filled_width, outer_rect.height()));

let (dark, bright) = (0.7, 1.0);
let color_factor = if animate {
Expand All @@ -129,19 +146,19 @@ impl Widget for ProgressBar {
Stroke::NONE,
);

if animate {
if animate && !is_custom_rounding {
let n_points = 20;
let time = ui.input(|i| i.time);
let start_angle = time * std::f64::consts::TAU;
let end_angle = start_angle + 240f64.to_radians() * time.sin();
let circle_radius = rounding - 2.0;
let circle_radius = corner_radius - 2.0;
let points: Vec<Pos2> = (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(-rounding, 0.0)
+ vec2(-corner_radius, 0.0)
})
.collect();
ui.painter()
Expand Down
Loading