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, NextTorrent,
PrevTorrent, PrevTorrent,
SwitchTab(u8), SwitchTab(u8),
TogglePopup, ToggleHelp, // Add this line
ToggleTorrent, ToggleTorrent,
ToggleAll, ToggleAll,
PauseAll, PauseAll,

View File

@ -17,7 +17,7 @@ pub struct App<'a> {
tabs: &'a [Tab], tabs: &'a [Tab],
pub state: TableState, pub state: TableState,
pub torrents: Torrents, pub torrents: Torrents,
pub show_popup: bool, pub show_help: bool,
} }
impl<'a> App<'a> { impl<'a> App<'a> {
@ -30,7 +30,7 @@ impl<'a> App<'a> {
index: 0, index: 0,
state: TableState::default(), state: TableState::default(),
torrents: Torrents::new()?, // Handle the Result here torrents: Torrents::new()?, // Handle the Result here
show_popup: false, show_help: false,
}) })
} }
@ -56,7 +56,7 @@ impl<'a> App<'a> {
} }
None => 0, None => 0,
}; };
self.close_popup(); self.close_help();
self.state.select(Some(i)); self.state.select(Some(i));
} }
@ -71,19 +71,19 @@ impl<'a> App<'a> {
} }
None => 0, None => 0,
}; };
self.close_popup(); self.close_help();
self.state.select(Some(i)); self.state.select(Some(i));
} }
/// Switches to the next tab. /// Switches to the next tab.
pub fn next_tab(&mut self) { pub fn next_tab(&mut self) {
self.close_popup(); self.close_help();
self.index = (self.index + 1) % self.tabs.len(); self.index = (self.index + 1) % self.tabs.len();
} }
/// Switches to the previous tab. /// Switches to the previous tab.
pub fn prev_tab(&mut self) { pub fn prev_tab(&mut self) {
self.close_popup(); self.close_help();
if self.index > 0 { if self.index > 0 {
self.index -= 1; self.index -= 1;
} else { } else {
@ -93,7 +93,7 @@ impl<'a> App<'a> {
/// Switches to the tab whose index is `idx`. /// Switches to the tab whose index is `idx`.
pub fn switch_tab(&mut self, idx: usize) { pub fn switch_tab(&mut self, idx: usize) {
self.close_popup(); self.close_help();
self.index = idx self.index = idx
} }
@ -107,29 +107,29 @@ impl<'a> App<'a> {
self.tabs self.tabs
} }
pub fn toggle_popup(&mut self) { pub fn toggle_help(&mut self) {
self.show_popup = !self.show_popup; self.show_help = !self.show_help;
} }
pub fn close_popup(&mut self) { pub fn close_help(&mut self) {
self.show_popup = false; self.show_help = false;
} }
pub fn open_popup(&mut self) { pub fn open_help(&mut self) {
self.show_popup = true; self.show_help = true;
} }
pub async fn toggle_torrents(&mut self) -> anyhow::Result<()> { pub async fn toggle_torrents(&mut self) -> anyhow::Result<()> {
let ids = self.selected(false); let ids = self.selected(false);
self.torrents.toggle(ids).await?; self.torrents.toggle(ids).await?;
self.close_popup(); self.close_help();
Ok(()) Ok(())
} }
pub async fn delete(&mut self, delete_local_data: bool) -> anyhow::Result<()> { pub async fn delete(&mut self, delete_local_data: bool) -> anyhow::Result<()> {
let ids = self.selected(false); let ids = self.selected(false);
self.torrents.delete(ids, delete_local_data).await?; self.torrents.delete(ids, delete_local_data).await?;
self.close_popup(); self.close_help();
Ok(()) 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(false)),
KeyCode::Char('D') => Some(Action::Delete(true)), KeyCode::Char('D') => Some(Action::Delete(true)),
KeyCode::Char(' ') => Some(Action::Select), KeyCode::Char(' ') => Some(Action::Select),
// Other handlers you could add here. KeyCode::Char('?') => Some(Action::ToggleHelp),
_ => None, _ => None,
} }
} }
@ -38,7 +38,7 @@ pub async fn update(app: &mut App<'_>, action: Action) -> anyhow::Result<()> {
Action::NextTorrent => app.next(), Action::NextTorrent => app.next(),
Action::PrevTorrent => app.previous(), Action::PrevTorrent => app.previous(),
Action::SwitchTab(x) => app.switch_tab(x as usize), 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::ToggleTorrent => app.toggle_torrents().await?,
Action::ToggleAll => app.torrents.toggle_all().await?, Action::ToggleAll => app.torrents.toggle_all().await?,
Action::PauseAll => app.torrents.stop_all().await?, Action::PauseAll => app.torrents.stop_all().await?,

View File

@ -12,7 +12,3 @@ pub mod tui;
/// Event handler. /// Event handler.
pub mod 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; mod table;
use crate::app::{App, Tab}; use crate::app::{App, Tab};
use popup::render_popup; use help::render_help;
use ratatui::{ use ratatui::{prelude::*, widgets::*};
layout::Alignment,
prelude::{Constraint, Direction, Layout},
style::{Color, Style},
text::Line,
widgets::{Block, BorderType, Borders, Clear, Tabs},
Frame,
};
use table::render_table; use table::render_table;
/// Renders the user interface widgets. /// 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 frame.render_stateful_widget(table, chunks[1], &mut app.state); // renders table
if app.show_popup { if app.show_help {
let block = Block::default().borders(Borders::ALL); render_help(frame);
let size = render_popup(size);
frame.render_widget(Clear, size);
frame.render_widget(block, size);
} }
} }

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::{ use ratatui::{
layout::Constraint, layout::Constraint,
style::{Color, Style, Styled}, style::{Color, Style, Styled},
widgets::{Block, BorderType, Borders, Row, Table}, 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> { pub fn render_table<'a>(app: &mut App, tab: Tab) -> Table<'a> {
let fields = tab.fields(); let fields = tab.fields();
let selected = &app.torrents.selected.clone(); let selected = &app.torrents.selected.clone();

View File

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