1#![allow(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.ctx().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 #[cfg(feature = "accesskit")]
720 {
721 use accesskit::Action;
722 ui.input(|input| {
723 decrement += input.num_accesskit_action_requests(response.id, Action::Decrement);
724 increment += input.num_accesskit_action_requests(response.id, Action::Increment);
725 });
726 }
727
728 let kb_step = increment as f32 - decrement as f32;
729
730 if kb_step != 0.0 {
731 let ui_point_per_step = 1.0; let prev_value = self.get_value();
733 let prev_position = self.position_from_value(prev_value, position_range);
734 let new_position = prev_position + ui_point_per_step * kb_step;
735 let mut new_value = match self.step {
736 Some(step) => prev_value + (kb_step as f64 * step),
737 None if self.smart_aim => {
738 let aim_radius = 0.49 * ui_point_per_step; emath::smart_aim::best_in_range_f64(
740 self.value_from_position(new_position - aim_radius, position_range),
741 self.value_from_position(new_position + aim_radius, position_range),
742 )
743 }
744 _ => self.value_from_position(new_position, position_range),
745 };
746 if let Some(max_decimals) = self.max_decimals {
747 let min_increment = 1.0 / (10.0_f64.powi(max_decimals as i32));
751 new_value = if new_value > prev_value {
752 f64::max(new_value, prev_value + min_increment * 1.001)
753 } else if new_value < prev_value {
754 f64::min(new_value, prev_value - min_increment * 1.001)
755 } else {
756 new_value
757 };
758 }
759 self.set_value(new_value);
760 }
761
762 #[cfg(feature = "accesskit")]
763 {
764 use accesskit::{Action, ActionData};
765 ui.input(|input| {
766 for request in input.accesskit_action_requests(response.id, Action::SetValue) {
767 if let Some(ActionData::NumericValue(new_value)) = request.data {
768 self.set_value(new_value);
769 }
770 }
771 });
772 }
773
774 if ui.is_rect_visible(response.rect) {
776 let value = self.get_value();
777
778 let visuals = ui.style().interact(response);
779 let widget_visuals = &ui.visuals().widgets;
780 let spacing = &ui.style().spacing;
781
782 let rail_radius = (spacing.slider_rail_height / 2.0).at_least(0.0);
783 let rail_rect = self.rail_rect(rect, rail_radius);
784 let corner_radius = widget_visuals.inactive.corner_radius;
785
786 ui.painter()
787 .rect_filled(rail_rect, corner_radius, widget_visuals.inactive.bg_fill);
788
789 let position_1d = self.position_from_value(value, position_range);
790 let center = self.marker_center(position_1d, &rail_rect);
791
792 let trailing_fill = self
794 .trailing_fill
795 .unwrap_or_else(|| ui.visuals().slider_trailing_fill);
796
797 if trailing_fill {
799 let mut trailing_rail_rect = rail_rect;
800
801 match self.orientation {
803 SliderOrientation::Horizontal => {
804 trailing_rail_rect.max.x = center.x + corner_radius.nw as f32;
805 }
806 SliderOrientation::Vertical => {
807 trailing_rail_rect.min.y = center.y - corner_radius.se as f32;
808 }
809 };
810
811 ui.painter().rect_filled(
812 trailing_rail_rect,
813 corner_radius,
814 ui.visuals().selection.bg_fill,
815 );
816 }
817
818 let radius = self.handle_radius(rect);
819
820 let handle_shape = self
821 .handle_shape
822 .unwrap_or_else(|| ui.style().visuals.handle_shape);
823 match handle_shape {
824 style::HandleShape::Circle => {
825 ui.painter().add(epaint::CircleShape {
826 center,
827 radius: radius + visuals.expansion,
828 fill: visuals.bg_fill,
829 stroke: visuals.fg_stroke,
830 });
831 }
832 style::HandleShape::Rect { aspect_ratio } => {
833 let v = match self.orientation {
834 SliderOrientation::Horizontal => Vec2::new(radius * aspect_ratio, radius),
835 SliderOrientation::Vertical => Vec2::new(radius, radius * aspect_ratio),
836 };
837 let v = v + Vec2::splat(visuals.expansion);
838 let rect = Rect::from_center_size(center, 2.0 * v);
839 ui.painter().rect(
840 rect,
841 visuals.corner_radius,
842 visuals.bg_fill,
843 visuals.fg_stroke,
844 epaint::StrokeKind::Inside,
845 );
846 }
847 }
848 }
849 }
850
851 fn marker_center(&self, position_1d: f32, rail_rect: &Rect) -> Pos2 {
852 match self.orientation {
853 SliderOrientation::Horizontal => pos2(position_1d, rail_rect.center().y),
854 SliderOrientation::Vertical => pos2(rail_rect.center().x, position_1d),
855 }
856 }
857
858 fn pointer_position(&self, pointer_position_2d: Pos2) -> f32 {
859 match self.orientation {
860 SliderOrientation::Horizontal => pointer_position_2d.x,
861 SliderOrientation::Vertical => pointer_position_2d.y,
862 }
863 }
864
865 fn position_range(&self, rect: &Rect, handle_shape: &style::HandleShape) -> Rangef {
866 let handle_radius = self.handle_radius(rect);
867 let handle_radius = match handle_shape {
868 style::HandleShape::Circle => handle_radius,
869 style::HandleShape::Rect { aspect_ratio } => handle_radius * aspect_ratio,
870 };
871 match self.orientation {
872 SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius),
873 SliderOrientation::Vertical => rect.y_range().shrink(handle_radius).flip(),
876 }
877 }
878
879 fn rail_rect(&self, rect: &Rect, radius: f32) -> Rect {
880 match self.orientation {
881 SliderOrientation::Horizontal => Rect::from_min_max(
882 pos2(rect.left(), rect.center().y - radius),
883 pos2(rect.right(), rect.center().y + radius),
884 ),
885 SliderOrientation::Vertical => Rect::from_min_max(
886 pos2(rect.center().x - radius, rect.top()),
887 pos2(rect.center().x + radius, rect.bottom()),
888 ),
889 }
890 }
891
892 fn handle_radius(&self, rect: &Rect) -> f32 {
893 let limit = match self.orientation {
894 SliderOrientation::Horizontal => rect.height(),
895 SliderOrientation::Vertical => rect.width(),
896 };
897 limit / 2.5
898 }
899
900 fn value_ui(&mut self, ui: &mut Ui, position_range: Rangef) -> Response {
901 let change = ui.input(|input| {
903 input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32
904 - input.num_presses(Key::ArrowDown) as i32
905 - input.num_presses(Key::ArrowLeft) as i32
906 });
907
908 let any_change = change != 0;
909 let speed = if let (Some(step), true) = (self.step, any_change) {
910 step
912 } else {
913 self.drag_value_speed
914 .unwrap_or_else(|| self.current_gradient(position_range))
915 };
916
917 let mut value = self.get_value();
918 let response = ui.add({
919 let mut dv = DragValue::new(&mut value)
920 .speed(speed)
921 .min_decimals(self.min_decimals)
922 .max_decimals_opt(self.max_decimals)
923 .suffix(self.suffix.clone())
924 .prefix(self.prefix.clone())
925 .update_while_editing(self.update_while_editing);
926
927 match self.clamping {
928 SliderClamping::Never => {}
929 SliderClamping::Edits => {
930 dv = dv.range(self.range.clone()).clamp_existing_to_range(false);
931 }
932 SliderClamping::Always => {
933 dv = dv.range(self.range.clone()).clamp_existing_to_range(true);
934 }
935 }
936
937 if let Some(fmt) = &self.custom_formatter {
938 dv = dv.custom_formatter(fmt);
939 };
940 if let Some(parser) = &self.custom_parser {
941 dv = dv.custom_parser(parser);
942 }
943 dv
944 });
945 if value != self.get_value() {
946 self.set_value(value);
947 }
948 response
949 }
950
951 fn current_gradient(&mut self, position_range: Rangef) -> f64 {
953 let value = self.get_value();
955 let value_from_pos = |position: f32| self.value_from_position(position, position_range);
956 let pos_from_value = |value: f64| self.position_from_value(value, position_range);
957 let left_value = value_from_pos(pos_from_value(value) - 0.5);
958 let right_value = value_from_pos(pos_from_value(value) + 0.5);
959 right_value - left_value
960 }
961
962 fn add_contents(&mut self, ui: &mut Ui) -> Response {
963 let old_value = self.get_value();
964
965 if self.clamping == SliderClamping::Always {
966 self.set_value(old_value);
967 }
968
969 let thickness = ui
970 .text_style_height(&TextStyle::Body)
971 .at_least(ui.spacing().interact_size.y);
972 let mut response = self.allocate_slider_space(ui, thickness);
973 self.slider_ui(ui, &response);
974
975 let value = self.get_value();
976 if value != old_value {
977 response.mark_changed();
978 }
979 response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text()));
980
981 #[cfg(feature = "accesskit")]
982 ui.ctx().accesskit_node_builder(response.id, |builder| {
983 use accesskit::Action;
984 builder.set_min_numeric_value(*self.range.start());
985 builder.set_max_numeric_value(*self.range.end());
986 if let Some(step) = self.step {
987 builder.set_numeric_value_step(step);
988 }
989 builder.add_action(Action::SetValue);
990
991 let clamp_range = if self.clamping == SliderClamping::Never {
992 f64::NEG_INFINITY..=f64::INFINITY
993 } else {
994 self.range()
995 };
996 if value < *clamp_range.end() {
997 builder.add_action(Action::Increment);
998 }
999 if value > *clamp_range.start() {
1000 builder.add_action(Action::Decrement);
1001 }
1002 });
1003
1004 let slider_response = response.clone();
1005
1006 let value_response = if self.show_value {
1007 let handle_shape = self
1008 .handle_shape
1009 .unwrap_or_else(|| ui.style().visuals.handle_shape);
1010 let position_range = self.position_range(&response.rect, &handle_shape);
1011 let value_response = self.value_ui(ui, position_range);
1012 if value_response.gained_focus()
1013 || value_response.has_focus()
1014 || value_response.lost_focus()
1015 {
1016 response = value_response.union(response);
1019 } else {
1020 response = response.union(value_response.clone());
1022 }
1023 Some(value_response)
1024 } else {
1025 None
1026 };
1027
1028 if !self.text.is_empty() {
1029 let label_response =
1030 ui.add(Label::new(self.text.clone()).wrap_mode(TextWrapMode::Extend));
1031 slider_response.labelled_by(label_response.id);
1036 if let Some(value_response) = value_response {
1037 value_response.labelled_by(label_response.id);
1038 }
1039 }
1040
1041 response
1042 }
1043}
1044
1045impl Widget for Slider<'_> {
1046 fn ui(mut self, ui: &mut Ui) -> Response {
1047 let inner_response = match self.orientation {
1048 SliderOrientation::Horizontal => ui.horizontal(|ui| self.add_contents(ui)),
1049 SliderOrientation::Vertical => ui.vertical(|ui| self.add_contents(ui)),
1050 };
1051
1052 inner_response.inner | inner_response.response
1053 }
1054}
1055
1056const INFINITY: f64 = f64::INFINITY;
1063
1064const INF_RANGE_MAGNITUDE: f64 = 10.0;
1067
1068fn value_from_normalized(normalized: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
1069 let (min, max) = (*range.start(), *range.end());
1070
1071 if min.is_nan() || max.is_nan() {
1072 f64::NAN
1073 } else if min == max {
1074 min
1075 } else if min > max {
1076 value_from_normalized(1.0 - normalized, max..=min, spec)
1077 } else if normalized <= 0.0 {
1078 min
1079 } else if normalized >= 1.0 {
1080 max
1081 } else if spec.logarithmic {
1082 if max <= 0.0 {
1083 -value_from_normalized(normalized, -min..=-max, spec)
1085 } else if 0.0 <= min {
1086 let (min_log, max_log) = range_log10(min, max, spec);
1087 let log = lerp(min_log..=max_log, normalized);
1088 10.0_f64.powf(log)
1089 } else {
1090 assert!(
1091 min < 0.0 && 0.0 < max,
1092 "min should be negative and max positive, but got min={min} and max={max}"
1093 );
1094 let zero_cutoff = logarithmic_zero_cutoff(min, max);
1095 if normalized < zero_cutoff {
1096 value_from_normalized(
1098 remap(normalized, 0.0..=zero_cutoff, 0.0..=1.0),
1099 min..=0.0,
1100 spec,
1101 )
1102 } else {
1103 value_from_normalized(
1105 remap(normalized, zero_cutoff..=1.0, 0.0..=1.0),
1106 0.0..=max,
1107 spec,
1108 )
1109 }
1110 }
1111 } else {
1112 debug_assert!(
1113 min.is_finite() && max.is_finite(),
1114 "You should use a logarithmic range"
1115 );
1116 lerp(range, normalized.clamp(0.0, 1.0))
1117 }
1118}
1119
1120fn normalized_from_value(value: f64, range: RangeInclusive<f64>, spec: &SliderSpec) -> f64 {
1121 let (min, max) = (*range.start(), *range.end());
1122
1123 if min.is_nan() || max.is_nan() {
1124 f64::NAN
1125 } else if min == max {
1126 0.5 } else if min > max {
1128 1.0 - normalized_from_value(value, max..=min, spec)
1129 } else if value <= min {
1130 0.0
1131 } else if value >= max {
1132 1.0
1133 } else if spec.logarithmic {
1134 if max <= 0.0 {
1135 normalized_from_value(-value, -min..=-max, spec)
1137 } else if 0.0 <= min {
1138 let (min_log, max_log) = range_log10(min, max, spec);
1139 let value_log = value.log10();
1140 remap_clamp(value_log, min_log..=max_log, 0.0..=1.0)
1141 } else {
1142 assert!(
1143 min < 0.0 && 0.0 < max,
1144 "min should be negative and max positive, but got min={min} and max={max}"
1145 );
1146 let zero_cutoff = logarithmic_zero_cutoff(min, max);
1147 if value < 0.0 {
1148 remap(
1150 normalized_from_value(value, min..=0.0, spec),
1151 0.0..=1.0,
1152 0.0..=zero_cutoff,
1153 )
1154 } else {
1155 remap(
1157 normalized_from_value(value, 0.0..=max, spec),
1158 0.0..=1.0,
1159 zero_cutoff..=1.0,
1160 )
1161 }
1162 }
1163 } else {
1164 debug_assert!(
1165 min.is_finite() && max.is_finite(),
1166 "You should use a logarithmic range"
1167 );
1168 remap_clamp(value, range, 0.0..=1.0)
1169 }
1170}
1171
1172fn range_log10(min: f64, max: f64, spec: &SliderSpec) -> (f64, f64) {
1173 assert!(spec.logarithmic, "spec must be logarithmic");
1174 assert!(
1175 min <= max,
1176 "min must be less than or equal to max, but was min={min} and max={max}"
1177 );
1178
1179 if min == 0.0 && max == INFINITY {
1180 (spec.smallest_positive.log10(), INF_RANGE_MAGNITUDE)
1181 } else if min == 0.0 {
1182 if spec.smallest_positive < max {
1183 (spec.smallest_positive.log10(), max.log10())
1184 } else {
1185 (max.log10() - INF_RANGE_MAGNITUDE, max.log10())
1186 }
1187 } else if max == INFINITY {
1188 if min < spec.largest_finite {
1189 (min.log10(), spec.largest_finite.log10())
1190 } else {
1191 (min.log10(), min.log10() + INF_RANGE_MAGNITUDE)
1192 }
1193 } else {
1194 (min.log10(), max.log10())
1195 }
1196}
1197
1198fn logarithmic_zero_cutoff(min: f64, max: f64) -> f64 {
1201 assert!(
1202 min < 0.0 && 0.0 < max,
1203 "min must be negative and max positive, but got min={min} and max={max}"
1204 );
1205
1206 let min_magnitude = if min == -INFINITY {
1207 INF_RANGE_MAGNITUDE
1208 } else {
1209 min.abs().log10().abs()
1210 };
1211 let max_magnitude = if max == INFINITY {
1212 INF_RANGE_MAGNITUDE
1213 } else {
1214 max.log10().abs()
1215 };
1216
1217 let cutoff = min_magnitude / (min_magnitude + max_magnitude);
1218 debug_assert!(
1219 0.0 <= cutoff && cutoff <= 1.0,
1220 "Bad cutoff {cutoff:?} for min {min:?} and max {max:?}"
1221 );
1222 cutoff
1223}