use crate::component::{Component, EventCx, LayoutCx, MeasureCx};
use crate::event::Event;
use crate::geom::{Rect, Size};
use crate::layout::Constraint;
use crate::node::Node;
use crate::render::RenderCx;
use crate::style::Style;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SplitDirection {
Horizontal,
Vertical,
}
pub struct SplitPane {
first: Option<Node>,
second: Option<Node>,
ratio: u16,
direction: SplitDirection,
rect: Rect,
style: Style,
}
impl SplitPane {
pub fn new() -> Self {
Self {
first: None,
second: None,
ratio: 50,
direction: SplitDirection::Horizontal,
rect: Rect::default(),
style: Style::default(),
}
}
pub fn first(mut self, component: impl Component + 'static) -> Self {
self.first = Some(Node::new(component));
self
}
pub fn second(mut self, component: impl Component + 'static) -> Self {
self.second = Some(Node::new(component));
self
}
pub fn ratio(mut self, ratio: u16) -> Self {
self.ratio = ratio.min(100);
self
}
pub fn direction(mut self, direction: SplitDirection) -> Self {
self.direction = direction;
self
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
}
impl Component for SplitPane {
fn render(&self, cx: &mut RenderCx) {
let is_h = self.direction == SplitDirection::Horizontal;
let (first_rect, second_rect) = self.child_rects();
if let Some(child) = &self.first {
let saved = child.rect();
child.set_rect(first_rect);
child.render_with_clip(cx.buffer, cx.focused_id, Some(first_rect));
child.set_rect(saved);
}
let div_style = Style::default().bg(crate::style::Color::White).fg(crate::style::Color::Black);
if is_h {
let div_x = second_rect.x.saturating_sub(1);
for y in self.rect.y..self.rect.y.saturating_add(self.rect.height) {
cx.buffer.write_text(
crate::geom::Pos { x: div_x, y },
self.rect, "│", &div_style,
);
}
} else {
let div_y = second_rect.y.saturating_sub(1);
for x in self.rect.x..self.rect.x.saturating_add(self.rect.width) {
cx.buffer.write_text(
crate::geom::Pos { x, y: div_y },
self.rect, "─", &div_style,
);
}
}
if let Some(child) = &self.second {
let saved = child.rect();
child.set_rect(second_rect);
child.render_with_clip(cx.buffer, cx.focused_id, Some(second_rect));
child.set_rect(saved);
}
}
fn measure(&self, constraint: Constraint, _cx: &mut MeasureCx) -> Size {
Size { width: constraint.max.width, height: constraint.max.height }
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
if let Event::Key(key_event) = event {
if key_event.modifiers.ctrl {
match self.direction {
SplitDirection::Horizontal => {
match &key_event.key {
crate::event::Key::Left => {
self.ratio = self.ratio.saturating_sub(5);
cx.invalidate_layout();
return;
}
crate::event::Key::Right => {
self.ratio = (self.ratio + 5).min(100);
cx.invalidate_layout();
return;
}
_ => {}
}
}
SplitDirection::Vertical => {
match &key_event.key {
crate::event::Key::Up => {
self.ratio = self.ratio.saturating_sub(5);
cx.invalidate_layout();
return;
}
crate::event::Key::Down => {
self.ratio = (self.ratio + 5).min(100);
cx.invalidate_layout();
return;
}
_ => {}
}
}
}
}
}
if cx.phase() == crate::event::EventPhase::Capture {
for child_opt in [&mut self.first, &mut self.second].iter_mut() {
if let Some(child) = child_opt {
let mut child_cx = EventCx::with_task_sender(
&mut child.dirty, cx.global_dirty, cx.quit,
cx.phase, cx.propagation_stopped, cx.task_sender.clone(),
);
child.component.event(event, &mut child_cx);
}
}
}
}
fn layout(&mut self, rect: Rect, _cx: &mut LayoutCx) {
self.rect = rect;
let (first_rect, second_rect) = self.child_rects();
if let Some(child) = &mut self.first { child.layout(first_rect); }
if let Some(child) = &mut self.second { child.layout(second_rect); }
}
fn for_each_child(&self, f: &mut dyn FnMut(&Node)) {
if let Some(child) = &self.first { f(child); }
if let Some(child) = &self.second { f(child); }
}
fn for_each_child_mut(&mut self, f: &mut dyn FnMut(&mut Node)) {
if let Some(child) = &mut self.first { f(child); }
if let Some(child) = &mut self.second { f(child); }
}
fn focusable(&self) -> bool { false }
fn style(&self) -> Style { self.style.clone() }
}
impl SplitPane {
fn child_rects(&self) -> (Rect, Rect) {
let is_h = self.direction == SplitDirection::Horizontal;
let total = if is_h { self.rect.width } else { self.rect.height };
let divider = 1u16;
let available = total.saturating_sub(divider);
let first_size = (available as u32 * self.ratio as u32 / 100) as u16;
let second_size = available.saturating_sub(first_size);
if is_h {
let first = Rect { x: self.rect.x, y: self.rect.y, width: first_size, height: self.rect.height };
let sx = self.rect.x.saturating_add(first_size).saturating_add(divider);
let second = Rect { x: sx, y: self.rect.y, width: second_size, height: self.rect.height };
(first, second)
} else {
let first = Rect { x: self.rect.x, y: self.rect.y, width: self.rect.width, height: first_size };
let sy = self.rect.y.saturating_add(first_size).saturating_add(divider);
let second = Rect { x: self.rect.x, y: sy, width: self.rect.width, height: second_size };
(first, second)
}
}
}