diff --git a/src/ui.rs b/src/ui.rs index e9c5374..2f2d44f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -3,7 +3,7 @@ use crossterm::{ cursor::{Hide, Show}, event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind, - KeyModifiers, + KeyModifiers, MouseButton, MouseEvent, MouseEventKind, }, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, @@ -24,7 +24,7 @@ use unicode_width::UnicodeWidthStr; use crate::{searchable::Searchable, ssh}; -const INFO_TEXT: &str = "(Esc) quit | (↑) move up | (↓) move down | (enter) select"; +const INFO_TEXT: &str = "(Esc) quit | (↑/↓/click) navigate | (enter) connect"; #[derive(Clone)] #[allow(clippy::struct_excessive_bools)] @@ -52,6 +52,9 @@ pub struct App { table_columns_constraints: Vec, palette: tailwind::Palette, + table_area: Rect, + table_header_height: u16, + table_top_border: u16, } #[derive(PartialEq)] @@ -103,6 +106,9 @@ impl App { table_state: TableState::default().with_selected(0), table_columns_constraints: Vec::new(), palette: tailwind::BLUE, + table_area: Rect::default(), + table_header_height: 0, + table_top_border: 0, hosts: Searchable::new( config.sort_by_levenshtein, @@ -155,30 +161,79 @@ impl App { let ev = event::read()?; - if let Event::Key(key) = ev { - if key.kind == KeyEventKind::Press { - let action = self.on_key_press(terminal, key)?; + match ev { + Event::Key(key) => { + if key.kind == KeyEventKind::Press { + let action = self.on_key_press(terminal, key)?; + match action { + AppKeyAction::Ok => continue, + AppKeyAction::Stop => break, + AppKeyAction::Continue => {} + } + } + + self.search.handle_event(&ev); + self.hosts.search(self.search.value()); + + let selected = self.table_state.selected().unwrap_or(0); + if selected >= self.hosts.len() { + self.table_state.select(Some(match self.hosts.len() { + 0 => 0, + _ => self.hosts.len() - 1, + })); + } + } + Event::Mouse(mouse) => { + let action = self.on_mouse_event(terminal, mouse)?; match action { AppKeyAction::Ok => continue, AppKeyAction::Stop => break, AppKeyAction::Continue => {} } } + _ => {} + } + } - self.search.handle_event(&ev); - self.hosts.search(self.search.value()); + Ok(()) + } - let selected = self.table_state.selected().unwrap_or(0); - if selected >= self.hosts.len() { - self.table_state.select(Some(match self.hosts.len() { - 0 => 0, - _ => self.hosts.len() - 1, - })); + fn on_mouse_event( + &mut self, + _terminal: &Rc>>, + mouse: MouseEvent, + ) -> Result + where + B: Backend + std::io::Write, + ::Error: Send + Sync + 'static, + { + match mouse.kind { + MouseEventKind::Down(MouseButton::Left) => { + // Check if click is within table area + let table_area = self.table_area; + if mouse.column >= table_area.x + && mouse.column < table_area.x + table_area.width + && mouse.row >= table_area.y + && mouse.row < table_area.y + table_area.height + { + // Calculate which row was clicked + let header_height = self.table_header_height; + let top_border = self.table_top_border; + let row_offset = table_area.y + top_border + header_height; + + if mouse.row >= row_offset { + let scroll_offset = self.table_state.offset(); + let clicked_row = usize::from(mouse.row - row_offset) + scroll_offset; + if clicked_row < self.hosts.len() { + self.table_state.select(Some(clicked_row)); + } + } } } + _ => {} } - Ok(()) + Ok(AppKeyAction::Ok) } fn on_key_press( @@ -453,6 +508,9 @@ fn render_searchbar(f: &mut Frame, app: &mut App, area: Rect) { } fn render_table(f: &mut Frame, app: &mut App, area: Rect) { + // Store the table area for mouse click detection + app.table_area = area; + let header_style = Style::default().fg(tailwind::CYAN.c500); let selected_style = Style::default().add_modifier(Modifier::REVERSED); @@ -461,13 +519,14 @@ fn render_table(f: &mut Frame, app: &mut App, area: Rect) { header_names.push("Proxy"); } + let header_height = 1u16; let header = header_names .iter() .copied() .map(Cell::from) .collect::() .style(header_style) - .height(1); + .height(header_height); let rows = app.hosts.iter().map(|host| { let mut content = vec![ @@ -505,6 +564,10 @@ fn render_table(f: &mut Frame, app: &mut App, area: Rect) { .border_type(BorderType::Rounded), ); + let top_border = u16::from(Borders::ALL.intersects(Borders::TOP)); + app.table_header_height = header_height; + app.table_top_border = top_border; + f.render_stateful_widget(t, area, &mut app.table_state); }