use egui::epaint::{emath::lerp, vec2, Color32, Pos2, Rect, Shape, Stroke};
use egui::{Response, Sense, Ui, Widget};
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
#[derive(Default)]
pub struct ProgressSpinner {
size: Option<f32>,
color: Option<Color32>,
progress: Option<f64>,
}
impl ProgressSpinner {
pub fn new() -> Self {
Self::default()
}
#[allow(unused)]
pub fn size(mut self, size: f32) -> Self {
self.size = Some(size);
self
}
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.color = Some(color.into());
self
}
pub fn progress(mut self, progress: impl Into<Option<f64>>) -> Self {
self.progress = progress.into();
self
}
pub fn paint_at(&self, ui: &Ui, rect: Rect) {
if ui.is_rect_visible(rect) {
ui.ctx().request_repaint();
let color = self
.color
.unwrap_or_else(|| ui.visuals().strong_text_color());
let radius = (rect.height() / 2.0) - 2.0;
let n_points = 20;
let (start_angle, end_angle) = if let Some(progress) = self.progress {
let start_angle = 0f64.to_radians();
let end_angle = start_angle + 360f64.to_radians() * progress;
(start_angle, end_angle)
} else {
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();
(start_angle, end_angle)
};
let points: Vec<Pos2> = (0..=n_points)
.map(|i| {
let angle = lerp(start_angle..=end_angle, f64::from(i) / f64::from(n_points));
let (sin, cos) = angle.sin_cos();
rect.center() + radius * vec2(cos as f32, sin as f32)
})
.collect();
ui.painter()
.add(Shape::line(points, Stroke::new(3.0, color)));
}
}
}
impl Widget for ProgressSpinner {
fn ui(self, ui: &mut Ui) -> Response {
let size = self
.size
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover());
self.paint_at(ui, rect);
response
}
}