From 811743e1e4e73cff9530531b7c8adeceab1d2748 Mon Sep 17 00:00:00 2001 From: Ian Keane Date: Sun, 16 Nov 2025 10:48:06 -0500 Subject: [PATCH] Help hints in UI --- src/config.rs | 9 +++- src/ui.rs | 147 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 127 insertions(+), 29 deletions(-) diff --git a/src/config.rs b/src/config.rs index 9db7397..5171b8d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,16 +5,23 @@ use std::path::PathBuf; pub struct Config { #[serde(default = "default_show_help_hint")] pub show_help_hint: bool, + #[serde(default = "default_show_command_hints")] + pub show_command_hints: bool, } fn default_show_help_hint() -> bool { true } +fn default_show_command_hints() -> bool { + true +} + impl Default for Config { fn default() -> Self { Self { - show_help_hint: true, + show_help_hint: default_show_help_hint(), + show_command_hints: default_show_command_hints(), } } } diff --git a/src/ui.rs b/src/ui.rs index 33553fe..ef58efb 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -14,24 +14,53 @@ const INACTIVE_COLOR: Color = Color::Yellow; pub fn render(frame: &mut Frame, app: &App) { match app.current_screen { Screen::Main => render_main(frame, app), - Screen::Help => render_help(frame), - Screen::ConfigHelp => render_config_help(frame), + Screen::Help => render_help(frame, app), + Screen::ConfigHelp => render_config_help(frame, app), } } fn render_main(frame: &mut Frame, app: &App) { + // Calculate layout - accounting for bottom bar if needed + let show_bottom_bar = app.config.show_help_hint || app.config.show_command_hints; + let constraints = if show_bottom_bar { + vec![ + Constraint::Min(3), // At least 3 lines for each section + Constraint::Min(3), + Constraint::Min(3), + Constraint::Length(1), // Command bar + ] + } else { + vec![ + Constraint::Min(3), + Constraint::Min(3), + Constraint::Min(3), + ] + }; + let chunks = Layout::default() .direction(Direction::Vertical) - .constraints([ - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - Constraint::Ratio(1, 3), - ]) + .constraints(constraints) .split(frame.size()); + let main_height = if show_bottom_bar { + chunks[0].height + chunks[1].height + chunks[2].height + } else { + frame.size().height + }; + + let section_height = main_height / 3; + + // Create sections with equal height + let sections = vec![ + Rect::new(0, 0, frame.size().width, section_height), + Rect::new(0, section_height, frame.size().width, section_height), + Rect::new(0, section_height * 2, frame.size().width, section_height), + ]; + + // Render main sections render_section( frame, - chunks[0], + sections[0], "Permanent Items", &app.state.permanent_items, app.state.current_pane == 0, @@ -41,7 +70,7 @@ fn render_main(frame: &mut Frame, app: &App) { render_section( frame, - chunks[1], + sections[1], "Recurring Items", &app.state.recurring_items, app.state.current_pane == 1, @@ -51,7 +80,7 @@ fn render_main(frame: &mut Frame, app: &App) { render_section( frame, - chunks[2], + sections[2], "Recent Items", &app.state.recent_items, app.state.current_pane == 2, @@ -59,22 +88,85 @@ fn render_main(frame: &mut Frame, app: &App) { &app.state, ); + // Render bottom bar if needed + if show_bottom_bar { + let bottom_area = Rect::new( + 0, + frame.size().height - 1, + frame.size().width, + 1, + ); + render_bottom_bar(frame, bottom_area, app); + } +} + +fn render_bottom_bar(frame: &mut Frame, area: Rect, app: &App) { + if app.config.show_command_hints { + let commands = vec![ + ("c", "config"), + ("q", "quit"), + ]; + + let command_text = format!(" {}", commands.iter() + .map(|(key, desc)| format!("{} ({})", key, desc)) + .collect::>() + .join(" · ")); + + let command_area = if app.config.show_help_hint { + // Leave space for help hint + Rect::new(area.x, area.y, area.width.saturating_sub(12), area.height) + } else { + area + }; + + let command_bar = Paragraph::new(command_text) + .style(Style::default().fg(Color::White)) + .alignment(Alignment::Left); + + frame.render_widget(command_bar, command_area); + } + if app.config.show_help_hint { let help_hint = Paragraph::new("(?) for help") .alignment(Alignment::Right) .style(Style::default().fg(Color::DarkGray)); - let help_area = Rect { - x: frame.size().width.saturating_sub(12), - y: frame.size().height.saturating_sub(1), - width: 12, - height: 1, - }; + let help_area = Rect::new( + area.width.saturating_sub(12), + area.y, + 12, + 1, + ); frame.render_widget(help_hint, help_area); } } +fn render_help_command_bar(frame: &mut Frame) { + let commands = vec![ + ("c", "configuration help"), + ("q/ESC", "back"), + ]; + + let command_text = format!(" {}", commands.iter() + .map(|(key, desc)| format!("{} ({})", key, desc)) + .collect::>() + .join(" · ")); + + let bar_area = Rect::new( + 0, + frame.size().height.saturating_sub(1), + frame.size().width, + 1, + ); + + let command_bar = Paragraph::new(command_text) + .style(Style::default().fg(Color::White)) + .alignment(Alignment::Left); + + frame.render_widget(command_bar, bar_area); +} + fn render_section( frame: &mut Frame, area: Rect, @@ -124,8 +216,8 @@ fn render_section( frame.render_widget(list, area); } -fn render_help(frame: &mut Frame) { - let area = centered_rect(60, 20, frame.size()); +fn render_help(frame: &mut Frame, _app: &App) { + let area = centered_rect(60, frame.size().height.saturating_sub(2), frame.size()); frame.render_widget(Clear, area); @@ -144,9 +236,6 @@ fn render_help(frame: &mut Frame) { "- Use h/l or ←/→ to switch between panes", "- Press Enter to start/stop time tracking", "", - "Help Pages:", - "c - Configuration help", - "", "Main Commands:", "j/k, arrows - Navigate", "h/l, arrows - Switch panes", @@ -169,10 +258,11 @@ fn render_help(frame: &mut Frame) { .style(Style::default().fg(Color::White)); frame.render_widget(paragraph, area); + render_help_command_bar(frame); } -fn render_config_help(frame: &mut Frame) { - let area = centered_rect(60, 15, frame.size()); +fn render_config_help(frame: &mut Frame, _app: &App) { + let area = centered_rect(60, frame.size().height.saturating_sub(2), frame.size()); frame.render_widget(Clear, area); @@ -182,14 +272,14 @@ fn render_config_help(frame: &mut Frame) { "The configuration file is in YAML format and supports these options:", "", "show_help_hint: true/false", - " Controls visibility of the help hint in the main interface", + " Controls visibility of the help hint in the bottom bar", + "", + "show_command_hints: true/false", + " Controls visibility of command hints in the bottom bar", "", "Example configuration:", "show_help_hint: true", - "", - "", - "Commands:", - "q (or ESC) - Return to main help", + "show_command_hints: true", ]; let text = help_text.join("\n"); @@ -204,6 +294,7 @@ fn render_config_help(frame: &mut Frame) { .style(Style::default().fg(Color::White)); frame.render_widget(paragraph, area); + render_help_command_bar(frame); } fn centered_rect(width: u16, height: u16, r: Rect) -> Rect {