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