diff --git a/.rustfmt.toml b/.rustfmt.toml deleted file mode 100644 index dd31ce2..0000000 --- a/.rustfmt.toml +++ /dev/null @@ -1,4 +0,0 @@ -edition = "2021" -fn_params_layout = "Compressed" -fn_single_line = true -hard_tabs = true diff --git a/build.rs b/build.rs index 83e10a0..cdb8a3e 100644 --- a/build.rs +++ b/build.rs @@ -1,171 +1,171 @@ use std::{ - collections::BTreeMap, - env, - fs::File, - io::{BufWriter, Write}, - path::Path, + collections::BTreeMap, + env, + fs::File, + io::{BufWriter, Write}, + path::Path, }; use epaint::{ - text::{FontData, FontDefinitions, FontTweak}, - FontFamily, + FontFamily, + text::{FontData, FontDefinitions, FontTweak}, }; use run_script::ScriptOptions; include!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/unicode_helper.rs" + env!("CARGO_MANIFEST_DIR"), + "/src/unicode_helper.rs" )); fn font_stripper(from: &str, out: &str, unicodes: Vec) -> Result, String> { - let unicodes: Vec = unicodes.iter().map(|c| to_unicode_hash(*c)).collect(); + let unicodes: Vec = unicodes.iter().map(|c| to_unicode_hash(*c)).collect(); - let new_path = [&env::var("OUT_DIR").unwrap(), out].concat(); - let unicodes_formatted = unicodes - .iter() - .map(|u| format!("U+{}", u)) - .collect::>() - .join(","); + let new_path = [&env::var("OUT_DIR").unwrap(), out].concat(); + let unicodes_formatted = unicodes + .iter() + .map(|u| format!("U+{}", u)) + .collect::>() + .join(","); - // Test to see if pyftsubset is found - let pyftsubset_detect = run_script::run("whereis pyftsubset", &(vec![]), &ScriptOptions::new()); - match pyftsubset_detect { - Ok((_i, s1, _s2)) => { - if s1 == "pyftsubset: " { - return Err(String::from("pyftsubset not found")); - } - } - // It was not, return an error and abort - Err(x) => return Err(x.to_string()), - } + // Test to see if pyftsubset is found + let pyftsubset_detect = run_script::run("whereis pyftsubset", &(vec![]), &ScriptOptions::new()); + match pyftsubset_detect { + Ok((_i, s1, _s2)) => { + if s1 == "pyftsubset: " { + return Err(String::from("pyftsubset not found")); + } + } + // It was not, return an error and abort + Err(x) => return Err(x.to_string()), + } - let script_result = run_script::run( - &format!( - "pyftsubset {}/assets/{} --unicodes={} + let script_result = run_script::run( + &format!( + "pyftsubset {}/assets/{} --unicodes={} mv {}/assets/{} {}", - env!("CARGO_MANIFEST_DIR"), - from, - unicodes_formatted, - env!("CARGO_MANIFEST_DIR"), - from.replace(".ttf", ".subset.ttf"), - new_path - ), - &(vec![]), - &ScriptOptions::new(), - ); + env!("CARGO_MANIFEST_DIR"), + from, + unicodes_formatted, + env!("CARGO_MANIFEST_DIR"), + from.replace(".ttf", ".subset.ttf"), + new_path + ), + &(vec![]), + &ScriptOptions::new(), + ); - if let Ok((_, _, error)) = script_result { - if error.is_empty() { - return Ok(std::fs::read(new_path).unwrap()); - } else { - return Err(error); - } - } else if let Err(error) = script_result { - return Err(error.to_string()); - } - unreachable!() + if let Ok((_, _, error)) = script_result { + if error.is_empty() { + return Ok(std::fs::read(new_path).unwrap()); + } else { + return Err(error); + } + } else if let Err(error) = script_result { + return Err(error.to_string()); + } + unreachable!() } fn main() { - // rebuild if new commit or contents of `assets` folder changed - println!("cargo:rerun-if-changed=.git/logs/HEAD"); - println!("cargo:rerun-if-changed=assets/*"); + // rebuild if new commit or contents of `assets` folder changed + println!("cargo:rerun-if-changed=.git/logs/HEAD"); + println!("cargo:rerun-if-changed=assets/*"); - shadow_rs::new().expect("Could not initialize shadow_rs"); + shadow_rs::new().expect("Could not initialize shadow_rs"); - let mut main_chars: Vec = + let mut main_chars: Vec = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzsu0123456789?.,!(){}[]-_=+-/<>'\\ :^*`@#$%&|~;" .iter() .map(|c| *c as char) .collect(); - main_chars.append(&mut vec!['π', '"']); + main_chars.append(&mut vec!['π', '"']); - { - let filtered_chars: Vec = main_chars - .iter() - .filter(|c| !c.is_alphanumeric()) - .cloned() - .collect(); + { + let filtered_chars: Vec = main_chars + .iter() + .filter(|c| !c.is_alphanumeric()) + .cloned() + .collect(); - let chars_array = format!( - "const VALID_EXTRA_CHARS: [char; {}] = {};", - filtered_chars.len(), - to_chars_array(filtered_chars), - ); - let path = Path::new(&env::var("OUT_DIR").unwrap()).join("valid_chars.rs"); - let mut file = BufWriter::new(File::create(path).expect("Could not save compressed_data")); + let chars_array = format!( + "const VALID_EXTRA_CHARS: [char; {}] = {};", + filtered_chars.len(), + to_chars_array(filtered_chars), + ); + let path = Path::new(&env::var("OUT_DIR").unwrap()).join("valid_chars.rs"); + let mut file = BufWriter::new(File::create(path).expect("Could not save compressed_data")); - write!(&mut file, "{}", chars_array).expect("unable to write chars_array"); - } + write!(&mut file, "{}", chars_array).expect("unable to write chars_array"); + } - let fonts = FontDefinitions { - font_data: BTreeMap::from([ - ( - "Ubuntu-Light".to_owned(), - FontData::from_owned( - font_stripper( - "Ubuntu-Light.ttf", - "ubuntu-light.ttf", - [main_chars, vec!['∫']].concat(), - ) - .unwrap(), - ), - ), - ( - "NotoEmoji-Regular".to_owned(), - FontData::from_owned( - font_stripper( - "NotoEmoji-Regular.ttf", - "noto-emoji.ttf", - vec!['🌞', '🌙', '✖'], - ) - .unwrap(), - ), - ), - ( - "emoji-icon-font".to_owned(), - FontData::from_owned( - font_stripper("emoji-icon-font.ttf", "emoji-icon.ttf", vec!['⚙']).unwrap(), - ) - .tweak(FontTweak { - scale: 0.8, - y_offset_factor: 0.07, - y_offset: 0.0, - baseline_offset_factor: -0.0333, - }), - ), - ]), - families: BTreeMap::from([ - ( - FontFamily::Monospace, - vec![ - "Ubuntu-Light".to_owned(), - "NotoEmoji-Regular".to_owned(), - "emoji-icon-font".to_owned(), - ], - ), - ( - FontFamily::Proportional, - vec![ - "Ubuntu-Light".to_owned(), - "NotoEmoji-Regular".to_owned(), - "emoji-icon-font".to_owned(), - ], - ), - ]), - }; + let fonts = FontDefinitions { + font_data: BTreeMap::from([ + ( + "Ubuntu-Light".to_owned(), + FontData::from_owned( + font_stripper( + "Ubuntu-Light.ttf", + "ubuntu-light.ttf", + [main_chars, vec!['∫']].concat(), + ) + .unwrap(), + ), + ), + ( + "NotoEmoji-Regular".to_owned(), + FontData::from_owned( + font_stripper( + "NotoEmoji-Regular.ttf", + "noto-emoji.ttf", + vec!['🌞', '🌙', '✖'], + ) + .unwrap(), + ), + ), + ( + "emoji-icon-font".to_owned(), + FontData::from_owned( + font_stripper("emoji-icon-font.ttf", "emoji-icon.ttf", vec!['⚙']).unwrap(), + ) + .tweak(FontTweak { + scale: 0.8, + y_offset_factor: 0.07, + y_offset: 0.0, + baseline_offset_factor: -0.0333, + }), + ), + ]), + families: BTreeMap::from([ + ( + FontFamily::Monospace, + vec![ + "Ubuntu-Light".to_owned(), + "NotoEmoji-Regular".to_owned(), + "emoji-icon-font".to_owned(), + ], + ), + ( + FontFamily::Proportional, + vec![ + "Ubuntu-Light".to_owned(), + "NotoEmoji-Regular".to_owned(), + "emoji-icon-font".to_owned(), + ], + ), + ]), + }; - let data = bincode::serialize(&fonts).unwrap(); + let data = bincode::serialize(&fonts).unwrap(); - let zstd_levels = zstd::compression_level_range(); - let data_compressed = - zstd::encode_all(data.as_slice(), *zstd_levels.end()).expect("Could not compress data"); + let zstd_levels = zstd::compression_level_range(); + let data_compressed = + zstd::encode_all(data.as_slice(), *zstd_levels.end()).expect("Could not compress data"); - let path = Path::new(&env::var("OUT_DIR").unwrap()).join("compressed_data"); - let mut file = BufWriter::new(File::create(path).expect("Could not save compressed_data")); + let path = Path::new(&env::var("OUT_DIR").unwrap()).join("compressed_data"); + let mut file = BufWriter::new(File::create(path).expect("Could not save compressed_data")); - file.write_all(data_compressed.as_slice()) - .expect("Failed to save compressed data"); + file.write_all(data_compressed.as_slice()) + .expect("Failed to save compressed data"); } diff --git a/src/consts.rs b/src/consts.rs index bf8b630..2a278af 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -5,13 +5,13 @@ shadow!(build); /// Constant string that has a string containing information about the build. pub const BUILD_INFO: &str = formatc!( - "Commit: {} ({})\nBuild Date: {}\nPackage Version: {}\nRust Channel: {}\nRust Version: {}", - &build::SHORT_COMMIT, - &build::BRANCH, - &build::BUILD_TIME, - &build::PKG_VERSION, - &build::RUST_CHANNEL, - &build::RUST_VERSION, + "Commit: {} ({})\nBuild Date: {}\nPackage Version: {}\nRust Channel: {}\nRust Version: {}", + &build::SHORT_COMMIT, + &build::BRANCH, + &build::BUILD_TIME, + &build::PKG_VERSION, + &build::RUST_CHANNEL, + &build::RUST_VERSION, ); pub const FONT_SIZE: f32 = 14.0; @@ -31,24 +31,24 @@ pub const DEFAULT_INTEGRAL_NUM: usize = 100; /// Colors used for plotting // Colors commented out are used elsewhere and are not included here for better user experience pub const COLORS: [Color32; 13] = [ - Color32::RED, - // Color32::GREEN, - // Color32::YELLOW, - // Color32::BLUE, - Color32::BROWN, - Color32::GOLD, - Color32::GRAY, - Color32::WHITE, - Color32::LIGHT_YELLOW, - Color32::LIGHT_GREEN, - // Color32::LIGHT_BLUE, - Color32::LIGHT_GRAY, - Color32::LIGHT_RED, - Color32::DARK_GRAY, - // Color32::DARK_RED, - Color32::KHAKI, - Color32::DARK_GREEN, - Color32::DARK_BLUE, + Color32::RED, + // Color32::GREEN, + // Color32::YELLOW, + // Color32::BLUE, + Color32::BROWN, + Color32::GOLD, + Color32::GRAY, + Color32::WHITE, + Color32::LIGHT_YELLOW, + Color32::LIGHT_GREEN, + // Color32::LIGHT_BLUE, + Color32::LIGHT_GRAY, + Color32::LIGHT_RED, + Color32::DARK_GRAY, + // Color32::DARK_RED, + Color32::KHAKI, + Color32::DARK_GREEN, + Color32::DARK_BLUE, ]; const_assert!(!COLORS.is_empty()); diff --git a/src/function_entry.rs b/src/function_entry.rs index 2cc7729..f825bdc 100644 --- a/src/function_entry.rs +++ b/src/function_entry.rs @@ -1,479 +1,510 @@ use crate::math_app::AppSettings; -use crate::misc::{newtons_method_helper, step_helper, EguiHelper}; +use crate::misc::{EguiHelper, newtons_method_helper, step_helper}; use egui::{Checkbox, Context}; use egui_plot::{Bar, BarChart, PlotPoint, PlotUi}; use epaint::Color32; -use parsing::{generate_hint, AutoComplete}; -use parsing::{process_func_str, BackingFunction}; -use serde::{ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; +use parsing::{AutoComplete, generate_hint}; +use parsing::{BackingFunction, process_func_str}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeStruct}; use std::{ - fmt::{self, Debug}, - hash::{Hash, Hasher}, + fmt::{self, Debug}, + hash::{Hash, Hasher}, }; /// Represents the possible variations of Riemann Sums #[derive(PartialEq, Eq, Debug, Copy, Clone, Default)] pub enum Riemann { - #[default] - Left, + #[default] + Left, - Middle, - Right, + Middle, + Right, } impl fmt::Display for Riemann { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } } /// `FunctionEntry` is a function that can calculate values, integrals, derivatives, etc etc #[derive(Clone)] pub struct FunctionEntry { - /// The `BackingFunction` instance that is used to generate `f(x)`, `f'(x)`, and `f''(x)` - function: BackingFunction, + /// The `BackingFunction` instance that is used to generate `f(x)`, `f'(x)`, and `f''(x)` + function: BackingFunction, - /// Stores a function string (that hasn't been processed via `process_func_str`) to display to the user - pub raw_func_str: String, + /// Stores a function string (that hasn't been processed via `process_func_str`) to display to the user + pub raw_func_str: String, - /// If calculating/displayingintegrals are enabled - pub integral: bool, + /// If calculating/displayingintegrals are enabled + pub integral: bool, - /// If displaying derivatives are enabled (note, they are still calculated for other purposes) - pub derivative: bool, + /// If displaying derivatives are enabled (note, they are still calculated for other purposes) + pub derivative: bool, - pub nth_derviative: bool, + pub nth_derviative: bool, - pub back_data: Vec, - pub integral_data: Option<(Vec, f64)>, - pub derivative_data: Vec, - pub extrema_data: Vec, - pub root_data: Vec, - nth_derivative_data: Option>, + pub back_data: Vec, + pub integral_data: Option<(Vec, f64)>, + pub derivative_data: Vec, + pub extrema_data: Vec, + pub root_data: Vec, + nth_derivative_data: Option>, - pub autocomplete: AutoComplete<'static>, + pub autocomplete: AutoComplete<'static>, - test_result: Option, - curr_nth: usize, + test_result: Option, + curr_nth: usize, - pub settings_opened: bool, + pub settings_opened: bool, } impl Hash for FunctionEntry { - fn hash(&self, state: &mut H) { - self.raw_func_str.hash(state); - self.integral.hash(state); - self.nth_derviative.hash(state); - self.curr_nth.hash(state); - self.settings_opened.hash(state); - } + fn hash(&self, state: &mut H) { + self.raw_func_str.hash(state); + self.integral.hash(state); + self.nth_derviative.hash(state); + self.curr_nth.hash(state); + self.settings_opened.hash(state); + } } impl Serialize for FunctionEntry { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut s = serializer.serialize_struct("FunctionEntry", 4)?; - s.serialize_field("raw_func_str", &self.raw_func_str)?; - s.serialize_field("integral", &self.integral)?; - s.serialize_field("derivative", &self.derivative)?; - s.serialize_field("curr_nth", &self.curr_nth)?; + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_struct("FunctionEntry", 4)?; + s.serialize_field("raw_func_str", &self.raw_func_str)?; + s.serialize_field("integral", &self.integral)?; + s.serialize_field("derivative", &self.derivative)?; + s.serialize_field("curr_nth", &self.curr_nth)?; - s.end() - } + s.end() + } } impl<'de> Deserialize<'de> for FunctionEntry { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - struct Helper { - raw_func_str: String, - integral: bool, - derivative: bool, - curr_nth: usize, - } + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + raw_func_str: String, + integral: bool, + derivative: bool, + curr_nth: usize, + } - let helper = Helper::deserialize(deserializer)?; - let mut new_func_entry = FunctionEntry::default(); - let gen_func = BackingFunction::new(&helper.raw_func_str); - match gen_func { - Ok(func) => new_func_entry.function = func, - Err(x) => new_func_entry.test_result = Some(x), - } + let helper = Helper::deserialize(deserializer)?; + let mut new_func_entry = FunctionEntry::default(); + let gen_func = BackingFunction::new(&helper.raw_func_str); + match gen_func { + Ok(func) => new_func_entry.function = func, + Err(x) => new_func_entry.test_result = Some(x), + } - new_func_entry.autocomplete = AutoComplete { - i: 0, - hint: generate_hint(&helper.raw_func_str), - string: helper.raw_func_str, - }; + new_func_entry.autocomplete = AutoComplete { + i: 0, + hint: generate_hint(&helper.raw_func_str), + string: helper.raw_func_str, + }; - new_func_entry.integral = helper.integral; - new_func_entry.derivative = helper.derivative; - new_func_entry.curr_nth = helper.curr_nth; + new_func_entry.integral = helper.integral; + new_func_entry.derivative = helper.derivative; + new_func_entry.curr_nth = helper.curr_nth; - Ok(new_func_entry) - } + Ok(new_func_entry) + } } impl Default for FunctionEntry { - /// Creates default FunctionEntry instance (which is empty) - fn default() -> FunctionEntry { - FunctionEntry { - function: BackingFunction::default(), - raw_func_str: String::new(), - integral: false, - derivative: false, - nth_derviative: false, - back_data: Vec::new(), - integral_data: None, - derivative_data: Vec::new(), - extrema_data: Vec::new(), - root_data: Vec::new(), - nth_derivative_data: None, - autocomplete: AutoComplete::EMPTY, - test_result: None, - curr_nth: 3, - settings_opened: false, - } - } + /// Creates default FunctionEntry instance (which is empty) + fn default() -> FunctionEntry { + FunctionEntry { + function: BackingFunction::default(), + raw_func_str: String::new(), + integral: false, + derivative: false, + nth_derviative: false, + back_data: Vec::new(), + integral_data: None, + derivative_data: Vec::new(), + extrema_data: Vec::new(), + root_data: Vec::new(), + nth_derivative_data: None, + autocomplete: AutoComplete::EMPTY, + test_result: None, + curr_nth: 3, + settings_opened: false, + } + } } impl FunctionEntry { - pub const fn is_some(&self) -> bool { !self.function.is_none() } + pub const fn is_some(&self) -> bool { + !self.function.is_none() + } - pub fn settings_window(&mut self, ctx: &Context) { - let mut invalidate_nth = false; - egui::Window::new(format!("Settings: {}", self.raw_func_str)) - .open(&mut self.settings_opened) - .default_pos([200.0, 200.0]) - .resizable(false) - .collapsible(false) - .show(ctx, |ui| { - ui.add(Checkbox::new( - &mut self.nth_derviative, - "Display Nth Derivative", - )); + pub fn settings_window(&mut self, ctx: &Context) { + let mut invalidate_nth = false; + egui::Window::new(format!("Settings: {}", self.raw_func_str)) + .open(&mut self.settings_opened) + .default_pos([200.0, 200.0]) + .resizable(false) + .collapsible(false) + .show(ctx, |ui| { + ui.add(Checkbox::new( + &mut self.nth_derviative, + "Display Nth Derivative", + )); - if ui - .add(egui::Slider::new(&mut self.curr_nth, 3..=5).text("Nth Derivative")) - .changed() - { - invalidate_nth = true; - } - }); + if ui + .add(egui::Slider::new(&mut self.curr_nth, 3..=5).text("Nth Derivative")) + .changed() + { + invalidate_nth = true; + } + }); - if invalidate_nth { - self.function.generate_derivative(self.curr_nth); - self.clear_nth(); - } - } + if invalidate_nth { + self.function.generate_derivative(self.curr_nth); + self.clear_nth(); + } + } - /// Get function's cached test result - pub fn get_test_result(&self) -> &Option { &self.test_result } + /// Get function's cached test result + pub fn get_test_result(&self) -> &Option { + &self.test_result + } - /// Update function string and test it - pub fn update_string(&mut self, raw_func_str: &str) { - if raw_func_str == self.raw_func_str { - return; - } + /// Update function string and test it + pub fn update_string(&mut self, raw_func_str: &str) { + if raw_func_str == self.raw_func_str { + return; + } - self.raw_func_str = raw_func_str.to_owned(); - let processed_func = process_func_str(raw_func_str); - let new_func_result = BackingFunction::new(&processed_func); + self.raw_func_str = raw_func_str.to_owned(); + let processed_func = process_func_str(raw_func_str); + let new_func_result = BackingFunction::new(&processed_func); - match new_func_result { - Ok(new_function) => { - self.test_result = None; - self.function = new_function; - self.invalidate_whole(); - } - Err(error) => { - self.test_result = Some(error); - } - } - } + match new_func_result { + Ok(new_function) => { + self.test_result = None; + self.function = new_function; + self.invalidate_whole(); + } + Err(error) => { + self.test_result = Some(error); + } + } + } - /// Creates and does the math for creating all the rectangles under the graph - fn integral_rectangles( - &mut self, integral_min_x: f64, integral_max_x: f64, sum: Riemann, integral_num: usize, - ) -> (Vec<(f64, f64)>, f64) { - let step = (integral_max_x - integral_min_x) / (integral_num as f64); + /// Creates and does the math for creating all the rectangles under the graph + fn integral_rectangles( + &mut self, + integral_min_x: f64, + integral_max_x: f64, + sum: Riemann, + integral_num: usize, + ) -> (Vec<(f64, f64)>, f64) { + let step = (integral_max_x - integral_min_x) / (integral_num as f64); - // let sum_func = self.get_sum_func(sum); + // let sum_func = self.get_sum_func(sum); - let data2: Vec<(f64, f64)> = step_helper(integral_num, integral_min_x, step) - .into_iter() - .map(|x| { - let step_offset = step.copysign(x); // store the offset here so it doesn't have to be calculated multiple times - let x2: f64 = x + step_offset; + let data2: Vec<(f64, f64)> = step_helper(integral_num, integral_min_x, step) + .into_iter() + .map(|x| { + let step_offset = step.copysign(x); // store the offset here so it doesn't have to be calculated multiple times + let x2: f64 = x + step_offset; - let (left_x, right_x) = match x.is_sign_positive() { - true => (x, x2), - false => (x2, x), - }; + let (left_x, right_x) = match x.is_sign_positive() { + true => (x, x2), + false => (x2, x), + }; - let y = match sum { - Riemann::Left => self.function.get(0, left_x), - Riemann::Right => self.function.get(0, right_x), - Riemann::Middle => { - (self.function.get(0, left_x) + self.function.get(0, right_x)) / 2.0 - } - }; + let y = match sum { + Riemann::Left => self.function.get(0, left_x), + Riemann::Right => self.function.get(0, right_x), + Riemann::Middle => { + (self.function.get(0, left_x) + self.function.get(0, right_x)) / 2.0 + } + }; - (x + (step_offset / 2.0), y) - }) - .filter(|(_, y)| y.is_finite()) - .collect(); + (x + (step_offset / 2.0), y) + }) + .filter(|(_, y)| y.is_finite()) + .collect(); - let area = data2.iter().map(move |(_, y)| y * step).sum(); + let area = data2.iter().map(move |(_, y)| y * step).sum(); - (data2, area) - } + (data2, area) + } - /// Helps with processing newton's method depending on level of derivative - fn newtons_method_helper( - &mut self, threshold: f64, derivative_level: usize, range: &std::ops::Range, - ) -> Vec { - self.function.generate_derivative(derivative_level); - self.function.generate_derivative(derivative_level + 1); - let newtons_method_output: Vec = match derivative_level { - 0 => newtons_method_helper( - threshold, - range, - self.back_data.as_slice(), - &self.function.get_function_derivative(0), - &self.function.get_function_derivative(1), - ), - 1 => newtons_method_helper( - threshold, - range, - self.derivative_data.as_slice(), - &self.function.get_function_derivative(1), - &self.function.get_function_derivative(2), - ), - _ => unreachable!(), - }; + /// Helps with processing newton's method depending on level of derivative + fn newtons_method_helper( + &mut self, + threshold: f64, + derivative_level: usize, + range: &std::ops::Range, + ) -> Vec { + self.function.generate_derivative(derivative_level); + self.function.generate_derivative(derivative_level + 1); + let newtons_method_output: Vec = match derivative_level { + 0 => newtons_method_helper( + threshold, + range, + self.back_data.as_slice(), + self.function.get_function_derivative(0), + self.function.get_function_derivative(1), + ), + 1 => newtons_method_helper( + threshold, + range, + self.derivative_data.as_slice(), + self.function.get_function_derivative(1), + self.function.get_function_derivative(2), + ), + _ => unreachable!(), + }; - newtons_method_output - .into_iter() - .map(|x| PlotPoint::new(x, self.function.get(0, x))) - .collect() - } + newtons_method_output + .into_iter() + .map(|x| PlotPoint::new(x, self.function.get(0, x))) + .collect() + } - /// Does the calculations and stores results in `self` - pub fn calculate( - &mut self, width_changed: bool, min_max_changed: bool, did_zoom: bool, - settings: AppSettings, - ) { - if self.test_result.is_some() | self.function.is_none() { - return; - } + /// Does the calculations and stores results in `self` + pub fn calculate( + &mut self, + width_changed: bool, + min_max_changed: bool, + did_zoom: bool, + settings: AppSettings, + ) { + if self.test_result.is_some() | self.function.is_none() { + return; + } - let resolution = (settings.max_x - settings.min_x) / (settings.plot_width as f64); - debug_assert!(resolution > 0.0); - let resolution_iter = step_helper(settings.plot_width + 1, settings.min_x, resolution); + let resolution = (settings.max_x - settings.min_x) / (settings.plot_width as f64); + debug_assert!(resolution > 0.0); + let resolution_iter = step_helper(settings.plot_width + 1, settings.min_x, resolution); - // Makes sure proper arguments are passed when integral is enabled - if self.integral && settings.integral_changed { - self.clear_integral(); - } + // Makes sure proper arguments are passed when integral is enabled + if self.integral && settings.integral_changed { + self.clear_integral(); + } - if width_changed | min_max_changed | did_zoom { - self.clear_back(); - self.clear_derivative(); - self.clear_nth(); - } + if width_changed | min_max_changed | did_zoom { + self.clear_back(); + self.clear_derivative(); + self.clear_nth(); + } - if self.back_data.is_empty() { - let data: Vec = resolution_iter - .clone() - .into_iter() - .map(|x| PlotPoint::new(x, self.function.get(0, x))) - .collect(); - debug_assert_eq!(data.len(), settings.plot_width + 1); + if self.back_data.is_empty() { + let data: Vec = resolution_iter + .clone() + .into_iter() + .map(|x| PlotPoint::new(x, self.function.get(0, x))) + .collect(); + debug_assert_eq!(data.len(), settings.plot_width + 1); - self.back_data = data; - } + self.back_data = data; + } - if self.derivative_data.is_empty() { - self.function.generate_derivative(1); - let data: Vec = resolution_iter - .clone() - .into_iter() - .map(|x| PlotPoint::new(x, self.function.get(1, x))) - .collect(); - debug_assert_eq!(data.len(), settings.plot_width + 1); - self.derivative_data = data; - } + if self.derivative_data.is_empty() { + self.function.generate_derivative(1); + let data: Vec = resolution_iter + .clone() + .into_iter() + .map(|x| PlotPoint::new(x, self.function.get(1, x))) + .collect(); + debug_assert_eq!(data.len(), settings.plot_width + 1); + self.derivative_data = data; + } - if self.nth_derviative && self.nth_derivative_data.is_none() { - let data: Vec = resolution_iter - .into_iter() - .map(|x| PlotPoint::new(x, self.function.get(self.curr_nth, x))) - .collect(); - debug_assert_eq!(data.len(), settings.plot_width + 1); - self.nth_derivative_data = Some(data); - } + if self.nth_derviative && self.nth_derivative_data.is_none() { + let data: Vec = resolution_iter + .into_iter() + .map(|x| PlotPoint::new(x, self.function.get(self.curr_nth, x))) + .collect(); + debug_assert_eq!(data.len(), settings.plot_width + 1); + self.nth_derivative_data = Some(data); + } - if self.integral { - if self.integral_data.is_none() { - let (data, area) = self.integral_rectangles( - settings.integral_min_x, - settings.integral_max_x, - settings.riemann_sum, - settings.integral_num, - ); + if self.integral { + if self.integral_data.is_none() { + let (data, area) = self.integral_rectangles( + settings.integral_min_x, + settings.integral_max_x, + settings.riemann_sum, + settings.integral_num, + ); - self.integral_data = Some(( - data.into_iter().map(|(x, y)| Bar::new(x, y)).collect(), - area, - )); - } - } else { - self.clear_integral(); - } + self.integral_data = Some(( + data.into_iter().map(|(x, y)| Bar::new(x, y)).collect(), + area, + )); + } + } else { + self.clear_integral(); + } - let threshold: f64 = resolution / 2.0; - let x_range = settings.min_x..settings.max_x; + let threshold: f64 = resolution / 2.0; + let x_range = settings.min_x..settings.max_x; - // Calculates extrema - if settings.do_extrema && (min_max_changed | self.extrema_data.is_empty()) { - self.extrema_data = self.newtons_method_helper(threshold, 1, &x_range); - } + // Calculates extrema + if settings.do_extrema && (min_max_changed | self.extrema_data.is_empty()) { + self.extrema_data = self.newtons_method_helper(threshold, 1, &x_range); + } - // Calculates roots - if settings.do_roots && (min_max_changed | self.root_data.is_empty()) { - self.root_data = self.newtons_method_helper(threshold, 0, &x_range); - } - } + // Calculates roots + if settings.do_roots && (min_max_changed | self.root_data.is_empty()) { + self.root_data = self.newtons_method_helper(threshold, 0, &x_range); + } + } - /// Displays the function's output on PlotUI `plot_ui` with settings `settings`. - /// Returns an `Option` of the calculated integral. - pub fn display( - &self, plot_ui: &mut PlotUi, settings: &AppSettings, main_plot_color: Color32, - ) -> Option { - if self.test_result.is_some() | self.function.is_none() { - return None; - } + /// Displays the function's output on PlotUI `plot_ui` with settings `settings`. + /// Returns an `Option` of the calculated integral. + pub fn display( + &self, + plot_ui: &mut PlotUi, + settings: &AppSettings, + main_plot_color: Color32, + ) -> Option { + if self.test_result.is_some() | self.function.is_none() { + return None; + } - let integral_step = - (settings.integral_max_x - settings.integral_min_x) / (settings.integral_num as f64); - debug_assert!(integral_step > 0.0); + let integral_step = + (settings.integral_max_x - settings.integral_min_x) / (settings.integral_num as f64); + debug_assert!(integral_step > 0.0); - let step = (settings.max_x - settings.min_x) / (settings.plot_width as f64); - debug_assert!(step > 0.0); + let step = (settings.max_x - settings.min_x) / (settings.plot_width as f64); + debug_assert!(step > 0.0); - // Plot back data - if !self.back_data.is_empty() { - if self.integral && (step >= integral_step) { - plot_ui.line( - self.back_data - .iter() - .filter(|value| { - (value.x > settings.integral_min_x) - && (settings.integral_max_x > value.x) - }) - .cloned() - .collect::>() - .to_line() - .stroke(epaint::Stroke::NONE) - .color(Color32::from_rgb(4, 4, 255)) - .fill(0.0), - ); - } - plot_ui.line( - self.back_data - .clone() - .to_line() - .stroke(egui::Stroke::new(4.0, main_plot_color)), - ); - } + // Plot back data + if !self.back_data.is_empty() { + if self.integral && (step >= integral_step) { + plot_ui.line( + self.back_data + .iter() + .filter(|value| { + (value.x > settings.integral_min_x) + && (settings.integral_max_x > value.x) + }) + .cloned() + .collect::>() + .to_line() + .stroke(epaint::Stroke::NONE) + .color(Color32::from_rgb(4, 4, 255)) + .fill(0.0), + ); + } + plot_ui.line( + self.back_data + .clone() + .to_line() + .stroke(egui::Stroke::new(4.0, main_plot_color)), + ); + } - // Plot derivative data - if self.derivative && !self.derivative_data.is_empty() { - plot_ui.line(self.derivative_data.clone().to_line().color(Color32::GREEN)); - } + // Plot derivative data + if self.derivative && !self.derivative_data.is_empty() { + plot_ui.line(self.derivative_data.clone().to_line().color(Color32::GREEN)); + } - // Plot extrema points - if settings.do_extrema && !self.extrema_data.is_empty() { - plot_ui.points( - self.extrema_data - .clone() - .to_points() - .color(Color32::YELLOW) - .radius(5.0), // Radius of points of Extrema - ); - } + // Plot extrema points + if settings.do_extrema && !self.extrema_data.is_empty() { + plot_ui.points( + self.extrema_data + .clone() + .to_points() + .color(Color32::YELLOW) + .radius(5.0), // Radius of points of Extrema + ); + } - // Plot roots points - if settings.do_roots && !self.root_data.is_empty() { - plot_ui.points( - self.root_data - .clone() - .to_points() - .color(Color32::LIGHT_BLUE) - .radius(5.0), // Radius of points of Roots - ); - } + // Plot roots points + if settings.do_roots && !self.root_data.is_empty() { + plot_ui.points( + self.root_data + .clone() + .to_points() + .color(Color32::LIGHT_BLUE) + .radius(5.0), // Radius of points of Roots + ); + } - if self.nth_derviative - && let Some(ref nth_derviative) = self.nth_derivative_data - { - plot_ui.line(nth_derviative.clone().to_line().color(Color32::DARK_RED)); - } + if self.nth_derviative + && let Some(ref nth_derviative) = self.nth_derivative_data + { + plot_ui.line(nth_derviative.clone().to_line().color(Color32::DARK_RED)); + } - // Plot integral data - match &self.integral_data { - Some(integral_data) => { - if integral_step > step { - plot_ui.bar_chart( - BarChart::new(integral_data.0.clone()) - .color(Color32::BLUE) - .width(integral_step), - ); - } + // Plot integral data + match &self.integral_data { + Some(integral_data) => { + if integral_step > step { + plot_ui.bar_chart( + BarChart::new(integral_data.0.clone()) + .color(Color32::BLUE) + .width(integral_step), + ); + } - // return value rounded to 8 decimal places - Some(emath::round_to_decimals(integral_data.1, 8)) - } - None => None, - } - } + // return value rounded to 8 decimal places + Some(emath::round_to_decimals(integral_data.1, 8)) + } + None => None, + } + } - /// Invalidate entire cache - fn invalidate_whole(&mut self) { - self.clear_back(); - self.clear_integral(); - self.clear_derivative(); - self.clear_nth(); - self.clear_extrema(); - self.clear_roots(); - } + /// Invalidate entire cache + fn invalidate_whole(&mut self) { + self.clear_back(); + self.clear_integral(); + self.clear_derivative(); + self.clear_nth(); + self.clear_extrema(); + self.clear_roots(); + } - /// Invalidate `back` data - #[inline] - fn clear_back(&mut self) { self.back_data.clear(); } + /// Invalidate `back` data + #[inline] + fn clear_back(&mut self) { + self.back_data.clear(); + } - /// Invalidate Integral data - #[inline] - fn clear_integral(&mut self) { self.integral_data = None; } + /// Invalidate Integral data + #[inline] + fn clear_integral(&mut self) { + self.integral_data = None; + } - /// Invalidate Derivative data - #[inline] - fn clear_derivative(&mut self) { self.derivative_data.clear(); } + /// Invalidate Derivative data + #[inline] + fn clear_derivative(&mut self) { + self.derivative_data.clear(); + } - /// Invalidates `n`th derivative data - #[inline] - fn clear_nth(&mut self) { self.nth_derivative_data = None } + /// Invalidates `n`th derivative data + #[inline] + fn clear_nth(&mut self) { + self.nth_derivative_data = None + } - /// Invalidate extrema data - #[inline] - fn clear_extrema(&mut self) { self.extrema_data.clear() } + /// Invalidate extrema data + #[inline] + fn clear_extrema(&mut self) { + self.extrema_data.clear() + } - /// Invalidate root data - #[inline] - fn clear_roots(&mut self) { self.root_data.clear() } + /// Invalidate root data + #[inline] + fn clear_roots(&mut self) { + self.root_data.clear() + } } diff --git a/src/function_manager.rs b/src/function_manager.rs index 26c850f..9981c88 100644 --- a/src/function_manager.rs +++ b/src/function_manager.rs @@ -1,8 +1,5 @@ use crate::{ - consts::COLORS, - function_entry::FunctionEntry, - misc::{random_u64}, - widgets::widgets_ontop, + consts::COLORS, function_entry::FunctionEntry, misc::random_u64, widgets::widgets_ontop, }; use egui::{Button, Id, Key, Modifiers, TextEdit, WidgetText}; use emath::vec2; @@ -15,21 +12,20 @@ use std::ops::BitXorAssign; type Functions = Vec<(Id, FunctionEntry)>; pub struct FunctionManager { - functions: Functions, + functions: Functions, } impl Default for FunctionManager { - fn default() -> Self { - let mut vec: Functions = Vec::with_capacity(COLORS.len()); - vec.push(( - Id::new(11414819524356497634 as u64), // Random number here to avoid call to crate::misc::random_u64() - FunctionEntry::default(), - )); - Self { functions: vec } - } + fn default() -> Self { + let mut vec: Functions = Vec::with_capacity(COLORS.len()); + vec.push(( + Id::new(11414819524356497634_u64), // Random number here to avoid call to crate::misc::random_u64() + FunctionEntry::default(), + )); + Self { functions: vec } + } } - impl Serialize for FunctionManager { fn serialize(&self, serializer: S) -> Result where @@ -56,7 +52,7 @@ impl<'de> Deserialize<'de> for FunctionManager { #[derive(Deserialize)] struct Helper(Vec<(Id, FunctionEntry)>); - let helper = Helper::deserialize(deserializer)?; + let helper = Helper::deserialize(deserializer)?; Ok(FunctionManager { functions: helper.0.to_vec(), @@ -66,207 +62,212 @@ impl<'de> Deserialize<'de> for FunctionManager { /// Function that creates button that's used with the `button_area` fn button_area_button<'a>(text: impl Into) -> Button<'a> { - Button::new(text).frame(false) + Button::new(text).frame(false) } impl FunctionManager { - #[inline] - fn get_hash(&self) -> u64 { - let mut hasher = DefaultHasher::new(); - self.functions.hash(&mut hasher); - hasher.finish() - } + #[inline] + fn get_hash(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.functions.hash(&mut hasher); + hasher.finish() + } - /// Displays function entries alongside returning whether or not functions have been modified - pub fn display_entries(&mut self, ui: &mut egui::Ui) -> bool { - let initial_hash = self.get_hash(); + /// Displays function entries alongside returning whether or not functions have been modified + pub fn display_entries(&mut self, ui: &mut egui::Ui) -> bool { + let initial_hash = self.get_hash(); - let can_remove = self.functions.len() > 1; + let can_remove = self.functions.len() > 1; - let available_width = ui.available_width(); - let mut remove_i: Option = None; - let target_size = vec2(available_width, crate::consts::FONT_SIZE); - for (i, (te_id, function)) in self.functions.iter_mut().map(|(a, b)| (*a, b)).enumerate() { - let mut new_string = function.autocomplete.string.clone(); - function.update_string(&new_string); + let available_width = ui.available_width(); + let mut remove_i: Option = None; + let target_size = vec2(available_width, crate::consts::FONT_SIZE); + for (i, (te_id, function)) in self.functions.iter_mut().map(|(a, b)| (*a, b)).enumerate() { + let mut new_string = function.autocomplete.string.clone(); + function.update_string(&new_string); - let mut movement: Movement = Movement::default(); + let mut movement: Movement = Movement::default(); - let size_multiplier = vec2(1.0, { - let had_focus = ui.memory(|x| x.has_focus(te_id)); - (ui.ctx().animate_bool(te_id, had_focus) * 1.5) + 1.0 - }); + let size_multiplier = vec2(1.0, { + let had_focus = ui.memory(|x| x.has_focus(te_id)); + (ui.ctx().animate_bool(te_id, had_focus) * 1.5) + 1.0 + }); - let re = ui.add_sized( - target_size * size_multiplier, - egui::TextEdit::singleline(&mut new_string) - .hint_forward(true) // Make the hint appear after the last text in the textbox - .lock_focus(true) - .id(te_id) // Set widget's id to `te_id` - .hint_text( - // If there's a single hint, go ahead and apply the hint here, if not, set the hint to an empty string - function.autocomplete.hint.single().unwrap_or(""), - ), - ); + let re = ui.add_sized( + target_size * size_multiplier, + egui::TextEdit::singleline(&mut new_string) + .hint_forward(true) // Make the hint appear after the last text in the textbox + .lock_focus(true) + .id(te_id) // Set widget's id to `te_id` + .hint_text( + // If there's a single hint, go ahead and apply the hint here, if not, set the hint to an empty string + function.autocomplete.hint.single().unwrap_or(""), + ), + ); - // Only keep valid chars - new_string.retain(crate::misc::is_valid_char); + // Only keep valid chars + new_string.retain(crate::misc::is_valid_char); - // If not fully open, return here as buttons cannot yet be displayed, therefore the user is inable to mark it for deletion - let animate_bool = ui.ctx().animate_bool(te_id, re.has_focus()); - if animate_bool == 1.0 { - function.autocomplete.update_string(&new_string); + // If not fully open, return here as buttons cannot yet be displayed, therefore the user is inable to mark it for deletion + let animate_bool = ui.ctx().animate_bool(te_id, re.has_focus()); + if animate_bool == 1.0 { + function.autocomplete.update_string(&new_string); - if function.autocomplete.hint.is_some() { - // only register up and down arrow movements if hint is type `Hint::Many` - if !function.autocomplete.hint.is_single() { - let (arrow_down, arrow_up) = ui.input(|x| { - (x.key_pressed(Key::ArrowDown), x.key_pressed(Key::ArrowUp)) - }); - if arrow_down { - movement = Movement::Down; - } else if arrow_up { - movement = Movement::Up; - } - } + if function.autocomplete.hint.is_some() { + // only register up and down arrow movements if hint is type `Hint::Many` + if !function.autocomplete.hint.is_single() { + let (arrow_down, arrow_up) = ui.input(|x| { + (x.key_pressed(Key::ArrowDown), x.key_pressed(Key::ArrowUp)) + }); + if arrow_down { + movement = Movement::Down; + } else if arrow_up { + movement = Movement::Up; + } + } - // Put here so these key presses don't interact with other elements - let movement_complete_action = ui.input_mut(|x| { - x.consume_key(Modifiers::NONE, Key::Enter) - | x.consume_key(Modifiers::NONE, Key::Tab) - | x.key_pressed(Key::ArrowRight) - }); + // Put here so these key presses don't interact with other elements + let movement_complete_action = ui.input_mut(|x| { + x.consume_key(Modifiers::NONE, Key::Enter) + | x.consume_key(Modifiers::NONE, Key::Tab) + | x.key_pressed(Key::ArrowRight) + }); - if movement_complete_action { - movement = Movement::Complete; - } + if movement_complete_action { + movement = Movement::Complete; + } - // Register movement and apply proper changes - function.autocomplete.register_movement(&movement); + // Register movement and apply proper changes + function.autocomplete.register_movement(&movement); - if movement != Movement::Complete - && let Some(hints) = function.autocomplete.hint.many() - { - let mut clicked = false; + if movement != Movement::Complete + && let Some(hints) = function.autocomplete.hint.many() + { + let mut clicked = false; - let autocomplete_popup_id = Id::new("autocomplete popup"); + let autocomplete_popup_id = Id::new("autocomplete popup"); - egui::popup_below_widget(ui, autocomplete_popup_id.clone(), &re, |ui| { - hints.iter().enumerate().for_each(|(i, candidate)| { - if ui - .selectable_label(i == function.autocomplete.i, *candidate) - .clicked() - { - clicked = true; - function.autocomplete.i = i; - } - }); - }); + egui::popup_below_widget(ui, autocomplete_popup_id, &re, |ui| { + hints.iter().enumerate().for_each(|(i, candidate)| { + if ui + .selectable_label(i == function.autocomplete.i, *candidate) + .clicked() + { + clicked = true; + function.autocomplete.i = i; + } + }); + }); - if clicked { - function - .autocomplete - .apply_hint(hints[function.autocomplete.i]); + if clicked { + function + .autocomplete + .apply_hint(hints[function.autocomplete.i]); - movement = Movement::Complete; - } else { - ui.memory_mut(|x| x.open_popup(autocomplete_popup_id.clone())); - } - } + movement = Movement::Complete; + } else { + ui.memory_mut(|x| x.open_popup(autocomplete_popup_id)); + } + } - // Push cursor to end if needed - if movement == Movement::Complete { - let mut state = - unsafe { TextEdit::load_state(ui.ctx(), te_id).unwrap_unchecked() }; - let ccursor = egui::text::CCursor::new(function.autocomplete.string.len()); - state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor))); - TextEdit::store_state(ui.ctx(), te_id, state); - } - } + // Push cursor to end if needed + if movement == Movement::Complete { + let mut state = + unsafe { TextEdit::load_state(ui.ctx(), te_id).unwrap_unchecked() }; + let ccursor = egui::text::CCursor::new(function.autocomplete.string.len()); + state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor))); + TextEdit::store_state(ui.ctx(), te_id, state); + } + } - /// The y offset multiplier of the `buttons_area` area - const BUTTONS_Y_OFFSET: f32 = 1.32; - const Y_OFFSET: f32 = crate::consts::FONT_SIZE * BUTTONS_Y_OFFSET; + /// The y offset multiplier of the `buttons_area` area + const BUTTONS_Y_OFFSET: f32 = 1.32; + const Y_OFFSET: f32 = crate::consts::FONT_SIZE * BUTTONS_Y_OFFSET; - widgets_ontop(ui, Id::new(i), &re, Y_OFFSET, |ui| { - ui.horizontal(|ui| { - // There's more than 1 function! Functions can now be deleted - if ui - .add_enabled(can_remove, button_area_button("✖")) - .on_hover_text("Delete Function") - .clicked() - { - remove_i = Some(i); - } + widgets_ontop(ui, Id::new(i), &re, Y_OFFSET, |ui| { + ui.horizontal(|ui| { + // There's more than 1 function! Functions can now be deleted + if ui + .add_enabled(can_remove, button_area_button("✖")) + .on_hover_text("Delete Function") + .clicked() + { + remove_i = Some(i); + } - ui.add_enabled_ui(function.is_some(), |ui| { - // Toggle integral being enabled or not - function.integral.bitxor_assign( - ui.add(button_area_button("∫")) - .on_hover_text(match function.integral { - true => "Don't integrate", - false => "Integrate", - }) - .clicked(), - ); + ui.add_enabled_ui(function.is_some(), |ui| { + // Toggle integral being enabled or not + function.integral.bitxor_assign( + ui.add(button_area_button("∫")) + .on_hover_text(match function.integral { + true => "Don't integrate", + false => "Integrate", + }) + .clicked(), + ); - // Toggle showing the derivative (even though it's already calculated this option just toggles if it's displayed or not) - function.derivative.bitxor_assign( - ui.add(button_area_button("d/dx")) - .on_hover_text(match function.derivative { - true => "Don't Differentiate", - false => "Differentiate", - }) - .clicked(), - ); + // Toggle showing the derivative (even though it's already calculated this option just toggles if it's displayed or not) + function.derivative.bitxor_assign( + ui.add(button_area_button("d/dx")) + .on_hover_text(match function.derivative { + true => "Don't Differentiate", + false => "Differentiate", + }) + .clicked(), + ); - // Toggle showing the settings window - function.settings_opened.bitxor_assign( - ui.add(button_area_button("⚙")) - .on_hover_text(match function.settings_opened { - true => "Close Settings", - false => "Open Settings", - }) - .clicked(), - ); - }); - }); - }); - } + // Toggle showing the settings window + function.settings_opened.bitxor_assign( + ui.add(button_area_button("⚙")) + .on_hover_text(match function.settings_opened { + true => "Close Settings", + false => "Open Settings", + }) + .clicked(), + ); + }); + }); + }); + } - function.settings_window(ui.ctx()); - } + function.settings_window(ui.ctx()); + } - // Remove function if the user requests it - if let Some(remove_i_unwrap) = remove_i { - self.functions.remove(remove_i_unwrap); - } + // Remove function if the user requests it + if let Some(remove_i_unwrap) = remove_i { + self.functions.remove(remove_i_unwrap); + } - let final_hash = self.get_hash(); + let final_hash = self.get_hash(); - initial_hash != final_hash - } + initial_hash != final_hash + } - /// Create and push new empty function entry - pub fn push_empty(&mut self) { - self.functions.push(( - Id::new(random_u64().expect("unable to generate random id")), - FunctionEntry::default(), - )); - } + /// Create and push new empty function entry + pub fn push_empty(&mut self) { + self.functions.push(( + Id::new(random_u64().expect("unable to generate random id")), + FunctionEntry::default(), + )); + } - /// Detect if any functions are using integrals - pub fn any_using_integral(&self) -> bool { - self.functions.iter().any(|(_, func)| func.integral) - } + /// Detect if any functions are using integrals + pub fn any_using_integral(&self) -> bool { + self.functions.iter().any(|(_, func)| func.integral) + } - #[inline] - pub fn len(&self) -> usize { self.functions.len() } + #[inline] + pub fn len(&self) -> usize { + self.functions.len() + } + #[inline] + pub fn get_entries_mut(&mut self) -> &mut Functions { + &mut self.functions + } - #[inline] - pub fn get_entries_mut(&mut self) -> &mut Functions { &mut self.functions } - - #[inline] - pub fn get_entries(&self) -> &Functions { &self.functions } + #[inline] + pub fn get_entries(&self) -> &Functions { + &self.functions + } } diff --git a/src/lib.rs b/src/lib.rs index 1a075ae..f2c1d2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,65 +10,65 @@ mod unicode_helper; mod widgets; pub use crate::{ - function_entry::{FunctionEntry, Riemann}, - math_app::AppSettings, - misc::{ - hashed_storage_create, hashed_storage_read, newtons_method, option_vec_printer, - step_helper, EguiHelper, HashBytes, - }, - unicode_helper::{to_chars_array, to_unicode_hash}, + function_entry::{FunctionEntry, Riemann}, + math_app::AppSettings, + misc::{ + EguiHelper, HashBytes, hashed_storage_create, hashed_storage_read, newtons_method, + option_vec_printer, step_helper, + }, + unicode_helper::{to_chars_array, to_unicode_hash}, }; cfg_if::cfg_if! { - if #[cfg(target_arch = "wasm32")] { - use wasm_bindgen::prelude::*; + if #[cfg(target_arch = "wasm32")] { + use wasm_bindgen::prelude::*; - use lol_alloc::{FreeListAllocator, LockedAllocator}; - #[global_allocator] - static ALLOCATOR: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); + use lol_alloc::{FreeListAllocator, LockedAllocator}; + #[global_allocator] + static ALLOCATOR: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); - use eframe::WebRunner; - // use tracing::metadata::LevelFilter; - #[derive(Clone)] - #[wasm_bindgen] - pub struct WebHandle { - runner: WebRunner, - } + use eframe::WebRunner; + // use tracing::metadata::LevelFilter; + #[derive(Clone)] + #[wasm_bindgen] + pub struct WebHandle { + runner: WebRunner, + } - #[wasm_bindgen] - impl WebHandle { - /// Installs a panic hook, then returns. - #[allow(clippy::new_without_default)] - #[wasm_bindgen(constructor)] - pub fn new() -> Self { - // eframe::WebLogger::init(LevelFilter::Debug).ok(); - tracing_wasm::set_as_global_default(); + #[wasm_bindgen] + impl WebHandle { + /// Installs a panic hook, then returns. + #[allow(clippy::new_without_default)] + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + // eframe::WebLogger::init(LevelFilter::Debug).ok(); + tracing_wasm::set_as_global_default(); - Self { - runner: WebRunner::new(), - } - } + Self { + runner: WebRunner::new(), + } + } - /// Call this once from JavaScript to start your app. - #[wasm_bindgen] - pub async fn start(&self, canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> { - self.runner - .start( - canvas_id, - eframe::WebOptions::default(), - Box::new(|cc| Box::new(math_app::MathApp::new(cc))), - ) - .await - } - } + /// Call this once from JavaScript to start your app. + #[wasm_bindgen] + pub async fn start(&self, canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> { + self.runner + .start( + canvas_id, + eframe::WebOptions::default(), + Box::new(|cc| Box::new(math_app::MathApp::new(cc))), + ) + .await + } + } - #[wasm_bindgen(start)] - pub async fn start() { - tracing::info!("Starting..."); + #[wasm_bindgen(start)] + pub async fn start() { + tracing::info!("Starting..."); - let web_handle = WebHandle::new(); - web_handle.start("canvas").await.unwrap() - } - } + let web_handle = WebHandle::new(); + web_handle.start("canvas").await.unwrap() + } + } } diff --git a/src/main.rs b/src/main.rs index d7a7c4e..0fc99a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,15 +12,15 @@ mod widgets; // For running the program natively! (Because why not?) #[cfg(not(target_arch = "wasm32"))] fn main() -> eframe::Result<()> { - let subscriber = tracing_subscriber::FmtSubscriber::builder() - .with_max_level(tracing::Level::INFO) - .finish(); + let subscriber = tracing_subscriber::FmtSubscriber::builder() + .with_max_level(tracing::Level::INFO) + .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - eframe::run_native( - "(Yet-to-be-named) Graphing Software", - eframe::NativeOptions::default(), - Box::new(|cc| Box::new(math_app::MathApp::new(cc))), - ) + eframe::run_native( + "(Yet-to-be-named) Graphing Software", + eframe::NativeOptions::default(), + Box::new(|cc| Box::new(math_app::MathApp::new(cc))), + ) } diff --git a/src/math_app.rs b/src/math_app.rs index 08e8758..123afa5 100644 --- a/src/math_app.rs +++ b/src/math_app.rs @@ -1,13 +1,13 @@ use crate::{ - consts::{build, BUILD_INFO, COLORS, DEFAULT_INTEGRAL_NUM, DEFAULT_MAX_X, DEFAULT_MIN_X}, - function_entry::Riemann, - function_manager::FunctionManager, - misc::option_vec_printer, + consts::{BUILD_INFO, COLORS, DEFAULT_INTEGRAL_NUM, DEFAULT_MAX_X, DEFAULT_MIN_X, build}, + function_entry::Riemann, + function_manager::FunctionManager, + misc::option_vec_printer, }; use eframe::App; use egui::{ - style::Margin, Button, CentralPanel, Color32, ComboBox, Context, DragValue, Frame, Key, Layout, - SidePanel, TopBottomPanel, Ui, Vec2, Window, + Button, CentralPanel, Color32, ComboBox, Context, DragValue, Frame, Key, Layout, SidePanel, + TopBottomPanel, Ui, Vec2, Window, style::Margin, }; use egui_plot::Plot; @@ -20,105 +20,107 @@ use std::{io::Read, ops::BitXorAssign}; /// Stores current settings/state of [`MathApp`] #[derive(Copy, Clone)] pub struct AppSettings { - /// Stores the type of Rienmann sum that should be calculated - pub riemann_sum: Riemann, + /// Stores the type of Rienmann sum that should be calculated + pub riemann_sum: Riemann, - /// Min and Max range for calculating an integral - pub integral_min_x: f64, + /// Min and Max range for calculating an integral + pub integral_min_x: f64, - /// Max value for calculating an - pub integral_max_x: f64, + /// Max value for calculating an + pub integral_max_x: f64, - /// Minimum x bound of plot - pub min_x: f64, + /// Minimum x bound of plot + pub min_x: f64, - /// Maximum x bound of plot - pub max_x: f64, + /// Maximum x bound of plot + pub max_x: f64, - /// Stores whether or not integral settings have changed - pub integral_changed: bool, + /// Stores whether or not integral settings have changed + pub integral_changed: bool, - /// Number of rectangles used to calculate integral - pub integral_num: usize, + /// Number of rectangles used to calculate integral + pub integral_num: usize, - /// Stores whether or not displaying extrema is enabled - pub do_extrema: bool, + /// Stores whether or not displaying extrema is enabled + pub do_extrema: bool, - /// Stores whether or not displaying roots is enabled - pub do_roots: bool, + /// Stores whether or not displaying roots is enabled + pub do_roots: bool, - /// Stores current plot pixel width - pub plot_width: usize, + /// Stores current plot pixel width + pub plot_width: usize, } impl Default for AppSettings { - /// Default implementation of `AppSettings`, this is how the application starts up - fn default() -> Self { - Self { - riemann_sum: Riemann::default(), - integral_min_x: DEFAULT_MIN_X, - integral_max_x: DEFAULT_MAX_X, - min_x: 0.0, - max_x: 0.0, - integral_changed: true, - integral_num: DEFAULT_INTEGRAL_NUM, - do_extrema: true, - do_roots: true, - plot_width: 0, - } - } + /// Default implementation of `AppSettings`, this is how the application starts up + fn default() -> Self { + Self { + riemann_sum: Riemann::default(), + integral_min_x: DEFAULT_MIN_X, + integral_max_x: DEFAULT_MAX_X, + min_x: 0.0, + max_x: 0.0, + integral_changed: true, + integral_num: DEFAULT_INTEGRAL_NUM, + do_extrema: true, + do_roots: true, + plot_width: 0, + } + } } /// Used to store the opened of windows/widgets struct Opened { - /// Help window - pub help: bool, + /// Help window + pub help: bool, - /// Info window - pub info: bool, + /// Info window + pub info: bool, - /// Sidepanel - pub side_panel: bool, + /// Sidepanel + pub side_panel: bool, - /// Welcome introduction - pub welcome: bool, + /// Welcome introduction + pub welcome: bool, } impl Default for Opened { - fn default() -> Opened { - Self { - help: false, - info: false, - side_panel: true, - welcome: true, - } - } + fn default() -> Opened { + Self { + help: false, + info: false, + side_panel: true, + welcome: true, + } + } } /// The actual application pub struct MathApp { - /// Stores vector of functions - functions: FunctionManager, + /// Stores vector of functions + functions: FunctionManager, - /// 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: (Option, Option), + /// 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: (Option, Option), - /// Stores opened windows/elements for later reference - opened: Opened, + /// Stores opened windows/elements for later reference + opened: Opened, - /// Stores settings (pretty self-explanatory) - settings: AppSettings, + /// Stores settings (pretty self-explanatory) + settings: AppSettings, } #[cfg(target_arch = "wasm32")] -fn get_window() -> web_sys::Window { web_sys::window().expect("Could not get web_sys window") } +fn get_window() -> web_sys::Window { + web_sys::window().expect("Could not get web_sys window") +} #[cfg(target_arch = "wasm32")] fn get_localstorage() -> web_sys::Storage { - get_window() - .local_storage() - .expect("failed to get localstorage1") - .expect("failed to get localstorage2") + get_window() + .local_storage() + .expect("failed to get localstorage1") + .expect("failed to get localstorage2") } #[cfg(target_arch = "wasm32")] @@ -127,342 +129,345 @@ const DATA_NAME: &str = "YTBN-DECOMPRESSED"; const FUNC_NAME: &str = "YTBN-FUNCTIONS"; impl MathApp { - #[allow(dead_code)] // This is used lol - /// Create new instance of [`MathApp`] and return it - pub fn new(cc: &eframe::CreationContext<'_>) -> Self { - #[cfg(threading)] - tracing::info!("Threading: Enabled"); + #[allow(dead_code)] // This is used lol + /// Create new instance of [`MathApp`] and return it + pub fn new(cc: &eframe::CreationContext<'_>) -> Self { + #[cfg(threading)] + tracing::info!("Threading: Enabled"); - #[cfg(not(threading))] - tracing::info!("Threading: Disabled"); + #[cfg(not(threading))] + tracing::info!("Threading: Disabled"); - tracing::info!("commit: {}", build::SHORT_COMMIT); + tracing::info!("commit: {}", build::SHORT_COMMIT); - tracing::info!("Initializing..."); - let start = Instant::now(); + tracing::info!("Initializing..."); + let start = Instant::now(); - cfg_if::cfg_if! { - if #[cfg(target_arch = "wasm32")] { + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { - tracing::info!("Web Info: {:?}", &cc.integration_info.web_info); + tracing::info!("Web Info: {:?}", &cc.integration_info.web_info); - fn get_storage_decompressed() -> Option> { - let data = get_localstorage().get_item(DATA_NAME).ok()??; - let (commit, cached_data) = crate::misc::hashed_storage_read(&data)?; + fn get_storage_decompressed() -> Option> { + let data = get_localstorage().get_item(DATA_NAME).ok()??; + let (commit, cached_data) = crate::misc::hashed_storage_read(&data)?; - if commit == unsafe { std::mem::transmute::<&str, crate::misc::HashBytes>(build::SHORT_COMMIT) } { - tracing::info!("Reading decompression cache. Bytes: {}", cached_data.len()); - return Some(cached_data); - } else { - None - } - } + if commit == unsafe { std::mem::transmute::<&str, crate::misc::HashBytes>(build::SHORT_COMMIT) } { + tracing::info!("Reading decompression cache. Bytes: {}", cached_data.len()); + return Some(cached_data); + } else { + None + } + } - fn load_functions() -> Option { - let data = get_localstorage().get_item(FUNC_NAME).ok()??; - let (commit, func_data) = crate::misc::hashed_storage_read(&data)?; + fn load_functions() -> Option { + let data = get_localstorage().get_item(FUNC_NAME).ok()??; + let (commit, func_data) = crate::misc::hashed_storage_read(&data)?; - if commit == unsafe { std::mem::transmute::<&str, &[u8]>(build::SHORT_COMMIT) } { - tracing::info!("Reading previous function data"); - let function_manager: FunctionManager = bincode::deserialize(&func_data).ok()?; - return Some(function_manager); - } else { - None - } - } + if commit == unsafe { std::mem::transmute::<&str, &[u8]>(build::SHORT_COMMIT) } { + tracing::info!("Reading previous function data"); + let function_manager: FunctionManager = bincode::deserialize(&func_data).ok()?; + return Some(function_manager); + } else { + None + } + } - } - } + } + } - fn decompress_fonts() -> epaint::text::FontDefinitions { - let mut data = Vec::new(); - let _ = ruzstd::StreamingDecoder::new( - &mut const { include_bytes!(concat!(env!("OUT_DIR"), "/compressed_data")).as_slice() }, - ) - .expect("unable to decode compressed data") - .read_to_end(&mut data) - .expect("unable to read compressed data"); + fn decompress_fonts() -> epaint::text::FontDefinitions { + let mut data = Vec::new(); + let _ = + ruzstd::StreamingDecoder::new( + &mut const { + include_bytes!(concat!(env!("OUT_DIR"), "/compressed_data")).as_slice() + }, + ) + .expect("unable to decode compressed data") + .read_to_end(&mut data) + .expect("unable to read compressed data"); - #[cfg(target = "wasm32")] - { - tracing::info!("Setting decompression cache"); - let commit: crate::misc::HashBytes = const { - unsafe { - std::mem::transmute::<&str, crate::misc::HashBytes>(build::SHORT_COMMIT) - } - }; - let saved_data = commit.hashed_storage_create(data); - tracing::info!("Bytes: {}", saved_data.len()); - get_localstorage() - .set_item(DATA_NAME, saved_data) - .expect("failed to set local storage cache"); - } + #[cfg(target = "wasm32")] + { + tracing::info!("Setting decompression cache"); + let commit: crate::misc::HashBytes = const { + unsafe { + std::mem::transmute::<&str, crate::misc::HashBytes>(build::SHORT_COMMIT) + } + }; + let saved_data = commit.hashed_storage_create(data); + tracing::info!("Bytes: {}", saved_data.len()); + get_localstorage() + .set_item(DATA_NAME, saved_data) + .expect("failed to set local storage cache"); + } - bincode::deserialize(data.as_slice()).expect("unable to deserialize bincode") - } + bincode::deserialize(data.as_slice()).expect("unable to deserialize bincode") + } - tracing::info!("Reading fonts..."); + tracing::info!("Reading fonts..."); - // Initialize fonts - // This used to be in the `update` method, but (after a ton of digging) this actually caused OOMs. that was a pain to debug - cc.egui_ctx.set_fonts({ - #[cfg(target = "wasm32")] - if let Some(Ok(data)) = - get_storage_decompressed().map(|data| bincode::deserialize(data.as_slice())) - { - data - } else { - decompress_fonts() - } + // Initialize fonts + // This used to be in the `update` method, but (after a ton of digging) this actually caused OOMs. that was a pain to debug + cc.egui_ctx.set_fonts({ + #[cfg(target = "wasm32")] + if let Some(Ok(data)) = + get_storage_decompressed().map(|data| bincode::deserialize(data.as_slice())) + { + data + } else { + decompress_fonts() + } - #[cfg(not(target = "wasm32"))] - decompress_fonts() - }); + #[cfg(not(target = "wasm32"))] + decompress_fonts() + }); - // Set dark mode by default - // cc.egui_ctx.set_visuals(crate::style::style()); + // Set dark mode by default + // cc.egui_ctx.set_visuals(crate::style::style()); - // Set spacing - // cc.egui_ctx.set_spacing(crate::style::SPACING); + // Set spacing + // cc.egui_ctx.set_spacing(crate::style::SPACING); - tracing::info!("Initialized! Took: {:?}", start.elapsed()); + tracing::info!("Initialized! Took: {:?}", start.elapsed()); - Self { - #[cfg(target_arch = "wasm32")] - functions: load_functions().unwrap_or_default(), + Self { + #[cfg(target_arch = "wasm32")] + functions: load_functions().unwrap_or_default(), - #[cfg(not(target_arch = "wasm32"))] - functions: FunctionManager::default(), + #[cfg(not(target_arch = "wasm32"))] + functions: FunctionManager::default(), - last_info: (None, None), - opened: Opened::default(), - settings: AppSettings::default(), - } - } + last_info: (None, None), + opened: Opened::default(), + settings: AppSettings::default(), + } + } - /// 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) - SidePanel::left("side_panel") - .resizable(false) - .show(ctx, |ui| { - let any_using_integral = self.functions.any_using_integral(); - let prev_sum = self.settings.riemann_sum; - // ComboBox for selecting what Riemann sum type to use - ui.add_enabled_ui(any_using_integral, |ui| { - let spacing_mut = ui.spacing_mut(); + /// 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) + SidePanel::left("side_panel") + .resizable(false) + .show(ctx, |ui| { + let any_using_integral = self.functions.any_using_integral(); + let prev_sum = self.settings.riemann_sum; + // ComboBox for selecting what Riemann sum type to use + ui.add_enabled_ui(any_using_integral, |ui| { + let spacing_mut = ui.spacing_mut(); - spacing_mut.item_spacing.x = 1.0; - spacing_mut.interact_size *= 0.5; - ComboBox::from_label("Riemann Sum") - .selected_text(self.settings.riemann_sum.to_string()) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.settings.riemann_sum, - Riemann::Left, - "Left", - ); - ui.selectable_value( - &mut self.settings.riemann_sum, - Riemann::Middle, - "Middle", - ); - ui.selectable_value( - &mut self.settings.riemann_sum, - Riemann::Right, - "Right", - ); - }); + spacing_mut.item_spacing.x = 1.0; + spacing_mut.interact_size *= 0.5; + ComboBox::from_label("Riemann Sum") + .selected_text(self.settings.riemann_sum.to_string()) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.settings.riemann_sum, + Riemann::Left, + "Left", + ); + ui.selectable_value( + &mut self.settings.riemann_sum, + Riemann::Middle, + "Middle", + ); + ui.selectable_value( + &mut self.settings.riemann_sum, + Riemann::Right, + "Right", + ); + }); - let riemann_changed = prev_sum != self.settings.riemann_sum; + let riemann_changed = prev_sum != self.settings.riemann_sum; - let min_x_old = self.settings.integral_min_x; - let max_x_old = self.settings.integral_max_x; + let min_x_old = self.settings.integral_min_x; + let max_x_old = self.settings.integral_max_x; - let (min_x_changed, max_x_changed) = ui - .horizontal(|ui: &mut Ui| { - // let spacing_mut = ui.spacing_mut(); + let (min_x_changed, max_x_changed) = ui + .horizontal(|ui: &mut Ui| { + // let spacing_mut = ui.spacing_mut(); - // spacing_mut.item_spacing = Vec2::new(1.0, 0.0); - // spacing_mut.interact_size *= 0.5; + // spacing_mut.item_spacing = Vec2::new(1.0, 0.0); + // spacing_mut.interact_size *= 0.5; - ui.label("Integral: ["); - let min_x_changed = ui - .add(DragValue::new(&mut self.settings.integral_min_x)) - .changed(); - ui.label(","); - let max_x_changed = ui - .add(DragValue::new(&mut self.settings.integral_max_x)) - .changed(); - ui.label("]"); - (min_x_changed, max_x_changed) - }) - .inner; + ui.label("Integral: ["); + let min_x_changed = ui + .add(DragValue::new(&mut self.settings.integral_min_x)) + .changed(); + ui.label(","); + let max_x_changed = ui + .add(DragValue::new(&mut self.settings.integral_max_x)) + .changed(); + ui.label("]"); + (min_x_changed, max_x_changed) + }) + .inner; - // Checks integral bounds, and if they are invalid, fix them - if self.settings.integral_min_x >= self.settings.integral_max_x { - if max_x_changed { - self.settings.integral_max_x = max_x_old; - } else if min_x_changed { - self.settings.integral_min_x = min_x_old; - } else { - // No clue how this would happen, but just in case - self.settings.integral_min_x = DEFAULT_MIN_X; - self.settings.integral_max_x = DEFAULT_MAX_X; - } - } + // Checks integral bounds, and if they are invalid, fix them + if self.settings.integral_min_x >= self.settings.integral_max_x { + if max_x_changed { + self.settings.integral_max_x = max_x_old; + } else if min_x_changed { + self.settings.integral_min_x = min_x_old; + } else { + // No clue how this would happen, but just in case + self.settings.integral_min_x = DEFAULT_MIN_X; + self.settings.integral_max_x = DEFAULT_MAX_X; + } + } - // Number of Rectangles for Riemann sum - let integral_num_changed = ui - .horizontal(|ui| { - let spacing_mut = ui.spacing_mut(); + // Number of Rectangles for Riemann sum + let integral_num_changed = ui + .horizontal(|ui| { + let spacing_mut = ui.spacing_mut(); - spacing_mut.item_spacing.x = 1.5; - ui.label("Interval:"); - ui.add(DragValue::new(&mut self.settings.integral_num)) - .changed() - }) - .inner; + spacing_mut.item_spacing.x = 1.5; + ui.label("Interval:"); + ui.add(DragValue::new(&mut self.settings.integral_num)) + .changed() + }) + .inner; - if integral_num_changed { - self.settings.integral_num = self.settings.integral_num.clamp(0, 500000); - } + if integral_num_changed { + self.settings.integral_num = self.settings.integral_num.clamp(0, 500000); + } - self.settings.integral_changed = any_using_integral - && (max_x_changed | min_x_changed | integral_num_changed | riemann_changed); - }); + self.settings.integral_changed = any_using_integral + && (max_x_changed | min_x_changed | integral_num_changed | riemann_changed); + }); - ui.horizontal(|ui| { - self.settings.do_extrema.bitxor_assign( - ui.add(Button::new("Extrema")) - .on_hover_text(match self.settings.do_extrema { - true => "Disable Displaying Extrema", - false => "Display Extrema", - }) - .clicked(), - ); + ui.horizontal(|ui| { + self.settings.do_extrema.bitxor_assign( + ui.add(Button::new("Extrema")) + .on_hover_text(match self.settings.do_extrema { + true => "Disable Displaying Extrema", + false => "Display Extrema", + }) + .clicked(), + ); - self.settings.do_roots.bitxor_assign( - ui.add(Button::new("Roots")) - .on_hover_text(match self.settings.do_roots { - true => "Disable Displaying Roots", - false => "Display Roots", - }) - .clicked(), - ); - }); + self.settings.do_roots.bitxor_assign( + ui.add(Button::new("Roots")) + .on_hover_text(match self.settings.do_roots { + true => "Disable Displaying Roots", + false => "Display Roots", + }) + .clicked(), + ); + }); - if self.functions.display_entries(ui) { - #[cfg(target_arch = "wasm32")] - { - tracing::info!("Saving function data"); - use crate::misc::{hashed_storage_create, HashBytes}; - let hash: HashBytes = - unsafe { std::mem::transmute::<&str, HashBytes>(build::SHORT_COMMIT) }; - let saved_data = hashed_storage_create( - hash, - &bincode::serialize(&self.functions) - .expect("unable to deserialize functions"), - ); - // tracing::info!("Bytes: {}", saved_data.len()); - get_localstorage() - .set_item(FUNC_NAME, &saved_data) - .expect("failed to set local function storage"); - } - } + if self.functions.display_entries(ui) { + #[cfg(target_arch = "wasm32")] + { + tracing::info!("Saving function data"); + use crate::misc::{HashBytes, hashed_storage_create}; + let hash: HashBytes = + unsafe { std::mem::transmute::<&str, HashBytes>(build::SHORT_COMMIT) }; + let saved_data = hashed_storage_create( + hash, + &bincode::serialize(&self.functions) + .expect("unable to deserialize functions"), + ); + // tracing::info!("Bytes: {}", saved_data.len()); + get_localstorage() + .set_item(FUNC_NAME, &saved_data) + .expect("failed to set local function storage"); + } + } - // Only render if there's enough space - if ui.available_height() > crate::consts::FONT_SIZE { - ui.with_layout(Layout::bottom_up(Align::Min), |ui| { - // Contents put in reverse order from bottom to top due to the 'buttom_up' layout + // Only render if there's enough space + if ui.available_height() > crate::consts::FONT_SIZE { + ui.with_layout(Layout::bottom_up(Align::Min), |ui| { + // Contents put in reverse order from bottom to top due to the 'buttom_up' layout - // Hyperlink to project's github - ui.hyperlink_to( - "I'm Open Source!", - "https://github.com/Titaniumtown/YTBN-Graphing-Software", - ); - }); - } - }); - } + // Hyperlink to project's github + ui.hyperlink_to( + "I'm Open Source!", + "https://github.com/Titaniumtown/YTBN-Graphing-Software", + ); + }); + } + }); + } } impl App for MathApp { - /// Called each time the UI needs repainting. - fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { - // start timer - let start = if self.opened.info { - Some(instant::Instant::now()) - } else { - // if disabled, clear the stored formatted time - self.last_info.1 = None; + /// Called each time the UI needs repainting. + fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { + // start timer + let start = if self.opened.info { + Some(instant::Instant::now()) + } else { + // if disabled, clear the stored formatted time + self.last_info.1 = None; - None - }; + None + }; - // If keyboard input isn't being grabbed, check for key combos - if !ctx.wants_keyboard_input() { - // If `H` key is pressed, toggle Side Panel - self.opened - .side_panel - .bitxor_assign(ctx.input_mut(|x| x.consume_key(egui::Modifiers::NONE, Key::H))); - } + // If keyboard input isn't being grabbed, check for key combos + if !ctx.wants_keyboard_input() { + // If `H` key is pressed, toggle Side Panel + self.opened + .side_panel + .bitxor_assign(ctx.input_mut(|x| x.consume_key(egui::Modifiers::NONE, Key::H))); + } - // Creates Top bar that contains some general options - TopBottomPanel::top("top_bar").show(ctx, |ui| { - ui.horizontal(|ui| { - // Button in top bar to toggle showing the side panel - self.opened.side_panel.bitxor_assign( - ui.add(Button::new("Panel")) - .on_hover_text(match self.opened.side_panel { - true => "Hide Side Panel", - false => "Show Side Panel", - }) - .clicked(), - ); + // Creates Top bar that contains some general options + TopBottomPanel::top("top_bar").show(ctx, |ui| { + ui.horizontal(|ui| { + // Button in top bar to toggle showing the side panel + self.opened.side_panel.bitxor_assign( + ui.add(Button::new("Panel")) + .on_hover_text(match self.opened.side_panel { + true => "Hide Side Panel", + false => "Show Side Panel", + }) + .clicked(), + ); - // Button to add a new function - if ui - .add_enabled( - COLORS.len() > self.functions.len(), - Button::new("Add Function"), - ) - .on_hover_text("Create and graph new function") - .clicked() - { - self.functions.push_empty(); - } + // Button to add a new function + if ui + .add_enabled( + COLORS.len() > self.functions.len(), + Button::new("Add Function"), + ) + .on_hover_text("Create and graph new function") + .clicked() + { + self.functions.push_empty(); + } - // Toggles opening the Help window - self.opened.help.bitxor_assign( - ui.add(Button::new("Help")) - .on_hover_text(match self.opened.help { - true => "Close Help Window", - false => "Open Help Window", - }) - .clicked(), - ); + // Toggles opening the Help window + self.opened.help.bitxor_assign( + ui.add(Button::new("Help")) + .on_hover_text(match self.opened.help { + true => "Close Help Window", + false => "Open Help Window", + }) + .clicked(), + ); - // Toggles opening the Info window - self.opened.info.bitxor_assign( - ui.add(Button::new("Info")) - .on_hover_text(match self.opened.info { - true => "Close Info Window", - false => "Open Info Window", - }) - .clicked(), - ); + // Toggles opening the Info window + self.opened.info.bitxor_assign( + ui.add(Button::new("Info")) + .on_hover_text(match self.opened.info { + true => "Close Info Window", + false => "Open Info Window", + }) + .clicked(), + ); - // Display Area and time of last frame - if let Some(ref area) = self.last_info.0 { - ui.label(area); - } - }); - }); + // Display Area and time of last frame + if let Some(ref area) = self.last_info.0 { + ui.label(area); + } + }); + }); - // Help window with information for users - Window::new("Help") + // Help window with information for users + Window::new("Help") .open(&mut self.opened.help) .default_pos([200.0, 200.0]) .resizable(false) @@ -489,9 +494,9 @@ impl App for MathApp { }); }); - // Welcome window - if self.opened.welcome { - let welcome_response = Window::new("Welcome") + // Welcome window + if self.opened.welcome { + let welcome_response = Window::new("Welcome") .anchor(Align2::CENTER_CENTER, Vec2::ZERO) .resizable(false) .collapsible(false) @@ -500,119 +505,119 @@ impl App for MathApp { ui.label("Welcome to the (Yet-to-be-named) Graphing Software!\n\nThis project aims to provide an intuitive experience graphing mathematical functions with features such as Integration, Differentiation, Extrema, Roots, and much more! (see the Help Window for more details)"); }); - if let Some(response) = welcome_response { - // if user clicks off welcome window, close it - if response.response.clicked_elsewhere() { - self.opened.welcome = false; - } - } - } + if let Some(response) = welcome_response { + // if user clicks off welcome window, close it + if response.response.clicked_elsewhere() { + self.opened.welcome = false; + } + } + } - // Window with information about the build and current commit - Window::new("Info") - .open(&mut self.opened.info) - .default_pos([200.0, 200.0]) - .resizable(false) - .collapsible(false) - .show(ctx, |ui| { - ui.add(egui::Label::new(BUILD_INFO)); + // Window with information about the build and current commit + Window::new("Info") + .open(&mut self.opened.info) + .default_pos([200.0, 200.0]) + .resizable(false) + .collapsible(false) + .show(ctx, |ui| { + ui.add(egui::Label::new(BUILD_INFO)); - if let Some(ref took) = self.last_info.1 { - ui.label(took); - } - }); + if let Some(ref took) = self.last_info.1 { + ui.label(took); + } + }); - // If side panel is enabled, show it. - if self.opened.side_panel { - self.side_panel(ctx); - } + // If side panel is enabled, show it. + if self.opened.side_panel { + self.side_panel(ctx); + } - // Central panel which contains the central plot (or an error created when parsing) - CentralPanel::default() - .frame(Frame { - inner_margin: Margin::symmetric(0.0, 0.0), - rounding: Rounding::ZERO, - // fill: crate::style::STYLE.window_fill(), - fill: Color32::from_gray(27), - ..Frame::none() - }) - .show(ctx, |ui| { - // Display an error if it exists - let errors_formatted: String = self - .functions - .get_entries() - .iter() - .map(|(_, func)| func.get_test_result()) - .enumerate() - .filter(|(_, error)| error.is_some()) - .map(|(i, error)| { - // use unwrap_unchecked as None Errors are already filtered out - unsafe { - format!("(Function #{}) {}\n", i, error.as_ref().unwrap_unchecked()) - } - }) - .join(""); + // Central panel which contains the central plot (or an error created when parsing) + CentralPanel::default() + .frame(Frame { + inner_margin: Margin::symmetric(0.0, 0.0), + rounding: Rounding::ZERO, + // fill: crate::style::STYLE.window_fill(), + fill: Color32::from_gray(27), + ..Frame::none() + }) + .show(ctx, |ui| { + // Display an error if it exists + let errors_formatted: String = self + .functions + .get_entries() + .iter() + .map(|(_, func)| func.get_test_result()) + .enumerate() + .filter(|(_, error)| error.is_some()) + .map(|(i, error)| { + // use unwrap_unchecked as None Errors are already filtered out + unsafe { + format!("(Function #{}) {}\n", i, error.as_ref().unwrap_unchecked()) + } + }) + .join(""); - if !errors_formatted.is_empty() { - ui.centered_and_justified(|ui| { - ui.heading(errors_formatted); - }); - return; - } + if !errors_formatted.is_empty() { + ui.centered_and_justified(|ui| { + ui.heading(errors_formatted); + }); + return; + } - let available_width: usize = (ui.available_width() as usize) + 1; // Used in later logic - let width_changed = available_width != self.settings.plot_width; - self.settings.plot_width = available_width; + let available_width: usize = (ui.available_width() as usize) + 1; // Used in later logic + let width_changed = available_width != self.settings.plot_width; + self.settings.plot_width = available_width; - // Create and setup plot - Plot::new("plot") - .set_margin_fraction(Vec2::ZERO) - .data_aspect(1.0) - .include_y(0) - .show(ui, |plot_ui| { - let (min_x, max_x): (f64, f64) = { - let bounds = plot_ui.plot_bounds(); - (bounds.min()[0], bounds.max()[0]) - }; + // Create and setup plot + Plot::new("plot") + .set_margin_fraction(Vec2::ZERO) + .data_aspect(1.0) + .include_y(0) + .show(ui, |plot_ui| { + let (min_x, max_x): (f64, f64) = { + let bounds = plot_ui.plot_bounds(); + (bounds.min()[0], bounds.max()[0]) + }; - let min_max_changed = - (min_x != self.settings.min_x) | (max_x != self.settings.max_x); - let did_zoom = (max_x - min_x).abs() - != (self.settings.max_x - self.settings.min_x).abs(); - self.settings.min_x = min_x; - self.settings.max_x = max_x; + let min_max_changed = + (min_x != self.settings.min_x) | (max_x != self.settings.max_x); + let did_zoom = (max_x - min_x).abs() + != (self.settings.max_x - self.settings.min_x).abs(); + self.settings.min_x = min_x; + self.settings.max_x = max_x; - self.functions - .get_entries_mut() - .iter_mut() - .for_each(|(_, function)| { - function.calculate( - width_changed, - min_max_changed, - did_zoom, - self.settings, - ) - }); + self.functions + .get_entries_mut() + .iter_mut() + .for_each(|(_, function)| { + function.calculate( + width_changed, + min_max_changed, + did_zoom, + self.settings, + ) + }); - let area: Vec> = self - .functions - .get_entries() - .iter() - .enumerate() - .map(|(i, (_, function))| { - function.display(plot_ui, &self.settings, COLORS[i]) - }) - .collect(); + let area: Vec> = self + .functions + .get_entries() + .iter() + .enumerate() + .map(|(i, (_, function))| { + function.display(plot_ui, &self.settings, COLORS[i]) + }) + .collect(); - self.last_info.0 = if area.iter().any(|e| e.is_some()) { - Some(format!("Area: {}", option_vec_printer(area.as_slice()))) - } else { - None - }; - }); - }); + self.last_info.0 = if area.iter().any(|e| e.is_some()) { + Some(format!("Area: {}", option_vec_printer(area.as_slice()))) + } else { + None + }; + }); + }); - // Calculate and store the last time it took to draw the frame - self.last_info.1 = start.map(|a| format!("Took: {}ms", a.elapsed().as_micros())); - } + // Calculate and store the last time it took to draw the frame + self.last_info.1 = start.map(|a| format!("Took: {}ms", a.elapsed().as_micros())); + } } diff --git a/src/misc.rs b/src/misc.rs index b9fa289..2d3db5d 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -1,4 +1,4 @@ -use base64::{engine::general_purpose, Engine as _}; +use base64::{Engine as _, engine::general_purpose}; use egui_plot::{Line, PlotPoint, PlotPoints, Points}; use emath::Pos2; use getrandom::getrandom; @@ -7,69 +7,69 @@ use parsing::FlatExWrapper; /// Implements traits that are useful when dealing with Vectors of egui's `Value` pub trait EguiHelper { - /// Converts to `egui::plot::Values` - fn to_values(self) -> PlotPoints; + /// Converts to `egui::plot::Values` + fn to_values(self) -> PlotPoints; - /// Converts to `egui::plot::Line` - fn to_line(self) -> Line; + /// Converts to `egui::plot::Line` + fn to_line(self) -> Line; - /// Converts to `egui::plot::Points` - fn to_points(self) -> Points; + /// Converts to `egui::plot::Points` + fn to_points(self) -> Points; - /// Converts Vector of Values into vector of tuples - fn to_tuple(self) -> Vec<(f64, f64)>; + /// Converts Vector of Values into vector of tuples + fn to_tuple(self) -> Vec<(f64, f64)>; } impl EguiHelper for Vec { - #[inline(always)] - fn to_values(self) -> PlotPoints { - PlotPoints::from(unsafe { std::mem::transmute::, Vec<[f64; 2]>>(self) }) - } + #[inline(always)] + fn to_values(self) -> PlotPoints { + PlotPoints::from(unsafe { std::mem::transmute::, Vec<[f64; 2]>>(self) }) + } - #[inline(always)] - fn to_line(self) -> Line { - Line::new(self.to_values()) - } + #[inline(always)] + fn to_line(self) -> Line { + Line::new(self.to_values()) + } - #[inline(always)] - fn to_points(self) -> Points { - Points::new(self.to_values()) - } + #[inline(always)] + fn to_points(self) -> Points { + Points::new(self.to_values()) + } - #[inline(always)] - fn to_tuple(self) -> Vec<(f64, f64)> { - unsafe { std::mem::transmute::, Vec<(f64, f64)>>(self) } - } + #[inline(always)] + fn to_tuple(self) -> Vec<(f64, f64)> { + unsafe { std::mem::transmute::, Vec<(f64, f64)>>(self) } + } } pub trait Offset { - fn offset_y(self, y_offset: f32) -> Pos2; - fn offset_x(self, x_offset: f32) -> Pos2; + fn offset_y(self, y_offset: f32) -> Pos2; + fn offset_x(self, x_offset: f32) -> Pos2; } impl Offset for Pos2 { - fn offset_y(self, y_offset: f32) -> Pos2 { - Pos2 { - x: self.x, - y: self.y + y_offset, - } - } + fn offset_y(self, y_offset: f32) -> Pos2 { + Pos2 { + x: self.x, + y: self.y + y_offset, + } + } - fn offset_x(self, x_offset: f32) -> Pos2 { - Pos2 { - x: self.x + x_offset, - y: self.y, - } - } + fn offset_x(self, x_offset: f32) -> Pos2 { + Pos2 { + x: self.x + x_offset, + y: self.y, + } + } } /* /// Rounds f64 to `n` decimal places pub fn decimal_round(x: f64, n: usize) -> f64 { - let large_number: f64 = 10.0_f64.powf(n as f64); // 10^n + let large_number: f64 = 10.0_f64.powf(n as f64); // 10^n - // round and devide in order to cutoff after the `n`th decimal place - (x * large_number).round() / large_number + // round and devide in order to cutoff after the `n`th decimal place + (x * large_number).round() / large_number } */ @@ -80,18 +80,21 @@ pub fn decimal_round(x: f64, n: usize) -> f64 { /// `f_1` is f'(x) aka the derivative of f(x) /// The function returns a Vector of `x` values where roots occur pub fn newtons_method_helper( - threshold: f64, range: &std::ops::Range, data: &[PlotPoint], f: &FlatExWrapper, - f_1: &FlatExWrapper, + threshold: f64, + range: &std::ops::Range, + data: &[PlotPoint], + f: &FlatExWrapper, + f_1: &FlatExWrapper, ) -> Vec { - data.iter() - .tuple_windows() - .filter(|(prev, curr)| prev.y.is_finite() && curr.y.is_finite()) - .filter(|(prev, curr)| prev.y.signum() != curr.y.signum()) - .map(|(start, _)| start.x) - .map(|x| newtons_method(f, f_1, x, range, threshold)) - .filter(|x| x.is_some()) - .map(|x| unsafe { x.unwrap_unchecked() }) - .collect() + data.iter() + .tuple_windows() + .filter(|(prev, curr)| prev.y.is_finite() && curr.y.is_finite()) + .filter(|(prev, curr)| prev.y.signum() != curr.y.signum()) + .map(|(start, _)| start.x) + .map(|x| newtons_method(f, f_1, x, range, threshold)) + .filter(|x| x.is_some()) + .map(|x| unsafe { x.unwrap_unchecked() }) + .collect() } /// `range` is the range of valid x values (used to stop calculation when @@ -99,64 +102,67 @@ pub fn newtons_method_helper( /// `f_1` is f'(x) aka the derivative of f(x) /// The function returns an `Option` of the x value at which a root occurs pub fn newtons_method( - f: &FlatExWrapper, f_1: &FlatExWrapper, start_x: f64, range: &std::ops::Range, - threshold: f64, + f: &FlatExWrapper, + f_1: &FlatExWrapper, + start_x: f64, + range: &std::ops::Range, + threshold: f64, ) -> Option { - let mut x1: f64 = start_x; - let mut x2: f64; - let mut derivative: f64; - loop { - derivative = f_1.eval(&[x1]); - if !derivative.is_finite() { - return None; - } + let mut x1: f64 = start_x; + let mut x2: f64; + let mut derivative: f64; + loop { + derivative = f_1.eval(&[x1]); + if !derivative.is_finite() { + return None; + } - x2 = x1 - (f.eval(&[x1]) / derivative); - if !x2.is_finite() | !range.contains(&x2) { - return None; - } + x2 = x1 - (f.eval(&[x1]) / derivative); + if !x2.is_finite() | !range.contains(&x2) { + return None; + } - // If below threshold, break - if (x2 - x1).abs() < threshold { - return Some(x2); - } + // If below threshold, break + if (x2 - x1).abs() < threshold { + return Some(x2); + } - x1 = x2; - } + x1 = x2; + } } /// Inputs `Vec>` and outputs a `String` containing a pretty representation of the Vector pub fn option_vec_printer(data: &[Option]) -> String { - let formatted: String = data - .iter() - .map(|item| match item { - Some(x) => x.to_string(), - None => "None".to_owned(), - }) - .join(", "); + let formatted: String = data + .iter() + .map(|item| match item { + Some(x) => x.to_string(), + None => "None".to_owned(), + }) + .join(", "); - format!("[{}]", formatted) + format!("[{}]", formatted) } /// Returns a vector of length `max_i` starting at value `min_x` with step of `step` pub fn step_helper(max_i: usize, min_x: f64, step: f64) -> Vec { - (0..max_i) - .map(move |x: usize| (x as f64 * step) + min_x) - .collect() + (0..max_i) + .map(move |x: usize| (x as f64 * step) + min_x) + .collect() } // TODO: use in hovering over points /// Attempts to see what variable `x` is almost #[allow(dead_code)] pub fn almost_variable(x: f64) -> Option { - const EPSILON: f32 = f32::EPSILON * 2.0; - if emath::almost_equal(x as f32, std::f32::consts::E, EPSILON) { - Some('e') - } else if emath::almost_equal(x as f32, std::f32::consts::PI, EPSILON) { - Some('π') - } else { - None - } + const EPSILON: f32 = f32::EPSILON * 2.0; + if emath::almost_equal(x as f32, std::f32::consts::E, EPSILON) { + Some('e') + } else if emath::almost_equal(x as f32, std::f32::consts::PI, EPSILON) { + Some('π') + } else { + None + } } pub const HASH_LENGTH: usize = 8; @@ -166,41 +172,41 @@ pub type HashBytes = [u8; HASH_LENGTH]; #[allow(dead_code)] pub fn hashed_storage_create(hashbytes: HashBytes, data: &[u8]) -> String { - let combined_data = [hashbytes.to_vec(), data.to_vec()].concat(); - general_purpose::STANDARD.encode(combined_data) + let combined_data = [hashbytes.to_vec(), data.to_vec()].concat(); + general_purpose::STANDARD.encode(combined_data) } #[allow(dead_code)] pub fn hashed_storage_read(data: &str) -> Option<(HashBytes, Vec)> { - // Decode base64 data - let decoded_bytes = general_purpose::STANDARD.decode(data).ok()?; + // Decode base64 data + let decoded_bytes = general_purpose::STANDARD.decode(data).ok()?; - // Make sure data is long enough to decode - if HASH_LENGTH > decoded_bytes.len() { - return None; - } + // Make sure data is long enough to decode + if HASH_LENGTH > decoded_bytes.len() { + return None; + } - // Split hash and data - let (hash_bytes, data_bytes) = decoded_bytes.split_at(HASH_LENGTH); + // Split hash and data + let (hash_bytes, data_bytes) = decoded_bytes.split_at(HASH_LENGTH); - // Convert hash bytes to HashBytes - let hash: HashBytes = hash_bytes.try_into().ok()?; + // Convert hash bytes to HashBytes + let hash: HashBytes = hash_bytes.try_into().ok()?; - Some((hash, data_bytes.to_vec())) + Some((hash, data_bytes.to_vec())) } /// Creates and returns random u64 pub fn random_u64() -> Result { - // Buffer of 8 `u8`s that are later merged into one u64 - let mut buf = [0u8; 8]; - // Populate buffer with random values - getrandom(&mut buf)?; - // Merge buffer into u64 - Ok(u64::from_be_bytes(buf)) + // Buffer of 8 `u8`s that are later merged into one u64 + let mut buf = [0u8; 8]; + // Populate buffer with random values + getrandom(&mut buf)?; + // Merge buffer into u64 + Ok(u64::from_be_bytes(buf)) } include!(concat!(env!("OUT_DIR"), "/valid_chars.rs")); pub fn is_valid_char(c: char) -> bool { - c.is_alphanumeric() | VALID_EXTRA_CHARS.contains(&c) + c.is_alphanumeric() | VALID_EXTRA_CHARS.contains(&c) } diff --git a/src/unicode_helper.rs b/src/unicode_helper.rs index 3f3c8b0..34bd2da 100644 --- a/src/unicode_helper.rs +++ b/src/unicode_helper.rs @@ -2,19 +2,19 @@ use itertools::Itertools; #[allow(dead_code)] pub fn to_unicode_hash(c: char) -> String { - c.escape_unicode() - .to_string() - .replace(r"\\u{", "") - .replace(['{', '}'], "") - .to_uppercase() + c.escape_unicode() + .to_string() + .replace(r"\\u{", "") + .replace(['{', '}'], "") + .to_uppercase() } #[allow(dead_code)] pub fn to_chars_array(chars: Vec) -> String { - "[".to_string() - + &chars - .iter() - .map(|c| format!("'{}'", c.escape_unicode())) - .join(", ") - + "]" + "[".to_string() + + &chars + .iter() + .map(|c| format!("'{}'", c.escape_unicode())) + .join(", ") + + "]" } diff --git a/src/widgets.rs b/src/widgets.rs index a6d1df6..aa67db9 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -3,12 +3,15 @@ use egui::{Id, InnerResponse}; /// Creates an area ontop of a widget with an y offset pub fn widgets_ontop( - ui: &egui::Ui, id: Id, re: &egui::Response, y_offset: f32, - add_contents: impl FnOnce(&mut egui::Ui) -> R, + ui: &egui::Ui, + id: Id, + re: &egui::Response, + y_offset: f32, + add_contents: impl FnOnce(&mut egui::Ui) -> R, ) -> InnerResponse { - let area = egui::Area::new(id) - .fixed_pos(re.rect.min.offset_y(y_offset)) - .order(egui::Order::Foreground); + let area = egui::Area::new(id) + .fixed_pos(re.rect.min.offset_y(y_offset)) + .order(egui::Order::Foreground); - area.show(ui.ctx(), |ui| add_contents(ui)) + area.show(ui.ctx(), |ui| add_contents(ui)) } diff --git a/tests/autocomplete.rs b/tests/autocomplete.rs index dd7a306..c0f0809 100644 --- a/tests/autocomplete.rs +++ b/tests/autocomplete.rs @@ -1,159 +1,159 @@ use parsing::{AutoComplete, Hint, Movement}; enum Action<'a> { - AssertIndex(usize), - AssertString(&'a str), - AssertHint(&'a str), - SetString(&'a str), - Move(Movement), + AssertIndex(usize), + AssertString(&'a str), + AssertHint(&'a str), + SetString(&'a str), + Move(Movement), } use Action::*; fn ac_tester(actions: &[Action]) { - let mut ac = AutoComplete::default(); - for action in actions.iter() { - match action { - AssertIndex(target_i) => { - if &ac.i != target_i { - panic!( - "AssertIndex failed: Current: '{}' Expected: '{}'", - ac.i, target_i - ) - } - } - AssertString(target_string) => { - if &ac.string != target_string { - panic!( - "AssertString failed: Current: '{}' Expected: '{}'", - ac.string, target_string - ) - } - } - AssertHint(target_hint) => match ac.hint { - Hint::None => { - if !target_hint.is_empty() { - panic!( - "AssertHint failed on `Hint::None`: Expected: {}", - target_hint - ); - } - } - Hint::Many(hints) => { - let hint = hints[ac.i]; - if &hint != target_hint { - panic!( - "AssertHint failed on `Hint::Many`: Current: '{}' (index: {}) Expected: '{}'", - hint, ac.i, target_hint - ) - } - } - Hint::Single(hint) => { - if hint != target_hint { - panic!( - "AssertHint failed on `Hint::Single`: Current: '{}' Expected: '{}'", - hint, target_hint - ) - } - } - }, - SetString(target_string) => { - ac.update_string(target_string); - } - Move(target_movement) => { - ac.register_movement(target_movement); - } - } - } + let mut ac = AutoComplete::default(); + for action in actions.iter() { + match action { + AssertIndex(target_i) => { + if &ac.i != target_i { + panic!( + "AssertIndex failed: Current: '{}' Expected: '{}'", + ac.i, target_i + ) + } + } + AssertString(target_string) => { + if &ac.string != target_string { + panic!( + "AssertString failed: Current: '{}' Expected: '{}'", + ac.string, target_string + ) + } + } + AssertHint(target_hint) => match ac.hint { + Hint::None => { + if !target_hint.is_empty() { + panic!( + "AssertHint failed on `Hint::None`: Expected: {}", + target_hint + ); + } + } + Hint::Many(hints) => { + let hint = hints[ac.i]; + if &hint != target_hint { + panic!( + "AssertHint failed on `Hint::Many`: Current: '{}' (index: {}) Expected: '{}'", + hint, ac.i, target_hint + ) + } + } + Hint::Single(hint) => { + if hint != target_hint { + panic!( + "AssertHint failed on `Hint::Single`: Current: '{}' Expected: '{}'", + hint, target_hint + ) + } + } + }, + SetString(target_string) => { + ac.update_string(target_string); + } + Move(target_movement) => { + ac.register_movement(target_movement); + } + } + } } #[test] fn single() { - ac_tester(&[ - SetString(""), - AssertHint("x^2"), - Move(Movement::Up), - AssertIndex(0), - AssertString(""), - AssertHint("x^2"), - Move(Movement::Down), - AssertIndex(0), - AssertString(""), - AssertHint("x^2"), - Move(Movement::Complete), - AssertString("x^2"), - AssertHint(""), - AssertIndex(0), - ]); + ac_tester(&[ + SetString(""), + AssertHint("x^2"), + Move(Movement::Up), + AssertIndex(0), + AssertString(""), + AssertHint("x^2"), + Move(Movement::Down), + AssertIndex(0), + AssertString(""), + AssertHint("x^2"), + Move(Movement::Complete), + AssertString("x^2"), + AssertHint(""), + AssertIndex(0), + ]); } #[test] fn multi() { - ac_tester(&[ - SetString("s"), - AssertHint("in("), - Move(Movement::Up), - AssertIndex(3), - AssertString("s"), - AssertHint("ignum("), - Move(Movement::Down), - AssertIndex(0), - AssertString("s"), - AssertHint("in("), - Move(Movement::Down), - AssertIndex(1), - AssertString("s"), - AssertHint("qrt("), - Move(Movement::Up), - AssertIndex(0), - AssertString("s"), - AssertHint("in("), - Move(Movement::Complete), - AssertString("sin("), - AssertHint(")"), - AssertIndex(0), - ]); + ac_tester(&[ + SetString("s"), + AssertHint("in("), + Move(Movement::Up), + AssertIndex(3), + AssertString("s"), + AssertHint("ignum("), + Move(Movement::Down), + AssertIndex(0), + AssertString("s"), + AssertHint("in("), + Move(Movement::Down), + AssertIndex(1), + AssertString("s"), + AssertHint("qrt("), + Move(Movement::Up), + AssertIndex(0), + AssertString("s"), + AssertHint("in("), + Move(Movement::Complete), + AssertString("sin("), + AssertHint(")"), + AssertIndex(0), + ]); } #[test] fn none() { - // string that should give no hints - let random = "qwert987gybhj"; - assert_eq!(parsing::generate_hint(random), &Hint::None); + // string that should give no hints + let random = "qwert987gybhj"; + assert_eq!(parsing::generate_hint(random), &Hint::None); - ac_tester(&[ - SetString(random), - AssertHint(""), - Move(Movement::Up), - AssertIndex(0), - AssertString(random), - AssertHint(""), - Move(Movement::Down), - AssertIndex(0), - AssertString(random), - AssertHint(""), - Move(Movement::Complete), - AssertString(random), - AssertHint(""), - AssertIndex(0), - ]); + ac_tester(&[ + SetString(random), + AssertHint(""), + Move(Movement::Up), + AssertIndex(0), + AssertString(random), + AssertHint(""), + Move(Movement::Down), + AssertIndex(0), + AssertString(random), + AssertHint(""), + Move(Movement::Complete), + AssertString(random), + AssertHint(""), + AssertIndex(0), + ]); } #[test] fn parens() { - ac_tester(&[ - SetString("sin(x"), - AssertHint(")"), - Move(Movement::Up), - AssertIndex(0), - AssertString("sin(x"), - AssertHint(")"), - Move(Movement::Down), - AssertIndex(0), - AssertString("sin(x"), - AssertHint(")"), - Move(Movement::Complete), - AssertString("sin(x)"), - AssertHint(""), - AssertIndex(0), - ]); + ac_tester(&[ + SetString("sin(x"), + AssertHint(")"), + Move(Movement::Up), + AssertIndex(0), + AssertString("sin(x"), + AssertHint(")"), + Move(Movement::Down), + AssertIndex(0), + AssertString("sin(x"), + AssertHint(")"), + Move(Movement::Complete), + AssertString("sin(x)"), + AssertHint(""), + AssertIndex(0), + ]); } diff --git a/tests/function.rs b/tests/function.rs index 689328e..f5912ba 100644 --- a/tests/function.rs +++ b/tests/function.rs @@ -1,262 +1,273 @@ use ytbn_graphing_software::{AppSettings, EguiHelper, FunctionEntry, Riemann}; fn app_settings_constructor( - sum: Riemann, integral_min_x: f64, integral_max_x: f64, pixel_width: usize, - integral_num: usize, min_x: f64, max_x: f64, + sum: Riemann, + integral_min_x: f64, + integral_max_x: f64, + pixel_width: usize, + integral_num: usize, + min_x: f64, + max_x: f64, ) -> AppSettings { - AppSettings { - riemann_sum: sum, - integral_min_x, - integral_max_x, - min_x, - max_x, - integral_changed: true, - integral_num, - do_extrema: false, - do_roots: false, - plot_width: pixel_width, - } + AppSettings { + riemann_sum: sum, + integral_min_x, + integral_max_x, + min_x, + max_x, + integral_changed: true, + integral_num, + do_extrema: false, + do_roots: false, + plot_width: pixel_width, + } } static BACK_TARGET: [(f64, f64); 11] = [ - (-1.0, 1.0), - (-0.8, 0.6400000000000001), - (-0.6, 0.36), - (-0.4, 0.16000000000000003), - (-0.19999999999999996, 0.03999999999999998), - (0.0, 0.0), - (0.19999999999999996, 0.03999999999999998), - (0.3999999999999999, 0.15999999999999992), - (0.6000000000000001, 0.3600000000000001), - (0.8, 0.6400000000000001), - (1.0, 1.0), + (-1.0, 1.0), + (-0.8, 0.6400000000000001), + (-0.6, 0.36), + (-0.4, 0.16000000000000003), + (-0.19999999999999996, 0.03999999999999998), + (0.0, 0.0), + (0.19999999999999996, 0.03999999999999998), + (0.3999999999999999, 0.15999999999999992), + (0.6000000000000001, 0.3600000000000001), + (0.8, 0.6400000000000001), + (1.0, 1.0), ]; static DERIVATIVE_TARGET: [(f64, f64); 11] = [ - (-1.0, -2.0), - (-0.8, -1.6), - (-0.6, -1.2), - (-0.4, -0.8), - (-0.19999999999999996, -0.3999999999999999), - (0.0, 0.0), - (0.19999999999999996, 0.3999999999999999), - (0.3999999999999999, 0.7999999999999998), - (0.6000000000000001, 1.2000000000000002), - (0.8, 1.6), - (1.0, 2.0), + (-1.0, -2.0), + (-0.8, -1.6), + (-0.6, -1.2), + (-0.4, -0.8), + (-0.19999999999999996, -0.3999999999999999), + (0.0, 0.0), + (0.19999999999999996, 0.3999999999999999), + (0.3999999999999999, 0.7999999999999998), + (0.6000000000000001, 1.2000000000000002), + (0.8, 1.6), + (1.0, 2.0), ]; #[cfg(test)] fn do_test(sum: Riemann, area_target: f64) { - let settings = app_settings_constructor(sum, -1.0, 1.0, 10, 10, -1.0, 1.0); + let settings = app_settings_constructor(sum, -1.0, 1.0, 10, 10, -1.0, 1.0); - let mut function = FunctionEntry::default(); - function.update_string("x^2"); - function.integral = true; - function.derivative = true; + let mut function = FunctionEntry::default(); + function.update_string("x^2"); + function.integral = true; + function.derivative = true; - let mut settings = settings; - { - function.calculate(true, true, false, settings); - assert!(!function.back_data.is_empty()); - assert_eq!(function.back_data.len(), settings.plot_width + 1); + let mut settings = settings; + { + function.calculate(true, true, false, settings); + assert!(!function.back_data.is_empty()); + assert_eq!(function.back_data.len(), settings.plot_width + 1); - assert!(function.integral); - assert!(function.derivative); + assert!(function.integral); + assert!(function.derivative); - assert_eq!(!function.root_data.is_empty(), settings.do_roots); - assert_eq!(!function.extrema_data.is_empty(), settings.do_extrema); - assert!(!function.derivative_data.is_empty()); - assert!(function.integral_data.is_some()); + assert_eq!(!function.root_data.is_empty(), settings.do_roots); + assert_eq!(!function.extrema_data.is_empty(), settings.do_extrema); + assert!(!function.derivative_data.is_empty()); + assert!(function.integral_data.is_some()); - assert_eq!(function.integral_data.clone().unwrap().1, area_target); + assert_eq!(function.integral_data.clone().unwrap().1, area_target); - let a = function.derivative_data.clone().to_tuple(); + let a = function.derivative_data.clone().to_tuple(); - assert_eq!(a.len(), DERIVATIVE_TARGET.len()); + assert_eq!(a.len(), DERIVATIVE_TARGET.len()); - for i in 0..a.len() { - if !emath::almost_equal(a[i].0 as f32, DERIVATIVE_TARGET[i].0 as f32, f32::EPSILON) - | !emath::almost_equal(a[i].1 as f32, DERIVATIVE_TARGET[i].1 as f32, f32::EPSILON) - { - panic!("Expected: {:?}\nGot: {:?}", a, DERIVATIVE_TARGET); - } - } + for i in 0..a.len() { + if !emath::almost_equal(a[i].0 as f32, DERIVATIVE_TARGET[i].0 as f32, f32::EPSILON) + | !emath::almost_equal(a[i].1 as f32, DERIVATIVE_TARGET[i].1 as f32, f32::EPSILON) + { + panic!("Expected: {:?}\nGot: {:?}", a, DERIVATIVE_TARGET); + } + } - let a_1 = function.back_data.clone().to_tuple(); + let a_1 = function.back_data.clone().to_tuple(); - assert_eq!(a_1.len(), BACK_TARGET.len()); + assert_eq!(a_1.len(), BACK_TARGET.len()); - assert_eq!(a.len(), BACK_TARGET.len()); + assert_eq!(a.len(), BACK_TARGET.len()); - for i in 0..a.len() { - if !emath::almost_equal(a_1[i].0 as f32, BACK_TARGET[i].0 as f32, f32::EPSILON) - | !emath::almost_equal(a_1[i].1 as f32, BACK_TARGET[i].1 as f32, f32::EPSILON) - { - panic!("Expected: {:?}\nGot: {:?}", a_1, BACK_TARGET); - } - } - } + for i in 0..a.len() { + if !emath::almost_equal(a_1[i].0 as f32, BACK_TARGET[i].0 as f32, f32::EPSILON) + | !emath::almost_equal(a_1[i].1 as f32, BACK_TARGET[i].1 as f32, f32::EPSILON) + { + panic!("Expected: {:?}\nGot: {:?}", a_1, BACK_TARGET); + } + } + } - { - settings.min_x += 1.0; - settings.max_x += 1.0; - function.calculate(true, true, false, settings); + { + settings.min_x += 1.0; + settings.max_x += 1.0; + function.calculate(true, true, false, settings); - let a = function - .derivative_data - .clone() - .to_tuple() - .iter() - .take(6) - .cloned() - .collect::>(); + let a = function + .derivative_data + .clone() + .to_tuple() + .iter() + .take(6) + .cloned() + .collect::>(); - let b = DERIVATIVE_TARGET - .iter() - .rev() - .take(6) - .rev() - .cloned() - .collect::>(); + let b = DERIVATIVE_TARGET + .iter() + .rev() + .take(6) + .rev() + .cloned() + .collect::>(); - assert_eq!(a.len(), b.len()); + assert_eq!(a.len(), b.len()); - for i in 0..a.len() { - if !emath::almost_equal(a[i].0 as f32, b[i].0 as f32, f32::EPSILON) - | !emath::almost_equal(a[i].1 as f32, b[i].1 as f32, f32::EPSILON) - { - panic!("Expected: {:?}\nGot: {:?}", a, b); - } - } + for i in 0..a.len() { + if !emath::almost_equal(a[i].0 as f32, b[i].0 as f32, f32::EPSILON) + | !emath::almost_equal(a[i].1 as f32, b[i].1 as f32, f32::EPSILON) + { + panic!("Expected: {:?}\nGot: {:?}", a, b); + } + } - let a_1 = function - .back_data - .clone() - .to_tuple() - .iter() - .take(6) - .cloned() - .collect::>(); + let a_1 = function + .back_data + .clone() + .to_tuple() + .iter() + .take(6) + .cloned() + .collect::>(); - let b_1 = BACK_TARGET - .iter() - .rev() - .take(6) - .rev() - .cloned() - .collect::>(); + let b_1 = BACK_TARGET + .iter() + .rev() + .take(6) + .rev() + .cloned() + .collect::>(); - assert_eq!(a_1.len(), b_1.len()); + assert_eq!(a_1.len(), b_1.len()); - assert_eq!(a.len(), b_1.len()); + assert_eq!(a.len(), b_1.len()); - for i in 0..a.len() { - if !emath::almost_equal(a_1[i].0 as f32, b_1[i].0 as f32, f32::EPSILON) - | !emath::almost_equal(a_1[i].1 as f32, b_1[i].1 as f32, f32::EPSILON) - { - panic!("Expected: {:?}\nGot: {:?}", a_1, b_1); - } - } - } + for i in 0..a.len() { + if !emath::almost_equal(a_1[i].0 as f32, b_1[i].0 as f32, f32::EPSILON) + | !emath::almost_equal(a_1[i].1 as f32, b_1[i].1 as f32, f32::EPSILON) + { + panic!("Expected: {:?}\nGot: {:?}", a_1, b_1); + } + } + } - { - settings.min_x -= 2.0; - settings.max_x -= 2.0; - function.calculate(true, true, false, settings); + { + settings.min_x -= 2.0; + settings.max_x -= 2.0; + function.calculate(true, true, false, settings); - let a = function - .derivative_data - .clone() - .to_tuple() - .iter() - .rev() - .take(6) - .rev() - .cloned() - .collect::>(); + let a = function + .derivative_data + .clone() + .to_tuple() + .iter() + .rev() + .take(6) + .rev() + .cloned() + .collect::>(); - let b = DERIVATIVE_TARGET - .iter() - .take(6) - .cloned() - .collect::>(); + let b = DERIVATIVE_TARGET + .iter() + .take(6) + .cloned() + .collect::>(); - assert_eq!(a.len(), b.len()); + assert_eq!(a.len(), b.len()); - for i in 0..a.len() { - if !emath::almost_equal(a[i].0 as f32, b[i].0 as f32, f32::EPSILON) - | !emath::almost_equal(a[i].1 as f32, b[i].1 as f32, f32::EPSILON) - { - panic!("Expected: {:?}\nGot: {:?}", a, b); - } - } + for i in 0..a.len() { + if !emath::almost_equal(a[i].0 as f32, b[i].0 as f32, f32::EPSILON) + | !emath::almost_equal(a[i].1 as f32, b[i].1 as f32, f32::EPSILON) + { + panic!("Expected: {:?}\nGot: {:?}", a, b); + } + } - let a_1 = function - .back_data - .clone() - .to_tuple() - .iter() - .rev() - .take(6) - .rev() - .cloned() - .collect::>(); + let a_1 = function + .back_data + .clone() + .to_tuple() + .iter() + .rev() + .take(6) + .rev() + .cloned() + .collect::>(); - let b_1 = BACK_TARGET - .iter() - .take(6) - .cloned() - .collect::>(); + let b_1 = BACK_TARGET + .iter() + .take(6) + .cloned() + .collect::>(); - assert_eq!(a_1.len(), b_1.len()); + assert_eq!(a_1.len(), b_1.len()); - assert_eq!(a.len(), b_1.len()); + assert_eq!(a.len(), b_1.len()); - for i in 0..a.len() { - if !emath::almost_equal(a_1[i].0 as f32, b_1[i].0 as f32, f32::EPSILON) - | !emath::almost_equal(a_1[i].1 as f32, b_1[i].1 as f32, f32::EPSILON) - { - panic!("Expected: {:?}\nGot: {:?}", a_1, b_1); - } - } - } + for i in 0..a.len() { + if !emath::almost_equal(a_1[i].0 as f32, b_1[i].0 as f32, f32::EPSILON) + | !emath::almost_equal(a_1[i].1 as f32, b_1[i].1 as f32, f32::EPSILON) + { + panic!("Expected: {:?}\nGot: {:?}", a_1, b_1); + } + } + } - { - function.update_string("sin(x)"); - assert!(function.get_test_result().is_none()); - assert_eq!(&function.raw_func_str, "sin(x)"); + { + function.update_string("sin(x)"); + assert!(function.get_test_result().is_none()); + assert_eq!(&function.raw_func_str, "sin(x)"); - function.integral = false; - function.derivative = false; + function.integral = false; + function.derivative = false; - assert!(!function.integral); - assert!(!function.derivative); + assert!(!function.integral); + assert!(!function.derivative); - assert!(function.back_data.is_empty()); - assert!(function.integral_data.is_none()); - assert!(function.root_data.is_empty()); - assert!(function.extrema_data.is_empty()); - assert!(function.derivative_data.is_empty()); + assert!(function.back_data.is_empty()); + assert!(function.integral_data.is_none()); + assert!(function.root_data.is_empty()); + assert!(function.extrema_data.is_empty()); + assert!(function.derivative_data.is_empty()); - settings.min_x -= 1.0; - settings.max_x -= 1.0; + settings.min_x -= 1.0; + settings.max_x -= 1.0; - function.calculate(true, true, false, settings); + function.calculate(true, true, false, settings); - assert!(!function.back_data.is_empty()); - assert!(function.integral_data.is_none()); - assert!(function.root_data.is_empty()); - assert!(function.extrema_data.is_empty()); - assert!(!function.derivative_data.is_empty()); - } + assert!(!function.back_data.is_empty()); + assert!(function.integral_data.is_none()); + assert!(function.root_data.is_empty()); + assert!(function.extrema_data.is_empty()); + assert!(!function.derivative_data.is_empty()); + } } #[test] -fn left_function() { do_test(Riemann::Left, 0.9600000000000001); } +fn left_function() { + do_test(Riemann::Left, 0.9600000000000001); +} #[test] -fn middle_function() { do_test(Riemann::Middle, 0.92); } +fn middle_function() { + do_test(Riemann::Middle, 0.92); +} #[test] -fn right_function() { do_test(Riemann::Right, 0.8800000000000001); } +fn right_function() { + do_test(Riemann::Right, 0.8800000000000001); +} diff --git a/tests/misc.rs b/tests/misc.rs index 9cb8255..504b0bd 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -2,90 +2,90 @@ /// Ensures [`decimal_round`] returns correct values #[test] fn decimal_round() { - use ytbn_graphing_software::decimal_round; + use ytbn_graphing_software::decimal_round; - assert_eq!(decimal_round(0.00001, 1), 0.0); - assert_eq!(decimal_round(0.00001, 2), 0.0); - assert_eq!(decimal_round(0.00001, 3), 0.0); - assert_eq!(decimal_round(0.00001, 4), 0.0); - assert_eq!(decimal_round(0.00001, 5), 0.00001); + assert_eq!(decimal_round(0.00001, 1), 0.0); + assert_eq!(decimal_round(0.00001, 2), 0.0); + assert_eq!(decimal_round(0.00001, 3), 0.0); + assert_eq!(decimal_round(0.00001, 4), 0.0); + assert_eq!(decimal_round(0.00001, 5), 0.00001); - assert_eq!(decimal_round(0.12345, 1), 0.1); - assert_eq!(decimal_round(0.12345, 2), 0.12); - assert_eq!(decimal_round(0.12345, 3), 0.123); - assert_eq!(decimal_round(0.12345, 4), 0.1235); // rounds up - assert_eq!(decimal_round(0.12345, 5), 0.12345); + assert_eq!(decimal_round(0.12345, 1), 0.1); + assert_eq!(decimal_round(0.12345, 2), 0.12); + assert_eq!(decimal_round(0.12345, 3), 0.123); + assert_eq!(decimal_round(0.12345, 4), 0.1235); // rounds up + assert_eq!(decimal_round(0.12345, 5), 0.12345); - assert_eq!(decimal_round(1.9, 0), 2.0); - assert_eq!(decimal_round(1.9, 1), 1.9); + assert_eq!(decimal_round(1.9, 0), 2.0); + assert_eq!(decimal_round(1.9, 1), 1.9); } */ #[test] fn step_helper() { - use ytbn_graphing_software::step_helper; + use ytbn_graphing_software::step_helper; - assert_eq!( - step_helper(10, 2.0, 3.0), - vec![2.0, 5.0, 8.0, 11.0, 14.0, 17.0, 20.0, 23.0, 26.0, 29.0] - ); + assert_eq!( + step_helper(10, 2.0, 3.0), + vec![2.0, 5.0, 8.0, 11.0, 14.0, 17.0, 20.0, 23.0, 26.0, 29.0] + ); } /// Tests [`option_vec_printer`] #[test] fn option_vec_printer() { - use std::collections::HashMap; - use ytbn_graphing_software::option_vec_printer; + use std::collections::HashMap; + use ytbn_graphing_software::option_vec_printer; - let values_strings: HashMap>, &str> = HashMap::from([ - (vec![None], "[None]"), - (vec![Some("text"), None], "[text, None]"), - (vec![None, None], "[None, None]"), - (vec![Some("text1"), Some("text2")], "[text1, text2]"), - ]); + let values_strings: HashMap>, &str> = HashMap::from([ + (vec![None], "[None]"), + (vec![Some("text"), None], "[text, None]"), + (vec![None, None], "[None, None]"), + (vec![Some("text1"), Some("text2")], "[text1, text2]"), + ]); - for (key, value) in values_strings { - assert_eq!(option_vec_printer(&key), value); - } + for (key, value) in values_strings { + assert_eq!(option_vec_printer(&key), value); + } - let values_nums = HashMap::from([ - (vec![Some(10)], "[10]"), - (vec![Some(10), None], "[10, None]"), - (vec![None, Some(10)], "[None, 10]"), - (vec![Some(10), Some(100)], "[10, 100]"), - ]); + let values_nums = HashMap::from([ + (vec![Some(10)], "[10]"), + (vec![Some(10), None], "[10, None]"), + (vec![None, Some(10)], "[None, 10]"), + (vec![Some(10), Some(100)], "[10, 100]"), + ]); - for (key, value) in values_nums { - assert_eq!(option_vec_printer(&key), value); - } + for (key, value) in values_nums { + assert_eq!(option_vec_printer(&key), value); + } } #[test] fn hashed_storage() { - use ytbn_graphing_software::{hashed_storage_create, hashed_storage_read}; + use ytbn_graphing_software::{hashed_storage_create, hashed_storage_read}; - let commit = "abcdefeg".chars().map(|c| c as u8).collect::>(); - let data = "really cool data" - .chars() - .map(|c| c as u8) - .collect::>(); - let storage_tmp: [u8; 8] = commit - .as_slice() - .try_into() - .expect("cannot turn into [u8; 8]"); - let storage = hashed_storage_create(storage_tmp, data.as_slice()); + let commit = "abcdefeg".chars().map(|c| c as u8).collect::>(); + let data = "really cool data" + .chars() + .map(|c| c as u8) + .collect::>(); + let storage_tmp: [u8; 8] = commit + .as_slice() + .try_into() + .expect("cannot turn into [u8; 8]"); + let storage = hashed_storage_create(storage_tmp, data.as_slice()); - let read = hashed_storage_read(&storage); - assert_eq!( - read.map(|(a, b)| (a.to_vec(), b.to_vec())), - Some((commit.to_vec(), data.to_vec())) - ); + let read = hashed_storage_read(&storage); + assert_eq!( + read.map(|(a, b)| (a.to_vec(), b.to_vec())), + Some((commit.to_vec(), data.to_vec())) + ); } #[test] fn invalid_hashed_storage() { - use ytbn_graphing_software::hashed_storage_read; - assert_eq!(hashed_storage_read("aaaa"), None); + use ytbn_graphing_software::hashed_storage_read; + assert_eq!(hashed_storage_read("aaaa"), None); } // #[test] @@ -141,45 +141,45 @@ fn invalid_hashed_storage() { #[test] fn newtons_method() { - use parsing::BackingFunction; - use parsing::FlatExWrapper; - fn get_flatexwrapper(func: &str) -> FlatExWrapper { - let mut backing_func = BackingFunction::new(func).unwrap(); - backing_func.get_function_derivative(0).clone() - } + use parsing::BackingFunction; + use parsing::FlatExWrapper; + fn get_flatexwrapper(func: &str) -> FlatExWrapper { + let mut backing_func = BackingFunction::new(func).unwrap(); + backing_func.get_function_derivative(0).clone() + } - use ytbn_graphing_software::newtons_method; + use ytbn_graphing_software::newtons_method; - let data = newtons_method( - &get_flatexwrapper("x^2 -1"), - &get_flatexwrapper("2x"), - 3.0, - &(0.0..5.0), - f64::EPSILON, - ); - assert_eq!(data, Some(1.0)); + let data = newtons_method( + &get_flatexwrapper("x^2 -1"), + &get_flatexwrapper("2x"), + 3.0, + &(0.0..5.0), + f64::EPSILON, + ); + assert_eq!(data, Some(1.0)); - let data = newtons_method( - &get_flatexwrapper("sin(x)"), - &get_flatexwrapper("cos(x)"), - 3.0, - &(2.95..3.18), - f64::EPSILON, - ); - assert_eq!(data, Some(std::f64::consts::PI)); + let data = newtons_method( + &get_flatexwrapper("sin(x)"), + &get_flatexwrapper("cos(x)"), + 3.0, + &(2.95..3.18), + f64::EPSILON, + ); + assert_eq!(data, Some(std::f64::consts::PI)); } #[test] fn to_unicode_hash() { - use ytbn_graphing_software::to_unicode_hash; - assert_eq!(to_unicode_hash('\u{1f31e}'), "\\U1F31E"); + use ytbn_graphing_software::to_unicode_hash; + assert_eq!(to_unicode_hash('\u{1f31e}'), "\\U1F31E"); } #[test] fn to_chars_array() { - use ytbn_graphing_software::to_chars_array; - assert_eq!( - to_chars_array(vec!['\u{1f31e}', '\u{2d12c}']), - r"['\u{1f31e}', '\u{2d12c}']" - ); + use ytbn_graphing_software::to_chars_array; + assert_eq!( + to_chars_array(vec!['\u{1f31e}', '\u{2d12c}']), + r"['\u{1f31e}', '\u{2d12c}']" + ); } diff --git a/tests/parsing.rs b/tests/parsing.rs index f04dfde..d44236a 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -3,292 +3,292 @@ use std::collections::HashMap; #[test] fn hashmap_gen_test() { - let data = ["time", "text", "test"]; - let expect = vec![ - ("t", "Hint::Many(&[\"ime(\", \"ext(\", \"est(\"])"), - ("te", "Hint::Many(&[\"xt(\", \"st(\"])"), - ("tes", "Hint::Single(\"t(\")"), - ("test", "Hint::Single(\"(\")"), - ("tex", "Hint::Single(\"t(\")"), - ("text", "Hint::Single(\"(\")"), - ("ti", "Hint::Single(\"me(\")"), - ("tim", "Hint::Single(\"e(\")"), - ("time", "Hint::Single(\"(\")"), - ]; + let data = ["time", "text", "test"]; + let expect = vec![ + ("t", "Hint::Many(&[\"ime(\", \"ext(\", \"est(\"])"), + ("te", "Hint::Many(&[\"xt(\", \"st(\"])"), + ("tes", "Hint::Single(\"t(\")"), + ("test", "Hint::Single(\"(\")"), + ("tex", "Hint::Single(\"t(\")"), + ("text", "Hint::Single(\"(\")"), + ("ti", "Hint::Single(\"me(\")"), + ("tim", "Hint::Single(\"e(\")"), + ("time", "Hint::Single(\"(\")"), + ]; - assert_eq!( - parsing::compile_hashmap(data.iter().map(|e| e.to_string()).collect()), - expect - .iter() - .map(|(a, b)| (a.to_string(), b.to_string())) - .collect::>() - ); + assert_eq!( + parsing::compile_hashmap(data.iter().map(|e| e.to_string()).collect()), + expect + .iter() + .map(|(a, b)| (a.to_string(), b.to_string())) + .collect::>() + ); } /// Returns if function with string `func_str` is valid after processing through [`process_func_str`] fn func_is_valid(func_str: &str) -> bool { - parsing::BackingFunction::new(&parsing::process_func_str(func_str)).is_ok() + parsing::BackingFunction::new(&parsing::process_func_str(func_str)).is_ok() } /// Used for testing: passes function to [`process_func_str`] before running [`test_func`]. if `expect_valid` == `true`, it expects no errors to be created. fn test_func_helper(func_str: &str, expect_valid: bool) { - let is_valid = func_is_valid(func_str); - let string = format!( - "function: {} (expected: {}, got: {})", - func_str, expect_valid, is_valid - ); + let is_valid = func_is_valid(func_str); + let string = format!( + "function: {} (expected: {}, got: {})", + func_str, expect_valid, is_valid + ); - if is_valid == expect_valid { - println!("{}", string); - } else { - panic!("{}", string); - } + if is_valid == expect_valid { + println!("{}", string); + } else { + panic!("{}", string); + } } /// Tests to make sure functions that are expected to succeed, succeed. #[test] fn test_expected() { - let values = HashMap::from([ - ("", true), - ("x^2", true), - ("2x", true), - ("E^x", true), - ("log10(x)", true), - ("xxxxx", true), - ("sin(x)", true), - ("xsin(x)", true), - ("sin(x)cos(x)", true), - ("x/0", true), - ("(x+1)(x-3)", true), - ("cos(xsin(x)x)", true), - ("(2x+1)x", true), - ("(2x+1)pi", true), - ("pi(2x+1)", true), - ("pipipipipipix", true), - ("e^sin(x)", true), - ("E^sin(x)", true), - ("e^x", true), - ("x**2", true), - ("a", false), - ("log222(x)", false), - ("abcdef", false), - ("log10(x", false), - ("x^a", false), - ("sin(cos(x)))", false), - ("0/0", false), - ]); + let values = HashMap::from([ + ("", true), + ("x^2", true), + ("2x", true), + ("E^x", true), + ("log10(x)", true), + ("xxxxx", true), + ("sin(x)", true), + ("xsin(x)", true), + ("sin(x)cos(x)", true), + ("x/0", true), + ("(x+1)(x-3)", true), + ("cos(xsin(x)x)", true), + ("(2x+1)x", true), + ("(2x+1)pi", true), + ("pi(2x+1)", true), + ("pipipipipipix", true), + ("e^sin(x)", true), + ("E^sin(x)", true), + ("e^x", true), + ("x**2", true), + ("a", false), + ("log222(x)", false), + ("abcdef", false), + ("log10(x", false), + ("x^a", false), + ("sin(cos(x)))", false), + ("0/0", false), + ]); - for (key, value) in values { - test_func_helper(key, value); - } + for (key, value) in values { + test_func_helper(key, value); + } } /// Helps with tests of [`process_func_str`] fn test_process_helper(input: &str, expected: &str) { - assert_eq!(&parsing::process_func_str(input), expected); + assert_eq!(&parsing::process_func_str(input), expected); } /// Tests to make sure my cursed function works as intended #[test] fn func_process_test() { - let values = HashMap::from([ - ("2x", "2*x"), - (")(", ")*("), - ("(2", "(2"), - ("log10(x)", "log10(x)"), - ("log2(x)", "log2(x)"), - ("pipipipipipi", "π*π*π*π*π*π"), - ("10pi", "10*π"), - ("pi10", "π*10"), - ("10pi10", "10*π*10"), - ("emax(x)", "e*max(x)"), - ("pisin(x)", "π*sin(x)"), - ("e^sin(x)", "e^sin(x)"), - ("x**2", "x^2"), - ("(x+1)(x-3)", "(x+1)*(x-3)"), - ]); + let values = HashMap::from([ + ("2x", "2*x"), + (")(", ")*("), + ("(2", "(2"), + ("log10(x)", "log10(x)"), + ("log2(x)", "log2(x)"), + ("pipipipipipi", "π*π*π*π*π*π"), + ("10pi", "10*π"), + ("pi10", "π*10"), + ("10pi10", "10*π*10"), + ("emax(x)", "e*max(x)"), + ("pisin(x)", "π*sin(x)"), + ("e^sin(x)", "e^sin(x)"), + ("x**2", "x^2"), + ("(x+1)(x-3)", "(x+1)*(x-3)"), + ]); - for (key, value) in values { - test_process_helper(key, value); - } + for (key, value) in values { + test_process_helper(key, value); + } - for func in SUPPORTED_FUNCTIONS.iter() { - let func_new = format!("{}(x)", func); - test_process_helper(&func_new, &func_new); - } + for func in SUPPORTED_FUNCTIONS.iter() { + let func_new = format!("{}(x)", func); + test_process_helper(&func_new, &func_new); + } } /// Tests to make sure hints are properly outputed based on input #[test] fn hints() { - let values = HashMap::from([ - ("", Hint::Single("x^2")), - ("si", Hint::Many(&["n(", "nh(", "gnum("])), - ("log", Hint::Many(&["2(", "10("])), - ("cos", Hint::Many(&["(", "h("])), - ("sin(", Hint::Single(")")), - ("sqrt", Hint::Single("(")), - ("ln(x)", Hint::None), - ("ln(x)cos", Hint::Many(&["(", "h("])), - ("ln(x)*cos", Hint::Many(&["(", "h("])), - ("sin(cos", Hint::Many(&["(", "h("])), - ]); + let values = HashMap::from([ + ("", Hint::Single("x^2")), + ("si", Hint::Many(&["n(", "nh(", "gnum("])), + ("log", Hint::Many(&["2(", "10("])), + ("cos", Hint::Many(&["(", "h("])), + ("sin(", Hint::Single(")")), + ("sqrt", Hint::Single("(")), + ("ln(x)", Hint::None), + ("ln(x)cos", Hint::Many(&["(", "h("])), + ("ln(x)*cos", Hint::Many(&["(", "h("])), + ("sin(cos", Hint::Many(&["(", "h("])), + ]); - for (key, value) in values { - println!("{} + {:?}", key, value); - assert_eq!(parsing::generate_hint(key), &value); - } + for (key, value) in values { + println!("{} + {:?}", key, value); + assert_eq!(parsing::generate_hint(key), &value); + } } #[test] fn hint_to_string() { - let values = HashMap::from([ - ("x^2", Hint::Single("x^2")), - ( - r#"["n(", "nh(", "gnum("]"#, - Hint::Many(&["n(", "nh(", "gnum("]), - ), - (r#"["n("]"#, Hint::Many(&["n("])), - ("None", Hint::None), - ]); + let values = HashMap::from([ + ("x^2", Hint::Single("x^2")), + ( + r#"["n(", "nh(", "gnum("]"#, + Hint::Many(&["n(", "nh(", "gnum("]), + ), + (r#"["n("]"#, Hint::Many(&["n("])), + ("None", Hint::None), + ]); - for (key, value) in values { - assert_eq!(value.to_string(), key); - } + for (key, value) in values { + assert_eq!(value.to_string(), key); + } } #[test] fn invalid_function() { - use parsing::SplitType; + use parsing::SplitType; - SUPPORTED_FUNCTIONS - .iter() - .flat_map(|func1| { - SUPPORTED_FUNCTIONS - .iter() - .map(|func2| func1.to_string() + func2) - .collect::>() - }) - .filter(|func| !SUPPORTED_FUNCTIONS.contains(&func.as_str())) - .for_each(|key| { - let split = parsing::split_function(&key, SplitType::Multiplication); + SUPPORTED_FUNCTIONS + .iter() + .flat_map(|func1| { + SUPPORTED_FUNCTIONS + .iter() + .map(|func2| func1.to_string() + func2) + .collect::>() + }) + .filter(|func| !SUPPORTED_FUNCTIONS.contains(&func.as_str())) + .for_each(|key| { + let split = parsing::split_function(&key, SplitType::Multiplication); - if split.len() != 1 { - panic!("failed: {} (len: {}, split: {:?})", key, split.len(), split); - } + if split.len() != 1 { + panic!("failed: {} (len: {}, split: {:?})", key, split.len(), split); + } - let generated_hint = parsing::generate_hint(&key); - if generated_hint.is_none() { - println!("success: {}", key); - } else { - panic!("failed: {} (Hint: '{}')", key, generated_hint); - } - }); + let generated_hint = parsing::generate_hint(&key); + if generated_hint.is_none() { + println!("success: {}", key); + } else { + panic!("failed: {} (Hint: '{}')", key, generated_hint); + } + }); } #[test] fn split_function_multiplication() { - use parsing::SplitType; + use parsing::SplitType; - let values = HashMap::from([ - ("cos(x)", vec!["cos(x)"]), - ("cos(", vec!["cos("]), - ("cos(x)sin(x)", vec!["cos(x)", "sin(x)"]), - ("aaaaaaaaaaa", vec!["aaaaaaaaaaa"]), - ("emax(x)", vec!["e", "max(x)"]), - ("x", vec!["x"]), - ("xxx", vec!["x", "x", "x"]), - ("sin(cos(x)x)", vec!["sin(cos(x)", "x)"]), - ("sin(x)*cos(x)", vec!["sin(x)", "cos(x)"]), - ("x*x", vec!["x", "x"]), - ("10*10", vec!["10", "10"]), - ("a1b2c3d4", vec!["a1b2c3d4"]), - ("cos(sin(x)cos(x))", vec!["cos(sin(x)", "cos(x))"]), - ("", Vec::new()), - ]); + let values = HashMap::from([ + ("cos(x)", vec!["cos(x)"]), + ("cos(", vec!["cos("]), + ("cos(x)sin(x)", vec!["cos(x)", "sin(x)"]), + ("aaaaaaaaaaa", vec!["aaaaaaaaaaa"]), + ("emax(x)", vec!["e", "max(x)"]), + ("x", vec!["x"]), + ("xxx", vec!["x", "x", "x"]), + ("sin(cos(x)x)", vec!["sin(cos(x)", "x)"]), + ("sin(x)*cos(x)", vec!["sin(x)", "cos(x)"]), + ("x*x", vec!["x", "x"]), + ("10*10", vec!["10", "10"]), + ("a1b2c3d4", vec!["a1b2c3d4"]), + ("cos(sin(x)cos(x))", vec!["cos(sin(x)", "cos(x))"]), + ("", Vec::new()), + ]); - for (key, value) in values { - assert_eq!( - parsing::split_function(key, SplitType::Multiplication), - value - ); - } + for (key, value) in values { + assert_eq!( + parsing::split_function(key, SplitType::Multiplication), + value + ); + } } #[test] fn split_function_terms() { - use parsing::SplitType; + use parsing::SplitType; - let values = HashMap::from([ - ( - "cos(sin(x)cos(x))", - vec!["cos(", "sin(", "x)", "cos(", "x))"], - ), - ("", Vec::new()), - ]); + let values = HashMap::from([ + ( + "cos(sin(x)cos(x))", + vec!["cos(", "sin(", "x)", "cos(", "x))"], + ), + ("", Vec::new()), + ]); - for (key, value) in values { - assert_eq!(parsing::split_function(key, SplitType::Term), value); - } + for (key, value) in values { + assert_eq!(parsing::split_function(key, SplitType::Term), value); + } } #[test] fn hint_tests() { - { - let hint = Hint::None; - assert!(hint.is_none()); - assert!(!hint.is_some()); - assert!(!hint.is_single()); - } + { + let hint = Hint::None; + assert!(hint.is_none()); + assert!(!hint.is_some()); + assert!(!hint.is_single()); + } - { - let hint = Hint::Single(""); - assert!(!hint.is_none()); - assert!(hint.is_some()); - assert!(hint.is_single()); - } + { + let hint = Hint::Single(""); + assert!(!hint.is_none()); + assert!(hint.is_some()); + assert!(hint.is_single()); + } - { - let hint = Hint::Many(&[""]); - assert!(!hint.is_none()); - assert!(hint.is_some()); - assert!(!hint.is_single()); - } + { + let hint = Hint::Many(&[""]); + assert!(!hint.is_none()); + assert!(hint.is_some()); + assert!(!hint.is_single()); + } } #[test] fn get_last_term() { - let values = HashMap::from([ - ("cos(x)", "x)"), - ("cos(", "cos("), - ("aaaaaaaaaaa", "aaaaaaaaaaa"), - ("x", "x"), - ("xxx", "x"), - ("x*x", "x"), - ("10*10", "10"), - ("sin(cos", "cos"), - ("exp(cos(exp(sin", "sin"), - ]); + let values = HashMap::from([ + ("cos(x)", "x)"), + ("cos(", "cos("), + ("aaaaaaaaaaa", "aaaaaaaaaaa"), + ("x", "x"), + ("xxx", "x"), + ("x*x", "x"), + ("10*10", "10"), + ("sin(cos", "cos"), + ("exp(cos(exp(sin", "sin"), + ]); - for (key, value) in values { - assert_eq!( - parsing::get_last_term(key.chars().collect::>().as_slice()), - Some(value.to_owned()) - ); - } + for (key, value) in values { + assert_eq!( + parsing::get_last_term(key.chars().collect::>().as_slice()), + Some(value.to_owned()) + ); + } } #[test] fn hint_accessor() { - assert_eq!(Hint::Single("hint").many(), None); - assert_eq!(Hint::Single("hint").single(), Some("hint")); + assert_eq!(Hint::Single("hint").many(), None); + assert_eq!(Hint::Single("hint").single(), Some("hint")); - assert_eq!(Hint::Many(&["hint", "hint2"]).single(), None); - assert_eq!( - Hint::Many(&["hint", "hint2"]).many(), - Some(["hint", "hint2"].as_slice()) - ); + assert_eq!(Hint::Many(&["hint", "hint2"]).single(), None); + assert_eq!( + Hint::Many(&["hint", "hint2"]).many(), + Some(["hint", "hint2"].as_slice()) + ); - assert_eq!(Hint::None.single(), None); - assert_eq!(Hint::None.many(), None); + assert_eq!(Hint::None.single(), None); + assert_eq!(Hint::None.many(), None); }