diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 2a3c52a..cfda912 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Redesigned tree / collapsing header API + ## [0.3.0] - 2020-02-15 ### Added diff --git a/imgui-examples/examples/collapsing_header.rs b/imgui-examples/examples/collapsing_header.rs new file mode 100644 index 0000000..29785ba --- /dev/null +++ b/imgui-examples/examples/collapsing_header.rs @@ -0,0 +1,70 @@ +use imgui::*; + +mod support; + +fn main() { + let mut state = State { + render_closable: true, + }; + let system = support::init(file!()); + system.main_loop(move |run, ui| { + let w = Window::new(im_str!("Collapsing header")) + .opened(run) + .position([20.0, 20.0], Condition::Appearing) + .size([700.0, 500.0], Condition::Appearing); + w.build(&ui, || { + if CollapsingHeader::new(im_str!("I'm a collapsing header. Click me!")).build(&ui) { + ui.text( + "A collapsing header can be used to toggle rendering of a group of widgets", + ); + } + + ui.spacing(); + if CollapsingHeader::new(im_str!("I'm open by default")) + .default_open(true) + .build(&ui) + { + ui.text("You can still close me with a click!"); + } + + ui.spacing(); + if CollapsingHeader::new(im_str!("I only open with double-click")) + .open_on_double_click(true) + .build(&ui) + { + ui.text("Double the clicks, double the fun!"); + } + + ui.spacing(); + if CollapsingHeader::new(im_str!("I don't have an arrow")) + .bullet(true) + .build(&ui) + { + ui.text("Collapsing headers can use a bullet instead of an arrow"); + } + + ui.spacing(); + if CollapsingHeader::new(im_str!("I only open if you click the arrow")) + .open_on_arrow(true) + .build(&ui) + { + ui.text("You clicked the arrow"); + } + + ui.spacing(); + ui.checkbox( + im_str!("Toggle rendering of the next example"), + &mut state.render_closable, + ); + if CollapsingHeader::new(im_str!("I've got a separate close button")) + .build_with_close_button(&ui, &mut state.render_closable) + { + ui.text("I've got contents just like any other collapsing header"); + } + }); + }); +} + +struct State { + render_closable: bool, +} diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index 50b28e6..70e229f 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -327,7 +327,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { menu_bar.end(ui); } ui.spacing(); - if ui.collapsing_header(im_str!("Help")).build() { + if CollapsingHeader::new(im_str!("Help")).build(&ui) { ui.text_wrapped(im_str!( "This window is being created by the show_test_window() \ function. Please refer to the code for programming \ @@ -336,7 +336,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { show_user_guide(ui); } - if ui.collapsing_header(im_str!("Window options")).build() { + if CollapsingHeader::new(im_str!("Window options")).build(&ui) { ui.checkbox(im_str!("No titlebar"), &mut state.no_titlebar); ui.same_line(150.0); ui.checkbox(im_str!("No scrollbar"), &mut state.no_scrollbar); @@ -349,13 +349,14 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { ui.checkbox(im_str!("No collapse"), &mut state.no_collapse); ui.checkbox(im_str!("No close"), &mut state.no_close); - ui.tree_node(im_str!("Style")) - .build(|| ui.show_default_style_editor()); + TreeNode::new(im_str!("Style")).build(&ui, || { + ui.show_default_style_editor(); + }); } - if ui.collapsing_header(im_str!("Widgets")).build() { - ui.tree_node(im_str!("Tree")).build(|| { + if CollapsingHeader::new(im_str!("Widgets")).build(&ui) { + TreeNode::new(im_str!("Tree")).build(&ui, || { for i in 0..5 { - ui.tree_node(&im_str!("Child {}", i)).build(|| { + TreeNode::new(&im_str!("Child {}", i)).build(&ui, || { ui.text(im_str!("blah blah")); ui.same_line(0.0); if ui.small_button(im_str!("print")) { @@ -364,7 +365,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { }); } }); - ui.tree_node(im_str!("Bullets")).build(|| { + 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")); ui.bullet(); @@ -373,13 +374,13 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { ui.bullet(); ui.small_button(im_str!("Button")); }); - ui.tree_node(im_str!("Colored text")).build(|| { + TreeNode::new(im_str!("Colored text")).build(&ui, || { ui.text_colored([1.0, 0.0, 1.0, 1.0], im_str!("Pink")); ui.text_colored([1.0, 1.0, 0.0, 1.0], im_str!("Yellow")); ui.text_disabled(im_str!("Disabled")); }); - ui.tree_node(im_str!("Multi-line text")).build(|| { + TreeNode::new(im_str!("Multi-line text")).build(&ui, || { ui.input_text_multiline( im_str!("multiline"), &mut state.text_multiline, @@ -387,7 +388,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { ).build(); }); - ui.tree_node(im_str!("Word Wrapping")).build(|| { + TreeNode::new(im_str!("Word wrapping")).build(&ui, || { ui.text_wrapped(im_str!( "This text should automatically wrap on the edge of \ the window.The current implementation for text \ @@ -406,7 +407,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { ui.text(im_str!("Test paragraph 2:")); // TODO }); - ui.tree_node(im_str!("UTF-8 Text")).build(|| { + TreeNode::new(im_str!("UTF-8 Text")).build(&ui, || { ui.text_wrapped(im_str!( "CJK text will only appear if the font was loaded \ with theappropriate CJK character ranges. Call \ @@ -469,7 +470,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { ColorEdit::new(im_str!("color 1"), &mut state.col1).build(ui); ColorEdit::new(im_str!("color 2"), &mut state.col2).build(ui); - ui.tree_node(im_str!("Multi-component Widgets")).build(|| { + TreeNode::new(im_str!("Multi-component Widgets")).build(&ui, || { ui.input_float2(im_str!("input float2"), &mut state.vec2f) .build(); ui.input_int2(im_str!("input int2"), &mut state.vec2i) @@ -483,7 +484,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { ui.spacing(); }); - ui.tree_node(im_str!("Color/Picker Widgets")).build(|| { + TreeNode::new(im_str!("Color/Picker Widgets")).build(&ui, || { let s = &mut state.color_edit; ui.checkbox(im_str!("With HDR"), &mut s.hdr); ui.same_line(0.0); @@ -584,11 +585,8 @@ CTRL+click on individual component to input value.\n", b.build(ui); }); } - if ui - .collapsing_header(im_str!("Popups & Modal windows")) - .build() - { - ui.tree_node(im_str!("Popups")).build(|| { + if CollapsingHeader::new(im_str!("Popups & Modal windows")).build(&ui) { + TreeNode::new(im_str!("Popups")).build(&ui, || { ui.text_wrapped(im_str!( "When a popup is active, it inhibits interacting \ with windows that are behind the popup. Clicking \ @@ -620,7 +618,7 @@ CTRL+click on individual component to input value.\n", }); }); - ui.tree_node(im_str!("Modals")).build(|| { + TreeNode::new(im_str!("Modals")).build(&ui, || { ui.text_wrapped(im_str!( "Modal windows are like popups but the user cannot close \ them by clicking outside the window." diff --git a/src/legacy.rs b/src/legacy.rs index f40b858..c2dad58 100644 --- a/src/legacy.rs +++ b/src/legacy.rs @@ -2,6 +2,10 @@ use bitflags::bitflags; use std::os::raw::c_int; +use crate::string::ImStr; +use crate::widget::tree::{CollapsingHeader, TreeNode, TreeNodeFlags}; +use crate::Ui; + bitflags!( /// Flags for igBeginDragDropSource(), igAcceptDragDropPayload() #[repr(C)] @@ -116,48 +120,21 @@ bitflags!( } ); -bitflags!( - /// Flags for trees and collapsing headers - #[repr(C)] - pub struct ImGuiTreeNodeFlags: c_int { - /// Draw as selected - const Selected = 1; - /// Full colored frame (e.g. for collapsing header) - const Framed = 1 << 1; - /// Hit testing to allow subsequent widgets to overlap this one - const AllowItemOverlap = 1 << 2; - /// Don't do a tree push when open (e.g. for collapsing header) = no extra indent nor - /// pushing on ID stack - const NoTreePushOnOpen = 1 << 3; - /// Don't automatically and temporarily open node when Logging is active (by default - /// logging will automatically open tree nodes) - const NoAutoOpenOnLog = 1 << 4; - /// Default node to be open - const DefaultOpen = 1 << 5; - /// Need double-click to open node - const OpenOnDoubleClick = 1 << 6; - /// Only open when clicking on the arrow part. If OpenOnDoubleClick is also set, - /// single-click arrow or double-click all box to open. - const OpenOnArrow = 1 << 7; - /// No collapsing, no arrow (use as a convenience for leaf nodes). - const Leaf = 1 << 8; - /// Display a bullet instead of arrow - const Bullet = 1 << 9; - /// Use FramePadding (even for an unframed text node) to vertically align text baseline to - /// regular widget height. - const FramePadding = 1 << 10; - /// Extend hit box to the right-most edge, even if not framed. - /// - /// This is not the default in order to allow adding other items on the same line. In the - /// future we may refactor the hit system to be front-to-back, allowing natural overlaps - /// and then this can become the default. - const SpanAvailWidth = 1 << 11; - /// Extend hit box to the left-most and right-most edges (bypass the indented area) - const SpanFullWidth = 1 << 12; - const NavLeftJumpsBackHere = 1 << 13; +pub type ImGuiTreeNodeFlags = TreeNodeFlags; - const CollapsingHeader = - ImGuiTreeNodeFlags::Framed.bits | ImGuiTreeNodeFlags::NoTreePushOnOpen.bits | - ImGuiTreeNodeFlags::NoAutoOpenOnLog.bits; +impl<'ui> Ui<'ui> { + #[deprecated( + since = "0.4.0", + note = "use imgui::TreeNode::new(...), and build() instead" + )] + pub fn tree_node<'a>(&self, id: &'a ImStr) -> TreeNode<'a> { + TreeNode::new(id) } -); + #[deprecated( + since = "0.4.0", + note = "use imgui::CollapsingHeader::new(...), and build() instead" + )] + pub fn collapsing_header<'a>(&self, label: &'a ImStr) -> CollapsingHeader<'a> { + CollapsingHeader::new(label) + } +} diff --git a/src/lib.rs b/src/lib.rs index c775d4c..bc25d74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,6 @@ pub use self::render::renderer::*; pub use self::stacks::*; pub use self::string::*; pub use self::style::*; -pub use self::trees::{CollapsingHeader, TreeNode}; pub use self::utils::*; pub use self::widget::color_editors::*; pub use self::widget::combo_box::*; @@ -44,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::tree::*; pub use self::window::child_window::*; pub use self::window::*; pub use self::window_draw_list::{ChannelsSplit, ImColor, WindowDrawList}; @@ -69,7 +69,6 @@ mod string; mod style; #[cfg(test)] mod test; -mod trees; mod utils; mod widget; mod window; @@ -109,12 +108,6 @@ pub struct Ui<'ui> { font_atlas: Option>, } -static FMT: &[u8] = b"%s\0"; - -fn fmt_ptr() -> *const c_char { - FMT.as_ptr() as *const c_char -} - impl<'ui> Ui<'ui> { /// Returns an immutable reference to the inputs/outputs object pub fn io(&self) -> &Io { @@ -334,16 +327,6 @@ impl<'ui> Ui<'ui> { } } -// Widgets: Trees -impl<'ui> Ui<'ui> { - pub fn tree_node<'p>(&self, id: &'p ImStr) -> TreeNode<'ui, 'p> { - TreeNode::new(self, id) - } - pub fn collapsing_header<'p>(&self, label: &'p ImStr) -> CollapsingHeader<'ui, 'p> { - CollapsingHeader::new(self, label) - } -} - /// # Tooltips impl<'ui> Ui<'ui> { /// Construct a tooltip window that can have any kind of content. diff --git a/src/trees.rs b/src/trees.rs deleted file mode 100644 index 2499867..0000000 --- a/src/trees.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::marker::PhantomData; -use sys; - -use super::{Condition, ImStr, Ui}; -use crate::legacy::ImGuiTreeNodeFlags; - -#[must_use] -pub struct TreeNode<'ui, 'p> { - id: &'p ImStr, - label: Option<&'p ImStr>, - opened: bool, - opened_cond: Condition, - flags: ImGuiTreeNodeFlags, - _phantom: PhantomData<&'ui Ui<'ui>>, -} - -impl<'ui, 'p> TreeNode<'ui, 'p> { - pub fn new(_: &Ui<'ui>, id: &'p ImStr) -> Self { - TreeNode { - id, - label: None, - opened: false, - opened_cond: Condition::Never, - flags: ImGuiTreeNodeFlags::empty(), - _phantom: PhantomData, - } - } - #[inline] - pub fn label(mut self, label: &'p ImStr) -> Self { - self.label = Some(label); - self - } - #[inline] - pub fn opened(mut self, opened: bool, cond: Condition) -> Self { - self.opened = opened; - self.opened_cond = cond; - self - } - #[inline] - pub fn flags(mut self, flags: ImGuiTreeNodeFlags) -> Self { - self.flags = flags; - self - } - #[inline] - pub fn selected(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::Selected, value); - self - } - #[inline] - pub fn framed(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::Framed, value); - self - } - #[inline] - pub fn allow_item_overlap(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::AllowItemOverlap, value); - self - } - #[inline] - pub fn no_tree_push_on_open(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::NoTreePushOnOpen, value); - self - } - #[inline] - pub fn no_auto_open_on_log(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::NoAutoOpenOnLog, value); - self - } - #[inline] - pub fn default_open(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::DefaultOpen, value); - self - } - #[inline] - pub fn open_on_double_click(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::OpenOnDoubleClick, value); - self - } - #[inline] - pub fn open_on_arrow(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::OpenOnArrow, value); - self - } - #[inline] - pub fn leaf(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::Leaf, value); - self - } - #[inline] - pub fn bullet(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::Bullet, value); - self - } - #[inline] - pub fn frame_padding(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::FramePadding, value); - self - } - pub fn build(self, f: F) { - let render = unsafe { - if self.opened_cond != Condition::Never { - sys::igSetNextItemOpen(self.opened, self.opened_cond as _); - } - sys::igTreeNodeExStrStr( - self.id.as_ptr(), - self.flags.bits(), - super::fmt_ptr(), - self.label.unwrap_or(self.id).as_ptr(), - ) - }; - if render { - f(); - unsafe { sys::igTreePop() }; - } - } -} - -#[must_use] -pub struct CollapsingHeader<'ui, 'p> { - label: &'p ImStr, - // Some flags are automatically set in ImGui::CollapsingHeader, so - // we only support a sensible subset here - flags: ImGuiTreeNodeFlags, - _phantom: PhantomData<&'ui Ui<'ui>>, -} - -impl<'ui, 'p> CollapsingHeader<'ui, 'p> { - pub fn new(_: &Ui<'ui>, label: &'p ImStr) -> Self { - CollapsingHeader { - label, - flags: ImGuiTreeNodeFlags::empty(), - _phantom: PhantomData, - } - } - #[inline] - pub fn flags(mut self, flags: ImGuiTreeNodeFlags) -> Self { - self.flags = flags; - self - } - #[inline] - pub fn selected(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::Selected, value); - self - } - #[inline] - pub fn default_open(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::DefaultOpen, value); - self - } - #[inline] - pub fn open_on_double_click(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::OpenOnDoubleClick, value); - self - } - #[inline] - pub fn open_on_arrow(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::OpenOnArrow, value); - self - } - #[inline] - pub fn leaf(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::Leaf, value); - self - } - #[inline] - pub fn bullet(mut self, value: bool) -> Self { - self.flags.set(ImGuiTreeNodeFlags::Bullet, value); - self - } - pub fn build(self) -> bool { - unsafe { sys::igCollapsingHeader(self.label.as_ptr(), self.flags.bits()) } - } -} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index aeff8ea..5edc6b9 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -7,3 +7,4 @@ pub mod progress_bar; pub mod selectable; pub mod slider; pub mod text; +pub mod tree; diff --git a/src/widget/tree.rs b/src/widget/tree.rs new file mode 100644 index 0000000..1115635 --- /dev/null +++ b/src/widget/tree.rs @@ -0,0 +1,407 @@ +use bitflags::bitflags; +use std::os::raw::{c_char, c_void}; +use std::ptr; +use std::thread; + +use crate::context::Context; +use crate::string::ImStr; +use crate::sys; +use crate::{Condition, Ui}; + +bitflags!( + /// Flags for tree nodes + #[repr(transparent)] + pub struct TreeNodeFlags: u32 { + /// Draw as selected + const SELECTED = sys::ImGuiTreeNodeFlags_Selected; + /// Full colored frame (e.g. for CollapsingHeader) + const FRAMED = sys::ImGuiTreeNodeFlags_Framed; + /// Hit testing to allow subsequent widgets to overlap this one + const ALLOW_ITEM_OVERLAP = sys::ImGuiTreeNodeFlags_AllowItemOverlap; + /// Don't push a tree node when open (e.g. for CollapsingHeader) = no extra indent nor + /// pushing on ID stack + const NO_TREE_PUSH_ON_OPEN = sys::ImGuiTreeNodeFlags_NoTreePushOnOpen; + /// Don't automatically and temporarily open node when Logging is active (by default + /// logging will automatically open tree nodes) + const NO_AUTO_OPEN_ON_LOG = sys::ImGuiTreeNodeFlags_NoAutoOpenOnLog; + /// Default node to be open + const DEFAULT_OPEN = sys::ImGuiTreeNodeFlags_DefaultOpen; + /// Need double-click to open node + const OPEN_ON_DOUBLE_CLICK = sys::ImGuiTreeNodeFlags_OpenOnDoubleClick; + /// Only open when clicking on the arrow part. + /// + /// If `TreeNodeFlags::OPEN_ON_DOUBLE_CLICK` is also set, single-click arrow or + /// double-click all box to open. + const OPEN_ON_ARROW = sys::ImGuiTreeNodeFlags_OpenOnArrow; + /// No collapsing, no arrow (use as a convenience for leaf nodes) + const LEAF = sys::ImGuiTreeNodeFlags_Leaf; + /// Display a bullet instead of arrow + const BULLET = sys::ImGuiTreeNodeFlags_Bullet; + /// Use `Style::frame_padding` (even for an unframed text node) to vertically align text + /// baseline to regular widget height. + /// + /// Equivalent to calling `Ui::align_text_to_frame_padding`. + const FRAME_PADDING = sys::ImGuiTreeNodeFlags_FramePadding; + /// Extend hit box to the right-most edge, even if not framed. + /// + /// This is not the default in order to allow adding other items on the same line. In the + /// future we may refactor the hit system to be front-to-back, allowing natural overlaps + /// and then this can become the default. + const SPAN_AVAIL_WIDTH = sys::ImGuiTreeNodeFlags_SpanAvailWidth; + /// Extend hit box to the left-most and right-most edges (bypass the indented area) + const SPAN_FULL_WIDTH = sys::ImGuiTreeNodeFlags_SpanFullWidth; + /// (WIP) Nav: left direction may move to this tree node from any of its child + const NAV_LEFT_JUMPS_BACK_HERE = sys::ImGuiTreeNodeFlags_NavLeftJumpsBackHere; + } +); + +static FMT: &'static [u8] = b"%s\0"; + +#[inline] +fn fmt_ptr() -> *const c_char { + FMT.as_ptr() as *const c_char +} + +/// Unique ID used by tree nodes +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum TreeNodeId<'a> { + Str(&'a ImStr), + Ptr(*const c_void), +} + +impl<'a, T: ?Sized + AsRef> From<&'a T> for TreeNodeId<'a> { + fn from(s: &'a T) -> Self { + TreeNodeId::Str(s.as_ref()) + } +} + +impl From<*const T> for TreeNodeId<'static> { + fn from(p: *const T) -> Self { + TreeNodeId::Ptr(p as *const c_void) + } +} + +impl From<*mut T> for TreeNodeId<'static> { + fn from(p: *mut T) -> Self { + TreeNodeId::Ptr(p as *const T as *const c_void) + } +} + +/// Builder for a tree node widget +#[derive(Copy, Clone, Debug)] +#[must_use] +pub struct TreeNode<'a> { + id: TreeNodeId<'a>, + label: Option<&'a ImStr>, + opened: bool, + opened_cond: Condition, + flags: TreeNodeFlags, +} + +impl<'a> TreeNode<'a> { + /// Constructs a new tree node builder + pub fn new>>(id: I) -> TreeNode<'a> { + TreeNode { + id: id.into(), + label: None, + opened: false, + opened_cond: Condition::Never, + flags: TreeNodeFlags::empty(), + } + } + /// Sets the tree node label + #[inline] + pub fn label(mut self, label: &'a ImStr) -> Self { + self.label = Some(label); + self + } + /// Sets the opened state of the tree node, which is applied based on the given condition value + #[inline] + pub fn opened(mut self, opened: bool, cond: Condition) -> Self { + self.opened = opened; + self.opened_cond = cond; + self + } + /// Replaces all current settings with the given flags. + #[inline] + pub fn flags(mut self, flags: TreeNodeFlags) -> Self { + self.flags = flags; + self + } + /// Enables/disables drawing the tree node in selected state. + /// + /// Disabled by default. + #[inline] + pub fn selected(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::SELECTED, value); + self + } + /// Enables/disables full-colored frame. + /// + /// Disabled by default. + #[inline] + pub fn framed(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::FRAMED, value); + self + } + /// Enables/disables allowing the tree node to overlap subsequent widgets. + /// + /// Disabled by default. + #[inline] + pub fn allow_item_overlap(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::ALLOW_ITEM_OVERLAP, value); + self + } + /// Enables/disables automatic tree push when the tree node is open (= adds extra indentation + /// and pushes to the ID stack). + /// + /// Enabled by default. + #[inline] + pub fn tree_push_on_open(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::NO_TREE_PUSH_ON_OPEN, !value); + self + } + /// Enables/disables automatic opening of the tree node when logging is active. + /// + /// By default, logging will automatically open all tree nodes. + /// + /// Enabled by default. + #[inline] + pub fn auto_open_on_log(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::NO_AUTO_OPEN_ON_LOG, !value); + self + } + /// Sets the default open state for the tree node. + /// + /// Tree nodes are closed by default. + #[inline] + pub fn default_open(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::DEFAULT_OPEN, value); + self + } + /// Only open when the tree node is double-clicked. + /// + /// Disabled by default. + #[inline] + pub fn open_on_double_click(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::OPEN_ON_DOUBLE_CLICK, value); + self + } + /// Only open when clicking the arrow part of the tree node. + /// + /// Disabled by default. + #[inline] + pub fn open_on_arrow(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::OPEN_ON_ARROW, value); + self + } + /// Enable/disables leaf mode (no collapsing, no arrow). + /// + /// Disabled by default. + #[inline] + pub fn leaf(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::LEAF, value); + self + } + /// Display a bullet instead of arrow. + /// + /// Disabled by default. + #[inline] + pub fn bullet(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::BULLET, value); + self + } + /// Use `frame_padding` to vertically align text baseline to regular widget height. + /// + /// Disabled by default. + #[inline] + pub fn frame_padding(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::FRAME_PADDING, value); + self + } + /// Left direction may move to this tree node from any of its child. + /// + /// Disabled by default. + #[inline] + pub fn nav_left_jumps_back_here(mut self, value: bool) -> Self { + self.flags + .set(TreeNodeFlags::NAV_LEFT_JUMPS_BACK_HERE, value); + self + } + /// Pushes a tree node and starts appending to it. + /// + /// Returns `Some(TreeNodeToken)` if the tree node is open. After content has been + /// rendered, the token must be popped by calling `.pop()`. + /// + /// Returns `None` if the tree node is not open and no content should be rendered. + #[must_use] + pub fn push(self, ui: &Ui) -> Option { + let open = unsafe { + if self.opened_cond != Condition::Never { + sys::igSetNextItemOpen(self.opened, self.opened_cond as i32); + } + match self.id { + TreeNodeId::Str(id) => sys::igTreeNodeExStrStr( + id.as_ptr(), + self.flags.bits() as i32, + fmt_ptr(), + self.label.unwrap_or(id).as_ptr(), + ), + TreeNodeId::Ptr(id) => sys::igTreeNodeExPtr( + id, + self.flags.bits() as i32, + fmt_ptr(), + self.label.unwrap_or_default().as_ptr(), + ), + } + }; + if open { + Some(TreeNodeToken { + ctx: if self.flags.contains(TreeNodeFlags::NO_TREE_PUSH_ON_OPEN) { + ptr::null() + } else { + ui.ctx + }, + }) + } else { + None + } + } + /// Creates a tree node and runs a closure to construct the contents. + /// + /// Note: the closure is not called if the tree node is not open. + pub fn build(self, ui: &Ui, f: F) { + if let Some(node) = self.push(ui) { + f(); + node.pop(ui); + } + } +} + +/// Tracks a tree node that must be popped by calling `.pop()`. +/// +/// If `TreeNodeFlags::NO_TREE_PUSH_ON_OPEN` was used when this token was created, calling `.pop()` +/// is not mandatory and is a no-op. +#[must_use] +pub struct TreeNodeToken { + ctx: *const Context, +} + +impl TreeNodeToken { + /// Pops a tree node + pub fn pop(mut self, _: &Ui) { + if !self.ctx.is_null() { + self.ctx = ptr::null(); + unsafe { sys::igTreePop() }; + } + } +} + +impl Drop for TreeNodeToken { + fn drop(&mut self) { + if !self.ctx.is_null() && !thread::panicking() { + panic!("A TreeNodeToken was leaked. Did you call .pop()?"); + } + } +} + +/// Builder for a collapsing header widget +#[derive(Copy, Clone, Debug)] +#[must_use] +pub struct CollapsingHeader<'a> { + label: &'a ImStr, + flags: TreeNodeFlags, +} + +impl<'a> CollapsingHeader<'a> { + /// Constructs a new collapsing header builder + pub fn new(label: &ImStr) -> CollapsingHeader { + CollapsingHeader { + label, + flags: TreeNodeFlags::empty(), + } + } + /// Replaces all current settings with the given flags. + #[inline] + pub fn flags(mut self, flags: TreeNodeFlags) -> Self { + self.flags = flags; + self + } + /// Enables/disables allowing the collapsing header to overlap subsequent widgets. + /// + /// Disabled by default. + #[inline] + pub fn allow_item_overlap(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::ALLOW_ITEM_OVERLAP, value); + self + } + /// Sets the default open state for the collapsing header. + /// + /// Collapsing headers are closed by default. + #[inline] + pub fn default_open(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::DEFAULT_OPEN, value); + self + } + /// Only open when the collapsing header is double-clicked. + /// + /// Disabled by default. + #[inline] + pub fn open_on_double_click(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::OPEN_ON_DOUBLE_CLICK, value); + self + } + /// Only open when clicking the arrow part of the collapsing header. + /// + /// Disabled by default. + #[inline] + pub fn open_on_arrow(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::OPEN_ON_ARROW, value); + self + } + /// Enable/disables leaf mode (no collapsing, no arrow). + /// + /// Disabled by default. + #[inline] + pub fn leaf(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::LEAF, value); + self + } + /// Display a bullet instead of arrow. + /// + /// Disabled by default. + #[inline] + pub fn bullet(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::BULLET, value); + self + } + /// Use `frame_padding` to vertically align text baseline to regular widget height. + /// + /// Disabled by default. + #[inline] + pub fn frame_padding(mut self, value: bool) -> Self { + self.flags.set(TreeNodeFlags::FRAME_PADDING, value); + self + } + /// Builds the collapsing header. + /// + /// Returns true if the collapsing header is open and content should be rendered. + #[must_use] + pub fn build(self, _: &Ui) -> bool { + unsafe { sys::igCollapsingHeader(self.label.as_ptr(), self.flags.bits() as i32) } + } + /// Builds the collapsing header, and adds an additional close button that changes the value of + /// the given mutable reference when clicked. + /// + /// Returns true if the collapsing header is open and content should be rendered. + #[must_use] + pub fn build_with_close_button(self, _: &Ui, opened: &mut bool) -> bool { + unsafe { + sys::igCollapsingHeaderBoolPtr( + self.label.as_ptr(), + opened as *mut bool, + self.flags.bits() as i32, + ) + } + } +}