footer (#3)
This commit is contained in:
parent
b1e9b027a2
commit
a9e573122d
61
src/app.rs
61
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
|
||||
|
||||
71
src/footer.rs
Normal file
71
src/footer.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user