use std::sync::Arc;
use crate::error::{PaneError, TreeError};
use crate::panel::fixed;
use crate::runtime::LayoutRuntime;
use crate::sequence::PanelSequence;
use crate::strategy::StrategyKind;
use crate::strategy::builder::Strategy;
use crate::tree::LayoutTree;
use crate::viewport::ViewportState;
#[derive(Debug, Clone)]
pub struct BreakpointEntry {
pub(crate) min_width: u32,
pub(crate) strategy: StrategyKind,
}
impl BreakpointEntry {
pub fn min_width(&self) -> u32 {
self.min_width
}
pub fn strategy(&self) -> &StrategyKind {
&self.strategy
}
}
pub struct AdaptiveBuilder {
panels: Box<[Arc<str>]>,
breakpoints: Vec<BreakpointEntry>,
}
impl AdaptiveBuilder {
pub(crate) fn new(panels: Box<[Arc<str>]>) -> Self {
Self {
panels,
breakpoints: Vec::new(),
}
}
pub fn at(mut self, min_width: u32, strategy: impl Into<Strategy>) -> Self {
let strategy: Strategy = strategy.into();
self.breakpoints.push(BreakpointEntry {
min_width,
strategy: strategy.kind,
});
self
}
pub fn into_runtime(mut self) -> Result<LayoutRuntime, PaneError> {
match self.breakpoints.is_empty() {
true => return Err(PaneError::InvalidTree(TreeError::NoBreakpoints)),
false => {}
}
self.breakpoints.sort_by_key(|bp| bp.min_width);
validate_breakpoint_widths(&self.breakpoints)?;
let active_idx = 0;
let breakpoints: Box<[BreakpointEntry]> = self.breakpoints.into();
LayoutRuntime::from_adaptive(&self.panels, breakpoints, active_idx)
}
}
fn validate_breakpoint_widths(breakpoints: &[BreakpointEntry]) -> Result<(), PaneError> {
for pair in breakpoints.windows(2) {
if pair[0].min_width == pair[1].min_width {
return Err(PaneError::InvalidTree(
TreeError::DuplicateBreakpointWidth {
width: pair[1].min_width,
},
));
}
}
Ok(())
}
pub(crate) fn select_breakpoint(breakpoints: &[BreakpointEntry], width: f32) -> usize {
let w = width as u32;
let idx = breakpoints.partition_point(|bp| bp.min_width <= w);
idx.saturating_sub(1)
}
pub(crate) fn rebuild_for_breakpoint(
breakpoints: &[BreakpointEntry],
new_idx: usize,
tree: &mut LayoutTree,
sequence: &mut PanelSequence,
) -> Result<Box<[Arc<str>]>, PaneError> {
let kinds = crate::strategy::collect_kinds_from_sequence(tree, sequence);
let strategy = &breakpoints[new_idx].strategy;
let new_tree = crate::strategy::build_tree_for_strategy(strategy, &kinds)?;
let mut new_seq = PanelSequence::default();
crate::strategy::populate_sequence_by_kinds(&new_tree, &kinds, &mut new_seq);
*tree = new_tree;
*sequence = new_seq;
Ok(kinds)
}
pub(crate) fn restore_breakpoint_viewport(
tree: &mut LayoutTree,
sequence: &mut PanelSequence,
viewport: &mut ViewportState,
strategy: Option<&StrategyKind>,
focused_seq_idx: Option<usize>,
collapsed_seq_indices: &[usize],
) -> Result<(), PaneError> {
let focus_pid = focused_seq_idx
.and_then(|idx| sequence.get(idx))
.or_else(|| sequence.get(0));
viewport.focus = focus_pid;
match (focus_pid, strategy) {
(Some(pid), Some(s)) => {
crate::strategy::try_apply_focus(s, tree, sequence, viewport, pid);
}
_ => {}
}
for &idx in collapsed_seq_indices {
let Some(pid) = sequence.get(idx) else {
continue;
};
let Ok(current) = tree.panel_constraints(pid) else {
continue;
};
viewport.saved_constraints.insert(pid, current);
tree.set_constraints(pid, fixed(0.0))?;
viewport.collapsed.insert(pid);
}
Ok(())
}