1use std::sync::Arc;
2
3use crate::compiler::{CompileResult, compile, compute_layout};
4use crate::diff::{self, LayoutDiff};
5use crate::error::PaneError;
6use crate::focus::{self, FocusDirection};
7use crate::layout::Layout;
8use crate::node::PanelId;
9use crate::panel::fixed;
10use crate::rect::Rect;
11use crate::resolver::{self, ResolveScratch, ResolvedLayout};
12use crate::sequence::PanelSequence;
13use crate::strategy::StrategyKind;
14use crate::tree::LayoutTree;
15use crate::viewport::ViewportState;
16
17pub struct Frame {
19 layout: Arc<ResolvedLayout>,
20 diff: LayoutDiff,
21}
22
23impl Frame {
24 pub fn layout(&self) -> &ResolvedLayout {
26 &self.layout
27 }
28
29 pub fn diff(&self) -> &LayoutDiff {
31 &self.diff
32 }
33}
34
35pub struct LayoutRuntime {
37 tree: LayoutTree,
38 viewport: ViewportState,
39 previous: Option<Arc<ResolvedLayout>>,
40 cached_compile: Option<CompileResult>,
41 cached_kinds: Option<resolver::KindIndex>,
42 rects_buf: Option<Vec<Option<Rect>>>,
43 diff_scratch: diff::DiffScratch,
44 resolve_scratch: ResolveScratch,
45 strategy: Option<StrategyKind>,
46 sequence: PanelSequence,
47}
48
49impl LayoutRuntime {
50 pub fn new(tree: LayoutTree) -> Self {
52 Self {
53 tree,
54 viewport: ViewportState::default(),
55 previous: None,
56 cached_compile: None,
57 cached_kinds: None,
58 rects_buf: None,
59 diff_scratch: diff::DiffScratch::default(),
60 resolve_scratch: ResolveScratch::default(),
61 strategy: None,
62 sequence: PanelSequence::default(),
63 }
64 }
65
66 pub fn from_strategy(strategy: StrategyKind, kinds: &[Arc<str>]) -> Result<Self, PaneError> {
68 let mut sequence = PanelSequence::default();
69 let mut viewport = ViewportState::default();
70 let tree = crate::strategy::build_initial(&strategy, kinds, &mut sequence, &mut viewport)?;
71 Ok(Self {
72 tree,
73 viewport,
74 previous: None,
75 cached_compile: None,
76 cached_kinds: None,
77 rects_buf: None,
78 diff_scratch: diff::DiffScratch::default(),
79 resolve_scratch: ResolveScratch::default(),
80 strategy: Some(strategy),
81 sequence,
82 })
83 }
84
85 pub fn from_tree_and_strategy(
88 tree: LayoutTree,
89 strategy: StrategyKind,
90 kinds: &[Arc<str>],
91 ) -> Result<Self, PaneError> {
92 let mut sequence = PanelSequence::default();
93 for kind in kinds {
94 for &pid in tree.panels_by_kind(kind) {
95 sequence.push(pid);
96 }
97 }
98 let focus = sequence.get(0);
99 Ok(Self {
100 tree,
101 viewport: ViewportState {
102 focus,
103 ..ViewportState::default()
104 },
105 previous: None,
106 cached_compile: None,
107 cached_kinds: None,
108 rects_buf: None,
109 diff_scratch: diff::DiffScratch::default(),
110 resolve_scratch: ResolveScratch::default(),
111 strategy: Some(strategy),
112 sequence,
113 })
114 }
115
116 pub fn tree(&self) -> &LayoutTree {
118 &self.tree
119 }
120
121 pub fn tree_mut(&mut self) -> &mut LayoutTree {
123 &mut self.tree
124 }
125
126 pub fn viewport(&self) -> &ViewportState {
128 &self.viewport
129 }
130
131 pub fn toggle_collapsed(&mut self, pid: PanelId) -> Result<(), PaneError> {
136 match self.viewport.collapsed.contains(&pid) {
137 true => {
138 let saved = self
139 .viewport
140 .saved_constraints
141 .remove(&pid)
142 .ok_or_else(|| {
143 PaneError::InvalidViewport(
144 format!("no saved constraints for panel {pid}").into(),
145 )
146 })?;
147 self.tree.set_constraints(pid, saved)?;
148 self.viewport.collapsed.remove(&pid);
149 Ok(())
150 }
151 false => {
152 let current = self.tree.panel_constraints(pid)?;
153 self.viewport.saved_constraints.insert(pid, current);
154 self.tree.set_constraints(pid, fixed(0.0))?;
155 self.viewport.collapsed.insert(pid);
156 Ok(())
157 }
158 }
159 }
160
161 pub fn scroll_by(&mut self, delta: f32) {
163 self.viewport.scroll_offset += delta;
164 }
165
166 pub fn scroll_to(&mut self, offset: f32) {
168 self.viewport.scroll_offset = offset;
169 }
170
171 pub fn set_active(&mut self, pid: PanelId) {
173 self.viewport.focus = Some(pid);
174 }
175
176 pub fn active_panel(&self) -> Option<PanelId> {
178 self.viewport.focus
179 }
180
181 pub fn strategy(&self) -> Option<&StrategyKind> {
183 self.strategy.as_ref()
184 }
185
186 pub fn sequence(&self) -> &PanelSequence {
188 &self.sequence
189 }
190
191 pub fn focused(&self) -> Option<PanelId> {
193 self.viewport.focus
194 }
195
196 pub fn focused_kind(&self) -> Option<&str> {
198 let pid = self.viewport.focus?;
199 self.tree.panel_kind(pid).ok()
200 }
201
202 pub fn is_decoration_for(&self, pid: PanelId, content_pid: PanelId) -> bool {
204 let (Ok(dec_kind), Ok(content_kind)) =
205 (self.tree.panel_kind(pid), self.tree.panel_kind(content_pid))
206 else {
207 return false;
208 };
209 let base = dec_kind
210 .strip_suffix("_tab")
211 .or_else(|| dec_kind.strip_suffix("_title"));
212 matches!(base, Some(b) if b == content_kind)
213 }
214
215 pub fn add_panel(&mut self, kind: Arc<str>) -> Result<PanelId, PaneError> {
217 let strategy = self
218 .strategy
219 .as_ref()
220 .ok_or_else(|| PaneError::InvalidMutation("no strategy set".into()))?
221 .clone();
222 crate::strategy::apply_add(
223 &strategy,
224 &mut self.tree,
225 &mut self.sequence,
226 &mut self.viewport,
227 kind,
228 )
229 }
230
231 pub fn remove_panel(&mut self, pid: PanelId) -> Result<Option<PanelId>, PaneError> {
233 let strategy = self
234 .strategy
235 .as_ref()
236 .ok_or_else(|| PaneError::InvalidMutation("no strategy set".into()))?
237 .clone();
238 crate::strategy::apply_remove(
239 &strategy,
240 &mut self.tree,
241 &mut self.sequence,
242 &mut self.viewport,
243 pid,
244 )
245 }
246
247 pub fn move_panel(&mut self, pid: PanelId, new_index: usize) -> Result<PanelId, PaneError> {
249 let strategy = self
250 .strategy
251 .as_ref()
252 .ok_or_else(|| PaneError::InvalidMutation("no strategy set".into()))?
253 .clone();
254 crate::strategy::apply_move(
255 &strategy,
256 &mut self.tree,
257 &mut self.sequence,
258 &mut self.viewport,
259 pid,
260 new_index,
261 )
262 }
263
264 pub fn focus(&mut self, pid: PanelId) -> Result<(), PaneError> {
266 match &self.strategy {
267 Some(strategy) => {
268 let strategy = strategy.clone();
269 crate::strategy::apply_focus(
270 &strategy,
271 &mut self.tree,
272 &mut self.sequence,
273 &mut self.viewport,
274 pid,
275 )
276 }
277 None => {
278 self.viewport.focus = Some(pid);
279 Ok(())
280 }
281 }
282 }
283
284 pub fn focus_next(&mut self) -> Result<(), PaneError> {
286 let next = match self.viewport.focus {
287 Some(current) => {
288 let idx = self.sequence.index_of(current).unwrap_or(0);
289 let next_idx = (idx + 1) % self.sequence.len().max(1);
290 self.sequence.get(next_idx)
291 }
292 None => self.sequence.get(0),
293 };
294 match next {
295 Some(pid) => self.focus(pid),
296 None => Ok(()),
297 }
298 }
299
300 pub fn focus_prev(&mut self) -> Result<(), PaneError> {
302 let prev = match self.viewport.focus {
303 Some(current) => {
304 let len = self.sequence.len().max(1);
305 let idx = self.sequence.index_of(current).unwrap_or(0);
306 let prev_idx = (idx + len - 1) % len;
307 self.sequence.get(prev_idx)
308 }
309 None => self.sequence.get(0),
310 };
311 match prev {
312 Some(pid) => self.focus(pid),
313 None => Ok(()),
314 }
315 }
316
317 pub fn focus_direction(
322 &mut self,
323 layout: &ResolvedLayout,
324 direction: FocusDirection,
325 ) -> Result<Option<PanelId>, PaneError> {
326 let focused = match self.focused() {
327 Some(pid) => pid,
328 None => return Ok(None),
329 };
330 match focus::find_nearest(layout, focused, &self.sequence, direction) {
331 Some(target) => {
332 self.focus(target)?;
333 Ok(Some(target))
334 }
335 None => Ok(None),
336 }
337 }
338
339 pub fn focus_direction_current(
346 &mut self,
347 direction: FocusDirection,
348 ) -> Result<Option<PanelId>, PaneError> {
349 let layout = Arc::clone(self.previous.as_ref().ok_or_else(|| {
350 PaneError::InvalidViewport("no resolved layout; call resolve() first".into())
351 })?);
352 self.focus_direction(&layout, direction)
353 }
354
355 pub fn resolve(&mut self, width: f32, height: f32) -> Result<Frame, PaneError> {
357 let tree_dirty = self.tree.is_dirty();
358
359 let mut result = match (tree_dirty, self.cached_compile.take()) {
360 (false, Some(cached)) => cached,
361 _ => {
362 self.tree.clear_dirty();
363 compile(&self.tree)?
364 }
365 };
366
367 compute_layout(&mut result, width, height)?;
368
369 let mut layout = match (tree_dirty, self.cached_kinds.take()) {
370 (false, Some(kinds)) => resolver::resolve_with_cached_kinds(
371 &result,
372 &self.tree,
373 kinds,
374 &mut self.resolve_scratch,
375 self.rects_buf.take(),
376 )?,
377 _ => resolver::resolve(&result, &self.tree)?,
378 };
379
380 self.cached_kinds = Some(Arc::clone(layout.kinds_arc()));
381 self.cached_compile = Some(result);
382
383 apply_scroll_offset(&mut layout, self.viewport.scroll_offset);
384
385 let prev_arc = self.previous.take();
386 let diff = match (tree_dirty, prev_arc.as_deref()) {
387 (_, None) => diff::first_frame(&layout),
388 (false, Some(prev)) => diff::diff_same_panels(prev, &layout),
389 (true, Some(prev)) => diff::diff_reuse(prev, &layout, &mut self.diff_scratch),
390 };
391
392 let layout = Arc::new(layout);
393 self.previous = Some(Arc::clone(&layout));
394
395 if let Some(Ok(mut prev_layout)) = prev_arc.map(Arc::try_unwrap) {
397 self.rects_buf = Some(prev_layout.take_rects());
398 }
399
400 Ok(Frame { layout, diff })
401 }
402}
403
404impl From<LayoutTree> for LayoutRuntime {
405 fn from(tree: LayoutTree) -> Self {
406 Self::new(tree)
407 }
408}
409
410fn apply_scroll_offset(layout: &mut ResolvedLayout, offset: f32) {
412 match offset.abs() < f32::EPSILON {
413 true => {}
414 false => layout.shift_x(-offset),
415 }
416}
417
418impl From<Layout> for LayoutRuntime {
419 fn from(layout: Layout) -> Self {
420 Self::new(LayoutTree::from(layout))
421 }
422}