Added vertical pane support

This commit is contained in:
Joshua Barretto 2025-11-12 16:54:51 +00:00
parent 4b5579c726
commit 1b4268b078
3 changed files with 184 additions and 44 deletions

View file

@ -7,7 +7,7 @@ use crossterm::event::{
}; };
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum Dir { pub enum Dir {
Left, Left,
Right, Right,
@ -65,7 +65,7 @@ pub enum Dist {
Doc, Doc,
} }
#[derive(Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum MouseAction { pub enum MouseAction {
Click, Click,
Drag, Drag,

View file

@ -20,6 +20,7 @@ pub struct Input {
// x/y location in the buffer that the pane is trying to focus on // x/y location in the buffer that the pane is trying to focus on
pub focus: [isize; 2], pub focus: [isize; 2],
// Remember the last area for things like scrolling // Remember the last area for things like scrolling
pub frame_area: Area,
pub last_area: Area, pub last_area: Area,
pub last_scroll_pos: Option<([isize; 2], usize, usize)>, pub last_scroll_pos: Option<([isize; 2], usize, usize)>,
pub scroll_grab: Option<(usize, isize)>, pub scroll_grab: Option<(usize, isize)>,
@ -147,6 +148,7 @@ impl Input {
} }
Some(Action::Mouse(MouseAction::Click, pos, false, drag_id)) => { Some(Action::Mouse(MouseAction::Click, pos, false, drag_id)) => {
if let Some((scroll_pos, h, _)) = self.last_scroll_pos if let Some((scroll_pos, h, _)) = self.last_scroll_pos
&& let Some(pos) = self.frame_area.contains(pos)
&& scroll_pos[0] == pos[0] && scroll_pos[0] == pos[0]
&& (scroll_pos[1]..=scroll_pos[1] + h as isize).contains(&pos[1]) && (scroll_pos[1]..=scroll_pos[1] + h as isize).contains(&pos[1])
{ {
@ -169,6 +171,7 @@ impl Input {
if self.scroll_grab.map_or(false, |(di, _)| di == drag_id) => if self.scroll_grab.map_or(false, |(di, _)| di == drag_id) =>
{ {
if let Some((_, offset)) = self.scroll_grab if let Some((_, offset)) = self.scroll_grab
&& let Some(pos) = self.frame_area.contains(pos)
&& let Some((_, scroll_sz, frame_sz)) = self.last_scroll_pos && let Some((_, scroll_sz, frame_sz)) = self.last_scroll_pos
{ {
self.focus[1] = ((pos[1] - offset).max(0) as usize self.focus[1] = ((pos[1] - offset).max(0) as usize
@ -181,11 +184,13 @@ impl Input {
Action::Mouse(MouseAction::Drag, pos, false, _) Action::Mouse(MouseAction::Drag, pos, false, _)
| Action::Mouse(MouseAction::Click, pos, true, _), | Action::Mouse(MouseAction::Click, pos, true, _),
) => { ) => {
buffer.goto_cursor( if let Some(pos) = self.frame_area.contains(pos) {
cursor_id, buffer.goto_cursor(
[self.focus[0] + pos[0], self.focus[1] + pos[1]], cursor_id,
false, [self.focus[0] + pos[0], self.focus[1] + pos[1]],
); false,
);
}
Ok(Resp::handled(None)) Ok(Resp::handled(None))
} }
Some(Action::Undo) => { Some(Action::Undo) => {
@ -250,6 +255,8 @@ impl Input {
finder: Option<&Finder>, finder: Option<&Finder>,
outer_frame: &mut Rect, outer_frame: &mut Rect,
) { ) {
self.frame_area = outer_frame.area();
// Add frame // Add frame
let mut frame = if matches!(self.mode, Mode::SearchResult) { let mut frame = if matches!(self.mode, Mode::SearchResult) {
outer_frame.rect([0; 2], [!0; 2]) outer_frame.rect([0; 2], [!0; 2])

View file

@ -11,45 +11,25 @@ pub struct Pane {
last_area: Area, last_area: Area,
} }
pub struct Panes { pub struct VBox {
selected: usize, selected: usize,
panes: Vec<Pane>, panes: Vec<Pane>,
last_area: Area, last_area: Area,
} }
impl Panes { impl Element<()> for VBox {
pub fn new(state: &mut State, buffers: &[BufferId]) -> Self { fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp<()>, Event> {
Self {
selected: 0,
panes: buffers
.iter()
.map(|b| Pane {
kind: PaneKind::Doc(Doc::new(state, *b)),
last_area: Area::default(),
})
.collect(),
last_area: Default::default(),
}
}
pub fn selected_mut(&mut self) -> Option<&mut Pane> {
self.panes.get_mut(self.selected)
}
}
impl Element for Panes {
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp, Event> {
match event.to_action(|e| { match event.to_action(|e| {
e.to_pane_move() e.to_pane_move()
.map(Action::PaneMove) .map(Action::PaneMove)
.or_else(|| e.to_pane_open().map(Action::PaneOpen)) .or_else(|| e.to_pane_open().map(Action::PaneOpen))
.or_else(|| e.to_pane_close()) .or_else(|| e.to_pane_close())
}) { }) {
Some(Action::PaneMove(Dir::Left)) => { Some(Action::PaneMove(Dir::Up)) => {
self.selected = (self.selected + self.panes.len() - 1) % self.panes.len(); self.selected = (self.selected + self.panes.len() - 1) % self.panes.len();
Ok(Resp::handled(None)) Ok(Resp::handled(None))
} }
Some(Action::PaneMove(Dir::Right)) => { Some(Action::PaneMove(Dir::Down)) => {
self.selected = (self.selected + 1) % self.panes.len(); self.selected = (self.selected + 1) % self.panes.len();
Ok(Resp::handled(None)) Ok(Resp::handled(None))
} }
@ -60,16 +40,20 @@ impl Element for Panes {
PaneKind::Doc(doc) => doc.close(state), PaneKind::Doc(doc) => doc.close(state),
} }
self.selected = self.selected.clamp(0, self.panes.len().saturating_sub(1)); self.selected = self.selected.clamp(0, self.panes.len().saturating_sub(1));
Ok(Resp::handled(None)) if self.panes.is_empty() {
Ok(Resp::end(None))
} else {
Ok(Resp::handled(None))
}
} else { } else {
Err(event) Err(event)
} }
} }
Some(Action::PaneOpen(dir)) => { Some(Action::PaneOpen(dir)) => {
let new_idx = match dir { let new_idx = match dir {
Dir::Left => self.selected.saturating_sub(1).clamp(0, self.panes.len()), Dir::Up => self.selected.clamp(0, self.panes.len()),
Dir::Right => (self.selected + 1).min(self.panes.len()), Dir::Down => (self.selected + 1).min(self.panes.len()),
Dir::Up | Dir::Down => return Err(event), Dir::Left | Dir::Right => return Err(event),
}; };
let kind = match state.buffers.keys().next() { let kind = match state.buffers.keys().next() {
Some(b) => PaneKind::Doc(Doc::new(state, b)), Some(b) => PaneKind::Doc(Doc::new(state, b)),
@ -85,16 +69,21 @@ impl Element for Panes {
self.selected = new_idx; self.selected = new_idx;
Ok(Resp::handled(None)) Ok(Resp::handled(None))
} }
Some(ref action @ Action::Mouse(ref m_action, pos, _, _)) => { Some(action @ Action::Mouse(m_action, pos, is_ctrl, drag_id)) => {
for (i, pane) in self.panes.iter_mut().enumerate() { for (i, pane) in self.panes.iter_mut().enumerate() {
if pane.last_area.contains(pos).is_some() { if pane.last_area.contains(pos).is_some() {
if matches!(m_action, MouseAction::Click) { if matches!(m_action, MouseAction::Click) {
self.selected = i; self.selected = i;
} }
match &mut pane.kind { match &mut pane.kind {
PaneKind::Doc(doc) => return doc.handle(state, action.clone().into()), PaneKind::Doc(doc) => {
return doc
.handle(state, action.clone().into())
.map(Resp::into_can_end);
}
PaneKind::Empty => {} PaneKind::Empty => {}
} }
break;
} }
} }
Ok(Resp::handled(None)) Ok(Resp::handled(None))
@ -105,7 +94,7 @@ impl Element for Panes {
// Pass to pane // Pass to pane
match &mut pane.kind { match &mut pane.kind {
PaneKind::Empty => Err(event), PaneKind::Empty => Err(event),
PaneKind::Doc(doc) => doc.handle(state, event), PaneKind::Doc(doc) => doc.handle(state, event).map(Resp::into_can_end),
} }
} else { } else {
// No active pane, don't handle // No active pane, don't handle
@ -116,20 +105,20 @@ impl Element for Panes {
} }
} }
impl Visual for Panes { impl Visual for VBox {
fn render(&mut self, state: &State, frame: &mut Rect) { fn render(&mut self, state: &State, frame: &mut Rect) {
let n = self.panes.len(); let n = self.panes.len();
let frame_w = frame.size()[0]; let frame_h = frame.size()[1];
let boundary = |i| frame_w * i / n; let boundary = |i| frame_h * i / n;
self.last_area = frame.area(); self.last_area = frame.area();
for (i, pane) in self.panes.iter_mut().enumerate() { for (i, pane) in self.panes.iter_mut().enumerate() {
let (x0, x1) = (boundary(i), boundary(i + 1)); let (y0, y1) = (boundary(i), boundary(i + 1));
// Draw pane contents // Draw pane contents
frame frame
.rect([x0, 0], [x1 - x0, frame.size()[1]]) .rect([0, y0], [frame.size()[0], y1 - y0])
.with_focus(self.selected == i) .with_focus(self.selected == i)
.with(|frame| { .with(|frame| {
pane.last_area = frame.area(); pane.last_area = frame.area();
@ -141,3 +130,147 @@ impl Visual for Panes {
} }
} }
} }
pub struct Panes {
selected: usize,
vboxes: Vec<VBox>,
last_area: Area,
}
impl Panes {
pub fn new(state: &mut State, buffers: &[BufferId]) -> Self {
Self {
selected: 0,
vboxes: buffers
.iter()
.map(|b| VBox {
selected: 0,
panes: vec![Pane {
kind: PaneKind::Doc(Doc::new(state, *b)),
last_area: Area::default(),
}],
last_area: Area::default(),
})
.collect(),
last_area: Default::default(),
}
}
pub fn selected_mut(&mut self) -> Option<&mut Pane> {
let vbox = self.vboxes.get_mut(self.selected)?;
vbox.panes.get_mut(vbox.selected)
}
pub fn selected_vbox_mut(&mut self) -> Option<&mut VBox> {
self.vboxes.get_mut(self.selected)
}
}
impl Element for Panes {
fn handle(&mut self, state: &mut State, event: Event) -> Result<Resp, Event> {
match event.to_action(|e| {
e.to_pane_move()
.map(Action::PaneMove)
.or_else(|| e.to_pane_open().map(Action::PaneOpen))
.or_else(|| e.to_pane_close())
}) {
Some(Action::PaneMove(Dir::Left)) => {
self.selected = (self.selected + self.vboxes.len() - 1) % self.vboxes.len();
Ok(Resp::handled(None))
}
Some(Action::PaneMove(Dir::Right)) => {
self.selected = (self.selected + 1) % self.vboxes.len();
Ok(Resp::handled(None))
}
Some(Action::PaneOpen(dir @ (Dir::Left | Dir::Right))) => {
let new_idx = match dir {
Dir::Left => self.selected.clamp(0, self.vboxes.len()),
Dir::Right => (self.selected + 1).min(self.vboxes.len()),
_ => unreachable!(),
};
let kind = match state.buffers.keys().next() {
Some(b) => PaneKind::Doc(Doc::new(state, b)),
None => PaneKind::Empty,
};
self.vboxes.insert(
new_idx,
VBox {
selected: 0,
panes: vec![Pane {
kind,
last_area: Area::default(),
}],
last_area: Area::default(),
},
);
self.selected = new_idx;
Ok(Resp::handled(None))
}
Some(action @ Action::Mouse(m_action, pos, is_ctrl, drag_id)) => {
for (i, vbox) in self.vboxes.iter_mut().enumerate() {
if vbox.last_area.contains(pos).is_some() {
if matches!(m_action, MouseAction::Click) {
self.selected = i;
}
let resp = vbox.handle(state, action.clone().into())?;
if resp.is_end() {
self.vboxes.remove(self.selected);
self.selected = self.selected.min(self.vboxes.len()).saturating_sub(1);
}
return Ok(Resp::handled(resp.event));
}
}
Ok(Resp::handled(None))
}
// Pass anything else through to the active pane
action => {
let mut to_handle = self.selected;
// Set selected vbox on mouse click
if let Some(Action::Mouse(ref m_action, pos, is_ctrl, drag_id)) = action {
for (i, vbox) in self.vboxes.iter_mut().enumerate() {
if vbox.last_area.contains(pos).is_some() {
if matches!(m_action, MouseAction::Click) {
self.selected = i;
}
to_handle = i;
break;
}
}
}
if let Some(vbox) = self.vboxes.get_mut(to_handle) {
// Pass to vbox
let resp = vbox.handle(state, event)?;
if resp.is_end() {
self.vboxes.remove(self.selected);
self.selected = self.selected.min(self.vboxes.len().saturating_sub(1));
}
Ok(Resp::handled(resp.event))
} else {
// No active pane, don't handle
Err(event)
}
}
}
}
}
impl Visual for Panes {
fn render(&mut self, state: &State, frame: &mut Rect) {
let n = self.vboxes.len();
let frame_w = frame.size()[0];
let boundary = |i| frame_w * i / n;
self.last_area = frame.area();
for (i, vbox) in self.vboxes.iter_mut().enumerate() {
let (x0, x1) = (boundary(i), boundary(i + 1));
// Draw pane contents
frame
.rect([x0, 0], [x1 - x0, frame.size()[1]])
.with_focus(self.selected == i)
.with(|frame| vbox.render(state, frame));
}
}
}