Skip to main content

dear_imgui_rs/widget/
drag.rs

1//! Drag slider widgets for numeric input
2//!
3//! Drag sliders allow users to modify numeric values by dragging with the mouse.
4//! They provide a more intuitive way to adjust values compared to text input.
5
6use std::os::raw::c_void;
7use std::ptr;
8
9use crate::Ui;
10use crate::internal::{DataTypeKind, component_count_i32};
11use crate::sys;
12use crate::widget::slider::SliderFlags;
13
14fn validate_drag_flags(caller: &str, flags: DragFlags) {
15    let unsupported = flags.bits() & !DragFlags::all().bits();
16    assert!(
17        unsupported == 0,
18        "{caller} received unsupported ImGuiSliderFlags bits: 0x{unsupported:X}"
19    );
20}
21
22bitflags::bitflags! {
23    /// Flags for drag widgets.
24    ///
25    /// Dear ImGui shares the underlying `ImGuiSliderFlags` enum between drag
26    /// and slider widgets, but `WRAP_AROUND` is supported by DragXXX only.
27    #[repr(transparent)]
28    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29    pub struct DragFlags: i32 {
30        /// No flags.
31        const NONE = 0;
32        /// Wrap the value around when exceeding the current range.
33        const WRAP_AROUND = sys::ImGuiSliderFlags_WrapAround as i32;
34        /// Clamp on input when editing via CTRL+Click or direct text input.
35        const CLAMP_ON_INPUT = sys::ImGuiSliderFlags_ClampOnInput as i32;
36        /// Clamp zero-range drags to avoid a zero-sized range.
37        const CLAMP_ZERO_RANGE = sys::ImGuiSliderFlags_ClampZeroRange as i32;
38        /// Disable small "smart" speed tweaks for very small/large ranges.
39        const NO_SPEED_TWEAKS = sys::ImGuiSliderFlags_NoSpeedTweaks as i32;
40        /// Clamp value to min/max bounds when input manually with CTRL+Click.
41        const ALWAYS_CLAMP = sys::ImGuiSliderFlags_AlwaysClamp as i32;
42        /// Make the widget logarithmic.
43        const LOGARITHMIC = sys::ImGuiSliderFlags_Logarithmic as i32;
44        /// Disable rounding underlying value to match the display format.
45        const NO_ROUND_TO_FORMAT = sys::ImGuiSliderFlags_NoRoundToFormat as i32;
46        /// Disable CTRL+Click or Enter key allowing direct text input.
47        const NO_INPUT = sys::ImGuiSliderFlags_NoInput as i32;
48        /// Draw R/G/B/A color markers on each component.
49        ///
50        /// Dear ImGui only defines four default component colors.
51        const COLOR_MARKERS = sys::ImGuiSliderFlags_ColorMarkers as i32;
52    }
53}
54
55impl From<SliderFlags> for DragFlags {
56    fn from(flags: SliderFlags) -> Self {
57        Self::from_bits_retain(flags.bits())
58    }
59}
60
61impl Ui {
62    /// Creates a new drag slider widget. Returns true if the value has been edited.
63    pub fn drag<T: AsRef<str>, K: DataTypeKind>(&self, label: T, value: &mut K) -> bool {
64        Drag::new(label).build(self, value)
65    }
66
67    /// Creates a new unbuilt Drag.
68    pub fn drag_config<T: AsRef<str>, K: DataTypeKind>(&self, label: T) -> Drag<K, T> {
69        Drag::new(label)
70    }
71
72    /// Creates a drag float2 slider (2 floats)
73    #[doc(alias = "DragFloat2")]
74    pub fn drag_float2(&self, label: impl AsRef<str>, values: &mut [f32; 2]) -> bool {
75        unsafe {
76            let label_cstr = self.scratch_txt(label);
77            sys::igDragFloat2(
78                label_cstr,
79                values.as_mut_ptr(),
80                1.0,
81                0.0,
82                0.0,
83                ptr::null(),
84                0,
85            )
86        }
87    }
88
89    /// Creates a drag float3 slider (3 floats)
90    #[doc(alias = "DragFloat3")]
91    pub fn drag_float3(&self, label: impl AsRef<str>, values: &mut [f32; 3]) -> bool {
92        unsafe {
93            let label_cstr = self.scratch_txt(label);
94            sys::igDragFloat3(
95                label_cstr,
96                values.as_mut_ptr(),
97                1.0,
98                0.0,
99                0.0,
100                ptr::null(),
101                0,
102            )
103        }
104    }
105
106    /// Creates a drag float4 slider (4 floats)
107    #[doc(alias = "DragFloat4")]
108    pub fn drag_float4(&self, label: impl AsRef<str>, values: &mut [f32; 4]) -> bool {
109        unsafe {
110            let label_cstr = self.scratch_txt(label);
111            sys::igDragFloat4(
112                label_cstr,
113                values.as_mut_ptr(),
114                1.0,
115                0.0,
116                0.0,
117                ptr::null(),
118                0,
119            )
120        }
121    }
122
123    /// Creates a drag int2 slider (2 ints)
124    #[doc(alias = "DragInt2")]
125    pub fn drag_int2(&self, label: impl AsRef<str>, values: &mut [i32; 2]) -> bool {
126        unsafe {
127            let label_cstr = self.scratch_txt(label);
128            sys::igDragInt2(label_cstr, values.as_mut_ptr(), 1.0, 0, 0, ptr::null(), 0)
129        }
130    }
131
132    /// Creates a drag int3 slider (3 ints)
133    #[doc(alias = "DragInt3")]
134    pub fn drag_int3(&self, label: impl AsRef<str>, values: &mut [i32; 3]) -> bool {
135        unsafe {
136            let label_cstr = self.scratch_txt(label);
137            sys::igDragInt3(label_cstr, values.as_mut_ptr(), 1.0, 0, 0, ptr::null(), 0)
138        }
139    }
140
141    /// Creates a drag int4 slider (4 ints)
142    #[doc(alias = "DragInt4")]
143    pub fn drag_int4(&self, label: impl AsRef<str>, values: &mut [i32; 4]) -> bool {
144        unsafe {
145            let label_cstr = self.scratch_txt(label);
146            sys::igDragInt4(label_cstr, values.as_mut_ptr(), 1.0, 0, 0, ptr::null(), 0)
147        }
148    }
149}
150
151/// Builder for a drag slider widget
152#[derive(Clone, Debug)]
153#[must_use]
154pub struct Drag<T, L, F = &'static str> {
155    label: L,
156    speed: f32,
157    min: Option<T>,
158    max: Option<T>,
159    display_format: Option<F>,
160    flags: DragFlags,
161}
162
163impl<L: AsRef<str>, T: DataTypeKind> Drag<T, L> {
164    /// Constructs a new drag slider builder
165    #[doc(alias = "DragScalar", alias = "DragScalarN")]
166    pub fn new(label: L) -> Self {
167        Drag {
168            label,
169            speed: 1.0,
170            min: None,
171            max: None,
172            display_format: None,
173            flags: DragFlags::empty(),
174        }
175    }
176}
177
178impl<L: AsRef<str>, T: DataTypeKind, F: AsRef<str>> Drag<T, L, F> {
179    /// Sets the range (inclusive)
180    pub fn range(mut self, min: T, max: T) -> Self {
181        self.min = Some(min);
182        self.max = Some(max);
183        self
184    }
185
186    /// Sets the value increment for a movement of one pixel
187    ///
188    /// Example: speed=0.2 means mouse needs to move 5 pixels to increase the slider value by 1
189    pub fn speed(mut self, speed: f32) -> Self {
190        self.speed = speed;
191        self
192    }
193
194    /// Sets the display format using *a C-style printf string*
195    pub fn display_format<F2: AsRef<str>>(self, display_format: F2) -> Drag<T, L, F2> {
196        Drag {
197            label: self.label,
198            speed: self.speed,
199            min: self.min,
200            max: self.max,
201            display_format: Some(display_format),
202            flags: self.flags,
203        }
204    }
205
206    /// Replaces all current settings with the given flags
207    pub fn flags(mut self, flags: impl Into<DragFlags>) -> Self {
208        self.flags = flags.into();
209        self
210    }
211
212    /// Builds a drag slider that is bound to the given value
213    ///
214    /// Returns true if the slider value was changed
215    pub fn build(self, ui: &Ui, value: &mut T) -> bool {
216        validate_drag_flags("Drag::build()", self.flags);
217        unsafe {
218            let (one, two) = ui.scratch_txt_with_opt(self.label, self.display_format);
219
220            sys::igDragScalar(
221                one,
222                T::KIND as i32,
223                value as *mut T as *mut c_void,
224                self.speed,
225                self.min
226                    .as_ref()
227                    .map(|min| min as *const T)
228                    .unwrap_or(ptr::null()) as *const c_void,
229                self.max
230                    .as_ref()
231                    .map(|max| max as *const T)
232                    .unwrap_or(ptr::null()) as *const c_void,
233                two,
234                self.flags.bits(),
235            )
236        }
237    }
238
239    /// Builds a horizontal array of multiple drag sliders attached to the given slice
240    ///
241    /// Returns true if any slider value was changed
242    pub fn build_array(self, ui: &Ui, values: &mut [T]) -> bool {
243        validate_drag_flags("Drag::build_array()", self.flags);
244        let count = component_count_i32("Drag::build_array()", values.len());
245        if self.flags.contains(DragFlags::COLOR_MARKERS) {
246            assert!(
247                count <= 4,
248                "Drag::build_array() supports at most 4 components with COLOR_MARKERS"
249            );
250        }
251        unsafe {
252            let (one, two) = ui.scratch_txt_with_opt(self.label, self.display_format);
253
254            sys::igDragScalarN(
255                one,
256                T::KIND as i32,
257                values.as_mut_ptr() as *mut c_void,
258                count,
259                self.speed,
260                self.min
261                    .as_ref()
262                    .map(|min| min as *const T)
263                    .unwrap_or(ptr::null()) as *const c_void,
264                self.max
265                    .as_ref()
266                    .map(|max| max as *const T)
267                    .unwrap_or(ptr::null()) as *const c_void,
268                two,
269                self.flags.bits(),
270            )
271        }
272    }
273}
274
275/// Builder for a drag range slider widget
276#[derive(Clone, Debug)]
277#[must_use]
278pub struct DragRange<T, L, F = &'static str, M = &'static str> {
279    label: L,
280    speed: f32,
281    min: Option<T>,
282    max: Option<T>,
283    display_format: Option<F>,
284    max_display_format: Option<M>,
285    flags: DragFlags,
286}
287
288impl<T: DataTypeKind, L: AsRef<str>> DragRange<T, L> {
289    /// Constructs a new drag range slider builder
290    #[doc(alias = "DragIntRange2", alias = "DragFloatRange2")]
291    pub fn new(label: L) -> DragRange<T, L> {
292        DragRange {
293            label,
294            speed: 1.0,
295            min: None,
296            max: None,
297            display_format: None,
298            max_display_format: None,
299            flags: DragFlags::NONE,
300        }
301    }
302}
303
304impl<T, L, F, M> DragRange<T, L, F, M>
305where
306    T: DataTypeKind,
307    L: AsRef<str>,
308    F: AsRef<str>,
309    M: AsRef<str>,
310{
311    /// Sets the range (inclusive)
312    pub fn range(mut self, min: T, max: T) -> Self {
313        self.min = Some(min);
314        self.max = Some(max);
315        self
316    }
317
318    /// Sets the value increment for a movement of one pixel
319    ///
320    /// Example: speed=0.2 means mouse needs to move 5 pixels to increase the slider value by 1
321    pub fn speed(mut self, speed: f32) -> Self {
322        self.speed = speed;
323        self
324    }
325
326    /// Sets the display format using *a C-style printf string*
327    pub fn display_format<F2: AsRef<str>>(self, display_format: F2) -> DragRange<T, L, F2, M> {
328        DragRange {
329            label: self.label,
330            speed: self.speed,
331            min: self.min,
332            max: self.max,
333            display_format: Some(display_format),
334            max_display_format: self.max_display_format,
335            flags: self.flags,
336        }
337    }
338
339    /// Sets the display format for the max value using *a C-style printf string*
340    pub fn max_display_format<M2: AsRef<str>>(
341        self,
342        max_display_format: M2,
343    ) -> DragRange<T, L, F, M2> {
344        DragRange {
345            label: self.label,
346            speed: self.speed,
347            min: self.min,
348            max: self.max,
349            display_format: self.display_format,
350            max_display_format: Some(max_display_format),
351            flags: self.flags,
352        }
353    }
354
355    /// Replaces all current settings with the given flags
356    pub fn flags(mut self, flags: impl Into<DragFlags>) -> Self {
357        self.flags = flags.into();
358        self
359    }
360}
361
362impl<L, F, M> DragRange<f32, L, F, M>
363where
364    L: AsRef<str>,
365    F: AsRef<str>,
366    M: AsRef<str>,
367{
368    /// Builds a drag range slider that is bound to the given min/max values
369    ///
370    /// Returns true if the slider value was changed
371    #[doc(alias = "DragFloatRange2")]
372    pub fn build(self, ui: &Ui, min: &mut f32, max: &mut f32) -> bool {
373        validate_drag_flags("DragRange::build()", self.flags);
374        unsafe {
375            let buffer = &mut *ui.scratch_buffer().get();
376            buffer.refresh_buffer();
377
378            let label_start = buffer.push(self.label);
379            let display_format = self.display_format.as_ref().map(|v| buffer.push(v));
380            let max_display_format = self.max_display_format.as_ref().map(|v| buffer.push(v));
381
382            let label = buffer.offset(label_start);
383            let display_format = display_format
384                .map(|v| buffer.offset(v))
385                .unwrap_or_else(std::ptr::null);
386            let max_display_format = max_display_format
387                .map(|v| buffer.offset(v))
388                .unwrap_or_else(std::ptr::null);
389
390            sys::igDragFloatRange2(
391                label,
392                min as *mut f32,
393                max as *mut f32,
394                self.speed,
395                self.min.unwrap_or(0.0),
396                self.max.unwrap_or(0.0),
397                display_format,
398                max_display_format,
399                self.flags.bits(),
400            )
401        }
402    }
403}
404
405impl<L, F, M> DragRange<i32, L, F, M>
406where
407    L: AsRef<str>,
408    F: AsRef<str>,
409    M: AsRef<str>,
410{
411    /// Builds a drag range slider that is bound to the given min/max values
412    ///
413    /// Returns true if the slider value was changed
414    #[doc(alias = "DragIntRange2")]
415    pub fn build(self, ui: &Ui, min: &mut i32, max: &mut i32) -> bool {
416        validate_drag_flags("DragRange::build()", self.flags);
417        unsafe {
418            let buffer = &mut *ui.scratch_buffer().get();
419            buffer.refresh_buffer();
420
421            let label_start = buffer.push(self.label);
422            let display_format = self.display_format.as_ref().map(|v| buffer.push(v));
423            let max_display_format = self.max_display_format.as_ref().map(|v| buffer.push(v));
424
425            let label = buffer.offset(label_start);
426            let display_format = display_format
427                .map(|v| buffer.offset(v))
428                .unwrap_or_else(std::ptr::null);
429            let max_display_format = max_display_format
430                .map(|v| buffer.offset(v))
431                .unwrap_or_else(std::ptr::null);
432
433            sys::igDragIntRange2(
434                label,
435                min as *mut i32,
436                max as *mut i32,
437                self.speed,
438                self.min.unwrap_or(0),
439                self.max.unwrap_or(0),
440                display_format,
441                max_display_format,
442                self.flags.bits(),
443            )
444        }
445    }
446}