From 857362b558d3c31c6c8fc551825ec2faa8ce1c11 Mon Sep 17 00:00:00 2001 From: Ian Keane Date: Sun, 23 Nov 2025 12:21:03 -0500 Subject: [PATCH] Scrolling help --- src/app.rs | 60 ++++++++++++++++++++++++++++++++--- src/ui.rs | 92 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 118 insertions(+), 34 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7e99729..38ecfd7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -60,6 +60,7 @@ pub struct App { pub log_view_content: Vec, pub log_view_scroll: usize, pub log_view_selected: usize, + pub help_scroll: usize, pub clipboard: Option, pub status_message: Option<(String, std::time::Instant)>, } @@ -97,6 +98,7 @@ impl App { log_view_content: Vec::new(), log_view_scroll: 0, log_view_selected: 0, + help_scroll: 0, clipboard: arboard::Clipboard::new().ok(), status_message: None, }) @@ -141,7 +143,10 @@ impl App { (KeyCode::Char('p'), KeyModifiers::CONTROL) => self.change_pane(-1), (KeyCode::Enter, _) => self.toggle_current_item()?, (KeyCode::Char('e'), KeyModifiers::CONTROL) => self.edit_config()?, - (KeyCode::Char('?'), _) => self.current_screen = Screen::Help, + (KeyCode::Char('?'), _) => { + self.help_scroll = 0; + self.current_screen = Screen::Help; + } (KeyCode::Char('c'), _) => self.edit_app_config()?, (KeyCode::Char('n'), _) => self.start_new_entry(), (KeyCode::Char('p'), _) => self.start_reassign_project(), @@ -245,8 +250,26 @@ impl App { fn handle_help_event(&mut self, event: Event) -> anyhow::Result { match event { Event::Key(KeyEvent { code, .. }) => match code { - KeyCode::Char('c') => self.current_screen = Screen::ConfigHelp, - KeyCode::Esc | KeyCode::Char('q') => self.current_screen = Screen::Main, + KeyCode::Char('c') => { + self.help_scroll = 0; + self.current_screen = Screen::ConfigHelp; + } + KeyCode::Char('j') | KeyCode::Down => { + self.help_scroll = self.help_scroll.saturating_add(1); + } + KeyCode::Char('k') | KeyCode::Up => { + self.help_scroll = self.help_scroll.saturating_sub(1); + } + KeyCode::PageDown => { + self.help_scroll = self.help_scroll.saturating_add(10); + } + KeyCode::PageUp => { + self.help_scroll = self.help_scroll.saturating_sub(10); + } + KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('?') => { + self.help_scroll = 0; + self.current_screen = Screen::Main; + } _ => {} }, _ => {} @@ -257,7 +280,22 @@ impl App { fn handle_config_help_event(&mut self, event: Event) -> anyhow::Result { match event { Event::Key(KeyEvent { code, .. }) => match code { - KeyCode::Esc | KeyCode::Char('q') => self.current_screen = Screen::Help, + KeyCode::Char('j') | KeyCode::Down => { + self.help_scroll = self.help_scroll.saturating_add(1); + } + KeyCode::Char('k') | KeyCode::Up => { + self.help_scroll = self.help_scroll.saturating_sub(1); + } + KeyCode::PageDown => { + self.help_scroll = self.help_scroll.saturating_add(10); + } + KeyCode::PageUp => { + self.help_scroll = self.help_scroll.saturating_sub(10); + } + KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('?') => { + self.help_scroll = 0; + self.current_screen = Screen::Help; + } _ => {} }, _ => {} @@ -268,7 +306,20 @@ impl App { fn handle_log_view_help_event(&mut self, event: Event) -> anyhow::Result { match event { Event::Key(KeyEvent { code, .. }) => match code { + KeyCode::Char('j') | KeyCode::Down => { + self.help_scroll = self.help_scroll.saturating_add(1); + } + KeyCode::Char('k') | KeyCode::Up => { + self.help_scroll = self.help_scroll.saturating_sub(1); + } + KeyCode::PageDown => { + self.help_scroll = self.help_scroll.saturating_add(10); + } + KeyCode::PageUp => { + self.help_scroll = self.help_scroll.saturating_sub(10); + } KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('?') => { + self.help_scroll = 0; self.current_screen = Screen::LogView; } _ => {} @@ -332,6 +383,7 @@ impl App { self.needs_clear = true; } KeyCode::Char('?') => { + self.help_scroll = 0; self.current_screen = Screen::LogViewHelp; } KeyCode::Char('d') => { diff --git a/src/ui.rs b/src/ui.rs index 6f49af2..a35478e 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -214,7 +214,7 @@ fn render_help_command_bar(frame: &mut Frame) { frame.render_widget(command_bar, bar_area); } -fn render_help(frame: &mut Frame, _app: &App) { +fn render_help(frame: &mut Frame, app: &App) { let width = frame.size().width.saturating_sub(4).min(60); let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size()); @@ -231,43 +231,53 @@ fn render_help(frame: &mut Frame, _app: &App) { "3. Ad-Hoc Items: One-off tasks and quick entries", "", "Navigation:", - "- Use j/k or ↑/↓ to move selection up/down", - "- Use h/l or ←/→ to switch between panes", - "- Use Ctrl+n/Ctrl+p to switch between panes", - "- Press Enter to start/stop time tracking", - "- Press n to create a new task in the current section", + "- j/k or ↑/↓: Move selection up/down", + "- h/l or ←/→: Switch between panes", + "- Ctrl+n/Ctrl+p: Switch between panes", + "- Enter: Start/stop time tracking for selected item", + "- n: Create a new task in the current section", "", "Main Commands:", - "j/k, arrows - Navigate", - "h/l, arrows - Switch panes", "Enter - Start/stop timer", - "d - Delete task", - "p - Reassign project", - "v - View Watson log (g to group, e to edit, x to delete, c to copy)", - "Ctrl+e - Edit tasks config", - "c - Edit app config", + "d - Delete task from list", + "p - Reassign project/tag", + "v - View Watson log", + "Ctrl+e - Edit tasks config file", + "c - Edit app config file", "n - New task", "q - Quit", - "? (or ESC) - Exit help", + "? - Show/hide this help (ESC also works)", + "", + "Log View (press 'v'):", + "Once in log view, you can:", + "- Switch time periods: d (day), w (week), m (month)", + "- Toggle grouping: g (by date or by project)", + "- Change selection: h/l (entry → project → day → all)", + "- Navigate: j/k to move through selections", + "- Edit entry: e (entry level only)", + "- Delete entry: x (entry level only)", + "- Copy to clipboard: c (works at all levels)", + "- Press ? for detailed log view help", ]; let text = help_text.join("\n"); let block = Block::default() - .title("Help") + .title("Help (j/k to scroll)") .borders(Borders::ALL) .style(Style::default().fg(Color::White)); let paragraph = Paragraph::new(text) .block(block) .style(Style::default().fg(Color::White)) + .scroll((app.help_scroll as u16, 0)) .wrap(ratatui::widgets::Wrap { trim: true }); frame.render_widget(paragraph, area); render_help_command_bar(frame); } -fn render_config_help(frame: &mut Frame, _app: &App) { +fn render_config_help(frame: &mut Frame, app: &App) { let width = frame.size().width.saturating_sub(4).min(60); let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size()); @@ -276,35 +286,49 @@ fn render_config_help(frame: &mut Frame, _app: &App) { let help_text = vec![ "WAT Configuration", "", - "The configuration file is in YAML format and supports these options:", + "Configuration file location:", + " ~/.config/wat/config.yaml", + "", + "Available options:", "", "show_help_hint: true/false", - " Controls visibility of the help hint in the bottom bar", + " Default: true", + " Shows '(?) for help' hint in the bottom-right corner", "", - "projects: [list of strings]", - " List of available project names", + "projects: [list of project names]", + " Default: [] (empty)", + " List of available project names shown when reassigning", + " Example: [\"work\", \"personal\", \"client-a\"]", "", "strict_projects: true/false", - " If true, only projects from the projects list are allowed", + " Default: false", + " When true, only allows projects from the 'projects' list", + " When false, any project name can be used", "", "Example configuration:", + "---", "show_help_hint: true", - "projects:", - " - project1", - " - project2", "strict_projects: false", + "projects:", + " - work", + " - personal", + " - open-source", + "", + "Note: The config file is created automatically on first run.", + "Edit it with 'c' from the main screen or manually with your editor.", ]; let text = help_text.join("\n"); let block = Block::default() - .title("Configuration Help") + .title("Configuration Help (j/k to scroll)") .borders(Borders::ALL) .style(Style::default().fg(Color::White)); let paragraph = Paragraph::new(text) .block(block) .style(Style::default().fg(Color::White)) + .scroll((app.help_scroll as u16, 0)) .wrap(ratatui::widgets::Wrap { trim: true }); frame.render_widget(paragraph, area); @@ -595,7 +619,7 @@ fn render_log_view(frame: &mut Frame, app: &App) { } } -fn render_log_view_help(frame: &mut Frame, _app: &App) { +fn render_log_view_help(frame: &mut Frame, app: &App) { let width = frame.size().width.saturating_sub(4).min(60); let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size()); @@ -622,34 +646,42 @@ fn render_log_view_help(frame: &mut Frame, _app: &App) { " - At Entry level: Move to next/previous entry", " - At Project level: Jump to next/previous project group", " - At Day level: Jump to next/previous day", - "- h/l or ←/→: Change selection level (Entry ↔ Project ↔ Day)", + " - At All level: No navigation (entire view selected)", + "- h/l or ←/→: Change selection level", + " - l (right): Zoom in (All → Day → Project → Entry)", + " - h (left): Zoom out (Entry → Project → Day → All)", "- PageUp/PageDown: Jump 10 entries (Entry level only)", "", - "Selection Levels:", + "Selection Levels (use h/l to change):", "- Entry: Select individual entry (can edit/delete/copy)", "- Project: Select whole project group (can copy only)", "- Day: Select entire day (can copy only)", + "- All: Select entire view/period (can copy only)", "", "Actions:", "- e: Edit the selected entry (Entry level only)", "- x: Delete the selected entry (Entry level only)", "- c: Copy selection to clipboard (works at all levels)", + " Copies based on current selection level", "", "Other:", - "- ?: Show this help", + "- ?: Show/hide this help", "- q or ESC: Return to main screen", + "", + "Tip: Use h/l to quickly select larger blocks for copying!", ]; let text = help_text.join("\n"); let block = Block::default() - .title("Log View Help") + .title("Log View Help (j/k to scroll)") .borders(Borders::ALL) .style(Style::default().fg(Color::White)); let paragraph = Paragraph::new(text) .block(block) .style(Style::default().fg(Color::White)) + .scroll((app.help_scroll as u16, 0)) .wrap(ratatui::widgets::Wrap { trim: true }); frame.render_widget(paragraph, area);