#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::as_conversions
)]
use crate::Ui;
use crate::create_token;
use crate::sys;
fn assert_finite_vec2(caller: &str, name: &str, value: [f32; 2]) {
assert!(
value[0].is_finite() && value[1].is_finite(),
"{caller} {name} must contain finite values"
);
}
fn validate_invisible_button_flags(caller: &str, flags: ButtonFlags) {
let unsupported = flags.bits() & !ButtonFlags::all().bits();
assert!(
unsupported == 0,
"{caller} received unsupported ImGuiButtonFlags bits: 0x{unsupported:X}"
);
}
fn validate_arrow_direction(caller: &str, dir: crate::Direction) {
assert!(
matches!(
dir,
crate::Direction::Left
| crate::Direction::Right
| crate::Direction::Up
| crate::Direction::Down
),
"{caller} direction must be Left, Right, Up, or Down"
);
}
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ButtonFlags: i32 {
const NONE = 0;
const ALLOW_OVERLAP = sys::ImGuiButtonFlags_AllowOverlap as i32;
const ENABLE_NAV = sys::ImGuiButtonFlags_EnableNav as i32;
const MOUSE_BUTTON_LEFT = sys::ImGuiButtonFlags_MouseButtonLeft as i32;
const MOUSE_BUTTON_RIGHT = sys::ImGuiButtonFlags_MouseButtonRight as i32;
const MOUSE_BUTTON_MIDDLE = sys::ImGuiButtonFlags_MouseButtonMiddle as i32;
}
}
pub use crate::Direction as ArrowDirection;
impl Ui {
#[doc(alias = "Bullet")]
pub fn bullet(&self) {
unsafe {
sys::igBullet();
}
}
#[doc(alias = "BulletText")]
pub fn bullet_text(&self, text: impl AsRef<str>) {
let text_ptr = self.scratch_txt(text);
unsafe {
const FMT: &[u8; 3] = b"%s\0";
sys::igBulletText(FMT.as_ptr() as *const std::os::raw::c_char, text_ptr);
}
}
}
impl Ui {
#[doc(alias = "SmallButton")]
pub fn small_button(&self, label: impl AsRef<str>) -> bool {
let label_ptr = self.scratch_txt(label);
unsafe { sys::igSmallButton(label_ptr) }
}
#[doc(alias = "InvisibleButton")]
pub fn invisible_button(&self, str_id: impl AsRef<str>, size: impl Into<[f32; 2]>) -> bool {
self.invisible_button_flags(str_id, size, crate::widget::ButtonFlags::NONE)
}
#[doc(alias = "InvisibleButton")]
pub fn invisible_button_flags(
&self,
str_id: impl AsRef<str>,
size: impl Into<[f32; 2]>,
flags: crate::widget::ButtonFlags,
) -> bool {
validate_invisible_button_flags("Ui::invisible_button_flags()", flags);
let id_ptr = self.scratch_txt(str_id);
let size = size.into();
assert_finite_vec2("Ui::invisible_button_flags()", "size", size);
let size_vec: sys::ImVec2 = size.into();
unsafe { sys::igInvisibleButton(id_ptr, size_vec, flags.bits()) }
}
#[doc(alias = "ArrowButton")]
pub fn arrow_button(&self, str_id: impl AsRef<str>, dir: crate::Direction) -> bool {
validate_arrow_direction("Ui::arrow_button()", dir);
let id_ptr = self.scratch_txt(str_id);
unsafe { sys::igArrowButton(id_ptr, dir as i32) }
}
}
#[must_use]
pub struct DisabledToken<'ui> {
_ui: &'ui Ui,
}
impl<'ui> DisabledToken<'ui> {
fn new(ui: &'ui Ui) -> Self {
DisabledToken { _ui: ui }
}
pub fn end(self) {
}
}
impl<'ui> Drop for DisabledToken<'ui> {
fn drop(&mut self) {
unsafe { sys::igEndDisabled() }
}
}
impl Ui {
#[doc(alias = "BeginDisabled")]
pub fn begin_disabled(&self) -> DisabledToken<'_> {
unsafe { sys::igBeginDisabled(true) }
DisabledToken::new(self)
}
#[doc(alias = "BeginDisabled")]
pub fn begin_disabled_with_cond(&self, disabled: bool) -> DisabledToken<'_> {
unsafe { sys::igBeginDisabled(disabled) }
DisabledToken::new(self)
}
}
create_token!(
pub struct ButtonRepeatToken<'ui>;
#[doc(alias = "PopButtonRepeat")]
drop { unsafe { sys::igPopItemFlag() } }
);
impl ButtonRepeatToken<'_> {
pub fn pop(self) {
self.end()
}
}
impl Ui {
#[doc(alias = "PushButtonRepeat")]
pub fn push_button_repeat(&self, repeat: bool) {
unsafe { sys::igPushItemFlag(sys::ImGuiItemFlags_ButtonRepeat as i32, repeat) }
}
#[doc(alias = "PushButtonRepeat")]
pub fn push_button_repeat_token(&self, repeat: bool) -> ButtonRepeatToken<'_> {
self.push_button_repeat(repeat);
ButtonRepeatToken::new(self)
}
#[doc(alias = "PushButtonRepeat", alias = "PopButtonRepeat")]
pub fn with_button_repeat<R>(&self, repeat: bool, f: impl FnOnce() -> R) -> R {
let _repeat = self.push_button_repeat_token(repeat);
f()
}
#[doc(alias = "PopButtonRepeat")]
pub fn pop_button_repeat(&self) {
unsafe { sys::igPopItemFlag() }
}
}
impl Ui {
#[doc(alias = "SetItemKeyOwner")]
pub fn set_item_key_owner(&self, key: crate::input::Key) -> bool {
let k: sys::ImGuiKey = key as sys::ImGuiKey;
unsafe { sys::igSetItemKeyOwner_Nil(k) }
}
#[doc(alias = "SetItemKeyOwner")]
pub fn set_item_key_owner_with_flags(
&self,
key: crate::input::Key,
flags: crate::input::ItemKeyOwnerFlags,
) -> bool {
let k: sys::ImGuiKey = key as sys::ImGuiKey;
unsafe { sys::igSetItemKeyOwner_InputFlags(k, flags.raw()) }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn setup_context() -> crate::Context {
let mut ctx = crate::Context::create();
let _ = ctx.font_atlas_mut().build();
ctx.io_mut().set_display_size([128.0, 128.0]);
ctx.io_mut().set_delta_time(1.0 / 60.0);
ctx
}
#[test]
fn with_button_repeat_pops_after_panic() {
let mut ctx = setup_context();
let ui = ctx.frame();
let raw_ctx = unsafe { sys::igGetCurrentContext() };
assert!(!raw_ctx.is_null());
let initial_stack_size = unsafe { (*raw_ctx).ItemFlagsStack.Size };
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
ui.with_button_repeat(true, || {
assert_eq!(
unsafe { (*raw_ctx).ItemFlagsStack.Size },
initial_stack_size + 1
);
panic!("forced panic while button repeat is pushed");
});
}));
assert!(result.is_err());
assert_eq!(
unsafe { (*raw_ctx).ItemFlagsStack.Size },
initial_stack_size
);
}
}