arcdps_imgui\widget/
tree.rs

1use bitflags::bitflags;
2use std::os::raw::{c_char, c_void};
3
4// use crate::string::ImStr;
5use crate::sys;
6use crate::{Condition, Ui};
7
8bitflags!(
9    /// Flags for tree nodes
10    #[repr(transparent)]
11    pub struct TreeNodeFlags: u32 {
12        /// Draw as selected
13        const SELECTED = sys::ImGuiTreeNodeFlags_Selected;
14        /// Full colored frame (e.g. for CollapsingHeader)
15        const FRAMED = sys::ImGuiTreeNodeFlags_Framed;
16        /// Hit testing to allow subsequent widgets to overlap this one
17        const ALLOW_ITEM_OVERLAP = sys::ImGuiTreeNodeFlags_AllowItemOverlap;
18        /// Don't push a tree node when open (e.g. for CollapsingHeader) = no extra indent nor
19        /// pushing on ID stack
20        const NO_TREE_PUSH_ON_OPEN = sys::ImGuiTreeNodeFlags_NoTreePushOnOpen;
21        /// Don't automatically and temporarily open node when Logging is active (by default
22        /// logging will automatically open tree nodes)
23        const NO_AUTO_OPEN_ON_LOG = sys::ImGuiTreeNodeFlags_NoAutoOpenOnLog;
24        /// Default node to be open
25        const DEFAULT_OPEN = sys::ImGuiTreeNodeFlags_DefaultOpen;
26        /// Need double-click to open node
27        const OPEN_ON_DOUBLE_CLICK = sys::ImGuiTreeNodeFlags_OpenOnDoubleClick;
28        /// Only open when clicking on the arrow part.
29        ///
30        /// If `TreeNodeFlags::OPEN_ON_DOUBLE_CLICK` is also set, single-click arrow or
31        /// double-click all box to open.
32        const OPEN_ON_ARROW = sys::ImGuiTreeNodeFlags_OpenOnArrow;
33        /// No collapsing, no arrow (use as a convenience for leaf nodes)
34        const LEAF = sys::ImGuiTreeNodeFlags_Leaf;
35        /// Display a bullet instead of arrow
36        const BULLET = sys::ImGuiTreeNodeFlags_Bullet;
37        /// Use `Style::frame_padding` (even for an unframed text node) to vertically align text
38        /// baseline to regular widget height.
39        ///
40        /// Equivalent to calling `Ui::align_text_to_frame_padding`.
41        const FRAME_PADDING = sys::ImGuiTreeNodeFlags_FramePadding;
42        /// Extend hit box to the right-most edge, even if not framed.
43        ///
44        /// This is not the default in order to allow adding other items on the same line. In the
45        /// future we may refactor the hit system to be front-to-back, allowing natural overlaps
46        /// and then this can become the default.
47        const SPAN_AVAIL_WIDTH = sys::ImGuiTreeNodeFlags_SpanAvailWidth;
48        /// Extend hit box to the left-most and right-most edges (bypass the indented area)
49        const SPAN_FULL_WIDTH = sys::ImGuiTreeNodeFlags_SpanFullWidth;
50        /// (WIP) Nav: left direction may move to this tree node from any of its child
51        const NAV_LEFT_JUMPS_BACK_HERE = sys::ImGuiTreeNodeFlags_NavLeftJumpsBackHere;
52    }
53);
54
55static FMT: &[u8] = b"%s\0";
56
57#[inline]
58fn fmt_ptr() -> *const c_char {
59    FMT.as_ptr() as *const c_char
60}
61
62/// Unique ID used by tree nodes
63#[derive(Copy, Clone, Debug, Eq, PartialEq)]
64pub enum TreeNodeId<T> {
65    Str(T),
66    Ptr(*const c_void),
67}
68
69impl<T: AsRef<str>> From<T> for TreeNodeId<T> {
70    fn from(s: T) -> Self {
71        TreeNodeId::Str(s)
72    }
73}
74
75// this is a bit wonky here using the T param...
76impl<T> From<*const T> for TreeNodeId<T> {
77    fn from(p: *const T) -> Self {
78        TreeNodeId::Ptr(p as *const c_void)
79    }
80}
81
82// this is a bit wonky here using the T param...
83impl<T> From<*mut T> for TreeNodeId<T> {
84    fn from(p: *mut T) -> Self {
85        TreeNodeId::Ptr(p as *const T as *const c_void)
86    }
87}
88
89/// Builder for a tree node widget
90#[derive(Copy, Clone, Debug)]
91#[must_use]
92pub struct TreeNode<T, L = &'static str> {
93    id: TreeNodeId<T>,
94    label: Option<L>,
95    opened: bool,
96    opened_cond: Condition,
97    flags: TreeNodeFlags,
98}
99
100impl<T: AsRef<str>> TreeNode<T, &'static str> {
101    /// Constructs a new tree node builder
102    pub fn new<I: Into<TreeNodeId<T>>>(id: I) -> TreeNode<T, &'static str> {
103        TreeNode {
104            id: id.into(),
105            label: None,
106            opened: false,
107            opened_cond: Condition::Never,
108            flags: TreeNodeFlags::empty(),
109        }
110    }
111}
112
113impl<T: AsRef<str>, L: AsRef<str>> TreeNode<T, L> {
114    /// Sets the tree node label
115    pub fn label<I: Into<TreeNodeId<L2>>, L2: AsRef<str>>(self, label: L2) -> TreeNode<T, L2> {
116        TreeNode {
117            label: Some(label),
118            id: self.id,
119            opened: self.opened,
120            opened_cond: self.opened_cond,
121            flags: self.flags,
122        }
123    }
124
125    /// Sets the opened state of the tree node, which is applied based on the given condition value
126    pub fn opened(mut self, opened: bool, cond: Condition) -> Self {
127        self.opened = opened;
128        self.opened_cond = cond;
129        self
130    }
131
132    /// Replaces all current settings with the given flags.
133    pub fn flags(mut self, flags: TreeNodeFlags) -> Self {
134        self.flags = flags;
135        self
136    }
137
138    /// Enables/disables drawing the tree node in selected state.
139    ///
140    /// Disabled by default.
141    pub fn selected(mut self, value: bool) -> Self {
142        self.flags.set(TreeNodeFlags::SELECTED, value);
143        self
144    }
145
146    /// Enables/disables full-colored frame.
147    ///
148    /// Disabled by default.
149    pub fn framed(mut self, value: bool) -> Self {
150        self.flags.set(TreeNodeFlags::FRAMED, value);
151        self
152    }
153
154    /// Enables/disables allowing the tree node to overlap subsequent widgets.
155    ///
156    /// Disabled by default.
157    pub fn allow_item_overlap(mut self, value: bool) -> Self {
158        self.flags.set(TreeNodeFlags::ALLOW_ITEM_OVERLAP, value);
159        self
160    }
161
162    /// Enables/disables automatic tree push when the tree node is open (= adds extra indentation
163    /// and pushes to the ID stack).
164    ///
165    /// Enabled by default.
166    pub fn tree_push_on_open(mut self, value: bool) -> Self {
167        self.flags.set(TreeNodeFlags::NO_TREE_PUSH_ON_OPEN, !value);
168        self
169    }
170
171    /// Enables/disables automatic opening of the tree node when logging is active.
172    ///
173    /// By default, logging will automatically open all tree nodes.
174    ///
175    /// Enabled by default.
176    pub fn auto_open_on_log(mut self, value: bool) -> Self {
177        self.flags.set(TreeNodeFlags::NO_AUTO_OPEN_ON_LOG, !value);
178        self
179    }
180
181    /// Sets the default open state for the tree node.
182    ///
183    /// Tree nodes are closed by default.
184    pub fn default_open(mut self, value: bool) -> Self {
185        self.flags.set(TreeNodeFlags::DEFAULT_OPEN, value);
186        self
187    }
188
189    /// Only open when the tree node is double-clicked.
190    ///
191    /// Disabled by default.
192    pub fn open_on_double_click(mut self, value: bool) -> Self {
193        self.flags.set(TreeNodeFlags::OPEN_ON_DOUBLE_CLICK, value);
194        self
195    }
196
197    /// Only open when clicking the arrow part of the tree node.
198    ///
199    /// Disabled by default.
200    pub fn open_on_arrow(mut self, value: bool) -> Self {
201        self.flags.set(TreeNodeFlags::OPEN_ON_ARROW, value);
202        self
203    }
204
205    /// Enable/disables leaf mode (no collapsing, no arrow).
206    ///
207    /// Disabled by default.
208    pub fn leaf(mut self, value: bool) -> Self {
209        self.flags.set(TreeNodeFlags::LEAF, value);
210        self
211    }
212
213    /// Display a bullet instead of arrow.
214    ///
215    /// Disabled by default.
216    pub fn bullet(mut self, value: bool) -> Self {
217        self.flags.set(TreeNodeFlags::BULLET, value);
218        self
219    }
220
221    /// Use `frame_padding` to vertically align text baseline to regular widget height.
222    ///
223    /// Disabled by default.
224    pub fn frame_padding(mut self, value: bool) -> Self {
225        self.flags.set(TreeNodeFlags::FRAME_PADDING, value);
226        self
227    }
228
229    /// Left direction may move to this tree node from any of its child.
230    ///
231    /// Disabled by default.
232    pub fn nav_left_jumps_back_here(mut self, value: bool) -> Self {
233        self.flags
234            .set(TreeNodeFlags::NAV_LEFT_JUMPS_BACK_HERE, value);
235        self
236    }
237
238    /// Pushes a tree node and starts appending to it.
239    ///
240    /// Returns `Some(TreeNodeToken)` if the tree node is open. After content has been
241    /// rendered, the token can be popped by calling `.pop()`.
242    ///
243    /// Returns `None` if the tree node is not open and no content should be rendered.
244    pub fn push<'ui>(self, ui: &Ui<'ui>) -> Option<TreeNodeToken<'ui>> {
245        let open = unsafe {
246            if self.opened_cond != Condition::Never {
247                sys::igSetNextItemOpen(self.opened, self.opened_cond as i32);
248            }
249            match self.id {
250                TreeNodeId::Str(id) => {
251                    let (id, label) = match self.label {
252                        Some(label) => ui.scratch_txt_two(id, label),
253                        None => {
254                            let v = ui.scratch_txt(id);
255                            (v, v)
256                        }
257                    };
258
259                    sys::igTreeNodeEx_StrStr(id, self.flags.bits() as i32, fmt_ptr(), label)
260                }
261                TreeNodeId::Ptr(id) => sys::igTreeNodeEx_Ptr(
262                    id,
263                    self.flags.bits() as i32,
264                    fmt_ptr(),
265                    match self.label {
266                        Some(v) => ui.scratch_txt(v),
267                        None => ui.scratch_txt(""),
268                    },
269                ),
270            }
271        };
272        if open {
273            Some(TreeNodeToken::new(
274                ui,
275                !self.flags.contains(TreeNodeFlags::NO_TREE_PUSH_ON_OPEN),
276            ))
277        } else {
278            None
279        }
280    }
281    /// Creates a tree node and runs a closure to construct the contents.
282    /// Returns the result of the closure, if it is called.
283    ///
284    /// Note: the closure is not called if the tree node is not open.
285    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui<'_>, f: F) -> Option<R> {
286        self.push(ui).map(|_node| f())
287    }
288}
289
290/// Tracks a tree node that can be popped by calling `.pop()`, `end()`, or by dropping.
291///
292/// If `TreeNodeFlags::NO_TREE_PUSH_ON_OPEN` was used when this token was created, calling `.pop()`
293/// is not mandatory and is a no-op.
294#[must_use]
295pub struct TreeNodeToken<'a>(core::marker::PhantomData<crate::Ui<'a>>, bool);
296
297impl<'a> TreeNodeToken<'a> {
298    /// Creates a new token type. This takes a bool for the no-op variant on NO_TREE_PUSH_ON_OPEN.
299    pub(crate) fn new(_: &crate::Ui<'a>, execute_drop: bool) -> Self {
300        Self(std::marker::PhantomData, execute_drop)
301    }
302
303    /// Pops a tree node
304    #[inline]
305    pub fn end(self) {
306        // left empty for drop
307    }
308
309    /// Pops a tree node
310    #[inline]
311    pub fn pop(self) {
312        self.end()
313    }
314}
315
316impl Drop for TreeNodeToken<'_> {
317    #[doc(alias = "TreePop")]
318    fn drop(&mut self) {
319        if self.1 {
320            unsafe { sys::igTreePop() }
321        }
322    }
323}
324
325/// Builder for a collapsing header widget
326#[derive(Copy, Clone, Debug)]
327#[must_use]
328pub struct CollapsingHeader<T> {
329    label: T,
330    flags: TreeNodeFlags,
331}
332
333impl<T: AsRef<str>> CollapsingHeader<T> {
334    /// Constructs a new collapsing header builder
335    #[doc(alias = "CollapsingHeader")]
336    pub fn new(label: T) -> CollapsingHeader<T> {
337        CollapsingHeader {
338            label,
339            flags: TreeNodeFlags::empty(),
340        }
341    }
342    /// Replaces all current settings with the given flags.
343    #[inline]
344    pub fn flags(mut self, flags: TreeNodeFlags) -> Self {
345        self.flags = flags;
346        self
347    }
348    /// Enables/disables allowing the collapsing header to overlap subsequent widgets.
349    ///
350    /// Disabled by default.
351    #[inline]
352    pub fn allow_item_overlap(mut self, value: bool) -> Self {
353        self.flags.set(TreeNodeFlags::ALLOW_ITEM_OVERLAP, value);
354        self
355    }
356    /// Sets the default open state for the collapsing header.
357    ///
358    /// Collapsing headers are closed by default.
359    #[inline]
360    pub fn default_open(mut self, value: bool) -> Self {
361        self.flags.set(TreeNodeFlags::DEFAULT_OPEN, value);
362        self
363    }
364    /// Only open when the collapsing header is double-clicked.
365    ///
366    /// Disabled by default.
367    #[inline]
368    pub fn open_on_double_click(mut self, value: bool) -> Self {
369        self.flags.set(TreeNodeFlags::OPEN_ON_DOUBLE_CLICK, value);
370        self
371    }
372    /// Only open when clicking the arrow part of the collapsing header.
373    ///
374    /// Disabled by default.
375    #[inline]
376    pub fn open_on_arrow(mut self, value: bool) -> Self {
377        self.flags.set(TreeNodeFlags::OPEN_ON_ARROW, value);
378        self
379    }
380    /// Enable/disables leaf mode (no collapsing, no arrow).
381    ///
382    /// Disabled by default.
383    #[inline]
384    pub fn leaf(mut self, value: bool) -> Self {
385        self.flags.set(TreeNodeFlags::LEAF, value);
386        self
387    }
388    /// Display a bullet instead of arrow.
389    ///
390    /// Disabled by default.
391    #[inline]
392    pub fn bullet(mut self, value: bool) -> Self {
393        self.flags.set(TreeNodeFlags::BULLET, value);
394        self
395    }
396    /// Use `frame_padding` to vertically align text baseline to regular widget height.
397    ///
398    /// Disabled by default.
399    #[inline]
400    pub fn frame_padding(mut self, value: bool) -> Self {
401        self.flags.set(TreeNodeFlags::FRAME_PADDING, value);
402        self
403    }
404
405    /// Begins the collapsing header.
406    ///
407    /// Returns true if the collapsing header is open and content should be rendered.
408    ///
409    /// This is the same as [build](Self::build) but is provided for consistent naming.
410    #[must_use]
411    pub fn begin(self, ui: &Ui<'_>) -> bool {
412        self.build(ui)
413    }
414
415    /// Begins the collapsing header.
416    ///
417    /// Returns true if the collapsing header is open and content should be rendered.
418    ///
419    /// This is the same as [build_with_close_button](Self::build_with_close_button)
420    /// but is provided for consistent naming.
421    #[must_use]
422    pub fn begin_with_close_button(self, ui: &Ui<'_>, opened: &mut bool) -> bool {
423        self.build_with_close_button(ui, opened)
424    }
425
426    /// Builds the collapsing header.
427    ///
428    /// Returns true if the collapsing header is open and content should be rendered.
429    #[must_use]
430    #[inline]
431    pub fn build(self, ui: &Ui<'_>) -> bool {
432        unsafe {
433            sys::igCollapsingHeader_TreeNodeFlags(
434                ui.scratch_txt(self.label),
435                self.flags.bits() as i32,
436            )
437        }
438    }
439    /// Builds the collapsing header, and adds an additional close button that changes the value of
440    /// the given mutable reference when clicked.
441    ///
442    /// Returns true if the collapsing header is open and content should be rendered.
443    #[must_use]
444    #[inline]
445    pub fn build_with_close_button(self, ui: &Ui<'_>, opened: &mut bool) -> bool {
446        unsafe {
447            sys::igCollapsingHeader_BoolPtr(
448                ui.scratch_txt(self.label),
449                opened as *mut bool,
450                self.flags.bits() as i32,
451            )
452        }
453    }
454}
455
456impl Ui<'_> {
457    /// Constructs a new collapsing header
458    #[doc(alias = "CollapsingHeader")]
459    pub fn collapsing_header(&self, label: impl AsRef<str>, flags: TreeNodeFlags) -> bool {
460        CollapsingHeader::new(label).flags(flags).build(self)
461    }
462
463    /// Constructs a new collapsing header
464    #[doc(alias = "CollapsingHeader")]
465    pub fn collapsing_header_with_close_button(
466        &self,
467        label: impl AsRef<str>,
468        flags: TreeNodeFlags,
469        opened: &mut bool,
470    ) -> bool {
471        CollapsingHeader::new(label)
472            .flags(flags)
473            .build_with_close_button(self, opened)
474    }
475}