Skip to main content

panes/
runtime.rs

1use std::sync::Arc;
2
3use crate::compiler::{CompileResult, compile, compute_layout};
4use crate::diff::{self, LayoutDiff};
5use crate::error::PaneError;
6use crate::layout::Layout;
7use crate::node::PanelId;
8use crate::panel::fixed;
9use crate::rect::Rect;
10use crate::resolver::{self, ResolveScratch, ResolvedLayout};
11use crate::sequence::PanelSequence;
12use crate::strategy::StrategyKind;
13use crate::tree::LayoutTree;
14use crate::viewport::ViewportState;
15
16/// Result of a single resolve call: the resolved layout and its diff against the previous frame.
17pub struct Frame {
18    layout: Arc<ResolvedLayout>,
19    diff: LayoutDiff,
20}
21
22impl Frame {
23    /// The resolved layout for this frame.
24    pub fn layout(&self) -> &ResolvedLayout {
25        &self.layout
26    }
27
28    /// The diff between this frame and the previous one.
29    pub fn diff(&self) -> &LayoutDiff {
30        &self.diff
31    }
32}
33
34/// Stateful layout wrapper that tracks tree, viewport, and frame history.
35pub struct LayoutRuntime {
36    tree: LayoutTree,
37    viewport: ViewportState,
38    previous: Option<Arc<ResolvedLayout>>,
39    cached_compile: Option<CompileResult>,
40    cached_kinds: Option<resolver::KindIndex>,
41    rects_buf: Option<Vec<Option<Rect>>>,
42    diff_scratch: diff::DiffScratch,
43    resolve_scratch: ResolveScratch,
44    strategy: Option<StrategyKind>,
45    sequence: PanelSequence,
46}
47
48impl LayoutRuntime {
49    /// Create a runtime from an existing tree (legacy path, no strategy).
50    pub fn new(tree: LayoutTree) -> Self {
51        Self {
52            tree,
53            viewport: ViewportState::default(),
54            previous: None,
55            cached_compile: None,
56            cached_kinds: None,
57            rects_buf: None,
58            diff_scratch: diff::DiffScratch::default(),
59            resolve_scratch: ResolveScratch::default(),
60            strategy: None,
61            sequence: PanelSequence::default(),
62        }
63    }
64
65    /// Create a runtime from a strategy and initial panel kinds.
66    pub fn from_strategy(strategy: StrategyKind, kinds: &[Arc<str>]) -> Result<Self, PaneError> {
67        let mut sequence = PanelSequence::default();
68        let mut viewport = ViewportState::default();
69        let tree = crate::strategy::build_initial(&strategy, kinds, &mut sequence, &mut viewport)?;
70        Ok(Self {
71            tree,
72            viewport,
73            previous: None,
74            cached_compile: None,
75            cached_kinds: None,
76            rects_buf: None,
77            diff_scratch: diff::DiffScratch::default(),
78            resolve_scratch: ResolveScratch::default(),
79            strategy: Some(strategy),
80            sequence,
81        })
82    }
83
84    /// Create a runtime from a pre-built tree and a strategy.
85    /// Populates the sequence by looking up each kind in the tree.
86    pub fn from_tree_and_strategy(
87        tree: LayoutTree,
88        strategy: StrategyKind,
89        kinds: &[Arc<str>],
90    ) -> Result<Self, PaneError> {
91        let mut sequence = PanelSequence::default();
92        for kind in kinds {
93            for &pid in tree.panels_by_kind(kind) {
94                sequence.push(pid);
95            }
96        }
97        let focus = sequence.get(0);
98        Ok(Self {
99            tree,
100            viewport: ViewportState {
101                focus,
102                ..ViewportState::default()
103            },
104            previous: None,
105            cached_compile: None,
106            cached_kinds: None,
107            rects_buf: None,
108            diff_scratch: diff::DiffScratch::default(),
109            resolve_scratch: ResolveScratch::default(),
110            strategy: Some(strategy),
111            sequence,
112        })
113    }
114
115    /// Immutable access to the underlying tree.
116    pub fn tree(&self) -> &LayoutTree {
117        &self.tree
118    }
119
120    /// Mutable access to the underlying tree for structural mutations.
121    pub fn tree_mut(&mut self) -> &mut LayoutTree {
122        &mut self.tree
123    }
124
125    /// Immutable access to the viewport state.
126    pub fn viewport(&self) -> &ViewportState {
127        &self.viewport
128    }
129
130    /// Toggle a panel's collapsed state.
131    ///
132    /// Collapsing saves the current constraints and sets the panel to fixed(0.0).
133    /// Uncollapsing restores the saved constraints.
134    pub fn toggle_collapsed(&mut self, pid: PanelId) -> Result<(), PaneError> {
135        match self.viewport.collapsed.contains(&pid) {
136            true => {
137                let saved = self
138                    .viewport
139                    .saved_constraints
140                    .remove(&pid)
141                    .ok_or_else(|| {
142                        PaneError::InvalidViewport(
143                            format!("no saved constraints for panel {pid}").into(),
144                        )
145                    })?;
146                self.tree.set_constraints(pid, saved)?;
147                self.viewport.collapsed.remove(&pid);
148                Ok(())
149            }
150            false => {
151                let current = self.tree.panel_constraints(pid)?;
152                self.viewport.saved_constraints.insert(pid, current);
153                self.tree.set_constraints(pid, fixed(0.0))?;
154                self.viewport.collapsed.insert(pid);
155                Ok(())
156            }
157        }
158    }
159
160    /// Shift the scroll offset by a delta.
161    pub fn scroll_by(&mut self, delta: f32) {
162        self.viewport.scroll_offset += delta;
163    }
164
165    /// Set the scroll offset to an absolute value.
166    pub fn scroll_to(&mut self, offset: f32) {
167        self.viewport.scroll_offset = offset;
168    }
169
170    /// Set the active panel.
171    pub fn set_active(&mut self, pid: PanelId) {
172        self.viewport.focus = Some(pid);
173    }
174
175    /// Get the currently active panel, if any.
176    pub fn active_panel(&self) -> Option<PanelId> {
177        self.viewport.focus
178    }
179
180    /// The layout strategy, if this runtime was created via `from_strategy`.
181    pub fn strategy(&self) -> Option<&StrategyKind> {
182        self.strategy.as_ref()
183    }
184
185    /// The panel sequence (logical order).
186    pub fn sequence(&self) -> &PanelSequence {
187        &self.sequence
188    }
189
190    /// The currently focused panel.
191    pub fn focused(&self) -> Option<PanelId> {
192        self.viewport.focus
193    }
194
195    /// The kind of the currently focused panel.
196    pub fn focused_kind(&self) -> Option<&str> {
197        let pid = self.viewport.focus?;
198        self.tree.panel_kind(pid).ok()
199    }
200
201    /// Whether `pid` is a decorative panel (tab bar, title bar) for `content_pid`.
202    pub fn is_decoration_for(&self, pid: PanelId, content_pid: PanelId) -> bool {
203        let (Ok(dec_kind), Ok(content_kind)) =
204            (self.tree.panel_kind(pid), self.tree.panel_kind(content_pid))
205        else {
206            return false;
207        };
208        let base = dec_kind
209            .strip_suffix("_tab")
210            .or_else(|| dec_kind.strip_suffix("_title"));
211        matches!(base, Some(b) if b == content_kind)
212    }
213
214    /// Add a panel using the active strategy.
215    pub fn add_panel(&mut self, kind: Arc<str>) -> Result<PanelId, PaneError> {
216        let strategy = self
217            .strategy
218            .as_ref()
219            .ok_or_else(|| PaneError::InvalidMutation("no strategy set".into()))?
220            .clone();
221        crate::strategy::apply_add(
222            &strategy,
223            &mut self.tree,
224            &mut self.sequence,
225            &mut self.viewport,
226            kind,
227        )
228    }
229
230    /// Remove a panel using the active strategy. Returns the new focus panel.
231    pub fn remove_panel(&mut self, pid: PanelId) -> Result<Option<PanelId>, PaneError> {
232        let strategy = self
233            .strategy
234            .as_ref()
235            .ok_or_else(|| PaneError::InvalidMutation("no strategy set".into()))?
236            .clone();
237        crate::strategy::apply_remove(
238            &strategy,
239            &mut self.tree,
240            &mut self.sequence,
241            &mut self.viewport,
242            pid,
243        )
244    }
245
246    /// Move a panel to a new sequence index using the active strategy.
247    pub fn move_panel(&mut self, pid: PanelId, new_index: usize) -> Result<PanelId, PaneError> {
248        let strategy = self
249            .strategy
250            .as_ref()
251            .ok_or_else(|| PaneError::InvalidMutation("no strategy set".into()))?
252            .clone();
253        crate::strategy::apply_move(
254            &strategy,
255            &mut self.tree,
256            &mut self.sequence,
257            &mut self.viewport,
258            pid,
259            new_index,
260        )
261    }
262
263    /// Set focus to a specific panel using the active strategy.
264    pub fn focus(&mut self, pid: PanelId) -> Result<(), PaneError> {
265        match &self.strategy {
266            Some(strategy) => {
267                let strategy = strategy.clone();
268                crate::strategy::apply_focus(
269                    &strategy,
270                    &mut self.tree,
271                    &mut self.sequence,
272                    &mut self.viewport,
273                    pid,
274                )
275            }
276            None => {
277                self.viewport.focus = Some(pid);
278                Ok(())
279            }
280        }
281    }
282
283    /// Move focus to the next panel in the sequence.
284    pub fn focus_next(&mut self) -> Result<(), PaneError> {
285        let next = match self.viewport.focus {
286            Some(current) => {
287                let idx = self.sequence.index_of(current).unwrap_or(0);
288                let next_idx = (idx + 1) % self.sequence.len().max(1);
289                self.sequence.get(next_idx)
290            }
291            None => self.sequence.get(0),
292        };
293        match next {
294            Some(pid) => self.focus(pid),
295            None => Ok(()),
296        }
297    }
298
299    /// Move focus to the previous panel in the sequence.
300    pub fn focus_prev(&mut self) -> Result<(), PaneError> {
301        let prev = match self.viewport.focus {
302            Some(current) => {
303                let len = self.sequence.len().max(1);
304                let idx = self.sequence.index_of(current).unwrap_or(0);
305                let prev_idx = (idx + len - 1) % len;
306                self.sequence.get(prev_idx)
307            }
308            None => self.sequence.get(0),
309        };
310        match prev {
311            Some(pid) => self.focus(pid),
312            None => Ok(()),
313        }
314    }
315
316    /// Resolve the layout at the given dimensions, producing a Frame with layout and diff.
317    pub fn resolve(&mut self, width: f32, height: f32) -> Result<Frame, PaneError> {
318        let tree_dirty = self.tree.is_dirty();
319
320        let mut result = match (tree_dirty, self.cached_compile.take()) {
321            (false, Some(cached)) => cached,
322            _ => {
323                self.tree.clear_dirty();
324                compile(&self.tree)?
325            }
326        };
327
328        compute_layout(&mut result, width, height)?;
329
330        let mut layout = match (tree_dirty, self.cached_kinds.take()) {
331            (false, Some(kinds)) => resolver::resolve_with_cached_kinds(
332                &result,
333                &self.tree,
334                kinds,
335                &mut self.resolve_scratch,
336                self.rects_buf.take(),
337            )?,
338            _ => resolver::resolve(&result, &self.tree)?,
339        };
340
341        self.cached_kinds = Some(Arc::clone(layout.kinds_arc()));
342        self.cached_compile = Some(result);
343
344        apply_scroll_offset(&mut layout, self.viewport.scroll_offset);
345
346        let prev_arc = self.previous.take();
347        let diff = match (tree_dirty, prev_arc.as_deref()) {
348            (_, None) => diff::first_frame(&layout),
349            (false, Some(prev)) => diff::diff_same_panels(prev, &layout),
350            (true, Some(prev)) => diff::diff_reuse(prev, &layout, &mut self.diff_scratch),
351        };
352
353        let layout = Arc::new(layout);
354        self.previous = Some(Arc::clone(&layout));
355
356        // Reclaim the previous frame's rects buffer if no other consumers hold a reference.
357        if let Some(Ok(mut prev_layout)) = prev_arc.map(Arc::try_unwrap) {
358            self.rects_buf = Some(prev_layout.take_rects());
359        }
360
361        Ok(Frame { layout, diff })
362    }
363}
364
365impl From<LayoutTree> for LayoutRuntime {
366    fn from(tree: LayoutTree) -> Self {
367        Self::new(tree)
368    }
369}
370
371/// Shift all resolved rect x-positions by the negative scroll offset.
372fn apply_scroll_offset(layout: &mut ResolvedLayout, offset: f32) {
373    match offset.abs() < f32::EPSILON {
374        true => {}
375        false => layout.shift_x(-offset),
376    }
377}
378
379impl From<Layout> for LayoutRuntime {
380    fn from(layout: Layout) -> Self {
381        Self::new(LayoutTree::from(layout))
382    }
383}