mirror of
https://github.com/kristoferssolo/traxor.git
synced 2026-01-14 12:36:14 +00:00
refactor: update UI
This commit is contained in:
parent
1b145f9ace
commit
d0b8f8177b
@ -48,8 +48,8 @@ quit = "q"
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
[colors]
|
[colors]
|
||||||
# UI colors
|
# UI colors
|
||||||
highlight_background = "magenta"
|
highlight_background = "#3a3a5a"
|
||||||
highlight_foreground = "black"
|
highlight_foreground = "white"
|
||||||
header_foreground = "yellow"
|
header_foreground = "yellow"
|
||||||
info_foreground = "blue"
|
info_foreground = "blue"
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,11 @@ pub async fn get_action(key_event: KeyEvent, app: &mut App) -> Result<Option<Act
|
|||||||
return handle_input(key_event, app).await;
|
return handle_input(key_event, app).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close help popup with Esc
|
||||||
|
if app.show_help && key_event.code == KeyCode::Esc {
|
||||||
|
return Ok(Some(Action::ToggleHelp));
|
||||||
|
}
|
||||||
|
|
||||||
debug!("handling key event: {:?}", key_event);
|
debug!("handling key event: {:?}", key_event);
|
||||||
|
|
||||||
let keybinds = &app.config.keybinds;
|
let keybinds = &app.config.keybinds;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
style::Modifier,
|
||||||
widgets::{Block, BorderType, Borders, Cell, Clear, Row, Table},
|
widgets::{Block, BorderType, Borders, Cell, Clear, Row, Table},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -8,18 +9,17 @@ pub fn render_help(frame: &mut Frame, app: &App) {
|
|||||||
let kb = &app.config.keybinds;
|
let kb = &app.config.keybinds;
|
||||||
let key_style = Style::default().fg(Color::Yellow).bold();
|
let key_style = Style::default().fg(Color::Yellow).bold();
|
||||||
let select_key = display_key(&kb.select);
|
let select_key = display_key(&kb.select);
|
||||||
|
let filter_key = display_key(&kb.filter);
|
||||||
|
|
||||||
let rows = vec![
|
let rows = vec![
|
||||||
section_row("── Navigation ──"),
|
section_row("Navigation"),
|
||||||
key_row(&kb.prev_torrent, "Move up", key_style),
|
key_row(&kb.prev_torrent, "Move up", key_style),
|
||||||
key_row(&kb.next_torrent, "Move down", key_style),
|
key_row(&kb.next_torrent, "Move down", key_style),
|
||||||
key_row(&kb.prev_tab, "Previous tab", key_style),
|
key_row(&kb.prev_tab, "Previous tab", key_style),
|
||||||
key_row(&kb.next_tab, "Next tab", key_style),
|
key_row(&kb.next_tab, "Next tab", key_style),
|
||||||
key_row(&kb.switch_tab_1, "All torrents", key_style),
|
key_row("1-9, 0", "Switch to tab", key_style),
|
||||||
key_row(&kb.switch_tab_2, "Active torrents", key_style),
|
|
||||||
key_row(&kb.switch_tab_3, "Downloading", key_style),
|
|
||||||
Row::default(),
|
Row::default(),
|
||||||
section_row("── Actions ──"),
|
section_row("Actions"),
|
||||||
key_row(&kb.toggle_torrent, "Start/stop torrent", key_style),
|
key_row(&kb.toggle_torrent, "Start/stop torrent", key_style),
|
||||||
key_row(&kb.toggle_all, "Start/stop all", key_style),
|
key_row(&kb.toggle_all, "Start/stop all", key_style),
|
||||||
key_row(&select_key, "Multi-select", key_style),
|
key_row(&select_key, "Multi-select", key_style),
|
||||||
@ -28,23 +28,28 @@ pub fn render_help(frame: &mut Frame, app: &App) {
|
|||||||
key_row(&kb.delete, "Remove torrent", key_style),
|
key_row(&kb.delete, "Remove torrent", key_style),
|
||||||
key_row(&kb.delete_force, "Delete with data", key_style),
|
key_row(&kb.delete_force, "Delete with data", key_style),
|
||||||
Row::default(),
|
Row::default(),
|
||||||
section_row("── General ──"),
|
section_row("Search"),
|
||||||
|
key_row(&filter_key, "Search/filter", key_style),
|
||||||
|
key_row("Esc", "Clear filter", key_style),
|
||||||
|
Row::default(),
|
||||||
|
section_row("General"),
|
||||||
key_row(&kb.toggle_help, "Toggle help", key_style),
|
key_row(&kb.toggle_help, "Toggle help", key_style),
|
||||||
key_row(&kb.quit, "Quit", key_style),
|
key_row(&kb.quit, "Quit", key_style),
|
||||||
];
|
];
|
||||||
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
let height = rows.len() as u16 + 4;
|
let height = rows.len() as u16 + 4;
|
||||||
let width = 40;
|
let width = 44;
|
||||||
|
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.title(" Keybindings ")
|
.title(" Keybindings ")
|
||||||
|
.title_style(Style::default().fg(Color::Cyan).bold())
|
||||||
.title_alignment(Alignment::Center)
|
.title_alignment(Alignment::Center)
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
.border_style(Style::default().fg(Color::Cyan));
|
.border_style(Style::default().fg(Color::Cyan));
|
||||||
|
|
||||||
let table = Table::new(rows, [Constraint::Length(16), Constraint::Fill(1)]).block(block);
|
let table = Table::new(rows, [Constraint::Length(14), Constraint::Fill(1)]).block(block);
|
||||||
|
|
||||||
let area = frame.area();
|
let area = frame.area();
|
||||||
let popup_area = Rect::new(
|
let popup_area = Rect::new(
|
||||||
@ -61,14 +66,19 @@ pub fn render_help(frame: &mut Frame, app: &App) {
|
|||||||
fn key_row<'a>(key: &'a str, desc: &'a str, key_style: Style) -> Row<'a> {
|
fn key_row<'a>(key: &'a str, desc: &'a str, key_style: Style) -> Row<'a> {
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(format!(" {key}")).style(key_style),
|
Cell::from(format!(" {key}")).style(key_style),
|
||||||
Cell::from(desc),
|
Cell::from(desc).style(Style::default().fg(Color::White)),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn section_row(title: &str) -> Row<'_> {
|
fn section_row(title: &str) -> Row<'_> {
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(title).style(Style::default().fg(Color::DarkGray)),
|
Cell::from(format!(" {title}")).style(
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Magenta)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
|
.top_margin(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_key(key: &str) -> String {
|
fn display_key(key: &str) -> String {
|
||||||
|
|||||||
@ -2,13 +2,14 @@ use crate::app::{App, InputMode};
|
|||||||
use ratatui::{
|
use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
text::Line,
|
text::Line,
|
||||||
widgets::{Block, Borders, Clear, Paragraph},
|
widgets::{Block, BorderType, Borders, Clear, Paragraph},
|
||||||
};
|
};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
pub fn render(f: &mut Frame, app: &App) {
|
pub fn render(f: &mut Frame, app: &App) {
|
||||||
match app.input_mode {
|
match app.input_mode {
|
||||||
InputMode::Move | InputMode::Rename | InputMode::Filter => render_text_input(f, app),
|
InputMode::Move | InputMode::Rename => render_text_input(f, app),
|
||||||
|
InputMode::Filter => render_filter_input(f, app),
|
||||||
InputMode::ConfirmDelete(delete_local_data) => render_confirm_delete(f, delete_local_data),
|
InputMode::ConfirmDelete(delete_local_data) => render_confirm_delete(f, delete_local_data),
|
||||||
InputMode::None => {}
|
InputMode::None => {}
|
||||||
}
|
}
|
||||||
@ -21,11 +22,13 @@ fn render_text_input(f: &mut Frame, app: &App) {
|
|||||||
let title = match app.input_mode {
|
let title = match app.input_mode {
|
||||||
InputMode::Move => "Move to",
|
InputMode::Move => "Move to",
|
||||||
InputMode::Rename => "Rename",
|
InputMode::Rename => "Rename",
|
||||||
InputMode::Filter => "Filter",
|
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let block = Block::default().title(title).borders(Borders::ALL);
|
let block = Block::default()
|
||||||
|
.title(title)
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded);
|
||||||
f.render_widget(Clear, input_area);
|
f.render_widget(Clear, input_area);
|
||||||
f.render_widget(block, input_area);
|
f.render_widget(block, input_area);
|
||||||
|
|
||||||
@ -49,6 +52,57 @@ fn render_text_input(f: &mut Frame, app: &App) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_filter_input(f: &mut Frame, app: &App) {
|
||||||
|
let size = f.area();
|
||||||
|
let width = size.width.min(60);
|
||||||
|
let input_area = Rect::new((size.width.saturating_sub(width)) / 2, 1, width, 3);
|
||||||
|
|
||||||
|
let filtered = app.filtered_torrents().len();
|
||||||
|
let total = app.torrents.len();
|
||||||
|
|
||||||
|
let block = Block::default()
|
||||||
|
.title(" Search ")
|
||||||
|
.title_style(Style::default().fg(Color::Cyan).bold())
|
||||||
|
.title_alignment(Alignment::Left)
|
||||||
|
.title_bottom(Line::from(vec![
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(
|
||||||
|
format!("{filtered}/{total}"),
|
||||||
|
Style::default().fg(Color::Yellow),
|
||||||
|
),
|
||||||
|
Span::raw(" matches "),
|
||||||
|
]))
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(Style::default().fg(Color::Cyan));
|
||||||
|
|
||||||
|
f.render_widget(Clear, input_area);
|
||||||
|
f.render_widget(block, input_area);
|
||||||
|
|
||||||
|
let prompt = Span::styled("> ", Style::default().fg(Color::Cyan).bold());
|
||||||
|
let text = Span::raw(app.input_handler.text.as_str());
|
||||||
|
let input = Paragraph::new(Line::from(vec![prompt, text]));
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
input,
|
||||||
|
input_area.inner(Margin {
|
||||||
|
vertical: 1,
|
||||||
|
horizontal: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let cursor_offset = u16::try_from(app.input_handler.cursor_position).unwrap_or_else(|_| {
|
||||||
|
warn!("cursor_position out of range, clamping");
|
||||||
|
0
|
||||||
|
});
|
||||||
|
|
||||||
|
// +2 for the "> " prompt
|
||||||
|
f.set_cursor_position(Position::new(
|
||||||
|
input_area.x + cursor_offset + 3,
|
||||||
|
input_area.y + 1,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
fn render_confirm_delete(f: &mut Frame, delete_local_data: bool) {
|
fn render_confirm_delete(f: &mut Frame, delete_local_data: bool) {
|
||||||
let size = f.area();
|
let size = f.area();
|
||||||
let dialog_width = 40;
|
let dialog_width = 40;
|
||||||
|
|||||||
@ -10,6 +10,7 @@ use crate::{
|
|||||||
use help::render_help;
|
use help::render_help;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
style::Modifier,
|
||||||
widgets::{Block, BorderType, Borders, Tabs},
|
widgets::{Block, BorderType, Borders, Tabs},
|
||||||
};
|
};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@ -82,7 +83,9 @@ fn tab_style(cfg: &ColorConfig) -> Style {
|
|||||||
|
|
||||||
fn highlighted_tab_style(cfg: &ColorConfig) -> Style {
|
fn highlighted_tab_style(cfg: &ColorConfig) -> Style {
|
||||||
let fg = to_color(&cfg.header_foreground);
|
let fg = to_color(&cfg.header_foreground);
|
||||||
Style::default().fg(fg)
|
Style::default()
|
||||||
|
.fg(fg)
|
||||||
|
.add_modifier(Modifier::BOLD | Modifier::UNDERLINED)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_block() -> Block<'static> {
|
fn default_block() -> Block<'static> {
|
||||||
@ -90,4 +93,5 @@ fn default_block() -> Block<'static> {
|
|||||||
.title_alignment(Alignment::Center)
|
.title_alignment(Alignment::Center)
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(Style::default().fg(Color::DarkGray))
|
||||||
}
|
}
|
||||||
|
|||||||
133
src/ui/status.rs
133
src/ui/status.rs
@ -8,14 +8,18 @@ use ratatui::{
|
|||||||
widgets::{Block, BorderType, Borders, Paragraph},
|
widgets::{Block, BorderType, Borders, Paragraph},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
||||||
let torrents = &app.torrents.torrents;
|
let torrents = &app.torrents.torrents;
|
||||||
|
|
||||||
// Aggregate stats
|
// Aggregate stats
|
||||||
let total_down_speed: i64 = torrents.iter().filter_map(|t| t.rate_download).sum();
|
let total_down_speed = torrents.iter().filter_map(|t| t.rate_download).sum::<i64>();
|
||||||
let total_up_speed: i64 = torrents.iter().filter_map(|t| t.rate_upload).sum();
|
let total_up_speed = torrents.iter().filter_map(|t| t.rate_upload).sum::<i64>();
|
||||||
let total_downloaded: u64 = torrents.iter().filter_map(|t| t.downloaded_ever).sum();
|
let total_downloaded = torrents
|
||||||
let total_uploaded: i64 = torrents.iter().filter_map(|t| t.uploaded_ever).sum();
|
.iter()
|
||||||
|
.filter_map(|t| t.downloaded_ever)
|
||||||
|
.sum::<u64>();
|
||||||
|
let total_uploaded = torrents.iter().filter_map(|t| t.uploaded_ever).sum::<i64>();
|
||||||
|
|
||||||
let down_speed = NetSpeed::new(total_down_speed.unsigned_abs());
|
let down_speed = NetSpeed::new(total_down_speed.unsigned_abs());
|
||||||
let up_speed = NetSpeed::new(total_up_speed.unsigned_abs());
|
let up_speed = NetSpeed::new(total_up_speed.unsigned_abs());
|
||||||
@ -27,13 +31,6 @@ pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
|||||||
let selected_count = app.torrents.selected.len();
|
let selected_count = app.torrents.selected.len();
|
||||||
|
|
||||||
let active_filter = app.active_filter();
|
let active_filter = app.active_filter();
|
||||||
let count_info = if selected_count > 0 {
|
|
||||||
format!("{selected_count}/{total}")
|
|
||||||
} else if !active_filter.is_empty() {
|
|
||||||
format!("{filtered}/{total}")
|
|
||||||
} else {
|
|
||||||
format!("{total}")
|
|
||||||
};
|
|
||||||
|
|
||||||
let mode_text = match app.input_mode {
|
let mode_text = match app.input_mode {
|
||||||
InputMode::Move => Some("MOVE".to_string()),
|
InputMode::Move => Some("MOVE".to_string()),
|
||||||
@ -45,33 +42,105 @@ pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let keybinds = match app.input_mode {
|
let keybinds = match app.input_mode {
|
||||||
InputMode::None if !app.filter_text.is_empty() => "Esc Clear filter │ ? Help",
|
InputMode::None if !app.filter_text.is_empty() => vec![
|
||||||
InputMode::None => "? Help",
|
Span::styled("Esc", Style::default().fg(Color::Yellow)),
|
||||||
InputMode::Move | InputMode::Rename => "Enter Submit │ Esc Cancel",
|
Span::raw(" Clear │ "),
|
||||||
InputMode::Filter => "Enter Confirm │ Esc Cancel",
|
Span::styled("?", Style::default().fg(Color::Yellow)),
|
||||||
InputMode::ConfirmDelete(_) => "y Confirm │ n Cancel",
|
Span::raw(" Help"),
|
||||||
|
],
|
||||||
|
InputMode::None => vec![
|
||||||
|
Span::styled("?", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" Help │ "),
|
||||||
|
Span::styled("/", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" Search"),
|
||||||
|
],
|
||||||
|
InputMode::Move | InputMode::Rename => vec![
|
||||||
|
Span::styled("Enter", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" Submit │ "),
|
||||||
|
Span::styled("Esc", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" Cancel"),
|
||||||
|
],
|
||||||
|
InputMode::Filter => vec![
|
||||||
|
Span::styled("Enter", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" Confirm │ "),
|
||||||
|
Span::styled("Esc", Style::default().fg(Color::Yellow)),
|
||||||
|
Span::raw(" Cancel"),
|
||||||
|
],
|
||||||
|
InputMode::ConfirmDelete(_) => vec![
|
||||||
|
Span::styled("y", Style::default().fg(Color::Green)),
|
||||||
|
Span::raw(" Confirm │ "),
|
||||||
|
Span::styled("n", Style::default().fg(Color::Red)),
|
||||||
|
Span::raw(" Cancel"),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
let left = format!(" {keybinds}");
|
// Build right side with colored spans
|
||||||
let right =
|
let count_style = if selected_count > 0 {
|
||||||
format!("{count_info} │ ↓ {down_speed} ↑ {up_speed} │ D: {downloaded} U: {uploaded} ");
|
Style::default().fg(Color::Magenta).bold()
|
||||||
|
} else if !active_filter.is_empty() {
|
||||||
let available_width = area.width.saturating_sub(2) as usize;
|
Style::default().fg(Color::Cyan)
|
||||||
let left_len = left.chars().count();
|
|
||||||
let right_len = right.chars().count();
|
|
||||||
|
|
||||||
let content = if left_len + right_len < available_width {
|
|
||||||
let padding = available_width.saturating_sub(left_len + right_len);
|
|
||||||
format!("{left}{}{right}", " ".repeat(padding))
|
|
||||||
} else if left_len < available_width {
|
|
||||||
left
|
|
||||||
} else {
|
} else {
|
||||||
right
|
Style::default().fg(Color::White)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let count_text = if selected_count > 0 {
|
||||||
|
format!("{selected_count}/{total}")
|
||||||
|
} else if !active_filter.is_empty() {
|
||||||
|
format!("{filtered}/{total}")
|
||||||
|
} else {
|
||||||
|
format!("{total}")
|
||||||
|
};
|
||||||
|
|
||||||
|
let down_style = if total_down_speed > 0 {
|
||||||
|
Style::default().fg(Color::Green)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::DarkGray)
|
||||||
|
};
|
||||||
|
|
||||||
|
let up_style = if total_up_speed > 0 {
|
||||||
|
Style::default().fg(Color::Cyan)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::DarkGray)
|
||||||
|
};
|
||||||
|
|
||||||
|
let right_spans = vec![
|
||||||
|
Span::styled(count_text, count_style),
|
||||||
|
Span::styled(" │ ", Style::default().fg(Color::DarkGray)),
|
||||||
|
Span::styled(format!("↓{down_speed}"), down_style),
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(format!("↑{up_speed}"), up_style),
|
||||||
|
Span::styled(" │ ", Style::default().fg(Color::DarkGray)),
|
||||||
|
Span::styled("D:", Style::default().fg(Color::DarkGray)),
|
||||||
|
Span::raw(format!("{downloaded} ")),
|
||||||
|
Span::styled("U:", Style::default().fg(Color::DarkGray)),
|
||||||
|
Span::raw(format!("{uploaded} ")),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Calculate widths
|
||||||
|
let available_width = area.width.saturating_sub(2) as usize;
|
||||||
|
let left_len = keybinds
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.content.chars().count())
|
||||||
|
.sum::<usize>()
|
||||||
|
+ 1;
|
||||||
|
let right_len = right_spans
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.content.chars().count())
|
||||||
|
.sum::<usize>();
|
||||||
|
|
||||||
|
let mut spans = vec![Span::raw(" ")];
|
||||||
|
spans.extend(keybinds);
|
||||||
|
|
||||||
|
if left_len + right_len < available_width {
|
||||||
|
let padding = available_width.saturating_sub(left_len + right_len);
|
||||||
|
spans.push(Span::raw(" ".repeat(padding)));
|
||||||
|
spans.extend(right_spans);
|
||||||
|
}
|
||||||
|
|
||||||
let mut block = Block::default()
|
let mut block = Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded);
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(Style::default().fg(Color::DarkGray));
|
||||||
|
|
||||||
if let Some(ref mode) = mode_text {
|
if let Some(ref mode) = mode_text {
|
||||||
block = block
|
block = block
|
||||||
@ -79,7 +148,7 @@ pub fn render(frame: &mut Frame, app: &App, area: Rect) {
|
|||||||
.title_style(Style::default().fg(Color::Yellow).bold());
|
.title_style(Style::default().fg(Color::Yellow).bold());
|
||||||
}
|
}
|
||||||
|
|
||||||
let paragraph = Paragraph::new(Span::raw(content)).block(block);
|
let paragraph = Paragraph::new(Line::from(spans)).block(block);
|
||||||
|
|
||||||
frame.render_widget(paragraph, area);
|
frame.render_widget(paragraph, area);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use super::to_color;
|
|||||||
use crate::{app::utils::Wrapper, config::color::ColorConfig};
|
use crate::{app::utils::Wrapper, config::color::ColorConfig};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::Constraint,
|
layout::Constraint,
|
||||||
style::{Style, Styled},
|
style::{Color, Modifier, Style, Styled},
|
||||||
widgets::{Block, BorderType, Borders, Row, Table},
|
widgets::{Block, BorderType, Borders, Row, Table},
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@ -14,13 +14,13 @@ pub fn build_table(
|
|||||||
colors: &ColorConfig,
|
colors: &ColorConfig,
|
||||||
fields: &[TorrentGetField],
|
fields: &[TorrentGetField],
|
||||||
) -> Table<'static> {
|
) -> Table<'static> {
|
||||||
let row_style = row_style(colors);
|
let select_style = select_style(colors);
|
||||||
let header_style = header_style(colors);
|
let header_style = header_style(colors);
|
||||||
let highlight_row_style = hightlighted_row_style(colors);
|
let highlight_row_style = highlighted_row_style(colors);
|
||||||
|
|
||||||
let rows = torrents
|
let rows = torrents
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| make_row(t, fields, selected, row_style, colors))
|
.map(|t| make_row(t, fields, selected, select_style, colors))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let widths = fields
|
let widths = fields
|
||||||
@ -28,12 +28,15 @@ pub fn build_table(
|
|||||||
.map(|&f| Constraint::Length(f.width()))
|
.map(|&f| Constraint::Length(f.width()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let header = Row::new(fields.iter().map(|&field| field.title())).style(header_style);
|
let header = Row::new(fields.iter().map(|&field| field.title()))
|
||||||
|
.style(header_style)
|
||||||
|
.bottom_margin(1);
|
||||||
|
|
||||||
Table::new(rows, widths)
|
Table::new(rows, widths)
|
||||||
.block(default_block())
|
.block(default_block())
|
||||||
.header(header)
|
.header(header)
|
||||||
.row_highlight_style(highlight_row_style)
|
.row_highlight_style(highlight_row_style)
|
||||||
|
.highlight_symbol("▶ ")
|
||||||
.column_spacing(1)
|
.column_spacing(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,23 +44,26 @@ fn default_block() -> Block<'static> {
|
|||||||
Block::default()
|
Block::default()
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
|
.border_style(Style::default().fg(Color::DarkGray))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn row_style(cfg: &ColorConfig) -> Style {
|
fn select_style(cfg: &ColorConfig) -> Style {
|
||||||
let fg = to_color(&cfg.highlight_foreground);
|
let fg = to_color(&cfg.highlight_foreground);
|
||||||
let bg = to_color(&cfg.info_foreground);
|
let bg = to_color(&cfg.highlight_background);
|
||||||
Style::default().bg(bg).fg(fg)
|
Style::default().fg(fg).bg(bg).add_modifier(Modifier::BOLD)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_style(cfg: &ColorConfig) -> Style {
|
fn header_style(cfg: &ColorConfig) -> Style {
|
||||||
let fg = to_color(&cfg.header_foreground);
|
let fg = to_color(&cfg.header_foreground);
|
||||||
Style::default().fg(fg)
|
Style::default()
|
||||||
|
.fg(fg)
|
||||||
|
.add_modifier(Modifier::BOLD | Modifier::UNDERLINED)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hightlighted_row_style(cfg: &ColorConfig) -> Style {
|
fn highlighted_row_style(cfg: &ColorConfig) -> Style {
|
||||||
let fg = to_color(&cfg.info_foreground);
|
let fg = to_color(&cfg.highlight_foreground);
|
||||||
let bg = to_color(&cfg.highlight_foreground);
|
let bg = to_color(&cfg.highlight_background);
|
||||||
Style::default().bg(bg).fg(fg)
|
Style::default().fg(fg).bg(bg).add_modifier(Modifier::BOLD)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_row(
|
fn make_row(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user