From 850c1d0234b2c1ae09a8f1c8f669e23a324fd644 Mon Sep 17 00:00:00 2001 From: mo8it Date: Tue, 9 Apr 2024 19:37:39 +0200 Subject: [PATCH] Add progress bar to list --- src/list.rs | 2 +- src/list/state.rs | 56 +++++++++++++++++++++++++++++++++------------ src/main.rs | 1 + src/progress_bar.rs | 41 +++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 15 deletions(-) create mode 100644 src/progress_bar.rs diff --git a/src/list.rs b/src/list.rs index d7fa05f..db83ea4 100644 --- a/src/list.rs +++ b/src/list.rs @@ -24,7 +24,7 @@ pub fn list(state_file: &mut StateFile, exercises: &[Exercise]) -> Result<()> { let mut ui_state = UiState::new(state_file, exercises); 'outer: loop { - terminal.draw(|frame| ui_state.draw(frame))?; + terminal.draw(|frame| ui_state.draw(frame).unwrap())?; let key = loop { match event::read()? { diff --git a/src/list/state.rs b/src/list/state.rs index dc9ff5f..7bfc163 100644 --- a/src/list/state.rs +++ b/src/list/state.rs @@ -1,12 +1,13 @@ +use anyhow::Result; use ratatui::{ layout::{Constraint, Rect}, style::{Style, Stylize}, text::Span, - widgets::{Block, Borders, HighlightSpacing, Row, Table, TableState}, + widgets::{Block, Borders, HighlightSpacing, Paragraph, Row, Table, TableState}, Frame, }; -use crate::{exercise::Exercise, state_file::StateFile}; +use crate::{exercise::Exercise, progress_bar::progress_bar, state_file::StateFile}; #[derive(Copy, Clone, PartialEq, Eq)] pub enum Filter { @@ -20,6 +21,7 @@ pub struct UiState<'a> { pub message: String, pub filter: Filter, exercises: &'a [Exercise], + progress: u16, selected: usize, table_state: TableState, last_ind: usize, @@ -28,16 +30,28 @@ pub struct UiState<'a> { impl<'a> UiState<'a> { pub fn with_updated_rows(mut self, state_file: &StateFile) -> Self { let mut rows_counter: usize = 0; + let mut progress: u16 = 0; let rows = self .exercises .iter() .zip(state_file.progress().iter().copied()) .enumerate() .filter_map(|(ind, (exercise, done))| { - match (self.filter, done) { - (Filter::Done, false) | (Filter::Pending, true) => return None, - _ => (), - } + let exercise_state = if done { + progress += 1; + + if self.filter == Filter::Pending { + return None; + } + + "DONE".green() + } else { + if self.filter == Filter::Done { + return None; + } + + "PENDING".yellow() + }; rows_counter += 1; @@ -47,12 +61,6 @@ impl<'a> UiState<'a> { Span::default() }; - let exercise_state = if done { - "DONE".green() - } else { - "PENDING".yellow() - }; - Some(Row::new([ next, exercise_state, @@ -66,6 +74,8 @@ impl<'a> UiState<'a> { self.last_ind = rows_counter.saturating_sub(1); self.select(self.selected.min(self.last_ind)); + self.progress = progress; + self } @@ -104,6 +114,7 @@ impl<'a> UiState<'a> { message: String::with_capacity(128), filter: Filter::None, exercises, + progress: 0, selected, table_state, last_ind: 0, @@ -140,7 +151,7 @@ impl<'a> UiState<'a> { self.select(self.last_ind); } - pub fn draw(&mut self, frame: &mut Frame) { + pub fn draw(&mut self, frame: &mut Frame) -> Result<()> { let area = frame.size(); frame.render_stateful_widget( @@ -149,11 +160,26 @@ impl<'a> UiState<'a> { x: 0, y: 0, width: area.width, - height: area.height - 1, + height: area.height - 3, }, &mut self.table_state, ); + frame.render_widget( + Paragraph::new(Span::raw(progress_bar( + self.progress, + self.exercises.len() as u16, + area.width, + )?)) + .block(Block::default().borders(Borders::BOTTOM)), + Rect { + x: 0, + y: area.height - 3, + width: area.width, + height: 2, + }, + ); + let message = if self.message.is_empty() { // Help footer. Span::raw( @@ -171,5 +197,7 @@ impl<'a> UiState<'a> { height: 1, }, ); + + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index f6c4c20..356b77c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod embedded; mod exercise; mod init; mod list; +mod progress_bar; mod run; mod state_file; mod verify; diff --git a/src/progress_bar.rs b/src/progress_bar.rs new file mode 100644 index 0000000..b4abbfc --- /dev/null +++ b/src/progress_bar.rs @@ -0,0 +1,41 @@ +use anyhow::{bail, Result}; +use std::fmt::Write; + +pub fn progress_bar(progress: u16, total: u16, line_width: u16) -> Result { + if progress > total { + bail!("The progress of the progress bar is higher than the maximum"); + } + + // "Progress: [".len() == 11 + // "] xxx/xxx".len() == 9 + // 11 + 9 = 20 + let wrapper_width = 20; + + // If the line width is too low for a progress bar, just show the ratio. + if line_width < wrapper_width + 4 { + return Ok(format!("Progress: {progress}/{total}")); + } + + let mut line = String::with_capacity(usize::from(line_width)); + line.push_str("Progress: ["); + + let remaining_width = line_width.saturating_sub(wrapper_width); + let filled = (remaining_width * progress) / total; + + for _ in 0..filled { + line.push('='); + } + + if filled < remaining_width { + line.push('>'); + } + + for _ in 0..(remaining_width - filled).saturating_sub(1) { + line.push(' '); + } + + line.write_fmt(format_args!("] {progress:>3}/{total:<3}")) + .unwrap(); + + Ok(line) +}