1#![allow(clippy::needless_pass_by_value)] use std::ops::RangeInclusive;
4
5use crate::{
6 emath, epaint, lerp, pos2, remap, remap_clamp, style, style::HandleShape, vec2, Color32,
7 DragValue, EventFilter, Key, Label, NumExt, Pos2, Rangef, Rect, Response, Sense, TextStyle,
8 TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, MINUS_CHAR_STR,
9};
10
11use super::drag_value::clamp_value_to_range;
12
13type NumFormatter<'a> = Box<dyn 'a + Fn(f64, RangeInclusive<usize>) -> String>;
16type NumParser<'a> = Box<dyn 'a + Fn(&str) -> Option<f64>>;
17
18type GetSetValue<'a> = Box<dyn 'a + FnMut(Option<f64>) -> f64>;
23
24fn get(get_set_value: &mut GetSetValue<'_>) -> f64 {
25 (get_set_value)(None)
26}
27
28fn set(get_set_value: &mut GetSetValue<'_>, value: f64) {
29 (get_set_value)(Some(value));
30}
31
32#[derive(Clone)]
35struct SliderSpec {
36 logarithmic: bool,
37
38 smallest_positive: f64,
41
42 largest_finite: f64,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
51pub enum SliderOrientation {
52 Horizontal,
53 Vertical,
54}
55
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
58#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
59pub enum SliderClamping {
60 Never,
67
68 Edits,
72
73 #[default]
75 Always,
76}
77
78#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
98pub struct Slider<'a> {
99 get_set_value: GetSetValue<'a>,
100 range: RangeInclusive<f64>,
101 spec: SliderSpec,
102 clamping: SliderClamping,
103 smart_aim: bool,
104 show_value: bool,
105 orientation: SliderOrientation,
106 prefix: String,
107 suffix: String,
108 text: WidgetText,
109
110 step: Option<f64>,
112
113 drag_value_speed: Option<f64>,
114 min_decimals: usize,
115 max_decimals: Option<usize>,
116 custom_formatter: Option<NumFormatter<'a>>,
117 custom_parser: Option<NumParser<'a>>,
118 trailing_fill: Option<bool>,
119 handle_shape: Option<HandleShape>,
120}
121
122impl<'a> Slider<'a> {
123 pub fn new<Num: emath::Numeric>(value: &'a mut Num, range: RangeInclusive<Num>) -> Self {
128 let range_f64 = range.start().to_f64()..=range.end().to_f64();
129 let slf = Self::from_get_set(range_f64, move |v: Option<f64>| {
130 if let Some(v) = v {
131 *value = Num::from_f64(v);
132 }
133 value.to_f64()
134 });
135
136 if Num::INTEGRAL {
137 slf.integer()
138 } else {
139 slf
140 }
141 }
142
143 pub fn from_get_set(
144 range: RangeInclusive<f64>,
145 get_set_value: impl 'a + FnMut(Option<f64>) -> f64,
146 ) -> Self {
147 Self {
148 get_set_value: Box::new(get_set_value),
149 range,
150 spec: SliderSpec {
151 logarithmic: false,
152 smallest_positive: 1e-6,
153 largest_finite: f64::INFINITY,
154 },
155 clamping: SliderClamping::default(),
156 smart_aim: true,
157 show_value: true,
158 orientation: SliderOrientation::Horizontal,
159 prefix: Default::default(),
160 suffix: Default::default(),
161 text: Default::default(),
162 step: None,
163 drag_value_speed: None,
164 min_decimals: 0,
165 max_decimals: None,
166 custom_formatter: None,
167 custom_parser: None,
168 trailing_fill: None,
169 handle_shape: None,
170 }
171 }
172
173 #[inline]
176 pub fn show_value(mut self, show_value: bool) -> Self {
177 self.show_value = show_value;
178 self
179 }
180
181 #[inline]
183 pub fn prefix(mut self, prefix: impl ToString) -> Self {
184 self.prefix = prefix.to_string();
185 self
186 }
187
188 #[inline]
190 pub fn suffix(mut self, suffix: impl ToString) -> Self {
191 self.suffix = suffix.to_string();
192 self
193 }
194
195 #[inline]
197 pub fn text(mut self, text: impl Into<WidgetText>) -> Self {
198 self.text = text.into();
199 self
200 }
201
202 #[inline]
203 pub fn text_color(mut self, text_color: Color32) -> Self {
204 self.text = self.text.color(text_color);
205 self
206 }
207
208 #[inline]
210 pub fn orientation(mut self, orientation: SliderOrientation) -> Self {
211 self.orientation = orientation;
212 self
213 }
214
215 #[inline]
217 pub fn vertical(mut self) -> Self {
218 self.orientation = SliderOrientation::Vertical;
219 self
220 }
221
222 #[inline]
227 pub fn logarithmic(mut self, logarithmic: bool) -> Self {
228 self.spec.logarithmic = logarithmic;
229 self
230 }
231
232 #[inline]
236 pub fn smallest_positive(mut self, smallest_positive: f64) -> Self {
237 self.spec.smallest_positive = smallest_positive;
238 self
239 }
240
241 #[inline]
245 pub fn largest_finite(mut self, largest_finite: f64) -> Self {
246 self.spec.largest_finite = largest_finite;
247 self
248 }
249
250 #[inline]
288 pub fn clamping(mut self, clamping: SliderClamping) -> Self {
289 self.clamping = clamping;
290 self
291 }
292
293 #[inline]
294 #[deprecated = "Use `slider.clamping(…) instead"]
295 pub fn clamp_to_range(self, clamp_to_range: bool) -> Self {
296 self.clamping(if clamp_to_range {
297 SliderClamping::Always
298 } else {
299 SliderClamping::Never
300 })
301 }
302
303 #[inline]
306 pub fn smart_aim(mut self, smart_aim: bool) -> Self {
307 self.smart_aim = smart_aim;
308 self
309 }
310
311 #[inline]
318 pub fn step_by(mut self, step: f64) -> Self {
319 self.step = if step != 0.0 { Some(step) } else { None };
320 self
321 }
322
323 #[inline]
332 pub fn drag_value_speed(mut self, drag_value_speed: f64) -> Self {
333 self.drag_value_speed = Some(drag_value_speed);
334 self
335 }
336
337 #[inline]
343 pub fn min_decimals(mut self, min_decimals: usize) -> Self {
344 self.min_decimals = min_decimals;
345 self
346 }
347
348 #[inline]
355 pub fn max_decimals(mut self, max_decimals: usize) -> Self {
356 self.max_decimals = Some(max_decimals);
357 self
358 }
359
360 #[inline]
361 pub fn max_decimals_opt(mut self, max_decimals: Option<usize>) -> Self {
362 self.max_decimals = max_decimals;
363 self
364 }
365
366 #[inline]
372 pub fn fixed_decimals(mut self, num_decimals: usize) -> Self {
373 self.min_decimals = num_decimals;
374 self.max_decimals = Some(num_decimals);
375 self
376 }
377
378 #[inline]
385 pub fn trailing_fill(mut self, trailing_fill: bool) -> Self {
386 self.trailing_fill = Some(trailing_fill);
387 self
388 }
389
390 #[inline]
395 pub fn handle_shape(mut self, handle_shape: HandleShape) -> Self {
396 self.handle_shape = Some(handle_shape);
397 self
398 }
399
400 pub fn custom_formatter(
438 mut self,
439 formatter: impl 'a + Fn(f64, RangeInclusive<usize>) -> String,
440 ) -> Self {
441 self.custom_formatter = Some(Box::new(formatter));
442 self
443 }
444
445 #[inline]
481 pub fn custom_parser(mut self, parser: impl 'a + Fn(&str) -> Option<f64>) -> Self {
482 self.custom_parser = Some(Box::new(parser));
483 self
484 }
485
486 pub fn binary(self, min_width: usize, twos_complement: bool) -> Self {
506 assert!(
507 min_width > 0,
508 "Slider::binary: `min_width` must be greater than 0"
509 );
510 if twos_complement {
511 self.custom_formatter(move |n, _| format!("{:0>min_width$b}", n as i64))
512 } else {
513 self.custom_formatter(move |n, _| {
514 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
515 format!("{sign}{:0>min_width$b}", n.abs() as i64)
516 })
517 }
518 .custom_parser(|s| i64::from_str_radix(s, 2).map(|n| n as f64).ok())
519 }
520
521 pub fn octal(self, min_width: usize, twos_complement: bool) -> Self {
541 assert!(
542 min_width > 0,
543 "Slider::octal: `min_width` must be greater than 0"
544 );
545 if twos_complement {
546 self.custom_formatter(move |n, _| format!("{:0>min_width$o}", n as i64))
547 } else {
548 self.custom_formatter(move |n, _| {
549 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
550 format!("{sign}{:0>min_width$o}", n.abs() as i64)
551 })
552 }
553 .custom_parser(|s| i64::from_str_radix(s, 8).map(|n| n as f64).ok())
554 }
555
556 pub fn hexadecimal(self, min_width: usize, twos_complement: bool, upper: bool) -> Self {
576 assert!(
577 min_width > 0,
578 "Slider::hexadecimal: `min_width` must be greater than 0"
579 );
580 match (twos_complement, upper) {
581 (true, true) => {
582 self.custom_formatter(move |n, _| format!("{:0>min_width$X}", n as i64))
583 }
584 (true, false) => {
585 self.custom_formatter(move |n, _| format!("{:0>min_width$x}", n as i64))
586 }
587 (false, true) => self.custom_formatter(move |n, _| {
588 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
589 format!("{sign}{:0>min_width$X}", n.abs() as i64)
590 }),
591 (false, false) => self.custom_formatter(move |n, _| {
592 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
593 format!("{sign}{:0>min_width$x}", n.abs() as i64)
594 }),
595 }
596 .custom_parser(|s| i64::from_str_radix(s, 16).map(|n| n as f64).ok())
597 }
598
599 pub fn integer(self) -> Self {
603 self.fixed_decimals(0).smallest_positive(1.0).step_by(1.0)
604 }
605
606 fn get_value(&mut self) -> f64 {
607 let value = get(&mut self.get_set_value);
608 if self.clamping == SliderClamping::Always {
609 clamp_value_to_range(value, self.range.clone())
610 } else {
611 value
612 }
613 }
614
615 fn set_value(&mut self, mut value: f64) {
616 if self.clamping != SliderClamping::Never {
617 value = clamp_value_to_range(value, self.range.clone());
618 }
619
620 if let Some(step) = self.step {
621 let start = *self.range.start();
622 value = start + ((value - start) / step).round() * step;
623 }
624 if let Some(max_decimals) = self.max_decimals {
625 value = emath::round_to_decimals(value, max_decimals);
626 }
627 set(&mut self.get_set_value, value);
628 }
629
630 fn range(&self) -> RangeInclusive<f64> {
631 self.range.clone()
632 }
633
634 fn value_from_position(&self, position: f32, position_range: Rangef) -> f64 {
636 let normalized = remap_clamp(position, position_range, 0.0..=1.0) as f64;
637 value_from_normalized(normalized, self.range(), &self.spec)
638 }
639
640 fn position_from_value(&self, value: f64, position_range: Rangef) -> f32 {
641 let normalized = normalized_from_value(value, self.range(), &self.spec);
642 lerp(position_range, normalized as f32)
643 }
644}
645
646impl Slider<'_> {
647 fn allocate_slider_space(&self, ui: &mut Ui, thickness: f32) -> Response {
649 let desired_size = match self.orientation {
650 SliderOrientation::Horizontal => vec2(ui.spacing().slider_width, thickness),
651 SliderOrientation::Vertical => vec2(thickness, ui.spacing().slider_width),
652 };
653 ui.allocate_response(desired_size, Sense::drag())
654 }
655
656 fn slider_ui(&mut self, ui: &Ui, response: &Response) {
658 let rect = &response.rect;
659 let handle_shape = self
660 .handle_shape
661 .unwrap_or_else(|| ui.style().visuals.handle_shape);
662 let position_range = self.position_range(rect, &handle_shape);
663
664 if let Some(pointer_position_2d) = response.interact_pointer_pos() {
665 let position = self.pointer_position(pointer_position_2d);
666 let new_value = if self.smart_aim {
667 let aim_radius = ui.input(|i| i.aim_radius());
668 emath::smart_aim::best_in_range_f64(
669 self.value_from_position(position - aim_radius, position_range),
670 self.value_from_position(position + aim_radius, position_range),
671 )
672 } else {
673 self.value_from_position(position, position_range)
674 };
675 self.set_value(new_value);
676 }
677
678 let mut decrement = 0usize;
679 let mut increment = 0usize;
680
681 if response.has_focus() {
682 ui.ctx().memory_mut(|m| {
683 m.set_focus_lock_filter(
684 response.id,
685 EventFilter {
686 horizontal_arrows: matches!(
689 self.orientation,
690 SliderOrientation::Horizontal
691 ),
692 vertical_arrows: matches!(self.orientation, SliderOrientation::Vertical),
693 ..Default::default()
694 },
695 );
696 });
697
698 let (dec_key, inc_key) = match self.orientation {
699 SliderOrientation::Horizontal => (Key::ArrowLeft, Key::ArrowRight),
700 SliderOrientation::Vertical => (Key::ArrowUp, Key::ArrowDown),
703 };
704
705 ui.input(|input| {
706 decrement += input.num_presses(dec_key);
707 increment += input.num_presses(inc_key);
708 });
709 }
710
711 #[cfg(feature = "accesskit")]
712 {
713 use accesskit::Action;
714 ui.input(|input| {
715 decrement += input.num_accesskit_action_requests(response.id, Action::Decrement);
716 increment += input.num_accesskit_action_requests(response.id, Action::Increment);
717 });
718 }
719
720 let kb_step = increment as f32 - decrement as f32;
721
722 if kb_step != 0.0 {
723 let ui_point_per_step = 1.0; let prev_value = self.get_value();
725 let prev_position = self.position_from_value(prev_value, position_range);
726 let new_position = prev_position + ui_point_per_step * kb_step;
727 let new_value = match self.step {
728 Some(step) => prev_value + (kb_step as f64 * step),
729 None if self.smart_aim => {
730 let aim_radius = 0.49 * ui_point_per_step; emath::smart_aim::best_in_range_f64(
732 self.value_from_position(new_position - aim_radius, position_range),
733 self.value_from_position(new_position + aim_radius, position_range),
734 )
735 }
736 _ => self.value_from_position(new_position, position_range),
737 };
738 self.set_value(new_value);
739 }
740
741 #[cfg(feature = "accesskit")]
742 {
743 use accesskit::{Action, ActionData};
744 ui.input(|input| {
745 for request in input.accesskit_action_requests(response.id, Action::SetValue) {
746 if let Some(ActionData::NumericValue(new_value)) = request.data {
747 self.set_value(new_value);
748 }
749 }
750 });
751 }
752
753 if ui.is_rect_visible(response.rect) {
755 let value = self.get_value();
756
757 let visuals = ui.style().interact(response);
758 let widget_visuals = &ui.visuals().widgets;
759 let spacing = &ui.style().spacing;
760
761 let rail_radius = (spacing.slider_rail_height / 2.0).at_least(0.0);
762 let rail_rect = self.rail_rect(rect, rail_radius);
763 let corner_radius = widget_visuals.inactive.corner_radius;
764
765 ui.painter()
766 .rect_filled(rail_rect, corner_radius, widget_visuals.inactive.bg_fill);
767
768 let position_1d = self.position_from_value(value, position_range);
769 let center = self.marker_center(position_1d, &rail_rect);
770
771 let trailing_fill = self
773 .trailing_fill
774 .unwrap_or_else(|| ui.visuals().slider_trailing_fill);
775
776 if trailing_fill {
778 let mut trailing_rail_rect = rail_rect;
779
780 match self.orientation {
782 SliderOrientation::Horizontal => {
783 trailing_rail_rect.max.x = center.x + corner_radius.nw as f32;
784 }
785 SliderOrientation::Vertical => {
786 trailing_rail_rect.min.y = center.y - corner_radius.se as f32;
787 }
788 };
789
790 ui.painter().rect_filled(
791 trailing_rail_rect,
792 corner_radius,
793 ui.visuals().selection.bg_fill,
794 );
795 }
796
797 let radius = self.handle_radius(rect);
798
799 let handle_shape = self
800 .handle_shape
801 .unwrap_or_else(|| ui.style().visuals.handle_shape);
802 match handle_shape {
803 style::HandleShape::Circle => {
804 ui.painter().add(epaint::CircleShape {
805 center,
806 radius: radius + visuals.expansion,
807 fill: visuals.bg_fill,
808 stroke: visuals.fg_stroke,
809 });
810 }
811 style::HandleShape::Rect { aspect_ratio } => {
812 let v = match self.orientation {
813 SliderOrientation::Horizontal => Vec2::new(radius * aspect_ratio, radius),
814 SliderOrientation::Vertical => Vec2::new(radius, radius * aspect_ratio),
815 };
816 let v = v + Vec2::splat(visuals.expansion);
817 let rect = Rect::from_center_size(center, 2.0 * v);
818 ui.painter().rect(
819 rect,
820 visuals.corner_radius,
821 visuals.bg_fill,
822 visuals.fg_stroke,
823 epaint::StrokeKind::Inside,
824 );
825 }
826 }
827 }
828 }
829
830 fn marker_center(&self, position_1d: f32, rail_rect: &Rect) -> Pos2 {
831 match self.orientation {
832 SliderOrientation::Horizontal => pos2(position_1d, rail_rect.center().y),
833 SliderOrientation::Vertical => pos2(rail_rect.center().x, position_1d),
834 }
835 }
836
837 fn pointer_position(&self, pointer_position_2d: Pos2) -> f32 {
838 match self.orientation {
839 SliderOrientation::Horizontal => pointer_position_2d.x,
840 SliderOrientation::Vertical => pointer_position_2d.y,
841 }
842 }
843
844 fn position_range(&self, rect: &Rect, handle_shape: &style::HandleShape) -> Rangef {
845 let handle_radius = self.handle_radius(rect);
846 let handle_radius = match handle_shape {
847 style::HandleShape::Circle => handle_radius,
848 style::HandleShape::Rect { aspect_ratio } => handle_radius * aspect_ratio,
849 };
850 match self.orientation {
851 SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius),
852 SliderOrientation::Vertical => rect.y_range().shrink(handle_radius).flip(),
855 }
856 }
857
858 fn rail_rect(&self, rect: &Rect, radius: f32) -> Rect {
859 match self.orientation {
860 SliderOrientation::Horizontal => Rect::from_min_max(
861 pos2(rect.left(), rect.center().y - radius),
862 pos2(rect.right(), rect.center().y + radius),
863 ),
864 SliderOrientation::Vertical => Rect::from_min_max(
865 pos2(rect.center().x - radius, rect.top()),
866 pos2(rect.center().x + radius, rect.bottom()),
867 ),
868 }
869 }
870
871 fn handle_radius(&self, rect: &Rect) -> f32 {
872 let limit = match self.orientation {
873 SliderOrientation::Horizontal => rect.height(),
874 SliderOrientation::Vertical => rect.width(),
875 };
876 limit / 2.5
877 }
878
879 fn value_ui(&mut self, ui: &mut Ui, position_range: Rangef) -> Response {
880 let change = ui.input(|input| {
882 input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32
883 - input.num_presses(Key::ArrowDown) as i32
884 - input.num_presses(Key::ArrowLeft) as i32
885 });
886
887 let any_change = change != 0;
888 let speed = if let (Some(step), true) = (self.step, any_change) {
889 step
891 } else {
892 self.drag_value_speed
893 .unwrap_or_else(|| self.current_gradient(position_range))
894 };
895
896 let mut value = self.get_value();
897 let response = ui.add({
898 let mut dv = DragValue::new(&mut value)
899 .speed(speed)
900 .min_decimals(self.min_decimals)
901 .max_decimals_opt(self.max_decimals)
902 .suffix(self.suffix.clone())
903 .prefix(self.prefix.clone());
904
905 match self.clamping {
906 SliderClamping::Never => {}
907 SliderClamping::Edits => {
908 dv = dv.range(self.range.clone()).clamp_existing_to_range(false);
909 }
910 SliderClamping::Always => {
911 dv = dv.range(self.range.clone()).clamp_existing_to_range(true);
912 }
913 }
914
915 if let Some(fmt) = &self.custom_formatter {
916 dv = dv.custom_formatter(fmt);
917 };
918 if let Some(parser) = &self.custom_parser {
919 dv = dv.custom_parser(parser);
920 }
921 dv
922 });
923 if value != self.get_value() {
924 self.set_value(value);
925 }
926 response
927 }
928
929 fn current_gradient(&mut self, position_range: Rangef) -> f64 {
931 let value = self.get_value();
933 let value_from_pos = |position: f32| self.value_from_position(position, position_range);
934 let pos_from_value = |value: f64| self.position_from_value(value, position_range);
935 let left_value = value_from_pos(pos_from_value(value) - 0.5);
936 let right_value = value_from_pos(pos_from_value(value) + 0.5);
937 right_value - left_value
938 }
939
940 fn add_contents(&mut self, ui: &mut Ui) -> Response {
941 let old_value = self.get_value();
942
943 if self.clamping == SliderClamping::Always {
944 self.set_value(old_value);
945 }
946
947 let thickness = ui
948 .text_style_height(&TextStyle::Body)
949 .at_least(ui.spacing().interact_size.y);
950 let mut response = self.allocate_slider_space(ui, thickness);
951 self.slider_ui(ui, &response);
952
953 let value = self.get_value();
954 if value != old_value {
955 response.mark_changed();
956 }
957 response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text()));
958
959 #[cfg(feature = "accesskit")]
960 ui.ctx().accesskit_node_builder(response.id, |builder| {
961 use accesskit::Action;
962 builder.set_min_numeric_value(*self.range.start());
963 builder.set_max_numeric_value(*self.range.end());
964 if let Some(step) = self.step {
965 builder.set_numeric_value_step(step);
966 }
967 builder.add_action(Action::SetValue);
968
969 let clamp_range = if self.clamping == SliderClamping::Never {
970 f64::NEG_INFINITY..=f64::INFINITY
971 } else {
972 self.range()
973 };
974 if value < *clamp_range.end() {
975 builder.add_action(Action::Increment);
976 }
977 if value > *clamp_range.start() {
978 builder.add_action(Action::Decrement);
979 }
980 });
981
982 let slider_response = response.clone();
983
984 let value_response = if self.show_value {
985 let handle_shape = self
986 .handle_shape
987 .unwrap_or_else(|| ui.style().visuals.handle_shape);
988 let position_range = self.position_range(&response.rect, &handle_shape);
989 let value_response = self.value_ui(ui, position_range);
990 if value_response.gained_focus()
991 || value_response.has_focus()
992 || value_response.lost_focus()
993 {
994 response = value_response.union(response);
997 } else {
998 response = response.union(value_response.clone());
1000 }
1001 Some(value_response)
1002 } else {
1003 None
1004 };
1005
1006 if !self.text.is_empty() {
1007 let label_response =
1008 ui.add(Label::new(self.text.clone()).wrap_mode(TextWrapMode::Extend));
1009 slider_response.labelled_by(label_response.id);
1014 if let Some(value_response) = value_response {
1015 value_response.labelled_by(label_response.id);
1016 }
1017 }
1018
1019 response
1020 }
1021}
1022
1023impl Widget for Slider<'_> {
1024 fn ui(mut self, ui: &mut Ui) -> Response {
1025 let inner_response = match self.orientation {
1026 SliderOrientation::Horizontal => ui.horizontal(|ui| self.add_contents(ui)),
1027 SliderOrientation::Vertical => ui.vertical(|ui| self.add_contents(ui)),
1028 };
1029
1030 inner_response.inner | inner_response.response
1031 }
1032}
1033
1034const INFINITY: f64 = f64::INFINITY;
1041
1042const INF_RANGE_MAGNITUDE: f64 = 10.0;
1045
1046fn value_from_normalized(normalized: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
1047 let (min, max) = (*range.start(), *range.end());
1048
1049 if min.is_nan() || max.is_nan() {
1050 f64::NAN
1051 } else if min == max {
1052 min
1053 } else if min > max {
1054 value_from_normalized(1.0 - normalized, max..=min, spec)
1055 } else if normalized <= 0.0 {
1056 min
1057 } else if normalized >= 1.0 {
1058 max
1059 } else if spec.logarithmic {
1060 if max <= 0.0 {
1061 -value_from_normalized(normalized, -min..=-max, spec)
1063 } else if 0.0 <= min {
1064 let (min_log, max_log) = range_log10(min, max, spec);
1065 let log = lerp(min_log..=max_log, normalized);
1066 10.0_f64.powf(log)
1067 } else {
1068 assert!(min < 0.0 && 0.0 < max);
1069 let zero_cutoff = logarithmic_zero_cutoff(min, max);
1070 if normalized < zero_cutoff {
1071 value_from_normalized(
1073 remap(normalized, 0.0..=zero_cutoff, 0.0..=1.0),
1074 min..=0.0,
1075 spec,
1076 )
1077 } else {
1078 value_from_normalized(
1080 remap(normalized, zero_cutoff..=1.0, 0.0..=1.0),
1081 0.0..=max,
1082 spec,
1083 )
1084 }
1085 }
1086 } else {
1087 debug_assert!(
1088 min.is_finite() && max.is_finite(),
1089 "You should use a logarithmic range"
1090 );
1091 lerp(range, normalized.clamp(0.0, 1.0))
1092 }
1093}
1094
1095fn normalized_from_value(value: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
1096 let (min, max) = (*range.start(), *range.end());
1097
1098 if min.is_nan() || max.is_nan() {
1099 f64::NAN
1100 } else if min == max {
1101 0.5 } else if min > max {
1103 1.0 - normalized_from_value(value, max..=min, spec)
1104 } else if value <= min {
1105 0.0
1106 } else if value >= max {
1107 1.0
1108 } else if spec.logarithmic {
1109 if max <= 0.0 {
1110 normalized_from_value(-value, -min..=-max, spec)
1112 } else if 0.0 <= min {
1113 let (min_log, max_log) = range_log10(min, max, spec);
1114 let value_log = value.log10();
1115 remap_clamp(value_log, min_log..=max_log, 0.0..=1.0)
1116 } else {
1117 assert!(min < 0.0 && 0.0 < max);
1118 let zero_cutoff = logarithmic_zero_cutoff(min, max);
1119 if value < 0.0 {
1120 remap(
1122 normalized_from_value(value, min..=0.0, spec),
1123 0.0..=1.0,
1124 0.0..=zero_cutoff,
1125 )
1126 } else {
1127 remap(
1129 normalized_from_value(value, 0.0..=max, spec),
1130 0.0..=1.0,
1131 zero_cutoff..=1.0,
1132 )
1133 }
1134 }
1135 } else {
1136 debug_assert!(
1137 min.is_finite() && max.is_finite(),
1138 "You should use a logarithmic range"
1139 );
1140 remap_clamp(value, range, 0.0..=1.0)
1141 }
1142}
1143
1144fn range_log10(min: f64, max: f64, spec: &SliderSpec) -> (f64, f64) {
1145 assert!(spec.logarithmic);
1146 assert!(min <= max);
1147
1148 if min == 0.0 && max == INFINITY {
1149 (spec.smallest_positive.log10(), INF_RANGE_MAGNITUDE)
1150 } else if min == 0.0 {
1151 if spec.smallest_positive < max {
1152 (spec.smallest_positive.log10(), max.log10())
1153 } else {
1154 (max.log10() - INF_RANGE_MAGNITUDE, max.log10())
1155 }
1156 } else if max == INFINITY {
1157 if min < spec.largest_finite {
1158 (min.log10(), spec.largest_finite.log10())
1159 } else {
1160 (min.log10(), min.log10() + INF_RANGE_MAGNITUDE)
1161 }
1162 } else {
1163 (min.log10(), max.log10())
1164 }
1165}
1166
1167fn logarithmic_zero_cutoff(min: f64, max: f64) -> f64 {
1170 assert!(min < 0.0 && 0.0 < max);
1171
1172 let min_magnitude = if min == -INFINITY {
1173 INF_RANGE_MAGNITUDE
1174 } else {
1175 min.abs().log10().abs()
1176 };
1177 let max_magnitude = if max == INFINITY {
1178 INF_RANGE_MAGNITUDE
1179 } else {
1180 max.log10().abs()
1181 };
1182
1183 let cutoff = min_magnitude / (min_magnitude + max_magnitude);
1184 debug_assert!(
1185 0.0 <= cutoff && cutoff <= 1.0,
1186 "Bad cutoff {cutoff:?} for min {min:?} and max {max:?}"
1187 );
1188 cutoff
1189}