diff --git a/examples/test_window_impl.rs b/examples/test_window_impl.rs index 1856363..309890b 100644 --- a/examples/test_window_impl.rs +++ b/examples/test_window_impl.rs @@ -33,6 +33,18 @@ struct State { bg_alpha: f32, wrap_width: f32, buf: String, + item: i32, + item2: i32, + text: String, + i0: i32, + f0: f32, + vec2f: [f32;2], + vec3f: [f32;3], + vec2i: [i32;2], + vec3i: [i32;3], + col1: [f32;3], + col2: [f32;4], + selected_fish: Option, auto_resize_state: AutoResizeState, file_menu: FileMenuState } @@ -42,6 +54,10 @@ impl Default for State { let mut buf = "日本語".to_owned(); buf.extend(repeat('\0').take(32)); buf.truncate(32); + let mut text = String::with_capacity(128); + text.push_str("Hello, world!"); + let remaining = text.capacity() - text.len(); + text.extend(repeat('\0').take(remaining)); State { clear_color: (114.0 / 255.0, 144.0 / 255.0, 154.0 / 255.0, 1.0), show_app_metrics: false, @@ -64,6 +80,18 @@ impl Default for State { bg_alpha: 0.65, wrap_width: 200.0, buf: buf, + item: 0, + item2: 0, + text: text, + i0: 123, + f0: 0.001, + vec2f: [0.10, 0.20], + vec3f: [0.10, 0.20, 0.30], + vec2i: [10, 20], + vec3i: [10, 20, 30], + col1: [1.0, 0.0, 0.2], + col2: [0.4, 0.7, 0.0, 0.5], + selected_fish: None, auto_resize_state: Default::default(), file_menu: Default::default() } @@ -213,7 +241,7 @@ fn show_test_window<'a>(ui: &Ui<'a>, state: &mut State, opened: &mut bool) { ui.same_line(300.0); ui.checkbox(im_str!("no collapse"), &mut state.no_collapse); ui.checkbox(im_str!("no menu"), &mut state.no_menu); - ui.slider_f32(im_str!("bg alpha"), &mut state.bg_alpha, 0.0, 1.0).build(); + ui.slider_float(im_str!("bg alpha"), &mut state.bg_alpha, 0.0, 1.0).build(); ui.tree_node(im_str!("Style")).build(|| { // TODO: Reimplement style editor @@ -261,7 +289,7 @@ fn show_test_window<'a>(ui: &Ui<'a>, state: &mut State, opened: &mut bool) { suitable for English and possibly other languages.")); ui.spacing(); - ui.slider_f32(im_str!("Wrap width"), &mut state.wrap_width, -20.0, 600.0) + ui.slider_float(im_str!("Wrap width"), &mut state.wrap_width, -20.0, 600.0) .display_format(im_str!("%.0f")) .build(); @@ -281,6 +309,57 @@ fn show_test_window<'a>(ui: &Ui<'a>, state: &mut State, opened: &mut bool) { ui.text(im_str!("Kanjis: 日本語 (nihongo)")); ui.input_text(im_str!("UTF-8 input"), &mut state.buf).build(); }); + + ui.separator(); + ui.label_text(im_str!("label"), im_str!("Value")); + ui.combo(im_str!("combo"), &mut state.item, &[im_str!("aaaa"), im_str!("bbbb"), + im_str!("cccc"), im_str!("dddd"), im_str!("eeee")], -1); + let items = [ + im_str!("AAAA"), im_str!("BBBB"), im_str!("CCCC"), im_str!("DDDD"), + im_str!("EEEE"), im_str!("FFFF"), im_str!("GGGG"), im_str!("HHHH"), + im_str!("IIII"), im_str!("JJJJ"), im_str!("KKKK")]; + ui.combo(im_str!("combo scroll"), &mut state.item2, &items, -1); + ui.input_text(im_str!("input text"), &mut state.text).build(); + ui.input_int(im_str!("input int"), &mut state.i0).build(); + ui.input_float(im_str!("input float"), &mut state.f0) + .step(0.01).step_fast(1.0).build(); + ui.input_float3(im_str!("input float3"), &mut state.vec3f).build(); + ui.color_edit3(im_str!("color 1"), &mut state.col1).build(); + ui.color_edit4(im_str!("color 2"), &mut state.col2).build(); + + ui.tree_node(im_str!("Multi-component Widgets")).build(|| { + ui.input_float2(im_str!("input float2"), &mut state.vec2f).build(); + ui.input_int2(im_str!("input int2"), &mut state.vec2i).build(); + ui.spacing(); + + ui.input_float3(im_str!("input float3"), &mut state.vec3f).build(); + ui.input_int3(im_str!("input int3"), &mut state.vec3i).build(); + ui.spacing(); + }); + } + if ui.collapsing_header(im_str!("Popups & Modal windows")).build() { + ui.tree_node(im_str!("Popups")).build(|| { + ui.text_wrapped(im_str!("When a popup is active, it inhibits interacting with windows that are behind the popup. Clicking outside the popup closes it.")); + let names = [im_str!("Bream"), im_str!("Haddock"), im_str!("Mackerel"), im_str!("Pollock"), im_str!("Tilefish")]; + if ui.small_button(im_str!("Select..")) { + ui.open_popup(im_str!("select")); + } + ui.same_line(0.0); + ui.text( + match state.selected_fish { + Some(index) => names[index].clone(), + None => im_str!("") + }); + ui.popup(im_str!("select"), || { + ui.text(im_str!("Aquarium")); + ui.separator(); + for (index, name) in names.iter().enumerate() { + if ui.selectable(name.clone(), false, ImGuiSelectableFlags::empty(), ImVec2::new(0.0, 0.0)) { + state.selected_fish = Some(index); + } + } + }); + }); } }) } @@ -344,7 +423,7 @@ fn show_example_app_auto_resize<'a>(ui: &Ui<'a>, state: &mut AutoResizeState, op ui.text(im_str!("Window will resize every-ui to the size of its content. Note that you probably don't want to query the window size to output your content because that would create a feedback loop.")); - ui.slider_i32(im_str!("Number of lines"), &mut state.lines, 1, 20).build(); + ui.slider_int(im_str!("Number of lines"), &mut state.lines, 1, 20).build(); for i in 0 .. state.lines { ui.text(im_str!("{:2$}This is line {}", "", i, i as usize * 4)); } diff --git a/imgui-sys/src/lib.rs b/imgui-sys/src/lib.rs index 25ed36e..2d7290d 100644 --- a/imgui-sys/src/lib.rs +++ b/imgui-sys/src/lib.rs @@ -794,8 +794,8 @@ extern "C" { data: *mut c_void, items_count: c_int, height_in_items: c_int) -> bool; pub fn igColorButton(col: ImVec4, small_height: bool, outline_border: bool) -> bool; - pub fn igColorEdit3(label: *const c_char, col: [c_float; 3]) -> bool; - pub fn igColorEdit4(label: *const c_char, col: [c_float; 4], show_alpha: bool) -> bool; + pub fn igColorEdit3(label: *const c_char, col: *mut f32) -> bool; + pub fn igColorEdit4(label: *const c_char, col: *mut f32, show_alpha: bool) -> bool; pub fn igColorEditMode(mode: ImGuiColorEditMode); pub fn igPlotLines(label: *const c_char, values: *const c_float, values_count: c_int, values_offset: c_int, diff --git a/src/input.rs b/src/input.rs index 3948e54..de50f85 100644 --- a/src/input.rs +++ b/src/input.rs @@ -15,6 +15,155 @@ use super::{ ImStr }; +macro_rules! impl_text_flags { + ($InputType:ident) => { + #[inline] + pub fn flags(self, flags: ImGuiInputTextFlags) -> Self { + $InputType { + flags: flags, + .. self + } + } + + #[inline] + pub fn chars_decimal(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_CharsDecimal, value), + .. self + } + } + + #[inline] + pub fn chars_hexadecimal(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_CharsHexadecimal, value), + .. self + } + } + + #[inline] + pub fn chars_uppercase(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_CharsUppercase, value), + .. self + } + } + + #[inline] + pub fn chars_noblank(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_CharsNoBlank, value), + .. self + } + } + + #[inline] + pub fn auto_select_all(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_AutoSelectAll, value), + .. self + } + } + + #[inline] + pub fn enter_returns_true(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_EnterReturnsTrue, value), + .. self + } + } + + #[inline] + pub fn callback_completion(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_CallbackCompletion, value), + .. self + } + } + + #[inline] + pub fn callback_history(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_CallbackHistory, value), + .. self + } + } + + #[inline] + pub fn callback_always(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_CallbackAlways, value), + .. self + } + } + + #[inline] + pub fn callback_char_filter(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_CallbackCharFilter, value), + .. self + } + } + + #[inline] + pub fn allow_tab_input(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_AllowTabInput, value), + .. self + } + } + + #[inline] + pub fn no_horizontal_scroll(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_NoHorizontalScroll, value), + .. self + } + } + + #[inline] + pub fn always_insert_mode(self, value: bool) -> Self { + $InputType { + flags: self.flags.with(ImGuiInputTextFlags_AlwaysInsertMode, value), + .. self + } + } + + } +} + +macro_rules! impl_step_params { + ($InputType:ident, $Value:ty) => { + #[inline] + pub fn step(self, value: $Value) -> Self { + $InputType { + step: value, + .. self + } + } + + #[inline] + pub fn step_fast(self, value: $Value) -> Self { + $InputType { + step_fast: value, + .. self + } + } + } +} + +macro_rules! impl_precision_params { + ($InputType:ident) => { + #[inline] + pub fn decimal_precision(self, value: i32) -> Self { + $InputType { + decimal_precision: value, + .. self + } + } + } +} + #[must_use] pub struct InputText<'ui, 'p> { label: ImStr<'p>, @@ -33,117 +182,7 @@ impl<'ui, 'p> InputText<'ui, 'p> { } } - #[inline] - pub fn flags(self, flags: ImGuiInputTextFlags) -> Self { - InputText { - flags: flags, - .. self - } - } - - #[inline] - pub fn chars_decimal(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_CharsDecimal, value), - .. self - } - } - - #[inline] - pub fn chars_hexadecimal(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_CharsHexadecimal, value), - .. self - } - } - - #[inline] - pub fn chars_uppercase(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_CharsUppercase, value), - .. self - } - } - - #[inline] - pub fn chars_noblank(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_CharsNoBlank, value), - .. self - } - } - - #[inline] - pub fn auto_select_all(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_AutoSelectAll, value), - .. self - } - } - - #[inline] - pub fn enter_returns_true(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_EnterReturnsTrue, value), - .. self - } - } - - #[inline] - pub fn callback_completion(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_CallbackCompletion, value), - .. self - } - } - - #[inline] - pub fn callback_history(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_CallbackHistory, value), - .. self - } - } - - #[inline] - pub fn callback_always(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_CallbackAlways, value), - .. self - } - } - - #[inline] - pub fn callback_char_filter(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_CallbackCharFilter, value), - .. self - } - } - - #[inline] - pub fn allow_tab_input(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_AllowTabInput, value), - .. self - } - } - - #[inline] - pub fn no_horizontal_scroll(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_NoHorizontalScroll, value), - .. self - } - } - - #[inline] - pub fn always_insert_mode(self, value: bool) -> Self { - InputText { - flags: self.flags.with(ImGuiInputTextFlags_AlwaysInsertMode, value), - .. self - } - } + impl_text_flags!(InputText); // TODO: boxed closure...? // pub fn callback(self) -> Self { } @@ -161,3 +200,214 @@ impl<'ui, 'p> InputText<'ui, 'p> { } } } + +#[must_use] +pub struct InputInt<'ui, 'p> { + label: ImStr<'p>, + value: &'p mut i32, + step: i32, + step_fast: i32, + flags: ImGuiInputTextFlags, + _phantom: PhantomData<&'ui Ui<'ui>> +} + +impl<'ui, 'p> InputInt<'ui, 'p> { + pub fn new(label: ImStr<'p>, value: &'p mut i32) -> Self { + InputInt { + label: label, + value: value, + step: 1, + step_fast: 100, + flags: ImGuiInputTextFlags::empty(), + _phantom: PhantomData + } + } + + pub fn build(self) -> bool { + unsafe { + imgui_sys::igInputInt( + self.label.as_ptr(), + self.value as *mut i32, + self.step, + self.step_fast, + self.flags) + } + } + + impl_step_params!(InputInt, i32); + impl_text_flags!(InputInt); +} + +#[must_use] +pub struct InputFloat<'ui, 'p> { + label: ImStr<'p>, + value: &'p mut f32, + step: f32, + step_fast: f32, + decimal_precision: i32, + flags: ImGuiInputTextFlags, + _phantom: PhantomData<&'ui Ui<'ui>> +} + +impl<'ui, 'p> InputFloat<'ui, 'p> { + pub fn new(label: ImStr<'p>, value: &'p mut f32) -> Self { + InputFloat { + label: label, + value: value, + step: 0.0, + step_fast: 0.0, + decimal_precision: -1, + flags: ImGuiInputTextFlags::empty(), + _phantom: PhantomData + } + } + + pub fn build(self) -> bool { + unsafe { + imgui_sys::igInputFloat( + self.label.as_ptr(), + self.value as *mut f32, + self.step, + self.step_fast, + self.decimal_precision, + self.flags) + } + } + + impl_step_params!(InputFloat, f32); + impl_precision_params!(InputFloat); + impl_text_flags!(InputFloat); +} + +macro_rules! impl_input_floatn { + ($InputFloatN:ident, $N:expr, $igInputFloatN:ident) => { + #[must_use] + pub struct $InputFloatN<'ui, 'p> { + label: ImStr<'p>, + value: &'p mut [f32;$N], + decimal_precision: i32, + flags: ImGuiInputTextFlags, + _phantom: PhantomData<&'ui Ui<'ui>> + } + + impl<'ui, 'p> $InputFloatN<'ui, 'p> { + pub fn new(label: ImStr<'p>, value: &'p mut [f32;$N]) -> Self { + $InputFloatN { + label: label, + value: value, + decimal_precision: -1, + flags: ImGuiInputTextFlags::empty(), + _phantom: PhantomData + } + } + + pub fn build(self) -> bool { + unsafe { + imgui_sys::$igInputFloatN( + self.label.as_ptr(), + self.value.as_mut_ptr(), + self.decimal_precision, + self.flags) + } + } + + impl_precision_params!($InputFloatN); + impl_text_flags!($InputFloatN); + } + } +} + +impl_input_floatn!(InputFloat2, 2, igInputFloat2); +impl_input_floatn!(InputFloat3, 3, igInputFloat3); +impl_input_floatn!(InputFloat4, 4, igInputFloat4); + +macro_rules! impl_input_intn { + ($InputIntN:ident, $N:expr, $igInputIntN:ident) => { + #[must_use] + pub struct $InputIntN<'ui, 'p> { + label: ImStr<'p>, + value: &'p mut [i32;$N], + flags: ImGuiInputTextFlags, + _phantom: PhantomData<&'ui Ui<'ui>> + } + + impl<'ui, 'p> $InputIntN<'ui, 'p> { + pub fn new(label: ImStr<'p>, value: &'p mut [i32;$N]) -> Self { + $InputIntN { + label: label, + value: value, + flags: ImGuiInputTextFlags::empty(), + _phantom: PhantomData + } + } + + pub fn build(self) -> bool { + unsafe { + imgui_sys::$igInputIntN( + self.label.as_ptr(), + self.value.as_mut_ptr(), + self.flags) + } + } + + impl_text_flags!($InputIntN); + } + } +} + +impl_input_intn!(InputInt2, 2, igInputInt2); +impl_input_intn!(InputInt3, 3, igInputInt3); +impl_input_intn!(InputInt4, 4, igInputInt4); + +#[must_use] +pub struct ColorEdit3<'ui, 'p> { + label: ImStr<'p>, + value: &'p mut [f32;3], + _phantom: PhantomData<&'ui Ui<'ui>> +} + +impl<'ui, 'p> ColorEdit3<'ui, 'p> { + pub fn new(label: ImStr<'p>, value: &'p mut [f32;3]) -> Self { + ColorEdit3 { + label: label, + value: value, + _phantom: PhantomData + } + } + + pub fn build(self) -> bool { + unsafe { + imgui_sys::igColorEdit3( + self.label.as_ptr(), + self.value.as_mut_ptr()) + } + } +} + +#[must_use] +pub struct ColorEdit4<'ui, 'p> { + label: ImStr<'p>, + value: &'p mut [f32;4], + show_alpha: bool, + _phantom: PhantomData<&'ui Ui<'ui>> +} + +impl<'ui, 'p> ColorEdit4<'ui, 'p> { + pub fn new(label: ImStr<'p>, value: &'p mut [f32;4]) -> Self { + ColorEdit4 { + label: label, + value: value, + show_alpha: true, + _phantom: PhantomData + } + } + + pub fn build(self) -> bool { + unsafe { + imgui_sys::igColorEdit4( + self.label.as_ptr(), + self.value.as_mut_ptr(), + self.show_alpha) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index fed01aa..5678dad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,8 @@ pub use imgui_sys::{ ImGuiInputTextFlags_NoHorizontalScroll, ImGuiInputTextFlags_AlwaysInsertMode, ImGuiInputTextFlags_ReadOnly, ImGuiInputTextFlags_Password, + ImGuiSelectableFlags, + ImGuiSelectableFlags_DontClosePopups, ImGuiSelectableFlags_SpanAllColumns, ImGuiSetCond, ImGuiSetCond_Always, ImGuiSetCond_Once, ImGuiSetCond_FirstUseEver, ImGuiSetCond_Appearing, @@ -40,7 +42,12 @@ pub use imgui_sys::{ ImVec2, ImVec4, ImGuiKey }; -pub use input::{InputText}; +pub use input::{ + ColorEdit3, ColorEdit4, + InputFloat, InputFloat2, InputFloat3, InputFloat4, + InputInt, InputInt2, InputInt3, InputInt4, + InputText +}; pub use menus::{Menu, MenuItem}; pub use sliders::{SliderFloat, SliderInt}; pub use trees::{TreeNode}; @@ -485,18 +492,48 @@ impl<'ui> Ui<'ui> { // Widgets: Input impl<'ui> Ui<'ui> { + pub fn color_edit3<'p>(&self, label: ImStr<'p>, value: &'p mut [f32;3]) -> ColorEdit3<'ui, 'p> { + ColorEdit3::new(label, value) + } + pub fn color_edit4<'p>(&self, label: ImStr<'p>, value: &'p mut [f32;4]) -> ColorEdit4<'ui, 'p> { + ColorEdit4::new(label, value) + } pub fn input_text<'p>(&self, label: ImStr<'p>, buf: &'p mut str) -> InputText<'ui, 'p> { InputText::new(label, buf) } + pub fn input_float<'p>(&self, label: ImStr<'p>, value: &'p mut f32) -> InputFloat<'ui, 'p> { + InputFloat::new(label, value) + } + pub fn input_float2<'p>(&self, label: ImStr<'p>, value: &'p mut [f32;2]) -> InputFloat2<'ui, 'p> { + InputFloat2::new(label, value) + } + pub fn input_float3<'p>(&self, label: ImStr<'p>, value: &'p mut [f32;3]) -> InputFloat3<'ui, 'p> { + InputFloat3::new(label, value) + } + pub fn input_float4<'p>(&self, label: ImStr<'p>, value: &'p mut [f32;4]) -> InputFloat4<'ui, 'p> { + InputFloat4::new(label, value) + } + pub fn input_int<'p>(&self, label: ImStr<'p>, value: &'p mut i32) -> InputInt<'ui, 'p> { + InputInt::new(label, value) + } + pub fn input_int2<'p>(&self, label: ImStr<'p>, value: &'p mut [i32;2]) -> InputInt2<'ui, 'p> { + InputInt2::new(label, value) + } + pub fn input_int3<'p>(&self, label: ImStr<'p>, value: &'p mut [i32;3]) -> InputInt3<'ui, 'p> { + InputInt3::new(label, value) + } + pub fn input_int4<'p>(&self, label: ImStr<'p>, value: &'p mut [i32;4]) -> InputInt4<'ui, 'p> { + InputInt4::new(label, value) + } } // Widgets: Sliders impl<'ui> Ui<'ui> { - pub fn slider_f32<'p>(&self, label: ImStr<'p>, + pub fn slider_float<'p>(&self, label: ImStr<'p>, value: &'p mut f32, min: f32, max: f32) -> SliderFloat<'ui, 'p> { SliderFloat::new(label, value, min, max) } - pub fn slider_i32<'p>(&self, label: ImStr<'p>, + pub fn slider_int<'p>(&self, label: ImStr<'p>, value: &'p mut i32, min: i32, max: i32) -> SliderInt<'ui, 'p> { SliderInt::new(label, value, min, max) } @@ -509,6 +546,14 @@ impl<'ui> Ui<'ui> { } } +// Widgets: Selectable / Lists +impl<'ui> Ui<'ui> { + pub fn selectable<'p>(&self, label: ImStr<'p>, selected: bool, flags: ImGuiSelectableFlags, + size: ImVec2) -> bool { + unsafe { imgui_sys::igSelectable(label.as_ptr(), selected, flags, size) } + } +} + // Widgets: Menus impl<'ui> Ui<'ui> { pub fn main_menu_bar(&self, f: F) where F: FnOnce() { @@ -529,6 +574,20 @@ impl<'ui> Ui<'ui> { pub fn menu_item<'p>(&self, label: ImStr<'p>) -> MenuItem<'ui, 'p> { MenuItem::new(label) } } +// Widgets: Popups +impl<'ui> Ui<'ui> { + pub fn open_popup<'p>(&self, str_id: ImStr<'p>) { + unsafe { imgui_sys::igOpenPopup(str_id.as_ptr()) }; + } + pub fn popup<'p, F>(&self, str_id: ImStr<'p>, f: F) where F: FnOnce() { + let render = unsafe { imgui_sys::igBeginPopup(str_id.as_ptr()) }; + if render { + f(); + unsafe { imgui_sys::igEndPopup() }; + } + } +} + //Widgets: Combos impl<'ui> Ui<'ui> { pub fn combo<'p>(&self,