diff --git a/src/layout.rs b/src/layout.rs new file mode 100644 index 0000000..120f2ed --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,33 @@ +use tui::layout::*; +use tui::style::{Color, Style}; +use tui::widgets::*; +pub fn selectable_list<'a, C: AsRef>( + title: &'a str, + content: &'a [C], + selected: Option, +) -> SelectableList<'a> { + SelectableList::default() + .block( + Block::default() + .title(title) + .borders(Borders::TOP | Borders::RIGHT | Borders::LEFT), + ) + .items(content) + .select(selected) + .highlight_style(Style::default().fg(Color::LightGreen)) + .highlight_symbol(">") +} + +pub fn layout(r: Rect) -> Vec { + Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(40), + Constraint::Percentage(40), + Constraint::Percentage(20), + ] + .as_ref(), + ) + .split(r) +} diff --git a/src/listview.rs b/src/listview.rs new file mode 100644 index 0000000..5a97a36 --- /dev/null +++ b/src/listview.rs @@ -0,0 +1,56 @@ +use std::fmt; + +pub trait ListView { + // get properties of implementations + fn selection_pointer(&mut self) -> &mut usize; + fn list(&mut self) -> &mut Vec; + fn register(&mut self) -> &mut Option; + + // specific input handling + fn backspace(&mut self); + fn append_to_current(&mut self, chr: char); + fn normal_mode(&mut self); + fn toggle_current(&mut self); + + // selection manipulation + fn selection_up(&mut self) { + *self.selection_pointer() = self.selection_pointer().saturating_sub(1); + } + + fn selection_down(&mut self) { + *self.selection_pointer() = + (*self.selection_pointer() + 1).min(self.list().len().saturating_sub(1)); + } + + // adding/removing elements + fn insert(&mut self, item: T, position: Option) { + let pos = position.unwrap_or(*self.selection_pointer()); + if pos == self.list().len().saturating_sub(1) { + self.list().push(item); + *self.selection_pointer() = self.list().len() - 1; + } else { + self.list().insert(pos + 1, item); + *self.selection_pointer() = pos + 1; + } + } + + fn remove_current(&mut self) { + if self.list().is_empty() { + return; + } + let index = *self.selection_pointer(); + *self.selection_pointer() = index.min(self.list().len().saturating_sub(2)); + *self.register() = self.list().remove(index).into(); + } + + fn paste(&mut self) { + if let Some(item) = self.register().clone() { + self.insert(item, None); + } + } + + // printing + fn printable(&mut self) -> Vec { + self.list().iter().map(T::to_string).collect() + } +} diff --git a/src/main.rs b/src/main.rs index d0d606e..8bfe647 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use std::io; use termion::raw::IntoRawMode; use tui::backend::TermionBackend; use tui::Terminal; +mod layout; +mod listview; mod timesheet; mod todolist; mod tracc; diff --git a/src/timesheet.rs b/src/timesheet.rs index b4f013e..e5b78f5 100644 --- a/src/timesheet.rs +++ b/src/timesheet.rs @@ -1,4 +1,4 @@ -use super::tracc::ListView; +use super::listview::ListView; use itertools::Itertools; use serde::{Deserialize, Serialize}; use serde_json::from_reader; diff --git a/src/todolist.rs b/src/todolist.rs index d7a57f9..45a0aef 100644 --- a/src/todolist.rs +++ b/src/todolist.rs @@ -1,4 +1,4 @@ -use crate::tracc::ListView; +use crate::listview::ListView; use serde::{Deserialize, Serialize}; use serde_json::from_reader; use std::fmt; diff --git a/src/tracc.rs b/src/tracc.rs index 6aa7b34..1d5a2db 100644 --- a/src/tracc.rs +++ b/src/tracc.rs @@ -1,13 +1,12 @@ +use super::layout; +use super::listview::ListView; use super::timesheet::TimeSheet; use super::todolist::TodoList; use std::default::Default; -use std::fmt; use std::io::{self, Write}; use termion::event::Key; use termion::input::TermRead; use tui::backend::TermionBackend; -use tui::layout::*; -use tui::style::{Color, Style}; use tui::widgets::*; type Terminal = tui::Terminal>>; @@ -107,56 +106,32 @@ impl Tracc { } fn refresh(&mut self) -> Result<(), io::Error> { - fn selectable_list<'a, C: AsRef>( - title: &'a str, - content: &'a [C], - selected: Option, - ) -> SelectableList<'a> { - SelectableList::default() - .block( - Block::default() - .title(title) - .borders(Borders::TOP | Borders::RIGHT | Borders::LEFT), - ) - .items(content) - .select(selected) - .highlight_style(Style::default().fg(Color::LightGreen)) - .highlight_symbol(">") - } - - let printable_todos = self.todos.printable(); - let top_focus = Some(self.todos.selected).filter(|_| self.focus == Focus::Top); - let printable_times = self.times.printable(); - let bottom_focus = Some(self.times.selected).filter(|_| self.focus == Focus::Bottom); - let total_time = self.times.sum_as_str(); - let times = self.times.time_by_tasks(); + let summary_content = [Text::raw(format!( + "Sum for today: {}\n{}", + self.times.sum_as_str(), + self.times.time_by_tasks() + ))]; + let mut summary = Paragraph::new(summary_content.iter()) + .wrap(true) + .block(Block::default().borders(Borders::ALL)); + let todos = self.todos.printable(); + let mut todolist = layout::selectable_list( + " t r a c c ", + &todos, + Some(self.todos.selected).filter(|_| self.focus == Focus::Top), + ); + let times = self.times.printable(); + let mut timelist = layout::selectable_list( + " 🕑 ", + ×, + Some(self.times.selected).filter(|_| self.focus == Focus::Bottom), + ); self.terminal.draw(|mut frame| { - let size = frame.size(); - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Percentage(40), - Constraint::Percentage(40), - Constraint::Percentage(20), - ] - .as_ref(), - ) - .split(size); - selectable_list(" t r a c c ", &printable_todos, top_focus) - .render(&mut frame, chunks[0]); - selectable_list(" 🕑 ", &printable_times, bottom_focus).render(&mut frame, chunks[1]); - Paragraph::new( - [ - Text::raw(format!("Sum for today: {}\n", total_time)), - Text::raw(times), - ] - .iter(), - ) - .wrap(true) - .block(Block::default().borders(Borders::ALL)) - .render(&mut frame, chunks[2]); + let chunks = layout::layout(frame.size()); + todolist.render(&mut frame, chunks[0]); + timelist.render(&mut frame, chunks[1]); + summary.render(&mut frame, chunks[2]); })?; Ok(()) } @@ -178,58 +153,3 @@ fn persist_state(todos: &TodoList, times: &TimeSheet) { let times = serde_json::to_string(×.times).unwrap(); write(JSON_PATH_TIME, times); } - -pub trait ListView { - // get properties of implementations - fn selection_pointer(&mut self) -> &mut usize; - fn list(&mut self) -> &mut Vec; - fn register(&mut self) -> &mut Option; - - // specific input handling - fn backspace(&mut self); - fn append_to_current(&mut self, chr: char); - fn normal_mode(&mut self); - fn toggle_current(&mut self); - - // selection manipulation - fn selection_up(&mut self) { - *self.selection_pointer() = self.selection_pointer().saturating_sub(1); - } - - fn selection_down(&mut self) { - *self.selection_pointer() = - (*self.selection_pointer() + 1).min(self.list().len().saturating_sub(1)); - } - - // adding/removing elements - fn insert(&mut self, item: T, position: Option) { - let pos = position.unwrap_or(*self.selection_pointer()); - if pos == self.list().len().saturating_sub(1) { - self.list().push(item); - *self.selection_pointer() = self.list().len() - 1; - } else { - self.list().insert(pos + 1, item); - *self.selection_pointer() = pos + 1; - } - } - - fn remove_current(&mut self) { - if self.list().is_empty() { - return; - } - let index = *self.selection_pointer(); - *self.selection_pointer() = index.min(self.list().len().saturating_sub(2)); - *self.register() = self.list().remove(index).into(); - } - - fn paste(&mut self) { - if let Some(item) = self.register().clone() { - self.insert(item, None); - } - } - - // printing - fn printable(&mut self) -> Vec { - self.list().iter().map(T::to_string).collect() - } -}