use super::*;
const KEY_STEP: f64 = 0.05;
const RATIO_GROW_SCALE: f64 = 1000.0;
#[derive(Debug, Clone, Copy)]
enum SplitOrientation {
Horizontal,
Vertical,
}
impl Context {
pub fn split_pane<L, R>(
&mut self,
state: &mut SplitPaneState,
left: L,
right: R,
) -> SplitPaneResponse
where
L: FnOnce(&mut Context),
R: FnOnce(&mut Context),
{
self.split_pane_impl(SplitOrientation::Horizontal, state, left, right)
}
pub fn vsplit_pane<T, B>(
&mut self,
state: &mut SplitPaneState,
top: T,
bottom: B,
) -> SplitPaneResponse
where
T: FnOnce(&mut Context),
B: FnOnce(&mut Context),
{
self.split_pane_impl(SplitOrientation::Vertical, state, top, bottom)
}
fn split_pane_impl<A, B>(
&mut self,
orientation: SplitOrientation,
state: &mut SplitPaneState,
first: A,
second: B,
) -> SplitPaneResponse
where
A: FnOnce(&mut Context),
B: FnOnce(&mut Context),
{
let handle_focused = self.register_focusable();
if handle_focused {
self.consume_split_pane_keys(state, orientation);
}
let handle_interaction_id = self.rollback.interaction_count;
self.consume_split_pane_drag(state, handle_interaction_id, orientation);
let theme = self.theme;
let ratio = state.ratio.clamp(state.min_ratio, 1.0 - state.min_ratio);
let left_grow = ((ratio * RATIO_GROW_SCALE).round() as u16).max(1);
let right_grow = (((1.0 - ratio) * RATIO_GROW_SCALE).round() as u16).max(1);
let drag_active = state.dragging;
let response = match orientation {
SplitOrientation::Horizontal => self.row(|ui| {
let _ = ui.container().grow(left_grow).col(first);
let handle_color = if handle_focused || drag_active {
theme.accent
} else {
theme.border
};
let _ = ui.container().w(1).grow(0).col(|ui| {
ui.styled("│", Style::new().fg(handle_color));
});
let _ = ui.container().grow(right_grow).col(second);
}),
SplitOrientation::Vertical => self.col(|ui| {
let _ = ui.container().grow(left_grow).col(first);
let handle_color = if handle_focused || drag_active {
theme.accent
} else {
theme.border
};
let _ = ui.container().h(1).grow(0).col(|ui| {
ui.styled("─", Style::new().fg(handle_color));
});
let _ = ui.container().grow(right_grow).col(second);
}),
};
SplitPaneResponse {
response,
ratio,
drag_active,
}
}
fn consume_split_pane_keys(
&mut self,
state: &mut SplitPaneState,
orientation: SplitOrientation,
) {
let (neg, pos) = match orientation {
SplitOrientation::Horizontal => (KeyCode::Left, KeyCode::Right),
SplitOrientation::Vertical => (KeyCode::Up, KeyCode::Down),
};
let mut consumed: Vec<usize> = Vec::new();
let mut delta = 0.0_f64;
for (i, key) in self.available_key_presses() {
if key.code == neg {
delta -= KEY_STEP;
consumed.push(i);
} else if key.code == pos {
delta += KEY_STEP;
consumed.push(i);
}
}
if delta.abs() > f64::EPSILON {
state.set_ratio(state.ratio + delta);
}
self.consume_indices(consumed);
}
fn consume_split_pane_drag(
&mut self,
state: &mut SplitPaneState,
handle_interaction_id: usize,
orientation: SplitOrientation,
) {
let outer_id = handle_interaction_id.saturating_sub(1);
let outer_rect = self.prev_hit_map.get(outer_id).copied();
let handle_rect = self.prev_hit_map.get(handle_interaction_id).copied();
let mut consumed: Vec<usize> = Vec::new();
let events: Vec<(usize, crate::event::MouseEvent)> = self
.events
.iter()
.enumerate()
.filter_map(|(i, e)| match e {
Event::Mouse(m) if !self.consumed[i] => Some((i, m.clone())),
_ => None,
})
.collect();
for (i, mouse) in events {
match mouse.kind {
MouseKind::Down(MouseButton::Left) => {
if let Some(rect) = handle_rect {
if rect.width > 0
&& mouse.x >= rect.x
&& mouse.x < rect.right()
&& mouse.y >= rect.y
&& mouse.y < rect.bottom()
{
state.dragging = true;
consumed.push(i);
}
}
}
MouseKind::Drag(MouseButton::Left) if state.dragging => {
if let Some(outer) = outer_rect {
let new_ratio = match orientation {
SplitOrientation::Horizontal => {
if outer.width <= 1 {
state.ratio
} else {
let rel = mouse
.x
.saturating_sub(outer.x)
.min(outer.width.saturating_sub(1));
f64::from(rel) / f64::from(outer.width)
}
}
SplitOrientation::Vertical => {
if outer.height <= 1 {
state.ratio
} else {
let rel = mouse
.y
.saturating_sub(outer.y)
.min(outer.height.saturating_sub(1));
f64::from(rel) / f64::from(outer.height)
}
}
};
state.set_ratio(new_ratio);
}
consumed.push(i);
}
MouseKind::Up(MouseButton::Left) if state.dragging => {
state.dragging = false;
consumed.push(i);
}
_ => {}
}
}
self.consume_indices(consumed);
}
}