use bitflags::bitflags;
use crate::{Ui, sys};
bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ItemFlags: i32 {
const NONE = sys::ImGuiItemFlags_None as i32;
const NO_TAB_STOP = sys::ImGuiItemFlags_NoTabStop as i32;
const NO_NAV = sys::ImGuiItemFlags_NoNav as i32;
const NO_NAV_DEFAULT_FOCUS = sys::ImGuiItemFlags_NoNavDefaultFocus as i32;
const BUTTON_REPEAT = sys::ImGuiItemFlags_ButtonRepeat as i32;
const AUTO_CLOSE_POPUPS = sys::ImGuiItemFlags_AutoClosePopups as i32;
const ALLOW_DUPLICATE_ID = sys::ImGuiItemFlags_AllowDuplicateId as i32;
const LIVE_EDIT_ON_INPUT_TEXT = sys::ImGuiItemFlags_LiveEditOnInputText as i32;
const LIVE_EDIT_ON_INPUT_SCALAR = sys::ImGuiItemFlags_LiveEditOnInputScalar as i32;
const LIVE_EDIT_ON_INPUT = sys::ImGuiItemFlags_LiveEditOnInput as i32;
}
}
bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ItemStateFlags: i32 {
const NONE = sys::ImGuiItemFlags_None as i32;
const NO_TAB_STOP = sys::ImGuiItemFlags_NoTabStop as i32;
const NO_NAV = sys::ImGuiItemFlags_NoNav as i32;
const NO_NAV_DEFAULT_FOCUS = sys::ImGuiItemFlags_NoNavDefaultFocus as i32;
const BUTTON_REPEAT = sys::ImGuiItemFlags_ButtonRepeat as i32;
const AUTO_CLOSE_POPUPS = sys::ImGuiItemFlags_AutoClosePopups as i32;
const ALLOW_DUPLICATE_ID = sys::ImGuiItemFlags_AllowDuplicateId as i32;
const LIVE_EDIT_ON_INPUT_TEXT = sys::ImGuiItemFlags_LiveEditOnInputText as i32;
const LIVE_EDIT_ON_INPUT_SCALAR = sys::ImGuiItemFlags_LiveEditOnInputScalar as i32;
const LIVE_EDIT_ON_INPUT = sys::ImGuiItemFlags_LiveEditOnInput as i32;
const DISABLED = sys::ImGuiItemFlags_Disabled as i32;
}
}
impl Default for ItemFlags {
fn default() -> Self {
Self::NONE
}
}
impl Default for ItemStateFlags {
fn default() -> Self {
Self::NONE
}
}
impl From<ItemFlags> for ItemStateFlags {
fn from(flags: ItemFlags) -> Self {
Self::from_bits_retain(flags.bits())
}
}
create_token!(
pub struct ItemFlagStackToken<'ui>;
pop crate::scope::NativeScopePop::PopItemFlag;
#[doc(alias = "PopItemFlag")]
drop { unsafe { sys::igPopItemFlag() } }
);
impl ItemFlagStackToken<'_> {
pub fn pop(self) {
self.end()
}
}
impl Ui {
#[doc(alias = "GetItemFlags")]
pub fn item_flags(&self) -> ItemStateFlags {
self.run_with_bound_context(|| unsafe {
ItemStateFlags::from_bits_retain(sys::igGetItemFlags())
})
}
#[doc(alias = "PushItemFlag")]
pub fn push_item_flag(&self, flags: ItemFlags, enabled: bool) -> ItemFlagStackToken<'_> {
self.run_with_bound_context(|| unsafe { sys::igPushItemFlag(flags.bits(), enabled) });
ItemFlagStackToken::new(self)
}
#[doc(alias = "PushItemFlag", alias = "PopItemFlag")]
pub fn with_item_flag<R>(&self, flags: ItemFlags, enabled: bool, f: impl FnOnce() -> R) -> R {
let flags = self.push_item_flag(flags, enabled);
let result = f();
drop(flags);
result
}
}
#[cfg(test)]
mod tests {
use super::*;
fn setup_context() -> crate::Context {
let mut ctx = crate::Context::create();
ctx.io_mut().set_display_size([128.0, 128.0]);
ctx.io_mut().set_delta_time(1.0 / 60.0);
ctx.font_atlas()
.try_claim_legacy_renderer()
.expect("legacy renderer font atlas should be available")
.build();
ctx
}
#[test]
fn item_flag_scope_is_typed_and_restores_previous_flags() {
let mut ctx = setup_context();
let ui = ctx.frame();
ui.window("item_flags").build(|| {
ui.with_item_flag(
ItemFlags::NO_NAV
| ItemFlags::ALLOW_DUPLICATE_ID
| ItemFlags::LIVE_EDIT_ON_INPUT_SCALAR,
true,
|| {
ui.button("scoped");
let flags = ui.item_flags();
assert!(flags.contains(ItemStateFlags::NO_NAV));
assert!(flags.contains(ItemStateFlags::ALLOW_DUPLICATE_ID));
assert!(flags.contains(ItemStateFlags::LIVE_EDIT_ON_INPUT_SCALAR));
},
);
ui.button("restored");
let flags = ui.item_flags();
assert!(!flags.contains(ItemStateFlags::NO_NAV));
assert!(!flags.contains(ItemStateFlags::ALLOW_DUPLICATE_ID));
assert!(!flags.contains(ItemStateFlags::LIVE_EDIT_ON_INPUT_SCALAR));
});
}
#[test]
fn item_flag_scope_can_clear_defaults_and_reports_disabled_items() {
let mut ctx = setup_context();
let ui = ctx.frame();
ui.window("item_flag_values").build(|| {
ui.button("default");
assert!(ui.item_flags().contains(ItemStateFlags::AUTO_CLOSE_POPUPS));
{
let _flags = ui.push_item_flag(ItemFlags::AUTO_CLOSE_POPUPS, false);
ui.button("without_auto_close");
assert!(!ui.item_flags().contains(ItemStateFlags::AUTO_CLOSE_POPUPS));
}
ui.button("restored_default");
assert!(ui.item_flags().contains(ItemStateFlags::AUTO_CLOSE_POPUPS));
{
let _disabled = ui.begin_disabled();
ui.button("disabled");
assert!(ui.item_flags().contains(ItemStateFlags::DISABLED));
}
});
}
#[test]
fn disabled_and_item_flag_tokens_share_one_strict_native_stack() {
let mut ctx = setup_context();
let ui = ctx.frame();
ui.window("item_flag_ordering").build(|| {
let flag = ui.push_item_flag(ItemFlags::NO_NAV, true);
let disabled = ui.begin_disabled();
assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(flag))).is_err());
assert!(
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
ui.button("blocked while recovery is pending");
}))
.is_err()
);
drop(disabled);
ui.button("recovered");
assert!(!ui.item_flags().contains(ItemStateFlags::NO_NAV));
assert!(!ui.item_flags().contains(ItemStateFlags::DISABLED));
});
}
#[test]
fn closure_scope_drops_its_return_value_before_recovering_an_outer_token() {
let mut ctx = setup_context();
let ui = ctx.frame();
ui.window("item_flag_closure_ordering").build(|| {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _ = ui.with_item_flag(ItemFlags::NO_NAV, true, || {
ui.push_item_flag(ItemFlags::BUTTON_REPEAT, true)
});
}));
assert!(result.is_err());
ui.button("recovered after closure result cleanup");
let flags = ui.item_flags();
assert!(!flags.contains(ItemStateFlags::NO_NAV));
assert!(!flags.contains(ItemStateFlags::BUTTON_REPEAT));
});
}
}