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/// # List Box Widgets
11impl Ui {
12    /// Constructs a new list box builder.
13    pub fn list_box_config<T: AsRef<str>>(&self, label: T) -> ListBox<T> {
14        ListBox::new(label)
15    }
16}
17
18/// Builder for a list box widget
19#[derive(Clone, Debug)]
20#[must_use]
21pub struct ListBox<T> {
22    label: T,
23    size: [f32; 2],
24}
25
26impl<T: AsRef<str>> ListBox<T> {
27    /// Constructs a new list box builder.
28    #[doc(alias = "ListBoxHeaderVec2", alias = "ListBoxHeaderInt")]
29    pub fn new(label: T) -> ListBox<T> {
30        ListBox {
31            label,
32            size: [0.0, 0.0],
33        }
34    }
35
36    /// Sets the list box size based on the given width and height
37    /// If width or height are 0 or smaller, a default value is calculated
38    /// Helper to calculate the size of a listbox and display a label on the right.
39    /// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an non-visible label e.g. "##empty"
40    ///
41    /// Default: [0.0, 0.0], in which case the combobox calculates a sensible width and height
42    #[inline]
43    pub fn size(mut self, size: impl Into<[f32; 2]>) -> Self {
44        self.size = size.into();
45        self
46    }
47    /// Creates a list box and starts appending to it.
48    ///
49    /// Returns `Some(ListBoxToken)` if the list box is open. After content has been
50    /// rendered, the token must be ended by calling `.end()`.
51    ///
52    /// Returns `None` if the list box is not open and no content should be rendered.
53    #[must_use]
54    pub fn begin(self, ui: &Ui) -> Option<ListBoxToken<'_>> {
55        let size_vec = sys::ImVec2 {
56            x: self.size[0],
57            y: self.size[1],
58        };
59        let should_render = unsafe { sys::igBeginListBox(ui.scratch_txt(self.label), size_vec) };
60        if should_render {
61            Some(ListBoxToken::new(ui))
62        } else {
63            None
64        }
65    }
66    /// Creates a list box and runs a closure to construct the list contents.
67    /// Returns the result of the closure, if it is called.
68    ///
69    /// Note: the closure is not called if the list box is not open.
70    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui, f: F) -> Option<R> {
71        self.begin(ui).map(|_list| f())
72    }
73}
74
75/// Tracks a list box that can be ended by calling `.end()`
76/// or by dropping
77pub struct ListBoxToken<'ui> {
78    ui: &'ui Ui,
79}
80
81impl<'ui> ListBoxToken<'ui> {
82    /// Creates a new list box token
83    pub fn new(ui: &'ui Ui) -> Self {
84        Self { ui }
85    }
86
87    /// Ends the list box
88    pub fn end(self) {
89        // The drop implementation will handle the actual ending
90    }
91}
92
93impl<'ui> Drop for ListBoxToken<'ui> {
94    fn drop(&mut self) {
95        unsafe {
96            sys::igEndListBox();
97        }
98    }
99}
100
101/// # Convenience functions
102impl<T: AsRef<str>> ListBox<T> {
103    /// Builds a simple list box for choosing from a slice of values
104    pub fn build_simple<V, L>(
105        self,
106        ui: &Ui,
107        current_item: &mut usize,
108        items: &[V],
109        label_fn: &L,
110    ) -> bool
111    where
112        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
113    {
114        let mut result = false;
115        let lb = self;
116        if let Some(_cb) = lb.begin(ui) {
117            for (idx, item) in items.iter().enumerate() {
118                let text = label_fn(item);
119                let selected = idx == *current_item;
120                if ui.selectable_config(&text).selected(selected).build() {
121                    *current_item = idx;
122                    result = true;
123                }
124            }
125        }
126        result
127    }
128
129    /// Builds a simple list box for choosing from a slice of values using an `i32` index.
130    ///
131    /// This is useful when you want to represent \"no selection\" with `-1`, matching Dear ImGui's
132    /// list-box patterns that use an `int*` index.
133    pub fn build_simple_i32<V, L>(
134        self,
135        ui: &Ui,
136        current_item: &mut i32,
137        items: &[V],
138        label_fn: &L,
139    ) -> bool
140    where
141        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
142    {
143        let mut result = false;
144        let lb = self;
145        if let Some(_cb) = lb.begin(ui) {
146            for (idx, item) in items.iter().enumerate() {
147                if idx > i32::MAX as usize {
148                    break;
149                }
150                let idx_i32 = idx as i32;
151                let text = label_fn(item);
152                let selected = idx_i32 == *current_item;
153                if ui.selectable_config(&text).selected(selected).build() {
154                    *current_item = idx_i32;
155                    result = true;
156                }
157            }
158        }
159        result
160    }
161}