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
10. rewrite FunctionEntry to move more information and handling to egui_app (such as config changes)
11. Threading
12. fix integral display
12. fix integral display
13. Improve loading indicator

View File

@ -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."
}

View File

@ -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() {

View File

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

View File

@ -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<FontData> = None;
// Stores text
let mut text_help_expr: Option<String> = 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;
let mut text_data: Option<JsonFileOutput> = 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<String, FontData> = 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<String, FontData> = 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::<web_sys::HtmlElement>().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::<web_sys::HtmlElement>().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<FunctionEntry>,
// 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<String>,
// 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<f64>, 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);
});
});

View File

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

View File

@ -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<f64>, data: Vec<Value>, f: &dyn Fn(f64) -> f64,
f_1: &dyn Fn(f64) -> f64,
threshold: f64, range: Range<f64>, data: Vec<eframe::egui::plot::Value>,
f: &dyn Fn(f64) -> f64, f_1: &dyn Fn(f64) -> f64,
) -> Vec<f64> {
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() {
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::<Vec<&str>>();
// 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"),
}
}
}