From 1d8b050a3db504d080fd34db607d806a910527cb Mon Sep 17 00:00:00 2001 From: Alex Selimov Date: Sun, 24 Nov 2024 22:52:52 -0500 Subject: [PATCH] Add docs and split state.rs --- src/event_handler.rs | 6 +++--- src/main.rs | 10 +++++++--- src/state/mod.rs | 4 ++++ src/{ => state}/state.rs | 26 ++++++++------------------ src/state/task.rs | 18 ++++++++++++++++++ src/tui.rs | 23 ++++++++++------------- src/utility.rs | 1 + 7 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 src/state/mod.rs rename src/{ => state}/state.rs (90%) create mode 100644 src/state/task.rs diff --git a/src/event_handler.rs b/src/event_handler.rs index 8251457..089ec3d 100644 --- a/src/event_handler.rs +++ b/src/event_handler.rs @@ -7,13 +7,13 @@ use crate::{ utility::Op, }; -/// Handle all key events +/// Handle all key events. +/// This function maps any user key input to the corresponding application state update pub fn event_key_handler(app: &mut ApplicationState) -> Result<(), Box> { if event::poll(std::time::Duration::from_millis(100))? { if let event::Event::Key(key_event) = event::read()? { match key_event.code { - // Escape to exit - KeyCode::Esc => app.should_quit = true, + KeyCode::Esc | KeyCode::Char('q') => app.should_quit = true, KeyCode::Char('a') => app.insert_task(app.selected_col, Task::new_test_task()), KeyCode::Char('h') => app.update_selected_column(&Op::Decrement), KeyCode::Char('l') => app.update_selected_column(&Op::Increment), diff --git a/src/main.rs b/src/main.rs index 34faea1..8cb1251 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,10 +27,14 @@ fn main() -> Result<(), Box> { 3 }; - // Initialize application state + // Initialize application state and terminal let mut app_state = ApplicationState::new(num_columns); - run(&mut app_state)?; + let mut terminal = ratatui::init(); + terminal.clear()?; + + // Run main loop + run(terminal, &mut app_state)?; // Clean up terminal state - terminal::disable_raw_mode()?; + ratatui::restore(); Ok(()) } diff --git a/src/state/mod.rs b/src/state/mod.rs new file mode 100644 index 0000000..1800849 --- /dev/null +++ b/src/state/mod.rs @@ -0,0 +1,4 @@ +mod state; +mod task; +pub use crate::state::state::ApplicationState; +pub use crate::state::task::Task; diff --git a/src/state.rs b/src/state/state.rs similarity index 90% rename from src/state.rs rename to src/state/state.rs index 6685cff..5b0a713 100644 --- a/src/state.rs +++ b/src/state/state.rs @@ -1,5 +1,7 @@ use crate::utility::Op; +/// Struct which holds the current application state. +/// In essence this reprents the data associated with the Kanban board. #[derive(Debug)] pub struct ApplicationState { pub columns: usize, @@ -11,24 +13,8 @@ pub struct ApplicationState { pub selected_item: usize, } -#[derive(Debug, Clone)] -pub struct Task { - pub title: String, - pub notes: String, - pub tags: Vec, -} - -impl Task { - pub fn new_test_task() -> Self { - Task { - title: "This is a test".to_string(), - notes: "".to_string(), - tags: vec![], - } - } -} - impl ApplicationState { + /// Initialize the application state from the number of columns pub fn new(columns: usize) -> Self { ApplicationState { columns, @@ -59,7 +45,7 @@ impl ApplicationState { self.col_idx[col] == self.col_idx[col + 1] } - /// Placeholder testing function to insert a new task in a column + /// Insert a new task at the end of a column pub fn insert_task(&mut self, col: usize, task: Task) { // Compute the index of the new item let idx = self.col_idx[col + 1]; @@ -74,6 +60,7 @@ impl ApplicationState { self.selected_item = idx; } + /// Remove a task from the board pub fn remove_task(&mut self, col: usize, item: usize) { self.tasks.remove(item); for start_idx in self.col_idx[col + 1..].iter_mut() { @@ -100,6 +87,8 @@ impl ApplicationState { /// Move a task either between columns or within a column pub fn move_task(&mut self, col: usize, item: usize, op: &Op, in_col: bool) { if in_col { + // Move a task up or down in the list by swapping. Make sure it can't go past the end + // of the column or before the start of the column. let new_idx = op .apply(item) .max(self.col_idx[col]) @@ -107,6 +96,7 @@ impl ApplicationState { self.tasks.swap(item, new_idx); self.selected_item = new_idx; } else { + // Move a task to the end of either the next column or previous column match op { Op::Decrement => { if self.selected_col > 0 { diff --git a/src/state/task.rs b/src/state/task.rs new file mode 100644 index 0000000..e83a91b --- /dev/null +++ b/src/state/task.rs @@ -0,0 +1,18 @@ +/// Struct representing a single task +#[derive(Debug, Clone)] +pub struct Task { + pub title: String, + pub notes: String, + pub tags: Vec, +} + +impl Task { + /// Testing function to generate a new dummy task + pub fn new_test_task() -> Self { + Task { + title: "This is a test".to_string(), + notes: "".to_string(), + tags: vec![], + } + } +} diff --git a/src/tui.rs b/src/tui.rs index 9db469c..5729c31 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -6,7 +6,7 @@ use ratatui::{ prelude::CrosstermBackend, style::{Color, Style}, widgets::{Block, Borders, List, ListItem, Paragraph, Wrap}, - Terminal, + DefaultTerminal, Terminal, }; use crate::{ @@ -15,17 +15,11 @@ use crate::{ widgets::blocks::{basic_block, highlighted_border_block, highlighted_item_block}, }; -pub fn run(app_state: &mut ApplicationState) -> Result<(), Box> { - // Set up terminal - let mut stdout = io::stdout(); - terminal::enable_raw_mode()?; - stdout.execute(terminal::Clear(terminal::ClearType::All))?; - stdout.execute(terminal::EnterAlternateScreen)?; - - // Initialize the terminal with the Crossterm backend - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - +/// Main running function for the application that sets up the interaction loop +pub fn run( + mut terminal: DefaultTerminal, + app_state: &mut ApplicationState, +) -> Result<(), Box> { // Start the main loop for rendering loop { terminal.draw(|f| { @@ -41,8 +35,8 @@ pub fn run(app_state: &mut ApplicationState) -> Result<(), Box> { .constraints::<&Vec>(constraints.as_ref()) .split(size); - // Render a item blocks in each column for (i, chunk) in chunks.iter().enumerate() { + // Create columns and apply styling based on wehter the column is selected or not let block = if i == app_state.selected_col { highlighted_border_block(i) } else { @@ -54,6 +48,8 @@ pub fn run(app_state: &mut ApplicationState) -> Result<(), Box> { if nitems == 0 { continue; } + + // Set up sub layout which holds each item let constraints: Vec = vec![Constraint::Max(5); nitems].into_iter().collect(); let sub_chunks = Layout::default() @@ -64,6 +60,7 @@ pub fn run(app_state: &mut ApplicationState) -> Result<(), Box> { for ((task, highlight), sub_chunk) in zip(app_state.get_col_slice(i), sub_chunks.iter()) { + // Set up each item and style the selected item let style = if highlight { Style::default().bg(Color::Blue).fg(Color::DarkGray) } else { diff --git a/src/utility.rs b/src/utility.rs index e68db0e..57ab3e8 100644 --- a/src/utility.rs +++ b/src/utility.rs @@ -5,6 +5,7 @@ pub enum Op { } impl Op { + /// Apply the operator to a usize. We use a saturating subtraction to avoid panicking pub fn apply(&self, val: usize) -> usize { match &self { Op::Decrement => val.saturating_sub(1),