Skip to main content

dear_imgui_rs/window/
mod.rs

1//! Windows and window utilities
2//!
3//! This module exposes the `Window` builder and related flags for creating
4//! top-level Dear ImGui windows. It also houses helpers for child windows,
5//! querying content-region size, and controlling window scrolling.
6//!
7//! Basic usage:
8//! ```no_run
9//! # use dear_imgui_rs::*;
10//! # let mut ctx = Context::create();
11//! # let ui = ctx.frame();
12//! ui.window("Hello")
13//!     .size([320.0, 240.0], Condition::FirstUseEver)
14//!     .position([60.0, 60.0], Condition::FirstUseEver)
15//!     .build(|| {
16//!         ui.text("Window contents go here");
17//!     });
18//! ```
19//!
20//! See also:
21//! - `child_window` for scoped child areas
22//! - `content_region` for available size queries
23//! - `scroll` for reading and setting scroll positions
24//!
25//! Quick example (flags + size/pos conditions):
26//! ```no_run
27//! # use dear_imgui_rs::*;
28//! # let mut ctx = Context::create();
29//! # let ui = ctx.frame();
30//! use dear_imgui_rs::WindowFlags;
31//! ui.window("Tools")
32//!     .flags(WindowFlags::NO_RESIZE | WindowFlags::NO_COLLAPSE)
33//!     .size([300.0, 200.0], Condition::FirstUseEver)
34//!     .position([50.0, 60.0], Condition::FirstUseEver)
35//!     .build(|| {
36//!         ui.text("Toolbox contents...");
37//!     });
38//! ```
39//!
40#![allow(
41    clippy::cast_possible_truncation,
42    clippy::cast_sign_loss,
43    clippy::as_conversions
44)]
45use bitflags::bitflags;
46use std::borrow::Cow;
47use std::f32;
48
49use crate::sys;
50use crate::{Condition, Ui};
51#[cfg(feature = "serde")]
52use serde::{Deserialize, Serialize};
53
54mod child_window;
55pub(crate) mod content_region;
56pub(crate) mod scroll;
57
58pub use child_window::{ChildFlags, ChildWindow, ChildWindowToken};
59
60// Window-focused/hovered helpers are available via utils.rs variants.
61// Window hovered/focused flag helpers are provided by crate::utils.
62
63bitflags! {
64    /// Configuration flags for windows
65    #[repr(transparent)]
66    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
67    pub struct WindowFlags: i32 {
68        /// Disable title-bar
69        const NO_TITLE_BAR = sys::ImGuiWindowFlags_NoTitleBar as i32;
70        /// Disable user resizing with the lower-right grip
71        const NO_RESIZE = sys::ImGuiWindowFlags_NoResize as i32;
72        /// Disable user moving the window
73        const NO_MOVE = sys::ImGuiWindowFlags_NoMove as i32;
74        /// Disable scrollbars (window can still scroll with mouse or programmatically)
75        const NO_SCROLLBAR = sys::ImGuiWindowFlags_NoScrollbar as i32;
76        /// Disable user vertically scrolling with mouse wheel
77        const NO_SCROLL_WITH_MOUSE = sys::ImGuiWindowFlags_NoScrollWithMouse as i32;
78        /// Disable user collapsing window by double-clicking on it
79        const NO_COLLAPSE = sys::ImGuiWindowFlags_NoCollapse as i32;
80        /// Resize every window to its content every frame
81        const ALWAYS_AUTO_RESIZE = sys::ImGuiWindowFlags_AlwaysAutoResize as i32;
82        /// Disable drawing background color (WindowBg, etc.) and outside border
83        const NO_BACKGROUND = sys::ImGuiWindowFlags_NoBackground as i32;
84        /// Never load/save settings in .ini file
85        const NO_SAVED_SETTINGS = sys::ImGuiWindowFlags_NoSavedSettings as i32;
86        /// Disable catching mouse, hovering test with pass through
87        const NO_MOUSE_INPUTS = sys::ImGuiWindowFlags_NoMouseInputs as i32;
88        /// Has a menu-bar
89        const MENU_BAR = sys::ImGuiWindowFlags_MenuBar as i32;
90        /// Allow horizontal scrollbar to appear (off by default)
91        const HORIZONTAL_SCROLLBAR = sys::ImGuiWindowFlags_HorizontalScrollbar as i32;
92        /// Disable taking focus when transitioning from hidden to visible state
93        const NO_FOCUS_ON_APPEARING = sys::ImGuiWindowFlags_NoFocusOnAppearing as i32;
94        /// Disable bringing window to front when taking focus (e.g. clicking on it or programmatically giving it focus)
95        const NO_BRING_TO_FRONT_ON_FOCUS = sys::ImGuiWindowFlags_NoBringToFrontOnFocus as i32;
96        /// Always show vertical scrollbar (even if ContentSize.y < Size.y)
97        const ALWAYS_VERTICAL_SCROLLBAR = sys::ImGuiWindowFlags_AlwaysVerticalScrollbar as i32;
98        /// Always show horizontal scrollbar (even if ContentSize.x < Size.x)
99        const ALWAYS_HORIZONTAL_SCROLLBAR = sys::ImGuiWindowFlags_AlwaysHorizontalScrollbar as i32;
100        /// No gamepad/keyboard navigation within the window
101        const NO_NAV_INPUTS = sys::ImGuiWindowFlags_NoNavInputs as i32;
102        /// No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB)
103        const NO_NAV_FOCUS = sys::ImGuiWindowFlags_NoNavFocus as i32;
104        /// Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar.
105        const UNSAVED_DOCUMENT = sys::ImGuiWindowFlags_UnsavedDocument as i32;
106        // Docking related flags
107        /// Disable docking for this window (the window will not be able to dock into another and others won't be able to dock into it)
108        const NO_DOCKING = sys::ImGuiWindowFlags_NoDocking as i32;
109        /// Disable gamepad/keyboard navigation and focusing
110        const NO_NAV = Self::NO_NAV_INPUTS.bits() | Self::NO_NAV_FOCUS.bits();
111        /// Disable all window decorations
112        const NO_DECORATION = Self::NO_TITLE_BAR.bits() | Self::NO_RESIZE.bits() | Self::NO_SCROLLBAR.bits() | Self::NO_COLLAPSE.bits();
113        /// Disable all user interactions
114        const NO_INPUTS = Self::NO_MOUSE_INPUTS.bits() | Self::NO_NAV_INPUTS.bits();
115    }
116}
117
118pub(crate) fn validate_window_flags(caller: &str, flags: WindowFlags) {
119    let unsupported = flags.bits() & !WindowFlags::all().bits();
120    assert!(
121        unsupported == 0,
122        "{caller} received unsupported ImGuiWindowFlags bits: 0x{unsupported:X}"
123    );
124}
125
126fn assert_finite_vec2(caller: &str, name: &str, value: [f32; 2]) {
127    assert!(
128        value[0].is_finite() && value[1].is_finite(),
129        "{caller} {name} must contain finite values"
130    );
131}
132
133#[cfg(feature = "serde")]
134impl Serialize for WindowFlags {
135    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
136    where
137        S: serde::Serializer,
138    {
139        serializer.serialize_i32(self.bits())
140    }
141}
142
143#[cfg(feature = "serde")]
144impl<'de> Deserialize<'de> for WindowFlags {
145    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
146    where
147        D: serde::Deserializer<'de>,
148    {
149        let bits = i32::deserialize(deserializer)?;
150        Ok(WindowFlags::from_bits_retain(bits))
151    }
152}
153
154/// Represents a window that can be built
155pub struct Window<'ui> {
156    ui: &'ui Ui,
157    name: Cow<'ui, str>,
158    opened: Option<&'ui mut bool>,
159    flags: WindowFlags,
160    size: Option<[f32; 2]>,
161    size_condition: Condition,
162    size_constraints: Option<([f32; 2], [f32; 2])>,
163    pos: Option<[f32; 2]>,
164    pos_condition: Condition,
165    content_size: Option<[f32; 2]>,
166    collapsed: Option<bool>,
167    collapsed_condition: Condition,
168    focused: Option<bool>,
169    bg_alpha: Option<f32>,
170    scroll: Option<[f32; 2]>,
171}
172
173impl<'ui> Window<'ui> {
174    /// Creates a new window builder
175    pub fn new(ui: &'ui Ui, name: impl Into<Cow<'ui, str>>) -> Self {
176        Self {
177            ui,
178            name: name.into(),
179            opened: None,
180            flags: WindowFlags::empty(),
181            size: None,
182            size_condition: Condition::Always,
183            size_constraints: None,
184            pos: None,
185            pos_condition: Condition::Always,
186            content_size: None,
187            collapsed: None,
188            collapsed_condition: Condition::Always,
189            focused: None,
190            bg_alpha: None,
191            scroll: None,
192        }
193    }
194
195    /// Sets window flags
196    pub fn flags(mut self, flags: WindowFlags) -> Self {
197        self.flags = flags;
198        self
199    }
200
201    /// Controls whether the window is open (adds a title-bar close button).
202    ///
203    /// In Dear ImGui, a window is "closed" by the user by toggling the `p_open` boolean.
204    /// When the close button (X) is pressed, `opened` will be set to `false`.
205    ///
206    /// Note: as an immediate-mode UI, you should stop submitting this window when
207    /// `*opened == false` (typically by guarding the `window(...).build(...)` call).
208    #[doc(alias = "Begin")]
209    pub fn opened(mut self, opened: &'ui mut bool) -> Self {
210        self.opened = Some(opened);
211        self
212    }
213
214    /// Sets window size
215    pub fn size(mut self, size: [f32; 2], condition: Condition) -> Self {
216        self.size = Some(size);
217        self.size_condition = condition;
218        self
219    }
220
221    /// Sets window size constraints for the next Begin call.
222    ///
223    /// This is a convenience wrapper over `ImGui::SetNextWindowSizeConstraints`
224    /// without a custom size callback.
225    #[doc(alias = "SetNextWindowSizeConstraints")]
226    pub fn size_constraints(mut self, min: [f32; 2], max: [f32; 2]) -> Self {
227        self.size_constraints = Some((min, max));
228        self
229    }
230
231    /// Sets window position
232    pub fn position(mut self, pos: [f32; 2], condition: Condition) -> Self {
233        self.pos = Some(pos);
234        self.pos_condition = condition;
235        self
236    }
237
238    /// Sets window content size
239    pub fn content_size(mut self, size: [f32; 2]) -> Self {
240        self.content_size = Some(size);
241        self
242    }
243
244    /// Sets window collapsed state
245    pub fn collapsed(mut self, collapsed: bool, condition: Condition) -> Self {
246        self.collapsed = Some(collapsed);
247        self.collapsed_condition = condition;
248        self
249    }
250
251    /// Sets window focused state
252    pub fn focused(mut self, focused: bool) -> Self {
253        self.focused = Some(focused);
254        self
255    }
256
257    /// Sets window background alpha
258    pub fn bg_alpha(mut self, alpha: f32) -> Self {
259        self.bg_alpha = Some(alpha);
260        self
261    }
262
263    /// Sets the initial scroll position for the next Begin call.
264    #[doc(alias = "SetNextWindowScroll")]
265    pub fn scroll(mut self, scroll: [f32; 2]) -> Self {
266        self.scroll = Some(scroll);
267        self
268    }
269
270    /// Builds the window and calls the provided closure
271    pub fn build<F, R>(self, f: F) -> Option<R>
272    where
273        F: FnOnce() -> R,
274    {
275        let _token = self.begin()?;
276        Some(f())
277    }
278
279    /// Begins the window and returns a token
280    fn begin(self) -> Option<WindowToken<'ui>> {
281        let name = self.name;
282        let name_ptr = self.ui.scratch_txt(name);
283        validate_window_flags("Window::begin()", self.flags);
284
285        // Set window properties before beginning
286        self.ui.run_with_bound_context(|| {
287            if let Some(size) = self.size {
288                assert_finite_vec2("Window::begin()", "size", size);
289                unsafe {
290                    let size_vec = crate::sys::ImVec2 {
291                        x: size[0],
292                        y: size[1],
293                    };
294                    crate::sys::igSetNextWindowSize(size_vec, self.size_condition as i32);
295                }
296            }
297
298            if let Some((min, max)) = self.size_constraints {
299                assert_finite_vec2("Window::begin()", "minimum size constraint", min);
300                assert_finite_vec2("Window::begin()", "maximum size constraint", max);
301                unsafe {
302                    let min_vec = sys::ImVec2_c {
303                        x: min[0],
304                        y: min[1],
305                    };
306                    let max_vec = sys::ImVec2_c {
307                        x: max[0],
308                        y: max[1],
309                    };
310                    sys::igSetNextWindowSizeConstraints(
311                        min_vec,
312                        max_vec,
313                        None,
314                        std::ptr::null_mut(),
315                    );
316                }
317            }
318
319            if let Some(pos) = self.pos {
320                assert_finite_vec2("Window::begin()", "position", pos);
321                unsafe {
322                    let pos_vec = crate::sys::ImVec2 {
323                        x: pos[0],
324                        y: pos[1],
325                    };
326                    let pivot_vec = crate::sys::ImVec2 { x: 0.0, y: 0.0 };
327                    crate::sys::igSetNextWindowPos(pos_vec, self.pos_condition as i32, pivot_vec);
328                }
329            }
330
331            if let Some(content_size) = self.content_size {
332                assert_finite_vec2("Window::begin()", "content size", content_size);
333                unsafe {
334                    let content_size_vec = crate::sys::ImVec2 {
335                        x: content_size[0],
336                        y: content_size[1],
337                    };
338                    crate::sys::igSetNextWindowContentSize(content_size_vec);
339                }
340            }
341
342            if let Some(collapsed) = self.collapsed {
343                unsafe {
344                    crate::sys::igSetNextWindowCollapsed(
345                        collapsed,
346                        self.collapsed_condition as i32,
347                    );
348                }
349            }
350
351            if let Some(focused) = self.focused
352                && focused
353            {
354                unsafe {
355                    crate::sys::igSetNextWindowFocus();
356                }
357            }
358
359            if let Some(alpha) = self.bg_alpha {
360                assert!(
361                    alpha.is_finite(),
362                    "Window::begin() background alpha must be finite"
363                );
364                unsafe {
365                    crate::sys::igSetNextWindowBgAlpha(alpha);
366                }
367            }
368
369            if let Some(scroll) = self.scroll {
370                assert_finite_vec2("Window::begin()", "scroll", scroll);
371                unsafe {
372                    let scroll_vec = sys::ImVec2_c {
373                        x: scroll[0],
374                        y: scroll[1],
375                    };
376                    sys::igSetNextWindowScroll(scroll_vec);
377                }
378            }
379
380            // Begin the window
381            let mut opened = self.opened;
382            let opened_ptr: *mut bool = match opened.as_deref_mut() {
383                Some(opened) => opened as *mut bool,
384                None => std::ptr::null_mut(),
385            };
386            let result = unsafe { crate::sys::igBegin(name_ptr, opened_ptr, self.flags.bits()) };
387            let is_open = opened.is_none_or(|opened| *opened);
388
389            // IMPORTANT: According to ImGui documentation, Begin/End calls must be balanced.
390            // If Begin returns false, we need to call End immediately and return None.
391            if result && is_open {
392                Some(WindowToken { ui: self.ui })
393            } else {
394                // If Begin returns false, call End immediately and return None
395                unsafe {
396                    crate::sys::igEnd();
397                }
398                None
399            }
400        })
401    }
402}
403
404/// Token representing an active window
405pub struct WindowToken<'ui> {
406    ui: &'ui Ui,
407}
408
409impl<'ui> Drop for WindowToken<'ui> {
410    fn drop(&mut self) {
411        self.ui.run_with_bound_context(|| unsafe {
412            crate::sys::igEnd();
413        });
414    }
415}