use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PathStep {
Left,
Right,
Top,
Bottom,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SplitNode {
Leaf {
project_id: Option<String>,
id: usize,
},
Horizontal {
left: Box<SplitNode>,
right: Box<SplitNode>,
ratio: f32,
},
Vertical {
top: Box<SplitNode>,
bottom: Box<SplitNode>,
ratio: f32,
},
}
impl SplitNode {
pub fn new_leaf(id: usize) -> Self {
SplitNode::Leaf {
project_id: None,
id,
}
}
pub fn split_horizontal(&mut self, new_id: usize) {
let old = std::mem::replace(self, SplitNode::new_leaf(0));
*self = SplitNode::Horizontal {
left: Box::new(old),
right: Box::new(SplitNode::new_leaf(new_id)),
ratio: 0.5,
};
}
pub fn split_vertical(&mut self, new_id: usize) {
let old = std::mem::replace(self, SplitNode::new_leaf(0));
*self = SplitNode::Vertical {
top: Box::new(old),
bottom: Box::new(SplitNode::new_leaf(new_id)),
ratio: 0.5,
};
}
pub fn find_pane_mut(&mut self, id: usize) -> Option<&mut SplitNode> {
match self {
SplitNode::Leaf { id: leaf_id, .. } if *leaf_id == id => Some(self),
SplitNode::Horizontal { left, right, .. } => {
left.find_pane_mut(id).or_else(|| right.find_pane_mut(id))
}
SplitNode::Vertical { top, bottom, .. } => {
top.find_pane_mut(id).or_else(|| bottom.find_pane_mut(id))
}
_ => None,
}
}
pub fn find_pane(&self, id: usize) -> Option<&SplitNode> {
match self {
SplitNode::Leaf { id: leaf_id, .. } if *leaf_id == id => Some(self),
SplitNode::Horizontal { left, right, .. } => {
left.find_pane(id).or_else(|| right.find_pane(id))
}
SplitNode::Vertical { top, bottom, .. } => {
top.find_pane(id).or_else(|| bottom.find_pane(id))
}
_ => None,
}
}
pub fn close_pane(&mut self, id: usize) -> bool {
match self {
SplitNode::Leaf { id: leaf_id, .. } if *leaf_id == id => {
false
}
SplitNode::Horizontal { left, right, .. } => {
if let SplitNode::Leaf { id: left_id, .. } = left.as_ref()
&& *left_id == id {
*self = *right.clone();
return true;
}
if let SplitNode::Leaf { id: right_id, .. } = right.as_ref()
&& *right_id == id {
*self = *left.clone();
return true;
}
left.close_pane(id) || right.close_pane(id)
}
SplitNode::Vertical { top, bottom, .. } => {
if let SplitNode::Leaf { id: top_id, .. } = top.as_ref()
&& *top_id == id {
*self = *bottom.clone();
return true;
}
if let SplitNode::Leaf { id: bottom_id, .. } = bottom.as_ref()
&& *bottom_id == id {
*self = *top.clone();
return true;
}
top.close_pane(id) || bottom.close_pane(id)
}
_ => false,
}
}
pub fn collect_pane_ids(&self) -> Vec<usize> {
match self {
SplitNode::Leaf { id, .. } => vec![*id],
SplitNode::Horizontal { left, right, .. } => {
let mut ids = left.collect_pane_ids();
ids.extend(right.collect_pane_ids());
ids
}
SplitNode::Vertical { top, bottom, .. } => {
let mut ids = top.collect_pane_ids();
ids.extend(bottom.collect_pane_ids());
ids
}
}
}
pub fn clear_project_from_all_panes(&mut self, project_name: &str) {
match self {
SplitNode::Leaf { project_id, .. } => {
if let Some(id) = project_id
&& id == project_name {
*project_id = None;
}
}
SplitNode::Horizontal { left, right, .. } => {
left.clear_project_from_all_panes(project_name);
right.clear_project_from_all_panes(project_name);
}
SplitNode::Vertical { top, bottom, .. } => {
top.clear_project_from_all_panes(project_name);
bottom.clear_project_from_all_panes(project_name);
}
}
}
pub fn find_adjacent_pane(&self, current_id: usize, direction: Direction) -> Option<usize> {
let mut path = Vec::new();
if !self.find_node_path(current_id, &mut path) {
return None; }
for i in (0..path.len()).rev() {
let step = path[i];
let parent_node = self.get_node_at_path(&path[..i]);
let sibling_node = match (parent_node, direction, step) {
(
Some(SplitNode::Horizontal { left, right: _, .. }),
Direction::Left,
PathStep::Right,
) => Some(left.as_ref()),
(
Some(SplitNode::Horizontal { left: _, right, .. }),
Direction::Right,
PathStep::Left,
) => Some(right.as_ref()),
(
Some(SplitNode::Vertical { top, bottom: _, .. }),
Direction::Up,
PathStep::Bottom,
) => Some(top.as_ref()),
(
Some(SplitNode::Vertical { top: _, bottom, .. }),
Direction::Down,
PathStep::Top,
) => Some(bottom.as_ref()),
_ => None,
};
if let Some(sibling) = sibling_node {
return sibling.get_leaf_with_preference(&path);
}
}
None }
fn get_node_at_path(&self, path: &[PathStep]) -> Option<&SplitNode> {
let mut current = self;
for &step in path {
current = match (current, step) {
(SplitNode::Horizontal { left, .. }, PathStep::Left) => left,
(SplitNode::Horizontal { right, .. }, PathStep::Right) => right,
(SplitNode::Vertical { top, .. }, PathStep::Top) => top,
(SplitNode::Vertical { bottom, .. }, PathStep::Bottom) => bottom,
_ => return None,
};
}
Some(current)
}
#[allow(dead_code)]
fn contains_pane(&self, id: usize) -> bool {
match self {
SplitNode::Leaf { id: leaf_id, .. } => *leaf_id == id,
SplitNode::Horizontal { left, right, .. } => {
left.contains_pane(id) || right.contains_pane(id)
}
SplitNode::Vertical { top, bottom, .. } => {
top.contains_pane(id) || bottom.contains_pane(id)
}
}
}
#[allow(dead_code)]
fn get_leftmost_pane(&self) -> Option<usize> {
match self {
SplitNode::Leaf { id, .. } => Some(*id),
SplitNode::Horizontal { left, .. } => left.get_leftmost_pane(),
SplitNode::Vertical { top, .. } => top.get_leftmost_pane(),
}
}
#[allow(dead_code)]
fn get_rightmost_pane(&self) -> Option<usize> {
match self {
SplitNode::Leaf { id, .. } => Some(*id),
SplitNode::Horizontal { right, .. } => right.get_rightmost_pane(),
SplitNode::Vertical { top, .. } => top.get_rightmost_pane(),
}
}
#[allow(dead_code)]
fn get_topmost_pane(&self) -> Option<usize> {
match self {
SplitNode::Leaf { id, .. } => Some(*id),
SplitNode::Horizontal { left, .. } => left.get_topmost_pane(),
SplitNode::Vertical { top, .. } => top.get_topmost_pane(),
}
}
#[allow(dead_code)]
fn get_bottommost_pane(&self) -> Option<usize> {
match self {
SplitNode::Leaf { id, .. } => Some(*id),
SplitNode::Horizontal { left, .. } => left.get_bottommost_pane(),
SplitNode::Vertical { bottom, .. } => bottom.get_bottommost_pane(),
}
}
fn find_node_path(&self, target_id: usize, path: &mut Vec<PathStep>) -> bool {
match self {
SplitNode::Leaf { id, .. } => *id == target_id,
SplitNode::Horizontal { left, right, .. } => {
path.push(PathStep::Left);
if left.find_node_path(target_id, path) {
return true;
}
path.pop();
path.push(PathStep::Right);
if right.find_node_path(target_id, path) {
return true;
}
path.pop();
false
}
SplitNode::Vertical { top, bottom, .. } => {
path.push(PathStep::Top);
if top.find_node_path(target_id, path) {
return true;
}
path.pop();
path.push(PathStep::Bottom);
if bottom.find_node_path(target_id, path) {
return true;
}
path.pop();
false
}
}
}
#[allow(dead_code)]
fn get_first_leaf(&self) -> Option<usize> {
match self {
SplitNode::Leaf { id, .. } => Some(*id),
SplitNode::Horizontal { left, .. } => left.get_first_leaf(),
SplitNode::Vertical { top, .. } => top.get_first_leaf(),
}
}
fn get_leaf_with_preference(&self, path: &[PathStep]) -> Option<usize> {
match self {
SplitNode::Leaf { id, .. } => Some(*id),
SplitNode::Horizontal { left, right, .. } => {
let prefer_right = path.contains(&PathStep::Right);
if prefer_right {
right.get_leaf_with_preference(path)
} else {
left.get_leaf_with_preference(path)
}
}
SplitNode::Vertical { top, bottom, .. } => {
let prefer_bottom = path.contains(&PathStep::Bottom);
if prefer_bottom {
bottom.get_leaf_with_preference(path)
} else {
top.get_leaf_with_preference(path)
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Left,
Right,
Up,
Down,
}