Skip to main content

dear_imgui_rs/
utils.rs

1//! Miscellaneous utilities
2//!
3//! Helper flags and `Ui` extension methods for common queries (hovered/focused
4//! checks, item rectangles, etc.). These are thin wrappers around Dear ImGui
5//! functions for convenience and type safety.
6//!
7use crate::input::{Key, MouseButton};
8use crate::{StyleColor, sys};
9use bitflags::bitflags;
10
11bitflags! {
12    /// Flags for hovering detection
13    #[repr(transparent)]
14    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15    pub struct HoveredFlags: i32 {
16        /// Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them.
17        const NONE = sys::ImGuiHoveredFlags_None as i32;
18        /// IsWindowHovered() only: Return true if any children of the window is hovered
19        const CHILD_WINDOWS = sys::ImGuiHoveredFlags_ChildWindows as i32;
20        /// IsWindowHovered() only: Test from root window (top most parent of the current hierarchy)
21        const ROOT_WINDOW = sys::ImGuiHoveredFlags_RootWindow as i32;
22        /// IsWindowHovered() only: Return true if any window is hovered
23        const ANY_WINDOW = sys::ImGuiHoveredFlags_AnyWindow as i32;
24        /// IsWindowHovered() only: Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow)
25        const NO_POPUP_HIERARCHY = sys::ImGuiHoveredFlags_NoPopupHierarchy as i32;
26        /// IsWindowHovered() only: Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow)
27        const DOCK_HIERARCHY = sys::ImGuiHoveredFlags_DockHierarchy as i32;
28        /// Return true even if a popup window is normally blocking access to this item/window
29        const ALLOW_WHEN_BLOCKED_BY_POPUP = sys::ImGuiHoveredFlags_AllowWhenBlockedByPopup as i32;
30        /// Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns.
31        const ALLOW_WHEN_BLOCKED_BY_ACTIVE_ITEM = sys::ImGuiHoveredFlags_AllowWhenBlockedByActiveItem as i32;
32        /// IsItemHovered() only: Return true even if the position is obstructed or overlapped by another window
33        const ALLOW_WHEN_OVERLAPPED = sys::ImGuiHoveredFlags_AllowWhenOverlapped as i32;
34        /// IsItemHovered() only: Return true even if the item is disabled
35        const ALLOW_WHEN_DISABLED = sys::ImGuiHoveredFlags_AllowWhenDisabled as i32;
36        /// IsItemHovered() only: Disable using gamepad/keyboard navigation state when active, always query mouse.
37        const NO_NAV_OVERRIDE = sys::ImGuiHoveredFlags_NoNavOverride as i32;
38        /// Shortcut for standard flags when using IsItemHovered() + SetTooltip() sequence.
39        const FOR_TOOLTIP = sys::ImGuiHoveredFlags_ForTooltip as i32;
40        /// Require mouse to be stationary for style.HoverStationaryDelay (~0.15 sec) _at least one time_. After this, can move on same item/window. Using the stationary test tends to reduces the need for a long delay.
41        const STATIONARY = sys::ImGuiHoveredFlags_Stationary as i32;
42        /// IsItemHovered() only: Return true immediately (default). As opposed to IsItemHovered() returning true only after style.HoverDelayNormal elapsed (~0.30 sec)
43        const DELAY_NONE = sys::ImGuiHoveredFlags_DelayNone as i32;
44        /// IsItemHovered() only: Return true after style.HoverDelayShort elapsed (~0.10 sec)
45        const DELAY_SHORT = sys::ImGuiHoveredFlags_DelayShort as i32;
46        /// IsItemHovered() only: Return true after style.HoverDelayNormal elapsed (~0.30 sec)
47        const DELAY_NORMAL = sys::ImGuiHoveredFlags_DelayNormal as i32;
48        /// IsItemHovered() only: Disable shared delay system where moving from one item to a neighboring item keeps the previous timer for a short time (standard for tooltips with long delays)
49        const NO_SHARED_DELAY = sys::ImGuiHoveredFlags_NoSharedDelay as i32;
50    }
51}
52
53impl Default for HoveredFlags {
54    fn default() -> Self {
55        HoveredFlags::NONE
56    }
57}
58
59bitflags! {
60    /// Flags for focus detection
61    #[repr(transparent)]
62    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
63    pub struct FocusedFlags: i32 {
64        /// Return true if window is focused
65        const NONE = sys::ImGuiFocusedFlags_None as i32;
66        /// IsWindowFocused() only: Return true if any children of the window is focused
67        const CHILD_WINDOWS = sys::ImGuiFocusedFlags_ChildWindows as i32;
68        /// IsWindowFocused() only: Test from root window (top most parent of the current hierarchy)
69        const ROOT_WINDOW = sys::ImGuiFocusedFlags_RootWindow as i32;
70        /// IsWindowFocused() only: Return true if any window is focused
71        const ANY_WINDOW = sys::ImGuiFocusedFlags_AnyWindow as i32;
72        /// IsWindowFocused() only: Do not consider popup hierarchy
73        const NO_POPUP_HIERARCHY = sys::ImGuiFocusedFlags_NoPopupHierarchy as i32;
74        /// IsWindowFocused() only: Consider docking hierarchy
75        const DOCK_HIERARCHY = sys::ImGuiFocusedFlags_DockHierarchy as i32;
76    }
77}
78
79impl Default for FocusedFlags {
80    fn default() -> Self {
81        FocusedFlags::NONE
82    }
83}
84
85/// Utility functions for Dear ImGui
86impl crate::ui::Ui {
87    // ============================================================================
88    // Item/widget utilities (non-duplicate functions)
89    // ============================================================================
90
91    /// Returns `true` if the last item open state was toggled
92    #[doc(alias = "IsItemToggledOpen")]
93    pub fn is_item_toggled_open(&self) -> bool {
94        unsafe { sys::igIsItemToggledOpen() }
95    }
96
97    /// Returns the upper-left bounding rectangle of the last item (screen space)
98    #[doc(alias = "GetItemRectMin")]
99    pub fn item_rect_min(&self) -> [f32; 2] {
100        let rect = unsafe { sys::igGetItemRectMin() };
101        [rect.x, rect.y]
102    }
103
104    /// Returns the lower-right bounding rectangle of the last item (screen space)
105    #[doc(alias = "GetItemRectMax")]
106    pub fn item_rect_max(&self) -> [f32; 2] {
107        let rect = unsafe { sys::igGetItemRectMax() };
108        [rect.x, rect.y]
109    }
110
111    // ============================================================================
112    // Window utilities
113    // ============================================================================
114
115    /// Returns `true` if the current window is hovered (and typically: not blocked by a popup/modal)
116    #[doc(alias = "IsWindowHovered")]
117    pub fn is_window_hovered(&self) -> bool {
118        unsafe { sys::igIsWindowHovered(HoveredFlags::NONE.bits()) }
119    }
120
121    /// Returns `true` if the current window is hovered based on the given flags
122    #[doc(alias = "IsWindowHovered")]
123    pub fn is_window_hovered_with_flags(&self, flags: HoveredFlags) -> bool {
124        unsafe { sys::igIsWindowHovered(flags.bits()) }
125    }
126
127    /// Returns `true` if the current window is focused (and typically: not blocked by a popup/modal)
128    #[doc(alias = "IsWindowFocused")]
129    pub fn is_window_focused(&self) -> bool {
130        self.is_window_focused_with_flags(FocusedFlags::NONE)
131    }
132
133    /// Returns `true` if the current window is focused based on the given flags
134    #[doc(alias = "IsWindowFocused")]
135    pub fn is_window_focused_with_flags(&self, flags: FocusedFlags) -> bool {
136        unsafe { sys::igIsWindowFocused(flags.bits()) }
137    }
138
139    /// Returns `true` if the current window is appearing this frame.
140    #[doc(alias = "IsWindowAppearing")]
141    pub fn is_window_appearing(&self) -> bool {
142        unsafe { sys::igIsWindowAppearing() }
143    }
144
145    /// Returns `true` if the current window is collapsed.
146    #[doc(alias = "IsWindowCollapsed")]
147    pub fn is_window_collapsed(&self) -> bool {
148        unsafe { sys::igIsWindowCollapsed() }
149    }
150
151    // ============================================================================
152    // Additional input utilities (non-duplicate functions)
153    // ============================================================================
154
155    /// Returns the number of times the key was pressed in the current frame
156    #[doc(alias = "GetKeyPressedAmount")]
157    pub fn get_key_pressed_amount(&self, key: Key, repeat_delay: f32, rate: f32) -> i32 {
158        unsafe { sys::igGetKeyPressedAmount(key.into(), repeat_delay, rate) }
159    }
160
161    /// Returns the name of a key
162    #[doc(alias = "GetKeyName")]
163    pub fn get_key_name(&self, key: Key) -> &str {
164        unsafe {
165            let name_ptr = sys::igGetKeyName(key.into());
166            if name_ptr.is_null() {
167                return "Unknown";
168            }
169            let c_str = std::ffi::CStr::from_ptr(name_ptr);
170            c_str.to_str().unwrap_or("Unknown")
171        }
172    }
173
174    /// Returns the number of times the mouse button was clicked in the current frame
175    #[doc(alias = "GetMouseClickedCount")]
176    pub fn get_mouse_clicked_count(&self, button: MouseButton) -> i32 {
177        unsafe { sys::igGetMouseClickedCount(button.into()) }
178    }
179
180    /// Returns the mouse position in screen coordinates
181    #[doc(alias = "GetMousePos")]
182    pub fn get_mouse_pos(&self) -> [f32; 2] {
183        let pos = unsafe { sys::igGetMousePos() };
184        [pos.x, pos.y]
185    }
186
187    /// Returns the mouse position when the button was clicked
188    #[doc(alias = "GetMousePosOnOpeningCurrentPopup")]
189    pub fn get_mouse_pos_on_opening_current_popup(&self) -> [f32; 2] {
190        let pos = unsafe { sys::igGetMousePosOnOpeningCurrentPopup() };
191        [pos.x, pos.y]
192    }
193
194    /// Returns the mouse drag delta
195    #[doc(alias = "GetMouseDragDelta")]
196    pub fn get_mouse_drag_delta(&self, button: MouseButton, lock_threshold: f32) -> [f32; 2] {
197        let delta = unsafe { sys::igGetMouseDragDelta(button.into(), lock_threshold) };
198        [delta.x, delta.y]
199    }
200
201    /// Returns the mouse wheel delta
202    #[doc(alias = "GetIO")]
203    pub fn get_mouse_wheel(&self) -> f32 {
204        self.io().mouse_wheel()
205    }
206
207    /// Returns the horizontal mouse wheel delta
208    #[doc(alias = "GetIO")]
209    pub fn get_mouse_wheel_h(&self) -> f32 {
210        self.io().mouse_wheel_h()
211    }
212
213    /// Returns `true` if any mouse button is down
214    #[doc(alias = "IsAnyMouseDown")]
215    pub fn is_any_mouse_down(&self) -> bool {
216        unsafe { sys::igIsAnyMouseDown() }
217    }
218
219    // ============================================================================
220    // General utilities
221    // ============================================================================
222
223    /// Get global imgui time. Incremented by io.DeltaTime every frame.
224    #[doc(alias = "GetTime")]
225    pub fn time(&self) -> f64 {
226        unsafe { sys::igGetTime() }
227    }
228
229    /// Get global imgui frame count. Incremented by 1 every frame.
230    #[doc(alias = "GetFrameCount")]
231    pub fn frame_count(&self) -> i32 {
232        unsafe { sys::igGetFrameCount() }
233    }
234
235    /// Returns the width of an item based on the current layout state.
236    #[doc(alias = "CalcItemWidth")]
237    pub fn calc_item_width(&self) -> f32 {
238        unsafe { sys::igCalcItemWidth() }
239    }
240
241    /// Start logging to TTY.
242    #[doc(alias = "LogToTTY")]
243    pub fn log_to_tty(&self, auto_open_depth: i32) {
244        unsafe { sys::igLogToTTY(auto_open_depth) }
245    }
246
247    /// Start logging to file with the default filename.
248    #[doc(alias = "LogToFile")]
249    pub fn log_to_file_default(&self, auto_open_depth: i32) {
250        unsafe { sys::igLogToFile(auto_open_depth, std::ptr::null()) }
251    }
252
253    /// Start logging to file.
254    ///
255    /// # Errors
256    ///
257    /// Returns an error if `filename` contains NUL bytes.
258    #[doc(alias = "LogToFile")]
259    pub fn log_to_file(
260        &self,
261        auto_open_depth: i32,
262        filename: &std::path::Path,
263    ) -> crate::error::ImGuiResult<()> {
264        use crate::error::SafeStringConversion;
265        let cstr = filename.to_string_lossy().into_owned().to_cstring_safe()?;
266        unsafe { sys::igLogToFile(auto_open_depth, cstr.as_ptr()) }
267        Ok(())
268    }
269
270    /// Start logging to clipboard.
271    #[doc(alias = "LogToClipboard")]
272    pub fn log_to_clipboard(&self, auto_open_depth: i32) {
273        unsafe { sys::igLogToClipboard(auto_open_depth) }
274    }
275
276    /// Show ImGui's logging buttons (TTY/File/Clipboard).
277    #[doc(alias = "LogButtons")]
278    pub fn log_buttons(&self) {
279        unsafe { sys::igLogButtons() }
280    }
281
282    /// Finish logging (close file / copy to clipboard as needed).
283    #[doc(alias = "LogFinish")]
284    pub fn log_finish(&self) {
285        unsafe { sys::igLogFinish() }
286    }
287
288    /// Returns a single style color from the user interface style.
289    ///
290    /// Use this function if you need to access the colors, but don't want to clone the entire
291    /// style object.
292    #[doc(alias = "GetStyle", alias = "GetStyleColorVec4")]
293    pub fn style_color(&self, style_color: StyleColor) -> [f32; 4] {
294        unsafe {
295            let color = sys::igGetStyleColorVec4(style_color as sys::ImGuiCol);
296            let color = &*color;
297            [color.x, color.y, color.z, color.w]
298        }
299    }
300
301    /// Returns an ImGui-packed ABGR color (`ImU32`) from a style color.
302    ///
303    /// This is a convenience wrapper over `ImGui::GetColorU32(ImGuiCol, alpha_mul)`.
304    #[doc(alias = "GetColorU32")]
305    pub fn get_color_u32(&self, style_color: StyleColor) -> u32 {
306        self.get_color_u32_with_alpha(style_color, 1.0)
307    }
308
309    /// Returns an ImGui-packed ABGR color (`ImU32`) from a style color, with alpha multiplier.
310    #[doc(alias = "GetColorU32")]
311    pub fn get_color_u32_with_alpha(&self, style_color: StyleColor, alpha_mul: f32) -> u32 {
312        unsafe { sys::igGetColorU32_Col(style_color as sys::ImGuiCol, alpha_mul) }
313    }
314
315    /// Returns an ImGui-packed ABGR color (`ImU32`) from an RGBA float color.
316    ///
317    /// Note: Dear ImGui applies the global style alpha when converting colors for rendering.
318    #[doc(alias = "GetColorU32")]
319    pub fn get_color_u32_from_rgba(&self, rgba: [f32; 4]) -> u32 {
320        unsafe {
321            sys::igGetColorU32_Vec4(sys::ImVec4_c {
322                x: rgba[0],
323                y: rgba[1],
324                z: rgba[2],
325                w: rgba[3],
326            })
327        }
328    }
329
330    /// Returns an ImGui-packed ABGR color (`ImU32`) from an existing packed color, with alpha multiplier.
331    #[doc(alias = "GetColorU32")]
332    pub fn get_color_u32_from_packed(&self, abgr: u32, alpha_mul: f32) -> u32 {
333        unsafe { sys::igGetColorU32_U32(abgr, alpha_mul) }
334    }
335
336    /// Returns the name of a style color.
337    ///
338    /// This is just a wrapper around calling [`name`] on [StyleColor].
339    ///
340    /// [`name`]: StyleColor::name
341    #[doc(alias = "GetStyleColorName")]
342    pub fn style_color_name(&self, style_color: StyleColor) -> &'static str {
343        unsafe {
344            let name_ptr = sys::igGetStyleColorName(style_color as sys::ImGuiCol);
345            if name_ptr.is_null() {
346                return "Unknown";
347            }
348            let c_str = std::ffi::CStr::from_ptr(name_ptr);
349            c_str.to_str().unwrap_or("Unknown")
350        }
351    }
352
353    /// Test if rectangle (of given size, starting from cursor position) is visible / not clipped.
354    #[doc(alias = "IsRectVisible")]
355    pub fn is_rect_visible(&self, size: [f32; 2]) -> bool {
356        unsafe {
357            let size = sys::ImVec2 {
358                x: size[0],
359                y: size[1],
360            };
361            sys::igIsRectVisible_Nil(size)
362        }
363    }
364
365    /// Test if rectangle (in screen space) is visible / not clipped.
366    #[doc(alias = "IsRectVisible")]
367    pub fn is_rect_visible_ex(&self, rect_min: [f32; 2], rect_max: [f32; 2]) -> bool {
368        unsafe {
369            let rect_min = sys::ImVec2 {
370                x: rect_min[0],
371                y: rect_min[1],
372            };
373            let rect_max = sys::ImVec2 {
374                x: rect_max[0],
375                y: rect_max[1],
376            };
377            sys::igIsRectVisible_Vec2(rect_min, rect_max)
378        }
379    }
380
381    // ========== Additional Geometry Functions ==========
382
383    /// Get cursor position in screen coordinates.
384    #[doc(alias = "GetCursorScreenPos")]
385    pub fn get_cursor_screen_pos(&self) -> [f32; 2] {
386        let pos = unsafe { sys::igGetCursorScreenPos() };
387        [pos.x, pos.y]
388    }
389
390    /// Get available content region size.
391    #[doc(alias = "GetContentRegionAvail")]
392    pub fn get_content_region_avail(&self) -> [f32; 2] {
393        let size = unsafe { sys::igGetContentRegionAvail() };
394        [size.x, size.y]
395    }
396
397    /// Check if a point is inside a rectangle.
398    pub fn is_point_in_rect(
399        &self,
400        point: [f32; 2],
401        rect_min: [f32; 2],
402        rect_max: [f32; 2],
403    ) -> bool {
404        point[0] >= rect_min[0]
405            && point[0] <= rect_max[0]
406            && point[1] >= rect_min[1]
407            && point[1] <= rect_max[1]
408    }
409
410    /// Calculate distance between two points.
411    pub fn distance(&self, p1: [f32; 2], p2: [f32; 2]) -> f32 {
412        let dx = p2[0] - p1[0];
413        let dy = p2[1] - p1[1];
414        (dx * dx + dy * dy).sqrt()
415    }
416
417    /// Calculate squared distance between two points (faster than distance).
418    pub fn distance_squared(&self, p1: [f32; 2], p2: [f32; 2]) -> f32 {
419        let dx = p2[0] - p1[0];
420        let dy = p2[1] - p1[1];
421        dx * dx + dy * dy
422    }
423
424    /// Check if two line segments intersect.
425    pub fn line_segments_intersect(
426        &self,
427        p1: [f32; 2],
428        p2: [f32; 2],
429        p3: [f32; 2],
430        p4: [f32; 2],
431    ) -> bool {
432        let d1 = self.cross_product(
433            [p4[0] - p3[0], p4[1] - p3[1]],
434            [p1[0] - p3[0], p1[1] - p3[1]],
435        );
436        let d2 = self.cross_product(
437            [p4[0] - p3[0], p4[1] - p3[1]],
438            [p2[0] - p3[0], p2[1] - p3[1]],
439        );
440        let d3 = self.cross_product(
441            [p2[0] - p1[0], p2[1] - p1[1]],
442            [p3[0] - p1[0], p3[1] - p1[1]],
443        );
444        let d4 = self.cross_product(
445            [p2[0] - p1[0], p2[1] - p1[1]],
446            [p4[0] - p1[0], p4[1] - p1[1]],
447        );
448
449        (d1 > 0.0) != (d2 > 0.0) && (d3 > 0.0) != (d4 > 0.0)
450    }
451
452    /// Calculate cross product of two 2D vectors.
453    fn cross_product(&self, v1: [f32; 2], v2: [f32; 2]) -> f32 {
454        v1[0] * v2[1] - v1[1] * v2[0]
455    }
456
457    /// Normalize a 2D vector.
458    pub fn normalize(&self, v: [f32; 2]) -> [f32; 2] {
459        let len = (v[0] * v[0] + v[1] * v[1]).sqrt();
460        if len > f32::EPSILON {
461            [v[0] / len, v[1] / len]
462        } else {
463            [0.0, 0.0]
464        }
465    }
466
467    /// Calculate dot product of two 2D vectors.
468    pub fn dot_product(&self, v1: [f32; 2], v2: [f32; 2]) -> f32 {
469        v1[0] * v2[0] + v1[1] * v2[1]
470    }
471
472    /// Calculate the angle between two vectors in radians.
473    pub fn angle_between_vectors(&self, v1: [f32; 2], v2: [f32; 2]) -> f32 {
474        let dot = self.dot_product(v1, v2);
475        let len1 = (v1[0] * v1[0] + v1[1] * v1[1]).sqrt();
476        let len2 = (v2[0] * v2[0] + v2[1] * v2[1]).sqrt();
477
478        if len1 > f32::EPSILON && len2 > f32::EPSILON {
479            (dot / (len1 * len2)).acos()
480        } else {
481            0.0
482        }
483    }
484
485    /// Check if a point is inside a circle.
486    pub fn is_point_in_circle(&self, point: [f32; 2], center: [f32; 2], radius: f32) -> bool {
487        self.distance_squared(point, center) <= radius * radius
488    }
489
490    /// Calculate the area of a triangle given three points.
491    pub fn triangle_area(&self, p1: [f32; 2], p2: [f32; 2], p3: [f32; 2]) -> f32 {
492        let cross = self.cross_product(
493            [p2[0] - p1[0], p2[1] - p1[1]],
494            [p3[0] - p1[0], p3[1] - p1[1]],
495        );
496        cross.abs() * 0.5
497    }
498
499    // Additional utility functions
500
501    /// Allows the next item to be overlapped by a subsequent item.
502    #[doc(alias = "SetNextItemAllowOverlap")]
503    pub fn set_next_item_allow_overlap(&self) {
504        unsafe { sys::igSetNextItemAllowOverlap() };
505    }
506}