dear_imgui_rs/widget/
list_box.rs

1//! List boxes
2//!
3//! Classic list-box widget and builder for fixed-height item selection.
4//!
5use std::borrow::Cow;
6
7use crate::Ui;
8use crate::sys;
9
10/// Builder for a list box widget
11#[derive(Clone, Debug)]
12#[must_use]
13pub struct ListBox<T> {
14    label: T,
15    size: [f32; 2],
16}
17
18impl<T: AsRef<str>> ListBox<T> {
19    /// Constructs a new list box builder.
20    #[doc(alias = "ListBoxHeaderVec2", alias = "ListBoxHeaderInt")]
21    pub fn new(label: T) -> ListBox<T> {
22        ListBox {
23            label,
24            size: [0.0, 0.0],
25        }
26    }
27
28    /// Sets the list box size based on the given width and height
29    /// If width or height are 0 or smaller, a default value is calculated
30    /// Helper to calculate the size of a listbox and display a label on the right.
31    /// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an non-visible label e.g. "##empty"
32    ///
33    /// Default: [0.0, 0.0], in which case the combobox calculates a sensible width and height
34    #[inline]
35    pub fn size(mut self, size: impl Into<[f32; 2]>) -> Self {
36        self.size = size.into();
37        self
38    }
39    /// Creates a list box and starts appending to it.
40    ///
41    /// Returns `Some(ListBoxToken)` if the list box is open. After content has been
42    /// rendered, the token must be ended by calling `.end()`.
43    ///
44    /// Returns `None` if the list box is not open and no content should be rendered.
45    #[must_use]
46    pub fn begin(self, ui: &Ui) -> Option<ListBoxToken<'_>> {
47        let size_vec = sys::ImVec2 {
48            x: self.size[0],
49            y: self.size[1],
50        };
51        let should_render = unsafe { sys::igBeginListBox(ui.scratch_txt(self.label), size_vec) };
52        if should_render {
53            Some(ListBoxToken::new(ui))
54        } else {
55            None
56        }
57    }
58    /// Creates a list box and runs a closure to construct the list contents.
59    /// Returns the result of the closure, if it is called.
60    ///
61    /// Note: the closure is not called if the list box is not open.
62    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui, f: F) -> Option<R> {
63        self.begin(ui).map(|_list| f())
64    }
65}
66
67/// Tracks a list box that can be ended by calling `.end()`
68/// or by dropping
69pub struct ListBoxToken<'ui> {
70    ui: &'ui Ui,
71}
72
73impl<'ui> ListBoxToken<'ui> {
74    /// Creates a new list box token
75    pub fn new(ui: &'ui Ui) -> Self {
76        Self { ui }
77    }
78
79    /// Ends the list box
80    pub fn end(self) {
81        // The drop implementation will handle the actual ending
82    }
83}
84
85impl<'ui> Drop for ListBoxToken<'ui> {
86    fn drop(&mut self) {
87        unsafe {
88            sys::igEndListBox();
89        }
90    }
91}
92
93/// # Convenience functions
94impl<T: AsRef<str>> ListBox<T> {
95    /// Builds a simple list box for choosing from a slice of values
96    pub fn build_simple<V, L>(
97        self,
98        ui: &Ui,
99        current_item: &mut usize,
100        items: &[V],
101        label_fn: &L,
102    ) -> bool
103    where
104        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
105    {
106        let mut result = false;
107        let lb = self;
108        if let Some(_cb) = lb.begin(ui) {
109            for (idx, item) in items.iter().enumerate() {
110                let text = label_fn(item);
111                let selected = idx == *current_item;
112                if ui.selectable_config(&text).selected(selected).build() {
113                    *current_item = idx;
114                    result = true;
115                }
116            }
117        }
118        result
119    }
120}