mirror of
https://github.com/eliasstepanik/imgui-rs.git
synced 2026-01-19 01:18:27 +00:00
added richer support for ListBox
This commit is contained in:
parent
e7b8af5939
commit
9210130872
@ -28,6 +28,7 @@ struct State {
|
||||
buf: ImString,
|
||||
item: usize,
|
||||
item2: usize,
|
||||
item3: i32,
|
||||
text: ImString,
|
||||
text_multiline: ImString,
|
||||
i0: i32,
|
||||
@ -39,6 +40,7 @@ struct State {
|
||||
col1: [f32; 3],
|
||||
col2: [f32; 4],
|
||||
selected_fish: Option<usize>,
|
||||
selected_fish2: Option<usize>,
|
||||
auto_resize_state: AutoResizeState,
|
||||
file_menu: FileMenuState,
|
||||
radio_button: i32,
|
||||
@ -85,6 +87,7 @@ impl Default for State {
|
||||
buf,
|
||||
item: 0,
|
||||
item2: 0,
|
||||
item3: 0,
|
||||
text,
|
||||
text_multiline,
|
||||
i0: 123,
|
||||
@ -96,6 +99,7 @@ impl Default for State {
|
||||
col1: [1.0, 0.0, 0.2],
|
||||
col2: [0.4, 0.7, 0.0, 0.5],
|
||||
selected_fish: None,
|
||||
selected_fish2: None,
|
||||
auto_resize_state: Default::default(),
|
||||
file_menu: Default::default(),
|
||||
radio_button: 0,
|
||||
@ -490,6 +494,37 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
|
||||
im_str!("KKKK"),
|
||||
];
|
||||
ComboBox::new(im_str!("combo scroll")).build_simple_string(ui, &mut state.item2, &items);
|
||||
|
||||
ui.list_box(im_str!("list"), &mut state.item3, &items, 8);
|
||||
|
||||
|
||||
let names = [
|
||||
im_str!("Bream"),
|
||||
im_str!("Haddock"),
|
||||
im_str!("Mackerel"),
|
||||
im_str!("Pollock"),
|
||||
im_str!("Tilefish"),
|
||||
];
|
||||
|
||||
ListBox::new(im_str!("selectables list")).calculate_size(8, 4).build(ui, || {
|
||||
for (index, name) in names.iter().enumerate() {
|
||||
let selected = matches!(state.selected_fish2, Some(i) if i == index );
|
||||
if Selectable::new(name).selected(selected).build(ui) {
|
||||
state.selected_fish2 = Some(index);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let last_size = ui.item_rect_size();
|
||||
ListBox::new(im_str!("selectable list 2")).size([0.0, last_size[1] * 0.66]).build(ui, || {
|
||||
for (index, name) in names.iter().enumerate() {
|
||||
let selected = matches!(state.selected_fish2, Some(i) if i == index );
|
||||
if Selectable::new(name).selected(selected).build(ui) {
|
||||
state.selected_fish2 = Some(index);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.input_text(im_str!("input text"), &mut state.text)
|
||||
.build();
|
||||
ui.input_int(im_str!("input int"), &mut state.i0).build();
|
||||
|
||||
@ -36,6 +36,7 @@ pub use self::widget::color_editors::*;
|
||||
pub use self::widget::combo_box::*;
|
||||
pub use self::widget::drag::*;
|
||||
pub use self::widget::image::*;
|
||||
pub use self::widget::list_box::*;
|
||||
pub use self::widget::menu::*;
|
||||
pub use self::widget::progress_bar::*;
|
||||
pub use self::widget::selectable::*;
|
||||
|
||||
146
imgui/src/widget/list_box.rs
Normal file
146
imgui/src/widget/list_box.rs
Normal file
@ -0,0 +1,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
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ pub mod color_editors;
|
||||
pub mod combo_box;
|
||||
pub mod drag;
|
||||
pub mod image;
|
||||
pub mod list_box;
|
||||
pub mod menu;
|
||||
pub mod misc;
|
||||
pub mod progress_bar;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user