From f2da97f216b6b50c7aa34e528b1ed7b958d2262b Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:24:48 -0500 Subject: [PATCH] feat: add help menu --- src/main.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index cd60acb..7a448b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,18 +6,12 @@ use std::{ }; use crossterm::{ - ExecutableCommand, - event::{self, Event, KeyCode, KeyModifiers}, - terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, + ExecutableCommand, event::{self, Event, KeyCode, KeyModifiers}, terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; use figlet_rs::Toilet; use ratatui::{ - Terminal, - backend::CrosstermBackend, - layout::{Alignment, Constraint, Direction, Layout}, - style::{Color, Style}, - widgets::Paragraph, + Terminal, backend::CrosstermBackend, layout::{Alignment, Constraint, Direction, Layout}, style::{Color, Style}, widgets::{Block, Borders, Clear, Paragraph}, }; const SECONDS_PER_MIN: i64 = 60; @@ -37,6 +31,7 @@ struct Termato { is_running: bool, duration_in_secs: i64, time_remaining_in_sec: i64, + show_help: bool, } impl Termato { @@ -49,6 +44,7 @@ impl Termato { is_running: false, duration_in_secs: work_mins * SECONDS_PER_MIN, time_remaining_in_sec: break_mins * SECONDS_PER_MIN, + show_help: false, } } @@ -97,8 +93,14 @@ impl Termato { self.duration_in_secs = self.work_mins * SECONDS_PER_MIN; self.time_remaining_in_sec = self.duration_in_secs; } + + fn toggle_help(&mut self) { + self.show_help = !self.show_help; + } } +// TODO: I think we can refactor this to be more like iced.rs +// basically implement an elem like approach. fn main() -> Result<(), io::Error> { enable_raw_mode()?; stdout().execute(EnterAlternateScreen)?; @@ -122,11 +124,11 @@ fn main() -> Result<(), io::Error> { loop { terminal.draw(|f| { let size = f.area(); - + let minutes = termato.time_remaining_in_sec / SECONDS_PER_MIN; let seconds = termato.time_remaining_in_sec % SECONDS_PER_MIN; let time_str = format!("{:02}:{:02}", minutes, seconds); - + let time_color = match termato.state { TimerState::Work => Color::Red, TimerState::Break => Color::Green, @@ -155,6 +157,52 @@ fn main() -> Result<(), io::Error> { .alignment(Alignment::Center); f.render_widget(timer_paragraph, vertical_layout[1]); + + if termato.show_help { + let popup_width = 44; + let popup_height = 7; + + if size.width >= popup_width && size.height >= popup_height { + let popup_vertical = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length((size.height.saturating_sub(popup_height)) / 2), + Constraint::Length(popup_height), + Constraint::Min(1), + ]) + .split(size); + + let popup_horizontal = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Length((size.width.saturating_sub(popup_width)) / 2), + Constraint::Length(popup_width), + Constraint::Min(1), + ]) + .split(popup_vertical[1]); + + let popup_area = popup_horizontal[1]; + + let help_block = Block::default() + .title(" Controls ") + .borders(Borders::ALL) + .border_style(Style::default().fg(Color::DarkGray)); + + let help_content = Paragraph::new( + "[Space] Pause / Resume\n\ + [S] Switch Mode\n\ + [R] Reset Timer\n\ + [?] Toggle Help\n\ + [Q] Quit", + ) + .block(help_block) + .style(Style::default().fg(Color::DarkGray)) + .alignment(Alignment::Left); + + f.render_widget(Clear, popup_area); + f.render_widget(help_content, popup_area); + } + } })?; if event::poll(Duration::from_millis(100))? @@ -167,6 +215,7 @@ fn main() -> Result<(), io::Error> { KeyCode::Char(' ') => termato.toggle_running(), KeyCode::Char('s') | KeyCode::Char('S') => termato.toggle_mode(), KeyCode::Char('r') | KeyCode::Char('R') => termato.reset(), + KeyCode::Char('?') => termato.toggle_help(), _ => {} } } @@ -311,4 +360,17 @@ mod tests { assert_eq!(termato.duration_in_secs, 60); assert_eq!(termato.time_remaining_in_sec, 60); } + + #[test] + fn termato_toggle_help_when_called_it_should_toggle_help() { + let mut termato = Termato::new(1, 1); + + termato.toggle_help(); + + assert!(termato.show_help); + + termato.toggle_help(); + + assert!(!termato.show_help); + } }