1#![allow(clippy::needless_pass_by_value)] use std::ops::RangeInclusive;
4
5use crate::{style::HandleShape, *};
6
7type NumFormatter<'a> = Box<dyn 'a + Fn(f64, RangeInclusive<usize>) -> String>;
10type NumParser<'a> = Box<dyn 'a + Fn(&str) -> Option<f64>>;
11
12type GetSetValue<'a> = Box<dyn 'a + FnMut(Option<f64>) -> f64>;
17
18fn get(get_set_value: &mut GetSetValue<'_>) -> f64 {
19 (get_set_value)(None)
20}
21
22fn set(get_set_value: &mut GetSetValue<'_>, value: f64) {
23 (get_set_value)(Some(value));
24}
25
26#[derive(Clone)]
29struct SliderSpec {
30 logarithmic: bool,
31
32 smallest_positive: f64,
35
36 largest_finite: f64,
40}
41
42pub enum SliderOrientation {
44 Horizontal,
45 Vertical,
46}
47
48#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
68pub struct Slider<'a> {
69 get_set_value: GetSetValue<'a>,
70 range: RangeInclusive<f64>,
71 spec: SliderSpec,
72 clamp_to_range: bool,
73 smart_aim: bool,
74 show_value: bool,
75 orientation: SliderOrientation,
76 prefix: String,
77 suffix: String,
78 text: WidgetText,
79
80 step: Option<f64>,
82
83 drag_value_speed: Option<f64>,
84 min_decimals: usize,
85 max_decimals: Option<usize>,
86 custom_formatter: Option<NumFormatter<'a>>,
87 custom_parser: Option<NumParser<'a>>,
88 trailing_fill: Option<bool>,
89 handle_shape: Option<HandleShape>,
90}
91
92impl<'a> Slider<'a> {
93 pub fn new<Num: emath::Numeric>(value: &'a mut Num, range: RangeInclusive<Num>) -> Self {
95 let range_f64 = range.start().to_f64()..=range.end().to_f64();
96 let slf = Self::from_get_set(range_f64, move |v: Option<f64>| {
97 if let Some(v) = v {
98 *value = Num::from_f64(v);
99 }
100 value.to_f64()
101 });
102
103 if Num::INTEGRAL {
104 slf.integer()
105 } else {
106 slf
107 }
108 }
109
110 pub fn from_get_set(
111 range: RangeInclusive<f64>,
112 get_set_value: impl 'a + FnMut(Option<f64>) -> f64,
113 ) -> Self {
114 Self {
115 get_set_value: Box::new(get_set_value),
116 range,
117 spec: SliderSpec {
118 logarithmic: false,
119 smallest_positive: 1e-6,
120 largest_finite: f64::INFINITY,
121 },
122 clamp_to_range: true,
123 smart_aim: true,
124 show_value: true,
125 orientation: SliderOrientation::Horizontal,
126 prefix: Default::default(),
127 suffix: Default::default(),
128 text: Default::default(),
129 step: None,
130 drag_value_speed: None,
131 min_decimals: 0,
132 max_decimals: None,
133 custom_formatter: None,
134 custom_parser: None,
135 trailing_fill: None,
136 handle_shape: None,
137 }
138 }
139
140 #[inline]
143 pub fn show_value(mut self, show_value: bool) -> Self {
144 self.show_value = show_value;
145 self
146 }
147
148 #[inline]
150 pub fn prefix(mut self, prefix: impl ToString) -> Self {
151 self.prefix = prefix.to_string();
152 self
153 }
154
155 #[inline]
157 pub fn suffix(mut self, suffix: impl ToString) -> Self {
158 self.suffix = suffix.to_string();
159 self
160 }
161
162 #[inline]
164 pub fn text(mut self, text: impl Into<WidgetText>) -> Self {
165 self.text = text.into();
166 self
167 }
168
169 #[inline]
170 pub fn text_color(mut self, text_color: Color32) -> Self {
171 self.text = self.text.color(text_color);
172 self
173 }
174
175 #[inline]
177 pub fn orientation(mut self, orientation: SliderOrientation) -> Self {
178 self.orientation = orientation;
179 self
180 }
181
182 #[inline]
184 pub fn vertical(mut self) -> Self {
185 self.orientation = SliderOrientation::Vertical;
186 self
187 }
188
189 #[inline]
194 pub fn logarithmic(mut self, logarithmic: bool) -> Self {
195 self.spec.logarithmic = logarithmic;
196 self
197 }
198
199 #[inline]
203 pub fn smallest_positive(mut self, smallest_positive: f64) -> Self {
204 self.spec.smallest_positive = smallest_positive;
205 self
206 }
207
208 #[inline]
212 pub fn largest_finite(mut self, largest_finite: f64) -> Self {
213 self.spec.largest_finite = largest_finite;
214 self
215 }
216
217 #[inline]
220 pub fn clamp_to_range(mut self, clamp_to_range: bool) -> Self {
221 self.clamp_to_range = clamp_to_range;
222 self
223 }
224
225 #[inline]
228 pub fn smart_aim(mut self, smart_aim: bool) -> Self {
229 self.smart_aim = smart_aim;
230 self
231 }
232
233 #[inline]
240 pub fn step_by(mut self, step: f64) -> Self {
241 self.step = if step != 0.0 { Some(step) } else { None };
242 self
243 }
244
245 #[inline]
254 pub fn drag_value_speed(mut self, drag_value_speed: f64) -> Self {
255 self.drag_value_speed = Some(drag_value_speed);
256 self
257 }
258
259 #[inline]
265 pub fn min_decimals(mut self, min_decimals: usize) -> Self {
266 self.min_decimals = min_decimals;
267 self
268 }
269
270 #[inline]
277 pub fn max_decimals(mut self, max_decimals: usize) -> Self {
278 self.max_decimals = Some(max_decimals);
279 self
280 }
281
282 #[inline]
288 pub fn fixed_decimals(mut self, num_decimals: usize) -> Self {
289 self.min_decimals = num_decimals;
290 self.max_decimals = Some(num_decimals);
291 self
292 }
293
294 #[inline]
301 pub fn trailing_fill(mut self, trailing_fill: bool) -> Self {
302 self.trailing_fill = Some(trailing_fill);
303 self
304 }
305
306 #[inline]
311 pub fn handle_shape(mut self, handle_shape: HandleShape) -> Self {
312 self.handle_shape = Some(handle_shape);
313 self
314 }
315
316 pub fn custom_formatter(
354 mut self,
355 formatter: impl 'a + Fn(f64, RangeInclusive<usize>) -> String,
356 ) -> Self {
357 self.custom_formatter = Some(Box::new(formatter));
358 self
359 }
360
361 #[inline]
397 pub fn custom_parser(mut self, parser: impl 'a + Fn(&str) -> Option<f64>) -> Self {
398 self.custom_parser = Some(Box::new(parser));
399 self
400 }
401
402 pub fn binary(self, min_width: usize, twos_complement: bool) -> Self {
422 assert!(
423 min_width > 0,
424 "Slider::binary: `min_width` must be greater than 0"
425 );
426 if twos_complement {
427 self.custom_formatter(move |n, _| format!("{:0>min_width$b}", n as i64))
428 } else {
429 self.custom_formatter(move |n, _| {
430 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
431 format!("{sign}{:0>min_width$b}", n.abs() as i64)
432 })
433 }
434 .custom_parser(|s| i64::from_str_radix(s, 2).map(|n| n as f64).ok())
435 }
436
437 pub fn octal(self, min_width: usize, twos_complement: bool) -> Self {
457 assert!(
458 min_width > 0,
459 "Slider::octal: `min_width` must be greater than 0"
460 );
461 if twos_complement {
462 self.custom_formatter(move |n, _| format!("{:0>min_width$o}", n as i64))
463 } else {
464 self.custom_formatter(move |n, _| {
465 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
466 format!("{sign}{:0>min_width$o}", n.abs() as i64)
467 })
468 }
469 .custom_parser(|s| i64::from_str_radix(s, 8).map(|n| n as f64).ok())
470 }
471
472 pub fn hexadecimal(self, min_width: usize, twos_complement: bool, upper: bool) -> Self {
492 assert!(
493 min_width > 0,
494 "Slider::hexadecimal: `min_width` must be greater than 0"
495 );
496 match (twos_complement, upper) {
497 (true, true) => {
498 self.custom_formatter(move |n, _| format!("{:0>min_width$X}", n as i64))
499 }
500 (true, false) => {
501 self.custom_formatter(move |n, _| format!("{:0>min_width$x}", n as i64))
502 }
503 (false, true) => self.custom_formatter(move |n, _| {
504 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
505 format!("{sign}{:0>min_width$X}", n.abs() as i64)
506 }),
507 (false, false) => self.custom_formatter(move |n, _| {
508 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
509 format!("{sign}{:0>min_width$x}", n.abs() as i64)
510 }),
511 }
512 .custom_parser(|s| i64::from_str_radix(s, 16).map(|n| n as f64).ok())
513 }
514
515 pub fn integer(self) -> Self {
519 self.fixed_decimals(0).smallest_positive(1.0).step_by(1.0)
520 }
521
522 fn get_value(&mut self) -> f64 {
523 let value = get(&mut self.get_set_value);
524 if self.clamp_to_range {
525 let start = *self.range.start();
526 let end = *self.range.end();
527 value.clamp(start.min(end), start.max(end))
528 } else {
529 value
530 }
531 }
532
533 fn set_value(&mut self, mut value: f64) {
534 if self.clamp_to_range {
535 let start = *self.range.start();
536 let end = *self.range.end();
537 value = value.clamp(start.min(end), start.max(end));
538 }
539 if let Some(max_decimals) = self.max_decimals {
540 value = emath::round_to_decimals(value, max_decimals);
541 }
542 if let Some(step) = self.step {
543 let start = *self.range.start();
544 value = start + ((value - start) / step).round() * step;
545 }
546 set(&mut self.get_set_value, value);
547 }
548
549 fn range(&self) -> RangeInclusive<f64> {
550 self.range.clone()
551 }
552
553 fn value_from_position(&self, position: f32, position_range: Rangef) -> f64 {
555 let normalized = remap_clamp(position, position_range, 0.0..=1.0) as f64;
556 value_from_normalized(normalized, self.range(), &self.spec)
557 }
558
559 fn position_from_value(&self, value: f64, position_range: Rangef) -> f32 {
560 let normalized = normalized_from_value(value, self.range(), &self.spec);
561 lerp(position_range, normalized as f32)
562 }
563}
564
565impl<'a> Slider<'a> {
566 fn allocate_slider_space(&self, ui: &mut Ui, thickness: f32) -> Response {
568 let desired_size = match self.orientation {
569 SliderOrientation::Horizontal => vec2(ui.spacing().slider_width, thickness),
570 SliderOrientation::Vertical => vec2(thickness, ui.spacing().slider_width),
571 };
572 ui.allocate_response(desired_size, Sense::drag())
573 }
574
575 fn slider_ui(&mut self, ui: &Ui, response: &Response) {
577 let rect = &response.rect;
578 let handle_shape = self
579 .handle_shape
580 .unwrap_or_else(|| ui.style().visuals.handle_shape);
581 let position_range = self.position_range(rect, &handle_shape);
582
583 if let Some(pointer_position_2d) = response.interact_pointer_pos() {
584 let position = self.pointer_position(pointer_position_2d);
585 let new_value = if self.smart_aim {
586 let aim_radius = ui.input(|i| i.aim_radius());
587 emath::smart_aim::best_in_range_f64(
588 self.value_from_position(position - aim_radius, position_range),
589 self.value_from_position(position + aim_radius, position_range),
590 )
591 } else {
592 self.value_from_position(position, position_range)
593 };
594 self.set_value(new_value);
595 }
596
597 let mut decrement = 0usize;
598 let mut increment = 0usize;
599
600 if response.has_focus() {
601 ui.ctx().memory_mut(|m| {
602 m.set_focus_lock_filter(
603 response.id,
604 EventFilter {
605 horizontal_arrows: matches!(
608 self.orientation,
609 SliderOrientation::Horizontal
610 ),
611 vertical_arrows: matches!(self.orientation, SliderOrientation::Vertical),
612 ..Default::default()
613 },
614 );
615 });
616
617 let (dec_key, inc_key) = match self.orientation {
618 SliderOrientation::Horizontal => (Key::ArrowLeft, Key::ArrowRight),
619 SliderOrientation::Vertical => (Key::ArrowUp, Key::ArrowDown),
622 };
623
624 ui.input(|input| {
625 decrement += input.num_presses(dec_key);
626 increment += input.num_presses(inc_key);
627 });
628 }
629
630 #[cfg(feature = "accesskit")]
631 {
632 use accesskit::Action;
633 ui.input(|input| {
634 decrement += input.num_accesskit_action_requests(response.id, Action::Decrement);
635 increment += input.num_accesskit_action_requests(response.id, Action::Increment);
636 });
637 }
638
639 let kb_step = increment as f32 - decrement as f32;
640
641 if kb_step != 0.0 {
642 let ui_point_per_step = 1.0; let prev_value = self.get_value();
644 let prev_position = self.position_from_value(prev_value, position_range);
645 let new_position = prev_position + ui_point_per_step * kb_step;
646 let new_value = match self.step {
647 Some(step) => prev_value + (kb_step as f64 * step),
648 None if self.smart_aim => {
649 let aim_radius = 0.49 * ui_point_per_step; emath::smart_aim::best_in_range_f64(
651 self.value_from_position(new_position - aim_radius, position_range),
652 self.value_from_position(new_position + aim_radius, position_range),
653 )
654 }
655 _ => self.value_from_position(new_position, position_range),
656 };
657 self.set_value(new_value);
658 }
659
660 #[cfg(feature = "accesskit")]
661 {
662 use accesskit::{Action, ActionData};
663 ui.input(|input| {
664 for request in input.accesskit_action_requests(response.id, Action::SetValue) {
665 if let Some(ActionData::NumericValue(new_value)) = request.data {
666 self.set_value(new_value);
667 }
668 }
669 });
670 }
671
672 if ui.is_rect_visible(response.rect) {
674 let value = self.get_value();
675
676 let visuals = ui.style().interact(response);
677 let widget_visuals = &ui.visuals().widgets;
678 let spacing = &ui.style().spacing;
679
680 let rail_radius = (spacing.slider_rail_height / 2.0).at_least(0.0);
681 let rail_rect = self.rail_rect(rect, rail_radius);
682 let rounding = widget_visuals.inactive.rounding;
683
684 ui.painter()
685 .rect_filled(rail_rect, rounding, widget_visuals.inactive.bg_fill);
686
687 let position_1d = self.position_from_value(value, position_range);
688 let center = self.marker_center(position_1d, &rail_rect);
689
690 let trailing_fill = self
692 .trailing_fill
693 .unwrap_or_else(|| ui.visuals().slider_trailing_fill);
694
695 if trailing_fill {
697 let mut trailing_rail_rect = rail_rect;
698
699 match self.orientation {
701 SliderOrientation::Horizontal => {
702 trailing_rail_rect.max.x = center.x + rounding.nw;
703 }
704 SliderOrientation::Vertical => {
705 trailing_rail_rect.min.y = center.y - rounding.se;
706 }
707 };
708
709 ui.painter().rect_filled(
710 trailing_rail_rect,
711 rounding,
712 ui.visuals().selection.bg_fill,
713 );
714 }
715
716 let radius = self.handle_radius(rect);
717
718 let handle_shape = self
719 .handle_shape
720 .unwrap_or_else(|| ui.style().visuals.handle_shape);
721 match handle_shape {
722 style::HandleShape::Circle => {
723 ui.painter().add(epaint::CircleShape {
724 center,
725 radius: radius + visuals.expansion,
726 fill: visuals.bg_fill,
727 stroke: visuals.fg_stroke,
728 });
729 }
730 style::HandleShape::Rect { aspect_ratio } => {
731 let v = match self.orientation {
732 SliderOrientation::Horizontal => Vec2::new(radius * aspect_ratio, radius),
733 SliderOrientation::Vertical => Vec2::new(radius, radius * aspect_ratio),
734 };
735 let v = v + Vec2::splat(visuals.expansion);
736 let rect = Rect::from_center_size(center, 2.0 * v);
737 ui.painter()
738 .rect(rect, visuals.rounding, visuals.bg_fill, visuals.fg_stroke);
739 }
740 }
741 }
742 }
743
744 fn marker_center(&self, position_1d: f32, rail_rect: &Rect) -> Pos2 {
745 match self.orientation {
746 SliderOrientation::Horizontal => pos2(position_1d, rail_rect.center().y),
747 SliderOrientation::Vertical => pos2(rail_rect.center().x, position_1d),
748 }
749 }
750
751 fn pointer_position(&self, pointer_position_2d: Pos2) -> f32 {
752 match self.orientation {
753 SliderOrientation::Horizontal => pointer_position_2d.x,
754 SliderOrientation::Vertical => pointer_position_2d.y,
755 }
756 }
757
758 fn position_range(&self, rect: &Rect, handle_shape: &style::HandleShape) -> Rangef {
759 let handle_radius = self.handle_radius(rect);
760 let handle_radius = match handle_shape {
761 style::HandleShape::Circle => handle_radius,
762 style::HandleShape::Rect { aspect_ratio } => handle_radius * aspect_ratio,
763 };
764 match self.orientation {
765 SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius),
766 SliderOrientation::Vertical => rect.y_range().shrink(handle_radius).flip(),
769 }
770 }
771
772 fn rail_rect(&self, rect: &Rect, radius: f32) -> Rect {
773 match self.orientation {
774 SliderOrientation::Horizontal => Rect::from_min_max(
775 pos2(rect.left(), rect.center().y - radius),
776 pos2(rect.right(), rect.center().y + radius),
777 ),
778 SliderOrientation::Vertical => Rect::from_min_max(
779 pos2(rect.center().x - radius, rect.top()),
780 pos2(rect.center().x + radius, rect.bottom()),
781 ),
782 }
783 }
784
785 fn handle_radius(&self, rect: &Rect) -> f32 {
786 let limit = match self.orientation {
787 SliderOrientation::Horizontal => rect.height(),
788 SliderOrientation::Vertical => rect.width(),
789 };
790 limit / 2.5
791 }
792
793 fn value_ui(&mut self, ui: &mut Ui, position_range: Rangef) -> Response {
794 let change = ui.input(|input| {
796 input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32
797 - input.num_presses(Key::ArrowDown) as i32
798 - input.num_presses(Key::ArrowLeft) as i32
799 });
800
801 let any_change = change != 0;
802 let speed = if let (Some(step), true) = (self.step, any_change) {
803 step
805 } else {
806 self.drag_value_speed
807 .unwrap_or_else(|| self.current_gradient(position_range))
808 };
809
810 let mut value = self.get_value();
811 let response = ui.add({
812 let mut dv = DragValue::new(&mut value)
813 .speed(speed)
814 .range(self.range.clone())
815 .clamp_to_range(self.clamp_to_range)
816 .min_decimals(self.min_decimals)
817 .max_decimals_opt(self.max_decimals)
818 .suffix(self.suffix.clone())
819 .prefix(self.prefix.clone());
820 if let Some(fmt) = &self.custom_formatter {
821 dv = dv.custom_formatter(fmt);
822 };
823 if let Some(parser) = &self.custom_parser {
824 dv = dv.custom_parser(parser);
825 }
826 dv
827 });
828 if value != self.get_value() {
829 self.set_value(value);
830 }
831 response
832 }
833
834 fn current_gradient(&mut self, position_range: Rangef) -> f64 {
836 let value = self.get_value();
838 let value_from_pos = |position: f32| self.value_from_position(position, position_range);
839 let pos_from_value = |value: f64| self.position_from_value(value, position_range);
840 let left_value = value_from_pos(pos_from_value(value) - 0.5);
841 let right_value = value_from_pos(pos_from_value(value) + 0.5);
842 right_value - left_value
843 }
844
845 fn add_contents(&mut self, ui: &mut Ui) -> Response {
846 let old_value = self.get_value();
847
848 let thickness = ui
849 .text_style_height(&TextStyle::Body)
850 .at_least(ui.spacing().interact_size.y);
851 let mut response = self.allocate_slider_space(ui, thickness);
852 self.slider_ui(ui, &response);
853
854 let value = self.get_value();
855 response.changed = value != old_value;
856 response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text()));
857
858 #[cfg(feature = "accesskit")]
859 ui.ctx().accesskit_node_builder(response.id, |builder| {
860 use accesskit::Action;
861 builder.set_min_numeric_value(*self.range.start());
862 builder.set_max_numeric_value(*self.range.end());
863 if let Some(step) = self.step {
864 builder.set_numeric_value_step(step);
865 }
866 builder.add_action(Action::SetValue);
867
868 let clamp_range = if self.clamp_to_range {
869 self.range()
870 } else {
871 f64::NEG_INFINITY..=f64::INFINITY
872 };
873 if value < *clamp_range.end() {
874 builder.add_action(Action::Increment);
875 }
876 if value > *clamp_range.start() {
877 builder.add_action(Action::Decrement);
878 }
879 });
880
881 let slider_response = response.clone();
882
883 let value_response = if self.show_value {
884 let handle_shape = self
885 .handle_shape
886 .unwrap_or_else(|| ui.style().visuals.handle_shape);
887 let position_range = self.position_range(&response.rect, &handle_shape);
888 let value_response = self.value_ui(ui, position_range);
889 if value_response.gained_focus()
890 || value_response.has_focus()
891 || value_response.lost_focus()
892 {
893 response = value_response.union(response);
896 } else {
897 response = response.union(value_response.clone());
899 }
900 Some(value_response)
901 } else {
902 None
903 };
904
905 if !self.text.is_empty() {
906 let label_response =
907 ui.add(Label::new(self.text.clone()).wrap_mode(TextWrapMode::Extend));
908 slider_response.labelled_by(label_response.id);
913 if let Some(value_response) = value_response {
914 value_response.labelled_by(label_response.id);
915 }
916 }
917
918 response
919 }
920}
921
922impl<'a> Widget for Slider<'a> {
923 fn ui(mut self, ui: &mut Ui) -> Response {
924 let inner_response = match self.orientation {
925 SliderOrientation::Horizontal => ui.horizontal(|ui| self.add_contents(ui)),
926 SliderOrientation::Vertical => ui.vertical(|ui| self.add_contents(ui)),
927 };
928
929 inner_response.inner | inner_response.response
930 }
931}
932
933use std::f64::INFINITY;
940
941const INF_RANGE_MAGNITUDE: f64 = 10.0;
944
945fn value_from_normalized(normalized: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
946 let (min, max) = (*range.start(), *range.end());
947
948 if min.is_nan() || max.is_nan() {
949 f64::NAN
950 } else if min == max {
951 min
952 } else if min > max {
953 value_from_normalized(1.0 - normalized, max..=min, spec)
954 } else if normalized <= 0.0 {
955 min
956 } else if normalized >= 1.0 {
957 max
958 } else if spec.logarithmic {
959 if max <= 0.0 {
960 -value_from_normalized(normalized, -min..=-max, spec)
962 } else if 0.0 <= min {
963 let (min_log, max_log) = range_log10(min, max, spec);
964 let log = lerp(min_log..=max_log, normalized);
965 10.0_f64.powf(log)
966 } else {
967 assert!(min < 0.0 && 0.0 < max);
968 let zero_cutoff = logarithmic_zero_cutoff(min, max);
969 if normalized < zero_cutoff {
970 value_from_normalized(
972 remap(normalized, 0.0..=zero_cutoff, 0.0..=1.0),
973 min..=0.0,
974 spec,
975 )
976 } else {
977 value_from_normalized(
979 remap(normalized, zero_cutoff..=1.0, 0.0..=1.0),
980 0.0..=max,
981 spec,
982 )
983 }
984 }
985 } else {
986 debug_assert!(
987 min.is_finite() && max.is_finite(),
988 "You should use a logarithmic range"
989 );
990 lerp(range, normalized.clamp(0.0, 1.0))
991 }
992}
993
994fn normalized_from_value(value: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
995 let (min, max) = (*range.start(), *range.end());
996
997 if min.is_nan() || max.is_nan() {
998 f64::NAN
999 } else if min == max {
1000 0.5 } else if min > max {
1002 1.0 - normalized_from_value(value, max..=min, spec)
1003 } else if value <= min {
1004 0.0
1005 } else if value >= max {
1006 1.0
1007 } else if spec.logarithmic {
1008 if max <= 0.0 {
1009 normalized_from_value(-value, -min..=-max, spec)
1011 } else if 0.0 <= min {
1012 let (min_log, max_log) = range_log10(min, max, spec);
1013 let value_log = value.log10();
1014 remap_clamp(value_log, min_log..=max_log, 0.0..=1.0)
1015 } else {
1016 assert!(min < 0.0 && 0.0 < max);
1017 let zero_cutoff = logarithmic_zero_cutoff(min, max);
1018 if value < 0.0 {
1019 remap(
1021 normalized_from_value(value, min..=0.0, spec),
1022 0.0..=1.0,
1023 0.0..=zero_cutoff,
1024 )
1025 } else {
1026 remap(
1028 normalized_from_value(value, 0.0..=max, spec),
1029 0.0..=1.0,
1030 zero_cutoff..=1.0,
1031 )
1032 }
1033 }
1034 } else {
1035 debug_assert!(
1036 min.is_finite() && max.is_finite(),
1037 "You should use a logarithmic range"
1038 );
1039 remap_clamp(value, range, 0.0..=1.0)
1040 }
1041}
1042
1043fn range_log10(min: f64, max: f64, spec: &SliderSpec) -> (f64, f64) {
1044 assert!(spec.logarithmic);
1045 assert!(min <= max);
1046
1047 if min == 0.0 && max == INFINITY {
1048 (spec.smallest_positive.log10(), INF_RANGE_MAGNITUDE)
1049 } else if min == 0.0 {
1050 if spec.smallest_positive < max {
1051 (spec.smallest_positive.log10(), max.log10())
1052 } else {
1053 (max.log10() - INF_RANGE_MAGNITUDE, max.log10())
1054 }
1055 } else if max == INFINITY {
1056 if min < spec.largest_finite {
1057 (min.log10(), spec.largest_finite.log10())
1058 } else {
1059 (min.log10(), min.log10() + INF_RANGE_MAGNITUDE)
1060 }
1061 } else {
1062 (min.log10(), max.log10())
1063 }
1064}
1065
1066fn logarithmic_zero_cutoff(min: f64, max: f64) -> f64 {
1069 assert!(min < 0.0 && 0.0 < max);
1070
1071 let min_magnitude = if min == -INFINITY {
1072 INF_RANGE_MAGNITUDE
1073 } else {
1074 min.abs().log10().abs()
1075 };
1076 let max_magnitude = if max == INFINITY {
1077 INF_RANGE_MAGNITUDE
1078 } else {
1079 max.log10().abs()
1080 };
1081
1082 let cutoff = min_magnitude / (min_magnitude + max_magnitude);
1083 debug_assert!(0.0 <= cutoff && cutoff <= 1.0);
1084 cutoff
1085}