Skip to main content

dear_imgui_rs/widget/
tree.rs

1//! Trees and collapsing headers
2//!
3//! Tree nodes and collapsing headers for hierarchical content. See
4//! `TreeNodeFlags` for customization options.
5//!
6#![allow(
7    clippy::cast_possible_truncation,
8    clippy::cast_sign_loss,
9    clippy::as_conversions
10)]
11use crate::Condition;
12use crate::sys;
13use crate::ui::Ui;
14use crate::widget::TreeNodeFlags;
15
16/// Tree node ID that can be constructed from different types
17#[derive(Copy, Clone, Debug)]
18pub enum TreeNodeId<T> {
19    Str(T),
20    Ptr(*const u8),
21    Int(i32),
22}
23
24impl<T> From<T> for TreeNodeId<T>
25where
26    T: AsRef<str>,
27{
28    fn from(s: T) -> Self {
29        TreeNodeId::Str(s)
30    }
31}
32
33impl From<*const u8> for TreeNodeId<&'static str> {
34    fn from(ptr: *const u8) -> Self {
35        TreeNodeId::Ptr(ptr)
36    }
37}
38
39impl From<i32> for TreeNodeId<&'static str> {
40    fn from(i: i32) -> Self {
41        TreeNodeId::Int(i)
42    }
43}
44
45/// # Tree Node Widgets
46impl Ui {
47    /// Constructs a new tree node with just a name, and pushes it.
48    ///
49    /// Use [tree_node_config] to access a builder to put additional
50    /// configurations on the tree node.
51    ///
52    /// [tree_node_config]: Self::tree_node_config
53    pub fn tree_node<I, T>(&self, id: I) -> Option<TreeNodeToken<'_>>
54    where
55        I: Into<TreeNodeId<T>>,
56        T: AsRef<str>,
57    {
58        self.tree_node_config(id).push()
59    }
60
61    /// Constructs a new tree node builder.
62    ///
63    /// Use [tree_node] to build a simple node with just a name.
64    ///
65    /// [tree_node]: Self::tree_node
66    pub fn tree_node_config<I, T>(&self, id: I) -> TreeNode<'_, T>
67    where
68        I: Into<TreeNodeId<T>>,
69        T: AsRef<str>,
70    {
71        TreeNode {
72            id: id.into(),
73            label: None,
74            opened: false,
75            opened_cond: Condition::Never,
76            flags: TreeNodeFlags::NONE,
77            ui: self,
78        }
79    }
80
81    /// Creates a collapsing header widget
82    #[doc(alias = "CollapsingHeader")]
83    pub fn collapsing_header(&self, label: impl AsRef<str>, flags: TreeNodeFlags) -> bool {
84        let label_ptr = self.scratch_txt(label);
85        unsafe { sys::igCollapsingHeader_TreeNodeFlags(label_ptr, flags.bits()) }
86    }
87
88    /// Creates a collapsing header widget with a visibility tracking variable.
89    ///
90    /// Passing `visible` enables a close button on the header. When clicked, ImGui will set
91    /// `*visible = false`. As with other immediate-mode widgets, you should stop submitting the
92    /// header when `*visible == false`.
93    #[doc(alias = "CollapsingHeader")]
94    pub fn collapsing_header_with_visible(
95        &self,
96        label: impl AsRef<str>,
97        visible: &mut bool,
98        flags: TreeNodeFlags,
99    ) -> bool {
100        let label_ptr = self.scratch_txt(label);
101        unsafe { sys::igCollapsingHeader_BoolPtr(label_ptr, visible as *mut bool, flags.bits()) }
102    }
103
104    /// Returns the distance from the start of a tree node to the label text.
105    #[doc(alias = "GetTreeNodeToLabelSpacing")]
106    pub fn tree_node_to_label_spacing(&self) -> f32 {
107        unsafe { sys::igGetTreeNodeToLabelSpacing() }
108    }
109
110    /// Returns whether the tree node identified by `storage_id` is open in storage.
111    #[doc(alias = "TreeNodeGetOpen")]
112    pub fn tree_node_get_open(&self, storage_id: crate::Id) -> bool {
113        unsafe { sys::igTreeNodeGetOpen(storage_id.raw()) }
114    }
115}
116
117/// Builder for a tree node widget
118#[derive(Clone, Debug)]
119#[must_use]
120pub struct TreeNode<'a, T, L = &'static str> {
121    id: TreeNodeId<T>,
122    label: Option<L>,
123    opened: bool,
124    opened_cond: Condition,
125    flags: TreeNodeFlags,
126    ui: &'a Ui,
127}
128
129impl<'a, T: AsRef<str>> TreeNode<'a, T, &'static str> {
130    /// Sets a custom label for the tree node
131    pub fn label<L: AsRef<str>>(self, label: L) -> TreeNode<'a, T, L> {
132        TreeNode {
133            id: self.id,
134            label: Some(label),
135            opened: self.opened,
136            opened_cond: self.opened_cond,
137            flags: self.flags,
138            ui: self.ui,
139        }
140    }
141}
142
143impl<'a, T: AsRef<str>, L: AsRef<str>> TreeNode<'a, T, L> {
144    /// Sets the opened state
145    pub fn opened(mut self, opened: bool, cond: Condition) -> Self {
146        self.opened = opened;
147        self.opened_cond = cond;
148        self
149    }
150
151    /// Draw as selected
152    pub fn selected(mut self, selected: bool) -> Self {
153        self.flags.set(TreeNodeFlags::SELECTED, selected);
154        self
155    }
156
157    /// Draw frame with background (e.g. for CollapsingHeader)
158    pub fn framed(mut self, framed: bool) -> Self {
159        self.flags.set(TreeNodeFlags::FRAMED, framed);
160        self
161    }
162
163    /// Hit testing to allow subsequent widgets to overlap this one
164    pub fn allow_item_overlap(mut self, allow: bool) -> Self {
165        self.flags.set(TreeNodeFlags::ALLOW_ITEM_OVERLAP, allow);
166        self
167    }
168
169    /// Don't do a TreePush() when open (e.g. for CollapsingHeader)
170    pub fn no_tree_push_on_open(mut self, no_push: bool) -> Self {
171        self.flags.set(TreeNodeFlags::NO_TREE_PUSH_ON_OPEN, no_push);
172        self
173    }
174
175    /// Don't automatically and temporarily open node when Logging is active
176    pub fn no_auto_open_on_log(mut self, no_auto: bool) -> Self {
177        self.flags.set(TreeNodeFlags::NO_AUTO_OPEN_ON_LOG, no_auto);
178        self
179    }
180
181    /// Default node to be open
182    pub fn default_open(mut self, default_open: bool) -> Self {
183        self.flags.set(TreeNodeFlags::DEFAULT_OPEN, default_open);
184        self
185    }
186
187    /// Need double-click to open node
188    pub fn open_on_double_click(mut self, double_click: bool) -> Self {
189        self.flags
190            .set(TreeNodeFlags::OPEN_ON_DOUBLE_CLICK, double_click);
191        self
192    }
193
194    /// Only open when clicking on the arrow part
195    pub fn open_on_arrow(mut self, arrow_only: bool) -> Self {
196        self.flags.set(TreeNodeFlags::OPEN_ON_ARROW, arrow_only);
197        self
198    }
199
200    /// No collapsing, no arrow (use as a convenience for leaf nodes)
201    pub fn leaf(mut self, leaf: bool) -> Self {
202        self.flags.set(TreeNodeFlags::LEAF, leaf);
203        self
204    }
205
206    /// Display a bullet instead of arrow
207    pub fn bullet(mut self, bullet: bool) -> Self {
208        self.flags.set(TreeNodeFlags::BULLET, bullet);
209        self
210    }
211
212    /// Use FramePadding to vertically align text baseline to regular widget height
213    pub fn frame_padding(mut self, frame_padding: bool) -> Self {
214        self.flags.set(TreeNodeFlags::FRAME_PADDING, frame_padding);
215        self
216    }
217
218    /// Extend hit box to the right-most edge
219    pub fn span_avail_width(mut self, span: bool) -> Self {
220        self.flags.set(TreeNodeFlags::SPAN_AVAIL_WIDTH, span);
221        self
222    }
223
224    /// Extend hit box to the left-most and right-most edges
225    pub fn span_full_width(mut self, span: bool) -> Self {
226        self.flags.set(TreeNodeFlags::SPAN_FULL_WIDTH, span);
227        self
228    }
229
230    /// Left direction may move to this tree node from any of its child
231    pub fn nav_left_jumps_back_here(mut self, nav: bool) -> Self {
232        self.flags.set(TreeNodeFlags::NAV_LEFT_JUMPS_BACK_HERE, nav);
233        self
234    }
235
236    /// Pushes a tree node and starts appending to it.
237    ///
238    /// Returns `Some(TreeNodeToken)` if the tree node is open. After content has been
239    /// rendered, the token can be popped by calling `.pop()`.
240    ///
241    /// Returns `None` if the tree node is not open and no content should be rendered.
242    pub fn push(self) -> Option<TreeNodeToken<'a>> {
243        let open = unsafe {
244            if self.opened_cond != Condition::Never {
245                sys::igSetNextItemOpen(self.opened, self.opened_cond as i32);
246            }
247
248            match &self.id {
249                TreeNodeId::Str(s) => {
250                    if let Some(label) = self.label.as_ref() {
251                        let (id_ptr, label_ptr) = self.ui.scratch_txt_two(s, label);
252                        sys::igPushID_Str(id_ptr);
253                        let open = sys::igTreeNodeEx_Str(label_ptr, self.flags.bits());
254                        sys::igPopID();
255                        open
256                    } else {
257                        let label_ptr = self.ui.scratch_txt(s);
258                        sys::igTreeNodeEx_Str(label_ptr, self.flags.bits())
259                    }
260                }
261                TreeNodeId::Ptr(ptr) => {
262                    let label = self.label.as_ref().map_or("", |l| l.as_ref());
263                    let label_ptr = self.ui.scratch_txt(label);
264                    sys::igPushID_Ptr(*ptr as *const std::os::raw::c_void);
265                    let open = sys::igTreeNodeEx_Str(label_ptr, self.flags.bits());
266                    sys::igPopID();
267                    open
268                }
269                TreeNodeId::Int(i) => {
270                    let label = self.label.as_ref().map_or("", |l| l.as_ref());
271                    let label_ptr = self.ui.scratch_txt(label);
272                    sys::igPushID_Int(*i);
273                    let open = sys::igTreeNodeEx_Str(label_ptr, self.flags.bits());
274                    sys::igPopID();
275                    open
276                }
277            }
278        };
279
280        if open {
281            Some(TreeNodeToken::new(self.ui))
282        } else {
283            None
284        }
285    }
286}
287
288/// Tracks a tree node that can be popped by calling `.pop()` or by dropping
289#[must_use]
290pub struct TreeNodeToken<'ui> {
291    _ui: &'ui Ui,
292}
293
294impl<'ui> TreeNodeToken<'ui> {
295    /// Creates a new tree node token
296    fn new(ui: &'ui Ui) -> Self {
297        TreeNodeToken { _ui: ui }
298    }
299
300    /// Pops the tree node
301    pub fn pop(self) {
302        // The drop implementation will handle the actual popping
303    }
304}
305
306impl<'ui> Drop for TreeNodeToken<'ui> {
307    fn drop(&mut self) {
308        unsafe {
309            sys::igTreePop();
310        }
311    }
312}