Compare commits

...

1 Commits

Author SHA1 Message Date
scoobybejesus 23ae25a1d5 Refactor some ui while upgrading tui partway. 2023-10-23 00:53:59 -04:00
5 changed files with 106 additions and 86 deletions

22
Cargo.lock generated
View File

@ -313,12 +313,6 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]] [[package]]
name = "endian-type" name = "endian-type"
version = "0.1.2" version = "0.1.2"
@ -441,15 +435,6 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "itertools"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.9" version = "1.0.9"
@ -990,15 +975,12 @@ dependencies = [
[[package]] [[package]]
name = "tui" name = "tui"
version = "0.8.0" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b871b61f4c4b81e630215cd12e0ec29953d4545898e21a9e023b7520a74a9f9" checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"cassowary", "cassowary",
"either",
"itertools",
"log",
"termion 1.5.5", "termion 1.5.5",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",

View File

@ -26,7 +26,7 @@ rust_decimal_macros = "1.32.0"
chrono = { version = "0.4.31", features = ["serde"] } chrono = { version = "0.4.31", features = ["serde"] }
structopt = "0.2.10" structopt = "0.2.10"
rustyline = "12.0.0" rustyline = "12.0.0"
tui = { version = "0.8", optional = true, features = ['termion'] } tui = { version = "0.14", optional = true, features = ['termion'] }
termion = { version = "2.0.1", optional = true } termion = { version = "2.0.1", optional = true }
dotenv = "0.15.0" dotenv = "0.15.0"

View File

@ -7,6 +7,7 @@ use std::collections::HashMap;
use crptls::transaction::{Transaction, ActionRecord}; use crptls::transaction::{Transaction, ActionRecord};
use crptls::account::{Account, RawAccount}; use crptls::account::{Account, RawAccount};
use crptls::core_functions::ImportProcessParameters; use crptls::core_functions::ImportProcessParameters;
use tui::widgets::ListState;
use crate::export::{export_csv, export_je, export_txt}; use crate::export::{export_csv, export_je, export_txt};
@ -24,36 +25,51 @@ pub (crate) const REPORTS: [&'static str; 11] = [
"11. TXT: Bookkeeping journal entries", "11. TXT: Bookkeeping journal entries",
]; ];
pub struct ListState<I> { pub struct StatefulList<I> {
pub items: Vec<I>, pub items: Vec<I>,
pub selected: usize, pub state: ListState,
} }
impl<I> ListState<I> { impl<T> StatefulList<T> {
fn new(items: Vec<I>) -> ListState<I> { fn new(items: Vec<T>) -> StatefulList<T> {
ListState { items, selected: 0 } StatefulList { items, state: ListState::default() }
} }
fn select_previous(&mut self) { fn select_previous(&mut self) {
let i = match self.state.selected() {
if self.selected > 0 { Some(i) => {
self.selected -= 1; if i == 0 {
self.items.len() - 1
} else {
i - 1
} }
} }
None => 0,
};
self.state.select(Some(i));
}
fn select_next(&mut self) { fn select_next(&mut self) {
let i = match self.state.selected() {
Some(i) => {
if i >= self.items.len() - 1 {
0
} else {
i + 1
}
}
None => 0,
};
self.state.select(Some(i));
}
if self.selected < self.items.len() - 1 {
self.selected += 1
}
}
} }
pub struct PrintWindow<'a> { pub struct PrintWindow<'a> {
pub title: &'a str, pub title: &'a str,
pub should_quit: bool, pub should_quit: bool,
pub tasks: ListState<&'a str>, pub tasks: StatefulList<&'a str>,
pub to_print_by_idx: Vec<usize>, pub to_print_by_idx: Vec<usize>,
pub to_print_by_title: Vec<&'a str>, pub to_print_by_title: Vec<&'a str>,
} }
@ -61,10 +77,13 @@ pub struct PrintWindow<'a> {
impl<'a> PrintWindow<'a> { impl<'a> PrintWindow<'a> {
pub fn new(title: &'a str) -> PrintWindow<'a> { pub fn new(title: &'a str) -> PrintWindow<'a> {
let mut tasks = StatefulList::new(REPORTS.to_vec());
tasks.state.select(Some(0));
PrintWindow { PrintWindow {
title, title,
should_quit: false, should_quit: false,
tasks: ListState::new(REPORTS.to_vec()), tasks,
to_print_by_idx: Vec::with_capacity(REPORTS.len()), to_print_by_idx: Vec::with_capacity(REPORTS.len()),
to_print_by_title: Vec::with_capacity(REPORTS.len()), to_print_by_title: Vec::with_capacity(REPORTS.len()),
} }
@ -78,7 +97,7 @@ impl<'a> PrintWindow<'a> {
self.tasks.select_next(); self.tasks.select_next();
} }
pub fn on_key(&mut self, c: char) { pub fn on_key(&mut self, c: char) -> Result<(), Box<dyn Error>> {
match c { match c {
@ -90,7 +109,7 @@ impl<'a> PrintWindow<'a> {
self.should_quit = true; self.should_quit = true;
} }
'x' => { 'x' => {
let selected = self.tasks.selected; let selected = self.tasks.state.selected().unwrap();
if self.to_print_by_idx.contains(&selected) {} else { if self.to_print_by_idx.contains(&selected) {} else {
self.to_print_by_idx.push(selected); self.to_print_by_idx.push(selected);
self.to_print_by_title.push(self.tasks.items[selected]) self.to_print_by_title.push(self.tasks.items[selected])
@ -99,7 +118,7 @@ impl<'a> PrintWindow<'a> {
self.tasks.select_next(); self.tasks.select_next();
} }
'd' => { 'd' => {
let selected_idx = self.tasks.selected; let selected_idx = self.tasks.state.selected().unwrap();
self.to_print_by_idx.retain(|&x| x != selected_idx ); self.to_print_by_idx.retain(|&x| x != selected_idx );
let selected_str = self.tasks.items[selected_idx]; let selected_str = self.tasks.items[selected_idx];
self.to_print_by_title.retain(|&x| x != selected_str ); self.to_print_by_title.retain(|&x| x != selected_str );
@ -107,6 +126,7 @@ impl<'a> PrintWindow<'a> {
} }
_ => {} _ => {}
} }
Ok(())
} }
fn change_vecs_to_chrono_order(vec: &mut Vec<usize>, strvec: &mut Vec<&str>) { fn change_vecs_to_chrono_order(vec: &mut Vec<usize>, strvec: &mut Vec<&str>) {

View File

@ -46,14 +46,14 @@ pub (crate) fn print_menu_tui(
loop { loop {
ui::draw(&mut terminal, &app)?; ui::draw(&mut terminal, &mut app)?;
if let Event::Input(key) = events.next()? { if let Event::Input(key) = events.next()? {
match key { match key {
Key::Char(c) => { Key::Char(c) => {
app.on_key(c); app.on_key(c)?;
} }
Key::Up => { Key::Up => {
app.on_up(); app.on_up();

View File

@ -5,30 +5,49 @@ use std::io;
use ::tui::Terminal; use ::tui::Terminal;
use ::tui::style::{Color, Modifier, Style}; use ::tui::style::{Color, Modifier, Style};
use ::tui::widgets::{Widget, Block, Borders, SelectableList, Text, Paragraph, List}; // use tui::widgets::ListState;
use ::tui::text::{Text, Span, Spans};
use tui::widgets::{Wrap, ListItem};
use ::tui::widgets::{
// Widget,
Block, Borders,
// SelectableList,
// Text,
Paragraph, List};
use ::tui::layout::{Layout, Constraint, Direction}; use ::tui::layout::{Layout, Constraint, Direction};
use ::tui::backend::Backend; use ::tui::backend::Backend;
use crate::mytui::app::{PrintWindow, REPORTS}; use crate::mytui::app::{PrintWindow, REPORTS};
pub fn draw<B: Backend>(terminal: &mut Terminal<B>, app: &PrintWindow) -> Result<(), io::Error> { pub fn draw<B: Backend>(terminal: &mut Terminal<B>, app: &mut PrintWindow) -> Result<(), io::Error> {
terminal.draw(|mut f| { terminal.draw(|f| {
let instructions = [ let instructions = vec![
Text::raw("\nPress '"), Spans::from(vec![Span::raw("")]),
Text::styled("x", Style::default().fg(Color::Cyan).modifier(Modifier::BOLD)), Spans::from(vec![
Text::raw("' to add the selected report to the list of reports to print/export.\n"), Span::raw("Press '"),
Text::raw("\nPress '"), Span::styled("x", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
Text::styled("d", Style::default().fg(Color::Yellow).modifier(Modifier::BOLD)), Span::raw("' to add the selected report to the list of reports to print/export."),
Text::raw("' to delete the selected report from the list of reports to print/export.\n"), ]),
Text::raw("\nPress '"), Spans::from(vec![
Text::styled("p", Style::default().fg(Color::Green).modifier(Modifier::BOLD)), Span::raw("Press '"),
Text::raw("' to print/export the selected reports.\n"), Span::styled("d", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),
Text::raw("\nPress '"), Span::raw("' to delete the selected report from the list of reports to print/export."),
Text::styled("q", Style::default().fg(Color::Red).modifier(Modifier::BOLD)), ]),
Text::raw("' to quit without printing.\n\n"), Spans::from(vec![
Span::raw("Press '"),
Span::styled("p", Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)),
Span::raw("' to print/export the selected reports."),
]),
Spans::from(vec![
Span::raw("Press '"),
Span::styled("q", Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)),
Span::raw("' to quit without printing."),
]),
Spans::from(vec![Span::raw("")]),
Spans::from(vec![Span::raw("")]),
]; ];
let rpts_to_prnt = app.to_print_by_title.iter().map(|&rpt_to_prnt| { let rpts_to_prnt = app.to_print_by_title.iter().map(|&rpt_to_prnt| {
Text::styled( Text::styled(
@ -40,53 +59,52 @@ pub fn draw<B: Backend>(terminal: &mut Terminal<B>, app: &PrintWindow) -> Result
let top_level_chunks = Layout::default() let top_level_chunks = Layout::default()
.constraints([ .constraints([
Constraint::Length(1), Constraint::Length(1),
Constraint::Length(2 + (instructions.len() as u16 / 3 * 2)), // 2 for title "Instructions", plus 3 TEXT array elements/instruction Constraint::Length(instructions.len() as u16), // 2 for title "Instructions", plus 3 TEXT array elements/instruction
Constraint::Length(REPORTS.len() as u16 + 2), Constraint::Length(REPORTS.len() as u16 + 2),
Constraint::Length(rpts_to_prnt.len() as u16 + 2), Constraint::Length(rpts_to_prnt.len() as u16 + 2),
Constraint::Length(1), Constraint::Length(1),
].as_ref()) ].as_ref())
.split(f.size()); .split(f.size());
Paragraph::new(instructions.iter()) let pg1 = Paragraph::new(instructions)
.block( .block(Block::default()
Block::default()
.borders(Borders::NONE) .borders(Borders::NONE)
.title("Instructions") .title("Instructions")
.title_style(Style::default().fg(Color::Blue).modifier(Modifier::BOLD).modifier(Modifier::UNDERLINED)), .title_style(Style::default().fg(Color::LightMagenta).add_modifier(Modifier::BOLD).add_modifier(Modifier::UNDERLINED)),
) )
.wrap(true) .wrap(Wrap {trim: true});
.render(&mut f, top_level_chunks[1]); f.render_widget(pg1, top_level_chunks[1]);
let level_2_chunks = Layout::default() let level_2_chunks = Layout::default()
.constraints([Constraint::Percentage(10), Constraint::Percentage(80),Constraint::Percentage(10),].as_ref()) .constraints([Constraint::Percentage(10), Constraint::Percentage(80),Constraint::Percentage(10),].as_ref())
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.split(top_level_chunks[2]); .split(top_level_chunks[2]);
SelectableList::default() let items: Vec<_> = app.tasks.items.iter().map(|i| ListItem::new(*i)).collect();
.block( // app.tasks.state.select(Some(0));
Block::default()
let items = List::new(items)
.block(Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.title("Reports available for exporting") .title("Reports available for exporting")
.title_style(Style::default().fg(Color::White).modifier(Modifier::BOLD).modifier(Modifier::UNDERLINED)) .title_style(Style::default().fg(Color::White).add_modifier(Modifier::BOLD).add_modifier(Modifier::UNDERLINED))
) )
.items(&app.tasks.items) .highlight_style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD))
.select(Some(app.tasks.selected)) .highlight_symbol(">");
.highlight_style(Style::default().fg(Color::Yellow).modifier(Modifier::BOLD)) f.render_stateful_widget(items, level_2_chunks[1], &mut app.tasks.state);
.highlight_symbol(">")
.render(&mut f, level_2_chunks[1]);
let level_2_chunks = Layout::default() let level_2_chunks = Layout::default()
.constraints([Constraint::Percentage(10), Constraint::Percentage(80),Constraint::Percentage(10),].as_ref()) .constraints([Constraint::Percentage(10), Constraint::Percentage(80),Constraint::Percentage(10),].as_ref())
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.split(top_level_chunks[3]); .split(top_level_chunks[3]);
List::new(rpts_to_prnt) let rpts_to_prnt: Vec<_> = app.to_print_by_title.iter().map(|i| ListItem::new(*i)).collect();
.block( let to_print = List::new(rpts_to_prnt)
Block::default() .block(Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.title("Reports to be exported") .title("Reports to be exported")
.title_style(Style::default().fg(Color::LightYellow).modifier(Modifier::BOLD).modifier(Modifier::UNDERLINED)) .title_style(Style::default().fg(Color::LightYellow).add_modifier(Modifier::BOLD).add_modifier(Modifier::UNDERLINED))
) );
.render(&mut f, level_2_chunks[1]); f.render_widget(to_print, level_2_chunks[1])
}) })
} }