Merge pull request #318 from SnoozeTime/tabbar-wrapper

implementation of TabBar and TabItem
This commit is contained in:
Joonas Javanainen 2020-07-08 18:39:16 +03:00 committed by GitHub
commit 5dea51c55b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 302 additions and 0 deletions

View File

@ -47,6 +47,8 @@ struct State {
dont_ask_me_next_time: bool,
stacked_modals_item: usize,
stacked_modals_color: [f32; 4],
tabs: TabState,
}
impl Default for State {
@ -102,6 +104,41 @@ 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: TabState::default(),
}
}
}
struct TabState {
// flags for the advanced tab example
reorderable: bool,
autoselect: bool,
listbutton: bool,
noclose_middlebutton: bool,
fitting_resizedown: bool,
fitting_scroll: bool,
// opened state for tab items
artichoke_tab: bool,
beetroot_tab: bool,
celery_tab: bool,
daikon_tab: bool,
}
impl Default for TabState {
fn default() -> Self {
Self {
reorderable: true,
autoselect: false,
listbutton: false,
noclose_middlebutton: false,
fitting_resizedown: true,
fitting_scroll: false,
artichoke_tab: true,
beetroot_tab: true,
celery_tab: true,
daikon_tab: true,
}
}
}
@ -365,6 +402,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
});
}
});
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"));
@ -585,6 +623,80 @@ CTRL+click on individual component to input value.\n",
b.build(ui);
});
}
if CollapsingHeader::new(im_str!("Layout")).build(&ui) {
TreeNode::new(im_str!("Tabs")).build(&ui, || {
TreeNode::new(im_str!("Basic")).build(&ui, || {
TabBar::new(im_str!("basictabbar")).build(&ui, || {
TabItem::new(im_str!("Avocado")).build(&ui, || {
ui.text(im_str!("This is the Avocado tab!"));
ui.text(im_str!("blah blah blah blah blah"));
});
TabItem::new(im_str!("Broccoli")).build(&ui, || {
ui.text(im_str!("This is the Broccoli tab!"));
ui.text(im_str!("blah blah blah blah blah"));
});
TabItem::new(im_str!("Cucumber")).build(&ui, || {
ui.text(im_str!("This is the Cucumber tab!"));
ui.text(im_str!("blah blah blah blah blah"));
});
});
});
TreeNode::new(im_str!("Advanced & Close button")).build(&ui, || {
ui.separator();
let s = &mut state.tabs;
ui.checkbox(im_str!("ImGuiTabBarFlags_Reorderable"), &mut s.reorderable);
ui.checkbox(im_str!("ImGuiTabBarFlags_AutoSelectNewTabs"), &mut s.autoselect);
ui.checkbox(im_str!("ImGuiTabBarFlags_TabListPopupButton"), &mut s.listbutton);
ui.checkbox(im_str!("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton"), &mut s.noclose_middlebutton);
if ui.checkbox(im_str!("ImGuiTabBarFlags_FittingPolicyResizeDown"), &mut s.fitting_resizedown) {
s.fitting_scroll = !s.fitting_resizedown;
}
if ui.checkbox(im_str!("ImGuiTabBarFlags_FittingPolicyScroll"), &mut s.fitting_scroll) {
s.fitting_resizedown = !s.fitting_scroll;
}
let style = ui.push_style_var(StyleVar::FramePadding([0.0, 0.0]));
ui.checkbox(im_str!("Artichoke"), &mut s.artichoke_tab);
ui.same_line(0.0);
ui.checkbox(im_str!("Beetroot"), &mut s.beetroot_tab);
ui.same_line(0.0);
ui.checkbox(im_str!("Celery"), &mut s.celery_tab);
ui.same_line(0.0);
ui.checkbox(im_str!("Daikon"), &mut s.daikon_tab);
style.pop(ui);
let flags = {
let mut f = TabBarFlags::empty();
f.set(TabBarFlags::REORDERABLE, s.reorderable);
f.set(TabBarFlags::AUTO_SELECT_NEW_TABS, s.autoselect);
f.set(TabBarFlags::TAB_LIST_POPUP_BUTTON, s.listbutton);
f.set(TabBarFlags::NO_CLOSE_WITH_MIDDLE_MOUSE_BUTTON, s.noclose_middlebutton);
f.set(TabBarFlags::FITTING_POLICY_RESIZE_DOWN, s.fitting_resizedown);
f.set(TabBarFlags::FITTING_POLICY_SCROLL, s.fitting_scroll);
f
};
TabBar::new(im_str!("tabbar")).flags(flags).build(&ui, || {
TabItem::new(im_str!("Artichoke")).opened(&mut s.artichoke_tab).build(&ui, || {
ui.text(im_str!("This is the Artichoke tab!"));
});
TabItem::new(im_str!("Beetroot")).opened(&mut s.beetroot_tab).build(&ui, || {
ui.text(im_str!("This is the Beetroot tab!"));
});
TabItem::new(im_str!("Celery")).opened(&mut s.celery_tab).build(&ui, || {
ui.text(im_str!("This is the Celery tab!"));
});
TabItem::new(im_str!("Daikon")).opened(&mut s.daikon_tab).build(&ui, || {
ui.text(im_str!("This is the Daikon tab!"));
});
});
});
});
}
if CollapsingHeader::new(im_str!("Popups & Modal windows")).build(&ui) {
TreeNode::new(im_str!("Popups")).build(&ui, || {
ui.text_wrapped(im_str!(

View File

@ -43,6 +43,7 @@ pub use self::widget::menu::*;
pub use self::widget::progress_bar::*;
pub use self::widget::selectable::*;
pub use self::widget::slider::*;
pub use self::widget::tab::*;
pub use self::widget::tree::*;
pub use self::window::child_window::*;
pub use self::window::*;

View File

@ -6,5 +6,6 @@ pub mod misc;
pub mod progress_bar;
pub mod selectable;
pub mod slider;
pub mod tab;
pub mod text;
pub mod tree;

188
src/widget/tab.rs Normal file
View File

@ -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<TabBarToken> {
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<F: FnOnce()>(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<TabItemToken> {
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<F: FnOnce()>(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()?");
}
}
}