refactor: round borders, Y/N delete confirm, fix selected row styling

This commit is contained in:
2026-05-27 01:40:59 +08:00
parent c9f218f2e3
commit d3bd4af634
10 changed files with 72 additions and 36 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ pub fn draw_dialog(
Block::default() Block::default()
.title(title.to_string()) .title(title.to_string())
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Plain) .border_type(BorderType::Rounded)
.border_style(Style::default().fg(border_color)) .border_style(Style::default().fg(border_color))
.bg(PANEL), .bg(PANEL),
) )
+1 -1
View File
@@ -21,7 +21,7 @@ pub fn draw_input(
Block::default() Block::default()
.title(title.to_string()) .title(title.to_string())
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Plain) .border_type(BorderType::Rounded)
.border_style(if focused { .border_style(if focused {
Style::default().fg(ACCENT) Style::default().fg(ACCENT)
} else { } else {
+2 -2
View File
@@ -10,7 +10,7 @@ pub fn panel(title: impl Into<String>) -> Block<'static> {
Block::default() Block::default()
.title(Line::from(format!(" {} ", title.into())).fg(TEXT).bold()) .title(Line::from(format!(" {} ", title.into())).fg(TEXT).bold())
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Plain) .border_type(BorderType::Rounded)
.border_style(Style::default().fg(DIM_BORDER)) .border_style(Style::default().fg(DIM_BORDER))
.bg(PANEL) .bg(PANEL)
} }
@@ -19,7 +19,7 @@ pub fn panel_accent(title: impl Into<String>) -> Block<'static> {
Block::default() Block::default()
.title(Line::from(format!(" {} ", title.into())).fg(ACCENT).bold()) .title(Line::from(format!(" {} ", title.into())).fg(ACCENT).bold())
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Plain) .border_type(BorderType::Rounded)
.border_style(Style::default().fg(ACCENT)) .border_style(Style::default().fg(ACCENT))
.bg(PANEL) .bg(PANEL)
} }
+2 -1
View File
@@ -1,7 +1,7 @@
use crate::ui::{GREEN, PANEL_ALT, RED}; use crate::ui::{GREEN, PANEL_ALT, RED};
use ratatui::{ use ratatui::{
style::{Style, Stylize}, 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) { 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(
Block::default() Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(border_color)) .border_style(Style::default().fg(border_color))
.bg(PANEL_ALT), .bg(PANEL_ALT),
) )
+1 -1
View File
@@ -12,6 +12,6 @@ pub fn draw_delete_confirm(frame: &mut ratatui::Frame<'_>, app: &App) {
7, 7,
RED, RED,
" Confirm Delete ", " Confirm Delete ",
&format!("Delete connection '{name}'?\n\n Enter confirm · Esc cancel"), &format!("Delete connection '{name}'?\n\n [Y] Yes · [N] No"),
); );
} }
+2 -2
View File
@@ -54,8 +54,8 @@ fn form_hints() -> Vec<Hint> {
fn delete_hints() -> Vec<Hint> { fn delete_hints() -> Vec<Hint> {
vec![ vec![
Hint { key: "Enter", desc: "confirm" }, Hint { key: "Y", desc: "yes" },
Hint { key: "Esc", desc: "cancel" }, Hint { key: "N", desc: "no" },
] ]
} }
+22 -11
View File
@@ -1,6 +1,5 @@
use crate::app::{App, Mode}; use crate::app::{App, Mode};
use crate::ui::component::common::layout::centered_rect; use crate::ui::{ACCENT, MUTED, PANEL, SELECTED_BG, TEXT};
use crate::ui::{ACCENT, PANEL, SELECTED_BG, TEXT};
use super::View; use super::View;
@@ -10,7 +9,8 @@ use ratatui::{
Frame, Frame,
layout::Rect, layout::Rect,
style::{Modifier, Style, Stylize}, style::{Modifier, Style, Stylize},
widgets::{Block, Borders, Clear, ListItem, ListState}, text::{Line, Span},
widgets::{Block, BorderType, Borders, Clear, ListState},
}; };
const ACTIONS: &[(&str, &str)] = &[ const ACTIONS: &[(&str, &str)] = &[
@@ -24,17 +24,27 @@ const ACTIONS: &[(&str, &str)] = &[
pub struct ActionMenuView; pub struct ActionMenuView;
impl View for ActionMenuView { impl View for ActionMenuView {
fn draw(&self, frame: &mut Frame<'_>, _app: &App, _area: Rect) { fn draw(&self, frame: &mut Frame<'_>, _app: &App, area: Rect) {
let width = 44u16; let list_width = 52u16.min(area.width.saturating_sub(4));
let height = (ACTIONS.len() as u16) + 4; let list_height = (ACTIONS.len() as u16 + 4).min(area.height);
let area = centered_rect(width, height, frame.area()); 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<ListItem<'_>> = ACTIONS let items: Vec<Line<'_>> = ACTIONS
.iter() .iter()
.map(|(label, desc)| { .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(); .collect();
@@ -44,6 +54,7 @@ impl View for ActionMenuView {
.title(" Actions ") .title(" Actions ")
.title_style(Style::default().fg(ACCENT).bold()) .title_style(Style::default().fg(ACCENT).bold())
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(ACCENT)) .border_style(Style::default().fg(ACCENT))
.bg(PANEL), .bg(PANEL),
) )
@@ -58,7 +69,7 @@ impl View for ActionMenuView {
let mut state = ListState::default(); let mut state = ListState::default();
state.select(Some(_app.session.action_menu.cursor)); 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<()> { fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> {
+6 -2
View File
@@ -74,7 +74,11 @@ fn draw_credentials(frame: &mut Frame<'_>, app: &App, area: Rect) {
Row::new([ Row::new([
Cell::from(format!("{} {}", if selected { ">" } else { " " }, (*name))) Cell::from(format!("{} {}", if selected { ">" } else { " " }, (*name)))
.style(style), .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), 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)), .style(Style::default().fg(BLUE).add_modifier(Modifier::BOLD)),
) )
.block(panel("Credentials")) .block(panel("Credentials"))
.column_spacing(2) .column_spacing(0)
.row_highlight_style(Style::default().bg(SELECTED_BG)); .row_highlight_style(Style::default().bg(SELECTED_BG));
frame.render_widget(table, area); frame.render_widget(table, area);
} }
+21 -12
View File
@@ -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)), .style(Style::default().fg(BLUE).add_modifier(Modifier::BOLD)),
) )
.block(panel(title)) .block(panel(title))
.column_spacing(2) .column_spacing(0)
.row_highlight_style(Style::default().bg(SELECTED_BG)); .row_highlight_style(Style::default().bg(SELECTED_BG));
frame.render_widget(table, area); frame.render_widget(table, area);
} }
@@ -220,7 +220,7 @@ fn connection_row(
Style::default().fg(TEXT) Style::default().fg(TEXT)
}; };
let is_shell = matches!(profile.kind, ConnectionType::Shell { .. }); 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 badge_color = if is_shell { PURPLE } else { ACCENT };
let target = match &profile.kind { let target = match &profile.kind {
@@ -259,16 +259,23 @@ fn connection_row(
_ => MUTED, _ => 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([ Row::new([
Cell::from(format!("{marker} {}", display_name(name))).style(row_style), Cell::from(format!("{marker} {}", display_name(name))).style(row_style),
Cell::from(type_badge).style( Cell::from(Line::from(vec![
Style::default() Span::styled(format!(" {} ", type_badge), badge_style),
.fg(crate::ui::BG) ])).style(row_style),
.bg(badge_color)
.add_modifier(Modifier::BOLD),
),
Cell::from(target).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) .height(1)
} }
@@ -295,7 +302,7 @@ pub fn draw_detail_panel(frame: &mut Frame<'_>, app: &App, area: Rect) {
let mut lines: Vec<Line> = Vec::new(); let mut lines: Vec<Line> = Vec::new();
let is_shell = matches!(profile.kind, ConnectionType::Shell { .. }); 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 }; let badge_color = if is_shell { PURPLE } else { ACCENT };
lines.push(Line::raw("")); lines.push(Line::raw(""));
lines.push(Line::from(vec![ lines.push(Line::from(vec![
@@ -553,14 +560,16 @@ impl View for DeleteConfirmView {
fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> { fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> {
match key.code { match key.code {
KeyCode::Esc => app.session.mode = Mode::Home, KeyCode::Char('y') | KeyCode::Char('Y') => {
KeyCode::Enter => {
match app.delete_selected() { match app.delete_selected() {
Ok(()) => app.toast("deleted", true), Ok(()) => app.toast("deleted", true),
Err(err) => app.toast(err.to_string(), false), Err(err) => app.toast(err.to_string(), false),
} }
app.session.mode = Mode::Home; app.session.mode = Mode::Home;
} }
KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('N') => {
app.session.mode = Mode::Home;
}
_ => {} _ => {}
} }
Ok(()) Ok(())
+14 -3
View File
@@ -69,8 +69,13 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
.as_ref() .as_ref()
.map(|c| format!("conflict: {}", c.name)) .map(|c| format!("conflict: {}", c.name))
.unwrap_or_else(|| "-".to_string()); .unwrap_or_else(|| "-".to_string());
let check_cell_style = if selected_row {
Style::default().bg(SELECTED_BG)
} else {
Style::default()
};
rows.push(Row::new([ 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.name.clone()).style(style),
Cell::from(item.path.display().to_string()).style(style), Cell::from(item.path.display().to_string()).style(style),
Cell::from(status).style(style), Cell::from(status).style(style),
@@ -98,8 +103,13 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
} else { } else {
Style::default().fg(MUTED) Style::default().fg(MUTED)
}; };
let check_cell_style = if selected_row {
Style::default().bg(SELECTED_BG)
} else {
Style::default()
};
rows.push(Row::new([ 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.name.clone()).style(style),
Cell::from(format!("{}@{}:{}", item.user, item.host, item.port)).style(style), Cell::from(format!("{}@{}:{}", item.user, item.host, item.port)).style(style),
Cell::from( Cell::from(
@@ -146,7 +156,8 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
"Import", "Import",
"Space toggle a all A none Enter import Esc cancel", "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); frame.render_widget(table, area);
} }