dear_imgui/widget/
popup.rs

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