buflist: Add support for clicks to switch buffers
Some checks failed
CI / lint (push) Failing after 2m5s
CI / Build and test (, beta) (push) Successful in 4m15s
CI / Build and test (, 1.73.0) (push) Successful in 4m23s
CI / Build and test (, nightly) (push) Successful in 5m12s

This commit is contained in:
2023-11-18 18:32:55 +01:00
parent f4ea84b862
commit e03970a931
6 changed files with 90 additions and 6 deletions

View File

@ -20,7 +20,9 @@ pub enum Action {
Error(String),
PreviousBuffer,
NextBuffer,
ToBuffer(isize),
RunCommand(String),
MouseEvent(crossterm::event::MouseEvent),
Help,
}

View File

@ -147,8 +147,8 @@ impl App {
let mut tui = tui::Tui::new()?
.tick_rate(self.tick_rate)
.frame_rate(self.frame_rate);
// tui.mouse(true);
.frame_rate(self.frame_rate)
.mouse(self.config.mouse.enable);
tui.enter()?;
for component in self.components.iter_mut() {
@ -255,6 +255,10 @@ impl App {
.buffers
.set_active_index(self.buffers.active_index() as isize - 1)
},
Action::ToBuffer(buffer_index) => {
changes_since_last_render = true;
self.buffers.set_active_index(buffer_index)
},
Action::Resize(w, h) => {
changes_since_last_render = true;
tui.resize(Rect::new(0, 0, w, h))?;
@ -306,8 +310,8 @@ impl App {
action_tx.send(Action::Resume)?;
tui = tui::Tui::new()?
.tick_rate(self.tick_rate)
.frame_rate(self.frame_rate);
// tui.mouse(true);
.frame_rate(self.frame_rate)
.mouse(self.config.mouse.enable);
tui.enter()?;
} else if self.should_quit.load(Ordering::Acquire) {
tui.stop()?;
@ -319,7 +323,7 @@ impl App {
}
fn handle_tui_event(
&self,
&mut self,
action_tx: &mpsc::UnboundedSender<Action>,
e: tui::Event,
) -> Result<()> {
@ -328,6 +332,15 @@ impl App {
tui::Event::Tick => action_tx.send(Action::Tick)?,
tui::Event::Render => action_tx.send(Action::Render)?,
tui::Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
tui::Event::Mouse(event) => {
// Don't go through the action queue, we need to process mouse events immediately
// or stuff may move on screen before we process the click
for component in &mut self.components {
if let Some(action) = component.handle_mouse_events(event)? {
action_tx.send(action)?;
}
}
},
tui::Event::Key(key) => {
if let Some(keymap) = self.config.keybindings.get(&self.mode) {
if let Some(command_line) = keymap.get(&vec![key]) {

View File

@ -15,6 +15,7 @@
*/
use color_eyre::eyre::{Result, WrapErr};
use crossterm::event::{MouseEvent, MouseEventKind};
use ratatui::{prelude::*, widgets::*};
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::OnceCell;
@ -28,12 +29,18 @@ use crate::{
pub struct Buflist {
command_tx: Option<UnboundedSender<Action>>,
config: Config,
/// Updated by [`draw`], used by [`handle_mouse_events`] to find which buffer was clicked.
last_area: Option<Rect>,
last_num_buffers: Option<usize>,
}
impl Buflist {
pub fn new(config: Config) -> Self {
Buflist {
command_tx: None,
config,
last_area: None,
last_num_buffers: None,
}
}
@ -57,6 +64,53 @@ impl Component for Buflist {
Ok(())
}
fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Result<Option<Action>> {
match mouse {
MouseEvent {
kind: MouseEventKind::Up(_),
column: x,
row: y,
modifiers: _,
} => {
let Some(last_area) = self.last_area else {
return Ok(None);
};
if !last_area.intersects(Rect {
x,
y,
height: 1,
width: 1,
}) {
// Clicked outside the buflist (or on the border)
return Ok(None);
}
let Some(last_num_buffers) = self.last_num_buffers else {
return Ok(None);
};
let column = (x - last_area.x) / self.config.layout.buflist.column_width;
let mut buffer_id: isize =
(column as isize) * (last_area.height as isize) + ((y as isize) - (last_area.y as isize));
let buffer_id_usize: usize = buffer_id.try_into().expect("negative buffer_id");
if buffer_id_usize >= last_num_buffers {
if self.config.layout.buflist.penultimate_right_overflow {
// Clicked on the overflow of the last-but-one column
buffer_id -= last_area.height as isize;
} else {
// Clicked past the last buffer
return Ok(None);
}
}
let buffer_id_usize: usize = buffer_id.try_into().expect("negative buffer_id");
if buffer_id_usize >= last_num_buffers {
tracing::error!("[BUG] Unexpected click past the last buffer");
return Ok(None);
}
Ok(Some(Action::ToBuffer(buffer_id)))
},
_ => Ok(None),
}
}
fn update(&mut self, action: &Action) -> Result<Option<Action>> {
Ok(None)
}
@ -72,6 +126,8 @@ impl Component for Buflist {
block.render(area, frame.buffer_mut());
let area = inner_area;
self.last_area = Some(area);
self.last_num_buffers = buffers.len().try_into().ok();
let num_digits = u32::min(5, buffers.len().ilog10() + 1) as usize;
let right_pad = " ".repeat(area.width.into());
@ -92,7 +148,7 @@ impl Component for Buflist {
} else {
false
};
for y in area.x..(area.x + area.height) {
for y in area.top()..area.bottom() {
let Some((i, buf)) = buffers_iter.next() else {
return Ok(());
};

View File

@ -105,6 +105,10 @@ impl Component for Home {
}
}
fn handle_mouse_events(&mut self, mouse: crossterm::event::MouseEvent) -> Result<Option<Action>> {
self.buflist.handle_mouse_events(mouse)
}
fn update(&mut self, action: &Action) -> Result<Option<Action>> {
self.buflist.update(action)?;
Ok(None)

View File

@ -40,6 +40,11 @@ fn default_device_name() -> String {
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct MouseConfig {
pub enable: bool,
}
#[derive(Clone, Debug, Deserialize)]
pub struct BuflistLayoutConfig {
pub column_width: u16,
@ -65,6 +70,7 @@ pub struct Config {
pub accounts: nonempty::NonEmpty<AccountConfig>,
#[serde(default)]
pub keybindings: KeyBindings,
pub mouse: MouseConfig,
pub layout: LayoutConfig,
#[serde(default)]
pub styles: Styles,

View File

@ -6,6 +6,9 @@
"<Alt-left>" = "/previous"
"<Alt-right>" = "/next"
[mouse]
enable = true
# Configuration for the list of rooms on the right.
[layout.buflist]
# How long room names can be before being truncated.