Skip to main content

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