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