Skip to main content

dear_imgui_rs/stacks/
item.rs

1use bitflags::bitflags;
2
3use crate::{Ui, sys};
4
5bitflags! {
6    /// Flags that can be applied to subsequently submitted items.
7    #[repr(transparent)]
8    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9    pub struct ItemFlags: i32 {
10        /// No item flags.
11        const NONE = sys::ImGuiItemFlags_None as i32;
12        /// Disable keyboard tabbing while retaining directional navigation.
13        const NO_TAB_STOP = sys::ImGuiItemFlags_NoTabStop as i32;
14        /// Disable keyboard and gamepad navigation.
15        const NO_NAV = sys::ImGuiItemFlags_NoNav as i32;
16        /// Prevent the item from receiving default navigation focus.
17        const NO_NAV_DEFAULT_FOCUS = sys::ImGuiItemFlags_NoNavDefaultFocus as i32;
18        /// Enable repeat behavior for button-like items.
19        const BUTTON_REPEAT = sys::ImGuiItemFlags_ButtonRepeat as i32;
20        /// Automatically close a parent popup after activating a menu item or selectable.
21        const AUTO_CLOSE_POPUPS = sys::ImGuiItemFlags_AutoClosePopups as i32;
22        /// Allow duplicate item IDs without a debug conflict warning.
23        const ALLOW_DUPLICATE_ID = sys::ImGuiItemFlags_AllowDuplicateId as i32;
24        /// Apply `InputText` keyboard edits to the backing value while typing.
25        const LIVE_EDIT_ON_INPUT_TEXT = sys::ImGuiItemFlags_LiveEditOnInputText as i32;
26        /// Apply scalar keyboard edits to the backing value while typing.
27        const LIVE_EDIT_ON_INPUT_SCALAR = sys::ImGuiItemFlags_LiveEditOnInputScalar as i32;
28        /// Apply text and scalar keyboard edits to backing values while typing.
29        const LIVE_EDIT_ON_INPUT = sys::ImGuiItemFlags_LiveEditOnInput as i32;
30    }
31}
32
33bitflags! {
34    /// Flags recorded for the last submitted item.
35    ///
36    /// This includes the public flags accepted by [`Ui::push_item_flag`] plus
37    /// read-only state such as [`Self::DISABLED`]. Unknown internal bits are
38    /// retained when returned by [`Ui::item_flags`].
39    #[repr(transparent)]
40    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41    pub struct ItemStateFlags: i32 {
42        /// No item flags.
43        const NONE = sys::ImGuiItemFlags_None as i32;
44        /// Keyboard tabbing was disabled while retaining directional navigation.
45        const NO_TAB_STOP = sys::ImGuiItemFlags_NoTabStop as i32;
46        /// Keyboard and gamepad navigation was disabled.
47        const NO_NAV = sys::ImGuiItemFlags_NoNav as i32;
48        /// The item could not receive default navigation focus.
49        const NO_NAV_DEFAULT_FOCUS = sys::ImGuiItemFlags_NoNavDefaultFocus as i32;
50        /// Repeat behavior was enabled for the item.
51        const BUTTON_REPEAT = sys::ImGuiItemFlags_ButtonRepeat as i32;
52        /// The item could automatically close its parent popup.
53        const AUTO_CLOSE_POPUPS = sys::ImGuiItemFlags_AutoClosePopups as i32;
54        /// Duplicate IDs were allowed for the item.
55        const ALLOW_DUPLICATE_ID = sys::ImGuiItemFlags_AllowDuplicateId as i32;
56        /// `InputText` keyboard edits were applied while typing.
57        const LIVE_EDIT_ON_INPUT_TEXT = sys::ImGuiItemFlags_LiveEditOnInputText as i32;
58        /// Scalar keyboard edits were applied while typing.
59        const LIVE_EDIT_ON_INPUT_SCALAR = sys::ImGuiItemFlags_LiveEditOnInputScalar as i32;
60        /// Text and scalar keyboard edits were applied while typing.
61        const LIVE_EDIT_ON_INPUT = sys::ImGuiItemFlags_LiveEditOnInput as i32;
62        /// The last item was disabled.
63        const DISABLED = sys::ImGuiItemFlags_Disabled as i32;
64    }
65}
66
67impl Default for ItemFlags {
68    fn default() -> Self {
69        Self::NONE
70    }
71}
72
73impl Default for ItemStateFlags {
74    fn default() -> Self {
75        Self::NONE
76    }
77}
78
79impl From<ItemFlags> for ItemStateFlags {
80    fn from(flags: ItemFlags) -> Self {
81        Self::from_bits_retain(flags.bits())
82    }
83}
84
85create_token!(
86    /// Tracks item flags pushed with [`Ui::push_item_flag`].
87    pub struct ItemFlagStackToken<'ui>;
88
89    pop crate::scope::NativeScopePop::PopItemFlag;
90
91    /// Pops item flags pushed with [`Ui::push_item_flag`].
92    #[doc(alias = "PopItemFlag")]
93    drop { unsafe { sys::igPopItemFlag() } }
94);
95
96impl ItemFlagStackToken<'_> {
97    /// Pops the item flag scope.
98    ///
99    /// # Panics
100    ///
101    /// Panics under the same conditions as [`Self::end`].
102    pub fn pop(self) {
103        self.end()
104    }
105}
106
107impl Ui {
108    /// Returns the flags recorded for the last submitted item.
109    ///
110    /// Unknown bits introduced by newer Dear ImGui versions are retained.
111    #[doc(alias = "GetItemFlags")]
112    pub fn item_flags(&self) -> ItemStateFlags {
113        self.run_with_bound_context(|| unsafe {
114            ItemStateFlags::from_bits_retain(sys::igGetItemFlags())
115        })
116    }
117
118    /// Enables or disables flags for subsequently submitted items.
119    ///
120    /// The returned token restores the previous item flags when dropped.
121    #[doc(alias = "PushItemFlag")]
122    pub fn push_item_flag(&self, flags: ItemFlags, enabled: bool) -> ItemFlagStackToken<'_> {
123        self.run_with_bound_context(|| unsafe { sys::igPushItemFlag(flags.bits(), enabled) });
124        ItemFlagStackToken::new(self)
125    }
126
127    /// Runs `f` with the requested item flags enabled or disabled.
128    ///
129    /// The previous flags are restored even if `f` panics.
130    #[doc(alias = "PushItemFlag", alias = "PopItemFlag")]
131    pub fn with_item_flag<R>(&self, flags: ItemFlags, enabled: bool, f: impl FnOnce() -> R) -> R {
132        let flags = self.push_item_flag(flags, enabled);
133        let result = f();
134        drop(flags);
135        result
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    fn setup_context() -> crate::Context {
144        let mut ctx = crate::Context::create();
145        ctx.io_mut().set_display_size([128.0, 128.0]);
146        ctx.io_mut().set_delta_time(1.0 / 60.0);
147        ctx.font_atlas()
148            .try_claim_legacy_renderer()
149            .expect("legacy renderer font atlas should be available")
150            .build();
151        ctx
152    }
153
154    #[test]
155    fn item_flag_scope_is_typed_and_restores_previous_flags() {
156        let mut ctx = setup_context();
157        let ui = ctx.frame();
158
159        ui.window("item_flags").build(|| {
160            ui.with_item_flag(
161                ItemFlags::NO_NAV
162                    | ItemFlags::ALLOW_DUPLICATE_ID
163                    | ItemFlags::LIVE_EDIT_ON_INPUT_SCALAR,
164                true,
165                || {
166                    ui.button("scoped");
167                    let flags = ui.item_flags();
168                    assert!(flags.contains(ItemStateFlags::NO_NAV));
169                    assert!(flags.contains(ItemStateFlags::ALLOW_DUPLICATE_ID));
170                    assert!(flags.contains(ItemStateFlags::LIVE_EDIT_ON_INPUT_SCALAR));
171                },
172            );
173
174            ui.button("restored");
175            let flags = ui.item_flags();
176            assert!(!flags.contains(ItemStateFlags::NO_NAV));
177            assert!(!flags.contains(ItemStateFlags::ALLOW_DUPLICATE_ID));
178            assert!(!flags.contains(ItemStateFlags::LIVE_EDIT_ON_INPUT_SCALAR));
179        });
180    }
181
182    #[test]
183    fn item_flag_scope_can_clear_defaults_and_reports_disabled_items() {
184        let mut ctx = setup_context();
185        let ui = ctx.frame();
186
187        ui.window("item_flag_values").build(|| {
188            ui.button("default");
189            assert!(ui.item_flags().contains(ItemStateFlags::AUTO_CLOSE_POPUPS));
190
191            {
192                let _flags = ui.push_item_flag(ItemFlags::AUTO_CLOSE_POPUPS, false);
193                ui.button("without_auto_close");
194                assert!(!ui.item_flags().contains(ItemStateFlags::AUTO_CLOSE_POPUPS));
195            }
196
197            ui.button("restored_default");
198            assert!(ui.item_flags().contains(ItemStateFlags::AUTO_CLOSE_POPUPS));
199
200            {
201                let _disabled = ui.begin_disabled();
202                ui.button("disabled");
203                assert!(ui.item_flags().contains(ItemStateFlags::DISABLED));
204            }
205        });
206    }
207
208    #[test]
209    fn disabled_and_item_flag_tokens_share_one_strict_native_stack() {
210        let mut ctx = setup_context();
211        let ui = ctx.frame();
212
213        ui.window("item_flag_ordering").build(|| {
214            let flag = ui.push_item_flag(ItemFlags::NO_NAV, true);
215            let disabled = ui.begin_disabled();
216
217            assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(flag))).is_err());
218            assert!(
219                std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
220                    ui.button("blocked while recovery is pending");
221                }))
222                .is_err()
223            );
224
225            drop(disabled);
226            ui.button("recovered");
227            assert!(!ui.item_flags().contains(ItemStateFlags::NO_NAV));
228            assert!(!ui.item_flags().contains(ItemStateFlags::DISABLED));
229        });
230    }
231
232    #[test]
233    fn closure_scope_drops_its_return_value_before_recovering_an_outer_token() {
234        let mut ctx = setup_context();
235        let ui = ctx.frame();
236
237        ui.window("item_flag_closure_ordering").build(|| {
238            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
239                let _ = ui.with_item_flag(ItemFlags::NO_NAV, true, || {
240                    ui.push_item_flag(ItemFlags::BUTTON_REPEAT, true)
241                });
242            }));
243            assert!(result.is_err());
244
245            ui.button("recovered after closure result cleanup");
246            let flags = ui.item_flags();
247            assert!(!flags.contains(ItemStateFlags::NO_NAV));
248            assert!(!flags.contains(ItemStateFlags::BUTTON_REPEAT));
249        });
250    }
251}