use ratatui::layout::{Constraint, Layout, Rect};
use crate::editor::window::WindowId;
use crate::editor::window::tree::{Axis, Node};
#[derive(Debug, Default)]
pub struct Panes {
pub windows: Vec<(WindowId, Rect)>,
pub separators: Vec<(Rect, Axis)>,
}
#[must_use]
pub fn solve(node: &Node, area: Rect) -> Panes {
let mut panes = Panes::default();
partition(node, area, &mut panes);
panes
}
fn partition(node: &Node, area: Rect, panes: &mut Panes) {
let (axis, children) = match node {
Node::Leaf(id) => {
panes.windows.push((*id, area));
return;
}
Node::Split { axis, children } => (*axis, children),
};
let mut constraints = Vec::with_capacity(children.len() * 2 - 1);
for (index, branch) in children.iter().enumerate() {
if index > 0 {
constraints.push(Constraint::Length(1));
}
constraints.push(Constraint::Fill(branch.weight));
}
let slots = match axis {
Axis::Horizontal => Layout::horizontal(constraints).split(area),
Axis::Vertical => Layout::vertical(constraints).split(area),
};
for (index, slot) in slots.iter().enumerate() {
if index % 2 == 1 {
panes.separators.push((*slot, axis));
} else {
partition(&children[index / 2].node, *slot, panes);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::window::Window;
use crate::editor::window::tree::Windows;
fn area() -> Rect {
Rect::new(0, 0, 81, 24)
}
#[test]
fn a_single_window_takes_the_whole_area() {
let windows = Windows::new(Window::new(0));
let panes = solve(windows.root(), area());
assert_eq!(panes.windows, vec![(0, area())]);
assert!(panes.separators.is_empty());
}
#[test]
fn a_vertical_split_divides_the_width_and_leaves_a_rule() {
let mut windows = Windows::new(Window::new(0));
let right = windows.split(Axis::Horizontal);
let panes = solve(windows.root(), area());
assert_eq!(panes.windows.len(), 2);
assert_eq!(panes.separators.len(), 1);
let (_, left_rect) = panes.windows[0];
let (id, right_rect) = panes.windows[1];
assert_eq!(id, right);
assert_eq!(left_rect.width, 40);
assert_eq!(right_rect.width, 40);
assert_eq!(panes.separators[0].0.x, 40);
assert_eq!(panes.separators[0].0.width, 1);
assert_eq!(right_rect.x, 41);
}
#[test]
fn a_horizontal_split_divides_the_height() {
let mut windows = Windows::new(Window::new(0));
windows.split(Axis::Vertical);
let panes = solve(windows.root(), Rect::new(0, 0, 80, 25));
assert_eq!(panes.windows.len(), 2);
assert_eq!(panes.windows[0].1.height, 12);
assert_eq!(panes.windows[1].1.height, 12);
assert_eq!(panes.separators[0].0.height, 1);
}
#[test]
fn three_columns_get_two_rules() {
let mut windows = Windows::new(Window::new(0));
windows.split(Axis::Horizontal);
windows.split(Axis::Horizontal);
let panes = solve(windows.root(), area());
assert_eq!(panes.windows.len(), 3);
assert_eq!(panes.separators.len(), 2);
let covered: u16 = panes
.windows
.iter()
.map(|(_, rect)| rect.width)
.sum::<u16>()
+ panes.separators.len() as u16;
assert_eq!(covered, area().width);
}
#[test]
fn weights_are_respected() {
let mut windows = Windows::new(Window::new(0));
windows.split(Axis::Horizontal);
windows.resize(Axis::Horizontal, 1);
let panes = solve(windows.root(), Rect::new(0, 0, 61, 24));
assert_eq!(panes.windows[0].1.width, 20);
assert_eq!(panes.windows[1].1.width, 40);
}
#[test]
fn nested_splits_stay_inside_their_parent() {
let mut windows = Windows::new(Window::new(0));
windows.split(Axis::Horizontal);
windows.split(Axis::Vertical);
let panes = solve(windows.root(), area());
assert_eq!(panes.windows.len(), 3);
for (_, rect) in &panes.windows {
assert!(rect.right() <= area().right());
assert!(rect.bottom() <= area().bottom());
}
}
}