Skip to main content

dear_imgui_rs/
dock_space.rs

1#![allow(
2    clippy::cast_possible_truncation,
3    clippy::cast_sign_loss,
4    clippy::as_conversions,
5    clippy::unnecessary_cast
6)]
7//! Docking space functionality for Dear ImGui
8//!
9//! This module provides high-level Rust bindings for Dear ImGui's docking system,
10//! allowing you to create dockable windows and manage dock spaces.
11//!
12//! # Notes
13//!
14//! Docking is always enabled in this crate; no feature flag required.
15//!
16//! # Basic Usage
17//!
18//! ```no_run
19//! # use dear_imgui_rs::*;
20//! # let mut ctx = Context::create();
21//! # let ui = ctx.frame();
22//! // Create a dockspace over the main viewport
23//! let dockspace_id = ui.dockspace_over_main_viewport();
24//!
25//! // Dock a window to the dockspace
26//! ui.set_next_window_dock_id(dockspace_id);
27//! ui.window("Tool Window").build(|| {
28//!     ui.text("This window is docked!");
29//! });
30//! ```
31
32use crate::Id;
33use crate::sys;
34use crate::ui::Ui;
35use std::ptr;
36
37bitflags::bitflags! {
38    /// Flags for dock nodes
39    #[repr(transparent)]
40    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41    pub struct DockNodeFlags: i32 {
42        /// No flags
43        const NONE = sys::ImGuiDockNodeFlags_None as i32;
44        /// Don't display the dockspace node but keep it alive. Windows docked into this dockspace node won't be undocked.
45        const KEEP_ALIVE_ONLY = sys::ImGuiDockNodeFlags_KeepAliveOnly as i32;
46        /// Disable docking over the Central Node, which will be always kept empty.
47        const NO_DOCKING_OVER_CENTRAL_NODE = sys::ImGuiDockNodeFlags_NoDockingOverCentralNode as i32;
48        /// Enable passthru dockspace: 1) DockSpace() will render a ImGuiCol_WindowBg background covering everything excepted the Central Node when empty. 2) When Central Node is empty: let inputs pass-through + won't display a DockingEmptyBg background.
49        const PASSTHRU_CENTRAL_NODE = sys::ImGuiDockNodeFlags_PassthruCentralNode as i32;
50        /// Disable other windows/nodes from splitting this node.
51        const NO_DOCKING_SPLIT = sys::ImGuiDockNodeFlags_NoDockingSplit as i32;
52        /// Disable resizing node using the splitter/separators. Useful with programmatically setup dockspaces.
53        const NO_RESIZE = sys::ImGuiDockNodeFlags_NoResize as i32;
54        /// Tab bar will automatically hide when there is a single window in the dock node.
55        const AUTO_HIDE_TAB_BAR = sys::ImGuiDockNodeFlags_AutoHideTabBar as i32;
56        /// Disable undocking this node.
57        const NO_UNDOCKING = sys::ImGuiDockNodeFlags_NoUndocking as i32;
58    }
59}
60
61pub(crate) fn validate_dock_node_flags(caller: &str, flags: DockNodeFlags) {
62    let unsupported = flags.bits() & !DockNodeFlags::all().bits();
63    assert!(
64        unsupported == 0,
65        "{caller} received unsupported ImGuiDockNodeFlags bits: 0x{unsupported:X}"
66    );
67}
68
69pub(crate) fn assert_nonzero_id(caller: &str, name: &str, id: Id) {
70    assert!(id.raw() != 0, "{caller} {name} must be non-zero");
71}
72
73pub(crate) fn assert_finite_vec2(caller: &str, name: &str, value: [f32; 2]) {
74    assert!(
75        value[0].is_finite() && value[1].is_finite(),
76        "{caller} {name} must contain finite values"
77    );
78}
79
80pub(crate) fn assert_positive_finite_vec2(caller: &str, name: &str, value: [f32; 2]) {
81    assert_finite_vec2(caller, name, value);
82    assert!(
83        value[0] > 0.0 && value[1] > 0.0,
84        "{caller} {name} must contain positive values"
85    );
86}
87
88/// Window class for docking configuration
89#[derive(Debug, Clone)]
90pub struct WindowClass {
91    /// User data. 0 = Default class (unclassed). Windows of different classes cannot be docked with each others.
92    pub class_id: sys::ImGuiID,
93    /// Hint for the platform backend. -1: use default. 0: request platform backend to not parent the platform. != 0: request platform backend to create a parent<>child relationship between the platform windows.
94    pub parent_viewport_id: sys::ImGuiID,
95    /// ID of parent window for shortcut focus route evaluation
96    pub focus_route_parent_window_id: sys::ImGuiID,
97    /// Viewport flags to set when a window of this class owns a viewport.
98    pub viewport_flags_override_set: crate::ViewportFlags,
99    /// Viewport flags to clear when a window of this class owns a viewport.
100    pub viewport_flags_override_clear: crate::ViewportFlags,
101    /// Tab item flags to set when a window of this class is submitted into a dock node tab bar.
102    pub tab_item_flags_override_set: crate::widget::TabItemOptions,
103    /// Dock node flags to set when a window of this class is hosted by a dock node.
104    pub dock_node_flags_override_set: DockNodeFlags,
105    /// Set to true to enforce single floating windows of this class always having their own docking node
106    pub docking_always_tab_bar: bool,
107    /// Set to true to allow windows of this class to be docked/merged with an unclassed window
108    pub docking_allow_unclassed: bool,
109    /// Opaque platform-backend icon payload.
110    ///
111    /// Dear ImGui treats this as backend-owned data. Keep the pointed-to allocation valid for as
112    /// long as the platform backend may inspect this window class.
113    pub platform_icon_data: Option<ptr::NonNull<std::ffi::c_void>>,
114}
115
116impl Default for WindowClass {
117    fn default() -> Self {
118        Self {
119            class_id: 0,
120            parent_viewport_id: !0, // -1 as u32
121            focus_route_parent_window_id: 0,
122            viewport_flags_override_set: crate::ViewportFlags::NONE,
123            viewport_flags_override_clear: crate::ViewportFlags::NONE,
124            tab_item_flags_override_set: crate::widget::TabItemOptions::new(),
125            dock_node_flags_override_set: DockNodeFlags::NONE,
126            docking_always_tab_bar: false,
127            docking_allow_unclassed: true,
128            platform_icon_data: None,
129        }
130    }
131}
132
133impl WindowClass {
134    /// Creates a new window class with the specified class ID
135    pub fn new(class_id: sys::ImGuiID) -> Self {
136        Self {
137            class_id,
138            ..Default::default()
139        }
140    }
141
142    /// Sets the parent viewport ID
143    pub fn parent_viewport_id(mut self, id: sys::ImGuiID) -> Self {
144        self.parent_viewport_id = id;
145        self
146    }
147
148    /// Sets the focus route parent window ID
149    pub fn focus_route_parent_window_id(mut self, id: sys::ImGuiID) -> Self {
150        self.focus_route_parent_window_id = id;
151        self
152    }
153
154    /// Sets viewport flags when a window of this class owns a viewport.
155    pub fn viewport_flags_override_set(mut self, flags: crate::ViewportFlags) -> Self {
156        self.viewport_flags_override_set = flags;
157        self
158    }
159
160    /// Clears viewport flags when a window of this class owns a viewport.
161    pub fn viewport_flags_override_clear(mut self, flags: crate::ViewportFlags) -> Self {
162        self.viewport_flags_override_clear = flags;
163        self
164    }
165
166    /// Sets and clears viewport flags when a window of this class owns a viewport.
167    pub fn viewport_flags_overrides(
168        mut self,
169        set: crate::ViewportFlags,
170        clear: crate::ViewportFlags,
171    ) -> Self {
172        self.viewport_flags_override_set = set;
173        self.viewport_flags_override_clear = clear;
174        self
175    }
176
177    /// Sets tab item flags when a window of this class is submitted into a dock node tab bar.
178    pub fn tab_item_flags_override_set(
179        mut self,
180        options: impl Into<crate::widget::TabItemOptions>,
181    ) -> Self {
182        self.tab_item_flags_override_set = options.into();
183        self
184    }
185
186    /// Sets dock node flags when a window of this class is hosted by a dock node.
187    pub fn dock_node_flags_override_set(mut self, flags: DockNodeFlags) -> Self {
188        self.dock_node_flags_override_set = flags;
189        self
190    }
191
192    /// Enables always showing tab bar for single floating windows
193    pub fn docking_always_tab_bar(mut self, enabled: bool) -> Self {
194        self.docking_always_tab_bar = enabled;
195        self
196    }
197
198    /// Allows docking with unclassed windows
199    pub fn docking_allow_unclassed(mut self, enabled: bool) -> Self {
200        self.docking_allow_unclassed = enabled;
201        self
202    }
203
204    /// Sets opaque icon data consumed by the platform backend.
205    ///
206    /// # Safety
207    ///
208    /// `data` must remain valid for as long as the platform backend may read it, and it must point
209    /// to the representation expected by that backend.
210    pub unsafe fn platform_icon_data_raw(mut self, data: *mut std::ffi::c_void) -> Self {
211        self.platform_icon_data = ptr::NonNull::new(data);
212        self
213    }
214
215    fn validate(&self, caller: &str) {
216        crate::io::validate_viewport_flags(
217            caller,
218            self.viewport_flags_override_set | self.viewport_flags_override_clear,
219        );
220        let overlap =
221            self.viewport_flags_override_set.bits() & self.viewport_flags_override_clear.bits();
222        assert!(
223            overlap == 0,
224            "{caller} cannot set and clear the same ImGuiViewportFlags bits: 0x{overlap:X}"
225        );
226        self.tab_item_flags_override_set
227            .validate_for_tab_item(caller);
228        validate_dock_node_flags(caller, self.dock_node_flags_override_set);
229    }
230
231    /// Converts to ImGui's internal representation
232    fn to_imgui(&self, caller: &str) -> sys::ImGuiWindowClass {
233        self.validate(caller);
234        sys::ImGuiWindowClass {
235            ClassId: self.class_id,
236            ParentViewportId: self.parent_viewport_id,
237            FocusRouteParentWindowId: self.focus_route_parent_window_id,
238            ViewportFlagsOverrideSet: self.viewport_flags_override_set.bits(),
239            ViewportFlagsOverrideClear: self.viewport_flags_override_clear.bits(),
240            TabItemFlagsOverrideSet: self.tab_item_flags_override_set.bits(),
241            DockNodeFlagsOverrideSet: self.dock_node_flags_override_set.bits(),
242            DockingAlwaysTabBar: self.docking_always_tab_bar,
243            DockingAllowUnclassed: self.docking_allow_unclassed,
244            PlatformIconData: self
245                .platform_icon_data
246                .map_or(ptr::null_mut(), ptr::NonNull::as_ptr),
247        }
248    }
249}
250
251/// Docking-related functionality
252impl Ui {
253    /// Creates a dockspace over the main viewport
254    ///
255    /// This is a convenience function that creates a dockspace covering the entire main viewport.
256    /// It's equivalent to calling `dock_space` with the main viewport's ID and size.
257    ///
258    /// # Parameters
259    ///
260    /// * `dockspace_id` - The ID for the dockspace (use 0 to auto-generate)
261    /// * `flags` - Dock node flags
262    ///
263    /// # Returns
264    ///
265    /// The ID of the created dockspace
266    ///
267    /// # Example
268    ///
269    /// ```no_run
270    /// # use dear_imgui_rs::*;
271    /// # let mut ctx = Context::create();
272    /// # let ui = ctx.frame();
273    /// let dockspace_id = ui.dockspace_over_main_viewport_with_flags(
274    ///     0.into(),
275    ///     DockNodeFlags::PASSTHRU_CENTRAL_NODE
276    /// );
277    /// ```
278    #[doc(alias = "DockSpaceOverViewport")]
279    pub fn dockspace_over_main_viewport_with_flags(
280        &self,
281        dockspace_id: Id,
282        flags: DockNodeFlags,
283    ) -> Id {
284        validate_dock_node_flags("Ui::dockspace_over_main_viewport_with_flags()", flags);
285        unsafe {
286            Id::from(sys::igDockSpaceOverViewport(
287                dockspace_id.into(),
288                sys::igGetMainViewport(),
289                flags.bits(),
290                ptr::null(),
291            ))
292        }
293    }
294
295    /// Creates a dockspace over the main viewport with default settings
296    ///
297    /// This is a convenience function that creates a dockspace covering the entire main viewport
298    /// with passthrough central node enabled.
299    ///
300    /// # Returns
301    ///
302    /// The ID of the created dockspace
303    ///
304    /// # Example
305    ///
306    /// ```no_run
307    /// # use dear_imgui_rs::*;
308    /// # let mut ctx = Context::create();
309    /// # let ui = ctx.frame();
310    /// let dockspace_id = ui.dockspace_over_main_viewport();
311    /// ```
312    #[doc(alias = "DockSpaceOverViewport")]
313    pub fn dockspace_over_main_viewport(&self) -> Id {
314        self.dockspace_over_main_viewport_with_flags(
315            Id::from(0u32),
316            DockNodeFlags::PASSTHRU_CENTRAL_NODE,
317        )
318    }
319
320    /// Creates a dockspace with the specified ID, size, and flags
321    ///
322    /// # Parameters
323    ///
324    /// * `id` - The non-zero ID for the dockspace. Use [`Ui::get_id`] to create one.
325    /// * `size` - The size of the dockspace in pixels
326    /// * `flags` - Dock node flags
327    /// * `window_class` - Optional window class for docking configuration
328    ///
329    /// # Returns
330    ///
331    /// The ID of the created dockspace
332    ///
333    /// # Example
334    ///
335    /// ```no_run
336    /// # use dear_imgui_rs::*;
337    /// # let mut ctx = Context::create();
338    /// # let ui = ctx.frame();
339    /// let dockspace_id = ui.get_id("MyDockspace");
340    /// let dockspace_id = ui.dock_space_with_class(
341    ///     dockspace_id,
342    ///     [800.0, 600.0],
343    ///     DockNodeFlags::NO_DOCKING_SPLIT,
344    ///     Some(&WindowClass::new(1))
345    /// );
346    /// ```
347    #[doc(alias = "DockSpace")]
348    pub fn dock_space_with_class(
349        &self,
350        id: Id,
351        size: [f32; 2],
352        flags: DockNodeFlags,
353        window_class: Option<&WindowClass>,
354    ) -> Id {
355        validate_dock_node_flags("Ui::dock_space_with_class()", flags);
356        assert_nonzero_id("Ui::dock_space_with_class()", "id", id);
357        assert_finite_vec2("Ui::dock_space_with_class()", "size", size);
358        unsafe {
359            let size_vec = sys::ImVec2 {
360                x: size[0],
361                y: size[1],
362            };
363            let imgui_window_class =
364                window_class.map(|class| class.to_imgui("Ui::dock_space_with_class()"));
365            let window_class_ptr = imgui_window_class
366                .as_ref()
367                .map_or(ptr::null(), |wc| wc as *const _);
368            Id::from(sys::igDockSpace(
369                id.into(),
370                size_vec,
371                flags.bits(),
372                window_class_ptr,
373            ))
374        }
375    }
376
377    /// Creates a dockspace with the specified ID and size
378    ///
379    /// # Parameters
380    ///
381    /// * `id` - The non-zero ID for the dockspace
382    /// * `size` - The size of the dockspace in pixels
383    ///
384    /// # Returns
385    ///
386    /// The ID of the created dockspace
387    ///
388    /// # Example
389    ///
390    /// ```no_run
391    /// # use dear_imgui_rs::*;
392    /// # let mut ctx = Context::create();
393    /// # let ui = ctx.frame();
394    /// let dockspace_id = ui.get_id("MyDockspace");
395    /// let dockspace_id = ui.dock_space(dockspace_id, [800.0, 600.0]);
396    /// ```
397    #[doc(alias = "DockSpace")]
398    pub fn dock_space(&self, id: Id, size: [f32; 2]) -> Id {
399        self.dock_space_with_class(id, size, DockNodeFlags::NONE, None)
400    }
401
402    /// Sets the dock ID for the next window with condition
403    ///
404    /// This function must be called before creating a window to dock it to a specific dock node.
405    ///
406    /// # Parameters
407    ///
408    /// * `dock_id` - The ID of the dock node to dock the next window to
409    /// * `cond` - Condition for when to apply the docking
410    ///
411    /// # Example
412    ///
413    /// ```no_run
414    /// # use dear_imgui_rs::*;
415    /// # let mut ctx = Context::create();
416    /// # let ui = ctx.frame();
417    /// let dockspace_id = ui.dockspace_over_main_viewport();
418    /// ui.set_next_window_dock_id_with_cond(dockspace_id, Condition::FirstUseEver);
419    /// ui.window("Docked Window").build(|| {
420    ///     ui.text("This window will be docked!");
421    /// });
422    /// ```
423    #[doc(alias = "SetNextWindowDockID")]
424    pub fn set_next_window_dock_id_with_cond(&self, dock_id: Id, cond: crate::Condition) {
425        unsafe {
426            sys::igSetNextWindowDockID(dock_id.into(), cond as i32);
427        }
428    }
429
430    /// Sets the dock ID for the next window
431    ///
432    /// This function must be called before creating a window to dock it to a specific dock node.
433    /// Uses `Condition::Always` by default.
434    ///
435    /// # Parameters
436    ///
437    /// * `dock_id` - The ID of the dock node to dock the next window to
438    ///
439    /// # Example
440    ///
441    /// ```no_run
442    /// # use dear_imgui_rs::*;
443    /// # let mut ctx = Context::create();
444    /// # let ui = ctx.frame();
445    /// let dockspace_id = ui.dockspace_over_main_viewport();
446    /// ui.set_next_window_dock_id(dockspace_id);
447    /// ui.window("Docked Window").build(|| {
448    ///     ui.text("This window will be docked!");
449    /// });
450    /// ```
451    #[doc(alias = "SetNextWindowDockID")]
452    pub fn set_next_window_dock_id(&self, dock_id: Id) {
453        self.set_next_window_dock_id_with_cond(dock_id, crate::Condition::Always)
454    }
455
456    /// Sets the window class for the next window
457    ///
458    /// This function must be called before creating a window to apply the window class configuration.
459    ///
460    /// # Parameters
461    ///
462    /// * `window_class` - The window class configuration
463    ///
464    /// # Example
465    ///
466    /// ```no_run
467    /// # use dear_imgui_rs::*;
468    /// # let mut ctx = Context::create();
469    /// # let ui = ctx.frame();
470    /// let window_class = WindowClass::new(1).docking_always_tab_bar(true);
471    /// ui.set_next_window_class(&window_class);
472    /// ui.window("Classed Window").build(|| {
473    ///     ui.text("This window has a custom class!");
474    /// });
475    /// ```
476    #[doc(alias = "SetNextWindowClass")]
477    pub fn set_next_window_class(&self, window_class: &WindowClass) {
478        unsafe {
479            let imgui_wc = window_class.to_imgui("Ui::set_next_window_class()");
480            sys::igSetNextWindowClass(&imgui_wc as *const _);
481        }
482    }
483
484    /// Gets the dock ID of the current window
485    ///
486    /// # Returns
487    ///
488    /// The dock ID of the current window, or 0 if the window is not docked
489    ///
490    /// # Example
491    ///
492    /// ```no_run
493    /// # use dear_imgui_rs::*;
494    /// # let mut ctx = Context::create();
495    /// # let ui = ctx.frame();
496    /// ui.window("My Window").build(|| {
497    ///     let dock_id = ui.get_window_dock_id();
498    ///     if dock_id != 0.into() {
499    ///         ui.text(format!("This window is docked with ID: {}", dock_id.raw()));
500    ///     } else {
501    ///         ui.text("This window is not docked");
502    ///     }
503    /// });
504    /// ```
505    #[doc(alias = "GetWindowDockID")]
506    pub fn get_window_dock_id(&self) -> Id {
507        unsafe { Id::from(sys::igGetWindowDockID()) }
508    }
509
510    /// Checks if the current window is docked
511    ///
512    /// # Returns
513    ///
514    /// `true` if the current window is docked, `false` otherwise
515    ///
516    /// # Example
517    ///
518    /// ```no_run
519    /// # use dear_imgui_rs::*;
520    /// # let mut ctx = Context::create();
521    /// # let ui = ctx.frame();
522    /// ui.window("My Window").build(|| {
523    ///     if ui.is_window_docked() {
524    ///         ui.text("This window is docked!");
525    ///     } else {
526    ///         ui.text("This window is floating");
527    ///     }
528    /// });
529    /// ```
530    #[doc(alias = "IsWindowDocked")]
531    pub fn is_window_docked(&self) -> bool {
532        unsafe { sys::igIsWindowDocked() }
533    }
534}
535
536// Re-export DockNodeFlags for convenience
537pub use DockNodeFlags as DockFlags;