From a9e573122d36a2b60d543d10ea6765a0e40c6860 Mon Sep 17 00:00:00 2001 From: "Jens K." <47693+sectore@users.noreply.github.com> Date: Thu, 28 Nov 2024 21:57:01 +0100 Subject: [PATCH] footer (#3) --- src/app.rs | 61 ++++++++++++++++++++++--------------------- src/footer.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 ++- 3 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 src/footer.rs diff --git a/src/app.rs b/src/app.rs index 2a6842a..d3b6615 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,40 +2,47 @@ use color_eyre::{eyre::Context, Result}; use crossterm::event; use ratatui::{ buffer::Buffer, - crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}, + crossterm::event::{Event, KeyCode, KeyEventKind}, layout::{Constraint, Layout, Rect}, widgets::{Block, Paragraph, Widget}, DefaultTerminal, Frame, }; -use std::time::Duration; use strum::{Display, EnumIter, FromRepr}; +use crate::footer::Footer; use crate::pomodoro::Pomodoro; use crate::timer::Timer; use crate::{countdown::Countdown, utils::center}; -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Mode { - #[default] Running, Quit, } -#[derive(Debug, Clone, Copy, Default, Display, EnumIter, FromRepr, PartialEq, Eq)] -enum Content { - #[default] +#[derive(Debug, Clone, Copy, Display, EnumIter, FromRepr, PartialEq, Eq, PartialOrd, Ord)] +pub enum Content { Countdown, Timer, Pomodoro, } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct App { content: Content, mode: Mode, + show_menu: bool, } impl App { + pub const fn new() -> Self { + Self { + content: Content::Countdown, + mode: Mode::Running, + show_menu: false, + } + } + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { while self.is_running() { terminal @@ -56,41 +63,36 @@ impl App { } fn handle_events(&mut self) -> Result<()> { - let timeout = Duration::from_secs_f64(1.0 / 50.0); - if !event::poll(timeout)? { - return Ok(()); - } - match event::read()? { - Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_key_press(key), - _ => {} + if let Event::Key(key) = event::read()? { + if key.kind != KeyEventKind::Press { + return Ok(()); + } + match key.code { + KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit, + KeyCode::Char('c') => self.content = Content::Countdown, + KeyCode::Char('t') => self.content = Content::Timer, + KeyCode::Char('p') => self.content = Content::Pomodoro, + KeyCode::Char('m') => self.show_menu = !self.show_menu, + _ => {} + }; } Ok(()) } - - fn handle_key_press(&mut self, key: KeyEvent) { - match key.code { - KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit, - KeyCode::Char('c') => self.content = Content::Countdown, - KeyCode::Char('t') => self.content = Content::Timer, - KeyCode::Char('p') => self.content = Content::Pomodoro, - _ => {} - }; - } } impl Widget for &App { fn render(self, area: Rect, buf: &mut Buffer) { let vertical = Layout::vertical([ Constraint::Length(1), - Constraint::Min(0), - Constraint::Length(1), + Constraint::Fill(0), + Constraint::Length(if self.show_menu { 2 } else { 1 }), ]); let [header_area, content_area, footer_area] = vertical.areas(area); Block::new().render(area, buf); self.render_header(header_area, buf); self.render_content(content_area, buf); - self.render_footer(footer_area, buf); + Footer::new(self.show_menu, self.content).render(footer_area, buf); } } @@ -98,9 +100,6 @@ impl App { fn render_header(&self, area: Rect, buf: &mut Buffer) { Paragraph::new("tim:r").render(area, buf); } - fn render_footer(&self, area: Rect, buf: &mut Buffer) { - Paragraph::new("footer").render(area, buf); - } fn render_content(&self, area: Rect, buf: &mut Buffer) { // center content diff --git a/src/footer.rs b/src/footer.rs new file mode 100644 index 0000000..7a48ccc --- /dev/null +++ b/src/footer.rs @@ -0,0 +1,71 @@ +use std::collections::BTreeMap; + +use crate::app::Content; +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Layout, Rect}, + style::{Modifier, Style}, + symbols, + text::{Line, Span}, + widgets::{Block, Borders, Widget}, +}; + +#[derive(Debug, Clone)] +pub struct Footer { + show_menu: bool, + selected_content: Content, + content_labels: BTreeMap, +} + +impl Footer { + pub fn new(show_menu: bool, selected_content: Content) -> Self { + Self { + show_menu, + selected_content, + content_labels: BTreeMap::from([ + (Content::Countdown, "[c]ountdown".into()), + (Content::Timer, "[t]imer".into()), + (Content::Pomodoro, "[p]omodoro".into()), + ]), + } + } +} + +impl Widget for Footer { + fn render(self, area: Rect, buf: &mut Buffer) { + let [border_area, menu_area] = + Layout::vertical([Constraint::Length(1), Constraint::Fill(0)]).areas(area); + Block::new() + .borders(Borders::TOP) + .title(format! {"[m]enu {:} ", if self.show_menu {"↓"} else {"↑"}}) + .border_set(symbols::border::DOUBLE) + .render(border_area, buf); + // show menu + if self.show_menu { + let [title_area, labels_area] = + Layout::horizontal([Constraint::Length(12), Constraint::Fill(0)]).areas(menu_area); + + Span::styled("screens", Style::default().add_modifier(Modifier::BOLD)) + .render(title_area, buf); + let spans: Vec = self + .content_labels + .iter() + .enumerate() + .map(|(index, (content, label))| { + let mut style = Style::default(); + // Add space for all except last + let label = if index < self.content_labels.len() - 1 { + format!("{} ", label) + } else { + label.into() + }; + if *content == self.selected_content { + style = style.add_modifier(Modifier::BOLD); + } + Span::styled(label, style) + }) + .collect(); + Line::from(spans).render(labels_area, buf); + } + } +} diff --git a/src/main.rs b/src/main.rs index 4ce6f4a..a6b95b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod app; mod countdown; +mod footer; mod pomodoro; mod timer; mod utils; @@ -10,7 +11,7 @@ use color_eyre::{eyre::Context, Result}; fn main() -> Result<()> { color_eyre::install()?; let terminal = ratatui::init(); - let app_result = App::default().run(terminal).context("app loop failed"); + let app_result = App::new().run(terminal).context("app loop failed"); ratatui::restore(); app_result }