big changes

This commit is contained in:
Simon Gardling 2022-03-21 12:50:48 -04:00
parent 1a3e04eff1
commit 3bb8da9209
7 changed files with 187 additions and 133 deletions

View File

@ -17,4 +17,5 @@
9. Update function tests 9. Update function tests
10. rewrite FunctionEntry to move more information and handling to egui_app (such as config changes) 10. rewrite FunctionEntry to move more information and handling to egui_app (such as config changes)
11. Threading 11. Threading
12. fix integral display 12. fix integral display
13. Improve loading indicator

View File

@ -33,5 +33,6 @@
"help_other": [ "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.", "- 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." "- 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."
} }

View File

@ -4,7 +4,7 @@ set -e
rm -fr tmp | true rm -fr tmp | true
rm -fr pkg | true rm -fr pkg | true
cargo test # cargo test
#apply optimizations via wasm-opt #apply optimizations via wasm-opt
wasm_opt() { wasm_opt() {

View File

@ -1,6 +1,8 @@
#!/bin/bash #!/bin/bash
set -e #kill script if error occurs set -e #kill script if error occurs
cargo test
bash build.sh bash build.sh
echo "rsyncing" echo "rsyncing"

View File

@ -1,5 +1,5 @@
use crate::function::{FunctionEntry, RiemannSum, EMPTY_FUNCTION_ENTRY}; 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 crate::parsing::{process_func_str, test_func};
use const_format::formatc; use const_format::formatc;
@ -12,7 +12,6 @@ use egui::{
}; };
use epi::Frame; use epi::Frame;
use instant::Duration; use instant::Duration;
use serde_json::Value;
use shadow_rs::shadow; use shadow_rs::shadow;
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
@ -47,23 +46,50 @@ const DEFAULT_MAX_X: f64 = 10.0;
const DEFAULT_INTEGRAL_NUM: usize = 100; const DEFAULT_INTEGRAL_NUM: usize = 100;
// Stores data loaded from files // Stores data loaded from files
struct FileData { struct Assets {
// Stores fonts // Stores `FontDefinitions`
pub font_ubuntu_light: FontData, pub fonts: FontDefinitions,
pub font_notoemoji: FontData,
pub font_hack: FontData,
// Stores text // Help blurbs
pub text_help_expr: String, pub text_help_expr: String,
pub text_help_vars: String, pub text_help_vars: String,
pub text_help_panel: String, pub text_help_panel: String,
pub text_help_function: String, pub text_help_function: String,
pub text_help_other: 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! { lazy_static::lazy_static! {
// Load all of the data from the compressed tarball // Load all of the data from the compressed tarball
static ref FILE_DATA: FileData = { static ref ASSETS: Assets = {
let start = instant::Instant::now(); let start = instant::Instant::now();
log_helper("Loading assets..."); log_helper("Loading assets...");
@ -78,11 +104,7 @@ lazy_static::lazy_static! {
let mut font_hack: Option<FontData> = None; let mut font_hack: Option<FontData> = None;
// Stores text // Stores text
let mut text_help_expr: Option<String> = None; let mut text_data: Option<JsonFileOutput> = None;
let mut text_help_vars: Option<String> = None;
let mut text_help_panel: Option<String> = None;
let mut text_help_function: Option<String> = None;
let mut text_help_other: Option<String> = None;
log_helper("Reading assets..."); log_helper("Reading assets...");
@ -119,16 +141,10 @@ lazy_static::lazy_static! {
let string_data = str::from_utf8(&data).unwrap().to_string(); let string_data = str::from_utf8(&data).unwrap().to_string();
match path_string.as_ref() { match path_string.as_ref() {
"text.json" => { "text.json" => {
let json_data: Value = serde_json::from_str(&string_data).unwrap(); text_data = Some(SerdeValueHelper::new(&string_data).parse_values());
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"]));
}, },
_ => { _ => {
panic!("Text file {} not expected!", path_string); panic!("Json file {} not expected!", path_string);
} }
} }
} else { } else {
@ -138,31 +154,20 @@ lazy_static::lazy_static! {
log_helper(&format!("Done loading assets! Took: {:?}", start.elapsed())); 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<String, FontData> = BTreeMap::new(); let mut font_data: BTreeMap<String, FontData> = BTreeMap::new();
let mut families = BTreeMap::new(); let mut families = BTreeMap::new();
font_data.insert("Hack".to_owned(), FILE_DATA.font_hack.clone()); font_data.insert("Hack".to_owned(), font_hack.expect("Hack font not found!"));
font_data.insert("Ubuntu-Light".to_owned(), FILE_DATA.font_ubuntu_light.clone()); font_data.insert("Ubuntu-Light".to_owned(), font_ubuntu_light.expect("Ubuntu Light font not found!"));
font_data.insert("NotoEmoji-Regular".to_owned(), FILE_DATA.font_notoemoji.clone()); font_data.insert("NotoEmoji-Regular".to_owned(), font_notoemoji.expect("Noto Emoji font not found!"));
families.insert( families.insert(
FontFamily::Monospace, 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( families.insert(
@ -170,105 +175,115 @@ lazy_static::lazy_static! {
vec!["Ubuntu-Light".to_owned(), "NotoEmoji-Regular".to_owned()], vec!["Ubuntu-Light".to_owned(), "NotoEmoji-Regular".to_owned()],
); );
FontDefinitions { let fonts = FontDefinitions {
font_data, font_data,
families, 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 // Tests to make sure archived (and compressed) assets match expected data
#[test] #[test]
fn test_file_data() { fn test_file_data() {
assert_eq!( let mut font_data: BTreeMap<String, FontData> = BTreeMap::new();
FILE_DATA.font_ubuntu_light, let mut families = BTreeMap::new();
FontData::from_owned(include_bytes!("../assets/Ubuntu-Light.ttf").to_vec())
font_data.insert(
"Hack".to_owned(),
FontData::from_owned(include_bytes!("../assets/Hack-Regular.ttf").to_vec()),
); );
assert_eq!( font_data.insert(
FILE_DATA.font_notoemoji, "Ubuntu-Light".to_owned(),
FontData::from_owned(include_bytes!("../assets/NotoEmoji-Regular.ttf").to_vec()) FontData::from_owned(include_bytes!("../assets/Ubuntu-Light.ttf").to_vec()),
); );
assert_eq!( font_data.insert(
FILE_DATA.font_hack, "NotoEmoji-Regular".to_owned(),
FontData::from_owned(include_bytes!("../assets/Hack-Regular.ttf").to_vec()) 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(); families.insert(
FontFamily::Monospace,
assert_eq!( vec![
FILE_DATA.text_help_expr, "Hack".to_owned(),
parse_value(&json_data["help_expr"]) "Ubuntu-Light".to_owned(),
"NotoEmoji-Regular".to_owned(),
],
); );
assert_eq!( families.insert(
FILE_DATA.text_help_vars, FontFamily::Proportional,
parse_value(&json_data["help_vars"]) vec!["Ubuntu-Light".to_owned(), "NotoEmoji-Regular".to_owned()],
);
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"])
); );
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! { cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] { if #[cfg(target_arch = "wasm32")] {
use wasm_bindgen::JsCast; 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() { fn stop_loading() {
let document = web_sys::window().unwrap().document().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").unwrap().dyn_into::<web_sys::HtmlElement>().unwrap();
let loading_element = document.get_element_by_id("loading").expect("Couldn't get loading indicator element")
.dyn_into::<web_sys::HtmlElement>().unwrap();
// Remove the element
loading_element.remove(); loading_element.remove();
} }
} }
} }
// Used to provide info on the Licensing of the project /// Stores current settings/state of `MathApp`
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."; // TODO: find a better name for this
// The URL of the project
const PROJECT_URL: &str = "https://github.com/Titaniumtown/YTBN-Graphing-Software";
// Stores settings
struct AppSettings { struct AppSettings {
// Stores whether or not the Help window is open /// Stores whether or not the Help window is open
pub help_open: bool, 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, 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, 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, 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_min_x: f64,
pub integral_max_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, pub integral_num: usize,
// Stores whether or not dark mode is enabled /// Stores whether or not dark mode is enabled
pub dark_mode: bool, pub dark_mode: bool,
// Stores whether or not displaying extrema is enabled /// Stores whether or not displaying extrema is enabled
pub extrema: bool, pub extrema: bool,
// Stores whether or not displaying roots is enabled /// Stores whether or not displaying roots is enabled
pub roots: bool, pub roots: bool,
} }
impl Default for AppSettings { impl Default for AppSettings {
/// Default implementation of `AppSettings`, this is how the application
/// starts up
fn default() -> Self { fn default() -> Self {
Self { Self {
help_open: true, help_open: true,
@ -285,23 +300,24 @@ impl Default for AppSettings {
} }
} }
/// The actual application
pub struct MathApp { pub struct MathApp {
// Stores vector of functions /// Stores vector of functions
functions: Vec<FunctionEntry>, functions: Vec<FunctionEntry>,
// Stores vector containing the string representation of the functions. This is used because of /// Stores vector containing the string representation of the functions.
// hacky reasons /// This is used because of hacky reasons
func_strs: Vec<String>, func_strs: Vec<String>,
// Stores last error from parsing functions (used to display the same error when side panel is /// Stores last error from parsing functions (used to display the same error
// minimized) /// when side panel is minimized)
last_error: Vec<(usize, String)>, last_error: Vec<(usize, String)>,
// Contains the list of Areas calculated (the vector of f64) and time it took for the last /// Contains the list of Areas calculated (the vector of f64) and time it
// frame (the Duration). Stored in a Tuple. /// took for the last frame (the Duration). Stored in a Tuple.
last_info: (Vec<f64>, Duration), last_info: (Vec<f64>, Duration),
// Stores Settings (pretty self-explanatory) /// Stores settings (pretty self-explanatory)
settings: AppSettings, settings: AppSettings,
} }
@ -318,14 +334,18 @@ impl Default for MathApp {
} }
impl 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 { pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
// Remove loading indicator on wasm
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
stop_loading(); stop_loading();
log_helper("egui app initialized."); log_helper("egui app initialized.");
Self::default() Self::default() // initialize `MathApp`
} }
/// Creates SidePanel which contains configuration options
fn side_panel(&mut self, ctx: &Context) { fn side_panel(&mut self, ctx: &Context) {
// Side Panel which contains vital options to the operation of the application // Side Panel which contains vital options to the operation of the application
// (such as adding functions and other options) // (such as adding functions and other options)
@ -500,10 +520,13 @@ impl MathApp {
} }
// Open Source and Licensing information // 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)) 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 .show_side_panel
.bitxor_assign(ctx.input().key_down(Key::H)); .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 // Creates Top bar that contains some general options
TopBottomPanel::top("top_bar").show(ctx, |ui| { TopBottomPanel::top("top_bar").show(ctx, |ui| {
@ -604,23 +627,23 @@ impl epi::App for MathApp {
ui.heading("Help With..."); ui.heading("Help With...");
ui.collapsing("Supported Expressions", |ui| { ui.collapsing("Supported Expressions", |ui| {
ui.label(&FILE_DATA.text_help_expr); ui.label(&ASSETS.text_help_expr);
}); });
ui.collapsing("Supported Constants", |ui| { ui.collapsing("Supported Constants", |ui| {
ui.label(&FILE_DATA.text_help_vars); ui.label(&ASSETS.text_help_vars);
}); });
ui.collapsing("Panel", |ui| { ui.collapsing("Panel", |ui| {
ui.label(&FILE_DATA.text_help_panel); ui.label(&ASSETS.text_help_panel);
}); });
ui.collapsing("Functions", |ui| { ui.collapsing("Functions", |ui| {
ui.label(&FILE_DATA.text_help_function); ui.label(&ASSETS.text_help_function);
}); });
ui.collapsing("Other", |ui| { ui.collapsing("Other", |ui| {
ui.label(&FILE_DATA.text_help_other); ui.label(&ASSETS.text_help_other);
}); });
}); });

View File

@ -14,6 +14,7 @@ fn main() {
drag_and_drop_support: true, drag_and_drop_support: true,
..Default::default() ..Default::default()
}; };
eframe::run_native( eframe::run_native(
"(Yet-to-be-named) Graphing Software", "(Yet-to-be-named) Graphing Software",
options, options,

View File

@ -1,7 +1,5 @@
use std::ops::Range; use std::ops::Range;
use eframe::egui::plot::Value;
// Handles logging based on if the target is wasm (or not) and if // Handles logging based on if the target is wasm (or not) and if
// `debug_assertions` is enabled or not // `debug_assertions` is enabled or not
cfg_if::cfg_if! { cfg_if::cfg_if! {
@ -136,11 +134,11 @@ pub fn decimal_round(x: f64, n: usize) -> f64 {
/// `f_1` is f'(x) /// `f_1` is f'(x)
/// The function returns a Vector of `x` values where roots occur /// The function returns a Vector of `x` values where roots occur
pub fn newtons_method( pub fn newtons_method(
threshold: f64, range: Range<f64>, data: Vec<Value>, f: &dyn Fn(f64) -> f64, threshold: f64, range: Range<f64>, data: Vec<eframe::egui::plot::Value>,
f_1: &dyn Fn(f64) -> f64, f: &dyn Fn(f64) -> f64, f_1: &dyn Fn(f64) -> f64,
) -> Vec<f64> { ) -> Vec<f64> {
let mut output_list: Vec<f64> = Vec::new(); let mut output_list: Vec<f64> = Vec::new();
let mut last_ele_option: Option<Value> = None; let mut last_ele_option: Option<eframe::egui::plot::Value> = None;
for ele in data.iter() { for ele in data.iter() {
if last_ele_option.is_none() { if last_ele_option.is_none() {
last_ele_option = Some(*ele); last_ele_option = Some(*ele);
@ -190,20 +188,48 @@ pub fn newtons_method(
output_list output_list
} }
/// Parses a json array of strings into a single, multiline string #[derive(PartialEq, Debug)]
pub fn parse_value(value: &serde_json::Value) -> String { pub struct JsonFileOutput {
// Create vector of strings pub help_expr: String,
let string_vector: Vec<&str> = value pub help_vars: String,
.as_array() pub help_panel: String,
.unwrap() pub help_function: String,
.iter() pub help_other: String,
.map(|ele| ele.as_str().unwrap()) pub license_info: String,
.collect::<Vec<&str>>(); }
// Deliminate vector with a new line and return the resulting multiline string pub struct SerdeValueHelper {
string_vector value: serde_json::Value,
.iter() }
.fold(String::new(), |s, l| s + l + "\n")
.trim_end() impl SerdeValueHelper {
.to_string() 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"),
}
}
} }