Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 78 additions & 15 deletions src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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)]
Expand Down Expand Up @@ -52,6 +52,9 @@ pub struct App {
table_columns_constraints: Vec<Constraint>,

palette: tailwind::Palette,
table_area: Rect,
table_header_height: u16,
table_top_border: u16,
}

#[derive(PartialEq)]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<B>(
&mut self,
_terminal: &Rc<RefCell<Terminal<B>>>,
mouse: MouseEvent,
) -> Result<AppKeyAction>
where
B: Backend + std::io::Write,
<B as Backend>::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<B>(
Expand Down Expand Up @@ -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);

Expand All @@ -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::<Row>()
.style(header_style)
.height(1);
.height(header_height);

let rows = app.hosts.iter().map(|host| {
let mut content = vec![
Expand Down Expand Up @@ -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);
}

Expand Down