Skip to main content

panes/
builder.rs

1use crate::error::{PaneError, TreeError};
2use crate::node::{NodeId, PanelId};
3use crate::panel::Constraints;
4use crate::tree::LayoutTree;
5use crate::validate::{check_f32_non_negative, float_invalid_to_constraint};
6
7/// Reject NaN, negative, or infinite gap values.
8fn validate_gap(value: f32) -> Result<(), PaneError> {
9    check_f32_non_negative(value)
10        .map_err(|e| PaneError::InvalidConstraint(float_invalid_to_constraint("gap", e)))
11}
12
13/// Sentinel PanelId returned when a `ContainerCtx` operation fails.
14fn sentinel_panel() -> PanelId {
15    PanelId::from_raw(u32::MAX)
16}
17
18/// Ergonomic builder for constructing layouts.
19///
20/// Users create panels, arrange them in `row()`/`col()` containers via closures,
21/// then call `build()` to get a `Layout` ready for resolution.
22pub struct LayoutBuilder {
23    tree: LayoutTree,
24    root_set: bool,
25}
26
27impl LayoutBuilder {
28    /// Create an empty builder with no root set.
29    pub fn new() -> Self {
30        Self {
31            tree: LayoutTree::new(),
32            root_set: false,
33        }
34    }
35
36    /// Create a panel with `grow(1.0)` default constraints.
37    pub fn panel(&mut self, kind: impl Into<std::sync::Arc<str>>) -> Result<PanelId, PaneError> {
38        self.panel_with(kind, crate::panel::grow(1.0))
39    }
40
41    /// Create a panel with explicit constraints.
42    pub fn panel_with(
43        &mut self,
44        kind: impl Into<std::sync::Arc<str>>,
45        constraints: Constraints,
46    ) -> Result<PanelId, PaneError> {
47        let (pid, _nid) = self.tree.add_panel(kind, constraints)?;
48        Ok(pid)
49    }
50
51    /// Set the root to a row container with zero gap.
52    pub fn row(&mut self, f: impl FnOnce(&mut ContainerCtx)) -> Result<(), PaneError> {
53        self.row_gap(0.0, f)
54    }
55
56    /// Set the root to a row container with the specified gap.
57    pub fn row_gap(
58        &mut self,
59        gap: f32,
60        f: impl FnOnce(&mut ContainerCtx),
61    ) -> Result<(), PaneError> {
62        self.require_no_root()?;
63        validate_gap(gap)?;
64        let children = collect_children(&mut self.tree, f)?;
65        let nid = self.tree.add_row(gap, children)?;
66        self.tree.set_root(nid);
67        self.root_set = true;
68        Ok(())
69    }
70
71    /// Set the root to a column container with zero gap.
72    pub fn col(&mut self, f: impl FnOnce(&mut ContainerCtx)) -> Result<(), PaneError> {
73        self.col_gap(0.0, f)
74    }
75
76    /// Set the root to a column container with the specified gap.
77    pub fn col_gap(
78        &mut self,
79        gap: f32,
80        f: impl FnOnce(&mut ContainerCtx),
81    ) -> Result<(), PaneError> {
82        self.require_no_root()?;
83        validate_gap(gap)?;
84        let children = collect_children(&mut self.tree, f)?;
85        let nid = self.tree.add_col(gap, children)?;
86        self.tree.set_root(nid);
87        self.root_set = true;
88        Ok(())
89    }
90
91    /// Consume the builder, validate the tree, and return a `Layout`.
92    /// Set how many panels the active window shows at once.
93    pub fn set_window_size(&mut self, size: usize) {
94        self.tree.set_window_size(size);
95    }
96
97    /// Validate the tree and produce a [`Layout`](crate::layout::Layout).
98    pub fn build(self) -> Result<crate::layout::Layout, PaneError> {
99        if !self.root_set {
100            return Err(PaneError::InvalidTree(TreeError::RootNotSet));
101        }
102        self.tree.validate()?;
103        Ok(crate::layout::Layout::from_tree(self.tree))
104    }
105
106    fn require_no_root(&self) -> Result<(), PaneError> {
107        match self.root_set {
108            true => Err(PaneError::InvalidTree(TreeError::RootAlreadySet)),
109            false => Ok(()),
110        }
111    }
112}
113
114impl Default for LayoutBuilder {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120/// Closure context for building container children.
121///
122/// Errors are deferred: operations no-op after the first failure.
123/// The error is surfaced when the parent collects children.
124pub struct ContainerCtx<'a> {
125    tree: &'a mut LayoutTree,
126    children: Vec<NodeId>,
127    error: Option<PaneError>,
128}
129
130impl ContainerCtx<'_> {
131    /// Place a pre-created panel into this container.
132    pub fn add(&mut self, pid: PanelId) {
133        if self.error.is_some() {
134            return;
135        }
136        match self.tree.node_for_panel(pid) {
137            Some(nid) => self.children.push(nid),
138            None => self.error = Some(PaneError::PanelNotFound(pid)),
139        }
140    }
141
142    /// Create a panel inline with `grow(1.0)` default constraints.
143    pub fn panel(&mut self, kind: impl Into<std::sync::Arc<str>>) -> PanelId {
144        self.panel_with(kind, crate::panel::grow(1.0))
145    }
146
147    /// Create a panel inline with explicit constraints.
148    pub fn panel_with(
149        &mut self,
150        kind: impl Into<std::sync::Arc<str>>,
151        constraints: Constraints,
152    ) -> PanelId {
153        if self.error.is_some() {
154            return sentinel_panel();
155        }
156        match self.tree.add_panel(kind, constraints) {
157            Ok((pid, nid)) => {
158                self.children.push(nid);
159                pid
160            }
161            Err(e) => {
162                self.error = Some(e);
163                sentinel_panel()
164            }
165        }
166    }
167
168    /// Create a nested row container with zero gap.
169    pub fn row(&mut self, f: impl FnOnce(&mut ContainerCtx)) {
170        self.row_gap(0.0, f);
171    }
172
173    /// Create a nested row container with the specified gap.
174    pub fn row_gap(&mut self, gap: f32, f: impl FnOnce(&mut ContainerCtx)) {
175        if self.error.is_some() {
176            return;
177        }
178        if let Err(e) = validate_gap(gap) {
179            self.error = Some(e);
180            return;
181        }
182        let children = match collect_children(self.tree, f) {
183            Ok(c) => c,
184            Err(e) => {
185                self.error = Some(e);
186                return;
187            }
188        };
189        match self.tree.add_row(gap, children) {
190            Ok(nid) => self.children.push(nid),
191            Err(e) => self.error = Some(e),
192        }
193    }
194
195    /// Create a nested column container with zero gap.
196    pub fn col(&mut self, f: impl FnOnce(&mut ContainerCtx)) {
197        self.col_gap(0.0, f);
198    }
199
200    /// Create a nested column container with the specified gap.
201    pub fn col_gap(&mut self, gap: f32, f: impl FnOnce(&mut ContainerCtx)) {
202        if self.error.is_some() {
203            return;
204        }
205        if let Err(e) = validate_gap(gap) {
206            self.error = Some(e);
207            return;
208        }
209        let children = match collect_children(self.tree, f) {
210            Ok(c) => c,
211            Err(e) => {
212                self.error = Some(e);
213                return;
214            }
215        };
216        match self.tree.add_col(gap, children) {
217            Ok(nid) => self.children.push(nid),
218            Err(e) => self.error = Some(e),
219        }
220    }
221
222    /// Escape hatch: insert a raw Taffy node with a custom style.
223    pub fn taffy_node(&mut self, style: taffy::Style, f: impl FnOnce(&mut ContainerCtx)) {
224        if self.error.is_some() {
225            return;
226        }
227        let children = match collect_children(self.tree, f) {
228            Ok(c) => c,
229            Err(e) => {
230                self.error = Some(e);
231                return;
232            }
233        };
234        match self.tree.add_taffy_node(style, children) {
235            Ok(nid) => self.children.push(nid),
236            Err(e) => self.error = Some(e),
237        }
238    }
239
240    /// Store an error in the deferred error slot.
241    /// Subsequent operations will no-op.
242    pub(crate) fn set_error(&mut self, err: PaneError) {
243        if self.error.is_none() {
244            self.error = Some(err);
245        }
246    }
247}
248
249/// Run a closure with a fresh `ContainerCtx`, collecting the children it produces.
250fn collect_children(
251    tree: &mut LayoutTree,
252    f: impl FnOnce(&mut ContainerCtx),
253) -> Result<Vec<NodeId>, PaneError> {
254    let mut ctx = ContainerCtx {
255        tree,
256        children: Vec::new(),
257        error: None,
258    };
259    f(&mut ctx);
260    match ctx.error {
261        Some(e) => Err(e),
262        None => Ok(ctx.children),
263    }
264}