Redesign tree node / collapsing header API

This commit is contained in:
Joonas Javanainen 2020-03-16 12:02:35 +02:00
parent 2915741ac9
commit a5a0be44e3
No known key found for this signature in database
GPG Key ID: D39CCA5CB19B9179
8 changed files with 521 additions and 254 deletions

View File

@ -2,6 +2,10 @@
## [Unreleased] ## [Unreleased]
### Changed
- Redesigned tree / collapsing header API
## [0.3.0] - 2020-02-15 ## [0.3.0] - 2020-02-15
### Added ### Added

View File

@ -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,
}

View File

@ -327,7 +327,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
menu_bar.end(ui); menu_bar.end(ui);
} }
ui.spacing(); ui.spacing();
if ui.collapsing_header(im_str!("Help")).build() { if CollapsingHeader::new(im_str!("Help")).build(&ui) {
ui.text_wrapped(im_str!( ui.text_wrapped(im_str!(
"This window is being created by the show_test_window() \ "This window is being created by the show_test_window() \
function. Please refer to the code for programming \ 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); 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.checkbox(im_str!("No titlebar"), &mut state.no_titlebar);
ui.same_line(150.0); ui.same_line(150.0);
ui.checkbox(im_str!("No scrollbar"), &mut state.no_scrollbar); 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 collapse"), &mut state.no_collapse);
ui.checkbox(im_str!("No close"), &mut state.no_close); ui.checkbox(im_str!("No close"), &mut state.no_close);
ui.tree_node(im_str!("Style")) TreeNode::new(im_str!("Style")).build(&ui, || {
.build(|| ui.show_default_style_editor()); ui.show_default_style_editor();
});
} }
if ui.collapsing_header(im_str!("Widgets")).build() { if CollapsingHeader::new(im_str!("Widgets")).build(&ui) {
ui.tree_node(im_str!("Tree")).build(|| { TreeNode::new(im_str!("Tree")).build(&ui, || {
for i in 0..5 { 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.text(im_str!("blah blah"));
ui.same_line(0.0); ui.same_line(0.0);
if ui.small_button(im_str!("print")) { 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 1"));
ui.bullet_text(im_str!("Bullet point 2\nOn multiple lines")); ui.bullet_text(im_str!("Bullet point 2\nOn multiple lines"));
ui.bullet(); ui.bullet();
@ -373,13 +374,13 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
ui.bullet(); ui.bullet();
ui.small_button(im_str!("Button")); 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, 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_colored([1.0, 1.0, 0.0, 1.0], im_str!("Yellow"));
ui.text_disabled(im_str!("Disabled")); 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( ui.input_text_multiline(
im_str!("multiline"), im_str!("multiline"),
&mut state.text_multiline, &mut state.text_multiline,
@ -387,7 +388,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
).build(); ).build();
}); });
ui.tree_node(im_str!("Word Wrapping")).build(|| { TreeNode::new(im_str!("Word wrapping")).build(&ui, || {
ui.text_wrapped(im_str!( ui.text_wrapped(im_str!(
"This text should automatically wrap on the edge of \ "This text should automatically wrap on the edge of \
the window.The current implementation for text \ 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:")); ui.text(im_str!("Test paragraph 2:"));
// TODO // TODO
}); });
ui.tree_node(im_str!("UTF-8 Text")).build(|| { TreeNode::new(im_str!("UTF-8 Text")).build(&ui, || {
ui.text_wrapped(im_str!( ui.text_wrapped(im_str!(
"CJK text will only appear if the font was loaded \ "CJK text will only appear if the font was loaded \
with theappropriate CJK character ranges. Call \ 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 1"), &mut state.col1).build(ui);
ColorEdit::new(im_str!("color 2"), &mut state.col2).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) ui.input_float2(im_str!("input float2"), &mut state.vec2f)
.build(); .build();
ui.input_int2(im_str!("input int2"), &mut state.vec2i) 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.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; let s = &mut state.color_edit;
ui.checkbox(im_str!("With HDR"), &mut s.hdr); ui.checkbox(im_str!("With HDR"), &mut s.hdr);
ui.same_line(0.0); ui.same_line(0.0);
@ -584,11 +585,8 @@ CTRL+click on individual component to input value.\n",
b.build(ui); b.build(ui);
}); });
} }
if ui if CollapsingHeader::new(im_str!("Popups & Modal windows")).build(&ui) {
.collapsing_header(im_str!("Popups & Modal windows")) TreeNode::new(im_str!("Popups")).build(&ui, || {
.build()
{
ui.tree_node(im_str!("Popups")).build(|| {
ui.text_wrapped(im_str!( ui.text_wrapped(im_str!(
"When a popup is active, it inhibits interacting \ "When a popup is active, it inhibits interacting \
with windows that are behind the popup. Clicking \ 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!( ui.text_wrapped(im_str!(
"Modal windows are like popups but the user cannot close \ "Modal windows are like popups but the user cannot close \
them by clicking outside the window." them by clicking outside the window."

View File

@ -2,6 +2,10 @@
use bitflags::bitflags; use bitflags::bitflags;
use std::os::raw::c_int; use std::os::raw::c_int;
use crate::string::ImStr;
use crate::widget::tree::{CollapsingHeader, TreeNode, TreeNodeFlags};
use crate::Ui;
bitflags!( bitflags!(
/// Flags for igBeginDragDropSource(), igAcceptDragDropPayload() /// Flags for igBeginDragDropSource(), igAcceptDragDropPayload()
#[repr(C)] #[repr(C)]
@ -116,48 +120,21 @@ bitflags!(
} }
); );
bitflags!( pub type ImGuiTreeNodeFlags = TreeNodeFlags;
/// 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;
const CollapsingHeader = impl<'ui> Ui<'ui> {
ImGuiTreeNodeFlags::Framed.bits | ImGuiTreeNodeFlags::NoTreePushOnOpen.bits | #[deprecated(
ImGuiTreeNodeFlags::NoAutoOpenOnLog.bits; 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)
}
}

View File

@ -35,7 +35,6 @@ pub use self::render::renderer::*;
pub use self::stacks::*; pub use self::stacks::*;
pub use self::string::*; pub use self::string::*;
pub use self::style::*; pub use self::style::*;
pub use self::trees::{CollapsingHeader, TreeNode};
pub use self::utils::*; pub use self::utils::*;
pub use self::widget::color_editors::*; pub use self::widget::color_editors::*;
pub use self::widget::combo_box::*; 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::progress_bar::*;
pub use self::widget::selectable::*; pub use self::widget::selectable::*;
pub use self::widget::slider::*; pub use self::widget::slider::*;
pub use self::widget::tree::*;
pub use self::window::child_window::*; pub use self::window::child_window::*;
pub use self::window::*; pub use self::window::*;
pub use self::window_draw_list::{ChannelsSplit, ImColor, WindowDrawList}; pub use self::window_draw_list::{ChannelsSplit, ImColor, WindowDrawList};
@ -69,7 +69,6 @@ mod string;
mod style; mod style;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
mod trees;
mod utils; mod utils;
mod widget; mod widget;
mod window; mod window;
@ -109,12 +108,6 @@ pub struct Ui<'ui> {
font_atlas: Option<cell::RefMut<'ui, SharedFontAtlas>>, font_atlas: Option<cell::RefMut<'ui, SharedFontAtlas>>,
} }
static FMT: &[u8] = b"%s\0";
fn fmt_ptr() -> *const c_char {
FMT.as_ptr() as *const c_char
}
impl<'ui> Ui<'ui> { impl<'ui> Ui<'ui> {
/// Returns an immutable reference to the inputs/outputs object /// Returns an immutable reference to the inputs/outputs object
pub fn io(&self) -> &Io { 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 /// # Tooltips
impl<'ui> Ui<'ui> { impl<'ui> Ui<'ui> {
/// Construct a tooltip window that can have any kind of content. /// Construct a tooltip window that can have any kind of content.

View File

@ -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<F: FnOnce()>(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()) }
}
}

View File

@ -7,3 +7,4 @@ pub mod progress_bar;
pub mod selectable; pub mod selectable;
pub mod slider; pub mod slider;
pub mod text; pub mod text;
pub mod tree;

407
src/widget/tree.rs Normal file
View File

@ -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<ImStr>> From<&'a T> for TreeNodeId<'a> {
fn from(s: &'a T) -> Self {
TreeNodeId::Str(s.as_ref())
}
}
impl<T> From<*const T> for TreeNodeId<'static> {
fn from(p: *const T) -> Self {
TreeNodeId::Ptr(p as *const c_void)
}
}
impl<T> 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<I: Into<TreeNodeId<'a>>>(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<TreeNodeToken> {
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<F: FnOnce()>(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,
)
}
}
}