diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index 14df72c..8a3fa8c 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -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, + selected_fish2: Option, 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(); diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index e42cd40..bb8b990 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -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::*; diff --git a/imgui/src/widget/list_box.rs b/imgui/src/widget/list_box.rs new file mode 100644 index 0000000..1985e75 --- /dev/null +++ b/imgui/src/widget/list_box.rs @@ -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 { + 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(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( + 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 + } +} diff --git a/imgui/src/widget/mod.rs b/imgui/src/widget/mod.rs index 064cdee..9ca7887 100644 --- a/imgui/src/widget/mod.rs +++ b/imgui/src/widget/mod.rs @@ -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;