Skip to main content

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