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;