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 ()>,
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    pub fn build<F, R>(self, ui: &'ui Ui, f: F) -> Option<R>
132    where
133        F: FnOnce() -> R,
134    {
135        let token = self.begin(ui)?;
136        let result = f();
137        drop(token); // Explicitly drop the token to call EndChild
138        Some(result)
139    }
140
141    /// Begins the child window and returns a token
142    fn begin(self, ui: &'ui Ui) -> Option<ChildWindowToken<'ui>> {
143        let name_ptr = ui.scratch_txt(self.name);
144        validate_child_flags("ChildWindow::begin()", self.child_flags, self.flags);
145        assert!(
146            self.size[0].is_finite() && self.size[1].is_finite(),
147            "ChildWindow::begin() size must contain finite values"
148        );
149
150        let result = unsafe {
151            let size_vec = sys::ImVec2 {
152                x: self.size[0],
153                y: self.size[1],
154            };
155            sys::igBeginChild_Str(
156                name_ptr,
157                size_vec,
158                self.child_flags.bits() as i32,
159                self.flags.bits(),
160            )
161        };
162
163        // IMPORTANT: According to ImGui documentation, BeginChild/EndChild are inconsistent
164        // with other Begin/End functions. EndChild() must ALWAYS be called regardless of
165        // what BeginChild() returns. However, if BeginChild returns false, EndChild must
166        // be called immediately and no content should be rendered.
167        if result {
168            Some(ChildWindowToken {
169                _phantom: std::marker::PhantomData,
170            })
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/// Token representing an active child window
182pub struct ChildWindowToken<'ui> {
183    _phantom: std::marker::PhantomData<&'ui ()>,
184}
185
186impl<'ui> Drop for ChildWindowToken<'ui> {
187    fn drop(&mut self) {
188        unsafe {
189            sys::igEndChild();
190        }
191    }
192}
193
194impl Ui {
195    /// Creates a child window builder
196    pub fn child_window<'ui>(&'ui self, name: impl Into<Cow<'ui, str>>) -> ChildWindow<'ui> {
197        ChildWindow::new(name)
198    }
199}