From c105722eb426c174d21e69eafd352f82caf1df49 Mon Sep 17 00:00:00 2001 From: sacha-renault Date: Sun, 6 Jul 2025 09:36:29 +0200 Subject: [PATCH] fix: Address PR feedback Prevent division by zero when min equals max value Also improves angle calculations using TAU constant and adds NaN validation. Also fix doc --- examples/example_knob.rs | 2 +- src/lib.rs | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/examples/example_knob.rs b/examples/example_knob.rs index 11a0690..2f7595a 100644 --- a/examples/example_knob.rs +++ b/examples/example_knob.rs @@ -122,7 +122,7 @@ impl eframe::App for KnobExample { ); ui.add( - Knob::new(&mut self.blue_value, 0.0, 100.0, egui_knob::KnobStyle::Dot) + Knob::new(&mut self.blue_value, 0.0, 100., egui_knob::KnobStyle::Dot) .with_label("Top", egui_knob::LabelPosition::Top) .with_colors( egui::Color32::from_rgb(30, 30, 80), diff --git a/src/lib.rs b/src/lib.rs index 5828620..f772cd9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,16 @@ pub struct Knob<'a> { label_offset: f32, label_format: Box String>, step: Option, + + /// Minimum angle in radians. + /// Specifies the lower bound of the knob's rotation. + /// Expected range: 0.0 to TAU. Values outside this range are allowed if your use case requires it. min_angle: f32, + + /// Maximum angle in radians. + /// Specifies the upper bound of the knob's rotation. + /// Can be any value > `min_angle` + /// range `max_angle` - `min_angle` > TAU is allowed but will induce multi-turn max_angle: f32, } @@ -73,6 +82,8 @@ impl<'a> Knob<'a> { label_offset: 1.0, label_format: Box::new(|v| format!("{:.2}", v)), step: None, + + // Hardcode those two angles to ENSURE backward compatibility min_angle: -std::f32::consts::PI, max_angle: std::f32::consts::PI * 0.5, } @@ -81,7 +92,7 @@ impl<'a> Knob<'a> { /// Sets the angular sweep range of the knob /// /// This controls where the knob starts and how far it can rotate. By default, - /// knobs start at the bottom-left (135°) and sweep 270° clockwise to bottom-right. + /// knobs start at the left (180°) and sweep 270° clockwise to bottom. /// /// # Arguments /// * `start_angle_normalized` - Starting position as fraction of full circle: @@ -95,12 +106,20 @@ impl<'a> Knob<'a> { /// - `0.75` = three-quarter turn (270°) /// - `1.0` = full turn (360°) /// - Values > 1.0 create multi-turn knobs + /// - Negative values are clamped to 0.0 + /// + /// Note: the start angle is offset by PI/2 so that `0.0` is at the bottom (6 o'clock) pub fn with_sweep_range(mut self, start_angle_normalized: f32, range: f32) -> Self { + if start_angle_normalized.is_nan() || range.is_nan() { + // Invalid input, return unchanged + return self; + } + self.min_angle = - start_angle_normalized.rem_euclid(1.) * f32::consts::PI * 2. + f32::consts::PI / 2.; + start_angle_normalized.rem_euclid(1.) * f32::consts::TAU + f32::consts::PI / 2.; // A range of 1. represent a full turn - self.max_angle = self.min_angle + range.max(0.) * 2. * f32::consts::PI; + self.max_angle = self.min_angle + range.max(0.) * f32::consts::TAU; self } @@ -242,8 +261,15 @@ impl Widget for Knob<'_> { let center = knob_rect.center(); let radius = knob_size.x / 2.0; - let angle = self.min_angle - + (*self.value - self.min) / (self.max - self.min) * (self.max_angle - self.min_angle); + let angle = if self.min == self.max { + // If min == max, just return min angle + // That's a edge case, using a 0 range knob is pretty useless + self.min_angle + } else { + self.min_angle + + (*self.value - self.min) / (self.max - self.min) + * (self.max_angle - self.min_angle) + }; painter.circle_stroke( center,