Add docs and split state.rs

master
Alex Selimov 4 weeks ago
parent 21ba0d9882
commit 1d8b050a3d

@ -7,13 +7,13 @@ use crate::{
utility::Op, 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<dyn Error>> { pub fn event_key_handler(app: &mut ApplicationState) -> Result<(), Box<dyn Error>> {
if event::poll(std::time::Duration::from_millis(100))? { if event::poll(std::time::Duration::from_millis(100))? {
if let event::Event::Key(key_event) = event::read()? { if let event::Event::Key(key_event) = event::read()? {
match key_event.code { match key_event.code {
// Escape to exit KeyCode::Esc | KeyCode::Char('q') => app.should_quit = true,
KeyCode::Esc => app.should_quit = true,
KeyCode::Char('a') => app.insert_task(app.selected_col, Task::new_test_task()), KeyCode::Char('a') => app.insert_task(app.selected_col, Task::new_test_task()),
KeyCode::Char('h') => app.update_selected_column(&Op::Decrement), KeyCode::Char('h') => app.update_selected_column(&Op::Decrement),
KeyCode::Char('l') => app.update_selected_column(&Op::Increment), KeyCode::Char('l') => app.update_selected_column(&Op::Increment),

@ -27,10 +27,14 @@ fn main() -> Result<(), Box<dyn Error>> {
3 3
}; };
// Initialize application state // Initialize application state and terminal
let mut app_state = ApplicationState::new(num_columns); 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 // Clean up terminal state
terminal::disable_raw_mode()?; ratatui::restore();
Ok(()) Ok(())
} }

@ -0,0 +1,4 @@
mod state;
mod task;
pub use crate::state::state::ApplicationState;
pub use crate::state::task::Task;

@ -1,5 +1,7 @@
use crate::utility::Op; use crate::utility::Op;
/// Struct which holds the current application state.
/// In essence this reprents the data associated with the Kanban board.
#[derive(Debug)] #[derive(Debug)]
pub struct ApplicationState { pub struct ApplicationState {
pub columns: usize, pub columns: usize,
@ -11,24 +13,8 @@ pub struct ApplicationState {
pub selected_item: usize, pub selected_item: usize,
} }
#[derive(Debug, Clone)]
pub struct Task {
pub title: String,
pub notes: String,
pub tags: Vec<String>,
}
impl Task {
pub fn new_test_task() -> Self {
Task {
title: "This is a test".to_string(),
notes: "".to_string(),
tags: vec![],
}
}
}
impl ApplicationState { impl ApplicationState {
/// Initialize the application state from the number of columns
pub fn new(columns: usize) -> Self { pub fn new(columns: usize) -> Self {
ApplicationState { ApplicationState {
columns, columns,
@ -59,7 +45,7 @@ impl ApplicationState {
self.col_idx[col] == self.col_idx[col + 1] 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) { pub fn insert_task(&mut self, col: usize, task: Task) {
// Compute the index of the new item // Compute the index of the new item
let idx = self.col_idx[col + 1]; let idx = self.col_idx[col + 1];
@ -74,6 +60,7 @@ impl ApplicationState {
self.selected_item = idx; self.selected_item = idx;
} }
/// Remove a task from the board
pub fn remove_task(&mut self, col: usize, item: usize) { pub fn remove_task(&mut self, col: usize, item: usize) {
self.tasks.remove(item); self.tasks.remove(item);
for start_idx in self.col_idx[col + 1..].iter_mut() { 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 /// 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) { pub fn move_task(&mut self, col: usize, item: usize, op: &Op, in_col: bool) {
if in_col { 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 let new_idx = op
.apply(item) .apply(item)
.max(self.col_idx[col]) .max(self.col_idx[col])
@ -107,6 +96,7 @@ impl ApplicationState {
self.tasks.swap(item, new_idx); self.tasks.swap(item, new_idx);
self.selected_item = new_idx; self.selected_item = new_idx;
} else { } else {
// Move a task to the end of either the next column or previous column
match op { match op {
Op::Decrement => { Op::Decrement => {
if self.selected_col > 0 { if self.selected_col > 0 {

@ -0,0 +1,18 @@
/// Struct representing a single task
#[derive(Debug, Clone)]
pub struct Task {
pub title: String,
pub notes: String,
pub tags: Vec<String>,
}
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![],
}
}
}

@ -6,7 +6,7 @@ use ratatui::{
prelude::CrosstermBackend, prelude::CrosstermBackend,
style::{Color, Style}, style::{Color, Style},
widgets::{Block, Borders, List, ListItem, Paragraph, Wrap}, widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},
Terminal, DefaultTerminal, Terminal,
}; };
use crate::{ use crate::{
@ -15,17 +15,11 @@ use crate::{
widgets::blocks::{basic_block, highlighted_border_block, highlighted_item_block}, widgets::blocks::{basic_block, highlighted_border_block, highlighted_item_block},
}; };
pub fn run(app_state: &mut ApplicationState) -> Result<(), Box<dyn Error>> { /// Main running function for the application that sets up the interaction loop
// Set up terminal pub fn run(
let mut stdout = io::stdout(); mut terminal: DefaultTerminal,
terminal::enable_raw_mode()?; app_state: &mut ApplicationState,
stdout.execute(terminal::Clear(terminal::ClearType::All))?; ) -> Result<(), Box<dyn Error>> {
stdout.execute(terminal::EnterAlternateScreen)?;
// Initialize the terminal with the Crossterm backend
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// Start the main loop for rendering // Start the main loop for rendering
loop { loop {
terminal.draw(|f| { terminal.draw(|f| {
@ -41,8 +35,8 @@ pub fn run(app_state: &mut ApplicationState) -> Result<(), Box<dyn Error>> {
.constraints::<&Vec<Constraint>>(constraints.as_ref()) .constraints::<&Vec<Constraint>>(constraints.as_ref())
.split(size); .split(size);
// Render a item blocks in each column
for (i, chunk) in chunks.iter().enumerate() { 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 { let block = if i == app_state.selected_col {
highlighted_border_block(i) highlighted_border_block(i)
} else { } else {
@ -54,6 +48,8 @@ pub fn run(app_state: &mut ApplicationState) -> Result<(), Box<dyn Error>> {
if nitems == 0 { if nitems == 0 {
continue; continue;
} }
// Set up sub layout which holds each item
let constraints: Vec<Constraint> = let constraints: Vec<Constraint> =
vec![Constraint::Max(5); nitems].into_iter().collect(); vec![Constraint::Max(5); nitems].into_iter().collect();
let sub_chunks = Layout::default() let sub_chunks = Layout::default()
@ -64,6 +60,7 @@ pub fn run(app_state: &mut ApplicationState) -> Result<(), Box<dyn Error>> {
for ((task, highlight), sub_chunk) in for ((task, highlight), sub_chunk) in
zip(app_state.get_col_slice(i), sub_chunks.iter()) zip(app_state.get_col_slice(i), sub_chunks.iter())
{ {
// Set up each item and style the selected item
let style = if highlight { let style = if highlight {
Style::default().bg(Color::Blue).fg(Color::DarkGray) Style::default().bg(Color::Blue).fg(Color::DarkGray)
} else { } else {

@ -5,6 +5,7 @@ pub enum Op {
} }
impl Op { impl Op {
/// Apply the operator to a usize. We use a saturating subtraction to avoid panicking
pub fn apply(&self, val: usize) -> usize { pub fn apply(&self, val: usize) -> usize {
match &self { match &self {
Op::Decrement => val.saturating_sub(1), Op::Decrement => val.saturating_sub(1),

Loading…
Cancel
Save