1#![expect(clippy::needless_pass_by_value)] use std::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>(value: &'a mut Num, range: RangeInclusive<Num>) -> Self {
129 let range_f64 = range.start().to_f64()..=range.end().to_f64();
130 let slf = Self::from_get_set(range_f64, move |v: Option<f64>| {
131 if let Some(v) = v {
132 *value = Num::from_f64(v);
133 }
134 value.to_f64()
135 });
136
137 if Num::INTEGRAL { slf.integer() } else { slf }
138 }
139
140 pub fn from_get_set(
141 range: RangeInclusive<f64>,
142 get_set_value: impl 'a + FnMut(Option<f64>) -> f64,
143 ) -> Self {
144 Self {
145 get_set_value: Box::new(get_set_value),
146 range,
147 spec: SliderSpec {
148 logarithmic: false,
149 smallest_positive: 1e-6,
150 largest_finite: f64::INFINITY,
151 },
152 clamping: SliderClamping::default(),
153 smart_aim: true,
154 show_value: true,
155 orientation: SliderOrientation::Horizontal,
156 prefix: Default::default(),
157 suffix: Default::default(),
158 text: Default::default(),
159 step: None,
160 drag_value_speed: None,
161 min_decimals: 0,
162 max_decimals: None,
163 custom_formatter: None,
164 custom_parser: None,
165 trailing_fill: None,
166 handle_shape: None,
167 update_while_editing: true,
168 }
169 }
170
171 #[inline]
174 pub fn show_value(mut self, show_value: bool) -> Self {
175 self.show_value = show_value;
176 self
177 }
178
179 #[inline]
181 pub fn prefix(mut self, prefix: impl ToString) -> Self {
182 self.prefix = prefix.to_string();
183 self
184 }
185
186 #[inline]
188 pub fn suffix(mut self, suffix: impl ToString) -> Self {
189 self.suffix = suffix.to_string();
190 self
191 }
192
193 #[inline]
195 pub fn text(mut self, text: impl Into<WidgetText>) -> Self {
196 self.text = text.into();
197 self
198 }
199
200 #[inline]
201 pub fn text_color(mut self, text_color: Color32) -> Self {
202 self.text = self.text.color(text_color);
203 self
204 }
205
206 #[inline]
208 pub fn orientation(mut self, orientation: SliderOrientation) -> Self {
209 self.orientation = orientation;
210 self
211 }
212
213 #[inline]
215 pub fn vertical(mut self) -> Self {
216 self.orientation = SliderOrientation::Vertical;
217 self
218 }
219
220 #[inline]
225 pub fn logarithmic(mut self, logarithmic: bool) -> Self {
226 self.spec.logarithmic = logarithmic;
227 self
228 }
229
230 #[inline]
234 pub fn smallest_positive(mut self, smallest_positive: f64) -> Self {
235 self.spec.smallest_positive = smallest_positive;
236 self
237 }
238
239 #[inline]
243 pub fn largest_finite(mut self, largest_finite: f64) -> Self {
244 self.spec.largest_finite = largest_finite;
245 self
246 }
247
248 #[inline]
286 pub fn clamping(mut self, clamping: SliderClamping) -> Self {
287 self.clamping = clamping;
288 self
289 }
290
291 #[inline]
292 #[deprecated = "Use `slider.clamping(…) instead"]
293 pub fn clamp_to_range(self, clamp_to_range: bool) -> Self {
294 self.clamping(if clamp_to_range {
295 SliderClamping::Always
296 } else {
297 SliderClamping::Never
298 })
299 }
300
301 #[inline]
304 pub fn smart_aim(mut self, smart_aim: bool) -> Self {
305 self.smart_aim = smart_aim;
306 self
307 }
308
309 #[inline]
316 pub fn step_by(mut self, step: f64) -> Self {
317 self.step = if step != 0.0 { Some(step) } else { None };
318 self
319 }
320
321 #[inline]
330 pub fn drag_value_speed(mut self, drag_value_speed: f64) -> Self {
331 self.drag_value_speed = Some(drag_value_speed);
332 self
333 }
334
335 #[inline]
341 pub fn min_decimals(mut self, min_decimals: usize) -> Self {
342 self.min_decimals = min_decimals;
343 self
344 }
345
346 #[inline]
353 pub fn max_decimals(mut self, max_decimals: usize) -> Self {
354 self.max_decimals = Some(max_decimals);
355 self
356 }
357
358 #[inline]
359 pub fn max_decimals_opt(mut self, max_decimals: Option<usize>) -> Self {
360 self.max_decimals = max_decimals;
361 self
362 }
363
364 #[inline]
370 pub fn fixed_decimals(mut self, num_decimals: usize) -> Self {
371 self.min_decimals = num_decimals;
372 self.max_decimals = Some(num_decimals);
373 self
374 }
375
376 #[inline]
383 pub fn trailing_fill(mut self, trailing_fill: bool) -> Self {
384 self.trailing_fill = Some(trailing_fill);
385 self
386 }
387
388 #[inline]
393 pub fn handle_shape(mut self, handle_shape: HandleShape) -> Self {
394 self.handle_shape = Some(handle_shape);
395 self
396 }
397
398 pub fn custom_formatter(
436 mut self,
437 formatter: impl 'a + Fn(f64, RangeInclusive<usize>) -> String,
438 ) -> Self {
439 self.custom_formatter = Some(Box::new(formatter));
440 self
441 }
442
443 #[inline]
479 pub fn custom_parser(mut self, parser: impl 'a + Fn(&str) -> Option<f64>) -> Self {
480 self.custom_parser = Some(Box::new(parser));
481 self
482 }
483
484 pub fn binary(self, min_width: usize, twos_complement: bool) -> Self {
504 assert!(
505 min_width > 0,
506 "Slider::binary: `min_width` must be greater than 0"
507 );
508 if twos_complement {
509 self.custom_formatter(move |n, _| format!("{:0>min_width$b}", n as i64))
510 } else {
511 self.custom_formatter(move |n, _| {
512 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
513 format!("{sign}{:0>min_width$b}", n.abs() as i64)
514 })
515 }
516 .custom_parser(|s| i64::from_str_radix(s, 2).map(|n| n as f64).ok())
517 }
518
519 pub fn octal(self, min_width: usize, twos_complement: bool) -> Self {
539 assert!(
540 min_width > 0,
541 "Slider::octal: `min_width` must be greater than 0"
542 );
543 if twos_complement {
544 self.custom_formatter(move |n, _| format!("{:0>min_width$o}", n as i64))
545 } else {
546 self.custom_formatter(move |n, _| {
547 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
548 format!("{sign}{:0>min_width$o}", n.abs() as i64)
549 })
550 }
551 .custom_parser(|s| i64::from_str_radix(s, 8).map(|n| n as f64).ok())
552 }
553
554 pub fn hexadecimal(self, min_width: usize, twos_complement: bool, upper: bool) -> Self {
574 assert!(
575 min_width > 0,
576 "Slider::hexadecimal: `min_width` must be greater than 0"
577 );
578 match (twos_complement, upper) {
579 (true, true) => {
580 self.custom_formatter(move |n, _| format!("{:0>min_width$X}", n as i64))
581 }
582 (true, false) => {
583 self.custom_formatter(move |n, _| format!("{:0>min_width$x}", n as i64))
584 }
585 (false, true) => self.custom_formatter(move |n, _| {
586 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
587 format!("{sign}{:0>min_width$X}", n.abs() as i64)
588 }),
589 (false, false) => self.custom_formatter(move |n, _| {
590 let sign = if n < 0.0 { MINUS_CHAR_STR } else { "" };
591 format!("{sign}{:0>min_width$x}", n.abs() as i64)
592 }),
593 }
594 .custom_parser(|s| i64::from_str_radix(s, 16).map(|n| n as f64).ok())
595 }
596
597 pub fn integer(self) -> Self {
601 self.fixed_decimals(0).smallest_positive(1.0).step_by(1.0)
602 }
603
604 fn get_value(&mut self) -> f64 {
605 let value = get(&mut self.get_set_value);
606 if self.clamping == SliderClamping::Always {
607 clamp_value_to_range(value, self.range.clone())
608 } else {
609 value
610 }
611 }
612
613 fn set_value(&mut self, mut value: f64) {
614 if self.clamping != SliderClamping::Never {
615 value = clamp_value_to_range(value, self.range.clone());
616 }
617
618 if let Some(step) = self.step {
619 let start = *self.range.start();
620 value = start + ((value - start) / step).round() * step;
621 }
622 if let Some(max_decimals) = self.max_decimals {
623 value = emath::round_to_decimals(value, max_decimals);
624 }
625 set(&mut self.get_set_value, value);
626 }
627
628 fn range(&self) -> RangeInclusive<f64> {
629 self.range.clone()
630 }
631
632 fn value_from_position(&self, position: f32, position_range: Rangef) -> f64 {
634 let normalized = remap_clamp(position, position_range, 0.0..=1.0) as f64;
635 value_from_normalized(normalized, self.range(), &self.spec)
636 }
637
638 fn position_from_value(&self, value: f64, position_range: Rangef) -> f32 {
639 let normalized = normalized_from_value(value, self.range(), &self.spec);
640 lerp(position_range, normalized as f32)
641 }
642
643 #[inline]
648 pub fn update_while_editing(mut self, update: bool) -> Self {
649 self.update_while_editing = update;
650 self
651 }
652}
653
654impl Slider<'_> {
655 fn allocate_slider_space(&self, ui: &mut Ui, thickness: f32) -> Response {
657 let desired_size = match self.orientation {
658 SliderOrientation::Horizontal => vec2(ui.spacing().slider_width, thickness),
659 SliderOrientation::Vertical => vec2(thickness, ui.spacing().slider_width),
660 };
661 ui.allocate_response(desired_size, Sense::drag())
662 }
663
664 fn slider_ui(&mut self, ui: &Ui, response: &Response) {
666 let rect = &response.rect;
667 let handle_shape = self
668 .handle_shape
669 .unwrap_or_else(|| ui.style().visuals.handle_shape);
670 let position_range = self.position_range(rect, &handle_shape);
671
672 if let Some(pointer_position_2d) = response.interact_pointer_pos() {
673 let position = self.pointer_position(pointer_position_2d);
674 let new_value = if self.smart_aim {
675 let aim_radius = ui.input(|i| i.aim_radius());
676 emath::smart_aim::best_in_range_f64(
677 self.value_from_position(position - aim_radius, position_range),
678 self.value_from_position(position + aim_radius, position_range),
679 )
680 } else {
681 self.value_from_position(position, position_range)
682 };
683 self.set_value(new_value);
684 }
685
686 let mut decrement = 0usize;
687 let mut increment = 0usize;
688
689 if response.has_focus() {
690 ui.memory_mut(|m| {
691 m.set_focus_lock_filter(
692 response.id,
693 EventFilter {
694 horizontal_arrows: matches!(
697 self.orientation,
698 SliderOrientation::Horizontal
699 ),
700 vertical_arrows: matches!(self.orientation, SliderOrientation::Vertical),
701 ..Default::default()
702 },
703 );
704 });
705
706 let (dec_key, inc_key) = match self.orientation {
707 SliderOrientation::Horizontal => (Key::ArrowLeft, Key::ArrowRight),
708 SliderOrientation::Vertical => (Key::ArrowUp, Key::ArrowDown),
711 };
712
713 ui.input(|input| {
714 decrement += input.num_presses(dec_key);
715 increment += input.num_presses(inc_key);
716 });
717 }
718
719 ui.input(|input| {
720 use accesskit::Action;
721 decrement += input.num_accesskit_action_requests(response.id, Action::Decrement);
722 increment += input.num_accesskit_action_requests(response.id, Action::Increment);
723 });
724
725 let kb_step = increment as f32 - decrement as f32;
726
727 if kb_step != 0.0 {
728 let ui_point_per_step = 1.0; let prev_value = self.get_value();
730 let prev_position = self.position_from_value(prev_value, position_range);
731 let new_position = prev_position + ui_point_per_step * kb_step;
732 let mut new_value = match self.step {
733 Some(step) => prev_value + (kb_step as f64 * step),
734 None if self.smart_aim => {
735 let aim_radius = 0.49 * ui_point_per_step; emath::smart_aim::best_in_range_f64(
737 self.value_from_position(new_position - aim_radius, position_range),
738 self.value_from_position(new_position + aim_radius, position_range),
739 )
740 }
741 _ => self.value_from_position(new_position, position_range),
742 };
743 if let Some(max_decimals) = self.max_decimals {
744 let min_increment = 1.0 / (10.0_f64.powi(max_decimals as i32));
748 new_value = if new_value > prev_value {
749 f64::max(new_value, prev_value + min_increment * 1.001)
750 } else if new_value < prev_value {
751 f64::min(new_value, prev_value - min_increment * 1.001)
752 } else {
753 new_value
754 };
755 }
756 self.set_value(new_value);
757 }
758
759 ui.input(|input| {
760 use accesskit::{Action, ActionData};
761 for request in input.accesskit_action_requests(response.id, Action::SetValue) {
762 if let Some(ActionData::NumericValue(new_value)) = request.data {
763 self.set_value(new_value);
764 }
765 }
766 });
767
768 if ui.is_rect_visible(response.rect) {
770 let value = self.get_value();
771
772 let visuals = ui.style().interact(response);
773 let widget_visuals = &ui.visuals().widgets;
774 let spacing = &ui.style().spacing;
775
776 let rail_radius = (spacing.slider_rail_height / 2.0).at_least(0.0);
777 let rail_rect = self.rail_rect(rect, rail_radius);
778 let corner_radius = widget_visuals.inactive.corner_radius;
779
780 ui.painter()
781 .rect_filled(rail_rect, corner_radius, widget_visuals.inactive.bg_fill);
782
783 let position_1d = self.position_from_value(value, position_range);
784 let center = self.marker_center(position_1d, &rail_rect);
785
786 let trailing_fill = self
788 .trailing_fill
789 .unwrap_or_else(|| ui.visuals().slider_trailing_fill);
790
791 if trailing_fill {
793 let mut trailing_rail_rect = rail_rect;
794
795 match self.orientation {
797 SliderOrientation::Horizontal => {
798 trailing_rail_rect.max.x = center.x + corner_radius.nw as f32;
799 }
800 SliderOrientation::Vertical => {
801 trailing_rail_rect.min.y = center.y - corner_radius.se as f32;
802 }
803 }
804
805 ui.painter().rect_filled(
806 trailing_rail_rect,
807 corner_radius,
808 ui.visuals().selection.bg_fill,
809 );
810 }
811
812 let radius = self.handle_radius(rect);
813
814 let handle_shape = self
815 .handle_shape
816 .unwrap_or_else(|| ui.style().visuals.handle_shape);
817 match handle_shape {
818 style::HandleShape::Circle => {
819 ui.painter().add(epaint::CircleShape {
820 center,
821 radius: radius + visuals.expansion,
822 fill: visuals.bg_fill,
823 stroke: visuals.fg_stroke,
824 });
825 }
826 style::HandleShape::Rect { aspect_ratio } => {
827 let v = match self.orientation {
828 SliderOrientation::Horizontal => Vec2::new(radius * aspect_ratio, radius),
829 SliderOrientation::Vertical => Vec2::new(radius, radius * aspect_ratio),
830 };
831 let v = v + Vec2::splat(visuals.expansion);
832 let rect = Rect::from_center_size(center, 2.0 * v);
833 ui.painter().rect(
834 rect,
835 visuals.corner_radius,
836 visuals.bg_fill,
837 visuals.fg_stroke,
838 epaint::StrokeKind::Inside,
839 );
840 }
841 }
842 }
843 }
844
845 fn marker_center(&self, position_1d: f32, rail_rect: &Rect) -> Pos2 {
846 match self.orientation {
847 SliderOrientation::Horizontal => pos2(position_1d, rail_rect.center().y),
848 SliderOrientation::Vertical => pos2(rail_rect.center().x, position_1d),
849 }
850 }
851
852 fn pointer_position(&self, pointer_position_2d: Pos2) -> f32 {
853 match self.orientation {
854 SliderOrientation::Horizontal => pointer_position_2d.x,
855 SliderOrientation::Vertical => pointer_position_2d.y,
856 }
857 }
858
859 fn position_range(&self, rect: &Rect, handle_shape: &style::HandleShape) -> Rangef {
860 let handle_radius = self.handle_radius(rect);
861 let handle_radius = match handle_shape {
862 style::HandleShape::Circle => handle_radius,
863 style::HandleShape::Rect { aspect_ratio } => handle_radius * aspect_ratio,
864 };
865 match self.orientation {
866 SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius),
867 SliderOrientation::Vertical => rect.y_range().shrink(handle_radius).flip(),
870 }
871 }
872
873 fn rail_rect(&self, rect: &Rect, radius: f32) -> Rect {
874 match self.orientation {
875 SliderOrientation::Horizontal => Rect::from_min_max(
876 pos2(rect.left(), rect.center().y - radius),
877 pos2(rect.right(), rect.center().y + radius),
878 ),
879 SliderOrientation::Vertical => Rect::from_min_max(
880 pos2(rect.center().x - radius, rect.top()),
881 pos2(rect.center().x + radius, rect.bottom()),
882 ),
883 }
884 }
885
886 fn handle_radius(&self, rect: &Rect) -> f32 {
887 let limit = match self.orientation {
888 SliderOrientation::Horizontal => rect.height(),
889 SliderOrientation::Vertical => rect.width(),
890 };
891 limit / 2.5
892 }
893
894 fn value_ui(&mut self, ui: &mut Ui, position_range: Rangef) -> Response {
895 let change = ui.input(|input| {
897 input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32
898 - input.num_presses(Key::ArrowDown) as i32
899 - input.num_presses(Key::ArrowLeft) as i32
900 });
901
902 let any_change = change != 0;
903 let speed = if let (Some(step), true) = (self.step, any_change) {
904 step
906 } else {
907 self.drag_value_speed
908 .unwrap_or_else(|| self.current_gradient(position_range))
909 };
910
911 let mut value = self.get_value();
912 let response = ui.add({
913 let mut dv = DragValue::new(&mut value)
914 .speed(speed)
915 .min_decimals(self.min_decimals)
916 .max_decimals_opt(self.max_decimals)
917 .suffix(self.suffix.clone())
918 .prefix(self.prefix.clone())
919 .update_while_editing(self.update_while_editing);
920
921 match self.clamping {
922 SliderClamping::Never => {}
923 SliderClamping::Edits => {
924 dv = dv.range(self.range.clone()).clamp_existing_to_range(false);
925 }
926 SliderClamping::Always => {
927 dv = dv.range(self.range.clone()).clamp_existing_to_range(true);
928 }
929 }
930
931 if let Some(fmt) = &self.custom_formatter {
932 dv = dv.custom_formatter(fmt);
933 }
934 if let Some(parser) = &self.custom_parser {
935 dv = dv.custom_parser(parser);
936 }
937 dv
938 });
939 if value != self.get_value() {
940 self.set_value(value);
941 }
942 response
943 }
944
945 fn current_gradient(&mut self, position_range: Rangef) -> f64 {
947 let value = self.get_value();
949 let value_from_pos = |position: f32| self.value_from_position(position, position_range);
950 let pos_from_value = |value: f64| self.position_from_value(value, position_range);
951 let left_value = value_from_pos(pos_from_value(value) - 0.5);
952 let right_value = value_from_pos(pos_from_value(value) + 0.5);
953 right_value - left_value
954 }
955
956 fn add_contents(&mut self, ui: &mut Ui) -> Response {
957 let old_value = self.get_value();
958
959 if self.clamping == SliderClamping::Always {
960 self.set_value(old_value);
961 }
962
963 let thickness = ui
964 .text_style_height(&TextStyle::Body)
965 .at_least(ui.spacing().interact_size.y);
966 let mut response = self.allocate_slider_space(ui, thickness);
967 self.slider_ui(ui, &response);
968
969 let value = self.get_value();
970 if value != old_value {
971 response.mark_changed();
972 }
973 response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text()));
974
975 ui.ctx().accesskit_node_builder(response.id, |builder| {
976 use accesskit::Action;
977 builder.set_min_numeric_value(*self.range.start());
978 builder.set_max_numeric_value(*self.range.end());
979 if let Some(step) = self.step {
980 builder.set_numeric_value_step(step);
981 }
982 builder.add_action(Action::SetValue);
983
984 let clamp_range = if self.clamping == SliderClamping::Never {
985 f64::NEG_INFINITY..=f64::INFINITY
986 } else {
987 self.range()
988 };
989 if value < *clamp_range.end() {
990 builder.add_action(Action::Increment);
991 }
992 if value > *clamp_range.start() {
993 builder.add_action(Action::Decrement);
994 }
995 });
996
997 let slider_response = response.clone();
998
999 let value_response = if self.show_value {
1000 let handle_shape = self
1001 .handle_shape
1002 .unwrap_or_else(|| ui.style().visuals.handle_shape);
1003 let position_range = self.position_range(&response.rect, &handle_shape);
1004 let value_response = self.value_ui(ui, position_range);
1005 if value_response.gained_focus()
1006 || value_response.has_focus()
1007 || value_response.lost_focus()
1008 {
1009 response = value_response.union(response);
1012 } else {
1013 response = response.union(value_response.clone());
1015 }
1016 Some(value_response)
1017 } else {
1018 None
1019 };
1020
1021 if !self.text.is_empty() {
1022 let label_response =
1023 ui.add(Label::new(self.text.clone()).wrap_mode(TextWrapMode::Extend));
1024 slider_response.labelled_by(label_response.id);
1029 if let Some(value_response) = value_response {
1030 value_response.labelled_by(label_response.id);
1031 }
1032 }
1033
1034 response
1035 }
1036}
1037
1038impl Widget for Slider<'_> {
1039 fn ui(mut self, ui: &mut Ui) -> Response {
1040 let inner_response = match self.orientation {
1041 SliderOrientation::Horizontal => ui.horizontal(|ui| self.add_contents(ui)),
1042 SliderOrientation::Vertical => ui.vertical(|ui| self.add_contents(ui)),
1043 };
1044
1045 inner_response.inner | inner_response.response
1046 }
1047}
1048
1049const INFINITY: f64 = f64::INFINITY;
1056
1057const INF_RANGE_MAGNITUDE: f64 = 10.0;
1060
1061fn value_from_normalized(normalized: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
1062 let (min, max) = (*range.start(), *range.end());
1063
1064 if min.is_nan() || max.is_nan() {
1065 f64::NAN
1066 } else if min == max {
1067 min
1068 } else if min > max {
1069 value_from_normalized(1.0 - normalized, max..=min, spec)
1070 } else if normalized <= 0.0 {
1071 min
1072 } else if normalized >= 1.0 {
1073 max
1074 } else if spec.logarithmic {
1075 if max <= 0.0 {
1076 -value_from_normalized(normalized, -min..=-max, spec)
1078 } else if 0.0 <= min {
1079 let (min_log, max_log) = range_log10(min, max, spec);
1080 let log = lerp(min_log..=max_log, normalized);
1081 10.0_f64.powf(log)
1082 } else {
1083 assert!(
1084 min < 0.0 && 0.0 < max,
1085 "min should be negative and max positive, but got min={min} and max={max}"
1086 );
1087 let zero_cutoff = logarithmic_zero_cutoff(min, max);
1088 if normalized < zero_cutoff {
1089 value_from_normalized(
1091 remap(normalized, 0.0..=zero_cutoff, 0.0..=1.0),
1092 min..=0.0,
1093 spec,
1094 )
1095 } else {
1096 value_from_normalized(
1098 remap(normalized, zero_cutoff..=1.0, 0.0..=1.0),
1099 0.0..=max,
1100 spec,
1101 )
1102 }
1103 }
1104 } else {
1105 debug_assert!(
1106 min.is_finite() && max.is_finite(),
1107 "You should use a logarithmic range"
1108 );
1109 lerp(range, normalized.clamp(0.0, 1.0))
1110 }
1111}
1112
1113fn normalized_from_value(value: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
1114 let (min, max) = (*range.start(), *range.end());
1115
1116 if min.is_nan() || max.is_nan() {
1117 f64::NAN
1118 } else if min == max {
1119 0.5 } else if min > max {
1121 1.0 - normalized_from_value(value, max..=min, spec)
1122 } else if value <= min {
1123 0.0
1124 } else if value >= max {
1125 1.0
1126 } else if spec.logarithmic {
1127 if max <= 0.0 {
1128 normalized_from_value(-value, -min..=-max, spec)
1130 } else if 0.0 <= min {
1131 let (min_log, max_log) = range_log10(min, max, spec);
1132 let value_log = value.log10();
1133 remap_clamp(value_log, min_log..=max_log, 0.0..=1.0)
1134 } else {
1135 assert!(
1136 min < 0.0 && 0.0 < max,
1137 "min should be negative and max positive, but got min={min} and max={max}"
1138 );
1139 let zero_cutoff = logarithmic_zero_cutoff(min, max);
1140 if value < 0.0 {
1141 remap(
1143 normalized_from_value(value, min..=0.0, spec),
1144 0.0..=1.0,
1145 0.0..=zero_cutoff,
1146 )
1147 } else {
1148 remap(
1150 normalized_from_value(value, 0.0..=max, spec),
1151 0.0..=1.0,
1152 zero_cutoff..=1.0,
1153 )
1154 }
1155 }
1156 } else {
1157 debug_assert!(
1158 min.is_finite() && max.is_finite(),
1159 "You should use a logarithmic range"
1160 );
1161 remap_clamp(value, range, 0.0..=1.0)
1162 }
1163}
1164
1165fn range_log10(min: f64, max: f64, spec: &SliderSpec) -> (f64, f64) {
1166 assert!(spec.logarithmic, "spec must be logarithmic");
1167 assert!(
1168 min <= max,
1169 "min must be less than or equal to max, but was min={min} and max={max}"
1170 );
1171
1172 if min == 0.0 && max == INFINITY {
1173 (spec.smallest_positive.log10(), INF_RANGE_MAGNITUDE)
1174 } else if min == 0.0 {
1175 if spec.smallest_positive < max {
1176 (spec.smallest_positive.log10(), max.log10())
1177 } else {
1178 (max.log10() - INF_RANGE_MAGNITUDE, max.log10())
1179 }
1180 } else if max == INFINITY {
1181 if min < spec.largest_finite {
1182 (min.log10(), spec.largest_finite.log10())
1183 } else {
1184 (min.log10(), min.log10() + INF_RANGE_MAGNITUDE)
1185 }
1186 } else {
1187 (min.log10(), max.log10())
1188 }
1189}
1190
1191fn logarithmic_zero_cutoff(min: f64, max: f64) -> f64 {
1194 assert!(
1195 min < 0.0 && 0.0 < max,
1196 "min must be negative and max positive, but got min={min} and max={max}"
1197 );
1198
1199 let min_magnitude = if min == -INFINITY {
1200 INF_RANGE_MAGNITUDE
1201 } else {
1202 min.abs().log10().abs()
1203 };
1204 let max_magnitude = if max == INFINITY {
1205 INF_RANGE_MAGNITUDE
1206 } else {
1207 max.log10().abs()
1208 };
1209
1210 let cutoff = min_magnitude / (min_magnitude + max_magnitude);
1211 debug_assert!(
1212 0.0 <= cutoff && cutoff <= 1.0,
1213 "Bad cutoff {cutoff:?} for min {min:?} and max {max:?}"
1214 );
1215 cutoff
1216}