Skip to main content

dear_imgui_rs/dock_layout/
model.rs

1use crate::{DockNodeFlags, Id, WindowClass, WindowClassError, WindowKey};
2use thiserror::Error;
3
4/// A complete declarative dock tree.
5#[derive(Clone, Debug, PartialEq)]
6pub enum DockLayout {
7    /// One leaf node containing zero or more tabbed windows.
8    ///
9    /// An empty list intentionally leaves the leaf available as an empty docking target.
10    /// Dear ImGui's builder does not preserve the relative order of these windows.
11    Tabs(Vec<WindowKey>),
12    /// Split a node and recursively populate both resulting children.
13    Split {
14        /// Side occupied by `first`.
15        direction: DockSplit,
16        /// Fraction of the parent occupied by `first`.
17        ratio: f32,
18        /// Layout placed on `direction`'s side.
19        first: Box<DockLayout>,
20        /// Layout placed in the remaining space.
21        second: Box<DockLayout>,
22    },
23}
24
25impl DockLayout {
26    /// Create one tab leaf from stable window keys.
27    pub fn tabs(windows: impl IntoIterator<Item = impl Into<WindowKey>>) -> Self {
28        Self::Tabs(windows.into_iter().map(Into::into).collect())
29    }
30
31    /// Split a node into a directional child and the remaining child.
32    pub fn split(direction: DockSplit, ratio: f32, first: DockLayout, second: DockLayout) -> Self {
33        Self::Split {
34            direction,
35            ratio,
36            first: Box::new(first),
37            second: Box::new(second),
38        }
39    }
40
41    /// Validate the complete layout without touching Dear ImGui state.
42    pub fn validate(&self) -> Result<(), DockspaceError> {
43        super::compile::compile_layout(self).map(|_| ())
44    }
45}
46
47/// Direction of the first child produced by a [`DockLayout::Split`].
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
49pub enum DockSplit {
50    Left,
51    Right,
52    Up,
53    Down,
54}
55
56/// Policy controlling whether an existing persisted dock tree is preserved.
57#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
58pub enum DockLayoutApply {
59    /// Build only when the root did not exist before this frame's dockspace submission.
60    #[default]
61    IfMissing,
62    /// Replace the existing root with the complete declared layout.
63    ///
64    /// The topology is staged under a temporary root before the native commit point. Validation
65    /// and recoverable staging failures leave an existing layout unchanged and alive for the
66    /// current frame. A successful replacement preserves the root ID, but child-node IDs, tab
67    /// selection, focus, and relative tab order are not stable across replacement. Replacement
68    /// must be submitted before any affected window begins its frame.
69    Replace,
70}
71
72#[derive(Clone, Debug)]
73pub(crate) struct DockspaceConfig {
74    root_id: Id,
75    flags: DockNodeFlags,
76    window_class: Option<WindowClass>,
77}
78
79impl DockspaceConfig {
80    pub(crate) fn new(
81        root_id: Id,
82        flags: DockNodeFlags,
83        window_class: Option<WindowClass>,
84    ) -> Self {
85        Self {
86            root_id,
87            flags,
88            window_class,
89        }
90    }
91
92    pub(crate) fn root_id(&self) -> Id {
93        self.root_id
94    }
95
96    pub(crate) fn dock_flags(&self) -> DockNodeFlags {
97        self.flags
98    }
99
100    pub(crate) fn window_class_ref(&self) -> Option<&WindowClass> {
101        self.window_class.as_ref()
102    }
103
104    pub(crate) fn validate(&self) -> Result<(), DockspaceError> {
105        if self.root_id.raw() == 0 {
106            return Err(DockspaceError::ZeroRootId);
107        }
108
109        let unsupported = self.flags.bits() & !DockNodeFlags::all().bits();
110        if unsupported != 0 {
111            return Err(DockspaceError::UnsupportedDockNodeFlags { bits: unsupported });
112        }
113
114        if let Some(window_class) = &self.window_class {
115            window_class.validate()?;
116        }
117
118        Ok(())
119    }
120}
121
122/// Validation or submission failure for a dockspace.
123#[derive(Clone, Debug, Error, PartialEq)]
124#[non_exhaustive]
125pub enum DockspaceError {
126    #[error("a dockspace in the current window requires an explicit non-zero root ID")]
127    MissingRootId,
128    #[error("dockspace root ID must be non-zero")]
129    ZeroRootId,
130    #[error("dockspace flags contain unsupported ImGuiDockNodeFlags bits: 0x{bits:X}")]
131    UnsupportedDockNodeFlags { bits: i32 },
132    #[error("dockspace host position must contain finite values: {position:?}")]
133    InvalidHostPosition { position: [f32; 2] },
134    #[error(
135        "dockspace host size must be positive, finite, and safely truncatable to i32: {size:?}"
136    )]
137    InvalidHostSize { size: [f32; 2] },
138    #[error("dockspace host window name is {bytes} bytes; at most {max_bytes} bytes are supported")]
139    HostWindowNameTooLong { bytes: usize, max_bytes: usize },
140    #[error("invalid dockspace window class: {0}")]
141    InvalidWindowClass(#[from] WindowClassError),
142    #[error("dock split ratio must be finite and strictly between 0 and 1: {ratio}")]
143    InvalidSplitRatio { ratio: f32 },
144    #[error(
145        "dock window keys {first_key:?} and {second_key:?} resolve to the same Dear ImGui ID {id:?}"
146    )]
147    DuplicateWindowKey {
148        first_key: String,
149        second_key: String,
150        id: Id,
151    },
152    #[error("dock layout contains too many nodes")]
153    LayoutTooLarge,
154    #[error("docking is not enabled in ConfigFlags")]
155    DockingDisabled,
156    #[error("dockspace {root_id:?} was already submitted during this frame")]
157    DuplicateDockspaceSubmission { root_id: Id },
158    #[error("existing dock node {id:?} is not an explicit dockspace root")]
159    ExistingNodeIsNotDockspaceRoot { id: Id },
160    #[error("dockspace {root_id:?} must be submitted before any window hosted by its dock tree")]
161    WindowSubmittedBeforeDockspace { root_id: Id },
162    #[error("Dear ImGui could not create dock node {id:?}")]
163    NodeCreationFailed { id: Id },
164    #[error("Dear ImGui failed to split a docking node {direction:?} at ratio {ratio}")]
165    SplitFailed { direction: DockSplit, ratio: f32 },
166}