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
use std::borrow::Cow;
use std::ptr;
use std::thread;
use crate::context::Context;
use crate::string::ImStr;
use crate::sys;
use crate::Ui;
#[derive(Copy, Clone, Debug)]
enum Size {
Vec {
size: sys::ImVec2,
},
Items {
items_count: i32,
height_in_items: i32,
},
}
/// Builder for a list box widget
#[derive(Copy, Clone, Debug)]
#[must_use]
pub struct ListBox<'a> {
label: &'a ImStr,
size: Size,
}
impl<'a> ListBox<'a> {
/// Constructs a new list box builder.
pub fn new(label: &'a ImStr) -> ListBox<'a> {
ListBox {
label,
size: Size::Vec {
size: [0.0, 0.0].into(),
},
}
}
/// Sets the list box size based on the number of items that you want to make visible
/// Size default to hold ~7.25 items.
/// We add +25% worth of item height to allow the user to see at a glance if there are more items up/down, without looking at the scrollbar.
/// We don't add this extra bit if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
#[inline]
pub fn calculate_size(mut self, items_count: i32, height_in_items: i32) -> Self {
self.size = Size::Items {
items_count,
height_in_items,
};
self
}
/// 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: [f32; 2]) -> Self {
self.size = Size::Vec { 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 should_render = unsafe {
match self.size {
Size::Vec { size } => sys::igListBoxHeaderVec2(self.label.as_ptr(), size),
Size::Items {
items_count,
height_in_items,
} => sys::igListBoxHeaderInt(self.label.as_ptr(), items_count, height_in_items),
}
};
if should_render {
Some(ListBoxToken { ctx: ui.ctx })
} else {
None
}
}
/// Creates a list box and runs a closure to construct the list contents.
///
/// Note: the closure is not called if the list box is not open.
pub fn build<F: FnOnce()>(self, ui: &Ui, f: F) {
if let Some(list) = self.begin(ui) {
f();
list.end(ui);
}
}
}
/// Tracks a list box that must be ended by calling `.end()`
#[must_use]
pub struct ListBoxToken {
ctx: *const Context,
}
impl ListBoxToken {
/// Ends a list box
pub fn end(mut self, _: &Ui) {
self.ctx = ptr::null();
unsafe { sys::igListBoxFooter() };
}
}
impl Drop for ListBoxToken {
fn drop(&mut self) {
if !self.ctx.is_null() && !thread::panicking() {
panic!("A ListBoxToken was leaked. Did you call .end()?");
}
}
}
/// # Convenience functions
impl<'a> ListBox<'a> {
/// Builds a simple list box for choosing from a slice of values
pub fn build_simple<T, L>(
self,
ui: &Ui,
current_item: &mut usize,
items: &[T],
label_fn: &L,
) -> bool
where
for<'b> L: Fn(&'b T) -> Cow<'b, ImStr>,
{
use crate::widget::selectable::Selectable;
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 Selectable::new(&text).selected(selected).build(ui) {
*current_item = idx;
result = true;
}
}
_cb.end(ui);
}
result
}
}