From 52b0da64a59b14a195f2efeb20909fe48716787a Mon Sep 17 00:00:00 2001 From: Jonathan Spira Date: Mon, 1 Feb 2021 22:13:30 -0800 Subject: [PATCH] holy god, the suffering --- imgui-examples/examples/multiple_fonts.rs | 4 +- imgui-examples/examples/test_window_impl.rs | 36 ++-- imgui/src/layout.rs | 35 +--- imgui/src/lib.rs | 81 ++------ imgui/src/popup_modal.rs | 120 ----------- imgui/src/popups.rs | 212 ++++++++++++++++++++ imgui/src/stacks.rs | 143 +++++++------ imgui/src/tokens.rs | 42 ++++ imgui/src/widget/combo_box.rs | 35 +--- imgui/src/widget/list_box.rs | 36 +--- imgui/src/widget/menu.rs | 98 +++------ imgui/src/widget/tab.rs | 67 ++----- imgui/src/widget/text.rs | 4 +- imgui/src/widget/tree.rs | 51 +++-- imgui/src/window/child_window.rs | 34 +--- imgui/src/window/mod.rs | 33 +-- 16 files changed, 489 insertions(+), 542 deletions(-) delete mode 100644 imgui/src/popup_modal.rs create mode 100644 imgui/src/popups.rs create mode 100644 imgui/src/tokens.rs diff --git a/imgui-examples/examples/multiple_fonts.rs b/imgui-examples/examples/multiple_fonts.rs index f551f4eb9..e78a17bd4 100644 --- a/imgui-examples/examples/multiple_fonts.rs +++ b/imgui-examples/examples/multiple_fonts.rs @@ -27,9 +27,9 @@ fn main() { ui.text("Hello, I'm Roboto Regular!"); let _dokdo = ui.push_font(dokdo); ui.text("Hello, I'm Dokdo Regular!"); - _dokdo.pop(&ui); + _dokdo.pop(); ui.text("Hello, I'm Roboto Regular again!"); - _roboto.pop(&ui); + _roboto.pop(); ui.text("Hello, I'm the default font again!"); }); }); diff --git a/imgui-examples/examples/test_window_impl.rs b/imgui-examples/examples/test_window_impl.rs index af0454865..424056164 100644 --- a/imgui-examples/examples/test_window_impl.rs +++ b/imgui-examples/examples/test_window_impl.rs @@ -338,7 +338,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { if let Some(menu_bar) = ui.begin_menu_bar() { if let Some(menu) = ui.begin_menu(im_str!("Menu"), true) { show_example_menu_file(ui, &mut state.file_menu); - menu.end(ui); + menu.end(); } if let Some(menu) = ui.begin_menu(im_str!("Examples"), true) { MenuItem::new(im_str!("Main menu bar")) @@ -363,7 +363,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { .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); - menu.end(ui); + menu.end(); } if let Some(menu) = ui.begin_menu(im_str!("Help"), true) { MenuItem::new(im_str!("Metrics")) @@ -372,9 +372,9 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { .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); - menu.end(ui); + menu.end(); } - menu_bar.end(ui); + menu_bar.end(); } ui.spacing(); if CollapsingHeader::new(im_str!("Help")).build(&ui) { @@ -709,7 +709,7 @@ CTRL+click on individual component to input value.\n", ui.checkbox(im_str!("Celery"), &mut s.celery_tab); ui.same_line(0.0); ui.checkbox(im_str!("Daikon"), &mut s.daikon_tab); - style.pop(ui); + style.pop(); let flags = { let mut f = TabBarFlags::empty(); @@ -782,7 +782,7 @@ CTRL+click on individual component to input value.\n", 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(|| { + PopupModal::new(im_str!("Delete?")).always_auto_resize(true).build(ui, || { ui.text("All those beautiful files will be deleted.\nThis operation cannot be undone!\n\n"); ui.separator(); let style = ui.push_style_var(StyleVar::FramePadding([0.0, 0.0])); @@ -795,13 +795,13 @@ CTRL+click on individual component to input value.\n", if ui.button(im_str!("Cancel"), [120.0, 0.0]) { ui.close_current_popup(); } - style.pop(ui); + style.pop(); }); 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(|| { + PopupModal::new(im_str!("Stacked 1")).build(ui, || { ui.text( "Hello from Stacked The First\n\ Using style[StyleColor::ModalWindowDarkening] for darkening." @@ -815,7 +815,7 @@ CTRL+click on individual component to input value.\n", 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(|| { + PopupModal::new(im_str!("Stacked 2")).build(ui, || { ui.text("Hello from Stacked The Second"); if ui.button(im_str!("Close"), [0.0, 0.0]) { ui.close_current_popup(); @@ -835,7 +835,7 @@ fn show_example_app_main_menu_bar<'a>(ui: &Ui<'a>, state: &mut State) { if let Some(menu_bar) = ui.begin_main_menu_bar() { if let Some(menu) = ui.begin_menu(im_str!("File"), true) { show_example_menu_file(ui, &mut state.file_menu); - menu.end(ui); + menu.end(); } if let Some(menu) = ui.begin_menu(im_str!("Edit"), true) { MenuItem::new(im_str!("Undo")) @@ -855,9 +855,9 @@ fn show_example_app_main_menu_bar<'a>(ui: &Ui<'a>, state: &mut State) { MenuItem::new(im_str!("Paste")) .shortcut(im_str!("CTRL+V")) .build(ui); - menu.end(ui); + menu.end(); } - menu_bar.end(ui); + menu_bar.end(); } } @@ -878,11 +878,11 @@ fn show_example_menu_file<'a>(ui: &Ui<'a>, state: &mut FileMenuState) { MenuItem::new(im_str!("Sailor")).build(ui); if let Some(menu) = ui.begin_menu(im_str!("Recurse.."), true) { show_example_menu_file(ui, state); - menu.end(ui); + menu.end(); } - menu.end(ui); + menu.end(); } - menu.end(ui); + menu.end(); } MenuItem::new(im_str!("Save")) .shortcut(im_str!("Ctrl+S")) @@ -909,13 +909,13 @@ 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); - menu.end(ui); + menu.end(); } if let Some(menu) = ui.begin_menu(im_str!("Colors"), true) { for &col in StyleColor::VARIANTS.iter() { MenuItem::new(&im_str!("{:?}", col)).build(ui); } - menu.end(ui); + menu.end(); } assert!(ui.begin_menu(im_str!("Disabled"), false).is_none()); MenuItem::new(im_str!("Checked")).selected(true).build(ui); @@ -966,7 +966,7 @@ fn show_example_app_fixed_overlay(ui: &Ui, opened: &mut bool) { mouse_pos[0], mouse_pos[1] )); }); - style.pop(ui); + style.pop(); } fn show_example_app_manipulating_window_title(ui: &Ui) { diff --git a/imgui/src/layout.rs b/imgui/src/layout.rs index b5be07d8e..299702845 100644 --- a/imgui/src/layout.rs +++ b/imgui/src/layout.rs @@ -1,31 +1,14 @@ -use std::ptr; -use std::thread; - -use crate::context::Context; use crate::sys; use crate::Ui; -/// Tracks a layout group that must be ended by calling `.end()` -#[must_use] -pub struct GroupToken { - ctx: *const Context, -} +create_token!( + /// Tracks a layout group that can be ended with `end` or by dropping. + pub struct GroupToken<'ui>; -impl GroupToken { - /// Ends a layout group - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEndGroup() }; - } -} - -impl Drop for GroupToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A GroupToken was leaked. Did you call .end()?"); - } - } -} + /// Drops the layout group manually. You can also just allow this token + /// to drop on its own. + drop { sys::igEndGroup() } +); /// # Cursor / Layout impl<'ui> Ui<'ui> { @@ -84,7 +67,7 @@ impl<'ui> Ui<'ui> { /// Returns a `GroupToken` that must be ended by calling `.end()` pub fn begin_group(&self) -> GroupToken { unsafe { sys::igBeginGroup() }; - GroupToken { ctx: self.ctx } + GroupToken::new(self) } /// Creates a layout group and runs a closure to construct the contents. /// @@ -92,7 +75,7 @@ impl<'ui> Ui<'ui> { pub fn group R>(&self, f: F) -> R { let group = self.begin_group(); let result = f(); - group.end(self); + group.end(); result } /// Returns the cursor position (in window coordinates) diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index f497fd34f..cd5dd362a 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -29,7 +29,7 @@ pub use self::legacy::*; pub use self::list_clipper::ListClipper; pub use self::plothistogram::PlotHistogram; pub use self::plotlines::PlotLines; -pub use self::popup_modal::PopupModal; +pub use self::popups::PopupModal; pub use self::render::draw_data::*; pub use self::render::renderer::*; pub use self::stacks::*; @@ -54,6 +54,9 @@ use internal::RawCast; #[macro_use] mod string; +#[macro_use] +mod tokens; + mod clipboard; pub mod color; mod columns; @@ -70,7 +73,7 @@ mod legacy; mod list_clipper; mod plothistogram; mod plotlines; -mod popup_modal; +mod popups; mod render; mod stacks; mod style; @@ -287,27 +290,14 @@ impl<'ui> Ui<'ui> { } } -/// Tracks a layout tooltip that must be ended by calling `.end()` -#[must_use] -pub struct TooltipToken { - ctx: *const Context, -} - -impl TooltipToken { - /// Ends a layout tooltip - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEndTooltip() }; - } -} +create_token!( + /// Tracks a layout tooltip that can be ended by calling `.end()` or by dropping. + pub struct TooltipToken<'ui>; -impl Drop for TooltipToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A TooltipToken was leaked. Did you call .end()?"); - } - } -} + /// Drops the layout tooltip manually. You can also just allow this token + /// to drop on its own. + drop { sys::igEndTooltip() } +); /// # Tooltips impl<'ui> Ui<'ui> { @@ -336,9 +326,9 @@ impl<'ui> Ui<'ui> { /// Construct a tooltip window that can have any kind of content. /// /// Returns a `TooltipToken` that must be ended by calling `.end()` - pub fn begin_tooltip(&self) -> TooltipToken { + pub fn begin_tooltip(&self) -> TooltipToken<'_> { unsafe { sys::igBeginTooltip() }; - TooltipToken { ctx: self.ctx } + TooltipToken::new(self) } /// Construct a tooltip window with simple text content. /// @@ -360,49 +350,6 @@ impl<'ui> Ui<'ui> { } } -// Widgets: Popups -impl<'ui> Ui<'ui> { - pub fn open_popup(&self, str_id: &ImStr) { - unsafe { sys::igOpenPopup(str_id.as_ptr(), 0) }; - } - pub fn popup(&self, str_id: &ImStr, f: F) - where - F: FnOnce(), - { - let render = - unsafe { sys::igBeginPopup(str_id.as_ptr(), WindowFlags::empty().bits() as i32) }; - if render { - f(); - unsafe { sys::igEndPopup() }; - } - } - /// Create a modal pop-up. - /// - /// # Example - /// ```rust,no_run - /// # use imgui::*; - /// # let mut imgui = Context::create(); - /// # let ui = imgui.frame(); - /// 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() }; - } -} - // Widgets: ListBox impl<'ui> Ui<'ui> { pub fn list_box<'p, StringType: AsRef + ?Sized>( diff --git a/imgui/src/popup_modal.rs b/imgui/src/popup_modal.rs deleted file mode 100644 index 0cb668a29..000000000 --- a/imgui/src/popup_modal.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::marker::PhantomData; -use std::ptr; - -use crate::sys; -use crate::window::WindowFlags; -use crate::{ImStr, Ui}; - -/// Created by call to [`Ui::popup_modal`]. -#[must_use] -pub struct PopupModal<'ui, 'p> { - label: &'p ImStr, - opened: Option<&'p mut bool>, - flags: WindowFlags, - _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: WindowFlags::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: WindowFlags) -> Self { - self.flags = flags; - self - } - pub fn title_bar(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::NO_TITLE_BAR, !value); - self - } - pub fn resizable(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::NO_RESIZE, !value); - self - } - pub fn movable(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::NO_MOVE, !value); - self - } - pub fn scroll_bar(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::NO_SCROLLBAR, !value); - self - } - pub fn scrollable(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::NO_SCROLL_WITH_MOUSE, !value); - self - } - pub fn collapsible(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::NO_COLLAPSE, !value); - self - } - pub fn always_auto_resize(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::ALWAYS_AUTO_RESIZE, value); - self - } - pub fn save_settings(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::NO_SAVED_SETTINGS, !value); - self - } - pub fn inputs(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::NO_INPUTS, !value); - self - } - pub fn menu_bar(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::MENU_BAR, value); - self - } - pub fn horizontal_scrollbar(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::HORIZONTAL_SCROLLBAR, value); - self - } - pub fn no_focus_on_appearing(mut self, value: bool) -> Self { - self.flags.set(WindowFlags::NO_FOCUS_ON_APPEARING, value); - self - } - pub fn no_bring_to_front_on_focus(mut self, value: bool) -> Self { - self.flags - .set(WindowFlags::NO_BRING_TO_FRONT_ON_FOCUS, value); - self - } - pub fn always_vertical_scrollbar(mut self, value: bool) -> Self { - self.flags - .set(WindowFlags::ALWAYS_VERTICAL_SCROLLBAR, value); - self - } - pub fn always_horizontal_scrollbar(mut self, value: bool) -> Self { - self.flags - .set(WindowFlags::ALWAYS_HORIZONTAL_SCROLLBAR, value); - self - } - pub fn always_use_window_padding(mut self, value: bool) -> Self { - self.flags - .set(WindowFlags::ALWAYS_USE_WINDOW_PADDING, 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.bits() as i32, - ) - }; - if render { - f(); - unsafe { sys::igEndPopup() }; - } - } -} diff --git a/imgui/src/popups.rs b/imgui/src/popups.rs new file mode 100644 index 000000000..a51020d38 --- /dev/null +++ b/imgui/src/popups.rs @@ -0,0 +1,212 @@ +use std::ptr; + +use crate::sys; +use crate::window::WindowFlags; +use crate::{ImStr, Ui}; + +create_token!( + /// Tracks a popup token that can be ended with `end` or by dropping. + pub struct PopupToken<'ui>; + + /// Drops the popup token manually. You can also just allow this token + /// to drop on its own. + drop { sys::igEndPopup() } +); + +/// Create a modal pop-up. +/// +/// # Example +/// ```rust,no_run +/// # use imgui::*; +/// # let mut imgui = Context::create(); +/// # let ui = imgui.frame(); +/// if ui.button(im_str!("Show modal"), [0.0, 0.0]) { +/// ui.open_popup(im_str!("modal")); +/// } +/// if let Some(_token) = PopupModal::new(im_str!("modal")).begin_popup() { +/// ui.text("Content of my modal"); +/// if ui.button(im_str!("OK"), [0.0, 0.0]) { +/// ui.close_current_popup(); +/// } +/// }); +/// ``` +#[must_use] +pub struct PopupModal<'p> { + label: &'p ImStr, + opened: Option<&'p mut bool>, + flags: WindowFlags, +} + +impl<'p> PopupModal<'p> { + pub fn new(label: &'p ImStr) -> Self { + PopupModal { + label, + opened: None, + flags: WindowFlags::empty(), + } + } + /// 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: WindowFlags) -> Self { + self.flags = flags; + self + } + pub fn title_bar(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::NO_TITLE_BAR, !value); + self + } + pub fn resizable(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::NO_RESIZE, !value); + self + } + pub fn movable(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::NO_MOVE, !value); + self + } + pub fn scroll_bar(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::NO_SCROLLBAR, !value); + self + } + pub fn scrollable(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::NO_SCROLL_WITH_MOUSE, !value); + self + } + pub fn collapsible(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::NO_COLLAPSE, !value); + self + } + pub fn always_auto_resize(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::ALWAYS_AUTO_RESIZE, value); + self + } + pub fn save_settings(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::NO_SAVED_SETTINGS, !value); + self + } + pub fn inputs(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::NO_INPUTS, !value); + self + } + pub fn menu_bar(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::MENU_BAR, value); + self + } + pub fn horizontal_scrollbar(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::HORIZONTAL_SCROLLBAR, value); + self + } + pub fn no_focus_on_appearing(mut self, value: bool) -> Self { + self.flags.set(WindowFlags::NO_FOCUS_ON_APPEARING, value); + self + } + pub fn no_bring_to_front_on_focus(mut self, value: bool) -> Self { + self.flags + .set(WindowFlags::NO_BRING_TO_FRONT_ON_FOCUS, value); + self + } + pub fn always_vertical_scrollbar(mut self, value: bool) -> Self { + self.flags + .set(WindowFlags::ALWAYS_VERTICAL_SCROLLBAR, value); + self + } + pub fn always_horizontal_scrollbar(mut self, value: bool) -> Self { + self.flags + .set(WindowFlags::ALWAYS_HORIZONTAL_SCROLLBAR, value); + self + } + pub fn always_use_window_padding(mut self, value: bool) -> Self { + self.flags + .set(WindowFlags::ALWAYS_USE_WINDOW_PADDING, value); + self + } + + /// Consume and draw the PopupModal. + pub fn build(self, ui: &Ui<'_>, f: F) { + if let Some(_popup) = self.begin_popup(ui) { + f(); + } + } + + /// Consume and draw the PopupModal. + /// Construct a popup that can have any kind of content. + /// + /// This should be called *per frame*, whereas [`open_popup`](Self::open_popup) should be called *once* + /// when you want to actual create the popup. + pub fn begin_popup<'ui>(self, ui: &Ui<'ui>) -> Option> { + let render = unsafe { + sys::igBeginPopupModal( + self.label.as_ptr(), + self.opened + .map(|x| x as *mut bool) + .unwrap_or(ptr::null_mut()), + self.flags.bits() as i32, + ) + }; + + if render { + Some(PopupToken::new(ui)) + } else { + None + } + } +} + +// Widgets: Popups +impl<'ui> Ui<'ui> { + /// Instructs ImGui to open a popup, which must be began with either [`begin_popup`](Self::begin_popup) + /// or [`popup`](Self::popup). You also use this function to begin [ModalPopups]. + /// + /// The confusing aspect to popups is that ImGui holds "control" over the popup fundamentally, so that ImGui + /// can also force close a popup when a user clicks outside a popup. If you do not want users to be + /// able to close a popup without selected an option, use [`PopupModal`]. + pub fn open_popup(&self, str_id: &ImStr) { + unsafe { sys::igOpenPopup(str_id.as_ptr(), 0) }; + } + + /// Construct a popup that can have any kind of content. + /// + /// This should be called *per frame*, whereas [`open_popup`](Self::open_popup) should be called *once* + /// when you want to actual create the popup. + pub fn begin_popup(&self, str_id: &ImStr) -> Option> { + let render = + unsafe { sys::igBeginPopup(str_id.as_ptr(), WindowFlags::empty().bits() as i32) }; + + if render { + Some(PopupToken::new(self)) + } else { + None + } + } + + /// Construct a popup that can have any kind of content. + /// + /// This should be called *per frame*, whereas [`open_popup`](Self::open_popup) should be called *once* + /// when you want to actual create the popup. + pub fn popup(&self, str_id: &ImStr, f: F) + where + F: FnOnce(), + { + let render = + unsafe { sys::igBeginPopup(str_id.as_ptr(), WindowFlags::empty().bits() as i32) }; + if render { + f(); + unsafe { sys::igEndPopup() }; + } + } + + /// Creates a PopupModal directly. + #[deprecated = "Please use PopupModal to create a modal popup."] + pub fn popup_modal<'p>(&self, str_id: &'p ImStr) -> PopupModal<'p> { + PopupModal::new(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/imgui/src/stacks.rs b/imgui/src/stacks.rs index 4cbccad64..1e4fe218a 100644 --- a/imgui/src/stacks.rs +++ b/imgui/src/stacks.rs @@ -34,13 +34,13 @@ impl<'ui> Ui<'ui> { /// ui.text("I use the custom font!"); /// font.pop(&ui); /// ``` - pub fn push_font(&self, id: FontId) -> FontStackToken { + pub fn push_font(&self, id: FontId) -> FontStackToken<'_> { let fonts = self.fonts(); let font = fonts .get_font(id) .expect("Font atlas did not contain the given font"); unsafe { sys::igPushFont(font.raw() as *const _ as *mut _) }; - FontStackToken { ctx: self.ctx } + FontStackToken::new(self) } /// Changes a style color by pushing a change to the color stack. /// @@ -59,11 +59,9 @@ impl<'ui> Ui<'ui> { /// ``` pub fn push_style_color(&self, style_color: StyleColor, color: [f32; 4]) -> ColorStackToken { unsafe { sys::igPushStyleColorVec4(style_color as i32, color.into()) }; - ColorStackToken { - count: 1, - ctx: self.ctx, - } + ColorStackToken::new(self) } + /// Changes style colors by pushing several changes to the color stack. /// /// Returns a `ColorStackToken` that must be popped by calling `.pop()` @@ -84,7 +82,8 @@ impl<'ui> Ui<'ui> { /// ui.text_disabled("I'm green!"); /// colors.pop(&ui); /// ``` - pub fn push_style_colors<'a, I>(&self, style_colors: I) -> ColorStackToken + #[deprecated = "deprecated in 0.7.0. Use `push_style_color` multiple times for similar effect."] + pub fn push_style_colors<'a, I>(&self, style_colors: I) -> MultiColorStackToken where I: IntoIterator, { @@ -93,14 +92,15 @@ impl<'ui> Ui<'ui> { unsafe { sys::igPushStyleColorVec4(style_color as i32, color.into()) }; count += 1; } - ColorStackToken { + MultiColorStackToken { count, ctx: self.ctx, } } /// Changes a style variable by pushing a change to the style stack. /// - /// Returns a `StyleStackToken` that must be popped by calling `.pop()` + /// Returns a `StyleStackToken` that can be popped by calling `.end()` + /// or by allowing to drop. /// /// # Examples /// @@ -114,10 +114,7 @@ impl<'ui> Ui<'ui> { /// ``` pub fn push_style_var(&self, style_var: StyleVar) -> StyleStackToken { unsafe { push_style_var(style_var) }; - StyleStackToken { - count: 1, - ctx: self.ctx, - } + StyleStackToken::new(self) } /// Changes style variables by pushing several changes to the style stack. /// @@ -137,7 +134,8 @@ impl<'ui> Ui<'ui> { /// ui.text("...with large spacing as well"); /// styles.pop(&ui); /// ``` - pub fn push_style_vars<'a, I>(&self, style_vars: I) -> StyleStackToken + #[deprecated = "deprecated in 0.7.0. Use `push_style_var` multiple times for similar effect."] + pub fn push_style_vars<'a, I>(&self, style_vars: I) -> MultiStyleStackToken where I: IntoIterator, { @@ -146,43 +144,53 @@ impl<'ui> Ui<'ui> { unsafe { push_style_var(style_var) }; count += 1; } - StyleStackToken { + MultiStyleStackToken { count, ctx: self.ctx, } } } -/// Tracks a font pushed to the font stack that must be popped by calling `.pop()` -#[must_use] -pub struct FontStackToken { - ctx: *const Context, -} +create_token!( + /// Tracks a font pushed to the font stack that can be popped by calling `.end()` + /// or by dropping. + pub struct FontStackToken<'ui>; -impl FontStackToken { - /// Pops a change from the font stack - pub fn pop(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igPopFont() }; + /// Pops a change from the font stack. + drop { sys::igPopFont() } +); + +impl FontStackToken<'_> { + /// Pops a change from the font stack. + pub fn pop(self) { + self.end() } } -impl Drop for FontStackToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A FontStackToken was leaked. Did you call .pop()?"); - } +create_token!( + /// Tracks a color pushed to the color stack that can be popped by calling `.end()` + /// or by dropping. + pub struct ColorStackToken<'ui>; + + /// Pops a change from the color stack. + drop { sys::igPopStyleColor(1) } +); + +impl ColorStackToken<'_> { + /// Pops a change from the color stack. + pub fn pop(self) { + self.end() } } /// Tracks one or more changes pushed to the color stack that must be popped by calling `.pop()` #[must_use] -pub struct ColorStackToken { +pub struct MultiColorStackToken { count: usize, ctx: *const Context, } -impl ColorStackToken { +impl MultiColorStackToken { /// Pops changes from the color stack pub fn pop(mut self, _: &Ui) { self.ctx = ptr::null(); @@ -190,7 +198,7 @@ impl ColorStackToken { } } -impl Drop for ColorStackToken { +impl Drop for MultiColorStackToken { fn drop(&mut self) { if !self.ctx.is_null() && !thread::panicking() { panic!("A ColorStackToken was leaked. Did you call .pop()?"); @@ -198,14 +206,30 @@ impl Drop for ColorStackToken { } } +create_token!( + /// Tracks a style pushed to the style stack that can be popped by calling `.end()` + /// or by dropping. + pub struct StyleStackToken<'ui>; + + /// Pops a change from the style stack. + drop { sys::igPopStyleVar(1) } +); + +impl StyleStackToken<'_> { + /// Pops a change from the style stack. + pub fn pop(self) { + self.end() + } +} + /// Tracks one or more changes pushed to the style stack that must be popped by calling `.pop()` #[must_use] -pub struct StyleStackToken { +pub struct MultiStyleStackToken { count: usize, ctx: *const Context, } -impl StyleStackToken { +impl MultiStyleStackToken { /// Pops changes from the style stack pub fn pop(mut self, _: &Ui) { self.ctx = ptr::null(); @@ -213,7 +237,7 @@ impl StyleStackToken { } } -impl Drop for StyleStackToken { +impl Drop for MultiStyleStackToken { fn drop(&mut self) { if !self.ctx.is_null() && !thread::panicking() { panic!("A StyleStackToken was leaked. Did you call .pop()?"); @@ -372,13 +396,30 @@ impl ItemFlagsStackToken { } } +create_token!( + /// Tracks an ID pushed to the ID stack that can be popped by calling `.pop()` + /// or by dropping. + pub struct IdStackToken<'ui>; + + /// Pops a change from the ID stack + drop { sys::igPopID() } +); + +impl IdStackToken<'_> { + /// Pops a change from the ID stack + #[deprecated = "deprecated in 0.7.0. Use `end` instead."] + pub fn pop(self) { + self.end() + } +} + /// # ID stack impl<'ui> Ui<'ui> { /// Pushes an identifier to the ID stack. /// - /// Returns an `IdStackToken` that must be popped by calling `.pop()` - /// - pub fn push_id<'a, I: Into>>(&self, id: I) -> IdStackToken { + /// Returns an `IdStackToken` that can be popped by calling `.end()` + /// or by dropping manually. + pub fn push_id<'a, I: Into>>(&self, id: I) -> IdStackToken<'_> { let id = id.into(); unsafe { @@ -392,28 +433,6 @@ impl<'ui> Ui<'ui> { Id::Ptr(p) => sys::igPushIDPtr(p as *const c_void), } } - IdStackToken { ctx: self.ctx } - } -} - -/// Tracks an ID pushed to the ID stack that must be popped by calling `.pop()` -#[must_use] -pub struct IdStackToken { - ctx: *const Context, -} - -impl IdStackToken { - /// Pops a change from the ID stack - pub fn pop(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igPopID() }; - } -} - -impl Drop for IdStackToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A IdStackToken was leaked. Did you call .pop()?"); - } + IdStackToken::new(self) } } diff --git a/imgui/src/tokens.rs b/imgui/src/tokens.rs new file mode 100644 index 000000000..5f3c54e4d --- /dev/null +++ b/imgui/src/tokens.rs @@ -0,0 +1,42 @@ +#[macro_export] +/// This is a macro used internally by imgui-rs to create StackTokens +/// representing various global state in DearImGui. +/// +/// These tokens can either be allowed to drop or dropped manually +/// by called `end` on them. Preventing this token from dropping, +/// or moving this token out of the block it was made in can have +/// unintended side effects, including failed asserts in the DearImGui C++. +/// +/// In general, if you're looking at this, don't overthink these -- just slap +/// a '_token` as their binding name and allow them to drop. +macro_rules! create_token { + ( + $(#[$struct_meta:meta])* + $v:vis struct $token_name:ident<'ui>; + + $(#[$end_meta:meta])* + drop { $on_drop:expr } + ) => { + #[must_use] + $(#[$struct_meta])* + pub struct $token_name<'a>($crate::__core::marker::PhantomData>); + + impl<'a> $token_name<'a> { + /// Creates a new token type. + pub(crate) fn new(_: &crate::Ui<'a>) -> Self { + Self(std::marker::PhantomData) + } + + $(#[$end_meta])* + pub fn end(self) { + // left empty for drop + } + } + + impl Drop for $token_name<'_> { + fn drop(&mut self) { + unsafe { $on_drop } + } + } + } +} diff --git a/imgui/src/widget/combo_box.rs b/imgui/src/widget/combo_box.rs index 6774c5b0b..c10ffabb1 100644 --- a/imgui/src/widget/combo_box.rs +++ b/imgui/src/widget/combo_box.rs @@ -1,9 +1,7 @@ use bitflags::bitflags; use std::borrow::Cow; use std::ptr; -use std::thread; -use crate::context::Context; use crate::string::ImStr; use crate::sys; use crate::Ui; @@ -141,7 +139,7 @@ impl<'a> ComboBox<'a> { /// /// Returns `None` if the combo box is not open and no content should be rendered. #[must_use] - pub fn begin(self, ui: &Ui) -> Option { + pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { let should_render = unsafe { sys::igBeginCombo( self.label.as_ptr(), @@ -150,7 +148,7 @@ impl<'a> ComboBox<'a> { ) }; if should_render { - Some(ComboBoxToken { ctx: ui.ctx }) + Some(ComboBoxToken::new(ui)) } else { None } @@ -159,34 +157,20 @@ impl<'a> ComboBox<'a> { /// /// Note: the closure is not called if the combo box is not open. pub fn build(self, ui: &Ui, f: F) { - if let Some(combo) = self.begin(ui) { + if let Some(_combo) = self.begin(ui) { f(); - combo.end(ui); } } } -/// Tracks a combo box that must be ended by calling `.end()` -#[must_use] -pub struct ComboBoxToken { - ctx: *const Context, -} +create_token!( + /// Tracks a combo box that can be ended by calling `.end()` + /// or by dropping. + pub struct ComboBoxToken<'ui>; -impl ComboBoxToken { /// Ends a combo box - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEndCombo() }; - } -} - -impl Drop for ComboBoxToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A ComboBoxToken was leaked. Did you call .end()?"); - } - } -} + drop { sys::igEndCombo() } +); /// # Convenience functions impl<'a> ComboBox<'a> { @@ -219,7 +203,6 @@ impl<'a> ComboBox<'a> { result = true; } } - _cb.end(ui); } result } diff --git a/imgui/src/widget/list_box.rs b/imgui/src/widget/list_box.rs index b58764c16..9bf4ffcfb 100644 --- a/imgui/src/widget/list_box.rs +++ b/imgui/src/widget/list_box.rs @@ -1,8 +1,5 @@ use std::borrow::Cow; -use std::ptr; -use std::thread; -use crate::context::Context; use crate::string::ImStr; use crate::sys; use crate::Ui; @@ -62,7 +59,7 @@ impl<'a> ListBox<'a> { /// /// Returns `None` if the list box is not open and no content should be rendered. #[must_use] - pub fn begin(self, ui: &Ui) -> Option { + pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { let should_render = unsafe { match self.size { Size::Vec(size) => sys::igListBoxHeaderVec2(self.label.as_ptr(), size), @@ -73,7 +70,7 @@ impl<'a> ListBox<'a> { } }; if should_render { - Some(ListBoxToken { ctx: ui.ctx }) + Some(ListBoxToken::new(ui)) } else { None } @@ -82,34 +79,20 @@ impl<'a> ListBox<'a> { /// /// Note: the closure is not called if the list box is not open. pub fn build(self, ui: &Ui, f: F) { - if let Some(list) = self.begin(ui) { + if let Some(_list) = self.begin(ui) { f(); - list.end(ui); } } } -/// Tracks a list box that must be ended by calling `.end()` -#[must_use] -pub struct ListBoxToken { - ctx: *const Context, -} +create_token!( + /// Tracks a list box that can be ended by calling `.end()` + /// or by dropping + pub struct ListBoxToken<'ui>; -impl ListBoxToken { /// Ends a list box - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igListBoxFooter() }; - } -} - -impl Drop for ListBoxToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A ListBoxToken was leaked. Did you call .end()?"); - } - } -} + drop { sys::igListBoxFooter() } +); /// # Convenience functions impl<'a> ListBox<'a> { @@ -136,7 +119,6 @@ impl<'a> ListBox<'a> { result = true; } } - _cb.end(ui); } result } diff --git a/imgui/src/widget/menu.rs b/imgui/src/widget/menu.rs index 883e7cf94..c15832788 100644 --- a/imgui/src/widget/menu.rs +++ b/imgui/src/widget/menu.rs @@ -1,7 +1,5 @@ use std::ptr; -use std::thread; -use crate::context::Context; use crate::string::ImStr; use crate::sys; use crate::Ui; @@ -15,9 +13,9 @@ impl<'ui> Ui<'ui> { /// /// Returns `None` if the menu bar is not visible and no content should be rendered. #[must_use] - pub fn begin_main_menu_bar(&self) -> Option { + pub fn begin_main_menu_bar(&self) -> Option> { if unsafe { sys::igBeginMainMenuBar() } { - Some(MainMenuBarToken { ctx: self.ctx }) + Some(MainMenuBarToken::new(self)) } else { None } @@ -26,9 +24,8 @@ impl<'ui> Ui<'ui> { /// /// Note: the closure is not called if the menu bar is not visible. pub fn main_menu_bar(&self, f: F) { - if let Some(menu_bar) = self.begin_main_menu_bar() { + if let Some(_menu_bar) = self.begin_main_menu_bar() { f(); - menu_bar.end(self); } } /// Creates and starts appending to the menu bar of the current window. @@ -38,9 +35,9 @@ impl<'ui> Ui<'ui> { /// /// Returns `None` if the menu bar is not visible and no content should be rendered. #[must_use] - pub fn begin_menu_bar(&self) -> Option { + pub fn begin_menu_bar(&self) -> Option> { if unsafe { sys::igBeginMenuBar() } { - Some(MenuBarToken { ctx: self.ctx }) + Some(MenuBarToken::new(self)) } else { None } @@ -49,9 +46,8 @@ impl<'ui> Ui<'ui> { /// /// Note: the closure is not called if the menu bar is not visible. pub fn menu_bar(&self, f: F) { - if let Some(menu_bar) = self.begin_menu_bar() { + if let Some(_menu_bar) = self.begin_menu_bar() { f(); - menu_bar.end(self); } } /// Creates and starts appending to a sub-menu entry. @@ -61,9 +57,9 @@ impl<'ui> Ui<'ui> { /// /// Returns `None` if the menu is not visible and no content should be rendered. #[must_use] - pub fn begin_menu(&self, label: &ImStr, enabled: bool) -> Option { + pub fn begin_menu(&self, label: &ImStr, enabled: bool) -> Option> { if unsafe { sys::igBeginMenu(label.as_ptr(), enabled) } { - Some(MenuToken { ctx: self.ctx }) + Some(MenuToken::new(self)) } else { None } @@ -72,9 +68,8 @@ impl<'ui> Ui<'ui> { /// /// Note: the closure is not called if the menu is not visible. pub fn menu(&self, label: &ImStr, enabled: bool, f: F) { - if let Some(menu) = self.begin_menu(label, enabled) { + if let Some(_menu) = self.begin_menu(label, enabled) { f(); - menu.end(self); } } } @@ -151,68 +146,29 @@ impl<'a> MenuItem<'a> { } } -/// Tracks a main menu bar that must be ended by calling `.end()` -#[must_use] -pub struct MainMenuBarToken { - ctx: *const Context, -} +create_token!( + /// Tracks a main menu bar that can be ended by calling `.end()` + /// or by dropping + pub struct MainMenuBarToken<'ui>; -impl MainMenuBarToken { /// Ends a main menu bar - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEndMainMenuBar() }; - } -} - -impl Drop for MainMenuBarToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A MainMenuBarToken was leaked. Did you call .end()?"); - } - } -} + drop { sys::igEndMainMenuBar() } +); -/// Tracks a menu bar that must be ended by calling `.end()` -#[must_use] -pub struct MenuBarToken { - ctx: *const Context, -} +create_token!( + /// Tracks a menu bar that can be ended by calling `.end()` + /// or by dropping + pub struct MenuBarToken<'ui>; -impl MenuBarToken { /// Ends a menu bar - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEndMenuBar() }; - } -} + drop { sys::igEndMenuBar() } +); -impl Drop for MenuBarToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A MenuBarToken was leaked. Did you call .end()?"); - } - } -} - -/// Tracks a menu that must be ended by calling `.end()` -#[must_use] -pub struct MenuToken { - ctx: *const Context, -} +create_token!( + /// Tracks a menu that can be ended by calling `.end()` + /// or by dropping + pub struct MenuToken<'ui>; -impl MenuToken { /// Ends a menu - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEndMenu() }; - } -} - -impl Drop for MenuToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A MenuToken was leaked. Did you call .end()?"); - } - } -} + drop { sys::igEndMenu() } +); diff --git a/imgui/src/widget/tab.rs b/imgui/src/widget/tab.rs index e2070f3d7..573ba56e7 100644 --- a/imgui/src/widget/tab.rs +++ b/imgui/src/widget/tab.rs @@ -19,12 +19,11 @@ //! ``` //! //! See `test_window_impl.rs` for a more complicated example. -use crate::context::Context; use crate::string::ImStr; use crate::sys; use crate::Ui; use bitflags::bitflags; -use std::{ptr, thread}; +use std::ptr; bitflags! { #[repr(transparent)] @@ -90,12 +89,12 @@ impl<'a> TabBar<'a> { } #[must_use] - pub fn begin(self, ui: &Ui) -> Option { + pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { let should_render = unsafe { sys::igBeginTabBar(self.id.as_ptr(), self.flags.bits() as i32) }; if should_render { - Some(TabBarToken { ctx: ui.ctx }) + Some(TabBarToken::new(ui)) } else { unsafe { sys::igEndTabBar() }; None @@ -106,33 +105,20 @@ impl<'a> TabBar<'a> { /// /// Note: the closure is not called if no tabbar content is visible pub fn build(self, ui: &Ui, f: F) { - if let Some(tab) = self.begin(ui) { + if let Some(_tab) = self.begin(ui) { f(); - tab.end(ui); } } } -/// Tracks a window that must be ended by calling `.end()` -pub struct TabBarToken { - ctx: *const Context, -} - -impl TabBarToken { - /// Ends a tab bar - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEndTabBar() }; - } -} +create_token!( + /// Tracks a window that can be ended by calling `.end()` + /// or by dropping + pub struct TabBarToken<'ui>; -impl Drop for TabBarToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A TabBarToken was leaked. Did you call .end()?"); - } - } -} + /// Ends a tab bar. + drop { sys::igEndTabBar() } +); pub struct TabItem<'a> { name: &'a ImStr, @@ -168,7 +154,7 @@ impl<'a> TabItem<'a> { } #[must_use] - pub fn begin(self, ui: &Ui) -> Option { + pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { let should_render = unsafe { sys::igBeginTabItem( self.name.as_ptr(), @@ -180,7 +166,7 @@ impl<'a> TabItem<'a> { }; if should_render { - Some(TabItemToken { ctx: ui.ctx }) + Some(TabItemToken::new(ui)) } else { None } @@ -190,28 +176,17 @@ impl<'a> TabItem<'a> { /// /// Note: the closure is not called if the tab item is not selected pub fn build(self, ui: &Ui, f: F) { - if let Some(tab) = self.begin(ui) { + if let Some(_tab) = self.begin(ui) { f(); - tab.end(ui); } } } -pub struct TabItemToken { - ctx: *const Context, -} - -impl TabItemToken { - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEndTabItem() }; - } -} +create_token!( + /// Tracks a tab bar item that can be ended by calling `.end()` + /// or by dropping + pub struct TabItemToken<'ui>; -impl Drop for TabItemToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A TabItemToken was leaked. Did you call .end()?"); - } - } -} + /// Ends a tab bar item. + drop { sys::igEndTabItem() } +); diff --git a/imgui/src/widget/text.rs b/imgui/src/widget/text.rs index fd0b9d898..d77f5ace7 100644 --- a/imgui/src/widget/text.rs +++ b/imgui/src/widget/text.rs @@ -26,14 +26,14 @@ impl<'ui> Ui<'ui> { pub fn text_colored>(&self, color: [f32; 4], text: T) { let style = self.push_style_color(StyleColor::Text, color); self.text(text); - style.pop(self); + style.end(); } /// Renders simple text using `StyleColor::TextDisabled` color pub fn text_disabled>(&self, text: T) { let color = self.style_color(StyleColor::TextDisabled); let style = self.push_style_color(StyleColor::Text, color); self.text(text); - style.pop(self); + style.end(); } /// Renders text wrapped to the end of window (or column) pub fn text_wrapped(&self, text: &ImStr) { diff --git a/imgui/src/widget/tree.rs b/imgui/src/widget/tree.rs index 99c12fe87..9babd7e45 100644 --- a/imgui/src/widget/tree.rs +++ b/imgui/src/widget/tree.rs @@ -1,9 +1,6 @@ use bitflags::bitflags; use std::os::raw::{c_char, c_void}; -use std::ptr; -use std::thread; -use crate::context::Context; use crate::string::ImStr; use crate::sys; use crate::{Condition, Ui}; @@ -238,7 +235,7 @@ impl<'a> TreeNode<'a> { /// /// Returns `None` if the tree node is not open and no content should be rendered. #[must_use] - pub fn push(self, ui: &Ui) -> Option { + pub fn push<'ui>(self, ui: &Ui<'ui>) -> Option> { let open = unsafe { if self.opened_cond != Condition::Never { sys::igSetNextItemOpen(self.opened, self.opened_cond as i32); @@ -259,13 +256,10 @@ impl<'a> TreeNode<'a> { } }; if open { - Some(TreeNodeToken { - ctx: if self.flags.contains(TreeNodeFlags::NO_TREE_PUSH_ON_OPEN) { - ptr::null() - } else { - ui.ctx - }, - }) + Some(TreeNodeToken::new( + ui, + !self.flags.contains(TreeNodeFlags::NO_TREE_PUSH_ON_OPEN), + )) } else { None } @@ -274,37 +268,42 @@ impl<'a> TreeNode<'a> { /// /// Note: the closure is not called if the tree node is not open. pub fn build(self, ui: &Ui, f: F) { - if let Some(node) = self.push(ui) { + if let Some(_node) = self.push(ui) { f(); - node.pop(ui); } } } -/// Tracks a tree node that must be popped by calling `.pop()`. +/// Tracks a tree node that can be popped by calling `.pop()`, `end()`, or by dropping. /// /// If `TreeNodeFlags::NO_TREE_PUSH_ON_OPEN` was used when this token was created, calling `.pop()` /// is not mandatory and is a no-op. #[must_use] -pub struct TreeNodeToken { - ctx: *const Context, -} +pub struct TreeNodeToken<'a>(core::marker::PhantomData>, bool); + +impl<'a> TreeNodeToken<'a> { + /// Creates a new token type. This takes a bool for the no-op variant on NO_TREE_PUSH_ON_OPEN. + pub(crate) fn new(_: &crate::Ui<'a>, execute_drop: bool) -> Self { + Self(std::marker::PhantomData, execute_drop) + } -impl TreeNodeToken { /// Pops a tree node #[inline] - pub fn pop(mut self, _: &Ui) { - if !self.ctx.is_null() { - self.ctx = ptr::null(); - unsafe { sys::igTreePop() }; - } + pub fn end(self) { + // left empty for drop + } + + /// Pops a tree node + #[inline] + pub fn pop(self) { + self.end() } } -impl Drop for TreeNodeToken { +impl Drop for TreeNodeToken<'_> { fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A TreeNodeToken was leaked. Did you call .pop()?"); + if self.1 { + unsafe { sys::igTreePop() } } } } diff --git a/imgui/src/window/child_window.rs b/imgui/src/window/child_window.rs index 2edcb54c5..cb6c1dc1b 100644 --- a/imgui/src/window/child_window.rs +++ b/imgui/src/window/child_window.rs @@ -1,9 +1,6 @@ use std::f32; use std::os::raw::{c_char, c_void}; -use std::ptr; -use std::thread; -use crate::context::Context; use crate::sys; use crate::window::WindowFlags; use crate::{Id, Ui}; @@ -244,7 +241,7 @@ impl<'a> ChildWindow<'a> { /// rendered, the token must be ended by calling `.end()`. /// /// Returns `None` if the window is not visible and no content should be rendered. - pub fn begin(self, ui: &Ui) -> Option { + pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { if self.content_size[0] != 0.0 || self.content_size[1] != 0.0 { unsafe { sys::igSetNextWindowContentSize(self.content_size.into()) }; } @@ -269,7 +266,7 @@ impl<'a> ChildWindow<'a> { sys::igBeginChildID(id, self.size.into(), self.border, self.flags.bits() as i32) }; if should_render { - Some(ChildWindowToken { ctx: ui.ctx }) + Some(ChildWindowToken::new(ui)) } else { unsafe { sys::igEndChild() }; None @@ -280,30 +277,17 @@ impl<'a> ChildWindow<'a> { /// Note: the closure is not called if no window content is visible (e.g. window is collapsed /// or fully clipped). pub fn build(self, ui: &Ui, f: F) { - if let Some(window) = self.begin(ui) { + if let Some(_window) = self.begin(ui) { f(); - window.end(ui); } } } -/// Tracks a child window that must be ended by calling `.end()` -pub struct ChildWindowToken { - ctx: *const Context, -} +create_token!( + /// Tracks a child window that can be ended by calling `.end()` + /// or by dropping + pub struct ChildWindowToken<'ui>; -impl ChildWindowToken { /// Ends a window - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEndChild() }; - } -} - -impl Drop for ChildWindowToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A ChildWindowToken was leaked. Did you call .end()?"); - } - } -} + drop { sys::igEndChild() } +); diff --git a/imgui/src/window/mod.rs b/imgui/src/window/mod.rs index 57ebe1eb9..85276f722 100644 --- a/imgui/src/window/mod.rs +++ b/imgui/src/window/mod.rs @@ -1,9 +1,7 @@ use bitflags::bitflags; use std::f32; use std::ptr; -use std::thread; -use crate::context::Context; use crate::string::ImStr; use crate::sys; use crate::{Condition, Ui}; @@ -482,7 +480,7 @@ impl<'a> Window<'a> { /// /// Returns `None` if the window is not visible and no content should be rendered. #[must_use] - pub fn begin(self, ui: &Ui) -> Option { + pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { if self.pos_cond != Condition::Never { unsafe { sys::igSetNextWindowPos( @@ -528,7 +526,7 @@ impl<'a> Window<'a> { ) }; if should_render { - Some(WindowToken { ctx: ui.ctx }) + Some(WindowToken::new(ui)) } else { unsafe { sys::igEnd() }; None @@ -539,30 +537,17 @@ impl<'a> Window<'a> { /// Note: the closure is not called if no window content is visible (e.g. window is collapsed /// or fully clipped). pub fn build(self, ui: &Ui, f: F) { - if let Some(window) = self.begin(ui) { + if let Some(_window) = self.begin(ui) { f(); - window.end(ui); } } } -/// Tracks a window that must be ended by calling `.end()` -pub struct WindowToken { - ctx: *const Context, -} +create_token!( + /// Tracks a window that can be ended by calling `.end()` + /// or by dropping. + pub struct WindowToken<'ui>; -impl WindowToken { /// Ends a window - pub fn end(mut self, _: &Ui) { - self.ctx = ptr::null(); - unsafe { sys::igEnd() }; - } -} - -impl Drop for WindowToken { - fn drop(&mut self) { - if !self.ctx.is_null() && !thread::panicking() { - panic!("A WindowToken was leaked. Did you call .end()?"); - } - } -} + drop { sys::igEnd() } +);