From baa9362a25f352dd52451abff420ff33eecf1e2a Mon Sep 17 00:00:00 2001 From: Joonas Javanainen Date: Sat, 13 Jul 2019 13:11:51 +0300 Subject: [PATCH] Redesign selectable API --- CHANGELOG.markdown | 1 + imgui-examples/examples/test_window_impl.rs | 7 +- src/legacy.rs | 30 ++--- src/lib.rs | 14 +-- src/widget/combo_box.rs | 6 +- src/widget/mod.rs | 1 + src/widget/selectable.rs | 130 ++++++++++++++++++++ 7 files changed, 152 insertions(+), 37 deletions(-) create mode 100644 src/widget/selectable.rs diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index ca21c89..decc8d1 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -16,6 +16,7 @@ - Redesigned child window API (previously known as child frame) - Redesigned image / image button API - Redesigned combo box API +- Redesigned selectable API - Updated layout API - Renderer errors implement std::error::Error - Glium renderer re-exports imgui and glium diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index f475f41..51395ea 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -623,12 +623,7 @@ CTRL+click on individual component to input value.\n", ui.text(im_str!("Aquarium")); ui.separator(); for (index, name) in names.iter().enumerate() { - if ui.selectable( - name, - false, - ImGuiSelectableFlags::empty(), - [0.0, 0.0] - ) { + if Selectable::new(name).build(ui) { state.selected_fish = Some(index); } } diff --git a/src/legacy.rs b/src/legacy.rs index bc55172..41d8f54 100644 --- a/src/legacy.rs +++ b/src/legacy.rs @@ -8,6 +8,7 @@ use crate::widget::color_editors::*; use crate::widget::combo_box::*; use crate::widget::image::{Image, ImageButton}; use crate::widget::progress_bar::ProgressBar; +use crate::widget::selectable::*; use crate::window::{Window, WindowFlags, WindowFocusedFlags}; use crate::{Id, Ui}; @@ -131,20 +132,8 @@ bitflags!( } ); -bitflags!( - /// Flags for selectables - #[repr(C)] - pub struct ImGuiSelectableFlags: c_int { - /// Clicking this don't close parent popup window - const DontClosePopups = 1; - /// Selectable frame can span all columns (text will still fit in current column) - const SpanAllColumns = 1 << 1; - /// Generate press events on double clicks too - const AllowDoubleClick = 1 << 2; - /// Cannot be selected, display greyed out text - const Disabled = 1 << 3; - } -); +#[deprecated(since = "0.2.0", note = "use SelectableFlags instead")] +pub type ImGuiSelectableFlags = SelectableFlags; bitflags!( /// Flags for trees and collapsing headers @@ -365,3 +354,16 @@ impl<'ui> Ui<'ui> { } } } + +impl<'ui> Ui<'ui> { + #[deprecated(since = "0.2.0", note = "use imgui::Selectable::new(...) instead")] + pub fn selectable( + &self, + label: &ImStr, + selected: bool, + flags: SelectableFlags, + size: [f32; 2], + ) -> bool { + unsafe { sys::igSelectable(label.as_ptr(), selected, flags.bits() as i32, size.into()) } + } +} diff --git a/src/lib.rs b/src/lib.rs index 4773643..a023519 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,7 @@ pub use self::widget::color_editors::*; pub use self::widget::combo_box::*; pub use self::widget::image::*; pub use self::widget::progress_bar::*; +pub use self::widget::selectable::*; pub use self::window::child_window::*; pub use self::window::*; pub use self::window_draw_list::{ChannelsSplit, ImColor, WindowDrawList}; @@ -424,19 +425,6 @@ impl<'ui> Ui<'ui> { } } -// Widgets: Selectable / Lists -impl<'ui> Ui<'ui> { - pub fn selectable( - &self, - label: &ImStr, - selected: bool, - flags: ImGuiSelectableFlags, - size: [f32; 2], - ) -> bool { - unsafe { sys::igSelectable(label.as_ptr(), selected, flags.bits(), size.into()) } - } -} - /// # Tooltips impl<'ui> Ui<'ui> { /// Construct a tooltip window that can have any kind of content. diff --git a/src/widget/combo_box.rs b/src/widget/combo_box.rs index 87d257c..6a22111 100644 --- a/src/widget/combo_box.rs +++ b/src/widget/combo_box.rs @@ -192,6 +192,7 @@ impl<'a> ComboBox<'a> { where for<'b> L: Fn(&'b T) -> Cow<'b, ImStr>, { + use crate::widget::selectable::Selectable; let mut result = false; let mut cb = self; let preview_value = items.get(*current_item).map(label_fn); @@ -204,10 +205,7 @@ impl<'a> ComboBox<'a> { for (idx, item) in items.iter().enumerate() { let text = label_fn(item); let selected = idx == *current_item; - // TODO: use safe API - let change = - unsafe { sys::igSelectable(text.as_ptr(), selected, 0, [0.0, 0.0].into()) }; - if change { + if Selectable::new(&text).selected(selected).build(ui) { *current_item = idx; result = true; } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 8fd6f2f..57067ba 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -3,4 +3,5 @@ pub mod combo_box; pub mod image; pub mod misc; pub mod progress_bar; +pub mod selectable; pub mod text; diff --git a/src/widget/selectable.rs b/src/widget/selectable.rs new file mode 100644 index 0000000..9264bee --- /dev/null +++ b/src/widget/selectable.rs @@ -0,0 +1,130 @@ +use bitflags::bitflags; + +use crate::string::ImStr; +use crate::sys; +use crate::Ui; + +bitflags!( + /// Flags for selectables + #[repr(transparent)] + pub struct SelectableFlags: u32 { + /// Clicking this don't close parent popup window + const DONT_CLOSE_POPUPS = sys::ImGuiSelectableFlags_DontClosePopups; + /// Selectable frame can span all columns (text will still fit in current column) + const SPAN_ALL_COLUMNS = sys::ImGuiSelectableFlags_SpanAllColumns; + /// Generate press events on double clicks too + const ALLOW_DOUBLE_CLICK = sys::ImGuiSelectableFlags_AllowDoubleClick; + /// Cannot be selected, display greyed out text + const DISABLED = sys::ImGuiSelectableFlags_Disabled; + } +); + +/// Builder for a selectable widget. +#[derive(Clone, Debug)] +#[must_use] +pub struct Selectable<'a> { + label: &'a ImStr, + selected: bool, + flags: SelectableFlags, + size: [f32; 2], +} + +impl<'a> Selectable<'a> { + /// Constructs a new selectable builder. + pub fn new(label: &ImStr) -> Selectable { + Selectable { + label, + selected: false, + flags: SelectableFlags::empty(), + size: [0.0, 0.0], + } + } + /// Replaces all current settings with the given flags + #[inline] + pub fn flags(mut self, flags: SelectableFlags) -> Self { + self.flags = flags; + self + } + /// Sets the selected state of the selectable + #[inline] + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + /// Enables/disables closing parent popup window on click. + /// + /// Default: enabled + #[inline] + pub fn close_popups(mut self, value: bool) -> Self { + self.flags.set(SelectableFlags::DONT_CLOSE_POPUPS, !value); + self + } + /// Enables/disables full column span (text will still fit in the current column). + /// + /// Default: disabled + #[inline] + pub fn span_all_columns(mut self, value: bool) -> Self { + self.flags.set(SelectableFlags::SPAN_ALL_COLUMNS, value); + self + } + /// Enables/disables click event generation on double clicks. + /// + /// Default: disabled + #[inline] + pub fn allow_double_click(mut self, value: bool) -> Self { + self.flags.set(SelectableFlags::ALLOW_DOUBLE_CLICK, value); + self + } + /// Enables/disables the selectable. + /// + /// When disabled, it cannot be selected and the text uses the disabled text color. + /// + /// Default: disabled + #[inline] + pub fn disabled(mut self, value: bool) -> Self { + self.flags.set(SelectableFlags::DISABLED, value); + self + } + /// Sets the size of the selectable. + /// + /// For the X axis: + /// + /// - `> 0.0`: use given width + /// - `= 0.0`: use remaining width + /// + /// For the Y axis: + /// + /// - `> 0.0`: use given height + /// - `= 0.0`: use label height + #[inline] + pub fn size(mut self, size: [f32; 2]) -> Self { + self.size = size.into(); + self + } + /// Builds the selectable. + /// + /// Returns true if the selectable was clicked. + pub fn build(self, _: &Ui) -> bool { + unsafe { + sys::igSelectable( + self.label.as_ptr(), + self.selected, + self.flags.bits() as i32, + self.size.into(), + ) + } + } +} + +/// # Convenience functions +impl<'a> Selectable<'a> { + /// Builds the selectable using a mutable reference to selected state. + pub fn build_with_ref(self, ui: &Ui, selected: &mut bool) -> bool { + if self.selected(*selected).build(ui) { + *selected = true; + true + } else { + false + } + } +}