From 6314e72b9ed9332dc965f0919e8d1b84d5e6859b Mon Sep 17 00:00:00 2001 From: Benoit Eudier Date: Fri, 8 May 2020 13:07:17 +0900 Subject: [PATCH] implementation of TabBar and TabItem --- imgui-examples/examples/test_window_impl.rs | 29 +++ src/lib.rs | 1 + src/widget/mod.rs | 1 + src/widget/tab.rs | 188 ++++++++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 src/widget/tab.rs diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index 70e229f..19c5514 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -47,6 +47,10 @@ struct State { dont_ask_me_next_time: bool, stacked_modals_item: usize, stacked_modals_color: [f32; 4], + + tabs_reorderable: bool, + tab_1_opened: bool, + tab_2_opened: bool, } impl Default for State { @@ -102,6 +106,9 @@ impl Default for State { dont_ask_me_next_time: false, stacked_modals_item: 0, stacked_modals_color: [0.4, 0.7, 0.0, 0.5], + tabs_reorderable: true, + tab_1_opened: true, + tab_2_opened: true, } } } @@ -365,6 +372,28 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { }); } }); + + TreeNode::new(im_str!("Tabs")).build(&ui, || { + ui.separator(); + let style = ui.push_style_var(StyleVar::FramePadding([0.0, 0.0])); + ui.checkbox(im_str!("Tab 1 opened"), &mut state.tab_1_opened); + ui.same_line(0.0); + ui.checkbox(im_str!("Tab 2 opened"), &mut state.tab_2_opened); + ui.same_line(0.0); + ui.checkbox(im_str!("Tabs reorderable"), &mut state.tabs_reorderable); + + style.pop(ui); + + TabBar::new(im_str!("tabbar")).reorderable(state.tabs_reorderable).build(&ui, || { + TabItem::new(im_str!("a tab")).opened(&mut state.tab_1_opened).build(&ui, || { + ui.text(im_str!("tab content 1")); + }); + TabItem::new(im_str!("2tab")).opened(&mut state.tab_2_opened).build(&ui, || { + ui.text(im_str!("tab content 2")); + }); + }); + }); + TreeNode::new(im_str!("Bullets")).build(&ui, || { ui.bullet_text(im_str!("Bullet point 1")); ui.bullet_text(im_str!("Bullet point 2\nOn multiple lines")); diff --git a/src/lib.rs b/src/lib.rs index 85dc0b5..2757dbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,7 @@ pub use self::widget::progress_bar::*; pub use self::widget::selectable::*; pub use self::widget::slider::*; pub use self::widget::tree::*; +pub use self::widget::tab::*; pub use self::window::child_window::*; pub use self::window::*; pub use self::window_draw_list::{ChannelsSplit, ImColor, WindowDrawList}; diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 5edc6b9..8ba3065 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -8,3 +8,4 @@ pub mod selectable; pub mod slider; pub mod text; pub mod tree; +pub mod tab; \ No newline at end of file diff --git a/src/widget/tab.rs b/src/widget/tab.rs new file mode 100644 index 0000000..7b77a74 --- /dev/null +++ b/src/widget/tab.rs @@ -0,0 +1,188 @@ +//! Safe wrapper around imgui-sys for tab menu. +//! +//! # Examples +// +//! ```no_run +//! # use imgui::*; +//! # let mut ctx = Context::create(); +//! # let ui = ctx.frame(); +//! +//! // During UI construction +//! TabBar::new(im_str!("tabbar")).build(&ui, || { +//! TabItem::new(im_str!("a tab")).build(&ui, || { +//! ui.text(im_str!("tab content 1")); +//! }); +//! TabItem::new(im_str!("2tab")).build(&ui, || { +//! ui.text(im_str!("tab content 2")); +//! }); +//! }); +//! ``` +//! +//! See `test_window_impl.rs` for a more complicated example. +use crate::context::Context; +use crate::string::ImStr; +use crate::sys; +use crate::Ui; +use bitflags::bitflags; +use std::{ptr, thread}; + +bitflags! { + #[repr(transparent)] + pub struct TabBarFlags: u32 { + const REORDERABLE = sys::ImGuiTabBarFlags_Reorderable; + const AUTO_SELECT_NEW_TABS = sys::ImGuiTabBarFlags_AutoSelectNewTabs; + const TAB_LIST_POPUP_BUTTON = sys::ImGuiTabBarFlags_TabListPopupButton; + const NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON = sys::ImGuiTabBarFlags_NoCloseWithMiddleMouseButton; + const NO_TAB_LIST_SCROLLING_BUTTONS = sys::ImGuiTabBarFlags_NoTabListScrollingButtons; + const NO_TOOLTIP = sys::ImGuiTabBarFlags_NoTooltip; + const FITTING_POLICY_RESIZE_DOWN = sys::ImGuiTabBarFlags_FittingPolicyResizeDown; + const FITTING_POLICY_SCROLL = sys::ImGuiTabBarFlags_FittingPolicyScroll; + const FITTING_POLICY_MASK = sys::ImGuiTabBarFlags_FittingPolicyMask_; + const FITTING_POLICY_DEFAULT = sys::ImGuiTabBarFlags_FittingPolicyDefault_; + } +} + +/// Builder for a tab bar. +pub struct TabBar<'a> { + id: &'a ImStr, + flags: TabBarFlags, +} + +impl<'a> TabBar<'a> { + pub fn new(id: &'a ImStr) -> Self { + Self { + id, + flags: TabBarFlags::empty(), + } + } + + /// Enable/Disable the reorderable property + /// + /// Disabled by default + #[inline] + pub fn reorderable(mut self, value: bool) -> Self { + self.flags.set(TabBarFlags::REORDERABLE, value); + self + } + + /// Set the flags of the tab bar. + /// + /// Flags are empty by default + #[inline] + pub fn flags(mut self, flags: TabBarFlags) -> Self { + self.flags = flags; + self + } + + #[must_use] + pub fn begin(self, ui: &Ui) -> Option { + let shoud_render = + unsafe { sys::igBeginTabBar(self.id.as_ptr(), self.flags.bits() as i32) }; + + if shoud_render { + Some(TabBarToken { ctx: ui.ctx }) + } else { + unsafe { sys::igEndTabBar() }; + None + } + } + + /// Creates a tab bar and runs a closure to construct the contents. + /// + /// Note: the closure is not called if no tabbar content is visible + pub fn build(self, ui: &Ui, f: F) { + if let Some(tab) = self.begin(ui) { + f(); + tab.end(ui); + } + } +} + +/// Tracks a window that must be ended by calling `.end()` +pub struct TabBarToken { + ctx: *const Context, +} + +impl TabBarToken { + /// Ends a tab bar + pub fn end(mut self, _: &Ui) { + self.ctx = ptr::null(); + unsafe { sys::igEndTabBar() }; + } +} + +impl Drop for TabBarToken { + fn drop(&mut self) { + if !self.ctx.is_null() && !thread::panicking() { + panic!("A TabBarToken was leaked. Did you call .end()?"); + } + } +} + +pub struct TabItem<'a> { + name: &'a ImStr, + opened: Option<&'a mut bool>, +} + +impl<'a> TabItem<'a> { + pub fn new(name: &'a ImStr) -> Self { + Self { name, opened: None } + } + + /// Will open or close the tab.\ + /// + /// True to display the tab. Tab item is visible by default. + #[inline] + pub fn opened(mut self, opened: &'a mut bool) -> Self { + self.opened = Some(opened); + self + } + + #[must_use] + pub fn begin(self, ui: &Ui) -> Option { + let shoud_render = unsafe { + sys::igBeginTabItem( + self.name.as_ptr(), + self.opened + .map(|x| x as *mut bool) + .unwrap_or(ptr::null_mut()), + 0 as ::std::os::raw::c_int, + ) + }; + + if shoud_render { + Some(TabItemToken { ctx: ui.ctx }) + } else { + None + } + } + + /// Creates a tab item and runs a closure to construct the contents. + /// + /// Note: the closure is not called if the tab item is not selected + pub fn build(self, ui: &Ui, f: F) { + if let Some(tab) = self.begin(ui) { + f(); + tab.end(ui); + } + } +} + +pub struct TabItemToken { + ctx: *const Context, +} + +impl TabItemToken { + pub fn end(mut self, _: &Ui) { + self.ctx = ptr::null(); + unsafe { sys::igEndTabItem() }; + } +} + +impl Drop for TabItemToken { + fn drop(&mut self) { + if !self.ctx.is_null() && !thread::panicking() { + panic!("A TabItemToken was leaked. Did you call .end()?"); + } + } +}