From ae1a48e5e8c90e4ce029e37fdb8a9ea4995217c7 Mon Sep 17 00:00:00 2001 From: "Jens K." <47693+sectore@users.noreply.github.com> Date: Wed, 25 Dec 2024 12:21:36 +0100 Subject: [PATCH] Progressbar (#25) * Progressbar * header * label * border --- src/app.rs | 27 ++++++++++++++++++++------ src/widgets.rs | 2 ++ src/widgets/clock.rs | 12 +++++++++++- src/widgets/footer.rs | 2 +- src/widgets/header.rs | 22 +++++++++++++++++++++ src/widgets/progressbar.rs | 39 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 src/widgets/header.rs create mode 100644 src/widgets/progressbar.rs diff --git a/src/app.rs b/src/app.rs index e1256f8..7e4487d 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,6 +8,7 @@ use crate::{ clock::{self, Clock, ClockArgs, Style}, countdown::{Countdown, CountdownWidget}, footer::Footer, + header::Header, pomodoro::{Mode as PomodoroMode, Pomodoro, PomodoroArgs, PomodoroWidget}, timer::{Timer, TimerWidget}, }, @@ -173,14 +174,22 @@ impl App { } } - fn clock_is_running(&mut self) -> bool { + fn clock_is_running(&self) -> bool { match self.content { - Content::Countdown => self.countdown.get_clock().clone().is_running(), - Content::Timer => self.timer.get_clock().clone().is_running(), + Content::Countdown => self.countdown.get_clock().is_running(), + Content::Timer => self.timer.get_clock().is_running(), Content::Pomodoro => self.pomodoro.get_clock().is_running(), } } + fn get_percentage_done(&self) -> Option { + match self.content { + Content::Countdown => Some(self.countdown.get_clock().get_percentage_done()), + Content::Timer => None, + Content::Pomodoro => Some(self.pomodoro.get_clock().get_percentage_done()), + } + } + fn handle_key_event(&mut self, key: KeyEvent) { debug!("Received key {:?}", key.code); match key.code { @@ -249,14 +258,20 @@ impl AppWidget { impl StatefulWidget for AppWidget { type State = App; fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { - let [v0, v1] = Layout::vertical([ + let [v0, v1, v2] = Layout::vertical([ + Constraint::Length(1), Constraint::Percentage(100), Constraint::Length(if state.show_menu { 4 } else { 1 }), ]) .areas(area); + // header + Header { + percentage: state.get_percentage_done(), + } + .render(v0, buf); // content - self.render_content(v0, buf, state); + self.render_content(v1, buf, state); // footer Footer { show_menu: state.show_menu, @@ -264,6 +279,6 @@ impl StatefulWidget for AppWidget { selected_content: state.content, edit_mode: state.is_edit_mode(), } - .render(v1, buf); + .render(v2, buf); } } diff --git a/src/widgets.rs b/src/widgets.rs index 7edf5e4..065b3d0 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -1,5 +1,7 @@ pub mod clock; pub mod countdown; pub mod footer; +pub mod header; pub mod pomodoro; +pub mod progressbar; pub mod timer; diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 4927641..220cfd6 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -393,12 +393,22 @@ impl Clock { } } - pub fn set_done(&mut self) { + fn set_done(&mut self) { if self.current_value.is_zero() { self.mode = Mode::Done; } } + pub fn get_percentage_done(&self) -> u16 { + let initial = self.initial_value.as_millis(); + let elapsed = self + .initial_value + .saturating_sub(self.current_value) + .as_millis(); + + (elapsed * 100 / initial) as u16 + } + pub fn edit_next(&mut self) { self.edit_mode_next(); } diff --git a/src/widgets/footer.rs b/src/widgets/footer.rs index ca14162..225eb69 100644 --- a/src/widgets/footer.rs +++ b/src/widgets/footer.rs @@ -36,7 +36,7 @@ impl Widget for Footer { .title( format! {"[m]enu {:} ", if self.show_menu {scrollbar::VERTICAL.end} else {scrollbar::VERTICAL.begin}}, ) - .border_set(border::DOUBLE) + .border_set(border::PLAIN) .render(border_area, buf); // show menu if self.show_menu { diff --git a/src/widgets/header.rs b/src/widgets/header.rs new file mode 100644 index 0000000..7c3e77e --- /dev/null +++ b/src/widgets/header.rs @@ -0,0 +1,22 @@ +use ratatui::{ + buffer::Buffer, + layout::Rect, + widgets::{Block, Borders, Widget}, +}; + +use crate::widgets::progressbar::Progressbar; + +#[derive(Debug, Clone)] +pub struct Header { + pub percentage: Option, +} + +impl Widget for Header { + fn render(self, area: Rect, buf: &mut Buffer) { + if let Some(percentage) = self.percentage { + Progressbar::new(percentage).render(area, buf); + } else { + Block::new().borders(Borders::TOP).render(area, buf); + } + } +} diff --git a/src/widgets/progressbar.rs b/src/widgets/progressbar.rs new file mode 100644 index 0000000..43b6ca1 --- /dev/null +++ b/src/widgets/progressbar.rs @@ -0,0 +1,39 @@ +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Layout, Rect}, + symbols::line, + text::Span, + widgets::Widget, +}; + +#[derive(Debug, Clone)] +pub struct Progressbar { + pub percentage: u16, +} + +impl Progressbar { + pub fn new(percentage: u16) -> Self { + Self { percentage } + } +} + +impl Widget for Progressbar { + fn render(self, area: Rect, buf: &mut Buffer) { + let label = Span::raw(format!(" {}% ", self.percentage)); + let [h0, area] = Layout::horizontal([ + Constraint::Length(label.width() as u16), + Constraint::Percentage(100), + ]) + .areas(area); + let [h1, h2] = + Layout::horizontal([Constraint::Percentage(self.percentage), Constraint::Fill(0)]) + .areas(area); + + // label + label.render(h0, buf); + // done + Span::from(line::THICK_HORIZONTAL.repeat(h1.width as usize)).render(h1, buf); + // rest + Span::from(line::HORIZONTAL.repeat(h2.width as usize)).render(h2, buf); + } +}