initial function management refactoring

This commit is contained in:
Simon Gardling
2022-04-23 15:39:40 -04:00
parent 22d1be59f5
commit 2172f3da61
11 changed files with 173 additions and 122 deletions

View File

@@ -71,3 +71,20 @@ pub const COLORS: &[Color32; 13] = &[
Color32::DARK_GREEN,
Color32::DARK_BLUE,
];
#[cfg(target_arch = "wasm32")]
lazy_static::lazy_static! {
pub static IS_MOBILE: bool = {
fn is_mobile() -> Option<bool> {
const MOBILE_DEVICE: [&str; 6] = ["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"];
let user_agent = web_sys::window()?.navigator().user_agent().ok()?;
Some(MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name)))
}
is_mobile().unwrap_or_default()
}
}
#[cfg(not(target_arch = "wasm32"))]
pub const IS_MOBILE: bool = false;

View File

@@ -1,5 +1,6 @@
#![allow(clippy::too_many_arguments)] // Clippy, shut
use crate::consts::IS_MOBILE;
use crate::math_app::AppSettings;
use crate::misc::*;
use crate::widgets::{widgets_ontop, AutoComplete, Movement};
@@ -101,9 +102,7 @@ impl Default for FunctionEntry {
impl FunctionEntry {
/// Creates edit box for [`FunctionEntry`] to edit function settings and string.
/// Returns whether or not this function was marked for removal.
pub fn function_entry(
&mut self, ui: &mut egui::Ui, can_remove: bool, i: usize, mobile: bool,
) -> bool {
pub fn function_entry(&mut self, ui: &mut egui::Ui, can_remove: bool, i: usize) -> bool {
let output_string = self.autocomplete.string.clone();
self.update_string(&output_string);
@@ -154,7 +153,7 @@ impl FunctionEntry {
self.autocomplete.update_string(&new_string);
if !self.autocomplete.hint.is_none() {
if !self.autocomplete.hint.is_single() {
if !IS_MOBILE && !self.autocomplete.hint.is_single() {
if ui.input().key_pressed(Key::ArrowDown) {
movement = Movement::Down;
} else if ui.input().key_pressed(Key::ArrowUp) {
@@ -729,7 +728,6 @@ mod tests {
do_extrema: false,
do_roots: false,
plot_width: pixel_width,
is_mobile: false,
}
}

55
src/function_manager.rs Normal file
View File

@@ -0,0 +1,55 @@
use crate::function_entry::{FunctionEntry, DEFAULT_FUNCTION_ENTRY};
use uuid::Uuid;
pub struct Manager {
functions: Vec<(Uuid, FunctionEntry)>,
}
impl Default for Manager {
fn default() -> Self {
Self {
functions: vec![(Uuid::new_v4(), DEFAULT_FUNCTION_ENTRY.clone())],
}
}
}
impl Manager {
pub fn display_entries(&mut self, ui: &mut egui::Ui) {
// ui.label("Functions:");
let can_remove = self.functions.len() > 1;
let mut remove_i: Option<usize> = None;
for (i, (uuid, function)) in self.functions.iter_mut().enumerate() {
// Entry for a function
if function.function_entry(ui, can_remove, i) {
remove_i = Some(i);
}
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);
}
}
pub fn new_function(&mut self) {
self.functions
.push((Uuid::new_v4(), DEFAULT_FUNCTION_ENTRY.clone()));
}
pub fn any_using_integral(&self) -> bool {
self.functions
.iter()
.filter(|(_, func)| func.integral)
.count() > 0
}
#[inline]
pub fn len(&self) -> usize { self.functions.len() }
pub fn get_entries_mut(&mut self) -> &mut Vec<(Uuid, FunctionEntry)> { &mut self.functions }
pub fn get_entries(&self) -> &Vec<(Uuid, FunctionEntry)> { &self.functions }
}

View File

@@ -7,6 +7,7 @@ extern crate static_assertions;
mod consts;
mod function_entry;
mod function_manager;
mod math_app;
mod misc;
mod widgets;

View File

@@ -7,6 +7,7 @@ extern crate static_assertions;
mod consts;
mod function_entry;
mod function_manager;
mod math_app;
mod misc;
mod widgets;

View File

@@ -1,5 +1,6 @@
use crate::consts::*;
use crate::function_entry::{FunctionEntry, Riemann, DEFAULT_FUNCTION_ENTRY};
use crate::function_entry::Riemann;
use crate::function_manager::Manager;
use crate::misc::{dyn_mut_iter, option_vec_printer, JsonFileOutput, SerdeValueHelper};
use egui::style::Margin;
use egui::Frame;
@@ -30,14 +31,6 @@ cfg_if::cfg_if! {
// Remove the element
loading_element.remove();
}
fn is_mobile() -> Option<bool> {
const MOBILE_DEVICE: [&str; 6] = ["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"];
let user_agent = web_sys::window()?.navigator().user_agent().ok()?;
Some(MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name)))
}
}
}
@@ -68,8 +61,6 @@ pub struct AppSettings {
/// Stores current plot pixel width
pub plot_width: usize,
pub is_mobile: bool,
}
impl Default for AppSettings {
@@ -85,7 +76,6 @@ impl Default for AppSettings {
do_extrema: true,
do_roots: true,
plot_width: 0,
is_mobile: false,
}
}
}
@@ -112,7 +102,7 @@ impl Default for Opened {
/// The actual application
pub struct MathApp {
/// Stores vector of functions
functions: Vec<FunctionEntry>,
functions: Manager,
/// Contains the list of Areas calculated (the vector of f64) and time it took for the last frame (the Duration). Stored in a Tuple.
last_info: (Vec<Option<f64>>, Duration),
@@ -135,19 +125,10 @@ impl MathApp {
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
let start = instant::Instant::now();
#[allow(unused_mut)]
#[allow(unused_assignments)]
let mut mobile = false;
// Remove loading indicator on wasm
#[cfg(target_arch = "wasm32")]
stop_loading();
#[cfg(target_arch = "wasm32")]
{
mobile = is_mobile().unwrap_or_default();
}
#[cfg(threading)]
tracing::info!("Threading: Enabled");
@@ -271,15 +252,12 @@ impl MathApp {
tracing::info!("Initialized! Took: {:?}", start.elapsed());
Self {
functions: vec![DEFAULT_FUNCTION_ENTRY.clone()],
functions: Default::default(),
last_info: (vec![None], Duration::ZERO),
dark_mode: true,
text: text_data.expect("text.json failed to load"),
opened: Opened::default(),
settings: AppSettings {
is_mobile: mobile,
..AppSettings::default()
},
settings: Default::default(),
}
}
@@ -292,30 +270,27 @@ impl MathApp {
.show(ctx, |ui| {
let prev_sum = self.settings.riemann_sum;
// ComboBox for selecting what Riemann sum type to use
ui.add_enabled_ui(
self.functions.iter().filter(|func| func.integral).count() > 0,
|ui| {
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",
);
});
},
);
ui.add_enabled_ui(self.functions.any_using_integral(), |ui| {
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;
@@ -379,39 +354,26 @@ impl MathApp {
self.settings.integral_changed =
max_x_changed | min_x_changed | integral_num_changed | riemann_changed;
let can_remove = self.functions.len() > 1;
ui.label("Functions:");
self.functions.display_entries(ui);
let mut remove_i: Option<usize> = None;
for (i, function) in self.functions.iter_mut().enumerate() {
// Entry for a function
if function.function_entry(ui, can_remove, i, self.settings.is_mobile) {
remove_i = Some(i);
}
// Only render if there's enough space
if ui.available_height() > 0.0 {
ui.with_layout(egui::Layout::bottom_up(emath::Align::Min), |ui| {
// Contents put in reverse order from bottom to top due to the 'buttom_up' layout
function.settings_window(ctx);
// Licensing information
ui.label(
RichText::new("(and licensed under AGPLv3)").color(Color32::LIGHT_GRAY),
)
.on_hover_text(&self.text.license_info);
// Hyperlink to project's github
ui.hyperlink_to(
"I'm Open Source!",
"https://github.com/Titaniumtown/YTBN-Graphing-Software",
);
});
}
// Remove function if the user requests it
if let Some(remove_i_unwrap) = remove_i {
self.functions.remove(remove_i_unwrap);
}
ui.with_layout(egui::Layout::bottom_up(emath::Align::Min), |ui| {
// Contents put in reverse order from bottom to top due to the 'buttom_up' layout
// Licensing information
ui.label(
RichText::new("(and licensed under AGPLv3)").color(Color32::LIGHT_GRAY),
)
.on_hover_text(&self.text.license_info);
// Hyperlink to project's github
ui.hyperlink_to(
"I'm Open Source!",
"https://github.com/Titaniumtown/YTBN-Graphing-Software",
);
});
});
}
}
@@ -458,7 +420,7 @@ impl epi::App for MathApp {
.on_hover_text("Create and graph new function")
.clicked()
{
self.functions.push(DEFAULT_FUNCTION_ENTRY.clone());
self.functions.new_function();
}
// Toggles opening the Help window
@@ -575,8 +537,9 @@ impl epi::App for MathApp {
// Display an error if it exists
let errors_formatted: String = self
.functions
.get_entries()
.iter()
.map(|func| func.get_test_result())
.map(|(_, func)| func.get_test_result())
.enumerate()
.filter(|(_, error)| error.is_some())
.map(|(i, error)| {
@@ -612,22 +575,21 @@ impl epi::App for MathApp {
let minx_bounds: f64 = bounds.min()[0];
let maxx_bounds: f64 = bounds.max()[0];
dyn_mut_iter(&mut self.functions)
.enumerate()
.for_each(|(_, function)| {
function.calculate(
&minx_bounds,
&maxx_bounds,
width_changed,
&self.settings,
)
});
dyn_mut_iter(self.functions.get_entries_mut()).for_each(|(_, function)| {
function.calculate(
&minx_bounds,
&maxx_bounds,
width_changed,
&self.settings,
)
});
area_list = self
.functions
.get_entries()
.iter()
.enumerate()
.map(|(i, function)| {
.map(|(i, (_, function))| {
function.display(plot_ui, &self.settings, COLORS[i])
})
.collect();