From a95df5aaa80da20f8ffac3160dde3de4a90d7712 Mon Sep 17 00:00:00 2001 From: Gavin Tran Date: Wed, 22 Mar 2023 23:10:15 -0400 Subject: [PATCH 1/6] Update UI --- src/input.rs | 105 +++++++----------------------- src/main.rs | 180 ++++++++++++++++++++++++--------------------------- 2 files changed, 107 insertions(+), 178 deletions(-) diff --git a/src/input.rs b/src/input.rs index 62d5b0f..c27337e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -18,93 +18,33 @@ use Element::{Br, F}; pub(crate) fn input_insert_mode(app_state: &State, state: &mut AppState, graph: &mut GridState) { for key_event in app_state.keyboard().last_key_events() { match key_event { - KeyEvent::Pressed(Key::B) => { - state.key = "B"; - update(state, graph, ComponentType::Element(Br)); - } - KeyEvent::Pressed(Key::C) => { - state.key = "C"; - update(state, graph, ComponentType::Element(C)); - } - KeyEvent::Pressed(Key::F) => { - state.key = "F"; - update(state, graph, ComponentType::Element(F)); - } - KeyEvent::Pressed(Key::H) => { - state.key = "H"; - update(state, graph, ComponentType::Element(H)); - } - KeyEvent::Pressed(Key::I) => { - state.key = "I"; - update(state, graph, ComponentType::Element(I)); - } - KeyEvent::Pressed(Key::L) => { - state.key = "L"; - update(state, graph, ComponentType::Element(Cl)); - } - KeyEvent::Pressed(Key::N) => { - state.key = "N"; - update(state, graph, ComponentType::Element(N)); - } - KeyEvent::Pressed(Key::O) => { - state.key = "O"; - update(state, graph, ComponentType::Element(O)); - } + KeyEvent::Pressed(Key::B) => update(state, graph, ComponentType::Element(Br)), + KeyEvent::Pressed(Key::C) => update(state, graph, ComponentType::Element(C)), + KeyEvent::Pressed(Key::F) => update(state, graph, ComponentType::Element(F)), + KeyEvent::Pressed(Key::H) => update(state, graph, ComponentType::Element(H)), + KeyEvent::Pressed(Key::I) => update(state, graph, ComponentType::Element(I)), + KeyEvent::Pressed(Key::L) => update(state, graph, ComponentType::Element(Cl)), + KeyEvent::Pressed(Key::N) => update(state, graph, ComponentType::Element(N)), + KeyEvent::Pressed(Key::O) => update(state, graph, ComponentType::Element(O)), KeyEvent::Pressed(Key::F5) => { graph.clear_all(); - state.key = "F5"; - update(state, graph, ComponentType::None); - } - KeyEvent::Pressed(Key::F7) => { - state.macros_enabled = !state.macros_enabled; - state.key = "F7"; - } - KeyEvent::Pressed(Key::F12) => { - state.debug = match debug_branches(graph) { - Ok(it) => it.to_string(), - Err(it) => it.to_string(), - }; - state.key = "F12"; - } - KeyEvent::Pressed(Key::Num1) => { - state.key = "1"; - update(state, graph, ComponentType::Order(Single)); - } - KeyEvent::Pressed(Key::Num2) => { - state.key = "2"; - update(state, graph, ComponentType::Order(Double)); - } - KeyEvent::Pressed(Key::Num3) => { - state.key = "3"; - update(state, graph, ComponentType::Order(Triple)); - } - KeyEvent::Pressed(Key::Backspace) => { - state.key = "Backspace"; update(state, graph, ComponentType::None); } - KeyEvent::Pressed(Key::Right) => { - graph.move_cursor(Direction::Right); - state.key = "→"; - } - KeyEvent::Pressed(Key::Left) => { - graph.move_cursor(Direction::Left); - state.key = "←"; - } - KeyEvent::Pressed(Key::Up) => { - graph.move_cursor(Direction::Up); - state.key = "↑"; - } - KeyEvent::Pressed(Key::Down) => { - graph.move_cursor(Direction::Down); - state.key = "↓"; - } + KeyEvent::Pressed(Key::F7) => state.macros_enabled = !state.macros_enabled, + KeyEvent::Pressed(Key::F12) => state.debug = match debug_branches(graph) { + Ok(it) => it.to_string(), + Err(it) => it.to_string(), + }, + KeyEvent::Pressed(Key::Num1) => update(state, graph, ComponentType::Order(Single)), + KeyEvent::Pressed(Key::Num2) => update(state, graph, ComponentType::Order(Double)), + KeyEvent::Pressed(Key::Num3) => update(state, graph, ComponentType::Order(Triple)), + KeyEvent::Pressed(Key::Backspace) => update(state, graph, ComponentType::None), + KeyEvent::Pressed(Key::Right) => graph.move_cursor(Direction::Right), + KeyEvent::Pressed(Key::Left) => graph.move_cursor(Direction::Left), + KeyEvent::Pressed(Key::Up) => graph.move_cursor(Direction::Up), + KeyEvent::Pressed(Key::Down) => graph.move_cursor(Direction::Down), KeyEvent::Pressed(Key::Esc) => state.mode = Mode::Normal, - _ => { - state.key = match key_event { - KeyEvent::Pressed(_) => "Pressed", - KeyEvent::Released(_) => "", - } - } + _ => {} } } } @@ -123,7 +63,6 @@ pub(crate) fn start_mode(app_state: &State, state: &mut AppState) { for key_event in app_state.keyboard().last_key_events() { if let KeyEvent::Pressed(_) = key_event { state.mode = Mode::Insert; - state.name = "".to_string(); } } } diff --git a/src/main.rs b/src/main.rs index 0006903..c0f9a10 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,10 +11,11 @@ use crate::molecule::Cell; use crate::spatial::{GridState, Invert}; use crate::Mode::Insert; use ruscii::app::{App, State}; -use ruscii::drawing::Pencil; +use ruscii::drawing::{Pencil, RectCharset}; use ruscii::spatial::Vec2; use ruscii::terminal::Color::{Cyan, Red, White}; -use ruscii::terminal::{Color, Window}; +use ruscii::terminal::{Color, Style, Window}; +use Mode::Normal; mod chain; mod groups; @@ -53,10 +54,9 @@ fn main() { Cell::None(_) => String::new(), }; } - Mode::Normal => { + Normal => { state.pos = "".to_string(); state.sym = "".to_string(); - state.key = ""; state.err = "".to_string(); input_view_mode(app_state, &mut state) } @@ -66,142 +66,132 @@ fn main() { let mut pencil = Pencil::new(window.canvas_mut()); // Grid and startup screen + pencil.set_origin(Vec2::xy(6, 0)); match state.mode { - Mode::Start => { - pencil - .draw_text( - "┌────┐ ", - Vec2::xy(graph.size.x + 2, graph.size.y / 2 + 2).inv(&graph), - ) - .draw_text( - "│ C │hem ", - Vec2::xy(graph.size.x + 2, graph.size.y / 2 + 1).inv(&graph), - ) - .draw_text( - "└────┼────┐ ", - Vec2::xy(graph.size.x + 2, graph.size.y / 2).inv(&graph), - ) - .draw_text( - " │ Cr │eator", - Vec2::xy(graph.size.x + 2, graph.size.y / 2 - 1).inv(&graph), - ) - .draw_text( - " └────┘ ", - Vec2::xy(graph.size.x + 2, graph.size.y / 2 - 2).inv(&graph), - ); - } - _ => { - for cell in graph.cells.iter().flatten() { - pencil.set_foreground(cell.color()).draw_text( - match &cell { - Cell::Atom(it) => it.symbol(), - Cell::Bond(it) => it.symbol(), - Cell::None(_) => match state.mode { - Mode::Insert => " • ", - Mode::Normal => " ", - _ => " ", - }, - }, - Vec2::xy(cell.pos().x * 3, cell.pos().y).inv(&graph), - ); - } - } + Mode::Start => draw_logo(&mut pencil, &graph, Vec2::y(2)), + _ => draw_grid(&mut pencil, &mut graph, Vec2::y(2), state.mode), } // Insert mode and cursor - if let Mode::Insert = state.mode { - pencil - .draw_text("-- INSERT MODE --", Vec2::xy(graph.size.x * 3 + 3, 1)) - .set_foreground(Cyan) - .draw_text( - "<", - Vec2::xy(graph.cursor.x * 3 - 1, graph.cursor.y).inv(&graph), - ) - .draw_text( - ">", - Vec2::xy(graph.cursor.x * 3 + 3, graph.cursor.y).inv(&graph), - ) - .set_foreground(White); + if let Insert = state.mode { + draw_insert_mode(&mut pencil, Vec2::xy(graph.size.x * 3 / 2, 1)); + draw_cursor(&mut pencil, &graph, Vec2::y(2)); + } else { + pencil.draw_rect(&RectCharset::simple_round_lines(), Vec2::xy(-1, 1), Vec2::xy(graph.size.x * 3 + 2, graph.size.y + 2)); } // Menu pencil - .draw_text( + .draw_center_text( match state.mode { Mode::Start => "", - _ => "ChemCreator", + _ => " ChemCreator ", }, - Vec2::xy(graph.size.x * 3 + 3, 0), - ) - .draw_text( - &format!("name | {}", state.name), - Vec2::xy(graph.size.x * 3 + 3, 2), - ) - .draw_text( - &format!(" pos | {}", state.pos), - Vec2::xy(graph.size.x * 3 + 3, 3), + Vec2::xy(graph.size.x * 3 / 2, if state.mode == Normal { 1 } else { 0 }), ) .draw_text( - &format!(" sym | {}", state.sym), - Vec2::xy(graph.size.x * 3 + 3, 4), + &state.pos.to_string(), + Vec2::xy(0, 1), ) .draw_text( - &format!( - " key | {}", - match state.mode { - Mode::Start => format!(" You're using version {version}. "), - _ => state.key.to_string(), - } - ), - Vec2::xy(graph.size.x * 3 + 3, 5), + &state.sym.to_string(), + Vec2::xy(graph.size.x * 3 - state.sym.len() as i32, 1), ) .set_foreground(Red) .draw_text(&state.err.to_string(), Vec2::xy(graph.size.x * 3 + 3, 7)) - .draw_text(&state.debug.to_string(), Vec2::xy(graph.size.x * 3 + 3, 8)); + .draw_text(&state.debug.to_string(), Vec2::xy(graph.size.x * 3 + 3, 8)) + .set_foreground(White); if state.macros_enabled && state.mode == Insert { pencil .set_foreground(Color::Yellow) .draw_text("Macro mode enabled.", Vec2::xy(graph.size.x * 3 + 3, 8)); } + + // Statistics + pencil + .move_origin(Vec2::xy(graph.size.x * 3 + 3, 1)) + .set_style(Style::Bold) + .draw_text( + &state.name.to_string(), + Vec2::zero(), + ) + .set_style(Style::Plain); }); } +fn draw_logo(pencil: &mut Pencil, graph: &GridState, pos: Vec2) { + pencil + .move_origin(pos) + .draw_text("┌────┐ ", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 + 2).inv(graph),) + .draw_text("│ C │hem ", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 + 1).inv(graph),) + .draw_text("└────┼────┐ ", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2).inv(graph),) + .draw_text(" │ Cr │eator", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 - 1).inv(graph),) + .draw_text(" └────┘ ", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 - 2).inv(graph),) + .move_origin(-pos); +} + +fn draw_grid(pencil: &mut Pencil, graph: &mut GridState, pos: Vec2, mode: Mode) { + pencil.move_origin(pos); + for cell in graph.cells.iter().flatten() { + pencil.set_foreground(cell.color()).draw_text( + match &cell { + Cell::Atom(it) => it.symbol(), + Cell::Bond(it) => it.symbol(), + Cell::None(_) => match mode { + Insert => " • ", + Normal => " ", + _ => " ", + }, + }, + Vec2::xy(cell.pos().x * 3, cell.pos().y).inv(graph), + ); + } + pencil.move_origin(-pos); +} + +fn draw_insert_mode(pencil: &mut Pencil, center: Vec2) { + pencil.draw_center_text("-- INSERT MODE --", center); +} + +fn draw_cursor(pencil: &mut Pencil, graph: &GridState, pos: Vec2) { + let color = *pencil.foreground(); + pencil + .move_origin(pos) + .set_foreground(Cyan) + .draw_text( + "<", + Vec2::xy(graph.cursor.x * 3 - 1, graph.cursor.y).inv(graph), + ) + .draw_text( + ">", + Vec2::xy(graph.cursor.x * 3 + 3, graph.cursor.y).inv(graph), + ) + .set_foreground(color) + .move_origin(-pos); +} + /// Represents the mode the app is in at a given time. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Default, Debug, PartialEq)] enum Mode { Insert, Normal, + #[default] Start, } /// Contains the running state of the app not including the grid. +#[derive(Default)] struct AppState { mode: Mode, name: String, pos: String, sym: String, - key: &'static str, err: String, debug: String, macros_enabled: bool, } -impl Default for AppState { - fn default() -> Self { - AppState { - mode: Mode::Start, - name: " ChemCreator ".to_string(), - pos: " Written in Rust by Gavin Tran. ".to_string(), - sym: " Press any key to start. ".to_string(), - key: "", // Overridden in Start mode to retain &str type - err: "".to_string(), - debug: "".to_string(), - macros_enabled: false, - } - } -} - #[cfg(test)] mod test_utils { use crate::molecule::BondOrder::Single; From bba76cf1de4e7b1f5bf6f540ea49dc34f78401f7 Mon Sep 17 00:00:00 2001 From: Gavin Tran Date: Wed, 22 Mar 2023 23:49:45 -0400 Subject: [PATCH 2/6] Add statistics --- src/main.rs | 41 ++++++++++++++++++++--- src/molecule.rs | 86 ++++++++++++++++++++++++++++++++----------------- src/spatial.rs | 11 +++++++ 3 files changed, 104 insertions(+), 34 deletions(-) diff --git a/src/main.rs b/src/main.rs index c0f9a10..8cfced2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,10 +5,11 @@ #![warn(missing_docs)] +use Color::Yellow; use crate::input::{input_insert_mode, input_view_mode, start_mode}; use crate::molecule::BondOrder::{Double, Single, Triple}; -use crate::molecule::Cell; -use crate::spatial::{GridState, Invert}; +use crate::molecule::{Cell, Element}; +use crate::spatial::{EnumAll, GridState, Invert}; use crate::Mode::Insert; use ruscii::app::{App, State}; use ruscii::drawing::{Pencil, RectCharset}; @@ -78,6 +79,9 @@ fn main() { draw_cursor(&mut pencil, &graph, Vec2::y(2)); } else { pencil.draw_rect(&RectCharset::simple_round_lines(), Vec2::xy(-1, 1), Vec2::xy(graph.size.x * 3 + 2, graph.size.y + 2)); + if let Mode::Start = state.mode { + pencil.draw_text(&format!(" {version} "), Vec2::xy(graph.size.x * 3 - version.len() as i32 - 3, graph.size.y + 2)); + } } // Menu @@ -104,19 +108,46 @@ fn main() { if state.macros_enabled && state.mode == Insert { pencil - .set_foreground(Color::Yellow) - .draw_text("Macro mode enabled.", Vec2::xy(graph.size.x * 3 + 3, 8)); + .set_foreground(Yellow) + .draw_text("Macro mode enabled.", Vec2::y(graph.size.y + 2)) + .set_foreground(White); } // Statistics pencil - .move_origin(Vec2::xy(graph.size.x * 3 + 3, 1)) + .move_origin(Vec2::xy(graph.size.x * 3 + 5, 1)) .set_style(Style::Bold) .draw_text( &state.name.to_string(), Vec2::zero(), ) .set_style(Style::Plain); + + if !matches!(state.mode, Normal) { + return + } + + let mut mass = 0.0; + + let mut missed = 0; + for (index, element) in Element::all().into_iter().enumerate() { + let count = graph.count(|it| it.is_atom() && it.unwrap_atom().element == element); + + if count == 0 { + missed += 1; + continue + } + + mass += element.mass() * count as f32; + + pencil + .set_foreground(element.color()) + .draw_text(element.symbol(), Vec2::y(2 + index - missed)) + .set_foreground(White) + .draw_text(&format!("| {}", count), Vec2::xy(6, 2 + index - missed)); + } + + pencil.draw_text(&format!("atomic weight | {:.3} amu", mass), Vec2::xy(15, 2)); }); } diff --git a/src/molecule.rs b/src/molecule.rs index 918b621..4feff6e 100644 --- a/src/molecule.rs +++ b/src/molecule.rs @@ -14,6 +14,7 @@ use std::cmp::Ordering; use std::fmt; use std::fmt::{Debug, Display, Formatter}; use std::str::FromStr; +use Element::{Br, Cl, F, I}; /// Represents a type of functional group on a molecule. #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -206,16 +207,7 @@ pub enum Cell { impl Cell { pub fn color(&self) -> Color { match &self { - Cell::Atom(it) => match it.element { - Element::Br => Xterm(1), - C => LightGrey, - Element::Cl => Green, - Element::F => Yellow, - Element::I => Magenta, - N => Blue, - O => Red, - _ => White, - }, + Cell::Atom(it) => it.element.color(), _ => White, } } @@ -277,16 +269,7 @@ pub struct Atom { impl Atom { pub const fn symbol(&self) -> &str { - match self.element { - Element::Br => "[Br]", - C => "[C]", - Element::Cl => "[Cl]", - Element::F => "[F]", - H => "[H]", - Element::I => "[I]", - N => "[N]", - O => "[O]", - } + self.element.symbol() } } @@ -318,23 +301,62 @@ impl Element { C => 4, N => 3, O => 2, - H | Element::Br | Element::Cl | Element::F | Element::I => 1, + H | Br | Cl | F | I => 1, } } /// Returns the single-character identifier of the [`Element`]. pub const fn id(&self) -> &str { match *self { - Element::Br => "B", + Br => "B", C => "C", - Element::Cl => "L", - Element::F => "F", + Cl => "L", + F => "F", H => "H", - Element::I => "I", + I => "I", N => "N", O => "O", } } + + pub const fn symbol(&self) -> &str { + match self { + Br => "[Br]", + C => "[C]", + Cl => "[Cl]", + F => "[F]", + H => "[H]", + I => "[I]", + N => "[N]", + O => "[O]", + } + } + + pub fn color(&self) -> Color { + match self { + Br => Xterm(1), + C => LightGrey, + Cl => Green, + F => Yellow, + I => Magenta, + N => Blue, + O => Red, + _ => White, + } + } + + pub fn mass(&self) -> f32 { + match self { + Br => 79.904, + C => 12.011, + Cl => 35.450, + F => 18.998, + H => 1.008, + I => 126.90, + N => 14.007, + O => 15.999, + } + } } impl Display for Element { @@ -343,12 +365,12 @@ impl Display for Element { f, "{}", match self { - Element::Br => "Bromine", + Br => "Bromine", C => "Carbon", - Element::Cl => "Chlorine", - Element::F => "Fluorine", + Cl => "Chlorine", + F => "Fluorine", H => "Hydrogen", - Element::I => "Iodine", + I => "Iodine", N => "Nitrogen", O => "Oxygen", } @@ -356,6 +378,12 @@ impl Display for Element { } } +impl EnumAll for Element { + fn all() -> Vec where Self: Sized { + vec![Br, C, Cl, F, H, I, N, O] + } +} + #[derive(Clone, Debug, PartialEq)] pub struct Bond { pub pos: Vec2, diff --git a/src/spatial.rs b/src/spatial.rs index c09ec70..ff553ff 100644 --- a/src/spatial.rs +++ b/src/spatial.rs @@ -192,6 +192,17 @@ impl GridState { .collect::>() } + pub fn count(&self, predicate: F) -> i32 + where + F: Fn(&Cell) -> bool + { + self.cells + .iter() + .flatten() + .filter(|&cell| predicate(cell)) + .count() as i32 + } + /// Determines if there are any [`Atom`]s that are horizontally adjacent to the current /// [`Cell`]. fn atom_adjacent(&self, pos: Vec2) -> bool { From eebdc51c19a337a4c8e1f6d57cea805df9881c11 Mon Sep 17 00:00:00 2001 From: Gavin Tran Date: Thu, 23 Mar 2023 00:06:46 -0400 Subject: [PATCH 3/6] Fix error not showing and add colored sym --- src/input.rs | 4 ++-- src/main.rs | 48 +++++++++++++++++++++++++++++------------------- src/molecule.rs | 17 ++++++++++++++--- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/input.rs b/src/input.rs index c27337e..35ccce4 100644 --- a/src/input.rs +++ b/src/input.rs @@ -85,7 +85,7 @@ pub(crate) fn update(state: &mut AppState, graph: &mut GridState, comp: Componen } (state.name, state.err) = match name_molecule(graph) { - Ok(it) => (it, "".to_string()), - Err(it) => ("unidentified".to_string(), it.to_string()), + Ok(it) => (it, false), + Err(it) => (it.to_string(), true), }; } diff --git a/src/main.rs b/src/main.rs index 8cfced2..17dc186 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use Color::Yellow; use crate::input::{input_insert_mode, input_view_mode, start_mode}; use crate::molecule::BondOrder::{Double, Single, Triple}; -use crate::molecule::{Cell, Element}; +use crate::molecule::{Cell, ComponentType, Element}; use crate::spatial::{EnumAll, GridState, Invert}; use crate::Mode::Insert; use ruscii::app::{App, State}; @@ -44,21 +44,13 @@ fn main() { .current_cell() .expect("current cell should be within bounds") { - Cell::Atom(it) => { - format!("Atom {}", it.symbol()) - } - Cell::Bond(it) => match it.order { - Single => "Bond 1x".to_string(), - Double => "Bond 2x".to_string(), - Triple => "Bond 3x".to_string(), - }, - Cell::None(_) => String::new(), + Cell::Atom(it) => ComponentType::Element(it.element), + Cell::Bond(it) => ComponentType::Order(it.order), + Cell::None(_) => ComponentType::None, }; } Normal => { state.pos = "".to_string(); - state.sym = "".to_string(); - state.err = "".to_string(); input_view_mode(app_state, &mut state) } Mode::Start => start_mode(app_state, &mut state), @@ -97,15 +89,31 @@ fn main() { &state.pos.to_string(), Vec2::xy(0, 1), ) - .draw_text( - &state.sym.to_string(), - Vec2::xy(graph.size.x * 3 - state.sym.len() as i32, 1), - ) .set_foreground(Red) - .draw_text(&state.err.to_string(), Vec2::xy(graph.size.x * 3 + 3, 7)) .draw_text(&state.debug.to_string(), Vec2::xy(graph.size.x * 3 + 3, 8)) .set_foreground(White); + let sym_type = match state.sym { + ComponentType::Element(_) => "Atom", + ComponentType::Order(_) => "Bond", + ComponentType::None => "", + }; + let sym_sym = match state.sym { + ComponentType::Element(it) => it.symbol().to_string(), + ComponentType::Order(Single) => "1x".to_string(), + ComponentType::Order(Double) => "2x".to_string(), + ComponentType::Order(Triple) => "3x".to_string(), + ComponentType::None => "".to_string(), + }; + pencil + .set_foreground(state.sym.color()) + .draw_text(&sym_sym, Vec2::xy(graph.size.x * 3 - sym_sym.len() as i32, 1)) + .set_foreground(White) + .draw_text( + sym_type, + Vec2::xy(graph.size.x * 3 - sym_type.len() as i32 - sym_sym.len() as i32 - 1, 1), + ); + if state.macros_enabled && state.mode == Insert { pencil .set_foreground(Yellow) @@ -117,10 +125,12 @@ fn main() { pencil .move_origin(Vec2::xy(graph.size.x * 3 + 5, 1)) .set_style(Style::Bold) + .set_foreground(if state.err { Red } else { White }) .draw_text( &state.name.to_string(), Vec2::zero(), ) + .set_foreground(White) .set_style(Style::Plain); if !matches!(state.mode, Normal) { @@ -217,8 +227,8 @@ struct AppState { mode: Mode, name: String, pos: String, - sym: String, - err: String, + sym: ComponentType, + err: bool, debug: String, macros_enabled: bool, } diff --git a/src/molecule.rs b/src/molecule.rs index 4feff6e..b58e556 100644 --- a/src/molecule.rs +++ b/src/molecule.rs @@ -13,6 +13,7 @@ use ruscii::terminal::Color::{Blue, Green, LightGrey, Magenta, Red, White, Xterm use std::cmp::Ordering; use std::fmt; use std::fmt::{Debug, Display, Formatter}; +use std::path::Component; use std::str::FromStr; use Element::{Br, Cl, F, I}; @@ -184,8 +185,8 @@ impl Display for Halogen { impl EnumAll for Halogen { fn all() -> Vec - where - Self: Sized, + where + Self: Sized, { vec![ Halogen::Fluorine, @@ -254,13 +255,23 @@ impl Cell { } } -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, Default, PartialEq)] pub enum ComponentType { Element(Element), Order(BondOrder), + #[default] None, } +impl ComponentType { + pub fn color(&self) -> Color { + match &self { + ComponentType::Element(it) => it.color(), + _ => White, + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct Atom { pub element: Element, From 981a8722feac3cdf40a57838ea2255e3c99968aa Mon Sep 17 00:00:00 2001 From: Gavin Tran Date: Thu, 23 Mar 2023 00:28:01 -0400 Subject: [PATCH 4/6] Add bond statistics --- src/main.rs | 73 ++++++++++++++++++++++++++++++++++--------------- src/molecule.rs | 9 +++++- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/main.rs b/src/main.rs index 17dc186..c8c42fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use Color::Yellow; use crate::input::{input_insert_mode, input_view_mode, start_mode}; use crate::molecule::BondOrder::{Double, Single, Triple}; -use crate::molecule::{Cell, ComponentType, Element}; +use crate::molecule::{Bond, BondOrientation, Cell, ComponentType, Element}; use crate::spatial::{EnumAll, GridState, Invert}; use crate::Mode::Insert; use ruscii::app::{App, State}; @@ -93,26 +93,28 @@ fn main() { .draw_text(&state.debug.to_string(), Vec2::xy(graph.size.x * 3 + 3, 8)) .set_foreground(White); - let sym_type = match state.sym { - ComponentType::Element(_) => "Atom", - ComponentType::Order(_) => "Bond", - ComponentType::None => "", - }; - let sym_sym = match state.sym { - ComponentType::Element(it) => it.symbol().to_string(), - ComponentType::Order(Single) => "1x".to_string(), - ComponentType::Order(Double) => "2x".to_string(), - ComponentType::Order(Triple) => "3x".to_string(), - ComponentType::None => "".to_string(), - }; - pencil - .set_foreground(state.sym.color()) - .draw_text(&sym_sym, Vec2::xy(graph.size.x * 3 - sym_sym.len() as i32, 1)) - .set_foreground(White) - .draw_text( - sym_type, - Vec2::xy(graph.size.x * 3 - sym_type.len() as i32 - sym_sym.len() as i32 - 1, 1), - ); + if matches!(state.mode, Insert) { + let sym_type = match state.sym { + ComponentType::Element(_) => "Atom", + ComponentType::Order(_) => "Bond", + ComponentType::None => "", + }; + let sym_sym = match state.sym { + ComponentType::Element(it) => it.symbol().to_string(), + ComponentType::Order(Single) => "1x".to_string(), + ComponentType::Order(Double) => "2x".to_string(), + ComponentType::Order(Triple) => "3x".to_string(), + ComponentType::None => "".to_string(), + }; + pencil + .set_foreground(state.sym.color()) + .draw_text(&sym_sym, Vec2::xy(graph.size.x * 3 - sym_sym.len() as i32, 1)) + .set_foreground(White) + .draw_text( + sym_type, + Vec2::xy(graph.size.x * 3 - sym_type.len() as i32 - sym_sym.len() as i32 - 1, 1), + ); + } if state.macros_enabled && state.mode == Insert { pencil @@ -122,6 +124,10 @@ fn main() { } // Statistics + if state.name.is_empty() { + return + } + pencil .move_origin(Vec2::xy(graph.size.x * 3 + 5, 1)) .set_style(Style::Bold) @@ -140,6 +146,7 @@ fn main() { let mut mass = 0.0; let mut missed = 0; + let mut final_index = 0; for (index, element) in Element::all().into_iter().enumerate() { let count = graph.count(|it| it.is_atom() && it.unwrap_atom().element == element); @@ -155,9 +162,31 @@ fn main() { .draw_text(element.symbol(), Vec2::y(2 + index - missed)) .set_foreground(White) .draw_text(&format!("| {}", count), Vec2::xy(6, 2 + index - missed)); + + final_index = 2 + index - missed; + } + + let mut missed = 0; + for (index, order) in [Double, Triple].into_iter().enumerate() { + let count = graph.count(|it| it.is_bond() && it.unwrap_bond().order == order); + + if count == 0 { + missed += 1; + continue + } + + pencil + .draw_text(Bond::new(Vec2::zero(), order, BondOrientation::Horiz).symbol(), Vec2::y(2 + final_index + index - missed)) + .draw_text(&format!("| {}", count), Vec2::xy(6, 2 + final_index + index - missed)); + } + + if state.err { + return } - pencil.draw_text(&format!("atomic weight | {:.3} amu", mass), Vec2::xy(15, 2)); + pencil + .draw_text(&format!("atomic weight | {:.3} amu", mass), Vec2::xy(15, 2)) + .draw_text(&format!("name length | {}", state.name.len()), Vec2::xy(15, 4)); }); } diff --git a/src/molecule.rs b/src/molecule.rs index b58e556..4b6b9ae 100644 --- a/src/molecule.rs +++ b/src/molecule.rs @@ -13,7 +13,6 @@ use ruscii::terminal::Color::{Blue, Green, LightGrey, Magenta, Red, White, Xterm use std::cmp::Ordering; use std::fmt; use std::fmt::{Debug, Display, Formatter}; -use std::path::Component; use std::str::FromStr; use Element::{Br, Cl, F, I}; @@ -234,6 +233,10 @@ impl Cell { matches!(self, Cell::Atom(_)) } + pub fn is_bond(&self) -> bool { + matches!(self, Cell::Bond(_)) + } + pub fn is_empty(&self) -> bool { !matches!(self, Cell::None(_)) } @@ -403,6 +406,10 @@ pub struct Bond { } impl Bond { + pub fn new(pos: Vec2, order: BondOrder, orient: BondOrientation) -> Bond { + Bond { pos, order, orient } + } + pub fn symbol(&self) -> &str { match (&self.order, &self.orient) { (Single, Horiz) => "———", From be5779c649882e3917095e7eb53fdc913deb95ce Mon Sep 17 00:00:00 2001 From: Gavin Tran Date: Thu, 23 Mar 2023 00:59:22 -0400 Subject: [PATCH 5/6] Add start message --- src/main.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index c8c42fa..fb7acb3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,7 +61,10 @@ fn main() { // Grid and startup screen pencil.set_origin(Vec2::xy(6, 0)); match state.mode { - Mode::Start => draw_logo(&mut pencil, &graph, Vec2::y(2)), + Mode::Start => { + draw_logo(&mut pencil, &graph, Vec2::y(2)); + draw_start_message(&mut pencil, Vec2::xy(graph.size.x * 3 + 5, 1), version); + }, _ => draw_grid(&mut pencil, &mut graph, Vec2::y(2), state.mode), } @@ -241,6 +244,17 @@ fn draw_cursor(pencil: &mut Pencil, graph: &GridState, pos: Vec2) { .move_origin(-pos); } +fn draw_start_message(pencil: &mut Pencil, pos: Vec2, version: &str) { + pencil + .move_origin(pos) + .draw_center_text("ChemCreator", Vec2::xy(20, 2)) + .draw_center_text("A text-based tool for", Vec2::xy(20, 4)) + .draw_center_text("identifying organic molecules.", Vec2::xy(20, 5)) + .draw_center_text("Written in Rust by Gavin Tran.", Vec2::xy(20, 7)) + .draw_center_text("Press any key to start.", Vec2::xy(20, 9)) + .move_origin(-pos); +} + /// Represents the mode the app is in at a given time. #[derive(Copy, Clone, Default, Debug, PartialEq)] enum Mode { From 5219a571fca01b71a63e03867263a89145998740 Mon Sep 17 00:00:00 2001 From: Gavin Tran Date: Thu, 23 Mar 2023 01:04:14 -0400 Subject: [PATCH 6/6] Apply rustfmt --- src/input.rs | 10 +++-- src/main.rs | 97 ++++++++++++++++++++++++++++++++++--------------- src/molecule.rs | 9 +++-- src/spatial.rs | 2 +- 4 files changed, 80 insertions(+), 38 deletions(-) diff --git a/src/input.rs b/src/input.rs index 35ccce4..69b4331 100644 --- a/src/input.rs +++ b/src/input.rs @@ -31,10 +31,12 @@ pub(crate) fn input_insert_mode(app_state: &State, state: &mut AppState, graph: update(state, graph, ComponentType::None); } KeyEvent::Pressed(Key::F7) => state.macros_enabled = !state.macros_enabled, - KeyEvent::Pressed(Key::F12) => state.debug = match debug_branches(graph) { - Ok(it) => it.to_string(), - Err(it) => it.to_string(), - }, + KeyEvent::Pressed(Key::F12) => { + state.debug = match debug_branches(graph) { + Ok(it) => it.to_string(), + Err(it) => it.to_string(), + } + } KeyEvent::Pressed(Key::Num1) => update(state, graph, ComponentType::Order(Single)), KeyEvent::Pressed(Key::Num2) => update(state, graph, ComponentType::Order(Double)), KeyEvent::Pressed(Key::Num3) => update(state, graph, ComponentType::Order(Triple)), diff --git a/src/main.rs b/src/main.rs index fb7acb3..18512e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,6 @@ #![warn(missing_docs)] -use Color::Yellow; use crate::input::{input_insert_mode, input_view_mode, start_mode}; use crate::molecule::BondOrder::{Double, Single, Triple}; use crate::molecule::{Bond, BondOrientation, Cell, ComponentType, Element}; @@ -16,6 +15,7 @@ use ruscii::drawing::{Pencil, RectCharset}; use ruscii::spatial::Vec2; use ruscii::terminal::Color::{Cyan, Red, White}; use ruscii::terminal::{Color, Style, Window}; +use Color::Yellow; use Mode::Normal; mod chain; @@ -63,8 +63,8 @@ fn main() { match state.mode { Mode::Start => { draw_logo(&mut pencil, &graph, Vec2::y(2)); - draw_start_message(&mut pencil, Vec2::xy(graph.size.x * 3 + 5, 1), version); - }, + draw_start_message(&mut pencil, Vec2::xy(graph.size.x * 3 + 5, 1)); + } _ => draw_grid(&mut pencil, &mut graph, Vec2::y(2), state.mode), } @@ -73,9 +73,19 @@ fn main() { draw_insert_mode(&mut pencil, Vec2::xy(graph.size.x * 3 / 2, 1)); draw_cursor(&mut pencil, &graph, Vec2::y(2)); } else { - pencil.draw_rect(&RectCharset::simple_round_lines(), Vec2::xy(-1, 1), Vec2::xy(graph.size.x * 3 + 2, graph.size.y + 2)); + pencil.draw_rect( + &RectCharset::simple_round_lines(), + Vec2::xy(-1, 1), + Vec2::xy(graph.size.x * 3 + 2, graph.size.y + 2), + ); if let Mode::Start = state.mode { - pencil.draw_text(&format!(" {version} "), Vec2::xy(graph.size.x * 3 - version.len() as i32 - 3, graph.size.y + 2)); + pencil.draw_text( + &format!(" {version} "), + Vec2::xy( + graph.size.x * 3 - version.len() as i32 - 3, + graph.size.y + 2, + ), + ); } } @@ -86,12 +96,12 @@ fn main() { Mode::Start => "", _ => " ChemCreator ", }, - Vec2::xy(graph.size.x * 3 / 2, if state.mode == Normal { 1 } else { 0 }), - ) - .draw_text( - &state.pos.to_string(), - Vec2::xy(0, 1), + Vec2::xy( + graph.size.x * 3 / 2, + if state.mode == Normal { 1 } else { 0 }, + ), ) + .draw_text(&state.pos.to_string(), Vec2::xy(0, 1)) .set_foreground(Red) .draw_text(&state.debug.to_string(), Vec2::xy(graph.size.x * 3 + 3, 8)) .set_foreground(White); @@ -111,11 +121,17 @@ fn main() { }; pencil .set_foreground(state.sym.color()) - .draw_text(&sym_sym, Vec2::xy(graph.size.x * 3 - sym_sym.len() as i32, 1)) + .draw_text( + &sym_sym, + Vec2::xy(graph.size.x * 3 - sym_sym.len() as i32, 1), + ) .set_foreground(White) .draw_text( sym_type, - Vec2::xy(graph.size.x * 3 - sym_type.len() as i32 - sym_sym.len() as i32 - 1, 1), + Vec2::xy( + graph.size.x * 3 - sym_type.len() as i32 - sym_sym.len() as i32 - 1, + 1, + ), ); } @@ -128,22 +144,19 @@ fn main() { // Statistics if state.name.is_empty() { - return + return; } pencil .move_origin(Vec2::xy(graph.size.x * 3 + 5, 1)) .set_style(Style::Bold) .set_foreground(if state.err { Red } else { White }) - .draw_text( - &state.name.to_string(), - Vec2::zero(), - ) + .draw_text(&state.name.to_string(), Vec2::zero()) .set_foreground(White) .set_style(Style::Plain); if !matches!(state.mode, Normal) { - return + return; } let mut mass = 0.0; @@ -155,7 +168,7 @@ fn main() { if count == 0 { missed += 1; - continue + continue; } mass += element.mass() * count as f32; @@ -175,32 +188,56 @@ fn main() { if count == 0 { missed += 1; - continue + continue; } pencil - .draw_text(Bond::new(Vec2::zero(), order, BondOrientation::Horiz).symbol(), Vec2::y(2 + final_index + index - missed)) - .draw_text(&format!("| {}", count), Vec2::xy(6, 2 + final_index + index - missed)); + .draw_text( + Bond::new(Vec2::zero(), order, BondOrientation::Horiz).symbol(), + Vec2::y(2 + final_index + index - missed), + ) + .draw_text( + &format!("| {}", count), + Vec2::xy(6, 2 + final_index + index - missed), + ); } if state.err { - return + return; } pencil .draw_text(&format!("atomic weight | {:.3} amu", mass), Vec2::xy(15, 2)) - .draw_text(&format!("name length | {}", state.name.len()), Vec2::xy(15, 4)); + .draw_text( + &format!("name length | {}", state.name.len()), + Vec2::xy(15, 4), + ); }); } fn draw_logo(pencil: &mut Pencil, graph: &GridState, pos: Vec2) { pencil .move_origin(pos) - .draw_text("┌────┐ ", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 + 2).inv(graph),) - .draw_text("│ C │hem ", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 + 1).inv(graph),) - .draw_text("└────┼────┐ ", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2).inv(graph),) - .draw_text(" │ Cr │eator", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 - 1).inv(graph),) - .draw_text(" └────┘ ", Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 - 2).inv(graph),) + .draw_text( + "┌────┐ ", + Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 + 2).inv(graph), + ) + .draw_text( + "│ C │hem ", + Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 + 1).inv(graph), + ) + .draw_text( + "└────┼────┐ ", + Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2).inv(graph), + ) + .draw_text( + " │ Cr │eator", + Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 - 1).inv(graph), + ) + .draw_text( + " └────┘ ", + Vec2::xy(graph.size.x * 3 / 2 - 8, graph.size.y / 2 - 2).inv(graph), + ) .move_origin(-pos); } @@ -244,7 +281,7 @@ fn draw_cursor(pencil: &mut Pencil, graph: &GridState, pos: Vec2) { .move_origin(-pos); } -fn draw_start_message(pencil: &mut Pencil, pos: Vec2, version: &str) { +fn draw_start_message(pencil: &mut Pencil, pos: Vec2) { pencil .move_origin(pos) .draw_center_text("ChemCreator", Vec2::xy(20, 2)) diff --git a/src/molecule.rs b/src/molecule.rs index 4b6b9ae..5164ca0 100644 --- a/src/molecule.rs +++ b/src/molecule.rs @@ -184,8 +184,8 @@ impl Display for Halogen { impl EnumAll for Halogen { fn all() -> Vec - where - Self: Sized, + where + Self: Sized, { vec![ Halogen::Fluorine, @@ -393,7 +393,10 @@ impl Display for Element { } impl EnumAll for Element { - fn all() -> Vec where Self: Sized { + fn all() -> Vec + where + Self: Sized, + { vec![Br, C, Cl, F, H, I, N, O] } } diff --git a/src/spatial.rs b/src/spatial.rs index ff553ff..4a171e4 100644 --- a/src/spatial.rs +++ b/src/spatial.rs @@ -194,7 +194,7 @@ impl GridState { pub fn count(&self, predicate: F) -> i32 where - F: Fn(&Cell) -> bool + F: Fn(&Cell) -> bool, { self.cells .iter()