feat(help): add help popup

This commit is contained in:
Kristofers Solo 2025-07-07 17:52:42 +03:00
parent cad0ac00e2
commit 98ad8efd3a
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
9 changed files with 85 additions and 62 deletions

View File

@ -6,7 +6,7 @@ pub enum Action {
NextTorrent,
PrevTorrent,
SwitchTab(u8),
TogglePopup,
ToggleHelp, // Add this line
ToggleTorrent,
ToggleAll,
PauseAll,

View File

@ -17,7 +17,7 @@ pub struct App<'a> {
tabs: &'a [Tab],
pub state: TableState,
pub torrents: Torrents,
pub show_popup: bool,
pub show_help: bool,
}
impl<'a> App<'a> {
@ -30,7 +30,7 @@ impl<'a> App<'a> {
index: 0,
state: TableState::default(),
torrents: Torrents::new()?, // Handle the Result here
show_popup: false,
show_help: false,
})
}
@ -56,7 +56,7 @@ impl<'a> App<'a> {
}
None => 0,
};
self.close_popup();
self.close_help();
self.state.select(Some(i));
}
@ -71,19 +71,19 @@ impl<'a> App<'a> {
}
None => 0,
};
self.close_popup();
self.close_help();
self.state.select(Some(i));
}
/// Switches to the next tab.
pub fn next_tab(&mut self) {
self.close_popup();
self.close_help();
self.index = (self.index + 1) % self.tabs.len();
}
/// Switches to the previous tab.
pub fn prev_tab(&mut self) {
self.close_popup();
self.close_help();
if self.index > 0 {
self.index -= 1;
} else {
@ -93,7 +93,7 @@ impl<'a> App<'a> {
/// Switches to the tab whose index is `idx`.
pub fn switch_tab(&mut self, idx: usize) {
self.close_popup();
self.close_help();
self.index = idx
}
@ -107,29 +107,29 @@ impl<'a> App<'a> {
self.tabs
}
pub fn toggle_popup(&mut self) {
self.show_popup = !self.show_popup;
pub fn toggle_help(&mut self) {
self.show_help = !self.show_help;
}
pub fn close_popup(&mut self) {
self.show_popup = false;
pub fn close_help(&mut self) {
self.show_help = false;
}
pub fn open_popup(&mut self) {
self.show_popup = true;
pub fn open_help(&mut self) {
self.show_help = true;
}
pub async fn toggle_torrents(&mut self) -> anyhow::Result<()> {
let ids = self.selected(false);
self.torrents.toggle(ids).await?;
self.close_popup();
self.close_help();
Ok(())
}
pub async fn delete(&mut self, delete_local_data: bool) -> anyhow::Result<()> {
let ids = self.selected(false);
self.torrents.delete(ids, delete_local_data).await?;
self.close_popup();
self.close_help();
Ok(())
}

View File

@ -24,7 +24,7 @@ pub fn get_action(key_event: KeyEvent) -> Option<Action> {
KeyCode::Char('d') => Some(Action::Delete(false)),
KeyCode::Char('D') => Some(Action::Delete(true)),
KeyCode::Char(' ') => Some(Action::Select),
// Other handlers you could add here.
KeyCode::Char('?') => Some(Action::ToggleHelp),
_ => None,
}
}
@ -38,7 +38,7 @@ pub async fn update(app: &mut App<'_>, action: Action) -> anyhow::Result<()> {
Action::NextTorrent => app.next(),
Action::PrevTorrent => app.previous(),
Action::SwitchTab(x) => app.switch_tab(x as usize),
Action::TogglePopup => app.toggle_popup(),
Action::ToggleHelp => app.toggle_help(),
Action::ToggleTorrent => app.toggle_torrents().await?,
Action::ToggleAll => app.torrents.toggle_all().await?,
Action::PauseAll => app.torrents.stop_all().await?,

View File

@ -12,7 +12,3 @@ pub mod tui;
/// Event handler.
pub mod handler;

51
src/ui/help.rs Normal file
View File

@ -0,0 +1,51 @@
use ratatui::{prelude::*, widgets::*};
pub fn render_help(frame: &mut Frame) {
let block = Block::default()
.title("Help")
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_type(BorderType::Rounded);
let rows = vec![
Row::new(vec![Cell::from("?"), Cell::from("Show help")]),
Row::new(vec![Cell::from("q"), Cell::from("Quit")]),
Row::new(vec![Cell::from("h"), Cell::from("Left")]),
Row::new(vec![Cell::from("l"), Cell::from("Right")]),
Row::new(vec![Cell::from("j"), Cell::from("Down")]),
Row::new(vec![Cell::from("k"), Cell::from("Up")]),
Row::new(vec![Cell::from("1"), Cell::from("Switch to All tab")]),
Row::new(vec![Cell::from("2"), Cell::from("Switch to Active tab")]),
Row::new(vec![
Cell::from("3"),
Cell::from("Switch to Downloading tab"),
]),
Row::new(vec![Cell::from("t"), Cell::from("Toggle torrent")]),
Row::new(vec![Cell::from("a"), Cell::from("Toggle all torrents")]),
Row::new(vec![Cell::from("d"), Cell::from("Delete torrent")]),
Row::new(vec![Cell::from("D"), Cell::from("Delete torrent and data")]),
Row::new(vec![Cell::from(" "), Cell::from("Select torrent")]),
];
let table = Table::new(
rows,
&[Constraint::Percentage(20), Constraint::Percentage(80)],
)
.block(block)
.style(Style::default().fg(Color::Green));
let area = frame.area();
let height = 15; // Desired height for the help menu
let width = area.width; // Full width of the screen
let popup_area = Rect::new(
area.x + (area.width - width) / 2, // Center horizontally
area.y + area.height - height, // Position at the very bottom
width,
height,
);
frame.render_widget(Clear, popup_area);
frame.render_widget(table, popup_area);
}

View File

@ -1,16 +1,9 @@
mod popup;
mod help;
mod table;
use crate::app::{App, Tab};
use popup::render_popup;
use ratatui::{
layout::Alignment,
prelude::{Constraint, Direction, Layout},
style::{Color, Style},
text::Line,
widgets::{Block, BorderType, Borders, Clear, Tabs},
Frame,
};
use help::render_help;
use ratatui::{prelude::*, widgets::*};
use table::render_table;
/// Renders the user interface widgets.
@ -57,10 +50,7 @@ pub fn render(app: &mut App, frame: &mut Frame) {
};
frame.render_stateful_widget(table, chunks[1], &mut app.state); // renders table
if app.show_popup {
let block = Block::default().borders(Borders::ALL);
let size = render_popup(size);
frame.render_widget(Clear, size);
frame.render_widget(block, size);
if app.show_help {
render_help(frame);
}
}

View File

@ -1,13 +0,0 @@
use ratatui::layout::Rect;
pub fn render_popup(r: Rect) -> Rect {
let vertical_margin = r.height / 5;
let horizontal_margin = r.width / 5;
Rect::new(
r.x + horizontal_margin,
r.y + vertical_margin,
r.width - (2 * horizontal_margin),
r.height - (2 * vertical_margin),
)
}

View File

@ -1,11 +1,10 @@
use crate::app::{utils::Wrapper, App, Tab};
use ratatui::{
layout::Constraint,
style::{Color, Style, Styled},
widgets::{Block, BorderType, Borders, Row, Table},
};
use crate::app::{utils::Wrapper, App, Tab};
pub fn render_table<'a>(app: &mut App, tab: Tab) -> Table<'a> {
let fields = tab.fields();
let selected = &app.torrents.selected.clone();

View File

@ -48,19 +48,19 @@ fn test_app_switch_tab() {
#[test]
fn test_app_toggle_popup() {
let mut app = App::new().unwrap();
assert!(!app.show_popup);
app.toggle_popup();
assert!(app.show_popup);
app.toggle_popup();
assert!(!app.show_popup);
assert!(!app.show_help);
app.toggle_help();
assert!(app.show_help);
app.toggle_help();
assert!(!app.show_help);
}
#[test]
fn test_app_open_close_popup() {
let mut app = App::new().unwrap();
assert!(!app.show_popup);
app.open_popup();
assert!(app.show_popup);
app.close_popup();
assert!(!app.show_popup);
assert!(!app.show_help);
app.open_help();
assert!(app.show_help);
app.close_help();
assert!(!app.show_help);
}