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            if name_ptr.is_null() {
135                return "Unknown";
136            }
137            let c_str = std::ffi::CStr::from_ptr(name_ptr);
138            c_str.to_str().unwrap_or("Unknown")
139        }
140    }
141
142    /// Returns the number of times the mouse button was clicked in the current frame
143    #[doc(alias = "GetMouseClickedCount")]
144    pub fn get_mouse_clicked_count(&self, button: MouseButton) -> i32 {
145        unsafe { sys::igGetMouseClickedCount(button.into()) }
146    }
147
148    /// Returns the mouse position in screen coordinates
149    #[doc(alias = "GetMousePos")]
150    pub fn get_mouse_pos(&self) -> [f32; 2] {
151        let pos = unsafe { sys::igGetMousePos() };
152        [pos.x, pos.y]
153    }
154
155    /// Returns the mouse position when the button was clicked
156    #[doc(alias = "GetMousePosOnOpeningCurrentPopup")]
157    pub fn get_mouse_pos_on_opening_current_popup(&self) -> [f32; 2] {
158        let pos = unsafe { sys::igGetMousePosOnOpeningCurrentPopup() };
159        [pos.x, pos.y]
160    }
161
162    /// Returns the mouse drag delta
163    #[doc(alias = "GetMouseDragDelta")]
164    pub fn get_mouse_drag_delta(&self, button: MouseButton, lock_threshold: f32) -> [f32; 2] {
165        let delta = unsafe { sys::igGetMouseDragDelta(button.into(), lock_threshold) };
166        [delta.x, delta.y]
167    }
168
169    /// Returns the mouse wheel delta
170    #[doc(alias = "GetIO")]
171    pub fn get_mouse_wheel(&self) -> f32 {
172        self.io().mouse_wheel()
173    }
174
175    /// Returns the horizontal mouse wheel delta
176    #[doc(alias = "GetIO")]
177    pub fn get_mouse_wheel_h(&self) -> f32 {
178        self.io().mouse_wheel_h()
179    }
180
181    /// Returns `true` if any mouse button is down
182    #[doc(alias = "IsAnyMouseDown")]
183    pub fn is_any_mouse_down(&self) -> bool {
184        unsafe { sys::igIsAnyMouseDown() }
185    }
186
187    // ============================================================================
188    // General utilities
189    // ============================================================================
190
191    /// Get global imgui time. Incremented by io.DeltaTime every frame.
192    #[doc(alias = "GetTime")]
193    pub fn time(&self) -> f64 {
194        unsafe { sys::igGetTime() }
195    }
196
197    /// Get global imgui frame count. Incremented by 1 every frame.
198    #[doc(alias = "GetFrameCount")]
199    pub fn frame_count(&self) -> i32 {
200        unsafe { sys::igGetFrameCount() }
201    }
202
203    /// Returns a single style color from the user interface style.
204    ///
205    /// Use this function if you need to access the colors, but don't want to clone the entire
206    /// style object.
207    #[doc(alias = "GetStyle")]
208    pub fn style_color(&self, style_color: StyleColor) -> [f32; 4] {
209        unsafe {
210            let style_ptr = sys::igGetStyle();
211            let colors = (*style_ptr).Colors.as_ptr();
212            let color = *colors.add(style_color as usize);
213            [color.x, color.y, color.z, color.w]
214        }
215    }
216
217    /// Returns the name of a style color.
218    ///
219    /// This is just a wrapper around calling [`name`] on [StyleColor].
220    ///
221    /// [`name`]: StyleColor::name
222    #[doc(alias = "GetStyleColorName")]
223    pub fn style_color_name(&self, style_color: StyleColor) -> &'static str {
224        unsafe {
225            let name_ptr = sys::igGetStyleColorName(style_color as sys::ImGuiCol);
226            if name_ptr.is_null() {
227                return "Unknown";
228            }
229            let c_str = std::ffi::CStr::from_ptr(name_ptr);
230            c_str.to_str().unwrap_or("Unknown")
231        }
232    }
233
234    /// Test if rectangle (of given size, starting from cursor position) is visible / not clipped.
235    #[doc(alias = "IsRectVisible")]
236    pub fn is_rect_visible(&self, size: [f32; 2]) -> bool {
237        unsafe {
238            let size = sys::ImVec2 {
239                x: size[0],
240                y: size[1],
241            };
242            sys::igIsRectVisible_Nil(size)
243        }
244    }
245
246    /// Test if rectangle (in screen space) is visible / not clipped.
247    #[doc(alias = "IsRectVisible")]
248    pub fn is_rect_visible_ex(&self, rect_min: [f32; 2], rect_max: [f32; 2]) -> bool {
249        unsafe {
250            let rect_min = sys::ImVec2 {
251                x: rect_min[0],
252                y: rect_min[1],
253            };
254            let rect_max = sys::ImVec2 {
255                x: rect_max[0],
256                y: rect_max[1],
257            };
258            sys::igIsRectVisible_Vec2(rect_min, rect_max)
259        }
260    }
261
262    // ========== Additional Geometry Functions ==========
263
264    /// Get cursor position in screen coordinates.
265    #[doc(alias = "GetCursorScreenPos")]
266    pub fn get_cursor_screen_pos(&self) -> [f32; 2] {
267        let pos = unsafe { sys::igGetCursorScreenPos() };
268        [pos.x, pos.y]
269    }
270
271    /// Get available content region size.
272    #[doc(alias = "GetContentRegionAvail")]
273    pub fn get_content_region_avail(&self) -> [f32; 2] {
274        let size = unsafe { sys::igGetContentRegionAvail() };
275        [size.x, size.y]
276    }
277
278    /// Check if a point is inside a rectangle.
279    pub fn is_point_in_rect(
280        &self,
281        point: [f32; 2],
282        rect_min: [f32; 2],
283        rect_max: [f32; 2],
284    ) -> bool {
285        point[0] >= rect_min[0]
286            && point[0] <= rect_max[0]
287            && point[1] >= rect_min[1]
288            && point[1] <= rect_max[1]
289    }
290
291    /// Calculate distance between two points.
292    pub fn distance(&self, p1: [f32; 2], p2: [f32; 2]) -> f32 {
293        let dx = p2[0] - p1[0];
294        let dy = p2[1] - p1[1];
295        (dx * dx + dy * dy).sqrt()
296    }
297
298    /// Calculate squared distance between two points (faster than distance).
299    pub fn distance_squared(&self, p1: [f32; 2], p2: [f32; 2]) -> f32 {
300        let dx = p2[0] - p1[0];
301        let dy = p2[1] - p1[1];
302        dx * dx + dy * dy
303    }
304
305    /// Check if two line segments intersect.
306    pub fn line_segments_intersect(
307        &self,
308        p1: [f32; 2],
309        p2: [f32; 2],
310        p3: [f32; 2],
311        p4: [f32; 2],
312    ) -> bool {
313        let d1 = self.cross_product(
314            [p4[0] - p3[0], p4[1] - p3[1]],
315            [p1[0] - p3[0], p1[1] - p3[1]],
316        );
317        let d2 = self.cross_product(
318            [p4[0] - p3[0], p4[1] - p3[1]],
319            [p2[0] - p3[0], p2[1] - p3[1]],
320        );
321        let d3 = self.cross_product(
322            [p2[0] - p1[0], p2[1] - p1[1]],
323            [p3[0] - p1[0], p3[1] - p1[1]],
324        );
325        let d4 = self.cross_product(
326            [p2[0] - p1[0], p2[1] - p1[1]],
327            [p4[0] - p1[0], p4[1] - p1[1]],
328        );
329
330        (d1 > 0.0) != (d2 > 0.0) && (d3 > 0.0) != (d4 > 0.0)
331    }
332
333    /// Calculate cross product of two 2D vectors.
334    fn cross_product(&self, v1: [f32; 2], v2: [f32; 2]) -> f32 {
335        v1[0] * v2[1] - v1[1] * v2[0]
336    }
337
338    /// Normalize a 2D vector.
339    pub fn normalize(&self, v: [f32; 2]) -> [f32; 2] {
340        let len = (v[0] * v[0] + v[1] * v[1]).sqrt();
341        if len > f32::EPSILON {
342            [v[0] / len, v[1] / len]
343        } else {
344            [0.0, 0.0]
345        }
346    }
347
348    /// Calculate dot product of two 2D vectors.
349    pub fn dot_product(&self, v1: [f32; 2], v2: [f32; 2]) -> f32 {
350        v1[0] * v2[0] + v1[1] * v2[1]
351    }
352
353    /// Calculate the angle between two vectors in radians.
354    pub fn angle_between_vectors(&self, v1: [f32; 2], v2: [f32; 2]) -> f32 {
355        let dot = self.dot_product(v1, v2);
356        let len1 = (v1[0] * v1[0] + v1[1] * v1[1]).sqrt();
357        let len2 = (v2[0] * v2[0] + v2[1] * v2[1]).sqrt();
358
359        if len1 > f32::EPSILON && len2 > f32::EPSILON {
360            (dot / (len1 * len2)).acos()
361        } else {
362            0.0
363        }
364    }
365
366    /// Check if a point is inside a circle.
367    pub fn is_point_in_circle(&self, point: [f32; 2], center: [f32; 2], radius: f32) -> bool {
368        self.distance_squared(point, center) <= radius * radius
369    }
370
371    /// Calculate the area of a triangle given three points.
372    pub fn triangle_area(&self, p1: [f32; 2], p2: [f32; 2], p3: [f32; 2]) -> f32 {
373        let cross = self.cross_product(
374            [p2[0] - p1[0], p2[1] - p1[1]],
375            [p3[0] - p1[0], p3[1] - p1[1]],
376        );
377        cross.abs() * 0.5
378    }
379
380    // Additional utility functions
381
382    /// Allows the next item to be overlapped by a subsequent item.
383    #[doc(alias = "SetNextItemAllowOverlap")]
384    pub fn set_next_item_allow_overlap(&self) {
385        unsafe { sys::igSetNextItemAllowOverlap() };
386    }
387}