dear_imgui_rs/widget/tree/
entry.rs1use crate::Id;
2use crate::sys;
3use crate::ui::Ui;
4
5use super::{TreeNode, TreeNodeFlags, TreeNodeId, TreeNodeToken};
6
7impl Ui {
9 #[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 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 #[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 #[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 #[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 #[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 #[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 #[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}