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