diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 2e66d07..cfea4e0 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -9,6 +9,7 @@ - `Ui::is_window_focused` - `Ui::is_root_window_focused` - `Ui::is_child_window_focused` +- `Ui::popup_modal` - `imgui-glutin-support` crate ### Changed diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index cbbeb21..979f840 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -49,6 +49,9 @@ struct State { radio_button: i32, color_edit: ColorEditState, custom_rendering: CustomRenderingState, + dont_ask_me_next_time: bool, + stacked_modals_item: i32, + stacked_modals_color: [f32; 4], } impl Default for State { @@ -101,6 +104,9 @@ impl Default for State { radio_button: 0, color_edit: ColorEditState::default(), custom_rendering: Default::default(), + dont_ask_me_next_time: false, + stacked_modals_item: 0, + stacked_modals_color: [0.4, 0.7, 0.0, 0.5], } } } @@ -642,6 +648,61 @@ CTRL+click on individual component to input value.\n", } }); }); + + ui.tree_node(im_str!("Modals")).build(|| { + ui.text_wrapped(im_str!( + "Modal windows are like popups but the user cannot close \ + them by clicking outside the window." + )); + + if ui.button(im_str!("Delete.."), (0.0, 0.0)) { + ui.open_popup(im_str!("Delete?")); + } + ui.popup_modal(im_str!("Delete?")).always_auto_resize(true).build(|| { + ui.text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); + ui.separator(); + ui.with_style_var(StyleVar::FramePadding(ImVec2::new(0.0, 0.0)), || { + ui.checkbox(im_str!("Don't ask me next time"), &mut state.dont_ask_me_next_time); + + if ui.button(im_str!("OK"), (120.0, 0.0)) { + ui.close_current_popup(); + } + ui.same_line(0.0); + if ui.button(im_str!("Cancel"), (120.0, 0.0)) { + ui.close_current_popup(); + } + }); + }); + + if ui.button(im_str!("Stacked modals.."), (0.0, 0.0)) { + ui.open_popup(im_str!("Stacked 1")); + } + ui.popup_modal(im_str!("Stacked 1")).build(|| { + ui.text( + "Hello from Stacked The First\n\ + Using style.Colors[ImGuiCol_ModalWindowDarkening] for darkening." + ); + + 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); + + ui.color_edit(im_str!("color"), &mut state.stacked_modals_color).build(); + + if ui.button(im_str!("Add another modal.."), (0.0, 0.0)) { + ui.open_popup(im_str!("Stacked 2")) ; + } + ui.popup_modal(im_str!("Stacked 2")).build(|| { + ui.text("Hello from Stacked The Second"); + if ui.button(im_str!("Close"), (0.0, 0.0)) { + ui.close_current_popup(); + } + }); + + if ui.button(im_str!("Close"), (0.0, 0.0)) { + ui.close_current_popup(); + } + }); + }); } }) } diff --git a/src/lib.rs b/src/lib.rs index 08d86bd..dab45db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ pub use input::{ pub use menus::{Menu, MenuItem}; pub use plothistogram::PlotHistogram; pub use plotlines::PlotLines; +pub use popup_modal::PopupModal; pub use progressbar::ProgressBar; pub use sliders::{ SliderFloat, SliderFloat2, SliderFloat3, SliderFloat4, SliderInt, SliderInt2, SliderInt3, @@ -49,6 +50,7 @@ mod input; mod menus; mod plothistogram; mod plotlines; +mod popup_modal; mod progressbar; mod sliders; mod string; @@ -1204,6 +1206,28 @@ impl<'ui> Ui<'ui> { unsafe { sys::igEndPopup() }; } } + /// Create a modal pop-up. + /// + /// # Example + /// ```rust,no_run + /// # use imgui::*; + /// # let mut imgui = ImGui::init(); + /// # let ui = imgui.frame(FrameSize::new(100.0, 100.0, 1.0), 0.1); + /// if ui.button(im_str!("Show modal"), (0.0, 0.0)) { + /// ui.open_popup(im_str!("modal")); + /// } + /// ui.popup_modal(im_str!("modal")).build(|| { + /// ui.text("Content of my modal"); + /// if ui.button(im_str!("OK"), (0.0, 0.0)) { + /// ui.close_current_popup(); + /// } + /// }); + /// ``` + pub fn popup_modal<'p>(&self, str_id: &'p ImStr) -> PopupModal<'ui, 'p> { + PopupModal::new(self, str_id) + } + /// Close a popup. Should be called within the closure given as argument to + /// [`Ui::popup`] or [`Ui::popup_modal`]. pub fn close_current_popup(&self) { unsafe { sys::igCloseCurrentPopup() }; } diff --git a/src/popup_modal.rs b/src/popup_modal.rs new file mode 100644 index 0000000..86f4cba --- /dev/null +++ b/src/popup_modal.rs @@ -0,0 +1,120 @@ +use std::marker::PhantomData; +use std::ptr; + +use super::{ImGuiWindowFlags, ImStr, Ui}; + +use sys; + +/// Created by call to [`Ui::popup_modal`]. +#[must_use] +pub struct PopupModal<'ui, 'p> { + label: &'p ImStr, + opened: Option<&'p mut bool>, + flags: ImGuiWindowFlags, + _phantom: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui, 'p> PopupModal<'ui, 'p> { + pub fn new(_: &Ui<'ui>, label: &'p ImStr) -> Self { + PopupModal { + label, + opened: None, + flags: ImGuiWindowFlags::empty(), + _phantom: PhantomData, + } + } + /// Pass a mutable boolean which will be updated to refer to the current + /// "open" state of the modal. + pub fn opened(mut self, opened: &'p mut bool) -> Self { + self.opened = Some(opened); + self + } + pub fn flags(mut self, flags: ImGuiWindowFlags) -> Self { + self.flags = flags; + self + } + pub fn title_bar(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::NoTitleBar, !value); + self + } + pub fn resizable(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::NoResize, !value); + self + } + pub fn movable(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::NoMove, !value); + self + } + pub fn scroll_bar(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::NoScrollbar, !value); + self + } + pub fn scrollable(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::NoScrollWithMouse, !value); + self + } + pub fn collapsible(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::NoCollapse, !value); + self + } + pub fn always_auto_resize(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::AlwaysAutoResize, value); + self + } + pub fn save_settings(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::NoSavedSettings, !value); + self + } + pub fn inputs(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::NoInputs, !value); + self + } + pub fn menu_bar(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::MenuBar, value); + self + } + pub fn horizontal_scrollbar(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::HorizontalScrollbar, value); + self + } + pub fn no_focus_on_appearing(mut self, value: bool) -> Self { + self.flags.set(ImGuiWindowFlags::NoFocusOnAppearing, value); + self + } + pub fn no_bring_to_front_on_focus(mut self, value: bool) -> Self { + self.flags + .set(ImGuiWindowFlags::NoBringToFrontOnFocus, value); + self + } + pub fn always_vertical_scrollbar(mut self, value: bool) -> Self { + self.flags + .set(ImGuiWindowFlags::AlwaysVerticalScrollbar, value); + self + } + pub fn always_horizontal_scrollbar(mut self, value: bool) -> Self { + self.flags + .set(ImGuiWindowFlags::AlwaysHorizontalScrollbar, value); + self + } + pub fn always_use_window_padding(mut self, value: bool) -> Self { + self.flags + .set(ImGuiWindowFlags::AlwaysUseWindowPadding, value); + self + } + /// Consume and draw the PopupModal. + pub fn build(self, f: F) { + let render = unsafe { + sys::igBeginPopupModal( + self.label.as_ptr(), + self.opened + .map(|x| x as *mut bool) + .unwrap_or(ptr::null_mut()), + self.flags, + ) + }; + if render { + f(); + unsafe { sys::igEndPopup() }; + } + } +}