Skip to main content

dear_imgui_rs/dock_space/
window_class.rs

1use super::flags::WindowClassDockNodeFlags;
2use super::validation::assert_nonzero_id;
3use crate::{Id, sys};
4use std::ptr;
5use thiserror::Error;
6
7/// Parent viewport policy for a docking window class.
8#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum WindowClassParentViewport {
10    /// Use Dear ImGui's default parent viewport behavior.
11    #[default]
12    Default,
13    /// Request the platform backend to avoid parent-child platform windows.
14    NoParent,
15    /// Request a specific parent viewport.
16    Parent(Id),
17}
18
19impl WindowClassParentViewport {
20    fn try_raw(self) -> Result<sys::ImGuiID, WindowClassError> {
21        match self {
22            Self::Default => Ok(!0),
23            Self::NoParent => Ok(0),
24            Self::Parent(id) if id.raw() != 0 => Ok(id.raw()),
25            Self::Parent(_) => Err(WindowClassError::ZeroParentViewportId),
26        }
27    }
28}
29
30/// Validation failure for a [`WindowClass`].
31#[derive(Clone, Debug, Error, PartialEq, Eq)]
32#[non_exhaustive]
33pub enum WindowClassError {
34    #[error("parent viewport ID must be non-zero")]
35    ZeroParentViewportId,
36    #[error("viewport overrides contain unsupported ImGuiViewportFlags bits: 0x{bits:X}")]
37    UnsupportedViewportFlags { bits: i32 },
38    #[error("viewport overrides set and clear the same ImGuiViewportFlags bits: 0x{bits:X}")]
39    OverlappingViewportFlags { bits: i32 },
40    #[error("tab overrides contain unsupported ImGuiTabItemFlags bits: 0x{bits:X}")]
41    UnsupportedTabItemFlags { bits: i32 },
42    #[error("dock-node overrides contain unsupported ImGuiDockNodeFlags bits: 0x{bits:X}")]
43    UnsupportedDockNodeFlags { bits: i32 },
44}
45
46/// Window class for docking configuration.
47///
48/// Native pointer fields are intentionally private so safe code cannot bypass their lifetime
49/// contracts.
50///
51/// ```compile_fail
52/// # use dear_imgui_rs::WindowClass;
53/// let mut class = WindowClass::default();
54/// class.platform_icon_data = None;
55/// ```
56#[derive(Debug, Clone)]
57pub struct WindowClass {
58    /// User class ID. `None` means the default unclassed window class.
59    class_id: Option<Id>,
60    /// Hint for the platform backend parent viewport behavior.
61    parent_viewport: WindowClassParentViewport,
62    /// ID of parent window for shortcut focus route evaluation
63    focus_route_parent_window_id: Option<Id>,
64    /// Viewport flags to set when a window of this class owns a viewport.
65    viewport_flags_override_set: crate::WindowClassViewportFlags,
66    /// Viewport flags to clear when a window of this class owns a viewport.
67    viewport_flags_override_clear: crate::WindowClassViewportFlags,
68    /// Tab item flags to set when a window of this class is submitted into a dock node tab bar.
69    tab_item_flags_override_set: crate::widget::TabItemOptions,
70    /// Dock node flags to set when a window of this class is hosted by a dock node.
71    dock_node_flags_override_set: WindowClassDockNodeFlags,
72    /// Set to true to enforce single floating windows of this class always having their own docking node
73    docking_always_tab_bar: bool,
74    /// Set to true to allow windows of this class to be docked/merged with an unclassed window
75    docking_allow_unclassed: bool,
76    /// Opaque platform-backend icon payload.
77    ///
78    /// Dear ImGui treats this as backend-owned data. Keep the pointed-to allocation valid for as
79    /// long as the platform backend may inspect this window class.
80    platform_icon_data: Option<ptr::NonNull<std::ffi::c_void>>,
81}
82
83impl Default for WindowClass {
84    fn default() -> Self {
85        Self {
86            class_id: None,
87            parent_viewport: WindowClassParentViewport::Default,
88            focus_route_parent_window_id: None,
89            viewport_flags_override_set: crate::WindowClassViewportFlags::empty(),
90            viewport_flags_override_clear: crate::WindowClassViewportFlags::empty(),
91            tab_item_flags_override_set: crate::widget::TabItemOptions::new(),
92            dock_node_flags_override_set: WindowClassDockNodeFlags::NONE,
93            docking_always_tab_bar: false,
94            docking_allow_unclassed: true,
95            platform_icon_data: None,
96        }
97    }
98}
99
100impl WindowClass {
101    /// Creates a new window class with the specified class ID
102    pub fn new(class_id: Id) -> Self {
103        assert_nonzero_id("WindowClass::new()", "class_id", class_id);
104        Self {
105            class_id: Some(class_id),
106            ..Default::default()
107        }
108    }
109
110    /// Returns the user class ID, or `None` for the default unclassed class.
111    pub fn class_id(&self) -> Option<Id> {
112        self.class_id
113    }
114
115    /// Returns the platform parent viewport policy.
116    pub fn parent_viewport_policy(&self) -> WindowClassParentViewport {
117        self.parent_viewport
118    }
119
120    /// Returns the raw focus-route parent window ID, when configured.
121    pub fn focus_route_parent_window_id_raw(&self) -> Option<Id> {
122        self.focus_route_parent_window_id
123    }
124
125    /// Returns the viewport flags this class sets.
126    pub fn viewport_flags_to_set(&self) -> crate::WindowClassViewportFlags {
127        self.viewport_flags_override_set
128    }
129
130    /// Returns the viewport flags this class clears.
131    pub fn viewport_flags_to_clear(&self) -> crate::WindowClassViewportFlags {
132        self.viewport_flags_override_clear
133    }
134
135    /// Returns the tab item options this class applies.
136    pub fn tab_item_options(&self) -> crate::widget::TabItemOptions {
137        self.tab_item_flags_override_set
138    }
139
140    /// Returns the dock node flags this class applies.
141    pub fn dock_node_flags_to_set(&self) -> WindowClassDockNodeFlags {
142        self.dock_node_flags_override_set
143    }
144
145    /// Returns whether single floating windows always receive a tab bar.
146    pub fn always_tab_bar(&self) -> bool {
147        self.docking_always_tab_bar
148    }
149
150    /// Returns whether this class may dock with unclassed windows.
151    pub fn allows_unclassed(&self) -> bool {
152        self.docking_allow_unclassed
153    }
154
155    /// Sets the raw parent viewport policy.
156    ///
157    /// # Safety
158    ///
159    /// For [`WindowClassParentViewport::Parent`], the target viewport must be live when the
160    /// window begins, belong to the same Context, and the resulting parent graph must contain no
161    /// self-edge or cycle. Dear ImGui stores a raw parent pointer and traverses it without cycle
162    /// detection. The `Default` and `NoParent` policies satisfy these requirements inherently.
163    pub unsafe fn parent_viewport(mut self, parent: WindowClassParentViewport) -> Self {
164        self.parent_viewport = parent;
165        self
166    }
167
168    /// Requests the platform backend to avoid parenting this class's platform windows.
169    pub fn no_parent_viewport(mut self) -> Self {
170        self.parent_viewport = WindowClassParentViewport::NoParent;
171        self
172    }
173
174    /// Requests a specific raw parent viewport ID.
175    ///
176    /// # Safety
177    ///
178    /// The target viewport must be live when the window begins, belong to the same Context, and
179    /// the resulting parent graph must contain no self-edge or cycle.
180    ///
181    /// ```compile_fail
182    /// # use dear_imgui_rs::{Id, WindowClass};
183    /// let class = WindowClass::default().parent_viewport_id(Id::from(1u32));
184    /// # let _ = class;
185    /// ```
186    pub unsafe fn parent_viewport_id(mut self, id: Id) -> Self {
187        assert_nonzero_id("WindowClass::parent_viewport_id()", "id", id);
188        self.parent_viewport = WindowClassParentViewport::Parent(id);
189        self
190    }
191
192    /// Sets the raw focus-route parent window ID.
193    ///
194    /// # Safety
195    ///
196    /// A window with `id` must already exist when a window using this class begins. The resulting
197    /// focus route must not contain a cycle and the class must not be applied to the parent window
198    /// itself. Dear ImGui assumes these conditions and may dereference the resolved parent without
199    /// checking it in non-assert builds.
200    ///
201    /// ```compile_fail
202    /// # use dear_imgui_rs::{Id, WindowClass};
203    /// let class = WindowClass::default().focus_route_parent_window_id(Id::from(1u32));
204    /// # let _ = class;
205    /// ```
206    pub unsafe fn focus_route_parent_window_id(mut self, id: Id) -> Self {
207        assert_nonzero_id("WindowClass::focus_route_parent_window_id()", "id", id);
208        self.focus_route_parent_window_id = Some(id);
209        self
210    }
211
212    /// Sets viewport flags when a window of this class owns a viewport.
213    pub fn viewport_flags_override_set(mut self, flags: crate::WindowClassViewportFlags) -> Self {
214        self.viewport_flags_override_set = flags;
215        self
216    }
217
218    /// Clears viewport flags when a window of this class owns a viewport.
219    pub fn viewport_flags_override_clear(mut self, flags: crate::WindowClassViewportFlags) -> Self {
220        self.viewport_flags_override_clear = flags;
221        self
222    }
223
224    /// Sets and clears viewport flags when a window of this class owns a viewport.
225    pub fn viewport_flags_overrides(
226        mut self,
227        set: crate::WindowClassViewportFlags,
228        clear: crate::WindowClassViewportFlags,
229    ) -> Self {
230        self.viewport_flags_override_set = set;
231        self.viewport_flags_override_clear = clear;
232        self
233    }
234
235    /// Sets tab item flags when a window of this class is submitted into a dock node tab bar.
236    pub fn tab_item_flags_override_set(
237        mut self,
238        options: impl Into<crate::widget::TabItemOptions>,
239    ) -> Self {
240        self.tab_item_flags_override_set = options.into();
241        self
242    }
243
244    /// Sets dock node flags when a window of this class is hosted by a dock node.
245    pub fn dock_node_flags_override_set(mut self, flags: WindowClassDockNodeFlags) -> Self {
246        self.dock_node_flags_override_set = flags;
247        self
248    }
249
250    /// Enables always showing tab bar for single floating windows
251    pub fn docking_always_tab_bar(mut self, enabled: bool) -> Self {
252        self.docking_always_tab_bar = enabled;
253        self
254    }
255
256    /// Allows docking with unclassed windows
257    pub fn docking_allow_unclassed(mut self, enabled: bool) -> Self {
258        self.docking_allow_unclassed = enabled;
259        self
260    }
261
262    /// Sets opaque icon data consumed by the platform backend.
263    ///
264    /// # Safety
265    ///
266    /// `data` must remain valid for as long as the platform backend may read it, and it must point
267    /// to the representation expected by that backend.
268    pub unsafe fn platform_icon_data_raw(mut self, data: *mut std::ffi::c_void) -> Self {
269        self.platform_icon_data = ptr::NonNull::new(data);
270        self
271    }
272
273    /// Validate every typed override without touching Dear ImGui state.
274    pub fn validate(&self) -> Result<(), WindowClassError> {
275        let viewport_bits =
276            (self.viewport_flags_override_set | self.viewport_flags_override_clear).bits();
277        let unsupported_viewport = viewport_bits & !crate::WindowClassViewportFlags::all().bits();
278        if unsupported_viewport != 0 {
279            return Err(WindowClassError::UnsupportedViewportFlags {
280                bits: unsupported_viewport,
281            });
282        }
283        let overlap =
284            self.viewport_flags_override_set.bits() & self.viewport_flags_override_clear.bits();
285        if overlap != 0 {
286            return Err(WindowClassError::OverlappingViewportFlags { bits: overlap });
287        }
288        let unsupported_tab =
289            self.tab_item_flags_override_set.flags.bits() & !crate::TabItemFlags::all().bits();
290        if unsupported_tab != 0 {
291            return Err(WindowClassError::UnsupportedTabItemFlags {
292                bits: unsupported_tab,
293            });
294        }
295        let unsupported_dock =
296            self.dock_node_flags_override_set.bits() & !WindowClassDockNodeFlags::all().bits();
297        if unsupported_dock != 0 {
298            return Err(WindowClassError::UnsupportedDockNodeFlags {
299                bits: unsupported_dock,
300            });
301        }
302        self.parent_viewport.try_raw()?;
303        Ok(())
304    }
305
306    pub(crate) fn try_to_imgui(&self) -> Result<sys::ImGuiWindowClass, WindowClassError> {
307        self.validate()?;
308        Ok(sys::ImGuiWindowClass {
309            ClassId: self.class_id.map_or(0, Id::raw),
310            ParentViewportId: self.parent_viewport.try_raw()?,
311            FocusRouteParentWindowId: self.focus_route_parent_window_id.map_or(0, Id::raw),
312            ViewportFlagsOverrideSet: self.viewport_flags_override_set.bits(),
313            ViewportFlagsOverrideClear: self.viewport_flags_override_clear.bits(),
314            TabItemFlagsOverrideSet: self.tab_item_flags_override_set.bits(),
315            DockNodeFlagsOverrideSet: self.dock_node_flags_override_set.bits(),
316            DockingAlwaysTabBar: self.docking_always_tab_bar,
317            DockingAllowUnclassed: self.docking_allow_unclassed,
318            PlatformIconData: self
319                .platform_icon_data
320                .map_or(ptr::null_mut(), ptr::NonNull::as_ptr),
321        })
322    }
323
324    /// Convert to Dear ImGui's internal representation for infallible direct APIs.
325    pub(crate) fn to_imgui(&self, caller: &str) -> sys::ImGuiWindowClass {
326        self.try_to_imgui()
327            .unwrap_or_else(|error| panic!("{caller}: {error}"))
328    }
329}