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