Skip to main content

dear_imgui_rs/widget/tree/
entry.rs

1use crate::Id;
2use crate::sys;
3use crate::ui::Ui;
4
5use super::{TreeNode, TreeNodeFlags, TreeNodeId, TreeNodeToken};
6
7/// # Tree Node Widgets
8impl Ui {
9    /// Constructs a new tree node with just a name, and pushes it.
10    ///
11    /// Use [tree_node_config] to access a builder to put additional
12    /// configurations on the tree node.
13    ///
14    /// [tree_node_config]: Self::tree_node_config
15    #[doc(alias = "TreeNode", alias = "TreeNodeEx")]
16    pub fn tree_node<I, T>(&self, id: I) -> Option<TreeNodeToken<'_>>
17    where
18        I: Into<TreeNodeId<T>>,
19        T: AsRef<str>,
20    {
21        self.tree_node_config(id).push()
22    }
23
24    /// Constructs a new tree node builder.
25    ///
26    /// Use [tree_node] to build a simple node with just a name.
27    ///
28    /// [tree_node]: Self::tree_node
29    pub fn tree_node_config<I, T>(&self, id: I) -> TreeNode<'_, T>
30    where
31        I: Into<TreeNodeId<T>>,
32        T: AsRef<str>,
33    {
34        TreeNode::new(id.into(), self)
35    }
36
37    /// Starts a tree indentation and ID scope without rendering a tree node.
38    ///
39    /// The returned token restores the tree depth, indentation, and ID stack
40    /// when dropped.
41    #[doc(alias = "TreePush")]
42    pub fn tree_push(&self, id: impl AsRef<str>) -> TreeNodeToken<'_> {
43        let id = self.scratch_txt(id);
44        self.run_with_bound_context(|| unsafe { sys::igTreePush_Str(id) });
45        TreeNodeToken::new(self)
46    }
47
48    /// Starts a tree indentation and ID scope using a pointer value as the ID.
49    ///
50    /// The pointer is used only as an identifier and is not dereferenced.
51    #[doc(alias = "TreePush")]
52    pub fn tree_push_ptr<T>(&self, id: *const T) -> TreeNodeToken<'_> {
53        self.run_with_bound_context(|| unsafe { sys::igTreePush_Ptr(id.cast()) });
54        TreeNodeToken::new(self)
55    }
56
57    /// Creates a collapsing header widget
58    #[doc(alias = "CollapsingHeader")]
59    pub fn collapsing_header(&self, label: impl AsRef<str>, flags: TreeNodeFlags) -> bool {
60        let label_ptr = self.scratch_txt(label);
61        self.run_with_bound_context(|| unsafe {
62            sys::igCollapsingHeader_TreeNodeFlags(label_ptr, flags.bits())
63        })
64    }
65
66    /// Creates a collapsing header widget with a visibility tracking variable.
67    ///
68    /// Passing `visible` enables a close button on the header. When clicked, ImGui will set
69    /// `*visible = false`. As with other immediate-mode widgets, you should stop submitting the
70    /// header when `*visible == false`.
71    #[doc(alias = "CollapsingHeader")]
72    pub fn collapsing_header_with_visible(
73        &self,
74        label: impl AsRef<str>,
75        visible: &mut bool,
76        flags: TreeNodeFlags,
77    ) -> bool {
78        let label_ptr = self.scratch_txt(label);
79        self.run_with_bound_context(|| unsafe {
80            sys::igCollapsingHeader_BoolPtr(label_ptr, visible as *mut bool, flags.bits())
81        })
82    }
83
84    /// Returns the distance from the start of a tree node to the label text.
85    #[doc(alias = "GetTreeNodeToLabelSpacing")]
86    pub fn tree_node_to_label_spacing(&self) -> f32 {
87        self.run_with_bound_context(|| unsafe { sys::igGetTreeNodeToLabelSpacing() })
88    }
89
90    /// Returns whether the tree node identified by `storage_id` is open in storage.
91    #[doc(alias = "TreeNodeGetOpen")]
92    pub fn tree_node_get_open(&self, storage_id: Id) -> bool {
93        self.run_with_bound_context(|| unsafe { sys::igTreeNodeGetOpen(storage_id.raw()) })
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    #[test]
100    fn manual_tree_scopes_restore_tree_depth_and_id_stack() {
101        let mut ctx = crate::Context::create();
102        ctx.io_mut().set_display_size([128.0, 128.0]);
103        ctx.io_mut().set_delta_time(1.0 / 60.0);
104        let _ = ctx.font_atlas().build();
105        let ui = ctx.frame();
106
107        ui.window("tree_push").build(|| {
108            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
109            let initial_depth = unsafe { (*window).DC.TreeDepth };
110            let initial_id_stack_size = unsafe { (*window).IDStack.Size };
111
112            {
113                let _tree = ui.tree_push("string_scope");
114                assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth + 1);
115                assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size + 1);
116            }
117
118            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
119            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
120
121            let marker = 0_u8;
122            let tree = ui.tree_push_ptr(&marker);
123            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth + 1);
124            tree.pop();
125            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
126            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
127        });
128    }
129
130    #[test]
131    fn tree_node_tokens_match_push_flags_and_keep_custom_ids_stable() {
132        let mut ctx = crate::Context::create();
133        ctx.io_mut().set_display_size([128.0, 128.0]);
134        ctx.io_mut().set_delta_time(1.0 / 60.0);
135        let _ = ctx.font_atlas().build();
136        let ui = ctx.frame();
137
138        ui.window("tree_node_tokens").build(|| {
139            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
140            let initial_depth = unsafe { (*window).DC.TreeDepth };
141            let initial_id_stack_size = unsafe { (*window).IDStack.Size };
142
143            let no_push = ui
144                .tree_node_config("no_push")
145                .opened(true, crate::Condition::Always)
146                .no_tree_push_on_open(true)
147                .push()
148                .expect("forced-open tree node");
149            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
150            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
151            drop(no_push);
152            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
153            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
154
155            let first = ui
156                .tree_node_config("stable_id")
157                .label("First label")
158                .opened(true, crate::Condition::Always)
159                .push()
160                .expect("forced-open tree node");
161            let first_id = ui.item_id();
162            drop(first);
163
164            let second = ui
165                .tree_node_config("stable_id")
166                .label("Second label")
167                .opened(true, crate::Condition::Always)
168                .push()
169                .expect("forced-open tree node");
170            let second_id = ui.item_id();
171            drop(second);
172            assert_eq!(first_id, second_id);
173
174            let first_int = ui
175                .tree_node_config(7_i32)
176                .label("First integer label")
177                .opened(true, crate::Condition::Always)
178                .nav_left_jumps_back_here(true)
179                .push()
180                .expect("forced-open tree node");
181            let first_int_id = ui.item_id();
182            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth + 1);
183            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size + 1);
184            drop(first_int);
185
186            let second_int = ui
187                .tree_node_config(7_i32)
188                .label("Second integer label")
189                .opened(true, crate::Condition::Always)
190                .push()
191                .expect("forced-open tree node");
192            let second_int_id = ui.item_id();
193            drop(second_int);
194            assert_eq!(first_int_id, second_int_id);
195            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
196            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
197        });
198    }
199}