Skip to main content

dear_imgui_rs/window/
child_window.rs

1//! Child windows
2//!
3//! Tools for building scrollable, optionally framed child regions within a
4//! parent window. Useful for panels, property views or nested layouts.
5//!
6//! Example:
7//! ```no_run
8//! # use dear_imgui_rs::*;
9//! # let mut ctx = Context::create();
10//! # let ui = ctx.frame();
11//! ui.window("Parent").build(|| {
12//!     ui.child_window("pane")
13//!         .size([200.0, 120.0])
14//!         .border(true)
15//!         .build(&ui, || {
16//!             ui.text("Inside child window");
17//!         });
18//! });
19//! ```
20//!
21#![allow(
22    clippy::cast_possible_truncation,
23    clippy::cast_sign_loss,
24    clippy::as_conversions
25)]
26// NOTE: Keep explicit `as u32` casts when using bindgen-generated flag constants.
27// The exact Rust type of `sys::ImGui*Flags_*` may vary across platforms/toolchains, while our
28// public wrapper APIs intentionally expose fixed underlying integer types.
29use super::validate_window_flags;
30use crate::sys;
31use crate::{Ui, WindowFlags};
32use std::borrow::Cow;
33
34bitflags::bitflags! {
35    /// Configuration flags for child windows
36    #[repr(transparent)]
37    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38    pub struct ChildFlags: u32 {
39        /// No flags
40        const NONE = 0;
41        /// Show an outer border and enable WindowPadding
42        const BORDERS = sys::ImGuiChildFlags_Borders as u32;
43        /// Pad with style.WindowPadding even if no border are drawn
44        const ALWAYS_USE_WINDOW_PADDING = sys::ImGuiChildFlags_AlwaysUseWindowPadding as u32;
45        /// Allow resize from right border
46        const RESIZE_X = sys::ImGuiChildFlags_ResizeX as u32;
47        /// Allow resize from bottom border
48        const RESIZE_Y = sys::ImGuiChildFlags_ResizeY as u32;
49        /// Enable auto-resizing width
50        const AUTO_RESIZE_X = sys::ImGuiChildFlags_AutoResizeX as u32;
51        /// Enable auto-resizing height
52        const AUTO_RESIZE_Y = sys::ImGuiChildFlags_AutoResizeY as u32;
53        /// Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden
54        const ALWAYS_AUTO_RESIZE = sys::ImGuiChildFlags_AlwaysAutoResize as u32;
55        /// Style the child window like a framed item
56        const FRAME_STYLE = sys::ImGuiChildFlags_FrameStyle as u32;
57        /// Share focus scope, allow gamepad/keyboard navigation to cross over parent border
58        const NAV_FLATTENED = sys::ImGuiChildFlags_NavFlattened as u32;
59    }
60}
61
62fn validate_child_flags(caller: &str, child_flags: ChildFlags, window_flags: WindowFlags) {
63    let unsupported = child_flags.bits() & !ChildFlags::all().bits();
64    assert!(
65        unsupported == 0,
66        "{caller} received unsupported ImGuiChildFlags bits: 0x{unsupported:X}"
67    );
68    validate_window_flags(caller, window_flags);
69    assert!(
70        !window_flags.contains(WindowFlags::ALWAYS_AUTO_RESIZE),
71        "{caller} cannot use WindowFlags::ALWAYS_AUTO_RESIZE; use ChildFlags::ALWAYS_AUTO_RESIZE"
72    );
73    if child_flags.contains(ChildFlags::ALWAYS_AUTO_RESIZE) {
74        assert!(
75            !child_flags.intersects(ChildFlags::RESIZE_X | ChildFlags::RESIZE_Y),
76            "{caller} cannot combine ALWAYS_AUTO_RESIZE with RESIZE_X or RESIZE_Y"
77        );
78        assert!(
79            child_flags.intersects(ChildFlags::AUTO_RESIZE_X | ChildFlags::AUTO_RESIZE_Y),
80            "{caller} requires AUTO_RESIZE_X or AUTO_RESIZE_Y when using ALWAYS_AUTO_RESIZE"
81        );
82    }
83}
84
85/// Represents a child window that can be built
86pub struct ChildWindow<'ui> {
87    name: Cow<'ui, str>,
88    size: [f32; 2],
89    child_flags: ChildFlags,
90    flags: WindowFlags,
91    _phantom: std::marker::PhantomData<&'ui Ui>,
92}
93
94impl<'ui> ChildWindow<'ui> {
95    /// Creates a new child window builder
96    pub fn new(name: impl Into<Cow<'ui, str>>) -> Self {
97        Self {
98            name: name.into(),
99            size: [0.0, 0.0],
100            child_flags: ChildFlags::NONE,
101            flags: WindowFlags::empty(),
102            _phantom: std::marker::PhantomData,
103        }
104    }
105
106    /// Sets the size of the child window
107    pub fn size(mut self, size: [f32; 2]) -> Self {
108        self.size = size;
109        self
110    }
111
112    /// Sets whether the child window has a border
113    pub fn border(mut self, border: bool) -> Self {
114        self.child_flags.set(ChildFlags::BORDERS, border);
115        self
116    }
117
118    /// Sets child flags for the child window
119    pub fn child_flags(mut self, child_flags: ChildFlags) -> Self {
120        self.child_flags = child_flags;
121        self
122    }
123
124    /// Sets window flags for the child window
125    pub fn flags(mut self, flags: WindowFlags) -> Self {
126        self.flags = flags;
127        self
128    }
129
130    /// Builds the child window and calls the provided closure
131    #[doc(alias = "BeginChild", alias = "EndChild")]
132    pub fn build<F, R>(self, ui: &'ui Ui, f: F) -> Option<R>
133    where
134        F: FnOnce() -> R,
135    {
136        let token = self.begin(ui)?;
137        let result = f();
138        drop(token); // Explicitly drop the token to call EndChild
139        Some(result)
140    }
141
142    /// Begins the child window and returns a token
143    fn begin(self, ui: &'ui Ui) -> Option<ChildWindowGuard<'ui>> {
144        let name_ptr = ui.scratch_txt(self.name);
145        validate_child_flags("ChildWindow::begin()", self.child_flags, self.flags);
146        assert!(
147            self.size[0].is_finite() && self.size[1].is_finite(),
148            "ChildWindow::begin() size must contain finite values"
149        );
150
151        ui.run_with_bound_context(|| {
152            let result = unsafe {
153                let size_vec = sys::ImVec2 {
154                    x: self.size[0],
155                    y: self.size[1],
156                };
157                sys::igBeginChild_Str(
158                    name_ptr,
159                    size_vec,
160                    self.child_flags.bits() as i32,
161                    self.flags.bits(),
162                )
163            };
164
165            // IMPORTANT: According to ImGui documentation, BeginChild/EndChild are inconsistent
166            // with other Begin/End functions. EndChild() must ALWAYS be called regardless of
167            // what BeginChild() returns. However, if BeginChild returns false, EndChild must
168            // be called immediately and no content should be rendered.
169            if result {
170                Some(ChildWindowGuard::new(ui))
171            } else {
172                // If BeginChild returns false, call EndChild immediately and return None
173                unsafe {
174                    sys::igEndChild();
175                }
176                None
177            }
178        })
179    }
180}
181
182/// Internal guard representing an active child window.
183struct ChildWindowGuard<'ui> {
184    scope: crate::scope::NativeScopeToken<'ui>,
185}
186
187impl<'ui> ChildWindowGuard<'ui> {
188    fn new(ui: &'ui Ui) -> Self {
189        Self {
190            scope: ui
191                .begin_native_scope(crate::scope::NativeScopePop::EndChild, "ChildWindowGuard"),
192        }
193    }
194}
195
196impl Drop for ChildWindowGuard<'_> {
197    fn drop(&mut self) {
198        self.scope.finish();
199    }
200}
201
202impl Ui {
203    /// Creates a child window builder
204    pub fn child_window<'ui>(&'ui self, name: impl Into<Cow<'ui, str>>) -> ChildWindow<'ui> {
205        ChildWindow::new(name)
206    }
207}