arcdps_imgui\input/
mouse.rs

1use std::ptr;
2
3use crate::sys;
4use crate::Ui;
5
6/// Represents one of the supported mouse buttons
7#[derive(Copy, Clone, Eq, PartialEq, Debug)]
8pub enum MouseButton {
9    Left = 0,
10    Right = 1,
11    Middle = 2,
12    Extra1 = 3,
13    Extra2 = 4,
14}
15
16impl MouseButton {
17    /// All possible `MouseButton` varirants
18    pub const VARIANTS: [MouseButton; MouseButton::COUNT] = [
19        MouseButton::Left,
20        MouseButton::Right,
21        MouseButton::Middle,
22        MouseButton::Extra1,
23        MouseButton::Extra2,
24    ];
25    /// Total count of `MouseButton` variants
26    pub const COUNT: usize = 5;
27}
28
29#[test]
30fn test_mouse_button_variants() {
31    for (idx, &value) in MouseButton::VARIANTS.iter().enumerate() {
32        assert_eq!(idx, value as usize);
33    }
34}
35
36/// Mouse cursor type identifier
37#[repr(i32)]
38#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
39// TODO: this should just be `#[allow(clippy::upper_case_acronyms)]`, but doing
40// so in a way that works before it stabilizes is a pain (in part because
41// `unknown_clippy_lints` was renamed to unknown_lints). Oh well, it's over a
42// small amount of code.
43#[allow(warnings)]
44pub enum MouseCursor {
45    Arrow = sys::ImGuiMouseCursor_Arrow,
46    /// Automatically used when hovering over text inputs, etc.
47    TextInput = sys::ImGuiMouseCursor_TextInput,
48    /// Not used automatically
49    ResizeAll = sys::ImGuiMouseCursor_ResizeAll,
50    /// Automatically used when hovering over a horizontal border
51    ResizeNS = sys::ImGuiMouseCursor_ResizeNS,
52    /// Automatically used when hovering over a vertical border or a column
53    ResizeEW = sys::ImGuiMouseCursor_ResizeEW,
54    /// Automatically used when hovering over the bottom-left corner of a window
55    ResizeNESW = sys::ImGuiMouseCursor_ResizeNESW,
56    /// Automatically used when hovering over the bottom-right corner of a window
57    ResizeNWSE = sys::ImGuiMouseCursor_ResizeNWSE,
58    /// Not used automatically, use for e.g. hyperlinks
59    Hand = sys::ImGuiMouseCursor_Hand,
60    /// When hovering something with disallowed interactions.
61    ///
62    /// Usually a crossed circle.
63    NotAllowed = sys::ImGuiMouseCursor_NotAllowed,
64}
65
66impl MouseCursor {
67    /// All possible `MouseCursor` varirants
68    pub const VARIANTS: [MouseCursor; MouseCursor::COUNT] = [
69        MouseCursor::Arrow,
70        MouseCursor::TextInput,
71        MouseCursor::ResizeAll,
72        MouseCursor::ResizeNS,
73        MouseCursor::ResizeEW,
74        MouseCursor::ResizeNESW,
75        MouseCursor::ResizeNWSE,
76        MouseCursor::Hand,
77        MouseCursor::NotAllowed,
78    ];
79    /// Total count of `MouseCursor` variants
80    pub const COUNT: usize = sys::ImGuiMouseCursor_COUNT as usize;
81}
82
83#[test]
84fn test_mouse_cursor_variants() {
85    for (idx, &value) in MouseCursor::VARIANTS.iter().enumerate() {
86        assert_eq!(idx, value as usize);
87    }
88}
89
90/// # Input: Mouse
91impl<'ui> Ui<'ui> {
92    /// Returns true if the given mouse button is held down.
93    ///
94    /// Equivalent to indexing the Io struct with the button, e.g. `ui.io()[button]`.
95    #[doc(alias = "IsMouseDown")]
96    pub fn is_mouse_down(&self, button: MouseButton) -> bool {
97        unsafe { sys::igIsMouseDown(button as i32) }
98    }
99    /// Returns true if any mouse button is held down
100    #[doc(alias = "IsAnyMouseDown")]
101    pub fn is_any_mouse_down(&self) -> bool {
102        unsafe { sys::igIsAnyMouseDown() }
103    }
104    /// Returns true if the given mouse button was clicked (went from !down to down)
105    #[doc(alias = "IsMouseClicked")]
106    pub fn is_mouse_clicked(&self, button: MouseButton) -> bool {
107        unsafe { sys::igIsMouseClicked(button as i32, false) }
108    }
109    /// Returns true if the given mouse button was double-clicked
110    #[doc(alias = "IsMouseDoubleClicked")]
111    pub fn is_mouse_double_clicked(&self, button: MouseButton) -> bool {
112        unsafe { sys::igIsMouseDoubleClicked(button as i32) }
113    }
114    /// Returns true if the given mouse button was released (went from down to !down)
115    #[doc(alias = "IsMouseReleased")]
116    pub fn is_mouse_released(&self, button: MouseButton) -> bool {
117        unsafe { sys::igIsMouseReleased(button as i32) }
118    }
119    /// Returns true if the mouse is currently dragging with the given mouse button held down
120    #[doc(alias = "IsMouseDragging")]
121    pub fn is_mouse_dragging(&self, button: MouseButton) -> bool {
122        unsafe { sys::igIsMouseDragging(button as i32, -1.0) }
123    }
124    /// Returns true if the mouse is currently dragging with the given mouse button held down.
125    ///
126    /// If the given threshold is invalid or negative, the global distance threshold is used
127    /// (`io.mouse_drag_threshold`).
128    #[doc(alias = "IsMouseDragging")]
129    pub fn is_mouse_dragging_with_threshold(&self, button: MouseButton, threshold: f32) -> bool {
130        unsafe { sys::igIsMouseDragging(button as i32, threshold) }
131    }
132    /// Returns true if the mouse is hovering over the given bounding rect.
133    ///
134    /// Clipped by current clipping settings, but disregards other factors like focus, window
135    /// ordering, modal popup blocking.
136    pub fn is_mouse_hovering_rect(&self, r_min: [f32; 2], r_max: [f32; 2]) -> bool {
137        unsafe { sys::igIsMouseHoveringRect(r_min.into(), r_max.into(), true) }
138    }
139    /// Returns the mouse position backed up at the time of opening a popup
140    #[doc(alias = "GetMousePosOnOpeningCurrentPopup")]
141    pub fn mouse_pos_on_opening_current_popup(&self) -> [f32; 2] {
142        let mut out = sys::ImVec2::zero();
143        unsafe { sys::igGetMousePosOnOpeningCurrentPopup(&mut out) };
144        out.into()
145    }
146
147    /// Returns the delta from the initial position when the left mouse button clicked.
148    ///
149    /// This is locked and returns [0.0, 0.0] until the mouse has moved past the global distance
150    /// threshold (`io.mouse_drag_threshold`).
151    ///
152    /// This is the same as [mouse_drag_delta_with_button](Self::mouse_drag_delta_with_button) with
153    /// `button` set to `MouseButton::Left`.
154    #[doc(alias = "GetMouseDragDelta")]
155    pub fn mouse_drag_delta(&self) -> [f32; 2] {
156        self.mouse_drag_delta_with_button(MouseButton::Left)
157    }
158
159    /// Returns the delta from the initial position when the given button was clicked.
160    ///
161    /// This is locked and returns [0.0, 0.0] until the mouse has moved past the global distance
162    /// threshold (`io.mouse_drag_threshold`).
163    ///
164    /// This is the same as [mouse_drag_delta_with_threshold](Self::mouse_drag_delta_with_threshold) with
165    /// `threshold` set to `-1.0`, which uses the global threshold `io.mouse_drag_threshold`.
166    #[doc(alias = "GetMouseDragDelta")]
167    pub fn mouse_drag_delta_with_button(&self, button: MouseButton) -> [f32; 2] {
168        self.mouse_drag_delta_with_threshold(button, -1.0)
169    }
170    /// Returns the delta from the initial clicking position.
171    ///
172    /// This is locked and returns [0.0, 0.0] until the mouse has moved past the given threshold.
173    /// If the given threshold is invalid or negative, the global distance threshold is used
174    /// (`io.mouse_drag_threshold`).
175    #[doc(alias = "GetMouseDragDelta")]
176    pub fn mouse_drag_delta_with_threshold(&self, button: MouseButton, threshold: f32) -> [f32; 2] {
177        let mut out = sys::ImVec2::zero();
178        unsafe { sys::igGetMouseDragDelta(&mut out, button as i32, threshold) };
179        out.into()
180    }
181    /// Resets the current delta from initial clicking position.
182    #[doc(alias = "ResetMouseDragDelta")]
183    pub fn reset_mouse_drag_delta(&self, button: MouseButton) {
184        // This mutates the Io struct, but targets an internal field so there can't be any
185        // references to it
186        unsafe { sys::igResetMouseDragDelta(button as i32) }
187    }
188    /// Returns the currently desired mouse cursor type.
189    ///
190    /// Returns `None` if no cursor should be displayed
191    #[doc(alias = "GetMouseCursor")]
192    pub fn mouse_cursor(&self) -> Option<MouseCursor> {
193        match unsafe { sys::igGetMouseCursor() } {
194            sys::ImGuiMouseCursor_Arrow => Some(MouseCursor::Arrow),
195            sys::ImGuiMouseCursor_TextInput => Some(MouseCursor::TextInput),
196            sys::ImGuiMouseCursor_ResizeAll => Some(MouseCursor::ResizeAll),
197            sys::ImGuiMouseCursor_ResizeNS => Some(MouseCursor::ResizeNS),
198            sys::ImGuiMouseCursor_ResizeEW => Some(MouseCursor::ResizeEW),
199            sys::ImGuiMouseCursor_ResizeNESW => Some(MouseCursor::ResizeNESW),
200            sys::ImGuiMouseCursor_ResizeNWSE => Some(MouseCursor::ResizeNWSE),
201            sys::ImGuiMouseCursor_Hand => Some(MouseCursor::Hand),
202            sys::ImGuiMouseCursor_NotAllowed => Some(MouseCursor::NotAllowed),
203            _ => None,
204        }
205    }
206    /// Sets the desired mouse cursor type.
207    ///
208    /// Passing `None` hides the mouse cursor.
209    #[doc(alias = "SetMouseCursor")]
210    pub fn set_mouse_cursor(&self, cursor_type: Option<MouseCursor>) {
211        unsafe {
212            sys::igSetMouseCursor(
213                cursor_type
214                    .map(|x| x as i32)
215                    .unwrap_or(sys::ImGuiMouseCursor_None),
216            );
217        }
218    }
219    #[doc(alias = "IsMousePosValid")]
220    pub fn is_current_mouse_pos_valid(&self) -> bool {
221        unsafe { sys::igIsMousePosValid(ptr::null()) }
222    }
223    #[doc(alias = "IsMousePosValid")]
224    pub fn is_mouse_pos_valid(&self, mouse_pos: [f32; 2]) -> bool {
225        unsafe { sys::igIsMousePosValid(&mouse_pos.into()) }
226    }
227}
228
229#[test]
230fn test_mouse_down_clicked_released() {
231    for &button in MouseButton::VARIANTS.iter() {
232        let (_guard, mut ctx) = crate::test::test_ctx_initialized();
233        {
234            ctx.io_mut().mouse_down = [false; 5];
235            let ui = ctx.frame();
236            assert!(!ui.is_mouse_down(button));
237            assert!(!ui.is_any_mouse_down());
238            assert!(!ui.is_mouse_clicked(button));
239            assert!(!ui.is_mouse_released(button));
240        }
241        {
242            ctx.io_mut()[button] = true;
243            let ui = ctx.frame();
244            assert!(ui.is_mouse_down(button));
245            assert!(ui.is_any_mouse_down());
246            assert!(ui.is_mouse_clicked(button));
247            assert!(!ui.is_mouse_released(button));
248        }
249        {
250            let ui = ctx.frame();
251            assert!(ui.is_mouse_down(button));
252            assert!(ui.is_any_mouse_down());
253            assert!(!ui.is_mouse_clicked(button));
254            assert!(!ui.is_mouse_released(button));
255        }
256        {
257            ctx.io_mut()[button] = false;
258            let ui = ctx.frame();
259            assert!(!ui.is_mouse_down(button));
260            assert!(!ui.is_any_mouse_down());
261            assert!(!ui.is_mouse_clicked(button));
262            assert!(ui.is_mouse_released(button));
263        }
264        {
265            let ui = ctx.frame();
266            assert!(!ui.is_mouse_down(button));
267            assert!(!ui.is_any_mouse_down());
268            assert!(!ui.is_mouse_clicked(button));
269            assert!(!ui.is_mouse_released(button));
270        }
271    }
272}
273
274#[test]
275fn test_mouse_double_click() {
276    let (_guard, mut ctx) = crate::test::test_ctx_initialized();
277    // Workaround for dear imgui bug/feature:
278    // If a button is clicked before io.mouse_double_click_time seconds has passed after the
279    // context is initialized, the single click is interpreted as a double-click.  This happens
280    // because internally g.IO.MouseClickedTime is set to 0.0, so the context creation is
281    // considered a "click".
282    {
283        // Pass one second of time
284        ctx.io_mut().delta_time = 1.0;
285        let _ = ctx.frame();
286    }
287    // Fast clicks
288    ctx.io_mut().delta_time = 1.0 / 60.0;
289    for &button in MouseButton::VARIANTS.iter() {
290        {
291            ctx.io_mut().mouse_down = [false; 5];
292            let ui = ctx.frame();
293            assert!(!ui.is_mouse_clicked(button));
294            assert!(!ui.is_mouse_double_clicked(button));
295        }
296        {
297            ctx.io_mut()[button] = true;
298            let ui = ctx.frame();
299            assert!(ui.is_mouse_clicked(button));
300            assert!(!ui.is_mouse_double_clicked(button));
301        }
302        {
303            let ui = ctx.frame();
304            assert!(!ui.is_mouse_clicked(button));
305            assert!(!ui.is_mouse_double_clicked(button));
306        }
307        {
308            ctx.io_mut()[button] = false;
309            let ui = ctx.frame();
310            assert!(!ui.is_mouse_clicked(button));
311            assert!(!ui.is_mouse_double_clicked(button));
312        }
313        {
314            ctx.io_mut()[button] = true;
315            let ui = ctx.frame();
316            assert!(ui.is_mouse_clicked(button));
317            assert!(ui.is_mouse_double_clicked(button));
318        }
319        {
320            let ui = ctx.frame();
321            assert!(!ui.is_mouse_clicked(button));
322            assert!(!ui.is_mouse_double_clicked(button));
323        }
324    }
325    // Slow clicks
326    ctx.io_mut().delta_time = 1.0;
327    for &button in MouseButton::VARIANTS.iter() {
328        {
329            ctx.io_mut().mouse_down = [false; 5];
330            let ui = ctx.frame();
331            assert!(!ui.is_mouse_clicked(button));
332            assert!(!ui.is_mouse_double_clicked(button));
333        }
334        {
335            ctx.io_mut()[button] = true;
336            let ui = ctx.frame();
337            assert!(ui.is_mouse_clicked(button));
338            assert!(!ui.is_mouse_double_clicked(button));
339        }
340        {
341            let ui = ctx.frame();
342            assert!(!ui.is_mouse_clicked(button));
343            assert!(!ui.is_mouse_double_clicked(button));
344        }
345        {
346            ctx.io_mut()[button] = false;
347            let ui = ctx.frame();
348            assert!(!ui.is_mouse_clicked(button));
349            assert!(!ui.is_mouse_double_clicked(button));
350        }
351        {
352            ctx.io_mut()[button] = true;
353            let ui = ctx.frame();
354            assert!(ui.is_mouse_clicked(button));
355            assert!(!ui.is_mouse_double_clicked(button));
356        }
357        {
358            let ui = ctx.frame();
359            assert!(!ui.is_mouse_clicked(button));
360            assert!(!ui.is_mouse_double_clicked(button));
361        }
362    }
363}
364
365#[test]
366fn test_set_get_mouse_cursor() {
367    let (_guard, mut ctx) = crate::test::test_ctx_initialized();
368    let ui = ctx.frame();
369    ui.set_mouse_cursor(None);
370    assert_eq!(None, ui.mouse_cursor());
371    ui.set_mouse_cursor(Some(MouseCursor::Hand));
372    assert_eq!(Some(MouseCursor::Hand), ui.mouse_cursor());
373}
374
375#[test]
376fn test_mouse_drags() {
377    for &button in MouseButton::VARIANTS.iter() {
378        let (_guard, mut ctx) = crate::test::test_ctx_initialized();
379        {
380            ctx.io_mut().mouse_pos = [0.0, 0.0];
381            ctx.io_mut().mouse_down = [false; 5];
382            let ui = ctx.frame();
383            assert!(!ui.is_mouse_dragging(button));
384            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
385            assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 0.0]);
386            assert_eq!(
387                ui.mouse_drag_delta_with_threshold(button, 200.0),
388                [0.0, 0.0]
389            );
390        }
391        {
392            ctx.io_mut()[button] = true;
393            let ui = ctx.frame();
394            assert!(!ui.is_mouse_dragging(button));
395            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
396            assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 0.0]);
397            assert_eq!(
398                ui.mouse_drag_delta_with_threshold(button, 200.0),
399                [0.0, 0.0]
400            );
401        }
402        {
403            ctx.io_mut().mouse_pos = [0.0, 100.0];
404            let ui = ctx.frame();
405            assert!(ui.is_mouse_dragging(button));
406            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
407            assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 100.0]);
408            assert_eq!(
409                ui.mouse_drag_delta_with_threshold(button, 200.0),
410                [0.0, 0.0]
411            );
412        }
413        {
414            ctx.io_mut().mouse_pos = [0.0, 200.0];
415            let ui = ctx.frame();
416            assert!(ui.is_mouse_dragging(button));
417            assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
418            assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 200.0]);
419            assert_eq!(
420                ui.mouse_drag_delta_with_threshold(button, 200.0),
421                [0.0, 200.0]
422            );
423        }
424        {
425            ctx.io_mut().mouse_pos = [10.0, 10.0];
426            ctx.io_mut()[button] = false;
427            let ui = ctx.frame();
428            assert!(!ui.is_mouse_dragging(button));
429            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
430            assert_eq!(ui.mouse_drag_delta_with_button(button), [10.0, 10.0]);
431            assert_eq!(
432                ui.mouse_drag_delta_with_threshold(button, 200.0),
433                [10.0, 10.0]
434            );
435        }
436        {
437            ctx.io_mut()[button] = true;
438            let ui = ctx.frame();
439            assert!(!ui.is_mouse_dragging(button));
440            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
441            assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 0.0]);
442            assert_eq!(
443                ui.mouse_drag_delta_with_threshold(button, 200.0),
444                [0.0, 0.0]
445            );
446        }
447        {
448            ctx.io_mut().mouse_pos = [180.0, 180.0];
449            let ui = ctx.frame();
450            assert!(ui.is_mouse_dragging(button));
451            assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
452            assert_eq!(ui.mouse_drag_delta_with_button(button), [170.0, 170.0]);
453            assert_eq!(
454                ui.mouse_drag_delta_with_threshold(button, 200.0),
455                [170.0, 170.0]
456            );
457            ui.reset_mouse_drag_delta(button);
458            assert!(ui.is_mouse_dragging(button));
459            assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
460            assert_eq!(ui.mouse_drag_delta_with_button(button), [0.0, 0.0]);
461            assert_eq!(
462                ui.mouse_drag_delta_with_threshold(button, 200.0),
463                [0.0, 0.0]
464            );
465        }
466        {
467            ctx.io_mut().mouse_pos = [200.0, 200.0];
468            let ui = ctx.frame();
469            assert!(ui.is_mouse_dragging(button));
470            assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
471            assert_eq!(ui.mouse_drag_delta_with_button(button), [20.0, 20.0]);
472            assert_eq!(
473                ui.mouse_drag_delta_with_threshold(button, 200.0),
474                [20.0, 20.0]
475            );
476        }
477    }
478}