dear_imgui_rs/widget/
popup.rs

1//! Popups and modals
2//!
3//! Popup windows (context menus, modals) with builders and token helpers to
4//! ensure balanced begin/end calls.
5//!
6#![allow(
7    clippy::cast_possible_truncation,
8    clippy::cast_sign_loss,
9    clippy::as_conversions
10)]
11use crate::sys;
12use crate::ui::Ui;
13use crate::window::WindowFlags;
14
15/// # Popup Widgets
16impl Ui {
17    /// Instructs ImGui that a popup is open.
18    ///
19    /// You should **call this function once** while calling any of the following per-frame:
20    ///
21    /// - [`begin_popup`](Self::begin_popup)
22    /// - [`popup`](Self::popup)
23    /// - [`begin_modal_popup`](Self::begin_modal_popup)
24    /// - [`modal_popup`](Self::modal_popup)
25    ///
26    /// The confusing aspect to popups is that ImGui holds control over the popup itself.
27    #[doc(alias = "OpenPopup")]
28    pub fn open_popup(&self, str_id: impl AsRef<str>) {
29        let str_id_ptr = self.scratch_txt(str_id);
30        unsafe { sys::igOpenPopup_Str(str_id_ptr, PopupFlags::NONE.bits()) }
31    }
32
33    /// Instructs ImGui that a popup is open with flags.
34    #[doc(alias = "OpenPopup")]
35    pub fn open_popup_with_flags(&self, str_id: impl AsRef<str>, flags: PopupFlags) {
36        let str_id_ptr = self.scratch_txt(str_id);
37        unsafe {
38            sys::igOpenPopup_Str(str_id_ptr, flags.bits());
39        }
40    }
41
42    /// Construct a popup that can have any kind of content.
43    ///
44    /// This should be called *per frame*, whereas [`open_popup`](Self::open_popup) should be called *once*
45    /// to signal that this popup is active.
46    #[doc(alias = "BeginPopup")]
47    pub fn begin_popup(&self, str_id: impl AsRef<str>) -> Option<PopupToken<'_>> {
48        self.begin_popup_with_flags(str_id, WindowFlags::empty())
49    }
50
51    /// Construct a popup with window flags.
52    #[doc(alias = "BeginPopup")]
53    pub fn begin_popup_with_flags(
54        &self,
55        str_id: impl AsRef<str>,
56        flags: WindowFlags,
57    ) -> Option<PopupToken<'_>> {
58        let str_id_ptr = self.scratch_txt(str_id);
59        let render = unsafe { sys::igBeginPopup(str_id_ptr, flags.bits()) };
60
61        if render {
62            Some(PopupToken::new(self))
63        } else {
64            None
65        }
66    }
67
68    /// Construct a popup that can have any kind of content.
69    ///
70    /// This should be called *per frame*, whereas [`open_popup`](Self::open_popup) should be called *once*
71    /// to signal that this popup is active.
72    #[doc(alias = "BeginPopup")]
73    pub fn popup<F>(&self, str_id: impl AsRef<str>, f: F)
74    where
75        F: FnOnce(),
76    {
77        if let Some(_token) = self.begin_popup(str_id) {
78            f();
79        }
80    }
81
82    /// Creates a modal popup.
83    ///
84    /// Modal popups block interaction with the rest of the application until closed.
85    #[doc(alias = "BeginPopupModal")]
86    pub fn begin_modal_popup(&self, name: impl AsRef<str>) -> Option<ModalPopupToken<'_>> {
87        let name_ptr = self.scratch_txt(name);
88        let render = unsafe {
89            sys::igBeginPopupModal(name_ptr, std::ptr::null_mut(), WindowFlags::empty().bits())
90        };
91
92        if render {
93            Some(ModalPopupToken::new(self))
94        } else {
95            None
96        }
97    }
98
99    /// Creates a modal popup builder.
100    pub fn begin_modal_popup_config<'a>(&'a self, name: &'a str) -> ModalPopup<'a> {
101        ModalPopup {
102            name,
103            opened: None,
104            flags: WindowFlags::empty(),
105            ui: self,
106        }
107    }
108
109    /// Creates a modal popup and runs a closure to construct the contents.
110    ///
111    /// Returns the result of the closure if the popup is open.
112    pub fn modal_popup<F, R>(&self, name: impl AsRef<str>, f: F) -> Option<R>
113    where
114        F: FnOnce() -> R,
115    {
116        self.begin_modal_popup(name).map(|_token| f())
117    }
118
119    /// Closes the current popup.
120    #[doc(alias = "CloseCurrentPopup")]
121    pub fn close_current_popup(&self) {
122        unsafe {
123            sys::igCloseCurrentPopup();
124        }
125    }
126
127    /// Returns true if the popup is open.
128    #[doc(alias = "IsPopupOpen")]
129    pub fn is_popup_open(&self, str_id: impl AsRef<str>) -> bool {
130        let str_id_ptr = self.scratch_txt(str_id);
131        unsafe { sys::igIsPopupOpen_Str(str_id_ptr, PopupFlags::NONE.bits()) }
132    }
133
134    /// Returns true if the popup is open with flags.
135    #[doc(alias = "IsPopupOpen")]
136    pub fn is_popup_open_with_flags(&self, str_id: impl AsRef<str>, flags: PopupFlags) -> bool {
137        let str_id_ptr = self.scratch_txt(str_id);
138        unsafe { sys::igIsPopupOpen_Str(str_id_ptr, flags.bits()) }
139    }
140
141    /// Begin a popup context menu for the last item.
142    /// This is typically used with right-click context menus.
143    #[doc(alias = "BeginPopupContextItem")]
144    pub fn begin_popup_context_item(&self) -> Option<PopupToken<'_>> {
145        self.begin_popup_context_item_with_label(None)
146    }
147
148    /// Begin a popup context menu for the last item with a custom label.
149    #[doc(alias = "BeginPopupContextItem")]
150    pub fn begin_popup_context_item_with_label(
151        &self,
152        str_id: Option<&str>,
153    ) -> Option<PopupToken<'_>> {
154        let str_id_ptr = str_id
155            .map(|s| self.scratch_txt(s))
156            .unwrap_or(std::ptr::null());
157
158        let render = unsafe {
159            sys::igBeginPopupContextItem(str_id_ptr, PopupFlags::MOUSE_BUTTON_RIGHT.bits())
160        };
161
162        if render {
163            Some(PopupToken::new(self))
164        } else {
165            None
166        }
167    }
168
169    /// Begin a popup context menu for the current window.
170    #[doc(alias = "BeginPopupContextWindow")]
171    pub fn begin_popup_context_window(&self) -> Option<PopupToken<'_>> {
172        self.begin_popup_context_window_with_label(None)
173    }
174
175    /// Begin a popup context menu for the current window with a custom label.
176    #[doc(alias = "BeginPopupContextWindow")]
177    pub fn begin_popup_context_window_with_label(
178        &self,
179        str_id: Option<&str>,
180    ) -> Option<PopupToken<'_>> {
181        let str_id_ptr = str_id
182            .map(|s| self.scratch_txt(s))
183            .unwrap_or(std::ptr::null());
184
185        let render = unsafe {
186            sys::igBeginPopupContextWindow(str_id_ptr, PopupFlags::MOUSE_BUTTON_RIGHT.bits())
187        };
188
189        if render {
190            Some(PopupToken::new(self))
191        } else {
192            None
193        }
194    }
195
196    /// Begin a popup context menu for empty space (void).
197    #[doc(alias = "BeginPopupContextVoid")]
198    pub fn begin_popup_context_void(&self) -> Option<PopupToken<'_>> {
199        self.begin_popup_context_void_with_label(None)
200    }
201
202    /// Begin a popup context menu for empty space with a custom label.
203    #[doc(alias = "BeginPopupContextVoid")]
204    pub fn begin_popup_context_void_with_label(
205        &self,
206        str_id: Option<&str>,
207    ) -> Option<PopupToken<'_>> {
208        let str_id_ptr = str_id
209            .map(|s| self.scratch_txt(s))
210            .unwrap_or(std::ptr::null());
211
212        let render = unsafe {
213            sys::igBeginPopupContextVoid(str_id_ptr, PopupFlags::MOUSE_BUTTON_RIGHT.bits())
214        };
215
216        if render {
217            Some(PopupToken::new(self))
218        } else {
219            None
220        }
221    }
222}
223
224bitflags::bitflags! {
225    /// Flags for popup functions
226    #[repr(transparent)]
227    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
228    pub struct PopupFlags: i32 {
229        /// No flags
230        const NONE = sys::ImGuiPopupFlags_None as i32;
231        /// For BeginPopupContext*(): open on Left Mouse release. Guaranteed to always be == 0 (same as ImGuiMouseButton_Left)
232        const MOUSE_BUTTON_LEFT = sys::ImGuiPopupFlags_MouseButtonLeft as i32;
233        /// For BeginPopupContext*(): open on Right Mouse release. Guaranteed to always be == 1 (same as ImGuiMouseButton_Right)
234        const MOUSE_BUTTON_RIGHT = sys::ImGuiPopupFlags_MouseButtonRight as i32;
235        /// For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle)
236        const MOUSE_BUTTON_MIDDLE = sys::ImGuiPopupFlags_MouseButtonMiddle as i32;
237        /// For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack
238        const NO_OPEN_OVER_EXISTING_POPUP = sys::ImGuiPopupFlags_NoOpenOverExistingPopup as i32;
239        /// For BeginPopupContext*(): don't return true when hovering items, only when hovering empty space
240        const NO_OPEN_OVER_ITEMS = sys::ImGuiPopupFlags_NoOpenOverItems as i32;
241        /// For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup
242        const ANY_POPUP_ID = sys::ImGuiPopupFlags_AnyPopupId as i32;
243        /// For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level)
244        const ANY_POPUP_LEVEL = sys::ImGuiPopupFlags_AnyPopupLevel as i32;
245        /// For IsPopupOpen(): test for any popup
246        const ANY_POPUP = Self::ANY_POPUP_ID.bits() | Self::ANY_POPUP_LEVEL.bits();
247    }
248}
249
250/// Builder for a modal popup
251#[derive(Debug)]
252#[must_use]
253pub struct ModalPopup<'ui> {
254    name: &'ui str,
255    opened: Option<&'ui mut bool>,
256    flags: WindowFlags,
257    ui: &'ui Ui,
258}
259
260impl<'ui> ModalPopup<'ui> {
261    /// Sets the opened state tracking variable
262    pub fn opened(mut self, opened: &'ui mut bool) -> Self {
263        self.opened = Some(opened);
264        self
265    }
266
267    /// Sets the window flags
268    pub fn flags(mut self, flags: WindowFlags) -> Self {
269        self.flags = flags;
270        self
271    }
272
273    /// Begins the modal popup
274    pub fn begin(self) -> Option<ModalPopupToken<'ui>> {
275        let name_ptr = self.ui.scratch_txt(self.name);
276        let opened_ptr = self
277            .opened
278            .map(|o| o as *mut bool)
279            .unwrap_or(std::ptr::null_mut());
280
281        let render = unsafe { sys::igBeginPopupModal(name_ptr, opened_ptr, self.flags.bits()) };
282
283        if render {
284            Some(ModalPopupToken::new(self.ui))
285        } else {
286            None
287        }
288    }
289}
290
291/// Tracks a popup that can be ended by calling `.end()` or by dropping
292#[must_use]
293pub struct PopupToken<'ui> {
294    ui: &'ui Ui,
295}
296
297impl<'ui> PopupToken<'ui> {
298    /// Creates a new popup token
299    fn new(ui: &'ui Ui) -> Self {
300        PopupToken { ui }
301    }
302
303    /// Ends the popup
304    pub fn end(self) {
305        // The drop implementation will handle the actual ending
306    }
307}
308
309impl<'ui> Drop for PopupToken<'ui> {
310    fn drop(&mut self) {
311        unsafe {
312            sys::igEndPopup();
313        }
314    }
315}
316
317/// Tracks a modal popup that can be ended by calling `.end()` or by dropping
318#[must_use]
319pub struct ModalPopupToken<'ui> {
320    ui: &'ui Ui,
321}
322
323impl<'ui> ModalPopupToken<'ui> {
324    /// Creates a new modal popup token
325    fn new(ui: &'ui Ui) -> Self {
326        ModalPopupToken { ui }
327    }
328
329    /// Ends the modal popup
330    pub fn end(self) {
331        // The drop implementation will handle the actual ending
332    }
333}
334
335impl<'ui> Drop for ModalPopupToken<'ui> {
336    fn drop(&mut self) {
337        unsafe {
338            sys::igEndPopup();
339        }
340    }
341}