diff --git a/src/ui/component/common/dialog.rs b/src/ui/component/common/dialog.rs index c6737c5..79e617e 100644 --- a/src/ui/component/common/dialog.rs +++ b/src/ui/component/common/dialog.rs @@ -25,7 +25,7 @@ pub fn draw_dialog( Block::default() .title(title.to_string()) .borders(Borders::ALL) - .border_type(BorderType::Plain) + .border_type(BorderType::Rounded) .border_style(Style::default().fg(border_color)) .bg(PANEL), ) diff --git a/src/ui/component/common/input.rs b/src/ui/component/common/input.rs index 3303cb9..263ba6c 100644 --- a/src/ui/component/common/input.rs +++ b/src/ui/component/common/input.rs @@ -21,7 +21,7 @@ pub fn draw_input( Block::default() .title(title.to_string()) .borders(Borders::ALL) - .border_type(BorderType::Plain) + .border_type(BorderType::Rounded) .border_style(if focused { Style::default().fg(ACCENT) } else { diff --git a/src/ui/component/common/panel.rs b/src/ui/component/common/panel.rs index 282fb99..39ab0d4 100644 --- a/src/ui/component/common/panel.rs +++ b/src/ui/component/common/panel.rs @@ -10,7 +10,7 @@ pub fn panel(title: impl Into) -> Block<'static> { Block::default() .title(Line::from(format!(" {} ", title.into())).fg(TEXT).bold()) .borders(Borders::ALL) - .border_type(BorderType::Plain) + .border_type(BorderType::Rounded) .border_style(Style::default().fg(DIM_BORDER)) .bg(PANEL) } @@ -19,7 +19,7 @@ pub fn panel_accent(title: impl Into) -> Block<'static> { Block::default() .title(Line::from(format!(" {} ", title.into())).fg(ACCENT).bold()) .borders(Borders::ALL) - .border_type(BorderType::Plain) + .border_type(BorderType::Rounded) .border_style(Style::default().fg(ACCENT)) .bg(PANEL) } diff --git a/src/ui/component/common/toast.rs b/src/ui/component/common/toast.rs index c98d6f7..31c70bd 100644 --- a/src/ui/component/common/toast.rs +++ b/src/ui/component/common/toast.rs @@ -1,7 +1,7 @@ use crate::ui::{GREEN, PANEL_ALT, RED}; use ratatui::{ style::{Style, Stylize}, - widgets::{Block, Borders, Clear, Paragraph, Widget}, + widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget}, }; pub fn draw_toast(frame: &mut ratatui::Frame<'_>, message: &str, success: bool) { @@ -19,6 +19,7 @@ pub fn draw_toast(frame: &mut ratatui::Frame<'_>, message: &str, success: bool) .block( Block::default() .borders(Borders::ALL) + .border_type(BorderType::Rounded) .border_style(Style::default().fg(border_color)) .bg(PANEL_ALT), ) diff --git a/src/ui/component/custom/delete_confirm.rs b/src/ui/component/custom/delete_confirm.rs index 055582e..89d9e4e 100644 --- a/src/ui/component/custom/delete_confirm.rs +++ b/src/ui/component/custom/delete_confirm.rs @@ -12,6 +12,6 @@ pub fn draw_delete_confirm(frame: &mut ratatui::Frame<'_>, app: &App) { 7, RED, " Confirm Delete ", - &format!("Delete connection '{name}'?\n\n Enter confirm · Esc cancel"), + &format!("Delete connection '{name}'?\n\n [Y] Yes · [N] No"), ); } diff --git a/src/ui/component/custom/help_bar.rs b/src/ui/component/custom/help_bar.rs index 458e0a7..0ae0ed5 100644 --- a/src/ui/component/custom/help_bar.rs +++ b/src/ui/component/custom/help_bar.rs @@ -54,8 +54,8 @@ fn form_hints() -> Vec { fn delete_hints() -> Vec { vec![ - Hint { key: "Enter", desc: "confirm" }, - Hint { key: "Esc", desc: "cancel" }, + Hint { key: "Y", desc: "yes" }, + Hint { key: "N", desc: "no" }, ] } diff --git a/src/ui/view/action_menu.rs b/src/ui/view/action_menu.rs index 8551995..0e6d586 100644 --- a/src/ui/view/action_menu.rs +++ b/src/ui/view/action_menu.rs @@ -1,6 +1,5 @@ use crate::app::{App, Mode}; -use crate::ui::component::common::layout::centered_rect; -use crate::ui::{ACCENT, PANEL, SELECTED_BG, TEXT}; +use crate::ui::{ACCENT, MUTED, PANEL, SELECTED_BG, TEXT}; use super::View; @@ -10,7 +9,8 @@ use ratatui::{ Frame, layout::Rect, style::{Modifier, Style, Stylize}, - widgets::{Block, Borders, Clear, ListItem, ListState}, + text::{Line, Span}, + widgets::{Block, BorderType, Borders, Clear, ListState}, }; const ACTIONS: &[(&str, &str)] = &[ @@ -24,17 +24,27 @@ const ACTIONS: &[(&str, &str)] = &[ pub struct ActionMenuView; impl View for ActionMenuView { - fn draw(&self, frame: &mut Frame<'_>, _app: &App, _area: Rect) { - let width = 44u16; - let height = (ACTIONS.len() as u16) + 4; - let area = centered_rect(width, height, frame.area()); + fn draw(&self, frame: &mut Frame<'_>, _app: &App, area: Rect) { + let list_width = 52u16.min(area.width.saturating_sub(4)); + let list_height = (ACTIONS.len() as u16 + 4).min(area.height); + let x = area.x + (area.width.saturating_sub(list_width)) / 2; + let y = area.y + (area.height.saturating_sub(list_height)) / 2; + let list_area = Rect { + x, + y, + width: list_width, + height: list_height, + }; - frame.render_widget(Clear, area); + frame.render_widget(Clear, list_area); - let items: Vec> = ACTIONS + let items: Vec> = ACTIONS .iter() .map(|(label, desc)| { - ListItem::new(format!(" {label:<14}{desc}")) + Line::from(vec![ + Span::styled(format!(" {label:<14}"), Style::default().fg(TEXT)), + Span::styled(desc.to_string(), Style::default().fg(MUTED)), + ]) }) .collect(); @@ -44,6 +54,7 @@ impl View for ActionMenuView { .title(" Actions ") .title_style(Style::default().fg(ACCENT).bold()) .borders(Borders::ALL) + .border_type(BorderType::Rounded) .border_style(Style::default().fg(ACCENT)) .bg(PANEL), ) @@ -58,7 +69,7 @@ impl View for ActionMenuView { let mut state = ListState::default(); state.select(Some(_app.session.action_menu.cursor)); - frame.render_stateful_widget(list, area, &mut state); + frame.render_stateful_widget(list, list_area, &mut state); } fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> { diff --git a/src/ui/view/cred_list.rs b/src/ui/view/cred_list.rs index 461a750..d595c1b 100644 --- a/src/ui/view/cred_list.rs +++ b/src/ui/view/cred_list.rs @@ -74,7 +74,11 @@ fn draw_credentials(frame: &mut Frame<'_>, app: &App, area: Rect) { Row::new([ Cell::from(format!("{} {}", if selected { ">" } else { " " }, (*name))) .style(style), - Cell::from(format!("● {kind_text}")).style(Style::default().fg(dot_color)), + Cell::from(format!("● {kind_text}")).style(if selected { + Style::default().fg(dot_color).bg(SELECTED_BG) + } else { + Style::default().fg(dot_color) + }), Cell::from(ref_text).style(style), ]) }) @@ -95,7 +99,7 @@ fn draw_credentials(frame: &mut Frame<'_>, app: &App, area: Rect) { .style(Style::default().fg(BLUE).add_modifier(Modifier::BOLD)), ) .block(panel("Credentials")) - .column_spacing(2) + .column_spacing(0) .row_highlight_style(Style::default().bg(SELECTED_BG)); frame.render_widget(table, area); } diff --git a/src/ui/view/home_list.rs b/src/ui/view/home_list.rs index 633d5b0..cee4e16 100644 --- a/src/ui/view/home_list.rs +++ b/src/ui/view/home_list.rs @@ -181,7 +181,7 @@ pub fn draw_connection_list(frame: &mut Frame<'_>, app: &App, area: Rect) { .style(Style::default().fg(BLUE).add_modifier(Modifier::BOLD)), ) .block(panel(title)) - .column_spacing(2) + .column_spacing(0) .row_highlight_style(Style::default().bg(SELECTED_BG)); frame.render_widget(table, area); } @@ -220,7 +220,7 @@ fn connection_row( Style::default().fg(TEXT) }; let is_shell = matches!(profile.kind, ConnectionType::Shell { .. }); - let type_badge = if is_shell { "[SHL]" } else { "[SSH]" }; + let type_badge = if is_shell { "SHL" } else { "SSH" }; let badge_color = if is_shell { PURPLE } else { ACCENT }; let target = match &profile.kind { @@ -259,16 +259,23 @@ fn connection_row( _ => MUTED, }; + let badge_style = if selected { + Style::default().fg(badge_color).bg(SELECTED_BG).add_modifier(Modifier::BOLD) + } else { + Style::default().fg(crate::ui::BG).bg(badge_color).add_modifier(Modifier::BOLD) + }; + Row::new([ Cell::from(format!("{marker} {}", display_name(name))).style(row_style), - Cell::from(type_badge).style( - Style::default() - .fg(crate::ui::BG) - .bg(badge_color) - .add_modifier(Modifier::BOLD), - ), + Cell::from(Line::from(vec![ + Span::styled(format!(" {} ", type_badge), badge_style), + ])).style(row_style), Cell::from(target).style(row_style), - Cell::from(auth_state).style(Style::default().fg(auth_color)), + Cell::from(auth_state).style(if selected { + Style::default().fg(auth_color).bg(SELECTED_BG) + } else { + Style::default().fg(auth_color) + }), ]) .height(1) } @@ -295,7 +302,7 @@ pub fn draw_detail_panel(frame: &mut Frame<'_>, app: &App, area: Rect) { let mut lines: Vec = Vec::new(); let is_shell = matches!(profile.kind, ConnectionType::Shell { .. }); - let badge = if is_shell { "[SHL]" } else { "[SSH]" }; + let badge = if is_shell { "SHL" } else { "SSH" }; let badge_color = if is_shell { PURPLE } else { ACCENT }; lines.push(Line::raw("")); lines.push(Line::from(vec![ @@ -553,14 +560,16 @@ impl View for DeleteConfirmView { fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> { match key.code { - KeyCode::Esc => app.session.mode = Mode::Home, - KeyCode::Enter => { + KeyCode::Char('y') | KeyCode::Char('Y') => { match app.delete_selected() { Ok(()) => app.toast("deleted", true), Err(err) => app.toast(err.to_string(), false), } app.session.mode = Mode::Home; } + KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('N') => { + app.session.mode = Mode::Home; + } _ => {} } Ok(()) diff --git a/src/ui/view/import.rs b/src/ui/view/import.rs index cf76f07..6dbc643 100644 --- a/src/ui/view/import.rs +++ b/src/ui/view/import.rs @@ -69,8 +69,13 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) { .as_ref() .map(|c| format!("conflict: {}", c.name)) .unwrap_or_else(|| "-".to_string()); + let check_cell_style = if selected_row { + Style::default().bg(SELECTED_BG) + } else { + Style::default() + }; rows.push(Row::new([ - Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style), + Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style.patch(check_cell_style)), Cell::from(item.name.clone()).style(style), Cell::from(item.path.display().to_string()).style(style), Cell::from(status).style(style), @@ -98,8 +103,13 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) { } else { Style::default().fg(MUTED) }; + let check_cell_style = if selected_row { + Style::default().bg(SELECTED_BG) + } else { + Style::default() + }; rows.push(Row::new([ - Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style), + Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style.patch(check_cell_style)), Cell::from(item.name.clone()).style(style), Cell::from(format!("{}@{}:{}", item.user, item.host, item.port)).style(style), Cell::from( @@ -146,7 +156,8 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) { "Import", "Space toggle a all A none Enter import Esc cancel", )) - .column_spacing(2); + .column_spacing(0) + .row_highlight_style(Style::default().bg(SELECTED_BG)); frame.render_widget(table, area); }