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 with an opened-state tracking variable.
100    ///
101    /// Passing `opened` enables the title-bar close button (X). When clicked, ImGui will set
102    /// `*opened = false` and close the popup.
103    ///
104    /// Notes:
105    /// - You still need to call [`open_popup`](Self::open_popup) once to open the modal.
106    /// - To pass window flags, use [`begin_modal_popup_config`](Self::begin_modal_popup_config).
107    #[doc(alias = "BeginPopupModal")]
108    pub fn begin_modal_popup_with_opened(
109        &self,
110        name: impl AsRef<str>,
111        opened: &mut bool,
112    ) -> Option<ModalPopupToken<'_>> {
113        let name_ptr = self.scratch_txt(name);
114        let opened_ptr = opened as *mut bool;
115        let render =
116            unsafe { sys::igBeginPopupModal(name_ptr, opened_ptr, WindowFlags::empty().bits()) };
117
118        if render {
119            Some(ModalPopupToken::new(self))
120        } else {
121            None
122        }
123    }
124
125    /// Creates a modal popup builder.
126    pub fn begin_modal_popup_config<'a>(&'a self, name: &'a str) -> ModalPopup<'a> {
127        ModalPopup {
128            name,
129            opened: None,
130            flags: WindowFlags::empty(),
131            ui: self,
132        }
133    }
134
135    /// Creates a modal popup and runs a closure to construct the contents.
136    ///
137    /// Returns the result of the closure if the popup is open.
138    pub fn modal_popup<F, R>(&self, name: impl AsRef<str>, f: F) -> Option<R>
139    where
140        F: FnOnce() -> R,
141    {
142        self.begin_modal_popup(name).map(|_token| f())
143    }
144
145    /// Creates a modal popup with an opened-state tracking variable and runs a closure to
146    /// construct the contents.
147    ///
148    /// Returns the result of the closure if the popup is open.
149    pub fn modal_popup_with_opened<F, R>(
150        &self,
151        name: impl AsRef<str>,
152        opened: &mut bool,
153        f: F,
154    ) -> Option<R>
155    where
156        F: FnOnce() -> R,
157    {
158        self.begin_modal_popup_with_opened(name, opened)
159            .map(|_token| f())
160    }
161
162    /// Closes the current popup.
163    #[doc(alias = "CloseCurrentPopup")]
164    pub fn close_current_popup(&self) {
165        unsafe {
166            sys::igCloseCurrentPopup();
167        }
168    }
169
170    /// Returns true if the popup is open.
171    #[doc(alias = "IsPopupOpen")]
172    pub fn is_popup_open(&self, str_id: impl AsRef<str>) -> bool {
173        let str_id_ptr = self.scratch_txt(str_id);
174        unsafe { sys::igIsPopupOpen_Str(str_id_ptr, PopupFlags::NONE.bits()) }
175    }
176
177    /// Returns true if the popup is open with flags.
178    #[doc(alias = "IsPopupOpen")]
179    pub fn is_popup_open_with_flags(&self, str_id: impl AsRef<str>, flags: PopupFlags) -> bool {
180        let str_id_ptr = self.scratch_txt(str_id);
181        unsafe { sys::igIsPopupOpen_Str(str_id_ptr, flags.bits()) }
182    }
183
184    /// Begin a popup context menu for the last item.
185    /// This is typically used with right-click context menus.
186    #[doc(alias = "BeginPopupContextItem")]
187    pub fn begin_popup_context_item(&self) -> Option<PopupToken<'_>> {
188        self.begin_popup_context_item_with_label(None)
189    }
190
191    /// Begin a popup context menu for the last item with a custom label.
192    #[doc(alias = "BeginPopupContextItem")]
193    pub fn begin_popup_context_item_with_label(
194        &self,
195        str_id: Option<&str>,
196    ) -> Option<PopupToken<'_>> {
197        let str_id_ptr = str_id
198            .map(|s| self.scratch_txt(s))
199            .unwrap_or(std::ptr::null());
200
201        let render = unsafe {
202            sys::igBeginPopupContextItem(str_id_ptr, PopupFlags::MOUSE_BUTTON_RIGHT.bits())
203        };
204
205        if render {
206            Some(PopupToken::new(self))
207        } else {
208            None
209        }
210    }
211
212    /// Begin a popup context menu for the current window.
213    #[doc(alias = "BeginPopupContextWindow")]
214    pub fn begin_popup_context_window(&self) -> Option<PopupToken<'_>> {
215        self.begin_popup_context_window_with_label(None)
216    }
217
218    /// Begin a popup context menu for the current window with a custom label.
219    #[doc(alias = "BeginPopupContextWindow")]
220    pub fn begin_popup_context_window_with_label(
221        &self,
222        str_id: Option<&str>,
223    ) -> Option<PopupToken<'_>> {
224        let str_id_ptr = str_id
225            .map(|s| self.scratch_txt(s))
226            .unwrap_or(std::ptr::null());
227
228        let render = unsafe {
229            sys::igBeginPopupContextWindow(str_id_ptr, PopupFlags::MOUSE_BUTTON_RIGHT.bits())
230        };
231
232        if render {
233            Some(PopupToken::new(self))
234        } else {
235            None
236        }
237    }
238
239    /// Begin a popup context menu for empty space (void).
240    #[doc(alias = "BeginPopupContextVoid")]
241    pub fn begin_popup_context_void(&self) -> Option<PopupToken<'_>> {
242        self.begin_popup_context_void_with_label(None)
243    }
244
245    /// Begin a popup context menu for empty space with a custom label.
246    #[doc(alias = "BeginPopupContextVoid")]
247    pub fn begin_popup_context_void_with_label(
248        &self,
249        str_id: Option<&str>,
250    ) -> Option<PopupToken<'_>> {
251        let str_id_ptr = str_id
252            .map(|s| self.scratch_txt(s))
253            .unwrap_or(std::ptr::null());
254
255        let render = unsafe {
256            sys::igBeginPopupContextVoid(str_id_ptr, PopupFlags::MOUSE_BUTTON_RIGHT.bits())
257        };
258
259        if render {
260            Some(PopupToken::new(self))
261        } else {
262            None
263        }
264    }
265}
266
267bitflags::bitflags! {
268    /// Flags for popup functions
269    #[repr(transparent)]
270    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
271    pub struct PopupFlags: i32 {
272        /// No flags
273        const NONE = sys::ImGuiPopupFlags_None as i32;
274        /// For BeginPopupContext*(): open on Left Mouse release. Guaranteed to always be == 0 (same as ImGuiMouseButton_Left)
275        const MOUSE_BUTTON_LEFT = sys::ImGuiPopupFlags_MouseButtonLeft as i32;
276        /// For BeginPopupContext*(): open on Right Mouse release. Guaranteed to always be == 1 (same as ImGuiMouseButton_Right)
277        const MOUSE_BUTTON_RIGHT = sys::ImGuiPopupFlags_MouseButtonRight as i32;
278        /// For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle)
279        const MOUSE_BUTTON_MIDDLE = sys::ImGuiPopupFlags_MouseButtonMiddle as i32;
280        /// For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack
281        const NO_OPEN_OVER_EXISTING_POPUP = sys::ImGuiPopupFlags_NoOpenOverExistingPopup as i32;
282        /// For BeginPopupContext*(): don't return true when hovering items, only when hovering empty space
283        const NO_OPEN_OVER_ITEMS = sys::ImGuiPopupFlags_NoOpenOverItems as i32;
284        /// For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup
285        const ANY_POPUP_ID = sys::ImGuiPopupFlags_AnyPopupId as i32;
286        /// For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level)
287        const ANY_POPUP_LEVEL = sys::ImGuiPopupFlags_AnyPopupLevel as i32;
288        /// For IsPopupOpen(): test for any popup
289        const ANY_POPUP = Self::ANY_POPUP_ID.bits() | Self::ANY_POPUP_LEVEL.bits();
290    }
291}
292
293/// Builder for a modal popup
294#[derive(Debug)]
295#[must_use]
296pub struct ModalPopup<'ui> {
297    name: &'ui str,
298    opened: Option<&'ui mut bool>,
299    flags: WindowFlags,
300    ui: &'ui Ui,
301}
302
303impl<'ui> ModalPopup<'ui> {
304    /// Sets the opened state tracking variable
305    pub fn opened(mut self, opened: &'ui mut bool) -> Self {
306        self.opened = Some(opened);
307        self
308    }
309
310    /// Sets the window flags
311    pub fn flags(mut self, flags: WindowFlags) -> Self {
312        self.flags = flags;
313        self
314    }
315
316    /// Begins the modal popup
317    pub fn begin(self) -> Option<ModalPopupToken<'ui>> {
318        let name_ptr = self.ui.scratch_txt(self.name);
319        let opened_ptr = self
320            .opened
321            .map(|o| o as *mut bool)
322            .unwrap_or(std::ptr::null_mut());
323
324        let render = unsafe { sys::igBeginPopupModal(name_ptr, opened_ptr, self.flags.bits()) };
325
326        if render {
327            Some(ModalPopupToken::new(self.ui))
328        } else {
329            None
330        }
331    }
332}
333
334/// Tracks a popup that can be ended by calling `.end()` or by dropping
335#[must_use]
336pub struct PopupToken<'ui> {
337    ui: &'ui Ui,
338}
339
340impl<'ui> PopupToken<'ui> {
341    /// Creates a new popup token
342    fn new(ui: &'ui Ui) -> Self {
343        PopupToken { ui }
344    }
345
346    /// Ends the popup
347    pub fn end(self) {
348        // The drop implementation will handle the actual ending
349    }
350}
351
352impl<'ui> Drop for PopupToken<'ui> {
353    fn drop(&mut self) {
354        unsafe {
355            sys::igEndPopup();
356        }
357    }
358}
359
360/// Tracks a modal popup that can be ended by calling `.end()` or by dropping
361#[must_use]
362pub struct ModalPopupToken<'ui> {
363    ui: &'ui Ui,
364}
365
366impl<'ui> ModalPopupToken<'ui> {
367    /// Creates a new modal popup token
368    fn new(ui: &'ui Ui) -> Self {
369        ModalPopupToken { ui }
370    }
371
372    /// Ends the modal popup
373    pub fn end(self) {
374        // The drop implementation will handle the actual ending
375    }
376}
377
378impl<'ui> Drop for ModalPopupToken<'ui> {
379    fn drop(&mut self) {
380        unsafe {
381            sys::igEndPopup();
382        }
383    }
384}