diff --git a/src/egui_app.rs b/src/egui_app.rs index 1ea1409..a4a6078 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -18,13 +18,15 @@ const MIN_X_TOTAL: f64 = -1000.0; const MAX_X_TOTAL: f64 = 1000.0; const X_RANGE: RangeInclusive = MIN_X_TOTAL..=MAX_X_TOTAL; const DEFAULT_FUNCION: &str = "x^2"; +const MARGINS: f64 = 0.9; pub struct MathApp { functions: Vec, - min_x: f64, - max_x: f64, - // Currently really unused. But once fully implemented it will represent the full graph's min_x and max_x, being seperate from min_x and max_x for the intergral. + // No clue why I need this, but I do. Rust being weird I guess. + // Ideally this should be information directly accessed from `functions` but it always returns an empty string. I don't know, I've been debuging this for a while now. + func_strs: Vec, + integral_min_x: f64, integral_max_x: f64, @@ -38,20 +40,17 @@ impl Default for MathApp { let def_max_x = 10.0; let def_interval: usize = 1000; - let def_funcs: Vec = vec![Function::new( - String::from(DEFAULT_FUNCION), - def_min_x, - def_max_x, - true, - Some(def_min_x), - Some(def_max_x), - Some(def_interval), - )]; - Self { - functions: def_funcs, - min_x: def_min_x, - max_x: def_max_x, + functions: vec![Function::new( + String::from(DEFAULT_FUNCION), + def_min_x, + def_max_x, + true, + Some(def_min_x), + Some(def_max_x), + Some(def_interval), + )], + func_strs: vec![String::from(DEFAULT_FUNCION)], integral_min_x: def_min_x, integral_max_x: def_max_x, integral_num: def_interval, @@ -78,12 +77,11 @@ impl epi::App for MathApp { } // Called each time the UI needs repainting, which may be many times per second. - #[inline] + #[inline(always)] fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { let Self { functions, - min_x, - max_x, + func_strs, integral_min_x, integral_max_x, integral_num, @@ -105,64 +103,68 @@ impl epi::App for MathApp { ui.label("- signum, min, max"); }); - let mut new_func_data: Vec<(String, bool, bool)> = Vec::new(); let mut parse_error: String = "".to_string(); egui::SidePanel::left("side_panel") .resizable(false) .show(ctx, |ui| { ui.heading("Side Panel"); if ui.add(egui::Button::new("Add function")).clicked() { - functions.push(Function::new(String::from(DEFAULT_FUNCION), *min_x, - *max_x, - true, - Some(*min_x), - Some(*max_x), - Some(*integral_num))); + // min_x and max_x will be updated later, doesn't matter here + functions.push(Function::new(String::from(DEFAULT_FUNCION), -1.0, + 1.0, + false, + None, + None, + None)); + func_strs.push(String::from(DEFAULT_FUNCION)); } - for function in functions.iter() { - let mut func_str = function.get_string(); + let min_x_old = *integral_min_x; + let min_x_response = + ui.add(egui::Slider::new(integral_min_x, X_RANGE.clone()).text("Min X")); + + let max_x_old = *integral_max_x; + let max_x_response = ui.add(egui::Slider::new(integral_max_x, X_RANGE).text("Max X")); + + // Checks bounds, and if they are invalid, fix them + if integral_min_x >= integral_max_x { + if max_x_response.changed() { + *integral_max_x = max_x_old; + } else if min_x_response.changed() { + *integral_min_x = min_x_old; + } else { + *integral_min_x = -10.0; + *integral_max_x = 10.0; + } + } + + ui.add(egui::Slider::new(integral_num, INTEGRAL_NUM_RANGE).text("Interval")); + + for (i, function) in functions.iter_mut().enumerate() { let mut integral_toggle: bool = false; ui.horizontal(|ui| { ui.label("Function: "); if ui.add(Button::new("Toggle Integrals")).clicked() { integral_toggle = true; } - ui.text_edit_singleline(&mut func_str); + ui.text_edit_singleline(&mut func_strs[i]); }); - let func_test_output = test_func(func_str.clone()); - let mut got_error: bool = false; - if !func_test_output.is_empty() { - parse_error += &func_test_output; - got_error = true; - } - - new_func_data.push((func_str, integral_toggle, got_error)); - } - - let min_x_old = *min_x; - let min_x_response = - ui.add(egui::Slider::new(min_x, X_RANGE.clone()).text("Min X")); - - let max_x_old = *max_x; - let max_x_response = ui.add(egui::Slider::new(max_x, X_RANGE).text("Max X")); - - // Checks bounds, and if they are invalid, fix them - if min_x >= max_x { - if max_x_response.changed() { - *max_x = max_x_old; - } else if min_x_response.changed() { - *min_x = min_x_old; + let integral: bool = if integral_toggle { + !function.is_integral() } else { - *min_x = -10.0; - *max_x = 10.0; - } - *integral_min_x = *min_x; - *integral_max_x = *max_x; - } + function.is_integral() + }; - ui.add(egui::Slider::new(integral_num, INTEGRAL_NUM_RANGE).text("Interval")); + if !func_strs[i].is_empty() { + let func_test_output = test_func(func_strs[i].clone()); + if !func_test_output.is_empty() { + parse_error += &func_test_output; + } else { + function.update(func_strs[i].clone(), integral, Some(*integral_min_x), Some(*integral_max_x), Some(*integral_num)); + } + } + } // Opensource and Licensing information ui.horizontal(|ui| { @@ -190,21 +192,6 @@ impl epi::App for MathApp { ui.label(GIT_VERSION); } }); - - let mut i: usize = 0; - for function in functions.iter_mut() { - let (func_str, integral_toggle, got_error) = (new_func_data[i].0.clone(), new_func_data[i].1, new_func_data[i].2); - - let integral: bool = if integral_toggle { - !function.is_integral() - } else { - function.is_integral() - }; - - - function.update(func_str, *min_x, *max_x, integral, Some(*integral_min_x), Some(*integral_max_x), Some(*integral_num), got_error); - i += 1; - } }); egui::CentralPanel::default().show(ctx, |ui| { @@ -220,8 +207,14 @@ impl epi::App for MathApp { .data_aspect(1.0) .include_y(0) .show(ui, |plot_ui| { - for function in self.functions.iter_mut() { - if function.is_broken() { + let bounds = plot_ui.plot_bounds(); + let minx_bounds: f64 = bounds.min()[0]; + let maxx_bounds: f64 = bounds.max()[0]; + // println!("({}, {})", minx_bounds, maxx_bounds); + + for (i, function) in self.functions.iter_mut().enumerate() { + function.update_bounds(minx_bounds * MARGINS, maxx_bounds * MARGINS); + if self.func_strs[i].is_empty() { continue; } diff --git a/src/function.rs b/src/function.rs index e3e4c24..bc31171 100644 --- a/src/function.rs +++ b/src/function.rs @@ -24,7 +24,7 @@ impl FunctionOutput { #[inline] pub fn get_front(&self) -> (Vec, f64) { match &self.front { - Some(x) => (x.0.clone(), x.1.clone()), + Some(x) => (x.0.clone(), x.1), None => panic!(""), } } @@ -40,7 +40,7 @@ impl FunctionOutput { pub struct Function { function: Box f64>, - func_str: String, + pub(crate) func_str: String, min_x: f64, max_x: f64, @@ -51,7 +51,6 @@ pub struct Function { integral_min_x: f64, integral_max_x: f64, integral_num: usize, - broken_state: bool, } impl Function { @@ -72,7 +71,7 @@ impl Function { let expr: Expr = func_str.parse().unwrap(); let func = expr.bind("x").unwrap(); - Self { + let mut output = Self { function: Box::new(func), func_str, min_x, @@ -88,12 +87,11 @@ impl Function { Some(x) => x, None => f64::NAN, }, - integral_num: match integral_num { - Some(x) => x, - None => 0, - }, - broken_state: false, - } + integral_num: integral_num.unwrap_or(0), + }; + + output.func_str = "".to_string(); + output } // Runs the internal function to get values @@ -102,13 +100,11 @@ impl Function { #[inline] pub fn update( - &mut self, func_str: String, min_x: f64, max_x: f64, integral: bool, - integral_min_x: Option, integral_max_x: Option, integral_num: Option, - broken_state: bool, + &mut self, func_str: String, integral: bool, integral_min_x: Option, + integral_max_x: Option, integral_num: Option, ) { - if broken_state { - self.func_str = func_str.clone(); - self.broken_state = true; + if func_str.is_empty() { + self.func_str = func_str; return; } @@ -116,8 +112,8 @@ impl Function { if func_str != self.func_str { *self = Self::new( func_str, - min_x, - max_x, + self.min_x, + self.max_x, integral, integral_min_x, integral_max_x, @@ -126,12 +122,6 @@ impl Function { return; } - if (min_x != self.min_x) | (max_x != self.max_x) { - self.back_cache.invalidate(); - self.min_x = min_x; - self.max_x = max_x; - } - if integral != self.integral { self.integral = integral; } @@ -158,6 +148,15 @@ impl Function { } } + #[inline] + pub fn update_bounds(&mut self, min_x: f64, max_x: f64) { + if (min_x != self.min_x) | (max_x != self.max_x) { + self.back_cache.invalidate(); + self.min_x = min_x; + self.max_x = max_x; + } + } + #[inline] pub fn get_step(&self) -> f64 { (self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64) @@ -166,10 +165,7 @@ impl Function { #[inline] pub fn is_integral(&self) -> bool { self.integral } - #[inline] - pub fn is_broken(&self) -> bool { self.broken_state } - - #[inline] + #[inline(always)] pub fn run(&mut self) -> FunctionOutput { let front_values: Vec = match self.back_cache.is_valid() { false => { @@ -208,14 +204,11 @@ impl Function { } } - #[inline] pub fn get_string(&self) -> String { self.func_str.clone() } - #[inline] pub fn str_compare(&self, other_string: String) -> bool { self.func_str == other_string } // Creates and does the math for creating all the rectangles under the graph - #[inline] fn integral_rectangles(&self) -> (Vec<(f64, f64)>, f64) { if !self.integral { panic!("integral_rectangles called, but self.integral is false!");