From d0c7b0a39ed6703940da4896d2caa79ad36590e0 Mon Sep 17 00:00:00 2001 From: sam-anthony Date: Wed, 9 Feb 2022 17:54:34 -0330 Subject: small tweaks --- Cargo.lock | 114 ++++++++++++++++++++++++++++++++++++------- Cargo.toml | 4 +- src/app.rs | 92 +++++++++++++++++++++++++++++------ src/lib.rs | 10 +--- src/main.rs | 117 ++++++++++++++++----------------------------- src/ui.rs | 91 +++++++++++++++++++++++++++++++++++ src/unit_of_measurement.rs | 116 +++++++------------------------------------- 7 files changed, 327 insertions(+), 217 deletions(-) create mode 100644 src/ui.rs diff --git a/Cargo.lock b/Cargo.lock index ce9c6e1..ae9b203 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,15 +22,31 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crossterm" -version = "0.20.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c" dependencies = [ "bitflags", "crossterm_winapi", "libc", "mio", - "parking_lot", + "parking_lot 0.11.2", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b75a27dc8d220f1f8521ea69cd55a34d720a200ebb3a624d9aa19193d3b432" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot 0.12.0", "signal-hook", "signal-hook-mio", "winapi", @@ -38,9 +54,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" dependencies = [ "winapi", ] @@ -56,15 +72,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.112" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -117,7 +133,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.1", ] [[package]] @@ -134,6 +160,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -187,22 +226,22 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "tui" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23" +checksum = "23ed0a32c88b039b73f1b6c5acbd0554bfa5b6be94467375fd947c4de3a02271" dependencies = [ "bitflags", "cassowary", - "crossterm", + "crossterm 0.22.1", "unicode-segmentation", "unicode-width", ] [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" @@ -214,7 +253,7 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" name = "volute" version = "0.1.0" dependencies = [ - "crossterm", + "crossterm 0.23.0", "tui", ] @@ -239,3 +278,46 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" diff --git a/Cargo.toml b/Cargo.toml index a37a388..f5ba083 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -crossterm = "0.20" -tui = { version="0.16", default-features=false, features=['crossterm'] } +crossterm = "0.23" +tui = "0.17" diff --git a/src/app.rs b/src/app.rs index 92f89c0..bc12812 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,11 +1,76 @@ use crate::input::{InputMode, InputParam, Row}; -pub const INPUT_TAB_INDEX: usize = 0; -pub const CONFIG_TAB_INDEX: usize = 1; +#[derive(Copy, Clone)] +pub enum Tab { + Const = 0, + Input = 1, + Config = 2, +} + +impl Tab { + fn next(&self) -> Self { + match self { + Self::Const => Self::Input, + Self::Input => Self::Config, + Self::Config => Self::Const, + } + } + + fn previous(&self) -> Self { + match self { + Self::Const => Self::Config, + Self::Input => Self::Const, + Self::Config => Self::Input, + } + } + + fn string(&self) -> String { + match self { + Self::Const => "Const".to_string(), + Self::Input => "Input".to_string(), + Self::Config => "Config".to_string(), + } + } +} + +impl IntoIterator for Tab { + type Item = Tab; + type IntoIter = TabIter; + + fn into_iter(self) -> Self::IntoIter { + TabIter { tab: Some(self) } + } +} + +pub struct TabIter { + tab: Option, +} + +impl Iterator for TabIter { + type Item = Tab; + + fn next(&mut self) -> Option { + match self.tab { + Some(Tab::Const) => { + self.tab = Some(Tab::Input); + Some(Tab::Const) + } + Some(Tab::Input) => { + self.tab = Some(Tab::Config); + Some(Tab::Input) + } + Some(Tab::Config) => { + self.tab = None; + Some(Tab::Config) + } + None => None, + } + } +} pub struct App { - pub tab_index: usize, - tab_titles: [&'static str; 2], + pub tab: Tab, + tab_titles: Vec, rows: Vec, selected_row: usize, @@ -19,20 +84,16 @@ impl App { &self.rows } - pub fn tab_titles(&self) -> &[&str] { + pub fn tab_titles(&self) -> &Vec { &self.tab_titles } pub fn next_tab(&mut self) { - self.tab_index = (self.tab_index + 1) % self.tab_titles.len(); + self.tab = self.tab.next(); } pub fn previous_tab(&mut self) { - if self.tab_index > 0 { - self.tab_index -= 1; - } else { - self.tab_index = self.tab_titles.len() - 1; - } + self.tab = self.tab.previous(); } pub fn next_row(&mut self) { @@ -67,8 +128,9 @@ impl App { pub fn remove_row(&mut self) { if self.rows.len() > 1 { self.rows.remove(self.selected_row); - if self.selected_row > 0 { - self.selected_row -= 1; + // If we remove the last row, the selected row will be out of range. + if self.selected_row >= self.rows.len() { + self.selected_row = self.rows.len() - 1; } } } @@ -93,8 +155,8 @@ impl App { impl Default for App { fn default() -> App { App { - tab_index: INPUT_TAB_INDEX, - tab_titles: ["Input", "Config"], + tab: Tab::Const, + tab_titles: Tab::Const.into_iter().map(|t| t.string()).collect(), rows: vec![Row::default()], selected_row: 0, selected_column: InputParam::Rpm(String::new()), diff --git a/src/lib.rs b/src/lib.rs index 1646e17..b33113b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,4 @@ pub mod app; pub mod input; +pub mod ui; pub mod unit_of_measurement; - -use crate::unit_of_measurement::{Pressure, Temperature, Volume}; - -const GAS_CONSTANT: f64 = 8.314472; -const MOLAR_MASS_OF_AIR: f64 = 0.0289647; // Kg/mol - -fn moles_from_gas_law(pres: Pressure, vol: Volume, temp: Temperature) -> f64 { - (pres.as_pascals() * vol.as_cubic_metres()) / (GAS_CONSTANT * temp.as_kelvin()) -} diff --git a/src/main.rs b/src/main.rs index f4163a8..207dde7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,18 +3,17 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use std::{error::Error, io, ptr}; +use std::{error::Error, io}; use tui::{ backend::{Backend, CrosstermBackend}, layout::{Constraint, Direction, Layout}, - style::{Color, Modifier, Style}, - text::Spans, - widgets::{self, Block, Borders, Cell, Paragraph, Table, Tabs, Widget}, + widgets::Paragraph, Frame, Terminal, }; use volute::{ - app::{App, CONFIG_TAB_INDEX, INPUT_TAB_INDEX}, + app::{App, Tab}, input::InputMode, + ui, }; fn main() -> Result<(), Box> { @@ -51,8 +50,16 @@ fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result<( terminal.draw(|f| ui(f, &app))?; if let Event::Key(key) = event::read()? { - match app.tab_index { - INPUT_TAB_INDEX => match app.input_mode { + match app.tab { + Tab::Const => match key.code { + KeyCode::Char('q') => { + return Ok(()); + } + KeyCode::Char('L') => app.next_tab(), + KeyCode::Char('H') => app.previous_tab(), + _ => {} + }, + Tab::Input => match app.input_mode { InputMode::Normal => match key.code { KeyCode::Char('q') => { return Ok(()); @@ -74,12 +81,16 @@ fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result<( KeyCode::Esc | KeyCode::Enter => { app.input_mode = InputMode::Normal; } - KeyCode::Char(c) => app.selected_input_param_mut().push(c), + KeyCode::Char(c) => { + if ('0'..'a').contains(&c) { + app.selected_input_param_mut().push(c); + } + } KeyCode::Backspace => app.selected_input_param_mut().pop(), _ => {} }, }, - CONFIG_TAB_INDEX => match key.code { + Tab::Config => match key.code { KeyCode::Char('q') => { return Ok(()); } @@ -87,87 +98,43 @@ fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result<( KeyCode::Char('H') => app.previous_tab(), _ => {} }, - _ => unreachable!(), } } } } fn ui(f: &mut Frame, app: &App) { - let titles = app.tab_titles().iter().map(|t| Spans::from(*t)).collect(); - let tabs = Tabs::new(titles) - .block(Block::default().borders(Borders::ALL).title("Tabs")) - .select(app.tab_index) - .highlight_style( - Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD), - ); - - match app.tab_index { - INPUT_TAB_INDEX => { + match app.tab { + Tab::Const => { let layout = Layout::default() .direction(Direction::Vertical) - .constraints( - [ - Constraint::Length(3), // Tabs - Constraint::Length(app.rows().len() as u16 + 3), // Input table - Constraint::Max(100), // Spacer - Constraint::Length(1), // Footer - ] - .as_ref(), - ) + .constraints(ui::constraints(app).as_ref()) .split(f.size()); - f.render_widget(tabs, layout[0]); - f.render_widget(input_table(app), layout[1]); - f.render_widget(footer(app), layout[3]); + f.render_widget(ui::tabs(app), layout[0]); + f.render_widget(Paragraph::new("Const Tab"), layout[1]); } - CONFIG_TAB_INDEX => { + Tab::Input => { let layout = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Length(3), Constraint::Min(3)].as_ref()) + .constraints(ui::constraints(app).as_ref()) .split(f.size()); - f.render_widget(tabs, layout[0]); - f.render_widget(Paragraph::new("Config tab"), layout[1]); - } - _ => unreachable!(), - } -} - -fn input_table(app: &App) -> impl Widget { - let rows = app.rows().iter().map(|row| { - let cells = row.iter().map(|item| { - if ptr::eq(item, app.selected_input_param()) { - Cell::from(item.string()).style(match app.input_mode { - InputMode::Normal => Style::default().fg(Color::Yellow), - InputMode::Insert => Style::default() - .fg(Color::Blue) - .add_modifier(Modifier::ITALIC), - }) - } else { - Cell::from(item.string()) - } - }); - widgets::Row::new(cells) - }); - - Table::new(rows) - .header(widgets::Row::new(vec!["rpm", "ve", "map"])) - .block(Block::default().borders(Borders::ALL).title("inputs")) - .widths(&[ - Constraint::Length(5), // rpm - Constraint::Length(3), // ve - Constraint::Length(3), // map - ]) -} + f.render_widget(ui::tabs(app), layout[0]); + f.render_widget(ui::footer(app), layout[3]); -fn footer(app: &App) -> impl Widget { - match app.input_mode { - InputMode::Normal => { - Paragraph::new("Normal").style(Style::default().fg(Color::Black).bg(Color::Yellow)) + let table_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) + .split(layout[1]); + f.render_widget(ui::input_table(app), table_layout[0]); + f.render_widget(ui::output_table(app), table_layout[1]); } - InputMode::Insert => { - Paragraph::new("Insert").style(Style::default().fg(Color::Black).bg(Color::Blue)) + Tab::Config => { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints(ui::constraints(app).as_ref()) + .split(f.size()); + f.render_widget(ui::tabs(app), layout[0]); + f.render_widget(Paragraph::new("Config tab"), layout[1]); } } } diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..8505018 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,91 @@ +use crate::{ + app::{App, Tab}, + input::InputMode, + unit_of_measurement::pressure::*, +}; +use std::ptr; +use tui::{ + layout::Constraint, + style::{Color, Modifier, Style}, + text::Spans, + widgets::{self, Block, Borders, Cell, Paragraph, Table, Tabs, Widget}, +}; + +pub fn constraints(app: &App) -> Vec { + match app.tab { + Tab::Const | Tab::Config => { + vec![Constraint::Length(3), Constraint::Length(3)] + } + Tab::Input => { + vec![ + Constraint::Length(3), // Tabs + Constraint::Length(app.rows().len() as u16 + 3), // tables + Constraint::Max(100), // Spacer + Constraint::Length(1), // Footer + ] + } + } +} + +pub fn tabs(app: &App) -> impl Widget + '_ { + let titles = app + .tab_titles() + .iter() + .map(|t| Spans::from(t.as_str())) + .collect(); + Tabs::new(titles) + .block(Block::default().borders(Borders::ALL).title("Tabs")) + .select(app.tab as usize) + .highlight_style( + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + ) +} + +pub fn input_table(app: &App) -> impl Widget { + let rows = app.rows().iter().map(|row| { + let cells = row.iter().map(|item| { + if ptr::eq(item, app.selected_input_param()) { + Cell::from(item.string()).style(match app.input_mode { + InputMode::Normal => Style::default().fg(Color::Yellow), + InputMode::Insert => Style::default() + .fg(Color::Blue) + .add_modifier(Modifier::ITALIC), + }) + } else { + Cell::from(item.string()) + } + }); + widgets::Row::new(cells) + }); + + Table::new(rows) + .header(widgets::Row::new(vec!["rpm", "ve", "map"])) + .block(Block::default().borders(Borders::ALL).title("inputs")) + .widths(&[ + Constraint::Length(5), // rpm + Constraint::Length(3), // ve + Constraint::Length(3), // map + ]) +} + +pub fn output_table(app: &App) -> impl Widget { + let map = match app.rows()[0].map.string().parse::() { + Ok(p) => Pressure::from_unit(PressureUnit::KiloPascal, p), + Err(_) => Pressure::default(), + }; + Paragraph::new(map.as_unit(PressureUnit::KiloPascal).to_string()) + .block(Block::default().title("map").borders(Borders::ALL)) +} + +pub fn footer(app: &App) -> impl Widget { + match app.input_mode { + InputMode::Normal => { + Paragraph::new("Normal").style(Style::default().fg(Color::Black).bg(Color::Yellow)) + } + InputMode::Insert => { + Paragraph::new("Insert").style(Style::default().fg(Color::Black).bg(Color::Blue)) + } + } +} diff --git a/src/unit_of_measurement.rs b/src/unit_of_measurement.rs index 03bb68c..30a6ff3 100644 --- a/src/unit_of_measurement.rs +++ b/src/unit_of_measurement.rs @@ -1,107 +1,23 @@ -use std::time::Duration; - -pub struct MassFlowRate { - pub mass: Mass, - pub duration: Duration, -} -impl MassFlowRate { - pub fn as_kilograms_per_minute(&self) -> f64 { - self.mass.as_kilograms() / (self.duration.as_secs() as f64 / 60.) - } - - pub fn as_pounds_per_minute(&self) -> f64 { - self.mass.as_pounds() / (self.duration.as_secs() as f64 / 60.) - } -} - -pub struct VolumetricFlowRate { - pub volume: Volume, - pub duration: Duration, -} -impl VolumetricFlowRate { - pub fn as_cubic_metres_per_second(&self) -> f64 { - self.volume.as_cubic_metres() / self.duration.as_secs() as f64 - } - - pub fn as_cubic_feet_per_minute(&self) -> f64 { - self.volume.as_cubic_feet() / (self.duration.as_secs() as f64 / 60.) - } -} - -#[derive(Default)] -pub struct Mass(f64); // Base unit is grams -impl Mass { - /* constructors */ - pub fn from_grams(grams: f64) -> Mass { - Mass(grams) - } - - pub fn from_kilograms(kilos: f64) -> Mass { - Mass(kilos / 1000.) +pub mod pressure { + pub enum PressureUnit { + Pascal = 1, // base unit. Every other variant will be a multiple of this. + KiloPascal = 1000, } - pub fn from_moles(moles: f64, molar_mass: f64) -> Mass { - let kilos = moles * molar_mass; - Mass::from_kilograms(kilos) + #[derive(Default)] + pub struct Pressure { + val: i32, // Base unit is pascals. } - /* metric */ - pub fn as_grams(&self) -> f64 { - self.0 - } - - pub fn as_kilograms(&self) -> f64 { - self.0 / 1000. - } - - /* imperial */ - pub fn as_pounds(&self) -> f64 { - self.0 * 0.002204623 - } -} - -pub struct Pressure(f64); // Base unit is pascals -impl Pressure { - pub fn from_pascals(pascals: f64) -> Self { - Self(pascals) - } - - pub fn as_pascals(&self) -> f64 { - self.0 - } -} - -#[derive(Default)] -pub struct Temperature(f64); // Base unit is kelvin -impl Temperature { - pub fn from_kelvin(kelvin: f64) -> Temperature { - Temperature(kelvin) - } - - pub fn as_kelvin(&self) -> f64 { - self.0 - } -} - -pub struct Volume(f64); // Base unit is cubic metres -impl Volume { - pub fn from_cubic_metres(cubic_metres: f64) -> Volume { - Volume(cubic_metres) - } - - pub fn from_cubic_centimetres(cubic_centimetres: f64) -> Volume { - Volume(cubic_centimetres / 1_000_000.) - } - - pub fn as_cubic_metres(&self) -> f64 { - self.0 - } - - pub fn as_cubic_centimetres(&self) -> f64 { - self.0 * 1_000_000. - } + impl Pressure { + pub fn from_unit(unit: PressureUnit, n: i32) -> Self { + Self { + val: n * unit as i32, + } + } - pub fn as_cubic_feet(&self) -> f64 { - self.0 * 35.3147 + pub fn as_unit(&self, unit: PressureUnit) -> i32 { + self.val / unit as i32 + } } } -- cgit v1.2.3