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        ctx.font_atlas()
105            .try_claim_legacy_renderer()
106            .expect("legacy renderer font atlas should be available")
107            .build();
108        let ui = ctx.frame();
109
110        ui.window("tree_push").build(|| {
111            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
112            let initial_depth = unsafe { (*window).DC.TreeDepth };
113            let initial_id_stack_size = unsafe { (*window).IDStack.Size };
114
115            {
116                let _tree = ui.tree_push("string_scope");
117                assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth + 1);
118                assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size + 1);
119            }
120
121            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
122            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
123
124            let marker = 0_u8;
125            let tree = ui.tree_push_ptr(&marker);
126            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth + 1);
127            tree.pop();
128            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
129            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
130        });
131    }
132
133    #[test]
134    fn tree_node_tokens_match_push_flags_and_keep_custom_ids_stable() {
135        let mut ctx = crate::Context::create();
136        ctx.io_mut().set_display_size([128.0, 128.0]);
137        ctx.io_mut().set_delta_time(1.0 / 60.0);
138        ctx.font_atlas()
139            .try_claim_legacy_renderer()
140            .expect("legacy renderer font atlas should be available")
141            .build();
142        let ui = ctx.frame();
143
144        ui.window("tree_node_tokens").build(|| {
145            let window = unsafe { crate::sys::igGetCurrentWindowRead() };
146            let initial_depth = unsafe { (*window).DC.TreeDepth };
147            let initial_id_stack_size = unsafe { (*window).IDStack.Size };
148
149            let no_push = ui
150                .tree_node_config("no_push")
151                .opened(true, crate::Condition::Always)
152                .no_tree_push_on_open(true)
153                .push()
154                .expect("forced-open tree node");
155            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
156            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
157            drop(no_push);
158            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
159            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
160
161            let first = ui
162                .tree_node_config("stable_id")
163                .label("First label")
164                .opened(true, crate::Condition::Always)
165                .push()
166                .expect("forced-open tree node");
167            let first_id = ui.item_id();
168            drop(first);
169
170            let second = ui
171                .tree_node_config("stable_id")
172                .label("Second label")
173                .opened(true, crate::Condition::Always)
174                .push()
175                .expect("forced-open tree node");
176            let second_id = ui.item_id();
177            drop(second);
178            assert_eq!(first_id, second_id);
179
180            let first_int = ui
181                .tree_node_config(7_i32)
182                .label("First integer label")
183                .opened(true, crate::Condition::Always)
184                .nav_left_jumps_back_here(true)
185                .push()
186                .expect("forced-open tree node");
187            let first_int_id = ui.item_id();
188            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth + 1);
189            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size + 1);
190            drop(first_int);
191
192            let second_int = ui
193                .tree_node_config(7_i32)
194                .label("Second integer label")
195                .opened(true, crate::Condition::Always)
196                .push()
197                .expect("forced-open tree node");
198            let second_int_id = ui.item_id();
199            drop(second_int);
200            assert_eq!(first_int_id, second_int_id);
201            assert_eq!(unsafe { (*window).DC.TreeDepth }, initial_depth);
202            assert_eq!(unsafe { (*window).IDStack.Size }, initial_id_stack_size);
203        });
204    }
205}