Skip to main content

gpui_base/
slider.rs

1use std::ops::Range;
2
3use gpui::{
4    AccessibleAction, Along, AnyElement, App, AppContext as _, Axis, Bounds, Context, Div,
5    DragMoveEvent, Empty, Entity, EntityId, EventEmitter, InteractiveElement, IntoElement,
6    MouseButton, MouseDownEvent, Orientation, ParentElement, Pixels, Point, Render, RenderOnce,
7    Role, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,
8    prelude::FluentBuilder as _, px,
9};
10
11use crate::{element_ext::ElementExt, geometry::AxisExt};
12
13/// Events emitted by the [`SliderState`].
14pub enum SliderEvent {
15    /// Emitted continuously while the slider value is being changed by the user.
16    Change(SliderValue),
17    /// Emitted once when the user releases the slider after a drag or click.
18    Release(SliderValue),
19}
20
21/// The value of the slider, can be a single value or a range of values.
22///
23/// - Can from a f32 value, which will be treated as a single value.
24/// - Or from a (f32, f32) tuple, which will be treated as a range of values.
25///
26/// The default value is `SliderValue::Single(0.0)`.
27#[derive(Clone, Copy, Debug, PartialEq)]
28pub enum SliderValue {
29    Single(f32),
30    Range(f32, f32),
31}
32
33impl std::fmt::Display for SliderValue {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            SliderValue::Single(value) => write!(f, "{}", value),
37            SliderValue::Range(start, end) => write!(f, "{}..{}", start, end),
38        }
39    }
40}
41
42impl From<f32> for SliderValue {
43    fn from(value: f32) -> Self {
44        SliderValue::Single(value)
45    }
46}
47
48impl From<(f32, f32)> for SliderValue {
49    fn from(value: (f32, f32)) -> Self {
50        SliderValue::Range(value.0, value.1)
51    }
52}
53
54impl From<Range<f32>> for SliderValue {
55    fn from(value: Range<f32>) -> Self {
56        SliderValue::Range(value.start, value.end)
57    }
58}
59
60impl Default for SliderValue {
61    fn default() -> Self {
62        SliderValue::Single(0.)
63    }
64}
65
66impl SliderValue {
67    /// Clamp the value to the given range.
68    pub fn clamp(self, min: f32, max: f32) -> Self {
69        match self {
70            SliderValue::Single(value) => SliderValue::Single(value.clamp(min, max)),
71            SliderValue::Range(start, end) => {
72                SliderValue::Range(start.clamp(min, max), end.clamp(min, max))
73            }
74        }
75    }
76
77    /// Check if the value is a single value.
78    #[inline]
79    pub fn is_single(&self) -> bool {
80        matches!(self, SliderValue::Single(_))
81    }
82
83    /// Check if the value is a range of values.
84    #[inline]
85    pub fn is_range(&self) -> bool {
86        matches!(self, SliderValue::Range(_, _))
87    }
88
89    /// Get the start value.
90    pub fn start(&self) -> f32 {
91        match self {
92            SliderValue::Single(value) => *value,
93            SliderValue::Range(start, _) => *start,
94        }
95    }
96
97    /// Get the end value.
98    pub fn end(&self) -> f32 {
99        match self {
100            SliderValue::Single(value) => *value,
101            SliderValue::Range(_, end) => *end,
102        }
103    }
104
105    fn set_start(&mut self, value: f32) {
106        if let SliderValue::Range(_, end) = self {
107            *self = SliderValue::Range(value.min(*end), *end);
108        } else {
109            *self = SliderValue::Single(value);
110        }
111    }
112
113    fn set_end(&mut self, value: f32) {
114        if let SliderValue::Range(start, _) = self {
115            *self = SliderValue::Range(*start, value.max(*start));
116        } else {
117            *self = SliderValue::Single(value);
118        }
119    }
120}
121
122/// The scale mode of the slider.
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
124pub enum SliderScale {
125    /// Linear scale where values change uniformly across the slider range.
126    /// This is the default mode.
127    #[default]
128    Linear,
129    /// Logarithmic scale where the distance between values increases exponentially.
130    ///
131    /// This is useful for parameters that have a large range of values where smaller
132    /// changes are more significant at lower values. Common examples include:
133    ///
134    /// - Volume controls (human hearing perception is logarithmic)
135    /// - Frequency controls (musical notes follow a logarithmic scale)
136    /// - Zoom levels
137    /// - Any parameter where you want finer control at lower values
138    ///
139    /// # For example
140    ///
141    /// ```
142    /// use gpui_base::slider::{SliderScale, SliderState};
143    ///
144    /// let slider = SliderState::new()
145    ///     .min(1.0)    // Must be > 0 for logarithmic scale
146    ///     .max(1000.0)
147    ///     .scale(SliderScale::Logarithmic);
148    /// ```
149    ///
150    /// - Moving the slider 1/3 of the way will yield ~10
151    /// - Moving it 2/3 of the way will yield ~100
152    /// - The full range covers 3 orders of magnitude evenly
153    Logarithmic,
154}
155
156impl SliderScale {
157    #[inline]
158    pub fn is_linear(&self) -> bool {
159        matches!(self, SliderScale::Linear)
160    }
161
162    #[inline]
163    pub fn is_logarithmic(&self) -> bool {
164        matches!(self, SliderScale::Logarithmic)
165    }
166}
167
168/// State of the [`Slider`].
169pub struct SliderState {
170    min: f32,
171    max: f32,
172    step: f32,
173    value: SliderValue,
174    /// When is single value mode, only `end` is used, the start is always 0.0.
175    percentage: Range<f32>,
176    /// The bounds of the slider after rendered.
177    bounds: Bounds<Pixels>,
178    scale: SliderScale,
179    /// Tracks whether the user is currently interacting with the slider so we
180    /// only emit [`SliderEvent::Release`] after a real press/drag.
181    dragging: bool,
182}
183
184impl SliderState {
185    /// Create a new [`SliderState`].
186    pub fn new() -> Self {
187        Self {
188            min: 0.0,
189            max: 100.0,
190            step: 1.0,
191            value: SliderValue::default(),
192            percentage: (0.0..0.0),
193            bounds: Bounds::default(),
194            scale: SliderScale::default(),
195            dragging: false,
196        }
197    }
198
199    /// Set the minimum value of the slider, default: 0.0
200    pub fn min(mut self, min: f32) -> Self {
201        if self.scale.is_logarithmic() {
202            assert!(
203                min > 0.0,
204                "`min` must be greater than 0 for SliderScale::Logarithmic"
205            );
206            assert!(
207                min < self.max,
208                "`min` must be less than `max` for Logarithmic scale"
209            );
210        }
211        self.min = min;
212        self.update_thumb_pos();
213        self
214    }
215
216    /// Set the maximum value of the slider, default: 100.0
217    pub fn max(mut self, max: f32) -> Self {
218        if self.scale.is_logarithmic() {
219            assert!(
220                max > self.min,
221                "`max` must be greater than `min` for Logarithmic scale"
222            );
223        }
224        self.max = max;
225        self.update_thumb_pos();
226        self
227    }
228
229    /// Set the step value of the slider, default: 1.0
230    pub fn step(mut self, step: f32) -> Self {
231        self.step = step;
232        self
233    }
234
235    /// Set the scale of the slider, default: [`SliderScale::Linear`].
236    pub fn scale(mut self, scale: SliderScale) -> Self {
237        if scale.is_logarithmic() {
238            assert!(
239                self.min > 0.0,
240                "`min` must be greater than 0 for Logarithmic scale"
241            );
242            assert!(
243                self.max > self.min,
244                "`max` must be greater than `min` for Logarithmic scale"
245            );
246        }
247        self.scale = scale;
248        self.update_thumb_pos();
249        self
250    }
251
252    /// Set the default value of the slider, default: 0.0
253    pub fn default_value(mut self, value: impl Into<SliderValue>) -> Self {
254        self.value = value.into();
255        self.update_thumb_pos();
256        self
257    }
258
259    /// Set the value of the slider.
260    pub fn set_value(
261        &mut self,
262        value: impl Into<SliderValue>,
263        _: &mut Window,
264        cx: &mut Context<Self>,
265    ) {
266        self.value = value.into();
267        self.update_thumb_pos();
268        cx.notify();
269    }
270
271    /// Get the value of the slider.
272    pub fn value(&self) -> SliderValue {
273        self.value
274    }
275
276    /// Get the minimum value.
277    pub fn min_value(&self) -> f32 {
278        self.min
279    }
280
281    /// Get the maximum value.
282    pub fn max_value(&self) -> f32 {
283        self.max
284    }
285
286    /// Get the step value.
287    pub fn step_value(&self) -> f32 {
288        self.step
289    }
290
291    /// Converts a value between 0.0 and 1.0 to a value between the minimum and maximum value,
292    /// depending on the chosen scale.
293    fn percentage_to_value(&self, percentage: f32) -> f32 {
294        match self.scale {
295            SliderScale::Linear => self.min + (self.max - self.min) * percentage,
296            SliderScale::Logarithmic => {
297                // when percentage is 0, this simplifies to (max/min)^0 * min = 1 * min = min
298                // when percentage is 1, this simplifies to (max/min)^1 * min = (max*min)/min = max
299                // we clamp just to make sure we don't have issue with floating point precision
300                let base = self.max / self.min;
301                (base.powf(percentage) * self.min).clamp(self.min, self.max)
302            }
303        }
304    }
305
306    /// Converts a value between the minimum and maximum value to a value between 0.0 and 1.0,
307    /// depending on the chosen scale.
308    fn value_to_percentage(&self, value: f32) -> f32 {
309        match self.scale {
310            SliderScale::Linear => {
311                let range = self.max - self.min;
312                if range <= 0.0 {
313                    0.0
314                } else {
315                    (value - self.min) / range
316                }
317            }
318            SliderScale::Logarithmic => {
319                let base = self.max / self.min;
320                (value / self.min).log(base).clamp(0.0, 1.0)
321            }
322        }
323    }
324
325    fn update_thumb_pos(&mut self) {
326        match self.value {
327            SliderValue::Single(value) => {
328                let percentage = self.value_to_percentage(value.clamp(self.min, self.max));
329                self.percentage = 0.0..percentage;
330            }
331            SliderValue::Range(start, end) => {
332                let clamped_start = start.clamp(self.min, self.max);
333                let clamped_end = end.clamp(self.min, self.max);
334                self.percentage =
335                    self.value_to_percentage(clamped_start)..self.value_to_percentage(clamped_end);
336            }
337        }
338    }
339
340    /// Update value by mouse position
341    #[doc(hidden)]
342    pub fn update_value_by_position(
343        &mut self,
344        axis: Axis,
345        position: Point<Pixels>,
346        is_start: bool,
347        _: &mut Window,
348        cx: &mut Context<Self>,
349    ) {
350        self.dragging = true;
351        let bounds = self.bounds;
352        let step = self.step;
353
354        let inner_pos = if axis.is_horizontal() {
355            position.x - bounds.left()
356        } else {
357            bounds.bottom() - position.y
358        };
359        let total_size = bounds.size.along(axis);
360        let percentage = inner_pos.clamp(px(0.), total_size) / total_size;
361
362        let percentage = if is_start {
363            percentage.clamp(0.0, self.percentage.end)
364        } else {
365            percentage.clamp(self.percentage.start, 1.0)
366        };
367
368        let value = self.percentage_to_value(percentage);
369        let value = (value / step).round() * step;
370
371        if is_start {
372            self.percentage.start = percentage;
373            self.value.set_start(value);
374        } else {
375            self.percentage.end = percentage;
376            self.value.set_end(value);
377        }
378        cx.emit(SliderEvent::Change(self.value));
379        cx.notify();
380    }
381
382    /// Emit [`SliderEvent::Release`] if the user was actively interacting
383    /// with the slider. Called on mouse-up both inside and outside the slider.
384    #[doc(hidden)]
385    pub fn handle_release(&mut self, cx: &mut Context<Self>) {
386        if !self.dragging {
387            return;
388        }
389        self.dragging = false;
390        cx.emit(SliderEvent::Release(self.value));
391    }
392}
393
394#[derive(Clone)]
395struct DragThumb((EntityId, bool));
396
397impl Render for DragThumb {
398    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
399        Empty
400    }
401}
402
403#[derive(Clone)]
404struct DragSlider(EntityId);
405
406impl Render for DragSlider {
407    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
408        Empty
409    }
410}
411
412/// An unstyled slider behavior root.
413///
414/// Applications provide the track, range, and thumb presentation as children.
415#[derive(IntoElement)]
416pub struct Slider {
417    state: Entity<SliderState>,
418    axis: Axis,
419    disabled: bool,
420    base: Div,
421    children: Vec<AnyElement>,
422}
423
424impl Slider {
425    pub fn new(state: &Entity<SliderState>) -> Self {
426        Self {
427            state: state.clone(),
428            axis: Axis::Horizontal,
429            disabled: false,
430            base: div(),
431            children: Vec::new(),
432        }
433    }
434
435    pub fn horizontal(mut self) -> Self {
436        self.axis = Axis::Horizontal;
437        self
438    }
439
440    pub fn vertical(mut self) -> Self {
441        self.axis = Axis::Vertical;
442        self
443    }
444
445    pub fn axis(mut self, axis: Axis) -> Self {
446        self.axis = axis;
447        self
448    }
449
450    pub fn disabled(mut self, disabled: bool) -> Self {
451        self.disabled = disabled;
452        self
453    }
454}
455
456impl ParentElement for Slider {
457    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
458        self.children.extend(elements);
459    }
460}
461
462impl Styled for Slider {
463    fn style(&mut self) -> &mut StyleRefinement {
464        self.base.style()
465    }
466}
467
468impl RenderOnce for Slider {
469    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
470        let axis = self.axis;
471        let entity_id = self.state.entity_id();
472        let state = self.state.read(cx);
473        let slider_state = self.state.clone();
474
475        self.base
476            .id(("slider", entity_id))
477            .role(Role::Slider)
478            .aria_numeric_value(state.value().end() as f64)
479            .aria_min_numeric_value(state.min_value() as f64)
480            .aria_max_numeric_value(state.max_value() as f64)
481            .aria_numeric_value_step(state.step_value() as f64)
482            .aria_orientation(if axis.is_vertical() {
483                Orientation::Vertical
484            } else {
485                Orientation::Horizontal
486            })
487            .on_a11y_action(AccessibleAction::Increment, {
488                let state = slider_state.clone();
489                move |_, window, cx| {
490                    state.update(cx, |state, cx| {
491                        let value =
492                            (state.value().end() + state.step_value()).min(state.max_value());
493                        state.set_value(value, window, cx);
494                    });
495                }
496            })
497            .on_a11y_action(AccessibleAction::Decrement, {
498                let state = slider_state.clone();
499                move |_, window, cx| {
500                    state.update(cx, |state, cx| {
501                        let value =
502                            (state.value().end() - state.step_value()).max(state.min_value());
503                        state.set_value(value, window, cx);
504                    });
505                }
506            })
507            .when(!self.disabled, |this| {
508                this.on_mouse_up(
509                    MouseButton::Left,
510                    window.listener_for(&self.state, |state, _, _, cx| state.handle_release(cx)),
511                )
512                .on_mouse_up_out(
513                    MouseButton::Left,
514                    window.listener_for(&self.state, |state, _, _, cx| state.handle_release(cx)),
515                )
516            })
517            .children(self.children)
518    }
519}
520
521/// An unstyled track that records the geometry used to map pointer positions.
522#[derive(IntoElement)]
523pub struct SliderTrack {
524    state: Entity<SliderState>,
525    axis: Axis,
526    disabled: bool,
527    base: Div,
528    children: Vec<AnyElement>,
529}
530
531impl SliderTrack {
532    pub fn new(state: &Entity<SliderState>) -> Self {
533        Self {
534            state: state.clone(),
535            axis: Axis::Horizontal,
536            disabled: false,
537            base: div(),
538            children: Vec::new(),
539        }
540    }
541
542    pub fn axis(mut self, axis: Axis) -> Self {
543        self.axis = axis;
544        self
545    }
546    pub fn disabled(mut self, disabled: bool) -> Self {
547        self.disabled = disabled;
548        self
549    }
550}
551
552impl ParentElement for SliderTrack {
553    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
554        self.children.extend(elements);
555    }
556}
557
558impl Styled for SliderTrack {
559    fn style(&mut self) -> &mut StyleRefinement {
560        self.base.style()
561    }
562}
563
564impl InteractiveElement for SliderTrack {
565    fn interactivity(&mut self) -> &mut gpui::Interactivity {
566        self.base.interactivity()
567    }
568}
569
570impl RenderOnce for SliderTrack {
571    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
572        let axis = self.axis;
573        let entity_id = self.state.entity_id();
574        let state = self.state.read(cx);
575        let is_range = state.value().is_range();
576        let percentage = state.percentage();
577        self.base
578            .id("slider-bar-container")
579            .children(self.children)
580            .when(!self.disabled, |this| {
581                this.on_mouse_down(
582                    MouseButton::Left,
583                    window.listener_for(
584                        &self.state,
585                        move |state, event: &MouseDownEvent, window, cx| {
586                            let is_start = if is_range {
587                                let size = state.bounds().size.along(axis);
588                                let position = if axis.is_horizontal() {
589                                    event.position.x - state.bounds().left()
590                                } else {
591                                    state.bounds().bottom() - event.position.y
592                                };
593                                let center = ((percentage.end - percentage.start) / 2.
594                                    + percentage.start)
595                                    * size;
596                                position < center
597                            } else {
598                                false
599                            };
600                            state.update_value_by_position(
601                                axis,
602                                event.position,
603                                is_start,
604                                window,
605                                cx,
606                            );
607                        },
608                    ),
609                )
610                .when(!is_range, |this| {
611                    this.on_drag(DragSlider(entity_id), |drag, _, _, cx| {
612                        cx.stop_propagation();
613                        cx.new(|_| drag.clone())
614                    })
615                    .on_drag_move(window.listener_for(
616                        &self.state,
617                        move |state, event: &DragMoveEvent<DragSlider>, window, cx| {
618                            let DragSlider(id) = event.drag(cx);
619                            if *id == entity_id {
620                                state.update_value_by_position(
621                                    axis,
622                                    event.event.position,
623                                    false,
624                                    window,
625                                    cx,
626                                );
627                            }
628                        },
629                    ))
630                })
631            })
632    }
633}
634
635/// An unstyled slider indicator that records the value-mapping bounds.
636#[derive(IntoElement)]
637pub struct SliderIndicator {
638    state: Entity<SliderState>,
639    base: Div,
640    children: Vec<AnyElement>,
641}
642
643impl SliderIndicator {
644    pub fn new(state: &Entity<SliderState>) -> Self {
645        Self {
646            state: state.clone(),
647            base: div(),
648            children: Vec::new(),
649        }
650    }
651}
652
653impl ParentElement for SliderIndicator {
654    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
655        self.children.extend(elements);
656    }
657}
658
659impl Styled for SliderIndicator {
660    fn style(&mut self) -> &mut StyleRefinement {
661        self.base.style()
662    }
663}
664
665impl InteractiveElement for SliderIndicator {
666    fn interactivity(&mut self) -> &mut gpui::Interactivity {
667        self.base.interactivity()
668    }
669}
670
671impl StatefulInteractiveElement for SliderIndicator {}
672
673impl RenderOnce for SliderIndicator {
674    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
675        self.base
676            .id("slider-bar")
677            .children(self.children)
678            .on_prepaint({
679                let state = self.state;
680                move |bounds, _, cx| state.update(cx, |state, _| state.set_bounds(bounds))
681            })
682    }
683}
684
685/// An unstyled draggable slider thumb.
686#[derive(IntoElement)]
687pub struct SliderThumb {
688    state: Entity<SliderState>,
689    axis: Axis,
690    start: bool,
691    disabled: bool,
692    base: Div,
693    children: Vec<AnyElement>,
694}
695
696impl SliderThumb {
697    pub fn new(state: &Entity<SliderState>) -> Self {
698        Self {
699            state: state.clone(),
700            axis: Axis::Horizontal,
701            start: false,
702            disabled: false,
703            base: div(),
704            children: Vec::new(),
705        }
706    }
707
708    pub fn axis(mut self, axis: Axis) -> Self {
709        self.axis = axis;
710        self
711    }
712    pub fn start(mut self, start: bool) -> Self {
713        self.start = start;
714        self
715    }
716    pub fn disabled(mut self, disabled: bool) -> Self {
717        self.disabled = disabled;
718        self
719    }
720}
721
722impl ParentElement for SliderThumb {
723    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
724        self.children.extend(elements);
725    }
726}
727
728impl Styled for SliderThumb {
729    fn style(&mut self) -> &mut StyleRefinement {
730        self.base.style()
731    }
732}
733
734impl InteractiveElement for SliderThumb {
735    fn interactivity(&mut self) -> &mut gpui::Interactivity {
736        self.base.interactivity()
737    }
738}
739
740impl StatefulInteractiveElement for SliderThumb {}
741
742impl RenderOnce for SliderThumb {
743    fn render(self, window: &mut Window, _: &mut App) -> impl IntoElement {
744        let entity_id = self.state.entity_id();
745        let axis = self.axis;
746        let start = self.start;
747        self.base
748            .id(("slider-thumb", start as u32))
749            .children(self.children)
750            .when(!self.disabled, |this| {
751                this.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
752                    .on_drag(DragThumb((entity_id, start)), |drag, _, _, cx| {
753                        cx.stop_propagation();
754                        cx.new(|_| drag.clone())
755                    })
756                    .on_drag_move(window.listener_for(
757                        &self.state,
758                        move |state, event: &DragMoveEvent<DragThumb>, window, cx| {
759                            let DragThumb((id, start)) = event.drag(cx);
760                            if *id == entity_id {
761                                state.update_value_by_position(
762                                    axis,
763                                    event.event.position,
764                                    *start,
765                                    window,
766                                    cx,
767                                );
768                            }
769                        },
770                    ))
771            })
772    }
773}
774
775impl EventEmitter<SliderEvent> for SliderState {}
776
777impl SliderState {
778    #[doc(hidden)]
779    pub fn percentage(&self) -> Range<f32> {
780        self.percentage.clone()
781    }
782
783    #[doc(hidden)]
784    pub fn bounds(&self) -> Bounds<Pixels> {
785        self.bounds
786    }
787
788    #[doc(hidden)]
789    pub fn set_bounds(&mut self, bounds: Bounds<Pixels>) {
790        self.bounds = bounds;
791    }
792}
793
794#[cfg(test)]
795mod tests {
796    use super::*;
797
798    #[test]
799    fn legacy_value_conversions_and_clamping_are_preserved() {
800        assert_eq!(SliderValue::from(5.), SliderValue::Single(5.));
801        assert_eq!(SliderValue::from((2., 8.)), SliderValue::Range(2., 8.));
802        assert_eq!(SliderValue::from(2.0..8.0), SliderValue::Range(2., 8.));
803        assert_eq!(
804            SliderValue::Range(-1., 12.).clamp(0., 10.),
805            SliderValue::Range(0., 10.)
806        );
807    }
808
809    #[test]
810    fn legacy_linear_state_keeps_percentage_and_range_ordering() {
811        let state = SliderState::new()
812            .min(0.)
813            .max(200.)
814            .default_value((50., 150.));
815        assert_eq!(state.value(), SliderValue::Range(50., 150.));
816        assert_eq!(state.percentage(), 0.25..0.75);
817    }
818
819    #[test]
820    fn legacy_logarithmic_state_keeps_mapping() {
821        let state = SliderState::new()
822            .min(1.)
823            .max(1000.)
824            .scale(SliderScale::Logarithmic)
825            .default_value(10.);
826        let percentage = state.percentage().end;
827        assert!((percentage - (1. / 3.)).abs() < 0.0001);
828    }
829
830    #[test]
831    #[should_panic(expected = "`min` must be greater than 0")]
832    fn legacy_logarithmic_validation_is_preserved() {
833        let _ = SliderState::new().scale(SliderScale::Logarithmic);
834    }
835}