egui/widgets/
slider.rs

1#![allow(clippy::needless_pass_by_value)] // False positives with `impl ToString`
2
3use std::ops::RangeInclusive;
4
5use crate::{style::HandleShape, *};
6
7// ----------------------------------------------------------------------------
8
9type NumFormatter<'a> = Box<dyn 'a + Fn(f64, RangeInclusive<usize>) -> String>;
10type NumParser<'a> = Box<dyn 'a + Fn(&str) -> Option<f64>>;
11
12// ----------------------------------------------------------------------------
13
14/// Combined into one function (rather than two) to make it easier
15/// for the borrow checker.
16type 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// ----------------------------------------------------------------------------
27
28#[derive(Clone)]
29struct SliderSpec {
30    logarithmic: bool,
31
32    /// For logarithmic sliders, the smallest positive value we are interested in.
33    /// 1 for integer sliders, maybe 1e-6 for others.
34    smallest_positive: f64,
35
36    /// For logarithmic sliders, the largest positive value we are interested in
37    /// before the slider switches to `INFINITY`, if that is the higher end.
38    /// Default: INFINITY.
39    largest_finite: f64,
40}
41
42/// Specifies the orientation of a [`Slider`].
43pub enum SliderOrientation {
44    Horizontal,
45    Vertical,
46}
47
48/// Control a number with a slider.
49///
50/// The slider range defines the values you get when pulling the slider to the far edges.
51/// By default all values are clamped to this range, even when not interacted with.
52/// You can change this behavior by passing `false` to [`Slider::clamp_to_range`].
53///
54/// The range can include any numbers, and go from low-to-high or from high-to-low.
55///
56/// The slider consists of three parts: a slider, a value display, and an optional text.
57/// The user can click the value display to edit its value. It can be turned off with `.show_value(false)`.
58///
59/// ```
60/// # egui::__run_test_ui(|ui| {
61/// # let mut my_f32: f32 = 0.0;
62/// ui.add(egui::Slider::new(&mut my_f32, 0.0..=100.0).text("My value"));
63/// # });
64/// ```
65///
66/// The default [`Slider`] size is set by [`crate::style::Spacing::slider_width`].
67#[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    /// Sets the minimal step of the widget value
81    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    /// Creates a new horizontal slider.
94    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    /// Control whether or not the slider shows the current value.
141    /// Default: `true`.
142    #[inline]
143    pub fn show_value(mut self, show_value: bool) -> Self {
144        self.show_value = show_value;
145        self
146    }
147
148    /// Show a prefix before the number, e.g. "x: "
149    #[inline]
150    pub fn prefix(mut self, prefix: impl ToString) -> Self {
151        self.prefix = prefix.to_string();
152        self
153    }
154
155    /// Add a suffix to the number, this can be e.g. a unit ("°" or " m")
156    #[inline]
157    pub fn suffix(mut self, suffix: impl ToString) -> Self {
158        self.suffix = suffix.to_string();
159        self
160    }
161
162    /// Show a text next to the slider (e.g. explaining what the slider controls).
163    #[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    /// Vertical or horizontal slider? The default is horizontal.
176    #[inline]
177    pub fn orientation(mut self, orientation: SliderOrientation) -> Self {
178        self.orientation = orientation;
179        self
180    }
181
182    /// Make this a vertical slider.
183    #[inline]
184    pub fn vertical(mut self) -> Self {
185        self.orientation = SliderOrientation::Vertical;
186        self
187    }
188
189    /// Make this a logarithmic slider.
190    /// This is great for when the slider spans a huge range,
191    /// e.g. from one to a million.
192    /// The default is OFF.
193    #[inline]
194    pub fn logarithmic(mut self, logarithmic: bool) -> Self {
195        self.spec.logarithmic = logarithmic;
196        self
197    }
198
199    /// For logarithmic sliders that includes zero:
200    /// what is the smallest positive value you want to be able to select?
201    /// The default is `1` for integer sliders and `1e-6` for real sliders.
202    #[inline]
203    pub fn smallest_positive(mut self, smallest_positive: f64) -> Self {
204        self.spec.smallest_positive = smallest_positive;
205        self
206    }
207
208    /// For logarithmic sliders, the largest positive value we are interested in
209    /// before the slider switches to `INFINITY`, if that is the higher end.
210    /// Default: INFINITY.
211    #[inline]
212    pub fn largest_finite(mut self, largest_finite: f64) -> Self {
213        self.spec.largest_finite = largest_finite;
214        self
215    }
216
217    /// If set to `true`, all incoming and outgoing values will be clamped to the slider range.
218    /// Default: `true`.
219    #[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    /// Turn smart aim on/off. Default is ON.
226    /// There is almost no point in turning this off.
227    #[inline]
228    pub fn smart_aim(mut self, smart_aim: bool) -> Self {
229        self.smart_aim = smart_aim;
230        self
231    }
232
233    /// Sets the minimal change of the value.
234    ///
235    /// Value `0.0` effectively disables the feature. If the new value is out of range
236    /// and `clamp_to_range` is enabled, you would not have the ability to change the value.
237    ///
238    /// Default: `0.0` (disabled).
239    #[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    /// When dragging the value, how fast does it move?
246    ///
247    /// Unit: values per point (logical pixel).
248    /// See also [`DragValue::speed`].
249    ///
250    /// By default this is the same speed as when dragging the slider,
251    /// but you can change it here to for instance have a much finer control
252    /// by dragging the slider value rather than the slider itself.
253    #[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    // TODO(emilk): we should also have a "min precision".
260    /// Set a minimum number of decimals to display.
261    ///
262    /// Normally you don't need to pick a precision, as the slider will intelligently pick a precision for you.
263    /// Regardless of precision the slider will use "smart aim" to help the user select nice, round values.
264    #[inline]
265    pub fn min_decimals(mut self, min_decimals: usize) -> Self {
266        self.min_decimals = min_decimals;
267        self
268    }
269
270    // TODO(emilk): we should also have a "max precision".
271    /// Set a maximum number of decimals to display.
272    ///
273    /// Values will also be rounded to this number of decimals.
274    /// Normally you don't need to pick a precision, as the slider will intelligently pick a precision for you.
275    /// Regardless of precision the slider will use "smart aim" to help the user select nice, round values.
276    #[inline]
277    pub fn max_decimals(mut self, max_decimals: usize) -> Self {
278        self.max_decimals = Some(max_decimals);
279        self
280    }
281
282    /// Set an exact number of decimals to display.
283    ///
284    /// Values will also be rounded to this number of decimals.
285    /// Normally you don't need to pick a precision, as the slider will intelligently pick a precision for you.
286    /// Regardless of precision the slider will use "smart aim" to help the user select nice, round values.
287    #[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    /// Display trailing color behind the slider's circle. Default is OFF.
295    ///
296    /// This setting can be enabled globally for all sliders with [`Visuals::slider_trailing_fill`].
297    /// Toggling it here will override the above setting ONLY for this individual slider.
298    ///
299    /// The fill color will be taken from `selection.bg_fill` in your [`Visuals`], the same as a [`ProgressBar`].
300    #[inline]
301    pub fn trailing_fill(mut self, trailing_fill: bool) -> Self {
302        self.trailing_fill = Some(trailing_fill);
303        self
304    }
305
306    /// Change the shape of the slider handle
307    ///
308    /// This setting can be enabled globally for all sliders with [`Visuals::handle_shape`].
309    /// Changing it here will override the above setting ONLY for this individual slider.
310    #[inline]
311    pub fn handle_shape(mut self, handle_shape: HandleShape) -> Self {
312        self.handle_shape = Some(handle_shape);
313        self
314    }
315
316    /// Set custom formatter defining how numbers are converted into text.
317    ///
318    /// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
319    /// the decimal range i.e. minimum and maximum number of decimal places shown.
320    ///
321    /// The default formatter is [`Style::number_formatter`].
322    ///
323    /// See also: [`Slider::custom_parser`]
324    ///
325    /// ```
326    /// # egui::__run_test_ui(|ui| {
327    /// # let mut my_i32: i32 = 0;
328    /// ui.add(egui::Slider::new(&mut my_i32, 0..=((60 * 60 * 24) - 1))
329    ///     .custom_formatter(|n, _| {
330    ///         let n = n as i32;
331    ///         let hours = n / (60 * 60);
332    ///         let mins = (n / 60) % 60;
333    ///         let secs = n % 60;
334    ///         format!("{hours:02}:{mins:02}:{secs:02}")
335    ///     })
336    ///     .custom_parser(|s| {
337    ///         let parts: Vec<&str> = s.split(':').collect();
338    ///         if parts.len() == 3 {
339    ///             parts[0].parse::<i32>().and_then(|h| {
340    ///                 parts[1].parse::<i32>().and_then(|m| {
341    ///                     parts[2].parse::<i32>().map(|s| {
342    ///                         ((h * 60 * 60) + (m * 60) + s) as f64
343    ///                     })
344    ///                 })
345    ///             })
346    ///             .ok()
347    ///         } else {
348    ///             None
349    ///         }
350    ///     }));
351    /// # });
352    /// ```
353    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    /// Set custom parser defining how the text input is parsed into a number.
362    ///
363    /// A custom parser takes an `&str` to parse into a number and returns `Some` if it was successfully parsed
364    /// or `None` otherwise.
365    ///
366    /// See also: [`Slider::custom_formatter`]
367    ///
368    /// ```
369    /// # egui::__run_test_ui(|ui| {
370    /// # let mut my_i32: i32 = 0;
371    /// ui.add(egui::Slider::new(&mut my_i32, 0..=((60 * 60 * 24) - 1))
372    ///     .custom_formatter(|n, _| {
373    ///         let n = n as i32;
374    ///         let hours = n / (60 * 60);
375    ///         let mins = (n / 60) % 60;
376    ///         let secs = n % 60;
377    ///         format!("{hours:02}:{mins:02}:{secs:02}")
378    ///     })
379    ///     .custom_parser(|s| {
380    ///         let parts: Vec<&str> = s.split(':').collect();
381    ///         if parts.len() == 3 {
382    ///             parts[0].parse::<i32>().and_then(|h| {
383    ///                 parts[1].parse::<i32>().and_then(|m| {
384    ///                     parts[2].parse::<i32>().map(|s| {
385    ///                         ((h * 60 * 60) + (m * 60) + s) as f64
386    ///                     })
387    ///                 })
388    ///             })
389    ///             .ok()
390    ///         } else {
391    ///             None
392    ///         }
393    ///     }));
394    /// # });
395    /// ```
396    #[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    /// Set `custom_formatter` and `custom_parser` to display and parse numbers as binary integers. Floating point
403    /// numbers are *not* supported.
404    ///
405    /// `min_width` specifies the minimum number of displayed digits; if the number is shorter than this, it will be
406    /// prefixed with additional 0s to match `min_width`.
407    ///
408    /// If `twos_complement` is true, negative values will be displayed as the 2's complement representation. Otherwise
409    /// they will be prefixed with a '-' sign.
410    ///
411    /// # Panics
412    ///
413    /// Panics if `min_width` is 0.
414    ///
415    /// ```
416    /// # egui::__run_test_ui(|ui| {
417    /// # let mut my_i32: i32 = 0;
418    /// ui.add(egui::Slider::new(&mut my_i32, -100..=100).binary(64, false));
419    /// # });
420    /// ```
421    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    /// Set `custom_formatter` and `custom_parser` to display and parse numbers as octal integers. Floating point
438    /// numbers are *not* supported.
439    ///
440    /// `min_width` specifies the minimum number of displayed digits; if the number is shorter than this, it will be
441    /// prefixed with additional 0s to match `min_width`.
442    ///
443    /// If `twos_complement` is true, negative values will be displayed as the 2's complement representation. Otherwise
444    /// they will be prefixed with a '-' sign.
445    ///
446    /// # Panics
447    ///
448    /// Panics if `min_width` is 0.
449    ///
450    /// ```
451    /// # egui::__run_test_ui(|ui| {
452    /// # let mut my_i32: i32 = 0;
453    /// ui.add(egui::Slider::new(&mut my_i32, -100..=100).octal(22, false));
454    /// # });
455    /// ```
456    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    /// Set `custom_formatter` and `custom_parser` to display and parse numbers as hexadecimal integers. Floating point
473    /// numbers are *not* supported.
474    ///
475    /// `min_width` specifies the minimum number of displayed digits; if the number is shorter than this, it will be
476    /// prefixed with additional 0s to match `min_width`.
477    ///
478    /// If `twos_complement` is true, negative values will be displayed as the 2's complement representation. Otherwise
479    /// they will be prefixed with a '-' sign.
480    ///
481    /// # Panics
482    ///
483    /// Panics if `min_width` is 0.
484    ///
485    /// ```
486    /// # egui::__run_test_ui(|ui| {
487    /// # let mut my_i32: i32 = 0;
488    /// ui.add(egui::Slider::new(&mut my_i32, -100..=100).hexadecimal(16, false, true));
489    /// # });
490    /// ```
491    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    /// Helper: equivalent to `self.precision(0).smallest_positive(1.0)`.
516    /// If you use one of the integer constructors (e.g. `Slider::i32`) this is called for you,
517    /// but if you want to have a slider for picking integer values in an `Slider::f64`, use this.
518    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    /// For instance, `position` is the mouse position and `position_range` is the physical location of the slider on the screen.
554    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    /// Just the slider, no text
567    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    /// Just the slider, no text
576    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                        // pressing arrows in the orientation of the
606                        // slider should not move focus to next widget
607                        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                // Note that this is for moving the slider position,
620                // so up = decrement y coordinate:
621                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; // move this many ui points for each kb_step
643            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; // Chosen so we don't include `prev_value` in the search.
650                    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        // Paint it:
673        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            // Decide if we should add trailing fill.
691            let trailing_fill = self
692                .trailing_fill
693                .unwrap_or_else(|| ui.visuals().slider_trailing_fill);
694
695            // Paint trailing fill.
696            if trailing_fill {
697                let mut trailing_rail_rect = rail_rect;
698
699                // The trailing rect has to be drawn differently depending on the orientation.
700                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            // The vertical case has to be flipped because the largest slider value maps to the
767            // lowest y value (which is at the top)
768            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        // If [`DragValue`] is controlled from the keyboard and `step` is defined, set speed to `step`
795        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            // If [`DragValue`] is controlled from the keyboard and `step` is defined, set speed to `step`
804            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    /// delta(value) / delta(points)
835    fn current_gradient(&mut self, position_range: Rangef) -> f64 {
836        // TODO(emilk): handle clamping
837        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                // Use the [`DragValue`] id as the id of the whole widget,
894                // so that the focus events work as expected.
895                response = value_response.union(response);
896            } else {
897                // Use the slider id as the id for the whole widget
898                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            // The slider already has an accessibility label via widget info,
909            // but sometimes it's useful for a screen reader to know
910            // that a piece of text is a label for another widget,
911            // e.g. so the text itself can be excluded from navigation.
912            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
933// ----------------------------------------------------------------------------
934// Helpers for converting slider range to/from normalized [0-1] range.
935// Always clamps.
936// Logarithmic sliders are allowed to include zero and infinity,
937// even though mathematically it doesn't make sense.
938
939use std::f64::INFINITY;
940
941/// When the user asks for an infinitely large range (e.g. logarithmic from zero),
942/// give a scale that this many orders of magnitude in size.
943const 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            // non-positive range
961            -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                // negative
971                value_from_normalized(
972                    remap(normalized, 0.0..=zero_cutoff, 0.0..=1.0),
973                    min..=0.0,
974                    spec,
975                )
976            } else {
977                // positive
978                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 // empty range, show center of slider
1001    } 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            // non-positive range
1010            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                // negative
1020                remap(
1021                    normalized_from_value(value, min..=0.0, spec),
1022                    0.0..=1.0,
1023                    0.0..=zero_cutoff,
1024                )
1025            } else {
1026                // positive side
1027                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
1066/// where to put the zero cutoff for logarithmic sliders
1067/// that crosses zero ?
1068fn 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}