use crate::pane::PaneId;
const RATIO_MIN: u16 = 15;
const RATIO_MAX: u16 = 85;
const RATIO_DEFAULT: u16 = 50;
#[derive(Debug, Clone)]
pub enum PaneLayout {
Single(PaneId),
HSplit {
top: Box<PaneLayout>,
bottom: Box<PaneLayout>,
ratio: u16,
},
VSplit {
left: Box<PaneLayout>,
right: Box<PaneLayout>,
ratio: u16,
},
}
impl PaneLayout {
pub fn pane_ids(&self) -> Vec<PaneId> {
match self {
PaneLayout::Single(id) => vec![*id],
PaneLayout::HSplit { top, bottom, .. } => {
let mut v = top.pane_ids();
v.extend(bottom.pane_ids());
v
}
PaneLayout::VSplit { left, right, .. } => {
let mut v = left.pane_ids();
v.extend(right.pane_ids());
v
}
}
}
pub fn count(&self) -> usize {
self.pane_ids().len()
}
fn contains(&self, id: PaneId) -> bool {
self.pane_ids().contains(&id)
}
pub fn hsplit(&mut self, focused: PaneId, new_id: PaneId) -> bool {
match self {
PaneLayout::Single(id) if *id == focused => {
let old = *id;
*self = PaneLayout::HSplit {
top: Box::new(PaneLayout::Single(old)),
bottom: Box::new(PaneLayout::Single(new_id)),
ratio: RATIO_DEFAULT,
};
true
}
PaneLayout::HSplit { top, bottom, .. } => {
top.hsplit(focused, new_id) || bottom.hsplit(focused, new_id)
}
PaneLayout::VSplit { left, right, .. } => {
left.hsplit(focused, new_id) || right.hsplit(focused, new_id)
}
_ => false,
}
}
pub fn vsplit(&mut self, focused: PaneId, new_id: PaneId) -> bool {
match self {
PaneLayout::Single(id) if *id == focused => {
let old = *id;
*self = PaneLayout::VSplit {
left: Box::new(PaneLayout::Single(old)),
right: Box::new(PaneLayout::Single(new_id)),
ratio: RATIO_DEFAULT,
};
true
}
PaneLayout::HSplit { top, bottom, .. } => {
top.vsplit(focused, new_id) || bottom.vsplit(focused, new_id)
}
PaneLayout::VSplit { left, right, .. } => {
left.vsplit(focused, new_id) || right.vsplit(focused, new_id)
}
_ => false,
}
}
pub fn resize(&mut self, focused: PaneId, delta: i16) -> bool {
match self {
PaneLayout::Single(_) => false,
PaneLayout::HSplit { top, bottom, ratio } => {
if top.resize(focused, delta) || bottom.resize(focused, delta) {
return true;
}
let focus_first = top.contains(focused);
if focus_first || bottom.contains(focused) {
let signed = if focus_first { delta } else { -delta };
*ratio = (*ratio as i16 + signed).clamp(RATIO_MIN as i16, RATIO_MAX as i16) as u16;
return true;
}
false
}
PaneLayout::VSplit { left, right, ratio } => {
if left.resize(focused, delta) || right.resize(focused, delta) {
return true;
}
let focus_first = left.contains(focused);
if focus_first || right.contains(focused) {
let signed = if focus_first { delta } else { -delta };
*ratio = (*ratio as i16 + signed).clamp(RATIO_MIN as i16, RATIO_MAX as i16) as u16;
return true;
}
false
}
}
}
pub fn remove(&mut self, id: PaneId) -> bool {
match self {
PaneLayout::Single(_) => false,
PaneLayout::HSplit { top, bottom, .. } => {
if matches!(top.as_ref(), PaneLayout::Single(x) if *x == id) {
*self = (**bottom).clone();
return true;
}
if matches!(bottom.as_ref(), PaneLayout::Single(x) if *x == id) {
*self = (**top).clone();
return true;
}
top.remove(id) || bottom.remove(id)
}
PaneLayout::VSplit { left, right, .. } => {
if matches!(left.as_ref(), PaneLayout::Single(x) if *x == id) {
*self = (**right).clone();
return true;
}
if matches!(right.as_ref(), PaneLayout::Single(x) if *x == id) {
*self = (**left).clone();
return true;
}
left.remove(id) || right.remove(id)
}
}
}
pub fn next_pane(&self, current: PaneId) -> PaneId {
let ids = self.pane_ids();
if ids.len() <= 1 {
return current;
}
let pos = ids.iter().position(|&id| id == current).unwrap_or(0);
ids[(pos + 1) % ids.len()]
}
pub fn prev_pane(&self, current: PaneId) -> PaneId {
let ids = self.pane_ids();
if ids.len() <= 1 {
return current;
}
let pos = ids.iter().position(|&id| id == current).unwrap_or(0);
ids[if pos == 0 { ids.len() - 1 } else { pos - 1 }]
}
}