dear_imgui/widget/
tree.rs

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