diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index c0eac05..8ebed2c 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -19,6 +19,7 @@ - Redesigned selectable API - Redesigned slider API. Generic scalar sliders support all main data types and replace previous individual sliders (int, int2, int3, int4, etc...) +- Redesigned menu 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 487a107..03a7ab2 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -285,57 +285,43 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { window.build(ui, || { let _token = ui.push_item_width(-140.0); ui.text(format!("dear imgui says hello. ({})", imgui::dear_imgui_version())); - ui.menu_bar(|| { - ui.menu(im_str!("Menu")).build(|| { + if let Some(_) = ui.menu_bar() { + if let Some(_) = ui.menu(im_str!("Menu"), true) { show_example_menu_file(ui, &mut state.file_menu); - }); - ui.menu(im_str!("Examples")).build(|| { - ui.menu_item(im_str!("Main menu bar")) - .selected(&mut state.show_app_main_menu_bar) - .build(); - ui.menu_item(im_str!("Console")) - .selected(&mut state.show_app_console) - .build(); - ui.menu_item(im_str!("Log")) - .selected(&mut state.show_app_log) - .build(); - ui.menu_item(im_str!("Simple layout")) - .selected(&mut state.show_app_layout) - .build(); - ui.menu_item(im_str!("Property editor")) - .selected(&mut state.show_app_property_editor) - .build(); - ui.menu_item(im_str!("Long text display")) - .selected(&mut state.show_app_long_text) - .build(); - ui.menu_item(im_str!("Auto-resizing window")) - .selected(&mut state.show_app_auto_resize) - .build(); - ui.menu_item(im_str!("Constrained-resizing window")) - .selected(&mut state.show_app_constrained_resize) - .build(); - ui.menu_item(im_str!("Simple overlay")) - .selected(&mut state.show_app_fixed_overlay) - .build(); - ui.menu_item(im_str!("Manipulating window title")) - .selected(&mut state.show_app_manipulating_window_title) - .build(); - ui.menu_item(im_str!("Custom rendering")) - .selected(&mut state.show_app_custom_rendering) - .build(); - }); - ui.menu(im_str!("Help")).build(|| { - ui.menu_item(im_str!("Metrics")) - .selected(&mut state.show_app_metrics) - .build(); - ui.menu_item(im_str!("Style Editor")) - .selected(&mut state.show_app_style_editor) - .build(); - ui.menu_item(im_str!("About ImGui")) - .selected(&mut state.show_app_about) - .build(); - }); - }); + } + if let Some(_) = ui.menu(im_str!("Examples"), true) { + MenuItem::new(im_str!("Main menu bar")) + .build_with_ref(ui, &mut state.show_app_main_menu_bar); + MenuItem::new(im_str!("Console")) + .build_with_ref(ui, &mut state.show_app_console); + MenuItem::new(im_str!("Log")) + .build_with_ref(ui, &mut state.show_app_log); + MenuItem::new(im_str!("Simple layout")) + .build_with_ref(ui, &mut state.show_app_layout); + MenuItem::new(im_str!("Property editor")) + .build_with_ref(ui, &mut state.show_app_property_editor); + MenuItem::new(im_str!("Long text display")) + .build_with_ref(ui, &mut state.show_app_long_text); + MenuItem::new(im_str!("Auto-resizing window")) + .build_with_ref(ui, &mut state.show_app_auto_resize); + MenuItem::new(im_str!("Constrained-resizing window")) + .build_with_ref(ui, &mut state.show_app_constrained_resize); + MenuItem::new(im_str!("Simple overlay")) + .build_with_ref(ui, &mut state.show_app_fixed_overlay); + MenuItem::new(im_str!("Manipulating window title")) + .build_with_ref(ui, &mut state.show_app_manipulating_window_title); + MenuItem::new(im_str!("Custom rendering")) + .build_with_ref(ui, &mut state.show_app_custom_rendering); + } + if let Some(_) = ui.menu(im_str!("Help"), true) { + MenuItem::new(im_str!("Metrics")) + .build_with_ref(ui, &mut state.show_app_metrics); + MenuItem::new(im_str!("Style Editor")) + .build_with_ref(ui, &mut state.show_app_style_editor); + MenuItem::new(im_str!("About ImGui")) + .build_with_ref(ui, &mut state.show_app_about); + } + } ui.spacing(); if ui.collapsing_header(im_str!("Help")).build() { ui.text_wrapped(im_str!( @@ -688,59 +674,59 @@ CTRL+click on individual component to input value.\n", } fn show_example_app_main_menu_bar<'a>(ui: &Ui<'a>, state: &mut State) { - ui.main_menu_bar(|| { - ui.menu(im_str!("File")).build(|| { + if let Some(_) = ui.main_menu_bar() { + if let Some(_) = ui.menu(im_str!("File"), true) { show_example_menu_file(ui, &mut state.file_menu); - }); - ui.menu(im_str!("Edit")).build(|| { - ui.menu_item(im_str!("Undo")) + } + if let Some(_) = ui.menu(im_str!("Edit"), true) { + MenuItem::new(im_str!("Undo")) .shortcut(im_str!("CTRL+Z")) - .build(); - ui.menu_item(im_str!("Redo")) + .build(ui); + MenuItem::new(im_str!("Redo")) .shortcut(im_str!("CTRL+Y")) .enabled(false) - .build(); + .build(ui); ui.separator(); - ui.menu_item(im_str!("Cut")) + MenuItem::new(im_str!("Cut")) .shortcut(im_str!("CTRL+X")) - .build(); - ui.menu_item(im_str!("Copy")) + .build(ui); + MenuItem::new(im_str!("Copy")) .shortcut(im_str!("CTRL+C")) - .build(); - ui.menu_item(im_str!("Paste")) + .build(ui); + MenuItem::new(im_str!("Paste")) .shortcut(im_str!("CTRL+V")) - .build(); - }); - }); + .build(ui); + } + } } fn show_example_menu_file<'a>(ui: &Ui<'a>, state: &mut FileMenuState) { - ui.menu_item(im_str!("(dummy menu)")).enabled(false).build(); - ui.menu_item(im_str!("New")).build(); - ui.menu_item(im_str!("Open")) + MenuItem::new(im_str!("(dummy menu)")) + .enabled(false) + .build(ui); + MenuItem::new(im_str!("New")).build(ui); + MenuItem::new(im_str!("Open")) .shortcut(im_str!("Ctrl+O")) - .build(); - ui.menu(im_str!("Open Recent")).build(|| { - ui.menu_item(im_str!("fish_hat.c")).build(); - ui.menu_item(im_str!("fish_hat.inl")).build(); - ui.menu_item(im_str!("fish_hat.h")).build(); - ui.menu(im_str!("More..")).build(|| { - ui.menu_item(im_str!("Hello")).build(); - ui.menu_item(im_str!("Sailor")).build(); - ui.menu(im_str!("Recurse..")).build(|| { + .build(ui); + if let Some(_) = ui.menu(im_str!("Open Recent"), true) { + MenuItem::new(im_str!("fish_hat.c")).build(ui); + MenuItem::new(im_str!("fish_hat.inl")).build(ui); + MenuItem::new(im_str!("fish_hat.h")).build(ui); + if let Some(_) = ui.menu(im_str!("More.."), true) { + MenuItem::new(im_str!("Hello")).build(ui); + MenuItem::new(im_str!("Sailor")).build(ui); + if let Some(_) = ui.menu(im_str!("Recurse.."), true) { show_example_menu_file(ui, state); - }); - }); - }); - ui.menu_item(im_str!("Save")) + } + } + } + MenuItem::new(im_str!("Save")) .shortcut(im_str!("Ctrl+S")) - .build(); - ui.menu_item(im_str!("Save As..")).build(); + .build(ui); + MenuItem::new(im_str!("Save As..")).build(ui); ui.separator(); - ui.menu(im_str!("Options")).build(|| { - ui.menu_item(im_str!("Enabled")) - .selected(&mut state.enabled) - .build(); + if let Some(_) = ui.menu(im_str!("Options"), true) { + MenuItem::new(im_str!("Enabled")).build_with_ref(ui, &mut state.enabled); ChildWindow::new("child") .size([0.0, 60.0]) .border(true) @@ -757,19 +743,19 @@ fn show_example_menu_file<'a>(ui: &Ui<'a>, state: &mut FileMenuState) { let items = [im_str!("Yes"), im_str!("No"), im_str!("Maybe")]; 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(|| { + } + if let Some(_) = ui.menu(im_str!("Colors"), true) { for &col in StyleColor::VARIANTS.iter() { - ui.menu_item(&im_str!("{:?}", col)).build(); + MenuItem::new(&im_str!("{:?}", col)).build(ui); } - }); - ui.menu(im_str!("Disabled")).enabled(false).build(|| { + } + if let Some(_) = ui.menu(im_str!("Disabled"), false) { unreachable!(); - }); - ui.menu_item(im_str!("Checked")).selected(&mut true).build(); - ui.menu_item(im_str!("Quit")) + } + MenuItem::new(im_str!("Checked")).selected(true).build(ui); + MenuItem::new(im_str!("Quit")) .shortcut(im_str!("Alt+F4")) - .build(); + .build(ui); } fn show_example_app_auto_resize(ui: &Ui, state: &mut AutoResizeState, opened: &mut bool) { diff --git a/src/legacy.rs b/src/legacy.rs index 41d8f54..e34afa9 100644 --- a/src/legacy.rs +++ b/src/legacy.rs @@ -7,6 +7,7 @@ use crate::string::ImStr; use crate::widget::color_editors::*; use crate::widget::combo_box::*; use crate::widget::image::{Image, ImageButton}; +use crate::widget::menu::*; use crate::widget::progress_bar::ProgressBar; use crate::widget::selectable::*; use crate::window::{Window, WindowFlags, WindowFocusedFlags}; @@ -367,3 +368,10 @@ impl<'ui> Ui<'ui> { unsafe { sys::igSelectable(label.as_ptr(), selected, flags.bits() as i32, size.into()) } } } + +impl<'ui> Ui<'ui> { + #[deprecated(since = "0.2.0", note = "use imgui::MenuItem::new(...) instead")] + pub fn menu_item<'a>(&self, label: &'a ImStr) -> MenuItem<'a> { + MenuItem::new(label) + } +} diff --git a/src/lib.rs b/src/lib.rs index 96ce08c..c386520 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,6 @@ pub use self::input_widget::{ }; pub use self::io::*; pub use self::legacy::*; -pub use self::menus::{Menu, MenuItem}; pub use self::plothistogram::PlotHistogram; pub use self::plotlines::PlotLines; pub use self::popup_modal::PopupModal; @@ -41,6 +40,7 @@ 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::menu::*; pub use self::widget::progress_bar::*; pub use self::widget::selectable::*; pub use self::widget::slider::*; @@ -60,7 +60,6 @@ pub mod internal; mod io; mod layout; mod legacy; -mod menus; mod plothistogram; mod plotlines; mod popup_modal; @@ -393,36 +392,6 @@ impl<'ui> Ui<'ui> { } } -// Widgets: Menus -impl<'ui> Ui<'ui> { - pub fn main_menu_bar(&self, f: F) - where - F: FnOnce(), - { - let render = unsafe { sys::igBeginMainMenuBar() }; - if render { - f(); - unsafe { sys::igEndMainMenuBar() }; - } - } - pub fn menu_bar(&self, f: F) - where - F: FnOnce(), - { - let render = unsafe { sys::igBeginMenuBar() }; - if render { - f(); - unsafe { sys::igEndMenuBar() }; - } - } - pub fn menu<'p>(&self, label: &'p ImStr) -> Menu<'ui, 'p> { - Menu::new(self, label) - } - pub fn menu_item<'p>(&self, label: &'p ImStr) -> MenuItem<'ui, 'p> { - MenuItem::new(self, label) - } -} - // Widgets: Popups impl<'ui> Ui<'ui> { pub fn open_popup<'p>(&self, str_id: &'p ImStr) { diff --git a/src/menus.rs b/src/menus.rs deleted file mode 100644 index 84d6d46..0000000 --- a/src/menus.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::marker::PhantomData; -use std::ptr; -use sys; - -use super::{ImStr, Ui}; - -#[must_use] -pub struct Menu<'ui, 'p> { - label: &'p ImStr, - enabled: bool, - _phantom: PhantomData<&'ui Ui<'ui>>, -} - -impl<'ui, 'p> Menu<'ui, 'p> { - pub fn new(_: &Ui<'ui>, label: &'p ImStr) -> Self { - Menu { - label, - enabled: true, - _phantom: PhantomData, - } - } - #[inline] - pub fn enabled(mut self, enabled: bool) -> Self { - self.enabled = enabled; - self - } - pub fn build(self, f: F) { - let render = unsafe { sys::igBeginMenu(self.label.as_ptr(), self.enabled) }; - if render { - f(); - unsafe { sys::igEndMenu() }; - } - } -} - -#[must_use] -pub struct MenuItem<'ui, 'p> { - label: &'p ImStr, - shortcut: Option<&'p ImStr>, - selected: Option<&'p mut bool>, - enabled: bool, - _phantom: PhantomData<&'ui Ui<'ui>>, -} - -impl<'ui, 'p> MenuItem<'ui, 'p> { - pub fn new(_: &Ui<'ui>, label: &'p ImStr) -> Self { - MenuItem { - label, - shortcut: None, - selected: None, - enabled: true, - _phantom: PhantomData, - } - } - #[inline] - pub fn shortcut(mut self, shortcut: &'p ImStr) -> Self { - self.shortcut = Some(shortcut); - self - } - #[inline] - pub fn selected(mut self, selected: &'p mut bool) -> Self { - self.selected = Some(selected); - self - } - #[inline] - pub fn enabled(mut self, enabled: bool) -> Self { - self.enabled = enabled; - self - } - pub fn build(self) -> bool { - let label = self.label.as_ptr(); - let shortcut = self.shortcut.map(|x| x.as_ptr()).unwrap_or(ptr::null()); - let selected = self - .selected - .map(|x| x as *mut bool) - .unwrap_or(ptr::null_mut()); - let enabled = self.enabled; - unsafe { sys::igMenuItemBoolPtr(label, shortcut, selected, enabled) } - } -} diff --git a/src/widget/menu.rs b/src/widget/menu.rs new file mode 100644 index 0000000..5ce4948 --- /dev/null +++ b/src/widget/menu.rs @@ -0,0 +1,142 @@ +use std::marker::PhantomData; +use std::ptr; + +use crate::string::ImStr; +use crate::sys; +use crate::Ui; + +/// # Widgets: Menus +impl<'ui> Ui<'ui> { + /// Creates and starts appending to a full-screen menu bar. + /// + /// Returns `None` if the menu bar is not visible and no content should be rendered. + pub fn main_menu_bar<'a>(&'a self) -> Option> { + match unsafe { sys::igBeginMainMenuBar() } { + true => Some(MainMenuBarToken { _ui: PhantomData }), + false => None, + } + } + /// Creates and starts appending to the menu bar of the current window. + /// + /// Returns `None` if the menu bar is not visible and no content should be rendered. + pub fn menu_bar<'a>(&'a self) -> Option> { + match unsafe { sys::igBeginMenuBar() } { + true => Some(MenuBarToken { _ui: PhantomData }), + false => None, + } + } + /// Creates and starts appending to a sub-menu entry. + /// + /// Returns `None` if the menu is not visible and no content should be rendered. + pub fn menu<'a>(&'a self, label: &ImStr, enabled: bool) -> Option> { + match unsafe { sys::igBeginMenu(label.as_ptr(), enabled) } { + true => Some(MenuToken { _ui: PhantomData }), + false => None, + } + } +} + +/// Builder for a menu item. +#[derive(Copy, Clone, Debug)] +#[must_use] +pub struct MenuItem<'a> { + label: &'a ImStr, + shortcut: Option<&'a ImStr>, + selected: bool, + enabled: bool, +} + +impl<'a> MenuItem<'a> { + /// Construct a new menu item builder. + pub fn new(label: &ImStr) -> MenuItem { + MenuItem { + label, + shortcut: None, + selected: false, + enabled: true, + } + } + /// Sets the menu item shortcut. + /// + /// Shortcuts are displayed for convenience only and are not automatically handled. + #[inline] + pub fn shortcut(mut self, shortcut: &'a ImStr) -> Self { + self.shortcut = Some(shortcut); + self + } + /// Sets the selected state of the menu item. + /// + /// Default: false + #[inline] + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + /// Enables/disables the menu item. + /// + /// Default: enabled + #[inline] + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + /// Builds the menu item. + /// + /// Returns true if the menu item is activated. + pub fn build(self, _: &Ui) -> bool { + unsafe { + sys::igMenuItemBool( + self.label.as_ptr(), + self.shortcut.map(ImStr::as_ptr).unwrap_or(ptr::null()), + self.selected, + self.enabled, + ) + } + } +} + +/// # Convenience functions +impl<'a> MenuItem<'a> { + /// Builds the menu item 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 + } + } +} + +/// Represents a main menu bar +pub struct MainMenuBarToken<'ui> { + _ui: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui> Drop for MainMenuBarToken<'ui> { + fn drop(&mut self) { + unsafe { sys::igEndMainMenuBar() }; + } +} + +/// Represents a menu bar +pub struct MenuBarToken<'ui> { + _ui: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui> Drop for MenuBarToken<'ui> { + fn drop(&mut self) { + unsafe { sys::igEndMenuBar() }; + } +} + +/// Represents a menu +pub struct MenuToken<'ui> { + _ui: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui> Drop for MenuToken<'ui> { + fn drop(&mut self) { + unsafe { sys::igEndMenu() }; + } +} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index a10d0a2..aeff8ea 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -1,6 +1,7 @@ pub mod color_editors; pub mod combo_box; pub mod image; +pub mod menu; pub mod misc; pub mod progress_bar; pub mod selectable;