This commit is contained in:
Jens K. 2024-11-28 21:57:01 +01:00 committed by GitHub
parent b1e9b027a2
commit a9e573122d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 103 additions and 32 deletions

View File

@ -2,40 +2,47 @@ use color_eyre::{eyre::Context, Result};
use crossterm::event; use crossterm::event;
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}, crossterm::event::{Event, KeyCode, KeyEventKind},
layout::{Constraint, Layout, Rect}, layout::{Constraint, Layout, Rect},
widgets::{Block, Paragraph, Widget}, widgets::{Block, Paragraph, Widget},
DefaultTerminal, Frame, DefaultTerminal, Frame,
}; };
use std::time::Duration;
use strum::{Display, EnumIter, FromRepr}; use strum::{Display, EnumIter, FromRepr};
use crate::footer::Footer;
use crate::pomodoro::Pomodoro; use crate::pomodoro::Pomodoro;
use crate::timer::Timer; use crate::timer::Timer;
use crate::{countdown::Countdown, utils::center}; use crate::{countdown::Countdown, utils::center};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode { enum Mode {
#[default]
Running, Running,
Quit, Quit,
} }
#[derive(Debug, Clone, Copy, Default, Display, EnumIter, FromRepr, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Display, EnumIter, FromRepr, PartialEq, Eq, PartialOrd, Ord)]
enum Content { pub enum Content {
#[default]
Countdown, Countdown,
Timer, Timer,
Pomodoro, Pomodoro,
} }
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct App { pub struct App {
content: Content, content: Content,
mode: Mode, mode: Mode,
show_menu: bool,
} }
impl App { 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<()> { pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
while self.is_running() { while self.is_running() {
terminal terminal
@ -56,41 +63,36 @@ impl App {
} }
fn handle_events(&mut self) -> Result<()> { fn handle_events(&mut self) -> Result<()> {
let timeout = Duration::from_secs_f64(1.0 / 50.0); if let Event::Key(key) = event::read()? {
if !event::poll(timeout)? { if key.kind != KeyEventKind::Press {
return Ok(()); return Ok(());
} }
match event::read()? {
Event::Key(key) if key.kind == KeyEventKind::Press => self.handle_key_press(key),
_ => {}
}
Ok(())
}
fn handle_key_press(&mut self, key: KeyEvent) {
match key.code { match key.code {
KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit, KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit,
KeyCode::Char('c') => self.content = Content::Countdown, KeyCode::Char('c') => self.content = Content::Countdown,
KeyCode::Char('t') => self.content = Content::Timer, KeyCode::Char('t') => self.content = Content::Timer,
KeyCode::Char('p') => self.content = Content::Pomodoro, KeyCode::Char('p') => self.content = Content::Pomodoro,
KeyCode::Char('m') => self.show_menu = !self.show_menu,
_ => {} _ => {}
}; };
} }
Ok(())
}
} }
impl Widget for &App { impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let vertical = Layout::vertical([ let vertical = Layout::vertical([
Constraint::Length(1), Constraint::Length(1),
Constraint::Min(0), Constraint::Fill(0),
Constraint::Length(1), Constraint::Length(if self.show_menu { 2 } else { 1 }),
]); ]);
let [header_area, content_area, footer_area] = vertical.areas(area); let [header_area, content_area, footer_area] = vertical.areas(area);
Block::new().render(area, buf); Block::new().render(area, buf);
self.render_header(header_area, buf); self.render_header(header_area, buf);
self.render_content(content_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) { fn render_header(&self, area: Rect, buf: &mut Buffer) {
Paragraph::new("tim:r").render(area, buf); 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) { fn render_content(&self, area: Rect, buf: &mut Buffer) {
// center content // center content

71
src/footer.rs Normal file
View File

@ -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<Content, String>,
}
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<Span> = 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);
}
}
}

View File

@ -1,5 +1,6 @@
mod app; mod app;
mod countdown; mod countdown;
mod footer;
mod pomodoro; mod pomodoro;
mod timer; mod timer;
mod utils; mod utils;
@ -10,7 +11,7 @@ use color_eyre::{eyre::Context, Result};
fn main() -> Result<()> { fn main() -> Result<()> {
color_eyre::install()?; color_eyre::install()?;
let terminal = ratatui::init(); 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(); ratatui::restore();
app_result app_result
} }