use super::state::PaneId;
use ratatui::layout::Rect;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SplitDirection {
Horizontal,
Vertical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SplitNode {
Leaf(PaneId),
Split {
direction: SplitDirection,
ratio: f32,
first: Box<SplitNode>,
second: Box<SplitNode>,
},
}
impl SplitNode {
pub fn layout(&self, area: Rect) -> Vec<(PaneId, Rect)> {
let mut result = Vec::new();
self.layout_inner(area, &mut result);
result
}
fn layout_inner(&self, area: Rect, out: &mut Vec<(PaneId, Rect)>) {
match self {
SplitNode::Leaf(id) => {
out.push((*id, area));
}
SplitNode::Split {
direction,
ratio,
first,
second,
} => {
let (a, b) = split_rect(area, *direction, *ratio);
first.layout_inner(a, out);
second.layout_inner(b, out);
}
}
}
pub fn leaves(&self) -> Vec<PaneId> {
let mut out = Vec::new();
self.collect_leaves(&mut out);
out
}
fn collect_leaves(&self, out: &mut Vec<PaneId>) {
match self {
SplitNode::Leaf(id) => out.push(*id),
SplitNode::Split { first, second, .. } => {
first.collect_leaves(out);
second.collect_leaves(out);
}
}
}
pub fn first_leaf(&self) -> PaneId {
match self {
SplitNode::Leaf(id) => *id,
SplitNode::Split { first, .. } => first.first_leaf(),
}
}
pub fn replace_leaf(self, target: PaneId, direction: SplitDirection, new: PaneId) -> Self {
match self {
SplitNode::Leaf(id) if id == target => SplitNode::Split {
direction,
ratio: 0.5,
first: Box::new(SplitNode::Leaf(id)),
second: Box::new(SplitNode::Leaf(new)),
},
SplitNode::Leaf(id) => SplitNode::Leaf(id),
SplitNode::Split {
direction: d,
ratio,
first,
second,
} => SplitNode::Split {
direction: d,
ratio,
first: Box::new(first.replace_leaf(target, direction, new)),
second: Box::new(second.replace_leaf(target, direction, new)),
},
}
}
pub fn remove_leaf(self, target: PaneId) -> Self {
match self {
SplitNode::Leaf(id) => SplitNode::Leaf(id), SplitNode::Split {
direction,
ratio,
first,
second,
} => {
if matches!(&*first, SplitNode::Leaf(id) if *id == target) {
return *second;
}
if matches!(&*second, SplitNode::Leaf(id) if *id == target) {
return *first;
}
SplitNode::Split {
direction,
ratio,
first: Box::new(first.remove_leaf(target)),
second: Box::new(second.remove_leaf(target)),
}
}
}
}
}
fn split_rect(area: Rect, direction: SplitDirection, ratio: f32) -> (Rect, Rect) {
match direction {
SplitDirection::Horizontal => {
let left_width = ((area.width as f32) * ratio).round() as u16;
let right_width = area.width.saturating_sub(left_width);
let left = Rect {
width: left_width,
..area
};
let right = Rect {
x: area.x + left_width,
width: right_width,
..area
};
(left, right)
}
SplitDirection::Vertical => {
let top_height = ((area.height as f32) * ratio).round() as u16;
let bottom_height = area.height.saturating_sub(top_height);
let top = Rect {
height: top_height,
..area
};
let bottom = Rect {
y: area.y + top_height,
height: bottom_height,
..area
};
(top, bottom)
}
}
}