Scrolling help

This commit is contained in:
Ian Keane 2025-11-23 12:21:03 -05:00
parent eb7790ff8f
commit 857362b558
2 changed files with 118 additions and 34 deletions

View file

@ -60,6 +60,7 @@ pub struct App {
pub log_view_content: Vec<String>, pub log_view_content: Vec<String>,
pub log_view_scroll: usize, pub log_view_scroll: usize,
pub log_view_selected: usize, pub log_view_selected: usize,
pub help_scroll: usize,
pub clipboard: Option<arboard::Clipboard>, pub clipboard: Option<arboard::Clipboard>,
pub status_message: Option<(String, std::time::Instant)>, pub status_message: Option<(String, std::time::Instant)>,
} }
@ -97,6 +98,7 @@ impl App {
log_view_content: Vec::new(), log_view_content: Vec::new(),
log_view_scroll: 0, log_view_scroll: 0,
log_view_selected: 0, log_view_selected: 0,
help_scroll: 0,
clipboard: arboard::Clipboard::new().ok(), clipboard: arboard::Clipboard::new().ok(),
status_message: None, status_message: None,
}) })
@ -141,7 +143,10 @@ impl App {
(KeyCode::Char('p'), KeyModifiers::CONTROL) => self.change_pane(-1), (KeyCode::Char('p'), KeyModifiers::CONTROL) => self.change_pane(-1),
(KeyCode::Enter, _) => self.toggle_current_item()?, (KeyCode::Enter, _) => self.toggle_current_item()?,
(KeyCode::Char('e'), KeyModifiers::CONTROL) => self.edit_config()?, (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('c'), _) => self.edit_app_config()?,
(KeyCode::Char('n'), _) => self.start_new_entry(), (KeyCode::Char('n'), _) => self.start_new_entry(),
(KeyCode::Char('p'), _) => self.start_reassign_project(), (KeyCode::Char('p'), _) => self.start_reassign_project(),
@ -245,8 +250,26 @@ impl App {
fn handle_help_event(&mut self, event: Event) -> anyhow::Result<bool> { fn handle_help_event(&mut self, event: Event) -> anyhow::Result<bool> {
match event { match event {
Event::Key(KeyEvent { code, .. }) => match code { Event::Key(KeyEvent { code, .. }) => match code {
KeyCode::Char('c') => self.current_screen = Screen::ConfigHelp, KeyCode::Char('c') => {
KeyCode::Esc | KeyCode::Char('q') => self.current_screen = Screen::Main, 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<bool> { fn handle_config_help_event(&mut self, event: Event) -> anyhow::Result<bool> {
match event { match event {
Event::Key(KeyEvent { code, .. }) => match code { 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<bool> { fn handle_log_view_help_event(&mut self, event: Event) -> anyhow::Result<bool> {
match event { match event {
Event::Key(KeyEvent { code, .. }) => match code { 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('?') => { KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('?') => {
self.help_scroll = 0;
self.current_screen = Screen::LogView; self.current_screen = Screen::LogView;
} }
_ => {} _ => {}
@ -332,6 +383,7 @@ impl App {
self.needs_clear = true; self.needs_clear = true;
} }
KeyCode::Char('?') => { KeyCode::Char('?') => {
self.help_scroll = 0;
self.current_screen = Screen::LogViewHelp; self.current_screen = Screen::LogViewHelp;
} }
KeyCode::Char('d') => { KeyCode::Char('d') => {

View file

@ -214,7 +214,7 @@ fn render_help_command_bar(frame: &mut Frame) {
frame.render_widget(command_bar, bar_area); 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 width = frame.size().width.saturating_sub(4).min(60);
let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size()); 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", "3. Ad-Hoc Items: One-off tasks and quick entries",
"", "",
"Navigation:", "Navigation:",
"- Use j/k or ↑/↓ to move selection up/down", "- j/k or ↑/↓: Move selection up/down",
"- Use h/l or ←/→ to switch between panes", "- h/l or ←/→: Switch between panes",
"- Use Ctrl+n/Ctrl+p to switch between panes", "- Ctrl+n/Ctrl+p: Switch between panes",
"- Press Enter to start/stop time tracking", "- Enter: Start/stop time tracking for selected item",
"- Press n to create a new task in the current section", "- n: Create a new task in the current section",
"", "",
"Main Commands:", "Main Commands:",
"j/k, arrows - Navigate",
"h/l, arrows - Switch panes",
"Enter - Start/stop timer", "Enter - Start/stop timer",
"d - Delete task", "d - Delete task from list",
"p - Reassign project", "p - Reassign project/tag",
"v - View Watson log (g to group, e to edit, x to delete, c to copy)", "v - View Watson log",
"Ctrl+e - Edit tasks config", "Ctrl+e - Edit tasks config file",
"c - Edit app config", "c - Edit app config file",
"n - New task", "n - New task",
"q - Quit", "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 text = help_text.join("\n");
let block = Block::default() let block = Block::default()
.title("Help") .title("Help (j/k to scroll)")
.borders(Borders::ALL) .borders(Borders::ALL)
.style(Style::default().fg(Color::White)); .style(Style::default().fg(Color::White));
let paragraph = Paragraph::new(text) let paragraph = Paragraph::new(text)
.block(block) .block(block)
.style(Style::default().fg(Color::White)) .style(Style::default().fg(Color::White))
.scroll((app.help_scroll as u16, 0))
.wrap(ratatui::widgets::Wrap { trim: true }); .wrap(ratatui::widgets::Wrap { trim: true });
frame.render_widget(paragraph, area); frame.render_widget(paragraph, area);
render_help_command_bar(frame); 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 width = frame.size().width.saturating_sub(4).min(60);
let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size()); 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![ let help_text = vec![
"WAT Configuration", "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", "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]", "projects: [list of project names]",
" List of available project names", " Default: [] (empty)",
" List of available project names shown when reassigning",
" Example: [\"work\", \"personal\", \"client-a\"]",
"", "",
"strict_projects: true/false", "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:", "Example configuration:",
"---",
"show_help_hint: true", "show_help_hint: true",
"projects:",
" - project1",
" - project2",
"strict_projects: false", "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 text = help_text.join("\n");
let block = Block::default() let block = Block::default()
.title("Configuration Help") .title("Configuration Help (j/k to scroll)")
.borders(Borders::ALL) .borders(Borders::ALL)
.style(Style::default().fg(Color::White)); .style(Style::default().fg(Color::White));
let paragraph = Paragraph::new(text) let paragraph = Paragraph::new(text)
.block(block) .block(block)
.style(Style::default().fg(Color::White)) .style(Style::default().fg(Color::White))
.scroll((app.help_scroll as u16, 0))
.wrap(ratatui::widgets::Wrap { trim: true }); .wrap(ratatui::widgets::Wrap { trim: true });
frame.render_widget(paragraph, area); 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 width = frame.size().width.saturating_sub(4).min(60);
let area = centered_rect(width, frame.size().height.saturating_sub(2), frame.size()); 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 Entry level: Move to next/previous entry",
" - At Project level: Jump to next/previous project group", " - At Project level: Jump to next/previous project group",
" - At Day level: Jump to next/previous day", " - 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)", "- 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)", "- Entry: Select individual entry (can edit/delete/copy)",
"- Project: Select whole project group (can copy only)", "- Project: Select whole project group (can copy only)",
"- Day: Select entire day (can copy only)", "- Day: Select entire day (can copy only)",
"- All: Select entire view/period (can copy only)",
"", "",
"Actions:", "Actions:",
"- e: Edit the selected entry (Entry level only)", "- e: Edit the selected entry (Entry level only)",
"- x: Delete the selected entry (Entry level only)", "- x: Delete the selected entry (Entry level only)",
"- c: Copy selection to clipboard (works at all levels)", "- c: Copy selection to clipboard (works at all levels)",
" Copies based on current selection level",
"", "",
"Other:", "Other:",
"- ?: Show this help", "- ?: Show/hide this help",
"- q or ESC: Return to main screen", "- 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 text = help_text.join("\n");
let block = Block::default() let block = Block::default()
.title("Log View Help") .title("Log View Help (j/k to scroll)")
.borders(Borders::ALL) .borders(Borders::ALL)
.style(Style::default().fg(Color::White)); .style(Style::default().fg(Color::White));
let paragraph = Paragraph::new(text) let paragraph = Paragraph::new(text)
.block(block) .block(block)
.style(Style::default().fg(Color::White)) .style(Style::default().fg(Color::White))
.scroll((app.help_scroll as u16, 0))
.wrap(ratatui::widgets::Wrap { trim: true }); .wrap(ratatui::widgets::Wrap { trim: true });
frame.render_widget(paragraph, area); frame.render_widget(paragraph, area);