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)]
26use crate::sys;
27use crate::{Ui, WindowFlags};
28use std::ffi::CString;
29
30bitflags::bitflags! {
31    /// Configuration flags for child windows
32    #[repr(transparent)]
33    pub struct ChildFlags: u32 {
34        /// No flags
35        const NONE = 0;
36        /// Show an outer border and enable WindowPadding
37        const BORDERS = 1 << 0;
38        /// Pad with style.WindowPadding even if no border are drawn
39        const ALWAYS_USE_WINDOW_PADDING = 1 << 1;
40        /// Allow resize from right border
41        const RESIZE_X = 1 << 2;
42        /// Allow resize from bottom border
43        const RESIZE_Y = 1 << 3;
44        /// Enable auto-resizing width
45        const AUTO_RESIZE_X = 1 << 4;
46        /// Enable auto-resizing height
47        const AUTO_RESIZE_Y = 1 << 5;
48        /// Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden
49        const ALWAYS_AUTO_RESIZE = 1 << 6;
50        /// Style the child window like a framed item
51        const FRAME_STYLE = 1 << 7;
52        /// Share focus scope, allow gamepad/keyboard navigation to cross over parent border
53        const NAV_FLATTENED = 1 << 8;
54    }
55}
56
57/// Represents a child window that can be built
58pub struct ChildWindow<'ui> {
59    name: String,
60    size: [f32; 2],
61    child_flags: ChildFlags,
62    flags: WindowFlags,
63    _phantom: std::marker::PhantomData<&'ui ()>,
64}
65
66impl<'ui> ChildWindow<'ui> {
67    /// Creates a new child window builder
68    pub fn new(name: impl Into<String>) -> Self {
69        Self {
70            name: name.into(),
71            size: [0.0, 0.0],
72            child_flags: ChildFlags::NONE,
73            flags: WindowFlags::empty(),
74            _phantom: std::marker::PhantomData,
75        }
76    }
77
78    /// Sets the size of the child window
79    pub fn size(mut self, size: [f32; 2]) -> Self {
80        self.size = size;
81        self
82    }
83
84    /// Sets whether the child window has a border
85    pub fn border(mut self, border: bool) -> Self {
86        self.child_flags.set(ChildFlags::BORDERS, border);
87        self
88    }
89
90    /// Sets child flags for the child window
91    pub fn child_flags(mut self, child_flags: ChildFlags) -> Self {
92        self.child_flags = child_flags;
93        self
94    }
95
96    /// Sets window flags for the child window
97    pub fn flags(mut self, flags: WindowFlags) -> Self {
98        self.flags = flags;
99        self
100    }
101
102    /// Builds the child window and calls the provided closure
103    pub fn build<F, R>(self, ui: &'ui Ui, f: F) -> Option<R>
104    where
105        F: FnOnce() -> R,
106    {
107        let token = self.begin(ui)?;
108        let result = f();
109        drop(token); // Explicitly drop the token to call EndChild
110        Some(result)
111    }
112
113    /// Begins the child window and returns a token
114    fn begin(self, _ui: &'ui Ui) -> Option<ChildWindowToken<'ui>> {
115        let name_cstr = CString::new(self.name).ok()?;
116
117        let result = unsafe {
118            let size_vec = sys::ImVec2 {
119                x: self.size[0],
120                y: self.size[1],
121            };
122            sys::igBeginChild_Str(
123                name_cstr.as_ptr(),
124                size_vec,
125                self.child_flags.bits() as i32,
126                self.flags.bits(),
127            )
128        };
129
130        // IMPORTANT: According to ImGui documentation, BeginChild/EndChild are inconsistent
131        // with other Begin/End functions. EndChild() must ALWAYS be called regardless of
132        // what BeginChild() returns. However, if BeginChild returns false, EndChild must
133        // be called immediately and no content should be rendered.
134        if result {
135            Some(ChildWindowToken {
136                _phantom: std::marker::PhantomData,
137            })
138        } else {
139            // If BeginChild returns false, call EndChild immediately and return None
140            unsafe {
141                sys::igEndChild();
142            }
143            None
144        }
145    }
146}
147
148/// Token representing an active child window
149pub struct ChildWindowToken<'ui> {
150    _phantom: std::marker::PhantomData<&'ui ()>,
151}
152
153impl<'ui> Drop for ChildWindowToken<'ui> {
154    fn drop(&mut self) {
155        unsafe {
156            sys::igEndChild();
157        }
158    }
159}
160
161impl Ui {
162    /// Creates a child window builder
163    pub fn child_window(&self, name: impl Into<String>) -> ChildWindow<'_> {
164        ChildWindow::new(name)
165    }
166}