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
59/// Utility functions for Dear ImGui
60impl crate::ui::Ui {
61    // ============================================================================
62    // Item/widget utilities (non-duplicate functions)
63    // ============================================================================
64
65    /// Returns `true` if the last item open state was toggled
66    #[doc(alias = "IsItemToggledOpen")]
67    pub fn is_item_toggled_open(&self) -> bool {
68        unsafe { sys::igIsItemToggledOpen() }
69    }
70
71    /// Returns the upper-left bounding rectangle of the last item (screen space)
72    #[doc(alias = "GetItemRectMin")]
73    pub fn item_rect_min(&self) -> [f32; 2] {
74        let rect = unsafe { sys::igGetItemRectMin() };
75        [rect.x, rect.y]
76    }
77
78    /// Returns the lower-right bounding rectangle of the last item (screen space)
79    #[doc(alias = "GetItemRectMax")]
80    pub fn item_rect_max(&self) -> [f32; 2] {
81        let rect = unsafe { sys::igGetItemRectMax() };
82        [rect.x, rect.y]
83    }
84
85    // ============================================================================
86    // Window utilities
87    // ============================================================================
88
89    /// Returns `true` if the current window is hovered (and typically: not blocked by a popup/modal)
90    #[doc(alias = "IsWindowHovered")]
91    pub fn is_window_hovered(&self) -> bool {
92        unsafe { sys::igIsWindowHovered(HoveredFlags::NONE.bits()) }
93    }
94
95    /// Returns `true` if the current window is hovered based on the given flags
96    #[doc(alias = "IsWindowHovered")]
97    pub fn is_window_hovered_with_flags(&self, flags: HoveredFlags) -> bool {
98        unsafe { sys::igIsWindowHovered(flags.bits()) }
99    }
100
101    /// Returns `true` if the current window is focused (and typically: not blocked by a popup/modal)
102    #[doc(alias = "IsWindowFocused")]
103    pub fn is_window_focused(&self) -> bool {
104        unsafe { sys::igIsWindowFocused(0) }
105    }
106
107    /// Returns `true` if the current window is appearing this frame.
108    #[doc(alias = "IsWindowAppearing")]
109    pub fn is_window_appearing(&self) -> bool {
110        unsafe { sys::igIsWindowAppearing() }
111    }
112
113    /// Returns `true` if the current window is collapsed.
114    #[doc(alias = "IsWindowCollapsed")]
115    pub fn is_window_collapsed(&self) -> bool {
116        unsafe { sys::igIsWindowCollapsed() }
117    }
118
119    // ============================================================================
120    // Additional input utilities (non-duplicate functions)
121    // ============================================================================
122
123    /// Returns the number of times the key was pressed in the current frame
124    #[doc(alias = "GetKeyPressedAmount")]
125    pub fn get_key_pressed_amount(&self, key: Key, repeat_delay: f32, rate: f32) -> i32 {
126        unsafe { sys::igGetKeyPressedAmount(key.into(), repeat_delay, rate) }
127    }
128
129    /// Returns the name of a key
130    #[doc(alias = "GetKeyName")]
131    pub fn get_key_name(&self, key: Key) -> &str {
132        unsafe {
133            let name_ptr = sys::igGetKeyName(key.into());
134            let c_str = std::ffi::CStr::from_ptr(name_ptr);
135            c_str.to_str().unwrap_or("Unknown")
136        }
137    }
138
139    /// Returns the number of times the mouse button was clicked in the current frame
140    #[doc(alias = "GetMouseClickedCount")]
141    pub fn get_mouse_clicked_count(&self, button: MouseButton) -> i32 {
142        unsafe { sys::igGetMouseClickedCount(button.into()) }
143    }
144
145    /// Returns the mouse position in screen coordinates
146    #[doc(alias = "GetMousePos")]
147    pub fn get_mouse_pos(&self) -> [f32; 2] {
148        let pos = unsafe { sys::igGetMousePos() };
149        [pos.x, pos.y]
150    }
151
152    /// Returns the mouse position when the button was clicked
153    #[doc(alias = "GetMousePosOnOpeningCurrentPopup")]
154    pub fn get_mouse_pos_on_opening_current_popup(&self) -> [f32; 2] {
155        let pos = unsafe { sys::igGetMousePosOnOpeningCurrentPopup() };
156        [pos.x, pos.y]
157    }
158
159    /// Returns the mouse drag delta
160    #[doc(alias = "GetMouseDragDelta")]
161    pub fn get_mouse_drag_delta(&self, button: MouseButton, lock_threshold: f32) -> [f32; 2] {
162        let delta = unsafe { sys::igGetMouseDragDelta(button.into(), lock_threshold) };
163        [delta.x, delta.y]
164    }
165
166    /// Returns the mouse wheel delta
167    #[doc(alias = "GetIO")]
168    pub fn get_mouse_wheel(&self) -> f32 {
169        unsafe { (*sys::igGetIO_Nil()).MouseWheel }
170    }
171
172    /// Returns the horizontal mouse wheel delta
173    #[doc(alias = "GetIO")]
174    pub fn get_mouse_wheel_h(&self) -> f32 {
175        unsafe { (*sys::igGetIO_Nil()).MouseWheelH }
176    }
177
178    /// Returns `true` if any mouse button is down
179    #[doc(alias = "IsAnyMouseDown")]
180    pub fn is_any_mouse_down(&self) -> bool {
181        unsafe { sys::igIsAnyMouseDown() }
182    }
183
184    // ============================================================================
185    // General utilities
186    // ============================================================================
187
188    /// Get global imgui time. Incremented by io.DeltaTime every frame.
189    #[doc(alias = "GetTime")]
190    pub fn time(&self) -> f64 {
191        unsafe { sys::igGetTime() }
192    }
193
194    /// Get global imgui frame count. Incremented by 1 every frame.
195    #[doc(alias = "GetFrameCount")]
196    pub fn frame_count(&self) -> i32 {
197        unsafe { sys::igGetFrameCount() }
198    }
199
200    /// Returns a single style color from the user interface style.
201    ///
202    /// Use this function if you need to access the colors, but don't want to clone the entire
203    /// style object.
204    #[doc(alias = "GetStyle")]
205    pub fn style_color(&self, style_color: StyleColor) -> [f32; 4] {
206        unsafe {
207            let style_ptr = sys::igGetStyle();
208            let colors = (*style_ptr).Colors.as_ptr();
209            let color = *colors.add(style_color as usize);
210            [color.x, color.y, color.z, color.w]
211        }
212    }
213
214    /// Returns the name of a style color.
215    ///
216    /// This is just a wrapper around calling [`name`] on [StyleColor].
217    ///
218    /// [`name`]: StyleColor::name
219    #[doc(alias = "GetStyleColorName")]
220    pub fn style_color_name(&self, style_color: StyleColor) -> &'static str {
221        unsafe {
222            let name_ptr = sys::igGetStyleColorName(style_color as sys::ImGuiCol);
223            let c_str = std::ffi::CStr::from_ptr(name_ptr);
224            c_str.to_str().unwrap_or("Unknown")
225        }
226    }
227
228    /// Test if rectangle (of given size, starting from cursor position) is visible / not clipped.
229    #[doc(alias = "IsRectVisible")]
230    pub fn is_rect_visible(&self, size: [f32; 2]) -> bool {
231        unsafe {
232            let size = sys::ImVec2 {
233                x: size[0],
234                y: size[1],
235            };
236            sys::igIsRectVisible_Nil(size)
237        }
238    }
239
240    /// Test if rectangle (in screen space) is visible / not clipped.
241    #[doc(alias = "IsRectVisible")]
242    pub fn is_rect_visible_ex(&self, rect_min: [f32; 2], rect_max: [f32; 2]) -> bool {
243        unsafe {
244            let rect_min = sys::ImVec2 {
245                x: rect_min[0],
246                y: rect_min[1],
247            };
248            let rect_max = sys::ImVec2 {
249                x: rect_max[0],
250                y: rect_max[1],
251            };
252            sys::igIsRectVisible_Vec2(rect_min, rect_max)
253        }
254    }
255
256    // ========== Additional Geometry Functions ==========
257
258    /// Get cursor position in screen coordinates.
259    #[doc(alias = "GetCursorScreenPos")]
260    pub fn get_cursor_screen_pos(&self) -> [f32; 2] {
261        let pos = unsafe { sys::igGetCursorScreenPos() };
262        [pos.x, pos.y]
263    }
264
265    /// Get available content region size.
266    #[doc(alias = "GetContentRegionAvail")]
267    pub fn get_content_region_avail(&self) -> [f32; 2] {
268        let size = unsafe { sys::igGetContentRegionAvail() };
269        [size.x, size.y]
270    }
271
272    /// Check if a point is inside a rectangle.
273    pub fn is_point_in_rect(
274        &self,
275        point: [f32; 2],
276        rect_min: [f32; 2],
277        rect_max: [f32; 2],
278    ) -> bool {
279        point[0] >= rect_min[0]
280            && point[0] <= rect_max[0]
281            && point[1] >= rect_min[1]
282            && point[1] <= rect_max[1]
283    }
284
285    /// Calculate distance between two points.
286    pub fn distance(&self, p1: [f32; 2], p2: [f32; 2]) -> f32 {
287        let dx = p2[0] - p1[0];
288        let dy = p2[1] - p1[1];
289        (dx * dx + dy * dy).sqrt()
290    }
291
292    /// Calculate squared distance between two points (faster than distance).
293    pub fn distance_squared(&self, p1: [f32; 2], p2: [f32; 2]) -> f32 {
294        let dx = p2[0] - p1[0];
295        let dy = p2[1] - p1[1];
296        dx * dx + dy * dy
297    }
298
299    /// Check if two line segments intersect.
300    pub fn line_segments_intersect(
301        &self,
302        p1: [f32; 2],
303        p2: [f32; 2],
304        p3: [f32; 2],
305        p4: [f32; 2],
306    ) -> bool {
307        let d1 = self.cross_product(
308            [p4[0] - p3[0], p4[1] - p3[1]],
309            [p1[0] - p3[0], p1[1] - p3[1]],
310        );
311        let d2 = self.cross_product(
312            [p4[0] - p3[0], p4[1] - p3[1]],
313            [p2[0] - p3[0], p2[1] - p3[1]],
314        );
315        let d3 = self.cross_product(
316            [p2[0] - p1[0], p2[1] - p1[1]],
317            [p3[0] - p1[0], p3[1] - p1[1]],
318        );
319        let d4 = self.cross_product(
320            [p2[0] - p1[0], p2[1] - p1[1]],
321            [p4[0] - p1[0], p4[1] - p1[1]],
322        );
323
324        (d1 > 0.0) != (d2 > 0.0) && (d3 > 0.0) != (d4 > 0.0)
325    }
326
327    /// Calculate cross product of two 2D vectors.
328    fn cross_product(&self, v1: [f32; 2], v2: [f32; 2]) -> f32 {
329        v1[0] * v2[1] - v1[1] * v2[0]
330    }
331
332    /// Normalize a 2D vector.
333    pub fn normalize(&self, v: [f32; 2]) -> [f32; 2] {
334        let len = (v[0] * v[0] + v[1] * v[1]).sqrt();
335        if len > f32::EPSILON {
336            [v[0] / len, v[1] / len]
337        } else {
338            [0.0, 0.0]
339        }
340    }
341
342    /// Calculate dot product of two 2D vectors.
343    pub fn dot_product(&self, v1: [f32; 2], v2: [f32; 2]) -> f32 {
344        v1[0] * v2[0] + v1[1] * v2[1]
345    }
346
347    /// Calculate the angle between two vectors in radians.
348    pub fn angle_between_vectors(&self, v1: [f32; 2], v2: [f32; 2]) -> f32 {
349        let dot = self.dot_product(v1, v2);
350        let len1 = (v1[0] * v1[0] + v1[1] * v1[1]).sqrt();
351        let len2 = (v2[0] * v2[0] + v2[1] * v2[1]).sqrt();
352
353        if len1 > f32::EPSILON && len2 > f32::EPSILON {
354            (dot / (len1 * len2)).acos()
355        } else {
356            0.0
357        }
358    }
359
360    /// Check if a point is inside a circle.
361    pub fn is_point_in_circle(&self, point: [f32; 2], center: [f32; 2], radius: f32) -> bool {
362        self.distance_squared(point, center) <= radius * radius
363    }
364
365    /// Calculate the area of a triangle given three points.
366    pub fn triangle_area(&self, p1: [f32; 2], p2: [f32; 2], p3: [f32; 2]) -> f32 {
367        let cross = self.cross_product(
368            [p2[0] - p1[0], p2[1] - p1[1]],
369            [p3[0] - p1[0], p3[1] - p1[1]],
370        );
371        cross.abs() * 0.5
372    }
373
374    // Additional utility functions
375
376    /// Allows the next item to be overlapped by a subsequent item.
377    #[doc(alias = "SetNextItemAllowOverlap")]
378    pub fn set_next_item_allow_overlap(&self) {
379        unsafe { sys::igSetNextItemAllowOverlap() };
380    }
381}