From 3bb8da9209fa940c478fb5854d35ff10538fcf33 Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Mon, 21 Mar 2022 12:50:48 -0400 Subject: [PATCH] big changes --- TODO.md | 3 +- assets/text.json | 3 +- build.sh | 2 +- push.sh | 2 + src/egui_app.rs | 241 ++++++++++++++++++++++++++--------------------- src/main.rs | 1 + src/misc.rs | 68 ++++++++----- 7 files changed, 187 insertions(+), 133 deletions(-) diff --git a/TODO.md b/TODO.md index 111dbf9..17f5dfc 100644 --- a/TODO.md +++ b/TODO.md @@ -17,4 +17,5 @@ 9. Update function tests 10. rewrite FunctionEntry to move more information and handling to egui_app (such as config changes) 11. Threading -12. fix integral display \ No newline at end of file +12. fix integral display +13. Improve loading indicator \ No newline at end of file diff --git a/assets/text.json b/assets/text.json index caa8992..fe0c0f4 100644 --- a/assets/text.json +++ b/assets/text.json @@ -33,5 +33,6 @@ "help_other": [ "- Extrema (local minimums and maximums) and Roots (intersections with the x-axis) are displayed though yellow and light blue points on the graph. You can toggle these in the Side Panel.", "- In some edge cases, math functions may not parse correctly. More specifically with implicit multiplication. If you incounter this issue, please do report it on the project's Github page (linked on the side panel). But a current workaround would be explicitly stating a multiplication operation through the use of an asterisk." - ] + ], + "license_info": "The AGPL license ensures that the end user, even if not hosting the program itself, is still guaranteed access to the source code of the project in question." } \ No newline at end of file diff --git a/build.sh b/build.sh index e9f1fd7..f414746 100755 --- a/build.sh +++ b/build.sh @@ -4,7 +4,7 @@ set -e rm -fr tmp | true rm -fr pkg | true -cargo test +# cargo test #apply optimizations via wasm-opt wasm_opt() { diff --git a/push.sh b/push.sh index d24e315..f757737 100755 --- a/push.sh +++ b/push.sh @@ -1,6 +1,8 @@ #!/bin/bash set -e #kill script if error occurs +cargo test + bash build.sh echo "rsyncing" diff --git a/src/egui_app.rs b/src/egui_app.rs index 762c4d2..381bdf4 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -1,5 +1,5 @@ use crate::function::{FunctionEntry, RiemannSum, EMPTY_FUNCTION_ENTRY}; -use crate::misc::{debug_log, log_helper, parse_value}; +use crate::misc::{debug_log, log_helper, JsonFileOutput, SerdeValueHelper}; use crate::parsing::{process_func_str, test_func}; use const_format::formatc; @@ -12,7 +12,6 @@ use egui::{ }; use epi::Frame; use instant::Duration; -use serde_json::Value; use shadow_rs::shadow; use std::{ collections::BTreeMap, @@ -47,23 +46,50 @@ const DEFAULT_MAX_X: f64 = 10.0; const DEFAULT_INTEGRAL_NUM: usize = 100; // Stores data loaded from files -struct FileData { - // Stores fonts - pub font_ubuntu_light: FontData, - pub font_notoemoji: FontData, - pub font_hack: FontData, +struct Assets { + // Stores `FontDefinitions` + pub fonts: FontDefinitions, - // Stores text + // Help blurbs pub text_help_expr: String, pub text_help_vars: String, pub text_help_panel: String, pub text_help_function: String, pub text_help_other: String, + + // Explanation of license + pub text_license_info: String, +} + +impl Assets { + pub fn new(fonts: FontDefinitions, json: JsonFileOutput) -> Self { + Self { + fonts, + text_help_expr: json.help_expr, + text_help_vars: json.help_vars, + text_help_panel: json.help_panel, + text_help_function: json.help_function, + text_help_other: json.help_other, + text_license_info: json.license_info, + } + } + + #[cfg(test)] // Only used for testing + pub fn get_json_file_output(&self) -> JsonFileOutput { + JsonFileOutput { + help_expr: self.text_help_expr.clone(), + help_vars: self.text_help_vars.clone(), + help_panel: self.text_help_panel.clone(), + help_function: self.text_help_function.clone(), + help_other: self.text_help_other.clone(), + license_info: self.text_license_info.clone(), + } + } } lazy_static::lazy_static! { // Load all of the data from the compressed tarball - static ref FILE_DATA: FileData = { + static ref ASSETS: Assets = { let start = instant::Instant::now(); log_helper("Loading assets..."); @@ -78,11 +104,7 @@ lazy_static::lazy_static! { let mut font_hack: Option = None; // Stores text - let mut text_help_expr: Option = None; - let mut text_help_vars: Option = None; - let mut text_help_panel: Option = None; - let mut text_help_function: Option = None; - let mut text_help_other: Option = None; + let mut text_data: Option = None; log_helper("Reading assets..."); @@ -119,16 +141,10 @@ lazy_static::lazy_static! { let string_data = str::from_utf8(&data).unwrap().to_string(); match path_string.as_ref() { "text.json" => { - let json_data: Value = serde_json::from_str(&string_data).unwrap(); - text_help_expr = Some(parse_value(&json_data["help_expr"])); - text_help_vars = Some(parse_value(&json_data["help_vars"])); - text_help_panel = Some(parse_value(&json_data["help_panel"])); - text_help_function = Some(parse_value(&json_data["help_function"])); - text_help_other = Some(parse_value(&json_data["help_other"])); - + text_data = Some(SerdeValueHelper::new(&string_data).parse_values()); }, _ => { - panic!("Text file {} not expected!", path_string); + panic!("Json file {} not expected!", path_string); } } } else { @@ -138,31 +154,20 @@ lazy_static::lazy_static! { log_helper(&format!("Done loading assets! Took: {:?}", start.elapsed())); - // Create and return FileData struct - FileData { - font_ubuntu_light: font_ubuntu_light.expect("Ubuntu Light font not found!"), - font_notoemoji: font_notoemoji.expect("Noto Emoji font not found!"), - font_hack: font_hack.expect("Hack font not found!"), - text_help_expr: text_help_expr.expect("HELP_EXPR not found"), - text_help_vars: text_help_vars.expect("HELP_VARS not found"), - text_help_panel: text_help_panel.expect("HELP_PANEL not found"), - text_help_function: text_help_function.expect("HELP_FUNCTION not found"), - text_help_other: text_help_other.expect("HELP_OTHER not found"), - } - }; - - // Stores the FontDefinitions used by egui - static ref FONT_DEFINITIONS: FontDefinitions = { let mut font_data: BTreeMap = BTreeMap::new(); let mut families = BTreeMap::new(); - font_data.insert("Hack".to_owned(), FILE_DATA.font_hack.clone()); - font_data.insert("Ubuntu-Light".to_owned(), FILE_DATA.font_ubuntu_light.clone()); - font_data.insert("NotoEmoji-Regular".to_owned(), FILE_DATA.font_notoemoji.clone()); + font_data.insert("Hack".to_owned(), font_hack.expect("Hack font not found!")); + font_data.insert("Ubuntu-Light".to_owned(), font_ubuntu_light.expect("Ubuntu Light font not found!")); + font_data.insert("NotoEmoji-Regular".to_owned(), font_notoemoji.expect("Noto Emoji font not found!")); families.insert( FontFamily::Monospace, - vec!["Hack".to_owned(), "Ubuntu-Light".to_owned(), "NotoEmoji-Regular".to_owned()], + vec![ + "Hack".to_owned(), + "Ubuntu-Light".to_owned(), + "NotoEmoji-Regular".to_owned(), + ], ); families.insert( @@ -170,105 +175,115 @@ lazy_static::lazy_static! { vec!["Ubuntu-Light".to_owned(), "NotoEmoji-Regular".to_owned()], ); - FontDefinitions { + let fonts = FontDefinitions { font_data, families, - } + }; + + + // Create and return Assets struct + Assets::new( + fonts, text_data.expect("Text data not found!")) }; } // Tests to make sure archived (and compressed) assets match expected data #[test] fn test_file_data() { - assert_eq!( - FILE_DATA.font_ubuntu_light, - FontData::from_owned(include_bytes!("../assets/Ubuntu-Light.ttf").to_vec()) + let mut font_data: BTreeMap = BTreeMap::new(); + let mut families = BTreeMap::new(); + + font_data.insert( + "Hack".to_owned(), + FontData::from_owned(include_bytes!("../assets/Hack-Regular.ttf").to_vec()), ); - assert_eq!( - FILE_DATA.font_notoemoji, - FontData::from_owned(include_bytes!("../assets/NotoEmoji-Regular.ttf").to_vec()) + font_data.insert( + "Ubuntu-Light".to_owned(), + FontData::from_owned(include_bytes!("../assets/Ubuntu-Light.ttf").to_vec()), ); - assert_eq!( - FILE_DATA.font_hack, - FontData::from_owned(include_bytes!("../assets/Hack-Regular.ttf").to_vec()) + font_data.insert( + "NotoEmoji-Regular".to_owned(), + FontData::from_owned(include_bytes!("../assets/NotoEmoji-Regular.ttf").to_vec()), ); - let json_data: Value = serde_json::from_str(include_str!("../assets/text.json")).unwrap(); - - assert_eq!( - FILE_DATA.text_help_expr, - parse_value(&json_data["help_expr"]) + families.insert( + FontFamily::Monospace, + vec![ + "Hack".to_owned(), + "Ubuntu-Light".to_owned(), + "NotoEmoji-Regular".to_owned(), + ], ); - assert_eq!( - FILE_DATA.text_help_vars, - parse_value(&json_data["help_vars"]) - ); - assert_eq!( - FILE_DATA.text_help_panel, - parse_value(&json_data["help_panel"]) - ); - assert_eq!( - FILE_DATA.text_help_function, - parse_value(&json_data["help_function"]) - ); - assert_eq!( - FILE_DATA.text_help_other, - parse_value(&json_data["help_other"]) + families.insert( + FontFamily::Proportional, + vec!["Ubuntu-Light".to_owned(), "NotoEmoji-Regular".to_owned()], ); + + let fonts = FontDefinitions { + font_data, + families, + }; + + assert_eq!(ASSETS.fonts, fonts); + + let json_data: SerdeValueHelper = SerdeValueHelper::new(include_str!("../assets/text.json")); + + assert_eq!(ASSETS.get_json_file_output(), json_data.parse_values()); } cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { use wasm_bindgen::JsCast; - // removes the "loading" element on the web page that displays a loading indicator + /// Removes the "loading" element on the web page that displays a loading indicator fn stop_loading() { - let document = web_sys::window().unwrap().document().unwrap(); - let loading_element = document.get_element_by_id("loading").unwrap().dyn_into::().unwrap(); + let document = web_sys::window().expect("Could not get web_sys window").document().expect("Could not get web_sys document"); + + let loading_element = document.get_element_by_id("loading").expect("Couldn't get loading indicator element") + .dyn_into::().unwrap(); + + // Remove the element loading_element.remove(); } } } -// Used to provide info on the Licensing of the project -const LICENSE_INFO: &str = "The AGPL license ensures that the end user, even if not hosting the program itself, is still guaranteed access to the source code of the project in question."; - -// The URL of the project -const PROJECT_URL: &str = "https://github.com/Titaniumtown/YTBN-Graphing-Software"; - -// Stores settings +/// Stores current settings/state of `MathApp` +// TODO: find a better name for this struct AppSettings { - // Stores whether or not the Help window is open + /// Stores whether or not the Help window is open pub help_open: bool, - // Stores whether or not the Info window is open + /// Stores whether or not the Info window is open pub info_open: bool, - // Stores whether or not the side panel is shown or not + /// Stores whether or not the side panel is shown or not pub show_side_panel: bool, - // Stores the type of Rienmann sum that should be calculated + /// Stores the type of Rienmann sum that should be calculated pub sum: RiemannSum, - // Min and Max range for calculating an integral + /// Min and Max range for calculating an integral pub integral_min_x: f64, pub integral_max_x: f64, - // Number of rectangles used to calculate integral + /// Number of rectangles used to calculate integral pub integral_num: usize, - // Stores whether or not dark mode is enabled + /// Stores whether or not dark mode is enabled pub dark_mode: bool, - // Stores whether or not displaying extrema is enabled + /// Stores whether or not displaying extrema is enabled pub extrema: bool, - // Stores whether or not displaying roots is enabled + /// Stores whether or not displaying roots is enabled pub roots: bool, } impl Default for AppSettings { + /// Default implementation of `AppSettings`, this is how the application + /// starts up fn default() -> Self { Self { help_open: true, @@ -285,23 +300,24 @@ impl Default for AppSettings { } } +/// The actual application pub struct MathApp { - // Stores vector of functions + /// Stores vector of functions functions: Vec, - // Stores vector containing the string representation of the functions. This is used because of - // hacky reasons + /// Stores vector containing the string representation of the functions. + /// This is used because of hacky reasons func_strs: Vec, - // Stores last error from parsing functions (used to display the same error when side panel is - // minimized) + /// Stores last error from parsing functions (used to display the same error + /// when side panel is minimized) last_error: Vec<(usize, String)>, - // Contains the list of Areas calculated (the vector of f64) and time it took for the last - // frame (the Duration). Stored in a Tuple. + /// Contains the list of Areas calculated (the vector of f64) and time it + /// took for the last frame (the Duration). Stored in a Tuple. last_info: (Vec, Duration), - // Stores Settings (pretty self-explanatory) + /// Stores settings (pretty self-explanatory) settings: AppSettings, } @@ -318,14 +334,18 @@ impl Default for MathApp { } impl MathApp { - #[allow(dead_code)] // this is used lol + #[allow(dead_code)] // This is used lol + /// Create new instance of `MathApp` and return it pub fn new(_cc: &eframe::CreationContext<'_>) -> Self { + // Remove loading indicator on wasm #[cfg(target_arch = "wasm32")] stop_loading(); + log_helper("egui app initialized."); - Self::default() + Self::default() // initialize `MathApp` } + /// Creates SidePanel which contains configuration options fn side_panel(&mut self, ctx: &Context) { // Side Panel which contains vital options to the operation of the application // (such as adding functions and other options) @@ -500,10 +520,13 @@ impl MathApp { } // Open Source and Licensing information - ui.hyperlink_to("I'm Opensource!", PROJECT_URL); + ui.hyperlink_to( + "I'm Opensource!", + "https://github.com/Titaniumtown/YTBN-Graphing-Software", + ); ui.label(RichText::new("(and licensed under AGPLv3)").color(Color32::LIGHT_GRAY)) - .on_hover_text(LICENSE_INFO); + .on_hover_text(&ASSETS.text_license_info); }); } } @@ -524,7 +547,7 @@ impl epi::App for MathApp { .show_side_panel .bitxor_assign(ctx.input().key_down(Key::H)); - ctx.set_fonts(FONT_DEFINITIONS.clone()); // Initialize fonts + ctx.set_fonts(ASSETS.fonts.clone()); // Initialize fonts // Creates Top bar that contains some general options TopBottomPanel::top("top_bar").show(ctx, |ui| { @@ -604,23 +627,23 @@ impl epi::App for MathApp { ui.heading("Help With..."); ui.collapsing("Supported Expressions", |ui| { - ui.label(&FILE_DATA.text_help_expr); + ui.label(&ASSETS.text_help_expr); }); ui.collapsing("Supported Constants", |ui| { - ui.label(&FILE_DATA.text_help_vars); + ui.label(&ASSETS.text_help_vars); }); ui.collapsing("Panel", |ui| { - ui.label(&FILE_DATA.text_help_panel); + ui.label(&ASSETS.text_help_panel); }); ui.collapsing("Functions", |ui| { - ui.label(&FILE_DATA.text_help_function); + ui.label(&ASSETS.text_help_function); }); ui.collapsing("Other", |ui| { - ui.label(&FILE_DATA.text_help_other); + ui.label(&ASSETS.text_help_other); }); }); diff --git a/src/main.rs b/src/main.rs index 18541e3..9aaafe3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ fn main() { drag_and_drop_support: true, ..Default::default() }; + eframe::run_native( "(Yet-to-be-named) Graphing Software", options, diff --git a/src/misc.rs b/src/misc.rs index 91d3d20..3e54f11 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,7 +1,5 @@ use std::ops::Range; -use eframe::egui::plot::Value; - // Handles logging based on if the target is wasm (or not) and if // `debug_assertions` is enabled or not cfg_if::cfg_if! { @@ -136,11 +134,11 @@ pub fn decimal_round(x: f64, n: usize) -> f64 { /// `f_1` is f'(x) /// The function returns a Vector of `x` values where roots occur pub fn newtons_method( - threshold: f64, range: Range, data: Vec, f: &dyn Fn(f64) -> f64, - f_1: &dyn Fn(f64) -> f64, + threshold: f64, range: Range, data: Vec, + f: &dyn Fn(f64) -> f64, f_1: &dyn Fn(f64) -> f64, ) -> Vec { let mut output_list: Vec = Vec::new(); - let mut last_ele_option: Option = None; + let mut last_ele_option: Option = None; for ele in data.iter() { if last_ele_option.is_none() { last_ele_option = Some(*ele); @@ -190,20 +188,48 @@ pub fn newtons_method( output_list } -/// Parses a json array of strings into a single, multiline string -pub fn parse_value(value: &serde_json::Value) -> String { - // Create vector of strings - let string_vector: Vec<&str> = value - .as_array() - .unwrap() - .iter() - .map(|ele| ele.as_str().unwrap()) - .collect::>(); - - // Deliminate vector with a new line and return the resulting multiline string - string_vector - .iter() - .fold(String::new(), |s, l| s + l + "\n") - .trim_end() - .to_string() +#[derive(PartialEq, Debug)] +pub struct JsonFileOutput { + pub help_expr: String, + pub help_vars: String, + pub help_panel: String, + pub help_function: String, + pub help_other: String, + pub license_info: String, +} + +pub struct SerdeValueHelper { + value: serde_json::Value, +} + +impl SerdeValueHelper { + pub fn new(string: &str) -> Self { + Self { + value: serde_json::from_str(string).unwrap(), + } + } + + fn parse_multiline(&self, key: &str) -> String { + (&self.value[key]) + .as_array() + .unwrap() + .iter() + .map(|ele| ele.as_str().unwrap()) + .fold(String::new(), |s, l| s + l + "\n") + .trim_end() + .to_owned() + } + + fn parse_singleline(&self, key: &str) -> String { self.value[key].as_str().unwrap().to_owned() } + + pub fn parse_values(&self) -> JsonFileOutput { + JsonFileOutput { + help_expr: self.parse_multiline("help_expr"), + help_vars: self.parse_multiline("help_vars"), + help_panel: self.parse_multiline("help_panel"), + help_function: self.parse_multiline("help_function"), + help_other: self.parse_multiline("help_other"), + license_info: self.parse_singleline("license_info"), + } + } }