1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
use std::ptr;

use crate::sys;
use crate::Ui;

/// Represents one of the supported mouse buttons
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum MouseButton {
    Left = 0,
    Right = 1,
    Middle = 2,
    Extra1 = 3,
    Extra2 = 4,
}

impl MouseButton {
    /// All possible `MouseButton` varirants
    pub const VARIANTS: [MouseButton; MouseButton::COUNT] = [
        MouseButton::Left,
        MouseButton::Right,
        MouseButton::Middle,
        MouseButton::Extra1,
        MouseButton::Extra2,
    ];
    /// Total count of `MouseButton` variants
    pub const COUNT: usize = 5;
}

#[test]
fn test_mouse_button_variants() {
    for (idx, &value) in MouseButton::VARIANTS.iter().enumerate() {
        assert_eq!(idx, value as usize);
    }
}

/// Mouse cursor type identifier
#[repr(i32)]
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
// TODO: this should just be `#[allow(clippy::upper_case_acronyms)]`, but doing
// so in a way that works before it stabilizes is a pain (in part because
// `unknown_clippy_lints` was renamed to unknown_lints). Oh well, it's over a
// small amount of code.
#[allow(warnings)]
pub enum MouseCursor {
    Arrow = sys::ImGuiMouseCursor_Arrow,
    /// Automatically used when hovering over text inputs, etc.
    TextInput = sys::ImGuiMouseCursor_TextInput,
    /// Not used automatically
    ResizeAll = sys::ImGuiMouseCursor_ResizeAll,
    /// Automatically used when hovering over a horizontal border
    ResizeNS = sys::ImGuiMouseCursor_ResizeNS,
    /// Automatically used when hovering over a vertical border or a column
    ResizeEW = sys::ImGuiMouseCursor_ResizeEW,
    /// Automatically used when hovering over the bottom-left corner of a window
    ResizeNESW = sys::ImGuiMouseCursor_ResizeNESW,
    /// Automatically used when hovering over the bottom-right corner of a window
    ResizeNWSE = sys::ImGuiMouseCursor_ResizeNWSE,
    /// Not used automatically, use for e.g. hyperlinks
    Hand = sys::ImGuiMouseCursor_Hand,
    /// When hovering something with disallowed interactions.
    ///
    /// Usually a crossed circle.
    NotAllowed = sys::ImGuiMouseCursor_NotAllowed,
}

impl MouseCursor {
    /// All possible `MouseCursor` varirants
    pub const VARIANTS: [MouseCursor; MouseCursor::COUNT] = [
        MouseCursor::Arrow,
        MouseCursor::TextInput,
        MouseCursor::ResizeAll,
        MouseCursor::ResizeNS,
        MouseCursor::ResizeEW,
        MouseCursor::ResizeNESW,
        MouseCursor::ResizeNWSE,
        MouseCursor::Hand,
        MouseCursor::NotAllowed,
    ];
    /// Total count of `MouseCursor` variants
    pub const COUNT: usize = sys::ImGuiMouseCursor_COUNT as usize;
}

#[test]
fn test_mouse_cursor_variants() {
    for (idx, &value) in MouseCursor::VARIANTS.iter().enumerate() {
        assert_eq!(idx, value as usize);
    }
}

/// # Input: Mouse
impl<'ui> Ui<'ui> {
    /// Returns true if the given mouse button is held down.
    ///
    /// Equivalent to indexing the Io struct with the button, e.g. `ui.io()[button]`.
    pub fn is_mouse_down(&self, button: MouseButton) -> bool {
        unsafe { sys::igIsMouseDown(button as i32) }
    }
    /// Returns true if any mouse button is held down
    pub fn is_any_mouse_down(&self) -> bool {
        unsafe { sys::igIsAnyMouseDown() }
    }
    /// Returns true if the given mouse button was clicked (went from !down to down)
    pub fn is_mouse_clicked(&self, button: MouseButton) -> bool {
        unsafe { sys::igIsMouseClicked(button as i32, false) }
    }
    /// Returns true if the given mouse button was double-clicked
    pub fn is_mouse_double_clicked(&self, button: MouseButton) -> bool {
        unsafe { sys::igIsMouseDoubleClicked(button as i32) }
    }
    /// Returns true if the given mouse button was released (went from down to !down)
    pub fn is_mouse_released(&self, button: MouseButton) -> bool {
        unsafe { sys::igIsMouseReleased(button as i32) }
    }
    /// Returns true if the mouse is currently dragging with the given mouse button held down
    pub fn is_mouse_dragging(&self, button: MouseButton) -> bool {
        unsafe { sys::igIsMouseDragging(button as i32, -1.0) }
    }
    /// Returns true if the mouse is currently dragging with the given mouse button held down.
    ///
    /// If the given threshold is invalid or negative, the global distance threshold is used
    /// (`io.mouse_drag_threshold`).
    pub fn is_mouse_dragging_with_threshold(&self, button: MouseButton, threshold: f32) -> bool {
        unsafe { sys::igIsMouseDragging(button as i32, threshold) }
    }
    /// Returns true if the mouse is hovering over the given bounding rect.
    ///
    /// Clipped by current clipping settings, but disregards other factors like focus, window
    /// ordering, modal popup blocking.
    pub fn is_mouse_hovering_rect(&self, r_min: [f32; 2], r_max: [f32; 2]) -> bool {
        unsafe { sys::igIsMouseHoveringRect(r_min.into(), r_max.into(), true) }
    }
    /// Returns the mouse position backed up at the time of opening a popup
    pub fn mouse_pos_on_opening_current_popup(&self) -> [f32; 2] {
        let mut out = sys::ImVec2::zero();
        unsafe { sys::igGetMousePosOnOpeningCurrentPopup(&mut out) };
        out.into()
    }
    /// Returns the delta from the initial clicking position.
    ///
    /// This is locked and returns [0.0, 0.0] until the mouse has moved past the global distance
    /// threshold (`io.mouse_drag_threshold`).
    pub fn mouse_drag_delta(&self, button: MouseButton) -> [f32; 2] {
        let mut out = sys::ImVec2::zero();
        unsafe { sys::igGetMouseDragDelta(&mut out, button as i32, -1.0) };
        out.into()
    }
    /// Returns the delta from the initial clicking position.
    ///
    /// This is locked and returns [0.0, 0.0] until the mouse has moved past the given threshold.
    /// If the given threshold is invalid or negative, the global distance threshold is used
    /// (`io.mouse_drag_threshold`).
    pub fn mouse_drag_delta_with_threshold(&self, button: MouseButton, threshold: f32) -> [f32; 2] {
        let mut out = sys::ImVec2::zero();
        unsafe { sys::igGetMouseDragDelta(&mut out, button as i32, threshold) };
        out.into()
    }
    /// Resets the current delta from initial clicking position.
    pub fn reset_mouse_drag_delta(&self, button: MouseButton) {
        // This mutates the Io struct, but targets an internal field so there can't be any
        // references to it
        unsafe { sys::igResetMouseDragDelta(button as i32) }
    }
    /// Returns the currently desired mouse cursor type.
    ///
    /// Returns `None` if no cursor should be displayed
    pub fn mouse_cursor(&self) -> Option<MouseCursor> {
        match unsafe { sys::igGetMouseCursor() } {
            sys::ImGuiMouseCursor_Arrow => Some(MouseCursor::Arrow),
            sys::ImGuiMouseCursor_TextInput => Some(MouseCursor::TextInput),
            sys::ImGuiMouseCursor_ResizeAll => Some(MouseCursor::ResizeAll),
            sys::ImGuiMouseCursor_ResizeNS => Some(MouseCursor::ResizeNS),
            sys::ImGuiMouseCursor_ResizeEW => Some(MouseCursor::ResizeEW),
            sys::ImGuiMouseCursor_ResizeNESW => Some(MouseCursor::ResizeNESW),
            sys::ImGuiMouseCursor_ResizeNWSE => Some(MouseCursor::ResizeNWSE),
            sys::ImGuiMouseCursor_Hand => Some(MouseCursor::Hand),
            sys::ImGuiMouseCursor_NotAllowed => Some(MouseCursor::NotAllowed),
            _ => None,
        }
    }
    /// Sets the desired mouse cursor type.
    ///
    /// Passing `None` hides the mouse cursor.
    pub fn set_mouse_cursor(&self, cursor_type: Option<MouseCursor>) {
        unsafe {
            sys::igSetMouseCursor(
                cursor_type
                    .map(|x| x as i32)
                    .unwrap_or(sys::ImGuiMouseCursor_None),
            );
        }
    }
    pub fn is_current_mouse_pos_valid(&self) -> bool {
        unsafe { sys::igIsMousePosValid(ptr::null()) }
    }
    pub fn is_mouse_pos_valid(&self, mouse_pos: [f32; 2]) -> bool {
        unsafe { sys::igIsMousePosValid(&mouse_pos.into()) }
    }
}

#[test]
fn test_mouse_down_clicked_released() {
    for &button in MouseButton::VARIANTS.iter() {
        let (_guard, mut ctx) = crate::test::test_ctx_initialized();
        {
            ctx.io_mut().mouse_down = [false; 5];
            let ui = ctx.frame();
            assert!(!ui.is_mouse_down(button));
            assert!(!ui.is_any_mouse_down());
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_released(button));
        }
        {
            ctx.io_mut()[button] = true;
            let ui = ctx.frame();
            assert!(ui.is_mouse_down(button));
            assert!(ui.is_any_mouse_down());
            assert!(ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_released(button));
        }
        {
            let ui = ctx.frame();
            assert!(ui.is_mouse_down(button));
            assert!(ui.is_any_mouse_down());
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_released(button));
        }
        {
            ctx.io_mut()[button] = false;
            let ui = ctx.frame();
            assert!(!ui.is_mouse_down(button));
            assert!(!ui.is_any_mouse_down());
            assert!(!ui.is_mouse_clicked(button));
            assert!(ui.is_mouse_released(button));
        }
        {
            let ui = ctx.frame();
            assert!(!ui.is_mouse_down(button));
            assert!(!ui.is_any_mouse_down());
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_released(button));
        }
    }
}

#[test]
fn test_mouse_double_click() {
    let (_guard, mut ctx) = crate::test::test_ctx_initialized();
    // Workaround for dear imgui bug/feature:
    // If a button is clicked before io.mouse_double_click_time seconds has passed after the
    // context is initialized, the single click is interpreted as a double-click.  This happens
    // because internally g.IO.MouseClickedTime is set to 0.0, so the context creation is
    // considered a "click".
    {
        // Pass one second of time
        ctx.io_mut().delta_time = 1.0;
        let _ = ctx.frame();
    }
    // Fast clicks
    ctx.io_mut().delta_time = 1.0 / 60.0;
    for &button in MouseButton::VARIANTS.iter() {
        {
            ctx.io_mut().mouse_down = [false; 5];
            let ui = ctx.frame();
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
        {
            ctx.io_mut()[button] = true;
            let ui = ctx.frame();
            assert!(ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
        {
            let ui = ctx.frame();
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
        {
            ctx.io_mut()[button] = false;
            let ui = ctx.frame();
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
        {
            ctx.io_mut()[button] = true;
            let ui = ctx.frame();
            assert!(ui.is_mouse_clicked(button));
            assert!(ui.is_mouse_double_clicked(button));
        }
        {
            let ui = ctx.frame();
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
    }
    // Slow clicks
    ctx.io_mut().delta_time = 1.0;
    for &button in MouseButton::VARIANTS.iter() {
        {
            ctx.io_mut().mouse_down = [false; 5];
            let ui = ctx.frame();
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
        {
            ctx.io_mut()[button] = true;
            let ui = ctx.frame();
            assert!(ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
        {
            let ui = ctx.frame();
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
        {
            ctx.io_mut()[button] = false;
            let ui = ctx.frame();
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
        {
            ctx.io_mut()[button] = true;
            let ui = ctx.frame();
            assert!(ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
        {
            let ui = ctx.frame();
            assert!(!ui.is_mouse_clicked(button));
            assert!(!ui.is_mouse_double_clicked(button));
        }
    }
}

#[test]
fn test_set_get_mouse_cursor() {
    let (_guard, mut ctx) = crate::test::test_ctx_initialized();
    let ui = ctx.frame();
    ui.set_mouse_cursor(None);
    assert_eq!(None, ui.mouse_cursor());
    ui.set_mouse_cursor(Some(MouseCursor::Hand));
    assert_eq!(Some(MouseCursor::Hand), ui.mouse_cursor());
}

#[test]
fn test_mouse_drags() {
    for &button in MouseButton::VARIANTS.iter() {
        let (_guard, mut ctx) = crate::test::test_ctx_initialized();
        {
            ctx.io_mut().mouse_pos = [0.0, 0.0];
            ctx.io_mut().mouse_down = [false; 5];
            let ui = ctx.frame();
            assert!(!ui.is_mouse_dragging(button));
            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
            assert_eq!(ui.mouse_drag_delta(button), [0.0, 0.0]);
            assert_eq!(
                ui.mouse_drag_delta_with_threshold(button, 200.0),
                [0.0, 0.0]
            );
        }
        {
            ctx.io_mut()[button] = true;
            let ui = ctx.frame();
            assert!(!ui.is_mouse_dragging(button));
            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
            assert_eq!(ui.mouse_drag_delta(button), [0.0, 0.0]);
            assert_eq!(
                ui.mouse_drag_delta_with_threshold(button, 200.0),
                [0.0, 0.0]
            );
        }
        {
            ctx.io_mut().mouse_pos = [0.0, 100.0];
            let ui = ctx.frame();
            assert!(ui.is_mouse_dragging(button));
            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
            assert_eq!(ui.mouse_drag_delta(button), [0.0, 100.0]);
            assert_eq!(
                ui.mouse_drag_delta_with_threshold(button, 200.0),
                [0.0, 0.0]
            );
        }
        {
            ctx.io_mut().mouse_pos = [0.0, 200.0];
            let ui = ctx.frame();
            assert!(ui.is_mouse_dragging(button));
            assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
            assert_eq!(ui.mouse_drag_delta(button), [0.0, 200.0]);
            assert_eq!(
                ui.mouse_drag_delta_with_threshold(button, 200.0),
                [0.0, 200.0]
            );
        }
        {
            ctx.io_mut().mouse_pos = [10.0, 10.0];
            ctx.io_mut()[button] = false;
            let ui = ctx.frame();
            assert!(!ui.is_mouse_dragging(button));
            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
            assert_eq!(ui.mouse_drag_delta(button), [10.0, 10.0]);
            assert_eq!(
                ui.mouse_drag_delta_with_threshold(button, 200.0),
                [10.0, 10.0]
            );
        }
        {
            ctx.io_mut()[button] = true;
            let ui = ctx.frame();
            assert!(!ui.is_mouse_dragging(button));
            assert!(!ui.is_mouse_dragging_with_threshold(button, 200.0));
            assert_eq!(ui.mouse_drag_delta(button), [0.0, 0.0]);
            assert_eq!(
                ui.mouse_drag_delta_with_threshold(button, 200.0),
                [0.0, 0.0]
            );
        }
        {
            ctx.io_mut().mouse_pos = [180.0, 180.0];
            let ui = ctx.frame();
            assert!(ui.is_mouse_dragging(button));
            assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
            assert_eq!(ui.mouse_drag_delta(button), [170.0, 170.0]);
            assert_eq!(
                ui.mouse_drag_delta_with_threshold(button, 200.0),
                [170.0, 170.0]
            );
            ui.reset_mouse_drag_delta(button);
            assert!(ui.is_mouse_dragging(button));
            assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
            assert_eq!(ui.mouse_drag_delta(button), [0.0, 0.0]);
            assert_eq!(
                ui.mouse_drag_delta_with_threshold(button, 200.0),
                [0.0, 0.0]
            );
        }
        {
            ctx.io_mut().mouse_pos = [200.0, 200.0];
            let ui = ctx.frame();
            assert!(ui.is_mouse_dragging(button));
            assert!(ui.is_mouse_dragging_with_threshold(button, 200.0));
            assert_eq!(ui.mouse_drag_delta(button), [20.0, 20.0]);
            assert_eq!(
                ui.mouse_drag_delta_with_threshold(button, 200.0),
                [20.0, 20.0]
            );
        }
    }
}