Redesign combo box API

This commit is contained in:
Joonas Javanainen 2019-07-13 12:13:10 +03:00
parent f05fd62c30
commit c0b9571160
No known key found for this signature in database
GPG Key ID: D39CCA5CB19B9179
6 changed files with 269 additions and 63 deletions

View File

@ -15,6 +15,7 @@
- Redesigned color editor/picker API
- Redesigned child window API (previously known as child frame)
- Redesigned image / image button API
- Redesigned combo box API
- Updated layout API
- Renderer errors implement std::error::Error
- Glium renderer re-exports imgui and glium

View File

@ -26,8 +26,8 @@ struct State {
no_close: bool,
wrap_width: f32,
buf: ImString,
item: i32,
item2: i32,
item: usize,
item2: usize,
text: ImString,
text_multiline: ImString,
i0: i32,
@ -45,7 +45,7 @@ struct State {
color_edit: ColorEditState,
custom_rendering: CustomRenderingState,
dont_ask_me_next_time: bool,
stacked_modals_item: i32,
stacked_modals_item: usize,
stacked_modals_color: [f32; 4],
}
@ -139,7 +139,7 @@ impl Default for ColorEditState {
struct FileMenuState {
enabled: bool,
f: f32,
n: i32,
n: usize,
b: bool,
}
@ -438,8 +438,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
ui.separator();
ui.label_text(im_str!("label"), im_str!("Value"));
ui.combo(
im_str!("combo"),
ComboBox::new(im_str!("combo")).build_simple_string(ui,
&mut state.item,
&[
im_str!("aaaa"),
@ -447,9 +446,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
im_str!("cccc"),
im_str!("dddd"),
im_str!("eeee"),
],
-1,
);
]);
let items = [
im_str!("AAAA"),
im_str!("BBBB"),
@ -463,7 +460,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) {
im_str!("JJJJ"),
im_str!("KKKK"),
];
ui.combo(im_str!("combo scroll"), &mut state.item2, &items, -1);
ComboBox::new(im_str!("combo scroll")).build_simple_string(ui, &mut state.item2, &items);
ui.input_text(im_str!("input text"), &mut state.text)
.build();
ui.input_int(im_str!("input int"), &mut state.i0).build();
@ -672,7 +669,7 @@ CTRL+click on individual component to input value.\n",
);
let items = &[im_str!("aaaa"), im_str!("bbbb"), im_str!("cccc"), im_str!("dddd"), im_str!("eeee")];
ui.combo(im_str!("Combo"), &mut state.stacked_modals_item, items, -1);
ComboBox::new(im_str!("Combo")).build_simple_string(ui, &mut state.stacked_modals_item, items);
ColorEdit::new(im_str!("color"), &mut state.stacked_modals_color).build(ui);
@ -763,7 +760,7 @@ fn show_example_menu_file<'a>(ui: &Ui<'a>, state: &mut FileMenuState) {
.step(0.1)
.build();
let items = [im_str!("Yes"), im_str!("No"), im_str!("Maybe")];
ui.combo(im_str!("Combo"), &mut state.n, &items, -1);
ComboBox::new(im_str!("Combo")).build_simple_string(ui, &mut state.n, &items);
ui.checkbox(im_str!("Check"), &mut state.b);
});
ui.menu(im_str!("Colors")).build(|| {

View File

@ -1,10 +1,11 @@
#![allow(non_upper_case_globals)]
use bitflags::bitflags;
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use crate::render::renderer::TextureId;
use crate::string::ImStr;
use crate::widget::color_editors::*;
use crate::widget::combo_box::*;
use crate::widget::image::{Image, ImageButton};
use crate::widget::progress_bar::ProgressBar;
use crate::window::{Window, WindowFlags, WindowFocusedFlags};
@ -13,31 +14,8 @@ use crate::{Id, Ui};
#[deprecated(since = "0.2.0", note = "use ColorEditFlags instead")]
pub type ImGuiColorEditFlags = ColorEditFlags;
bitflags!(
/// Flags for combo boxes
#[repr(C)]
pub struct ImGuiComboFlags: c_int {
/// Align the popup toward the left by default
const PopupAlignLeft = 1;
/// Max ~4 items visible.
const HeightSmall = 1 << 1;
/// Max ~8 items visible (default)
const HeightRegular = 1 << 2;
/// Max ~20 items visible
const HeightLarge = 1 << 3;
/// As many fitting items as possible
const HeightLargest = 1 << 4;
/// Display on the preview box without the square arrow button
const NoArrowButton = 1 << 5;
/// Display only a square arrow button
const NoPreview = 1 << 6;
const HeightMask = ImGuiComboFlags::HeightSmall.bits
| ImGuiComboFlags::HeightRegular.bits
| ImGuiComboFlags::HeightLarge.bits
| ImGuiComboFlags::HeightLargest.bits;
}
);
#[deprecated(since = "0.2.0", note = "use ComboFlags instead")]
pub type ImGuiComboFlags = ComboBoxFlags;
bitflags!(
/// Flags for igBeginDragDropSource(), igAcceptDragDropPayload()
@ -359,3 +337,31 @@ impl<'ui> Ui<'ui> {
ImageButton::new(texture, size)
}
}
impl<'ui> Ui<'ui> {
#[deprecated(
since = "0.2.0",
note = "use imgui::ComboBox::new(...), and either build_simple(), build_simple_string(), or custom rendering (e.g. selectables) instead"
)]
pub fn combo<'p, StringType: AsRef<ImStr> + ?Sized>(
&self,
label: &'p ImStr,
current_item: &mut i32,
items: &'p [&'p StringType],
height_in_items: i32,
) -> bool {
let items_inner: Vec<*const c_char> = items
.into_iter()
.map(|item| item.as_ref().as_ptr())
.collect();
unsafe {
sys::igCombo(
label.as_ptr(),
current_item,
items_inner.as_ptr() as *mut *const c_char,
items_inner.len() as i32,
height_in_items,
)
}
}
}

View File

@ -43,6 +43,7 @@ pub use self::style::*;
pub use self::trees::{CollapsingHeader, TreeNode};
pub use self::utils::*;
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::window::child_window::*;
@ -557,31 +558,6 @@ impl<'ui> Ui<'ui> {
}
}
// Widgets: Combos
impl<'ui> Ui<'ui> {
pub fn combo<'p, StringType: AsRef<ImStr> + ?Sized>(
&self,
label: &'p ImStr,
current_item: &mut i32,
items: &'p [&'p StringType],
height_in_items: i32,
) -> bool {
let items_inner: Vec<*const c_char> = items
.into_iter()
.map(|item| item.as_ref().as_ptr())
.collect();
unsafe {
sys::igCombo(
label.as_ptr(),
current_item,
items_inner.as_ptr() as *mut *const c_char,
items_inner.len() as i32,
height_in_items,
)
}
}
}
// Widgets: ListBox
impl<'ui> Ui<'ui> {
pub fn list_box<'p, StringType: AsRef<ImStr> + ?Sized>(

225
src/widget/combo_box.rs Normal file
View File

@ -0,0 +1,225 @@
use bitflags::bitflags;
use std::borrow::Cow;
use std::marker::PhantomData;
use std::ptr;
use crate::string::ImStr;
use crate::sys;
use crate::Ui;
// TODO: support size constraints
/// Combo box height mode.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ComboBoxHeight {
/// Max ~4 items visible.
Small,
/// Max ~8 items visible.
Regular,
/// Max ~20 items visible.
Large,
/// As many fitting items as possible visible.
Largest,
}
/// Combo box preview mode.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ComboBoxPreviewMode {
/// Show only a box with the preview value
Label,
/// Show only an arrow button
ArrowButton,
/// Show a box with the preview value and an arrow button
Full,
}
bitflags!(
/// Flags for combo boxes
#[repr(transparent)]
pub struct ComboBoxFlags: u32 {
/// Align the popup toward the left by default
const POPUP_ALIGN_LEFT = sys::ImGuiComboFlags_PopupAlignLeft;
/// Max ~4 items visible.
const HEIGHT_SMALL = sys::ImGuiComboFlags_HeightSmall;
/// Max ~8 items visible (default)
const HEIGHT_REGULAR = sys::ImGuiComboFlags_HeightRegular;
/// Max ~20 items visible
const HEIGHT_LARGE = sys::ImGuiComboFlags_HeightLarge;
/// As many fitting items as possible
const HEIGHT_LARGEST = sys::ImGuiComboFlags_HeightLargest;
/// Display on the preview box without the square arrow button
const NO_ARROW_BUTTON = sys::ImGuiComboFlags_NoArrowButton;
/// Display only a square arrow button
const NO_PREVIEW = sys::ImGuiComboFlags_NoPreview;
}
);
/// Builder for a combo box widget
#[derive(Clone, Debug)]
#[must_use]
pub struct ComboBox<'a> {
label: &'a ImStr,
preview_value: Option<&'a ImStr>,
flags: ComboBoxFlags,
}
impl<'a> ComboBox<'a> {
/// Constructs a new combo box builder.
pub fn new(label: &'a ImStr) -> ComboBox<'a> {
ComboBox {
label,
preview_value: None,
flags: ComboBoxFlags::empty(),
}
}
/// Sets the preview value displayed in the preview box (if visible).
#[inline]
pub fn preview_value(mut self, preview_value: &'a ImStr) -> Self {
self.preview_value = Some(preview_value);
self
}
/// Replaces all current settings with the given flags.
#[inline]
pub fn flags(mut self, flags: ComboBoxFlags) -> Self {
self.flags = flags;
self
}
/// Enables/disables aligning the combo box popup toward the left.
///
/// Disabled by default.
#[inline]
pub fn popup_align_left(mut self, popup_align_left: bool) -> Self {
self.flags
.set(ComboBoxFlags::POPUP_ALIGN_LEFT, popup_align_left);
self
}
/// Sets the combo box height.
///
/// Default: `ComboBoxHeight::Regular`
#[inline]
pub fn height(mut self, height: ComboBoxHeight) -> Self {
self.flags
.set(ComboBoxFlags::HEIGHT_SMALL, height == ComboBoxHeight::Small);
self.flags.set(
ComboBoxFlags::HEIGHT_REGULAR,
height == ComboBoxHeight::Regular,
);
self.flags
.set(ComboBoxFlags::HEIGHT_LARGE, height == ComboBoxHeight::Large);
self.flags.set(
ComboBoxFlags::HEIGHT_LARGEST,
height == ComboBoxHeight::Largest,
);
self
}
/// Sets the combo box preview mode.
///
/// Default: `ComboBoxPreviewMode::Full`
#[inline]
pub fn preview_mode(mut self, preview_mode: ComboBoxPreviewMode) -> Self {
self.flags.set(
ComboBoxFlags::NO_ARROW_BUTTON,
preview_mode == ComboBoxPreviewMode::Label,
);
self.flags.set(
ComboBoxFlags::NO_PREVIEW,
preview_mode == ComboBoxPreviewMode::ArrowButton,
);
self
}
/// Builds this combo box, and starts appending to it.
///
/// Returns `None` if the combo box is not open and no content should be rendered.
pub fn begin<'ui>(self, _: &'ui Ui<'ui>) -> Option<ComboBoxToken<'ui>> {
let should_render = unsafe {
sys::igBeginCombo(
self.label.as_ptr(),
self.preview_value.map(ImStr::as_ptr).unwrap_or(ptr::null()),
self.flags.bits() as i32,
)
};
if should_render {
Some(ComboBoxToken {
should_end: true,
_ui: PhantomData,
})
} else {
None
}
}
/// Builds the combo box.
///
/// Note: the closure is not called if the combo box is not open
pub fn build<F: FnOnce()>(self, ui: &Ui, f: F) {
if let Some(_combo) = self.begin(ui) {
f();
}
}
}
/// Represents a combo box pushed to the window stack
pub struct ComboBoxToken<'ui> {
should_end: bool,
_ui: PhantomData<&'ui Ui<'ui>>,
}
impl<'ui> ComboBoxToken<'ui> {
/// Finishes the current combo box and pops it from the window stack
pub fn end(mut self) {
self.should_end = false;
unsafe { sys::igEndCombo() };
}
}
impl<'ui> Drop for ComboBoxToken<'ui> {
fn drop(&mut self) {
if self.should_end {
unsafe { sys::igEndCombo() };
}
}
}
/// # Convenience functions
impl<'a> ComboBox<'a> {
/// Builds a simple combo 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>,
{
let mut result = false;
let mut cb = self;
let preview_value = items.get(*current_item).map(label_fn);
if cb.preview_value.is_none() {
if let Some(preview_value) = preview_value.as_ref() {
cb = cb.preview_value(preview_value);
}
}
if let Some(_cb) = cb.begin(ui) {
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 {
*current_item = idx;
result = true;
}
}
}
result
}
/// Builds a simple combo box for choosing from a slice of strings
pub fn build_simple_string<S>(self, ui: &Ui, current_item: &mut usize, items: &[&S]) -> bool
where
S: AsRef<ImStr> + ?Sized,
{
self.build_simple(ui, current_item, items, &|&s| s.as_ref().into())
}
}

View File

@ -1,4 +1,5 @@
pub mod color_editors;
pub mod combo_box;
pub mod image;
pub mod misc;
pub mod progress_bar;