Skip to main content

dear_imgui_rs/widget/
misc.rs

1//! Miscellaneous widgets
2//!
3//! Small convenience widgets that don’t fit elsewhere (e.g. bullets, help
4//! markers). See functions on `Ui` for details.
5//!
6#![allow(
7    clippy::cast_possible_truncation,
8    clippy::cast_sign_loss,
9    clippy::as_conversions
10)]
11use crate::Ui;
12use crate::create_token;
13use crate::sys;
14
15fn assert_finite_vec2(caller: &str, name: &str, value: [f32; 2]) {
16    assert!(
17        value[0].is_finite() && value[1].is_finite(),
18        "{caller} {name} must contain finite values"
19    );
20}
21
22fn validate_invisible_button_flags(caller: &str, flags: ButtonFlags) {
23    let unsupported = flags.bits() & !ButtonFlags::all().bits();
24    assert!(
25        unsupported == 0,
26        "{caller} received unsupported ImGuiButtonFlags bits: 0x{unsupported:X}"
27    );
28}
29
30fn validate_arrow_direction(caller: &str, dir: crate::Direction) {
31    assert!(
32        matches!(
33            dir,
34            crate::Direction::Left
35                | crate::Direction::Right
36                | crate::Direction::Up
37                | crate::Direction::Down
38        ),
39        "{caller} direction must be Left, Right, Up, or Down"
40    );
41}
42
43bitflags::bitflags! {
44    /// Flags for invisible buttons
45    #[repr(transparent)]
46    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47    pub struct ButtonFlags: i32 {
48        /// No flags
49        const NONE = 0;
50        /// Allow interaction overlap with following items.
51        const ALLOW_OVERLAP = sys::ImGuiButtonFlags_AllowOverlap as i32;
52        /// Keep navigation/tabbing enabled for this invisible button.
53        const ENABLE_NAV = sys::ImGuiButtonFlags_EnableNav as i32;
54        /// React on left mouse button
55        const MOUSE_BUTTON_LEFT = sys::ImGuiButtonFlags_MouseButtonLeft as i32;
56        /// React on right mouse button
57        const MOUSE_BUTTON_RIGHT = sys::ImGuiButtonFlags_MouseButtonRight as i32;
58        /// React on middle mouse button
59        const MOUSE_BUTTON_MIDDLE = sys::ImGuiButtonFlags_MouseButtonMiddle as i32;
60    }
61}
62
63/// Direction for arrow buttons (alias for Direction)
64pub use crate::Direction as ArrowDirection;
65
66impl Ui {
67    /// Creates a bullet point
68    #[doc(alias = "Bullet")]
69    pub fn bullet(&self) {
70        unsafe {
71            sys::igBullet();
72        }
73    }
74
75    /// Creates a bullet point with text
76    #[doc(alias = "BulletText")]
77    pub fn bullet_text(&self, text: impl AsRef<str>) {
78        let text_ptr = self.scratch_txt(text);
79        unsafe {
80            // Always treat the value as unformatted user text.
81            const FMT: &[u8; 3] = b"%s\0";
82            sys::igBulletText(FMT.as_ptr() as *const std::os::raw::c_char, text_ptr);
83        }
84    }
85}
86
87impl Ui {
88    /// Creates a small button
89    #[doc(alias = "SmallButton")]
90    pub fn small_button(&self, label: impl AsRef<str>) -> bool {
91        let label_ptr = self.scratch_txt(label);
92        unsafe { sys::igSmallButton(label_ptr) }
93    }
94
95    /// Creates an invisible button
96    #[doc(alias = "InvisibleButton")]
97    pub fn invisible_button(&self, str_id: impl AsRef<str>, size: impl Into<[f32; 2]>) -> bool {
98        self.invisible_button_flags(str_id, size, crate::widget::ButtonFlags::NONE)
99    }
100
101    /// Creates an invisible button with flags
102    #[doc(alias = "InvisibleButton")]
103    pub fn invisible_button_flags(
104        &self,
105        str_id: impl AsRef<str>,
106        size: impl Into<[f32; 2]>,
107        flags: crate::widget::ButtonFlags,
108    ) -> bool {
109        validate_invisible_button_flags("Ui::invisible_button_flags()", flags);
110        let id_ptr = self.scratch_txt(str_id);
111        let size = size.into();
112        assert_finite_vec2("Ui::invisible_button_flags()", "size", size);
113        let size_vec: sys::ImVec2 = size.into();
114        unsafe { sys::igInvisibleButton(id_ptr, size_vec, flags.bits()) }
115    }
116
117    /// Creates an arrow button
118    #[doc(alias = "ArrowButton")]
119    pub fn arrow_button(&self, str_id: impl AsRef<str>, dir: crate::Direction) -> bool {
120        validate_arrow_direction("Ui::arrow_button()", dir);
121        let id_ptr = self.scratch_txt(str_id);
122        unsafe { sys::igArrowButton(id_ptr, dir as i32) }
123    }
124}
125
126// ============================================================================
127// Disabled scope (RAII)
128// ============================================================================
129
130/// Tracks a disabled scope begun with [`Ui::begin_disabled`] and ended on drop.
131#[must_use]
132pub struct DisabledToken<'ui> {
133    _ui: &'ui Ui,
134}
135
136impl<'ui> DisabledToken<'ui> {
137    fn new(ui: &'ui Ui) -> Self {
138        DisabledToken { _ui: ui }
139    }
140
141    /// Ends the disabled scope explicitly.
142    pub fn end(self) {
143        // Drop will call EndDisabled
144    }
145}
146
147impl<'ui> Drop for DisabledToken<'ui> {
148    fn drop(&mut self) {
149        unsafe { sys::igEndDisabled() }
150    }
151}
152
153impl Ui {
154    /// Begin a disabled scope for subsequent items.
155    ///
156    /// All following widgets will be disabled (grayed out and non-interactive)
157    /// until the returned token is dropped.
158    #[doc(alias = "BeginDisabled")]
159    pub fn begin_disabled(&self) -> DisabledToken<'_> {
160        unsafe { sys::igBeginDisabled(true) }
161        DisabledToken::new(self)
162    }
163
164    /// Begin a conditionally disabled scope for subsequent items.
165    ///
166    /// If `disabled` is false, this still needs to be paired with the returned
167    /// token being dropped to correctly balance the internal stack.
168    #[doc(alias = "BeginDisabled")]
169    pub fn begin_disabled_with_cond(&self, disabled: bool) -> DisabledToken<'_> {
170        unsafe { sys::igBeginDisabled(disabled) }
171        DisabledToken::new(self)
172    }
173}
174
175// ============================================================================
176// Button repeat (convenience over item flag)
177// ============================================================================
178
179create_token!(
180    /// Tracks a button repeat item flag pushed with [`Ui::push_button_repeat_token`].
181    pub struct ButtonRepeatToken<'ui>;
182
183    /// Pops the button repeat item flag.
184    #[doc(alias = "PopButtonRepeat")]
185    drop { unsafe { sys::igPopItemFlag() } }
186);
187
188impl ButtonRepeatToken<'_> {
189    /// Pops the button repeat item flag.
190    pub fn pop(self) {
191        self.end()
192    }
193}
194
195impl Ui {
196    /// Enable/disable repeating behavior for subsequent buttons.
197    ///
198    /// Internally uses `PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat)`.
199    ///
200    /// Prefer [`Self::push_button_repeat_token`] or [`Self::with_button_repeat`]
201    /// for scoped usage that remains balanced if a panic unwinds through the
202    /// scope. This manual API is kept for compatibility with existing
203    /// push/pop-style code.
204    #[doc(alias = "PushButtonRepeat")]
205    pub fn push_button_repeat(&self, repeat: bool) {
206        unsafe { sys::igPushItemFlag(sys::ImGuiItemFlags_ButtonRepeat as i32, repeat) }
207    }
208
209    /// Push a button repeat item flag and return an RAII token that pops it on drop.
210    #[doc(alias = "PushButtonRepeat")]
211    pub fn push_button_repeat_token(&self, repeat: bool) -> ButtonRepeatToken<'_> {
212        self.push_button_repeat(repeat);
213        ButtonRepeatToken::new(self)
214    }
215
216    /// Push a button repeat item flag, run `f`, then pop the flag.
217    ///
218    /// The flag is popped during unwinding if `f` panics.
219    #[doc(alias = "PushButtonRepeat", alias = "PopButtonRepeat")]
220    pub fn with_button_repeat<R>(&self, repeat: bool, f: impl FnOnce() -> R) -> R {
221        let _repeat = self.push_button_repeat_token(repeat);
222        f()
223    }
224
225    /// Pop the button repeat item flag.
226    #[doc(alias = "PopButtonRepeat")]
227    pub fn pop_button_repeat(&self) {
228        unsafe { sys::igPopItemFlag() }
229    }
230}
231
232// ============================================================================
233// Item key ownership
234// ============================================================================
235
236impl Ui {
237    /// Set the key owner for the last item, without flags.
238    ///
239    /// Returns `true` when ownership was requested for the item.
240    #[doc(alias = "SetItemKeyOwner")]
241    pub fn set_item_key_owner(&self, key: crate::input::Key) -> bool {
242        let k: sys::ImGuiKey = key as sys::ImGuiKey;
243        unsafe { sys::igSetItemKeyOwner_Nil(k) }
244    }
245
246    /// Set the key owner for the last item with input flags.
247    ///
248    /// Returns `true` when ownership was requested for the item.
249    #[doc(alias = "SetItemKeyOwner")]
250    pub fn set_item_key_owner_with_flags(
251        &self,
252        key: crate::input::Key,
253        flags: crate::input::ItemKeyOwnerFlags,
254    ) -> bool {
255        let k: sys::ImGuiKey = key as sys::ImGuiKey;
256        unsafe { sys::igSetItemKeyOwner_InputFlags(k, flags.raw()) }
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    fn setup_context() -> crate::Context {
265        let mut ctx = crate::Context::create();
266        let _ = ctx.font_atlas_mut().build();
267        ctx.io_mut().set_display_size([128.0, 128.0]);
268        ctx.io_mut().set_delta_time(1.0 / 60.0);
269        ctx
270    }
271
272    #[test]
273    fn with_button_repeat_pops_after_panic() {
274        let mut ctx = setup_context();
275        let ui = ctx.frame();
276        let raw_ctx = unsafe { sys::igGetCurrentContext() };
277        assert!(!raw_ctx.is_null());
278        let initial_stack_size = unsafe { (*raw_ctx).ItemFlagsStack.Size };
279
280        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
281            ui.with_button_repeat(true, || {
282                assert_eq!(
283                    unsafe { (*raw_ctx).ItemFlagsStack.Size },
284                    initial_stack_size + 1
285                );
286                panic!("forced panic while button repeat is pushed");
287            });
288        }));
289
290        assert!(result.is_err());
291        assert_eq!(
292            unsafe { (*raw_ctx).ItemFlagsStack.Size },
293            initial_stack_size
294        );
295    }
296}