aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.rs92
-rw-r--r--src/lib.rs10
-rw-r--r--src/main.rs117
-rw-r--r--src/ui.rs91
-rw-r--r--src/unit_of_measurement.rs116
5 files changed, 227 insertions, 199 deletions
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<Tab>,
+}
+
+impl Iterator for TabIter {
+ type Item = Tab;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<String>,
rows: Vec<Row>,
selected_row: usize,
@@ -19,20 +84,16 @@ impl App {
&self.rows
}
- pub fn tab_titles(&self) -> &[&str] {
+ pub fn tab_titles(&self) -> &Vec<String> {
&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<dyn Error>> {
@@ -51,8 +50,16 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, 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<B: Backend>(terminal: &mut Terminal<B>, 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<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<(
KeyCode::Char('H') => app.previous_tab(),
_ => {}
},
- _ => unreachable!(),
}
}
}
}
fn ui<B: Backend>(f: &mut Frame<B>, 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<Constraint> {
+ 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::<i32>() {
+ 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
+ }
}
}