StatefulWidgets (#7)
* trait EventHandler * StatefulWidget: AppWidget, CountdownWidget * StatefulWidget: TimerWidget
This commit is contained in:
parent
4f66ea86d4
commit
cbb6a60ee9
92
src/app.rs
92
src/app.rs
@ -1,11 +1,15 @@
|
||||
use crate::{
|
||||
clock::{self, Clock},
|
||||
constants::TICK_VALUE_MS,
|
||||
events::{Event, Events},
|
||||
events::{Event, EventHandler, Events},
|
||||
terminal::Terminal,
|
||||
utils::center,
|
||||
widgets::{
|
||||
countdown::Countdown, footer::Footer, header::Header, pomodoro::Pomodoro, timer::Timer,
|
||||
countdown::{Countdown, CountdownWidget},
|
||||
footer::Footer,
|
||||
header::Header,
|
||||
pomodoro::Pomodoro,
|
||||
timer::{Timer, TimerWidget},
|
||||
},
|
||||
};
|
||||
use color_eyre::Result;
|
||||
@ -13,7 +17,7 @@ use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::{KeyCode, KeyEvent},
|
||||
layout::{Constraint, Layout, Rect},
|
||||
widgets::{Block, Widget},
|
||||
widgets::{Block, StatefulWidget, Widget},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@ -34,8 +38,8 @@ pub struct App {
|
||||
content: Content,
|
||||
mode: Mode,
|
||||
show_menu: bool,
|
||||
clock_countdown: Clock<clock::Countdown>,
|
||||
clock_timer: Clock<clock::Timer>,
|
||||
countdown: Countdown,
|
||||
timer: Timer,
|
||||
}
|
||||
|
||||
impl Default for App {
|
||||
@ -44,11 +48,14 @@ impl Default for App {
|
||||
mode: Mode::Running,
|
||||
content: Content::Countdown,
|
||||
show_menu: false,
|
||||
clock_countdown: Clock::<clock::Countdown>::new(
|
||||
10 * 60 * 1000, /* 10min in milliseconds */
|
||||
TICK_VALUE_MS,
|
||||
countdown: Countdown::new(
|
||||
"Countdown".into(),
|
||||
Clock::<clock::Countdown>::new(
|
||||
10 * 60 * 1000, /* 10min in milliseconds */
|
||||
TICK_VALUE_MS,
|
||||
),
|
||||
),
|
||||
clock_timer: Clock::<clock::Timer>::new(0, TICK_VALUE_MS),
|
||||
timer: Timer::new("Timer".into(), Clock::<clock::Timer>::new(0, TICK_VALUE_MS)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,13 +68,15 @@ impl App {
|
||||
pub async fn run(&mut self, mut terminal: Terminal, mut events: Events) -> Result<()> {
|
||||
while self.is_running() {
|
||||
if let Some(event) = events.next().await {
|
||||
match self.content {
|
||||
Content::Countdown => self.countdown.update(event.clone()),
|
||||
Content::Timer => self.timer.update(event.clone()),
|
||||
_ => {}
|
||||
};
|
||||
match event {
|
||||
Event::Render | Event::Resize(_, _) => {
|
||||
Event::Render | Event::Resize => {
|
||||
self.draw(&mut terminal)?;
|
||||
}
|
||||
Event::Tick => {
|
||||
self.tick();
|
||||
}
|
||||
Event::Key(key) => self.handle_key_event(key),
|
||||
_ => {}
|
||||
}
|
||||
@ -84,73 +93,48 @@ impl App {
|
||||
match key.code {
|
||||
KeyCode::Char('q') | KeyCode::Esc => self.mode = Mode::Quit,
|
||||
KeyCode::Char('c') => self.content = Content::Countdown,
|
||||
KeyCode::Char('s') => self.toggle(),
|
||||
KeyCode::Char('t') => self.content = Content::Timer,
|
||||
KeyCode::Char('p') => self.content = Content::Pomodoro,
|
||||
KeyCode::Char('m') => self.show_menu = !self.show_menu,
|
||||
KeyCode::Char('r') => self.reset(),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn draw(&self, terminal: &mut Terminal) -> Result<()> {
|
||||
fn draw(&mut self, terminal: &mut Terminal) -> Result<()> {
|
||||
terminal.draw(|frame| {
|
||||
frame.render_widget(self, frame.area());
|
||||
frame.render_stateful_widget(AppWidget, frame.area(), self);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn render_content(&self, area: Rect, buf: &mut Buffer) {
|
||||
struct AppWidget;
|
||||
|
||||
impl AppWidget {
|
||||
fn render_content(&self, area: Rect, buf: &mut Buffer, state: &mut App) {
|
||||
// center content
|
||||
let area = center(area, Constraint::Length(50), Constraint::Length(2));
|
||||
match self.content {
|
||||
Content::Timer => {
|
||||
Timer::new("Timer".into(), self.clock_timer.clone()).render(area, buf)
|
||||
}
|
||||
Content::Countdown => {
|
||||
Countdown::new("Countdown".into(), self.clock_countdown.clone()).render(area, buf)
|
||||
}
|
||||
match state.content {
|
||||
Content::Timer => TimerWidget.render(area, buf, &mut state.timer),
|
||||
Content::Countdown => CountdownWidget.render(area, buf, &mut state.countdown),
|
||||
Content::Pomodoro => Pomodoro::new("Pomodoro".into()).render(area, buf),
|
||||
};
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
match self.content {
|
||||
Content::Timer => self.clock_timer.reset(),
|
||||
Content::Countdown => self.clock_countdown.reset(),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn toggle(&mut self) {
|
||||
match self.content {
|
||||
Content::Timer => self.clock_timer.toggle_pause(),
|
||||
Content::Countdown => self.clock_countdown.toggle_pause(),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn tick(&mut self) {
|
||||
match self.content {
|
||||
Content::Timer => self.clock_timer.tick(),
|
||||
Content::Countdown => self.clock_countdown.tick(),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for &App {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
impl StatefulWidget for AppWidget {
|
||||
type State = App;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let vertical = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Fill(0),
|
||||
Constraint::Length(if self.show_menu { 2 } else { 1 }),
|
||||
Constraint::Length(if state.show_menu { 2 } else { 1 }),
|
||||
]);
|
||||
let [v0, v1, v4] = vertical.areas(area);
|
||||
|
||||
Block::new().render(area, buf);
|
||||
Header::new(true).render(v0, buf);
|
||||
self.render_content(v1, buf);
|
||||
Footer::new(self.show_menu, self.content).render(v4, buf);
|
||||
self.render_content(v1, buf, state);
|
||||
Footer::new(state.show_menu, state.content).render(v4, buf);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,13 +14,11 @@ enum StreamKey {
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
Init,
|
||||
Quit,
|
||||
Error,
|
||||
Tick,
|
||||
Render,
|
||||
Key(KeyEvent),
|
||||
Resize(u16, u16),
|
||||
Resize,
|
||||
}
|
||||
|
||||
pub struct Events {
|
||||
@ -69,10 +67,14 @@ fn crossterm_stream() -> Pin<Box<dyn Stream<Item = Event>>> {
|
||||
Ok(CrosstermEvent::Key(key)) if key.kind == KeyEventKind::Press => {
|
||||
Some(Event::Key(key))
|
||||
}
|
||||
Ok(CrosstermEvent::Resize(x, y)) => Some(Event::Resize(x, y)),
|
||||
Ok(CrosstermEvent::Resize(_, _)) => Some(Event::Resize),
|
||||
Err(_) => Some(Event::Error),
|
||||
_ => None,
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub trait EventHandler {
|
||||
fn update(&mut self, _: Event);
|
||||
}
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::KeyCode,
|
||||
layout::{Constraint, Layout, Rect},
|
||||
widgets::{Paragraph, Widget},
|
||||
widgets::{Paragraph, StatefulWidget, Widget},
|
||||
};
|
||||
|
||||
use crate::clock::{self, Clock};
|
||||
use crate::{
|
||||
clock::{self, Clock},
|
||||
events::{Event, EventHandler},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Countdown {
|
||||
headline: String,
|
||||
clock: Clock<clock::Countdown>,
|
||||
@ -18,13 +22,33 @@ impl Countdown {
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Countdown {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
let h = Paragraph::new(self.headline).centered();
|
||||
let c = Paragraph::new(self.clock.format()).centered();
|
||||
impl EventHandler for Countdown {
|
||||
fn update(&mut self, event: Event) {
|
||||
match event {
|
||||
Event::Tick => {
|
||||
self.clock.tick();
|
||||
}
|
||||
Event::Key(key) if key.code == KeyCode::Char('s') => {
|
||||
self.clock.toggle_pause();
|
||||
}
|
||||
Event::Key(key) if key.code == KeyCode::Char('r') => {
|
||||
self.clock.reset();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CountdownWidget;
|
||||
|
||||
impl StatefulWidget for &CountdownWidget {
|
||||
type State = Countdown;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let h = Paragraph::new(state.headline.clone()).centered();
|
||||
let c = Paragraph::new(state.clock.format()).centered();
|
||||
let [v1, v2] = Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area);
|
||||
|
||||
h.render(v1, buf);
|
||||
c.render(v2, buf)
|
||||
c.render(v2, buf);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
crossterm::event::KeyCode,
|
||||
layout::{Constraint, Layout, Rect},
|
||||
widgets::{Paragraph, Widget},
|
||||
widgets::{Paragraph, StatefulWidget, Widget},
|
||||
};
|
||||
|
||||
use crate::clock::{self, Clock};
|
||||
use crate::{
|
||||
clock::{self, Clock},
|
||||
events::{Event, EventHandler},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Timer {
|
||||
headline: String,
|
||||
clock: Clock<clock::Timer>,
|
||||
@ -18,10 +22,30 @@ impl Timer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Timer {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer) {
|
||||
let h = Paragraph::new(self.headline).centered();
|
||||
let c = Paragraph::new(self.clock.format()).centered();
|
||||
impl EventHandler for Timer {
|
||||
fn update(&mut self, event: Event) {
|
||||
match event {
|
||||
Event::Tick => {
|
||||
self.clock.tick();
|
||||
}
|
||||
Event::Key(key) if key.code == KeyCode::Char('s') => {
|
||||
self.clock.toggle_pause();
|
||||
}
|
||||
Event::Key(key) if key.code == KeyCode::Char('r') => {
|
||||
self.clock.reset();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TimerWidget;
|
||||
|
||||
impl StatefulWidget for TimerWidget {
|
||||
type State = Timer;
|
||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||
let h = Paragraph::new(state.headline.clone()).centered();
|
||||
let c = Paragraph::new(state.clock.format()).centered();
|
||||
let [v1, v2] = Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area);
|
||||
|
||||
h.render(v1, buf);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user