1#![expect(clippy::needless_pass_by_value)] use core::ops::RangeInclusive;
4
5use crate::{
6 Color32, DragValue, EventFilter, Key, Label, MINUS_CHAR_STR, NumExt as _, Pos2, Rangef, Rect,
7 Response, Sense, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, emath,
8 epaint, lerp, pos2, remap, remap_clamp, style, style::HandleShape, vec2,
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 update_while_editing: bool,
121}
122
123impl<'a> Slider<'a> {
124 pub fn new<Num: emath::Numeric>(
129 value: &'a mut Num,
130 range: impl Into<RangeInclusive<Num>>,
131 ) -> Self {
132 let range = range.into();
133 let range_f64 = range.start().to_f64()..=range.end().to_f64();
134 let slf = Self::from_get_set(range_f64, move |v: Option<f64>| {
135 if let Some(v) = v {
136 *value = Num::from_f64(v);
137 }
138 value.to_f64()
139 });
140
141 if Num::INTEGRAL { slf.integer() } else { slf }
142 }
143
144 pub fn from_get_set(
145 range: RangeInclusive<f64>,
146 get_set_value: impl 'a + FnMut(Option<f64>) -> f64,
147 ) -> Self {
148 Self {
149 get_set_value: Box::new(get_set_value),
150 range,
151 spec: SliderSpec {
152 logarithmic: false,
153 smallest_positive: 1e-6,
154 largest_finite: f64::INFINITY,
155 },
156 clamping: SliderClamping::default(),
157 smart_aim: true,
158 show_value: true,
159 orientation: SliderOrientation::Horizontal,
160 prefix: Default::default(),
161 suffix: Default::default(),
162 text: Default::default(),
163 step: None,
164 drag_value_speed: None,
165 min_decimals: 0,
166 max_decimals: None,
167 custom_formatter: None,
168 custom_parser: None,
169 trailing_fill: None,
170 handle_shape: None,
171 update_while_editing: true,
172 }
173 }
174
175 #[inline]
178 pub fn show_value(mut self, show_value: bool) -> Self {
179 self.show_value = show_value;
180 self
181 }
182
183 #[inline]
185 pub fn prefix(mut self, prefix: impl ToString) -> Self {
186 self.prefix = prefix.to_string();
187 self
188 }
189
190 #[inline]
192 pub fn suffix(mut self, suffix: impl ToString) -> Self {
193 self.suffix = suffix.to_string();
194 self
195 }
196
197 #[inline]
199 pub fn text(mut self, text: impl Into<WidgetText>) -> Self {
200 self.text = text.into();
201 self
202 }
203
204 #[inline]
205 pub fn text_color(mut self, text_color: Color32) -> Self {
206 self.text = self.text.color(text_color);
207 self
208 }
209
210 #[inline]
212 pub fn orientation(mut self, orientation: SliderOrientation) -> Self {
213 self.orientation = orientation;
214 self
215 }
216
217 #[inline]
219 pub fn vertical(mut self) -> Self {
220 self.orientation = SliderOrientation::Vertical;
221 self
222 }
223
224 #[inline]
229 pub fn logarithmic(mut self, logarithmic: bool) -> Self {
230 self.spec.logarithmic = logarithmic;
231 self
232 }
233
234 #[inline]
238 pub fn smallest_positive(mut self, smallest_positive: f64) -> Self {
239 self.spec.smallest_positive = smallest_positive;
240 self
241 }
242
243 #[inline]
247 pub fn largest_finite(mut self, largest_finite: f64) -> Self {
248 self.spec.largest_finite = largest_finite;
249 self
250 }
251
252 #[inline]
290 pub fn clamping(mut self, clamping: SliderClamping) -> Self {
291 self.clamping = clamping;
292 self
293 }
294
295 #[inline]
298 pub fn smart_aim(mut self, smart_aim: bool) -> Self {
299 self.smart_aim = smart_aim;
300 self
301 }
302
303 #[inline]
310 pub fn step_by(mut self, step: f64) -> Self {
311 self.step = if step == 0.0 { None } else { Some(step) };
312 self
313 }
314
315 #[inline]
324 pub fn drag_value_speed(mut self, drag_value_speed: f64) -> Self {
325 self.drag_value_speed = Some(drag_value_speed);
326 self
327 }
328
329 #[inline]
335 pub fn min_decimals(mut self, min_decimals: usize) -> Self {
336 self.min_decimals = min_decimals;
337 self
338 }
339
340 #[inline]
347 pub fn max_decimals(mut self, max_decimals: usize) -> Self {
348 self.max_decimals = Some(max_decimals);
349 self
350 }
351
352 #[inline]
353 pub fn max_decimals_opt(mut self, max_decimals: Option<usize>) -> Self {
354 self.max_decimals = max_decimals;
355 self
356 }
357
358 #[inline]
364 pub fn fixed_decimals(mut self, num_decimals: usize) -> Self {
365 self.min_decimals = num_decimals;
366 self.max_decimals = Some(num_decimals);
367 self
368 }
369
370 #[inline]
377 pub fn trailing_fill(mut self, trailing_fill: bool) -> Self {
378 self.trailing_fill = Some(trailing_fill);
379 self
380 }
381
382 #[inline]
387 pub fn handle_shape(mut self, handle_shape: HandleShape) -> Self {
388 self.handle_shape = Some(handle_shape);
389 self
390 }
391
392 pub fn custom_formatter(
430 mut self,
431 formatter: impl 'a + Fn(f64, RangeInclusive<usize>) -> String,
432 ) -> Self {
433 self.custom_formatter = Some(Box::new(formatter));
434 self
435 }
436
437 #[inline]
473 pub fn custom_parser(mut self, parser: impl 'a + Fn(&str) -> Option<f64>) -> Self {
474 self.custom_parser = Some(Box::new(parser));
475 self
476 }
477
478 pub fn binary(self, min_width: usize, twos_complement: bool) -> Self {
498 assert!(
499 min_width > 0,
500 "Slider::binary: `min_width` must be greater than 0"
501 );
502 if twos_complement {
503 self.custom_formatter(move |n, _| format!("{:0>min_width$b}", n as i64))
504 } else {
505 self.custom_formatter(move |n, _| {
506 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
507 format!("{sign}{:0>min_width$b}", n.abs() as i64)
508 })
509 }
510 .custom_parser(|s| i64::from_str_radix(s, 2).map(|n| n as f64).ok())
511 }
512
513 pub fn octal(self, min_width: usize, twos_complement: bool) -> Self {
533 assert!(
534 min_width > 0,
535 "Slider::octal: `min_width` must be greater than 0"
536 );
537 if twos_complement {
538 self.custom_formatter(move |n, _| format!("{:0>min_width$o}", n as i64))
539 } else {
540 self.custom_formatter(move |n, _| {
541 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
542 format!("{sign}{:0>min_width$o}", n.abs() as i64)
543 })
544 }
545 .custom_parser(|s| i64::from_str_radix(s, 8).map(|n| n as f64).ok())
546 }
547
548 pub fn hexadecimal(self, min_width: usize, twos_complement: bool, upper: bool) -> Self {
568 assert!(
569 min_width > 0,
570 "Slider::hexadecimal: `min_width` must be greater than 0"
571 );
572 match (twos_complement, upper) {
573 (true, true) => {
574 self.custom_formatter(move |n, _| format!("{:0>min_width$X}", n as i64))
575 }
576 (true, false) => {
577 self.custom_formatter(move |n, _| format!("{:0>min_width$x}", n as i64))
578 }
579 (false, true) => self.custom_formatter(move |n, _| {
580 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
581 format!("{sign}{:0>min_width$X}", n.abs() as i64)
582 }),
583 (false, false) => self.custom_formatter(move |n, _| {
584 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
585 format!("{sign}{:0>min_width$x}", n.abs() as i64)
586 }),
587 }
588 .custom_parser(|s| i64::from_str_radix(s, 16).map(|n| n as f64).ok())
589 }
590
591 pub fn integer(self) -> Self {
595 self.fixed_decimals(0).smallest_positive(1.0).step_by(1.0)
596 }
597
598 fn get_value(&mut self) -> f64 {
599 let value = get(&mut self.get_set_value);
600 if self.clamping == SliderClamping::Always {
601 clamp_value_to_range(value, self.range.clone())
602 } else {
603 value
604 }
605 }
606
607 fn set_value(&mut self, mut value: f64) {
608 if self.clamping != SliderClamping::Never {
609 value = clamp_value_to_range(value, self.range.clone());
610 }
611
612 if let Some(step) = self.step {
613 let start = *self.range.start();
614 value = start + ((value - start) / step).round() * step;
615 }
616 if let Some(max_decimals) = self.max_decimals {
617 value = emath::round_to_decimals(value, max_decimals);
618 }
619 set(&mut self.get_set_value, value);
620 }
621
622 fn range(&self) -> RangeInclusive<f64> {
623 self.range.clone()
624 }
625
626 fn value_from_position(&self, position: f32, position_range: Rangef) -> f64 {
628 let normalized = remap_clamp(position, position_range, 0.0..=1.0) as f64;
629 value_from_normalized(normalized, self.range(), &self.spec)
630 }
631
632 fn position_from_value(&self, value: f64, position_range: Rangef) -> f32 {
633 let normalized = normalized_from_value(value, self.range(), &self.spec);
634 lerp(position_range, normalized as f32)
635 }
636
637 #[inline]
642 pub fn update_while_editing(mut self, update: bool) -> Self {
643 self.update_while_editing = update;
644 self
645 }
646}
647
648impl Slider<'_> {
649 fn allocate_slider_space(&self, ui: &mut Ui, thickness: f32) -> Response {
651 let desired_size = match self.orientation {
652 SliderOrientation::Horizontal => vec2(ui.spacing().slider_width, thickness),
653 SliderOrientation::Vertical => vec2(thickness, ui.spacing().slider_width),
654 };
655 ui.allocate_response(desired_size, Sense::drag())
656 }
657
658 fn slider_ui(&mut self, ui: &Ui, response: &Response) {
660 let rect = &response.rect;
661 let handle_shape = self
662 .handle_shape
663 .unwrap_or_else(|| ui.style().visuals.handle_shape);
664 let position_range = self.position_range(rect, &handle_shape);
665
666 if let Some(pointer_position_2d) = response.interact_pointer_pos() {
667 let position = self.pointer_position(pointer_position_2d);
668 let new_value = if self.smart_aim {
669 let aim_radius = ui.input(|i| i.aim_radius());
670 emath::smart_aim::best_in_range_f64(
671 self.value_from_position(position - aim_radius, position_range),
672 self.value_from_position(position + aim_radius, position_range),
673 )
674 } else {
675 self.value_from_position(position, position_range)
676 };
677 self.set_value(new_value);
678 }
679
680 let mut decrement = 0usize;
681 let mut increment = 0usize;
682
683 if response.has_focus() {
684 ui.memory_mut(|m| {
685 m.set_focus_lock_filter(
686 response.id,
687 EventFilter {
688 horizontal_arrows: matches!(
691 self.orientation,
692 SliderOrientation::Horizontal
693 ),
694 vertical_arrows: matches!(self.orientation, SliderOrientation::Vertical),
695 ..Default::default()
696 },
697 );
698 });
699
700 let (dec_key, inc_key) = match self.orientation {
701 SliderOrientation::Horizontal => (Key::ArrowLeft, Key::ArrowRight),
702 SliderOrientation::Vertical => (Key::ArrowUp, Key::ArrowDown),
705 };
706
707 ui.input(|input| {
708 decrement += input.num_presses(dec_key);
709 increment += input.num_presses(inc_key);
710 });
711 }
712
713 ui.input(|input| {
714 use accesskit::Action;
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 let kb_step = increment as f32 - decrement as f32;
720
721 if kb_step != 0.0 {
722 let ui_point_per_step = 1.0; let prev_value = self.get_value();
724 let prev_position = self.position_from_value(prev_value, position_range);
725 let new_position = prev_position + ui_point_per_step * kb_step;
726 let mut new_value = match self.step {
727 Some(step) => prev_value + (kb_step as f64 * step),
728 None if self.smart_aim => {
729 let aim_radius = 0.49 * ui_point_per_step; emath::smart_aim::best_in_range_f64(
731 self.value_from_position(new_position - aim_radius, position_range),
732 self.value_from_position(new_position + aim_radius, position_range),
733 )
734 }
735 _ => self.value_from_position(new_position, position_range),
736 };
737 if let Some(max_decimals) = self.max_decimals {
738 let min_increment = 1.0 / (10.0_f64.powi(max_decimals as i32));
742 new_value = if new_value > prev_value {
743 f64::max(new_value, prev_value + min_increment * 1.001)
744 } else if new_value < prev_value {
745 f64::min(new_value, prev_value - min_increment * 1.001)
746 } else {
747 new_value
748 };
749 }
750 self.set_value(new_value);
751 }
752
753 ui.input(|input| {
754 use accesskit::{Action, ActionData};
755 for request in input.accesskit_action_requests(response.id, Action::SetValue) {
756 if let Some(ActionData::NumericValue(new_value)) = request.data {
757 self.set_value(new_value);
758 }
759 }
760 });
761
762 if ui.is_rect_visible(response.rect) {
764 let value = self.get_value();
765
766 let visuals = ui.style().interact(response);
767 let widget_visuals = &ui.visuals().widgets;
768 let spacing = &ui.style().spacing;
769
770 let rail_radius = (spacing.slider_rail_height / 2.0).at_least(0.0);
771 let rail_rect = self.rail_rect(rect, rail_radius);
772 let corner_radius = widget_visuals.inactive.corner_radius;
773
774 ui.painter()
775 .rect_filled(rail_rect, corner_radius, widget_visuals.inactive.bg_fill);
776
777 let position_1d = self.position_from_value(value, position_range);
778 let center = self.marker_center(position_1d, &rail_rect);
779
780 let trailing_fill = self
782 .trailing_fill
783 .unwrap_or_else(|| ui.visuals().slider_trailing_fill);
784
785 if trailing_fill {
787 let mut trailing_rail_rect = rail_rect;
788
789 match self.orientation {
791 SliderOrientation::Horizontal => {
792 trailing_rail_rect.max.x = center.x + corner_radius.nw as f32;
793 }
794 SliderOrientation::Vertical => {
795 trailing_rail_rect.min.y = center.y - corner_radius.se as f32;
796 }
797 }
798
799 ui.painter().rect_filled(
800 trailing_rail_rect,
801 corner_radius,
802 ui.visuals().selection.bg_fill,
803 );
804 }
805
806 let radius = self.handle_radius(rect);
807
808 let handle_shape = self
809 .handle_shape
810 .unwrap_or_else(|| ui.style().visuals.handle_shape);
811 match handle_shape {
812 style::HandleShape::Circle => {
813 ui.painter().add(epaint::CircleShape {
814 center,
815 radius: radius + visuals.expansion,
816 fill: visuals.bg_fill,
817 stroke: visuals.fg_stroke,
818 });
819 }
820 style::HandleShape::Rect { aspect_ratio } => {
821 let v = match self.orientation {
822 SliderOrientation::Horizontal => Vec2::new(radius * aspect_ratio, radius),
823 SliderOrientation::Vertical => Vec2::new(radius, radius * aspect_ratio),
824 };
825 let v = v + Vec2::splat(visuals.expansion);
826 let rect = Rect::from_center_size(center, 2.0 * v);
827 ui.painter().rect(
828 rect,
829 visuals.corner_radius,
830 visuals.bg_fill,
831 visuals.fg_stroke,
832 epaint::StrokeKind::Inside,
833 );
834 }
835 }
836 }
837 }
838
839 fn marker_center(&self, position_1d: f32, rail_rect: &Rect) -> Pos2 {
840 match self.orientation {
841 SliderOrientation::Horizontal => pos2(position_1d, rail_rect.center().y),
842 SliderOrientation::Vertical => pos2(rail_rect.center().x, position_1d),
843 }
844 }
845
846 fn pointer_position(&self, pointer_position_2d: Pos2) -> f32 {
847 match self.orientation {
848 SliderOrientation::Horizontal => pointer_position_2d.x,
849 SliderOrientation::Vertical => pointer_position_2d.y,
850 }
851 }
852
853 fn position_range(&self, rect: &Rect, handle_shape: &style::HandleShape) -> Rangef {
854 let handle_radius = self.handle_radius(rect);
855 let handle_radius = match handle_shape {
856 style::HandleShape::Circle => handle_radius,
857 style::HandleShape::Rect { aspect_ratio } => handle_radius * aspect_ratio,
858 };
859 match self.orientation {
860 SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius),
861 SliderOrientation::Vertical => rect.y_range().shrink(handle_radius).flip(),
864 }
865 }
866
867 fn rail_rect(&self, rect: &Rect, radius: f32) -> Rect {
868 match self.orientation {
869 SliderOrientation::Horizontal => Rect::from_min_max(
870 pos2(rect.left(), rect.center().y - radius),
871 pos2(rect.right(), rect.center().y + radius),
872 ),
873 SliderOrientation::Vertical => Rect::from_min_max(
874 pos2(rect.center().x - radius, rect.top()),
875 pos2(rect.center().x + radius, rect.bottom()),
876 ),
877 }
878 }
879
880 fn handle_radius(&self, rect: &Rect) -> f32 {
881 let limit = match self.orientation {
882 SliderOrientation::Horizontal => rect.height(),
883 SliderOrientation::Vertical => rect.width(),
884 };
885 limit / 2.5
886 }
887
888 fn value_ui(&mut self, ui: &mut Ui, position_range: Rangef) -> Response {
889 let change = ui.input(|input| {
891 input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32
892 - input.num_presses(Key::ArrowDown) as i32
893 - input.num_presses(Key::ArrowLeft) as i32
894 });
895
896 let any_change = change != 0;
897 let speed = if let (Some(step), true) = (self.step, any_change) {
898 step
900 } else {
901 self.drag_value_speed
902 .unwrap_or_else(|| self.current_gradient(position_range))
903 };
904
905 let mut value = self.get_value();
906 let response = ui.add({
907 let mut dv = DragValue::new(&mut value)
908 .speed(speed)
909 .min_decimals(self.min_decimals)
910 .max_decimals_opt(self.max_decimals)
911 .suffix(self.suffix.clone())
912 .prefix(self.prefix.clone())
913 .update_while_editing(self.update_while_editing);
914
915 match self.clamping {
916 SliderClamping::Never => {}
917 SliderClamping::Edits => {
918 dv = dv.range(self.range.clone()).clamp_existing_to_range(false);
919 }
920 SliderClamping::Always => {
921 dv = dv.range(self.range.clone()).clamp_existing_to_range(true);
922 }
923 }
924
925 if let Some(fmt) = &self.custom_formatter {
926 dv = dv.custom_formatter(fmt);
927 }
928 if let Some(parser) = &self.custom_parser {
929 dv = dv.custom_parser(parser);
930 }
931 dv
932 });
933 if value != self.get_value() {
934 self.set_value(value);
935 }
936 response
937 }
938
939 fn current_gradient(&mut self, position_range: Rangef) -> f64 {
941 let value = self.get_value();
943 let value_from_pos = |position: f32| self.value_from_position(position, position_range);
944 let pos_from_value = |value: f64| self.position_from_value(value, position_range);
945 let left_value = value_from_pos(pos_from_value(value) - 0.5);
946 let right_value = value_from_pos(pos_from_value(value) + 0.5);
947 right_value - left_value
948 }
949
950 fn add_contents(&mut self, ui: &mut Ui) -> Response {
951 let old_value = self.get_value();
952
953 if self.clamping == SliderClamping::Always {
954 self.set_value(old_value);
955 }
956
957 let thickness = ui
958 .text_style_height(&TextStyle::Body)
959 .at_least(ui.spacing().interact_size.y);
960 let mut response = self.allocate_slider_space(ui, thickness);
961 self.slider_ui(ui, &response);
962
963 let value = self.get_value();
964 if value != old_value {
965 response.mark_changed();
966 }
967 response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text()));
968
969 ui.ctx().accesskit_node_builder(response.id, |builder| {
970 use accesskit::Action;
971 builder.set_min_numeric_value(*self.range.start());
972 builder.set_max_numeric_value(*self.range.end());
973 if let Some(step) = self.step {
974 builder.set_numeric_value_step(step);
975 }
976 builder.add_action(Action::SetValue);
977
978 let clamp_range = if self.clamping == SliderClamping::Never {
979 f64::NEG_INFINITY..=f64::INFINITY
980 } else {
981 self.range()
982 };
983 if value < *clamp_range.end() {
984 builder.add_action(Action::Increment);
985 }
986 if value > *clamp_range.start() {
987 builder.add_action(Action::Decrement);
988 }
989 });
990
991 let slider_response = response.clone();
992
993 let value_response = if self.show_value {
994 let handle_shape = self
995 .handle_shape
996 .unwrap_or_else(|| ui.style().visuals.handle_shape);
997 let position_range = self.position_range(&response.rect, &handle_shape);
998 let value_response = self.value_ui(ui, position_range);
999 if value_response.gained_focus()
1000 || value_response.has_focus()
1001 || value_response.lost_focus()
1002 {
1003 response = value_response.union(response);
1006 } else {
1007 response = response.union(value_response.clone());
1009 }
1010 Some(value_response)
1011 } else {
1012 None
1013 };
1014
1015 if !self.text.is_empty() {
1016 let label_response =
1017 ui.add(Label::new(self.text.clone()).wrap_mode(TextWrapMode::Extend));
1018 slider_response.labelled_by(label_response.id);
1023 if let Some(value_response) = value_response {
1024 value_response.labelled_by(label_response.id);
1025 }
1026 }
1027
1028 response
1029 }
1030}
1031
1032impl Widget for Slider<'_> {
1033 fn ui(mut self, ui: &mut Ui) -> Response {
1034 let inner_response = match self.orientation {
1035 SliderOrientation::Horizontal => ui.horizontal(|ui| self.add_contents(ui)),
1036 SliderOrientation::Vertical => ui.vertical(|ui| self.add_contents(ui)),
1037 };
1038
1039 inner_response.inner | inner_response.response
1040 }
1041}
1042
1043const INFINITY: f64 = f64::INFINITY;
1050
1051const INF_RANGE_MAGNITUDE: f64 = 10.0;
1054
1055fn value_from_normalized(normalized: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
1056 let (min, max) = (*range.start(), *range.end());
1057
1058 if min.is_nan() || max.is_nan() {
1059 f64::NAN
1060 } else if min == max {
1061 min
1062 } else if min > max {
1063 value_from_normalized(1.0 - normalized, max..=min, spec)
1064 } else if normalized <= 0.0 {
1065 min
1066 } else if normalized >= 1.0 {
1067 max
1068 } else if spec.logarithmic {
1069 if max <= 0.0 {
1070 -value_from_normalized(normalized, -min..=-max, spec)
1072 } else if 0.0 <= min {
1073 let (min_log, max_log) = range_log10(min, max, spec);
1074 let log = lerp(min_log..=max_log, normalized);
1075 10.0_f64.powf(log)
1076 } else {
1077 assert!(
1078 min < 0.0 && 0.0 < max,
1079 "min should be negative and max positive, but got min={min} and max={max}"
1080 );
1081 let zero_cutoff = logarithmic_zero_cutoff(min, max);
1082 if normalized < zero_cutoff {
1083 value_from_normalized(
1085 remap(normalized, 0.0..=zero_cutoff, 0.0..=1.0),
1086 min..=0.0,
1087 spec,
1088 )
1089 } else {
1090 value_from_normalized(
1092 remap(normalized, zero_cutoff..=1.0, 0.0..=1.0),
1093 0.0..=max,
1094 spec,
1095 )
1096 }
1097 }
1098 } else {
1099 debug_assert!(
1100 min.is_finite() && max.is_finite(),
1101 "You should use a logarithmic range"
1102 );
1103 lerp(range, normalized.clamp(0.0, 1.0))
1104 }
1105}
1106
1107fn normalized_from_value(value: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
1108 let (min, max) = (*range.start(), *range.end());
1109
1110 if min.is_nan() || max.is_nan() {
1111 f64::NAN
1112 } else if min == max {
1113 0.5 } else if min > max {
1115 1.0 - normalized_from_value(value, max..=min, spec)
1116 } else if value <= min {
1117 0.0
1118 } else if value >= max {
1119 1.0
1120 } else if spec.logarithmic {
1121 if max <= 0.0 {
1122 normalized_from_value(-value, -min..=-max, spec)
1124 } else if 0.0 <= min {
1125 let (min_log, max_log) = range_log10(min, max, spec);
1126 let value_log = value.log10();
1127 remap_clamp(value_log, min_log..=max_log, 0.0..=1.0)
1128 } else {
1129 assert!(
1130 min < 0.0 && 0.0 < max,
1131 "min should be negative and max positive, but got min={min} and max={max}"
1132 );
1133 let zero_cutoff = logarithmic_zero_cutoff(min, max);
1134 if value < 0.0 {
1135 remap(
1137 normalized_from_value(value, min..=0.0, spec),
1138 0.0..=1.0,
1139 0.0..=zero_cutoff,
1140 )
1141 } else {
1142 remap(
1144 normalized_from_value(value, 0.0..=max, spec),
1145 0.0..=1.0,
1146 zero_cutoff..=1.0,
1147 )
1148 }
1149 }
1150 } else {
1151 debug_assert!(
1152 min.is_finite() && max.is_finite(),
1153 "You should use a logarithmic range"
1154 );
1155 remap_clamp(value, range, 0.0..=1.0)
1156 }
1157}
1158
1159fn range_log10(min: f64, max: f64, spec: &SliderSpec) -> (f64, f64) {
1160 assert!(spec.logarithmic, "spec must be logarithmic");
1161 assert!(
1162 min <= max,
1163 "min must be less than or equal to max, but was min={min} and max={max}"
1164 );
1165
1166 if min == 0.0 && max == INFINITY {
1167 (spec.smallest_positive.log10(), INF_RANGE_MAGNITUDE)
1168 } else if min == 0.0 {
1169 if spec.smallest_positive < max {
1170 (spec.smallest_positive.log10(), max.log10())
1171 } else {
1172 (max.log10() - INF_RANGE_MAGNITUDE, max.log10())
1173 }
1174 } else if max == INFINITY {
1175 if min < spec.largest_finite {
1176 (min.log10(), spec.largest_finite.log10())
1177 } else {
1178 (min.log10(), min.log10() + INF_RANGE_MAGNITUDE)
1179 }
1180 } else {
1181 (min.log10(), max.log10())
1182 }
1183}
1184
1185fn logarithmic_zero_cutoff(min: f64, max: f64) -> f64 {
1188 assert!(
1189 min < 0.0 && 0.0 < max,
1190 "min must be negative and max positive, but got min={min} and max={max}"
1191 );
1192
1193 let min_magnitude = if min == -INFINITY {
1194 INF_RANGE_MAGNITUDE
1195 } else {
1196 min.abs().log10().abs()
1197 };
1198 let max_magnitude = if max == INFINITY {
1199 INF_RANGE_MAGNITUDE
1200 } else {
1201 max.log10().abs()
1202 };
1203
1204 let cutoff = min_magnitude / (min_magnitude + max_magnitude);
1205 debug_assert!(
1206 0.0 <= cutoff && cutoff <= 1.0,
1207 "Bad cutoff {cutoff:?} for min {min:?} and max {max:?}"
1208 );
1209 cutoff
1210}