Skip to main content

dear_imgui_rs/widget/
slider.rs

1//! Sliders
2//!
3//! Scalar and range sliders for numeric input with builder-based configuration
4//! of speed, format and clamping.
5//!
6#![allow(
7    clippy::cast_possible_truncation,
8    clippy::cast_sign_loss,
9    clippy::as_conversions
10)]
11use crate::Ui;
12use crate::internal::{DataType, DataTypeKind, component_count_i32};
13use crate::sys;
14use std::ffi::c_void;
15
16fn validate_slider_flags(caller: &str, flags: SliderFlags) {
17    let bits = flags.bits();
18    assert!(
19        bits & (sys::ImGuiSliderFlags_WrapAround as i32) == 0,
20        "{caller} does not support ImGuiSliderFlags_WrapAround; use DragFlags::WRAP_AROUND with drag widgets"
21    );
22    let unsupported = bits & !SliderFlags::all().bits();
23    assert!(
24        unsupported == 0,
25        "{caller} received unsupported ImGuiSliderFlags bits: 0x{unsupported:X}"
26    );
27}
28
29fn validate_slider_range<Data: DataTypeKind>(caller: &str, min: &Data, max: &Data) {
30    match Data::KIND {
31        DataType::I8 | DataType::U8 | DataType::I16 | DataType::U16 => {}
32        DataType::I32 => {
33            let min = unsafe { *(min as *const Data as *const i32) };
34            let max = unsafe { *(max as *const Data as *const i32) };
35            let lower = i32::MIN / 2;
36            let upper = i32::MAX / 2;
37            assert!(
38                (lower..=upper).contains(&min) && (lower..=upper).contains(&max),
39                "{caller} i32/isize range endpoints must stay within i32::MIN/2..=i32::MAX/2"
40            );
41        }
42        DataType::U32 => {
43            let min = unsafe { *(min as *const Data as *const u32) };
44            let max = unsafe { *(max as *const Data as *const u32) };
45            let upper = u32::MAX / 2;
46            assert!(
47                min <= upper && max <= upper,
48                "{caller} u32/usize range endpoints must be <= u32::MAX/2"
49            );
50        }
51        DataType::I64 => {
52            let min = unsafe { *(min as *const Data as *const i64) };
53            let max = unsafe { *(max as *const Data as *const i64) };
54            let lower = i64::MIN / 2;
55            let upper = i64::MAX / 2;
56            assert!(
57                (lower..=upper).contains(&min) && (lower..=upper).contains(&max),
58                "{caller} i64/isize range endpoints must stay within i64::MIN/2..=i64::MAX/2"
59            );
60        }
61        DataType::U64 => {
62            let min = unsafe { *(min as *const Data as *const u64) };
63            let max = unsafe { *(max as *const Data as *const u64) };
64            let upper = u64::MAX / 2;
65            assert!(
66                min <= upper && max <= upper,
67                "{caller} u64/usize range endpoints must be <= u64::MAX/2"
68            );
69        }
70        DataType::F32 => {
71            let min = unsafe { *(min as *const Data as *const f32) };
72            let max = unsafe { *(max as *const Data as *const f32) };
73            assert!(
74                min.is_finite()
75                    && max.is_finite()
76                    && (-f32::MAX / 2.0..=f32::MAX / 2.0).contains(&min)
77                    && (-f32::MAX / 2.0..=f32::MAX / 2.0).contains(&max),
78                "{caller} f32 range endpoints must be finite and stay within -f32::MAX/2..=f32::MAX/2"
79            );
80        }
81        DataType::F64 => {
82            let min = unsafe { *(min as *const Data as *const f64) };
83            let max = unsafe { *(max as *const Data as *const f64) };
84            assert!(
85                min.is_finite()
86                    && max.is_finite()
87                    && (-f64::MAX / 2.0..=f64::MAX / 2.0).contains(&min)
88                    && (-f64::MAX / 2.0..=f64::MAX / 2.0).contains(&max),
89                "{caller} f64 range endpoints must be finite and stay within -f64::MAX/2..=f64::MAX/2"
90            );
91        }
92    }
93}
94
95fn validate_slider_preconditions<Data: DataTypeKind>(
96    caller: &str,
97    min: &Data,
98    max: &Data,
99    flags: SliderFlags,
100) {
101    validate_slider_flags(caller, flags);
102    validate_slider_range(caller, min, max);
103}
104
105/// Builder for slider widgets
106#[derive(Clone, Debug)]
107#[must_use]
108pub struct Slider<'ui, Label, Data, Format = &'static str> {
109    ui: &'ui Ui,
110    label: Label,
111    min: Data,
112    max: Data,
113    display_format: Option<Format>,
114    flags: SliderFlags,
115}
116
117impl<'ui, Label, Data> Slider<'ui, Label, Data>
118where
119    Label: AsRef<str>,
120    Data: DataTypeKind,
121{
122    /// Creates a new slider builder
123    #[doc(alias = "SliderScalar", alias = "SliderScalarN")]
124    #[deprecated(note = "Use `Ui::slider` or `Ui::slider_config`.", since = "0.1.0")]
125    pub fn new(ui: &'ui Ui, label: Label, min: Data, max: Data) -> Self {
126        Self {
127            ui,
128            label,
129            min,
130            max,
131            display_format: None,
132            flags: SliderFlags::NONE,
133        }
134    }
135}
136
137impl<'ui, Label, Data, Format> Slider<'ui, Label, Data, Format>
138where
139    Label: AsRef<str>,
140    Data: DataTypeKind,
141    Format: AsRef<str>,
142{
143    /// Sets the range inclusively, such that both values given
144    /// are valid values which the slider can be dragged to.
145    ///
146    /// ```no_run
147    /// # use dear_imgui_rs::*;
148    /// # let mut ctx = Context::create();
149    /// # let ui = ctx.frame();
150    /// ui.slider_config("Example", i8::MIN, i8::MAX)
151    ///     .range(4, 8)
152    ///     // Remember to call .build()
153    ///     ;
154    /// ```
155    ///
156    /// It is safe, though up to C++ Dear ImGui, on how to handle when
157    /// `min > max`.
158    ///
159    /// Note for f32 and f64 sliders, Dear ImGui limits each range endpoint to
160    /// finite values within half their full range (e.g.
161    /// `-f32::MAX/2.0..=f32::MAX/2.0`). Specifying a value outside this range
162    /// will panic before calling into Dear ImGui.
163    /// For large ranged values, consider using input widgets instead
164    #[inline]
165    pub fn range(mut self, min: Data, max: Data) -> Self {
166        self.min = min;
167        self.max = max;
168        self
169    }
170
171    /// Sets the display format using *a C-style printf string*
172    #[inline]
173    pub fn display_format<Format2: AsRef<str>>(
174        self,
175        display_format: Format2,
176    ) -> Slider<'ui, Label, Data, Format2> {
177        Slider {
178            ui: self.ui,
179            label: self.label,
180            min: self.min,
181            max: self.max,
182            display_format: Some(display_format),
183            flags: self.flags,
184        }
185    }
186
187    /// Replaces all current settings with the given flags
188    #[inline]
189    pub fn flags(mut self, flags: SliderFlags) -> Self {
190        self.flags = flags;
191        self
192    }
193
194    /// Builds a slider that is bound to the given value.
195    ///
196    /// Returns true if the slider value was changed.
197    pub fn build(self, value: &mut Data) -> bool {
198        validate_slider_preconditions("Slider::build()", &self.min, &self.max, self.flags);
199        unsafe {
200            let (label, display_format) = self
201                .ui
202                .scratch_txt_with_opt(self.label, self.display_format);
203
204            sys::igSliderScalar(
205                label,
206                Data::KIND as i32,
207                value as *mut Data as *mut c_void,
208                &self.min as *const Data as *const c_void,
209                &self.max as *const Data as *const c_void,
210                display_format,
211                self.flags.bits(),
212            )
213        }
214    }
215
216    /// Builds a horizontal array of multiple sliders attached to the given slice.
217    ///
218    /// Returns true if any slider value was changed.
219    pub fn build_array(self, values: &mut [Data]) -> bool {
220        validate_slider_preconditions("Slider::build_array()", &self.min, &self.max, self.flags);
221        let count = component_count_i32("Slider::build_array()", values.len());
222        if self.flags.contains(SliderFlags::COLOR_MARKERS) {
223            assert!(
224                count <= 4,
225                "Slider::build_array() supports at most 4 components with COLOR_MARKERS"
226            );
227        }
228        unsafe {
229            let (label, display_format) = self
230                .ui
231                .scratch_txt_with_opt(self.label, self.display_format);
232
233            sys::igSliderScalarN(
234                label,
235                Data::KIND as i32,
236                values.as_mut_ptr() as *mut c_void,
237                count,
238                &self.min as *const Data as *const c_void,
239                &self.max as *const Data as *const c_void,
240                display_format,
241                self.flags.bits(),
242            )
243        }
244    }
245}
246
247/// Builder for a vertical slider widget.
248#[derive(Clone, Debug)]
249#[must_use]
250pub struct VerticalSlider<Label, Data, Format = &'static str> {
251    label: Label,
252    size: [f32; 2],
253    min: Data,
254    max: Data,
255    display_format: Option<Format>,
256    flags: SliderFlags,
257}
258
259impl<Label, Data> VerticalSlider<Label, Data>
260where
261    Label: AsRef<str>,
262    Data: DataTypeKind,
263{
264    /// Constructs a new vertical slider builder with the given size and range.
265    ///
266    /// ```no_run
267    /// # use dear_imgui_rs::*;
268    /// # let mut ctx = Context::create();
269    /// # let ui = ctx.frame();
270    /// VerticalSlider::new("Example", [20.0, 20.0], i8::MIN, i8::MAX)
271    ///     .range(4, 8)
272    ///     // Remember to call .build(&ui)
273    ///     ;
274    /// ```
275    ///
276    /// It is safe, though up to C++ Dear ImGui, on how to handle when
277    /// `min > max`.
278    #[doc(alias = "VSliderScalar")]
279    pub fn new(label: Label, size: impl Into<[f32; 2]>, min: Data, max: Data) -> Self {
280        VerticalSlider {
281            label,
282            size: size.into(),
283            min,
284            max,
285            display_format: None,
286            flags: SliderFlags::NONE,
287        }
288    }
289}
290
291impl<Label, Data, Format> VerticalSlider<Label, Data, Format>
292where
293    Label: AsRef<str>,
294    Data: DataTypeKind,
295    Format: AsRef<str>,
296{
297    /// Sets the range for the vertical slider.
298    ///
299    /// ```no_run
300    /// # use dear_imgui_rs::*;
301    /// # let mut ctx = Context::create();
302    /// # let ui = ctx.frame();
303    /// VerticalSlider::new("Example", [20.0, 20.0], i8::MIN, i8::MAX)
304    ///     .range(4, 8)
305    ///     // Remember to call .build(&ui)
306    ///     ;
307    /// ```
308    ///
309    /// It is safe, though up to C++ Dear ImGui, on how to handle when
310    /// `min > max`.
311    #[inline]
312    pub fn range(mut self, min: Data, max: Data) -> Self {
313        self.min = min;
314        self.max = max;
315        self
316    }
317
318    /// Sets the display format using *a C-style printf string*
319    #[inline]
320    pub fn display_format<Format2: AsRef<str>>(
321        self,
322        display_format: Format2,
323    ) -> VerticalSlider<Label, Data, Format2> {
324        VerticalSlider {
325            label: self.label,
326            size: self.size,
327            min: self.min,
328            max: self.max,
329            display_format: Some(display_format),
330            flags: self.flags,
331        }
332    }
333
334    /// Replaces all current settings with the given flags
335    #[inline]
336    pub fn flags(mut self, flags: SliderFlags) -> Self {
337        self.flags = flags;
338        self
339    }
340
341    /// Builds a vertical slider that is bound to the given value.
342    ///
343    /// Returns true if the slider value was changed.
344    pub fn build(self, ui: &Ui, value: &mut Data) -> bool {
345        validate_slider_preconditions("VerticalSlider::build()", &self.min, &self.max, self.flags);
346        unsafe {
347            let (label, display_format) = ui.scratch_txt_with_opt(self.label, self.display_format);
348            let size = sys::ImVec2::new(self.size[0], self.size[1]);
349
350            sys::igVSliderScalar(
351                label,
352                size,
353                Data::KIND as i32,
354                value as *mut Data as *mut c_void,
355                &self.min as *const Data as *const c_void,
356                &self.max as *const Data as *const c_void,
357                display_format,
358                self.flags.bits(),
359            )
360        }
361    }
362}
363
364/// Builder for an angle slider widget.
365#[derive(Copy, Clone, Debug)]
366#[must_use]
367pub struct AngleSlider<Label, Format = &'static str> {
368    label: Label,
369    min_degrees: f32,
370    max_degrees: f32,
371    display_format: Format,
372    flags: SliderFlags,
373}
374
375impl<Label> AngleSlider<Label>
376where
377    Label: AsRef<str>,
378{
379    /// Constructs a new angle slider builder, where its minimum defaults to -360.0 and
380    /// maximum defaults to 360.0
381    #[doc(alias = "SliderAngle")]
382    pub fn new(label: Label) -> Self {
383        AngleSlider {
384            label,
385            min_degrees: -360.0,
386            max_degrees: 360.0,
387            display_format: "%.0f deg",
388            flags: SliderFlags::NONE,
389        }
390    }
391}
392
393impl<Label, Format> AngleSlider<Label, Format>
394where
395    Label: AsRef<str>,
396    Format: AsRef<str>,
397{
398    /// Sets the range in degrees (inclusive)
399    /// ```no_run
400    /// # use dear_imgui_rs::*;
401    /// # let mut ctx = Context::create();
402    /// # let ui = ctx.frame();
403    /// AngleSlider::new("Example")
404    ///     .range_degrees(-20.0, 20.0)
405    ///     // Remember to call .build(&ui)
406    ///     ;
407    /// ```
408    ///
409    /// It is safe, though up to C++ Dear ImGui, on how to handle when
410    /// `min > max`.
411    #[inline]
412    pub fn range_degrees(mut self, min_degrees: f32, max_degrees: f32) -> Self {
413        self.min_degrees = min_degrees;
414        self.max_degrees = max_degrees;
415        self
416    }
417
418    /// Sets the minimum value (in degrees)
419    #[inline]
420    pub fn min_degrees(mut self, min_degrees: f32) -> Self {
421        self.min_degrees = min_degrees;
422        self
423    }
424
425    /// Sets the maximum value (in degrees)
426    #[inline]
427    pub fn max_degrees(mut self, max_degrees: f32) -> Self {
428        self.max_degrees = max_degrees;
429        self
430    }
431
432    /// Sets the display format using *a C-style printf string*
433    #[inline]
434    pub fn display_format<Format2: AsRef<str>>(
435        self,
436        display_format: Format2,
437    ) -> AngleSlider<Label, Format2> {
438        AngleSlider {
439            label: self.label,
440            min_degrees: self.min_degrees,
441            max_degrees: self.max_degrees,
442            display_format,
443            flags: self.flags,
444        }
445    }
446
447    /// Replaces all current settings with the given flags
448    #[inline]
449    pub fn flags(mut self, flags: SliderFlags) -> Self {
450        self.flags = flags;
451        self
452    }
453
454    /// Builds an angle slider that is bound to the given value (in radians).
455    ///
456    /// Returns true if the slider value was changed.
457    pub fn build(self, ui: &Ui, value_rad: &mut f32) -> bool {
458        validate_slider_flags("AngleSlider::build()", self.flags);
459        validate_slider_range("AngleSlider::build()", &self.min_degrees, &self.max_degrees);
460        unsafe {
461            let (label, display_format) = ui.scratch_txt_two(self.label, self.display_format);
462
463            sys::igSliderAngle(
464                label,
465                value_rad as *mut _,
466                self.min_degrees,
467                self.max_degrees,
468                display_format,
469                self.flags.bits(),
470            )
471        }
472    }
473}
474
475bitflags::bitflags! {
476    /// Flags for slider widgets.
477    #[repr(transparent)]
478    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
479    pub struct SliderFlags: i32 {
480        /// No flags
481        const NONE = 0;
482        /// Clamp on input when editing via CTRL+Click or direct text input.
483        ///
484        /// This is one of the two bits combined by `ALWAYS_CLAMP`.
485        const CLAMP_ON_INPUT = sys::ImGuiSliderFlags_ClampOnInput as i32;
486        /// Clamp zero-range sliders to avoid a zero-sized range.
487        ///
488        /// This is one of the two bits combined by `ALWAYS_CLAMP`.
489        const CLAMP_ZERO_RANGE = sys::ImGuiSliderFlags_ClampZeroRange as i32;
490        /// Disable small "smart" speed tweaks for very small/large ranges.
491        ///
492        /// Useful when precise, linear dragging behavior is desired.
493        const NO_SPEED_TWEAKS = sys::ImGuiSliderFlags_NoSpeedTweaks as i32;
494        /// Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds.
495        const ALWAYS_CLAMP = sys::ImGuiSliderFlags_AlwaysClamp as i32;
496        /// Make the widget logarithmic (linear otherwise). Consider using ImGuiSliderFlags_NoRoundToFormat with this if using a format-string with small amount of digits.
497        const LOGARITHMIC = sys::ImGuiSliderFlags_Logarithmic as i32;
498        /// Disable rounding underlying value to match precision of the display format string (e.g. %.3f values are rounded to those 3 digits)
499        const NO_ROUND_TO_FORMAT = sys::ImGuiSliderFlags_NoRoundToFormat as i32;
500        /// Disable CTRL+Click or Enter key allowing to input text directly into the widget
501        const NO_INPUT = sys::ImGuiSliderFlags_NoInput as i32;
502        /// Draw R/G/B/A color markers on each component.
503        ///
504        /// Dear ImGui only defines four default component colors.
505        const COLOR_MARKERS = sys::ImGuiSliderFlags_ColorMarkers as i32;
506    }
507}
508
509impl Ui {
510    /// Creates a new slider widget. Returns true if the value has been edited.
511    pub fn slider<T: AsRef<str>, K: DataTypeKind>(
512        &self,
513        label: T,
514        min: K,
515        max: K,
516        value: &mut K,
517    ) -> bool {
518        self.slider_config(label, min, max).build(value)
519    }
520
521    /// Creates a new unbuilt Slider.
522    pub fn slider_config<T: AsRef<str>, K: DataTypeKind>(
523        &self,
524        label: T,
525        min: K,
526        max: K,
527    ) -> Slider<'_, T, K> {
528        Slider {
529            ui: self,
530            label,
531            min,
532            max,
533            display_format: Option::<&'static str>::None,
534            flags: SliderFlags::NONE,
535        }
536    }
537
538    /// Creates a float slider
539    #[doc(alias = "SliderFloat")]
540    pub fn slider_f32(&self, label: impl AsRef<str>, value: &mut f32, min: f32, max: f32) -> bool {
541        self.slider_config(label, min, max).build(value)
542    }
543
544    /// Creates an integer slider
545    #[doc(alias = "SliderInt")]
546    pub fn slider_i32(&self, label: impl AsRef<str>, value: &mut i32, min: i32, max: i32) -> bool {
547        self.slider_config(label, min, max).build(value)
548    }
549
550    /// Creates a float2 slider
551    #[doc(alias = "SliderFloat2")]
552    pub fn slider_float2(
553        &self,
554        label: impl AsRef<str>,
555        value: &mut [f32; 2],
556        min: f32,
557        max: f32,
558    ) -> bool {
559        self.slider_config(label, min, max)
560            .build_array(value.as_mut_slice())
561    }
562
563    /// Creates a float3 slider
564    #[doc(alias = "SliderFloat3")]
565    pub fn slider_float3(
566        &self,
567        label: impl AsRef<str>,
568        value: &mut [f32; 3],
569        min: f32,
570        max: f32,
571    ) -> bool {
572        self.slider_config(label, min, max)
573            .build_array(value.as_mut_slice())
574    }
575
576    /// Creates a float4 slider
577    #[doc(alias = "SliderFloat4")]
578    pub fn slider_float4(
579        &self,
580        label: impl AsRef<str>,
581        value: &mut [f32; 4],
582        min: f32,
583        max: f32,
584    ) -> bool {
585        self.slider_config(label, min, max)
586            .build_array(value.as_mut_slice())
587    }
588
589    /// Creates an int2 slider
590    #[doc(alias = "SliderInt2")]
591    pub fn slider_int2(
592        &self,
593        label: impl AsRef<str>,
594        value: &mut [i32; 2],
595        min: i32,
596        max: i32,
597    ) -> bool {
598        self.slider_config(label, min, max)
599            .build_array(value.as_mut_slice())
600    }
601
602    /// Creates an int3 slider
603    #[doc(alias = "SliderInt3")]
604    pub fn slider_int3(
605        &self,
606        label: impl AsRef<str>,
607        value: &mut [i32; 3],
608        min: i32,
609        max: i32,
610    ) -> bool {
611        self.slider_config(label, min, max)
612            .build_array(value.as_mut_slice())
613    }
614
615    /// Creates an int4 slider
616    #[doc(alias = "SliderInt4")]
617    pub fn slider_int4(
618        &self,
619        label: impl AsRef<str>,
620        value: &mut [i32; 4],
621        min: i32,
622        max: i32,
623    ) -> bool {
624        self.slider_config(label, min, max)
625            .build_array(value.as_mut_slice())
626    }
627
628    /// Creates a vertical slider
629    #[doc(alias = "VSliderFloat")]
630    pub fn v_slider_f32(
631        &self,
632        label: impl AsRef<str>,
633        size: impl Into<[f32; 2]>,
634        value: &mut f32,
635        min: f32,
636        max: f32,
637    ) -> bool {
638        VerticalSlider::new(label, size, min, max).build(self, value)
639    }
640
641    /// Creates a vertical integer slider
642    #[doc(alias = "VSliderInt")]
643    pub fn v_slider_i32(
644        &self,
645        label: impl AsRef<str>,
646        size: impl Into<[f32; 2]>,
647        value: &mut i32,
648        min: i32,
649        max: i32,
650    ) -> bool {
651        VerticalSlider::new(label, size, min, max).build(self, value)
652    }
653
654    /// Creates an angle slider (value in radians)
655    #[doc(alias = "SliderAngle")]
656    pub fn slider_angle(&self, label: impl AsRef<str>, value_rad: &mut f32) -> bool {
657        AngleSlider::new(label).build(self, value_rad)
658    }
659}