From 6620ac18b9a6cbfeb65bba56089161f26ff49823 Mon Sep 17 00:00:00 2001 From: KongJian <2501491361@qq.com> Date: Wed, 10 Sep 2025 17:56:38 +0800 Subject: [PATCH 1/5] feat: Add Configurable Speed and Direction to Spinner Control --- crates/egui/src/widgets/spinner.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/widgets/spinner.rs b/crates/egui/src/widgets/spinner.rs index 573517dcb93..3e536dc4e9b 100644 --- a/crates/egui/src/widgets/spinner.rs +++ b/crates/egui/src/widgets/spinner.rs @@ -2,6 +2,7 @@ use epaint::{Color32, Pos2, Rect, Shape, Stroke, emath::lerp, vec2}; use crate::{Response, Sense, Ui, Widget, WidgetInfo, WidgetType}; + /// A spinner widget used to indicate loading. /// /// See also: [`crate::ProgressBar`]. @@ -11,6 +12,8 @@ pub struct Spinner { /// Uses the style's `interact_size` if `None`. size: Option, color: Option, + speed: Option, // 旋转速度 + clockwise: Option, // 是否顺时针 } impl Spinner { @@ -34,8 +37,20 @@ impl Spinner { self } + /// Sets the spinner's rotation speed. + pub fn speed(mut self, speed: f64) -> Self { + self.speed = Some(speed); + self + } + + /// Sets the spinner's rotation direction. True for clockwise, false for counter-clockwise. + pub fn clockwise(mut self, clockwise: bool) -> Self { + self.clockwise = Some(clockwise); + self + } + /// Paint the spinner in the given rectangle. - pub fn paint_at(&self, ui: &Ui, rect: Rect) { + pub fn paint_at(&self, ui: &Ui, rect: Rect, speed: f64, clockwise: bool) { if ui.is_rect_visible(rect) { ui.ctx().request_repaint(); // because it is animated @@ -45,7 +60,7 @@ impl Spinner { let radius = (rect.height() / 2.0) - 2.0; let n_points = (radius.round() as u32).clamp(8, 128); let time = ui.input(|i| i.time); - let start_angle = time * std::f64::consts::TAU; + let start_angle = time * speed * if clockwise { 1.0 } else { -1.0 } * std::f64::consts::TAU; let end_angle = start_angle + 240f64.to_radians() * time.sin(); let points: Vec = (0..n_points) .map(|i| { @@ -66,8 +81,10 @@ impl Widget for Spinner { .size .unwrap_or_else(|| ui.style().spacing.interact_size.y); let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover()); + let speed = self.speed.unwrap_or(1.0); + let clockwise = self.clockwise.unwrap_or(true); response.widget_info(|| WidgetInfo::new(WidgetType::ProgressIndicator)); - self.paint_at(ui, rect); + self.paint_at(ui, rect, speed, clockwise); response } From 2cb479e30284675f3ffc48a3e6cdd8a558f7e454 Mon Sep 17 00:00:00 2001 From: KongJian <2501491361@qq.com> Date: Wed, 10 Sep 2025 18:05:26 +0800 Subject: [PATCH 2/5] feat: Add Configurable Speed and Direction to Spinner Control --- crates/egui/src/widgets/spinner.rs | 135 ++++++++++++++--------------- 1 file changed, 67 insertions(+), 68 deletions(-) diff --git a/crates/egui/src/widgets/spinner.rs b/crates/egui/src/widgets/spinner.rs index 3e536dc4e9b..159d03e8e42 100644 --- a/crates/egui/src/widgets/spinner.rs +++ b/crates/egui/src/widgets/spinner.rs @@ -1,91 +1,90 @@ -use epaint::{Color32, Pos2, Rect, Shape, Stroke, emath::lerp, vec2}; +use epaint::{emath::lerp, vec2, Color32, Pos2, Rect, Shape, Stroke}; use crate::{Response, Sense, Ui, Widget, WidgetInfo, WidgetType}; - /// A spinner widget used to indicate loading. /// /// See also: [`crate::ProgressBar`]. #[must_use = "You should put this widget in a ui with `ui.add(widget);`"] #[derive(Default)] pub struct Spinner { - /// Uses the style's `interact_size` if `None`. - size: Option, - color: Option, - speed: Option, // 旋转速度 - clockwise: Option, // 是否顺时针 + /// Uses the style's `interact_size` if `None`. + size: Option, + color: Option, + speed: Option, // 旋转速度 + clockwise: Option, // 是否顺时针 } impl Spinner { - /// Create a new spinner that uses the style's `interact_size` unless changed. - pub fn new() -> Self { - Self::default() - } + /// Create a new spinner that uses the style's `interact_size` unless changed. + pub fn new() -> Self { + Self::default() + } - /// Sets the spinner's size. The size sets both the height and width, as the spinner is always - /// square. If the size isn't set explicitly, the active style's `interact_size` is used. - #[inline] - pub fn size(mut self, size: f32) -> Self { - self.size = Some(size); - self - } + /// Sets the spinner's size. The size sets both the height and width, as the spinner is always + /// square. If the size isn't set explicitly, the active style's `interact_size` is used. + #[inline] + pub fn size(mut self, size: f32) -> Self { + self.size = Some(size); + self + } - /// Sets the spinner's color. - #[inline] - pub fn color(mut self, color: impl Into) -> Self { - self.color = Some(color.into()); - self - } + /// Sets the spinner's color. + #[inline] + pub fn color(mut self, color: impl Into) -> Self { + self.color = Some(color.into()); + self + } - /// Sets the spinner's rotation speed. - pub fn speed(mut self, speed: f64) -> Self { - self.speed = Some(speed); - self - } + /// Sets the spinner's rotation speed. + pub fn speed(mut self, speed: f64) -> Self { + self.speed = Some(speed); + self + } - /// Sets the spinner's rotation direction. True for clockwise, false for counter-clockwise. - pub fn clockwise(mut self, clockwise: bool) -> Self { - self.clockwise = Some(clockwise); - self - } + /// Sets the spinner's rotation direction. True for clockwise, false for counter-clockwise. + pub fn clockwise(mut self, clockwise: bool) -> Self { + self.clockwise = Some(clockwise); + self + } - /// Paint the spinner in the given rectangle. - pub fn paint_at(&self, ui: &Ui, rect: Rect, speed: f64, clockwise: bool) { - if ui.is_rect_visible(rect) { - ui.ctx().request_repaint(); // because it is animated + /// Paint the spinner in the given rectangle. + pub fn paint_at(&self, ui: &Ui, rect: Rect, speed: f64, clockwise: bool) { + if ui.is_rect_visible(rect) { + ui.ctx().request_repaint(); // because it is animated - let color = self - .color - .unwrap_or_else(|| ui.visuals().strong_text_color()); - let radius = (rect.height() / 2.0) - 2.0; - let n_points = (radius.round() as u32).clamp(8, 128); - let time = ui.input(|i| i.time); - let start_angle = time * speed * if clockwise { 1.0 } else { -1.0 } * std::f64::consts::TAU; - let end_angle = start_angle + 240f64.to_radians() * time.sin(); - 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(); - rect.center() + radius * vec2(cos as f32, sin as f32) - }) - .collect(); - ui.painter() - .add(Shape::line(points, Stroke::new(3.0, color))); - } - } + let color = self + .color + .unwrap_or_else(|| ui.visuals().strong_text_color()); + let radius = (rect.height() / 2.0) - 2.0; + let n_points = (radius.round() as u32).clamp(8, 128); + let time = ui.input(|i| i.time); + let start_angle = time * speed * if clockwise { 1.0 } else { -1.0 } * std::f64::consts::TAU; + let end_angle = start_angle + 240f64.to_radians() * time.sin(); + 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(); + 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 Spinner { - 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()); - let speed = self.speed.unwrap_or(1.0); - let clockwise = self.clockwise.unwrap_or(true); - response.widget_info(|| WidgetInfo::new(WidgetType::ProgressIndicator)); - self.paint_at(ui, rect, speed, clockwise); + 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()); + let speed = self.speed.unwrap_or(1.0); + let clockwise = self.clockwise.unwrap_or(true); + response.widget_info(|| WidgetInfo::new(WidgetType::ProgressIndicator)); + self.paint_at(ui, rect, speed, clockwise); - response - } + response + } } From fb73c281008de83ecc6f6e4455948025fe007655 Mon Sep 17 00:00:00 2001 From: KongJian <2501491361@qq.com> Date: Wed, 10 Sep 2025 20:25:40 +0800 Subject: [PATCH 3/5] fix: update from `Spinner::new().paint_at(ui, rect);` To `Spinner::new().paint_at(ui, rect, 1.0, true);` --- crates/egui/src/widgets/image.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 167920adc99..81f65742679 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -683,7 +683,7 @@ pub fn paint_texture_load_result( let show_loading_spinner = show_loading_spinner.unwrap_or(ui.visuals().image_loading_spinners); if show_loading_spinner { - Spinner::new().paint_at(ui, rect); + Spinner::new().paint_at(ui, rect, 1.0, true); } } Err(_) => { From 43bade9edaaa1d4545fa3e2c2cddbdaa0f93ba07 Mon Sep 17 00:00:00 2001 From: KongJianGhost <2501491361@qq.com> Date: Wed, 24 Sep 2025 13:02:00 +0800 Subject: [PATCH 4/5] update:del clockwise because it's useless --- crates/egui/src/widgets/image.rs | 2 +- crates/egui/src/widgets/spinner.rs | 144 +++++++++++++++-------------- 2 files changed, 75 insertions(+), 71 deletions(-) diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 81f65742679..00f8ed001fc 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -683,7 +683,7 @@ pub fn paint_texture_load_result( let show_loading_spinner = show_loading_spinner.unwrap_or(ui.visuals().image_loading_spinners); if show_loading_spinner { - Spinner::new().paint_at(ui, rect, 1.0, true); + Spinner::new().paint_at(ui, rect, 1.0); } } Err(_) => { diff --git a/crates/egui/src/widgets/spinner.rs b/crates/egui/src/widgets/spinner.rs index 159d03e8e42..6723231da19 100644 --- a/crates/egui/src/widgets/spinner.rs +++ b/crates/egui/src/widgets/spinner.rs @@ -1,4 +1,4 @@ -use epaint::{emath::lerp, vec2, Color32, Pos2, Rect, Shape, Stroke}; +use epaint::{Color32, Pos2, Rect, Shape, Stroke, emath::lerp, vec2}; use crate::{Response, Sense, Ui, Widget, WidgetInfo, WidgetType}; @@ -8,83 +8,87 @@ use crate::{Response, Sense, Ui, Widget, WidgetInfo, WidgetType}; #[must_use = "You should put this widget in a ui with `ui.add(widget);`"] #[derive(Default)] pub struct Spinner { - /// Uses the style's `interact_size` if `None`. - size: Option, - color: Option, - speed: Option, // 旋转速度 - clockwise: Option, // 是否顺时针 + /// Uses the style's `interact_size` if `None`. + size: Option, + color: Option, + speed: Option, } - impl Spinner { - /// Create a new spinner that uses the style's `interact_size` unless changed. - pub fn new() -> Self { - Self::default() - } - - /// Sets the spinner's size. The size sets both the height and width, as the spinner is always - /// square. If the size isn't set explicitly, the active style's `interact_size` is used. - #[inline] - pub fn size(mut self, size: f32) -> Self { - self.size = Some(size); - self - } + /// Create a new spinner that uses the style's `interact_size` unless changed. + pub fn new() -> Self { + Self::default() + } - /// Sets the spinner's color. - #[inline] - pub fn color(mut self, color: impl Into) -> Self { - self.color = Some(color.into()); - self - } + /// Sets the spinner's size. The size sets both the height and width, as the spinner is always + /// square. If the size isn't set explicitly, the active style's `interact_size` is used. + #[inline] + pub fn size(mut self, size: f32) -> Self { + self.size = Some(size); + self + } - /// Sets the spinner's rotation speed. - pub fn speed(mut self, speed: f64) -> Self { - self.speed = Some(speed); - self - } + /// Sets the spinner's color. + #[inline] + pub fn color(mut self, color: impl Into) -> Self { + self.color = Some(color.into()); + self + } - /// Sets the spinner's rotation direction. True for clockwise, false for counter-clockwise. - pub fn clockwise(mut self, clockwise: bool) -> Self { - self.clockwise = Some(clockwise); - self - } + /// Sets the spinner's rotation speed and direction. + /// + /// A `speed` of `1.0` corresponds to one full rotation per second clockwise. + /// A negative value, such as `-1.0`, indicates a counter-clockwise rotation + /// at the same speed. A `speed` of `0.0` will halt the spinner. + /// + /// # Examples + /// + /// ```rust + /// // Sets a clockwise rotation at two rotations per second. + /// let clockwise = Spinner::new().speed(2.0); + /// + /// // Sets a counter-clockwise rotation at one rotation per second. + /// let counter_clockwise = Spinner::new().speed(-1.0); + /// ``` + pub fn speed(mut self, speed: f64) -> Self { + self.speed = Some(speed); + self + } - /// Paint the spinner in the given rectangle. - pub fn paint_at(&self, ui: &Ui, rect: Rect, speed: f64, clockwise: bool) { - if ui.is_rect_visible(rect) { - ui.ctx().request_repaint(); // because it is animated + /// Paint the spinner in the given rectangle. + pub fn paint_at(&self, ui: &Ui, rect: Rect, speed: f64) { + if ui.is_rect_visible(rect) { + ui.ctx().request_repaint(); // because it is animated - let color = self - .color - .unwrap_or_else(|| ui.visuals().strong_text_color()); - let radius = (rect.height() / 2.0) - 2.0; - let n_points = (radius.round() as u32).clamp(8, 128); - let time = ui.input(|i| i.time); - let start_angle = time * speed * if clockwise { 1.0 } else { -1.0 } * std::f64::consts::TAU; - let end_angle = start_angle + 240f64.to_radians() * time.sin(); - 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(); - rect.center() + radius * vec2(cos as f32, sin as f32) - }) - .collect(); - ui.painter() - .add(Shape::line(points, Stroke::new(3.0, color))); - } - } + let color = self + .color + .unwrap_or_else(|| ui.visuals().strong_text_color()); + let radius = (rect.height() / 2.0) - 2.0; + let n_points = (radius.round() as u32).clamp(8, 128); + let time = ui.input(|i| i.time); + let start_angle = time * speed * std::f64::consts::TAU; + let end_angle = start_angle + 240f64.to_radians() * time.sin(); + 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(); + 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 Spinner { - 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()); - let speed = self.speed.unwrap_or(1.0); - let clockwise = self.clockwise.unwrap_or(true); - response.widget_info(|| WidgetInfo::new(WidgetType::ProgressIndicator)); - self.paint_at(ui, rect, speed, clockwise); - - response - } + 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()); + let speed = self.speed.unwrap_or(1.0); + response.widget_info(|| WidgetInfo::new(WidgetType::ProgressIndicator)); + self.paint_at(ui, rect, speed); + response + } } From b93c857122e446793ad5edcb34edaebf12c26180 Mon Sep 17 00:00:00 2001 From: KongJianGhost <2501491361@qq.com> Date: Wed, 24 Sep 2025 13:39:27 +0800 Subject: [PATCH 5/5] update:switch tabs to spaces --- crates/egui/src/widgets/spinner.rs | 35 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/crates/egui/src/widgets/spinner.rs b/crates/egui/src/widgets/spinner.rs index f7388f1e35e..d6a7f88fbad 100644 --- a/crates/egui/src/widgets/spinner.rs +++ b/crates/egui/src/widgets/spinner.rs @@ -14,25 +14,25 @@ pub struct Spinner { speed: Option, } impl Spinner { - /// Create a new spinner that uses the style's `interact_size` unless changed. - pub fn new() -> Self { - Self::default() - } + /// Create a new spinner that uses the style's `interact_size` unless changed. + pub fn new() -> Self { + Self::default() + } - /// Sets the spinner's size. The size sets both the height and width, as the spinner is always - /// square. If the size isn't set explicitly, the active style's `interact_size` is used. - #[inline] - pub fn size(mut self, size: f32) -> Self { - self.size = Some(size); - self - } + /// Sets the spinner's size. The size sets both the height and width, as the spinner is always + /// square. If the size isn't set explicitly, the active style's `interact_size` is used. + #[inline] + pub fn size(mut self, size: f32) -> Self { + self.size = Some(size); + self + } - /// Sets the spinner's color. - #[inline] - pub fn color(mut self, color: impl Into) -> Self { - self.color = Some(color.into()); - self - } + /// Sets the spinner's color. + #[inline] + pub fn color(mut self, color: impl Into) -> Self { + self.color = Some(color.into()); + self + } /// Sets the spinner's rotation speed and direction. /// /// A `speed` of `1.0` corresponds to one full rotation per second clockwise. @@ -90,5 +90,4 @@ impl Widget for Spinner { self.paint_at(ui, rect, speed); response } - }