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

11
Cargo.lock generated
View File

@ -1913,6 +1913,16 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "uuid"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0"
dependencies = [
"getrandom",
"rand",
]
[[package]]
name = "valuable"
version = "0.1.0"
@ -2337,6 +2347,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"tracing-wasm",
"uuid",
"wasm-bindgen",
"web-sys",
"wee_alloc",

View File

@ -47,6 +47,8 @@ tracing = "0.1.34"
itertools = "0.10.3"
static_assertions = "1.1.0"
phf = "0.10.1"
uuid = { version = "1.0.0", features = ["v4", "fast-rng"] }
[build-dependencies]
shadow-rs = "0.11.0"

28
TODO.md
View File

@ -1,18 +1,18 @@
## TODO:
1. Multiple functions in one graph.
- Backend support
1. Function management
- Integrals between functions (too hard to implement, maybe will shelve)
- Display intersection between functions (would have to rewrite a lot of the function plotting handling)
2. Rerwite of function parsing code
- Non `y=` functions.
3. Smart display of graph
- Sort by UUIDs
- [Drag and drop support](https://github.com/emilk/egui/discussions/1530) in the UI
- Hide/disable functions
2. Smart display of graph
- Display of intersections between functions
4. Allow constants in min/max integral input (like pi or euler's number)
5. Sliding values for functions (like a user-interactable slider that adjusts a variable in the function, like desmos)
6. Fix integral display
7. Better handling of panics and errors to display to the user
8. Turn Dynamic Iterator functions into traits
9. Better handling of roots and extrema finding
10. Add closing animation for function entry
11. Create actual icon(s) for PWA/favicon (using placeholder from eframe_template)
12. Fix mobile text input
3. Allow constants in min/max integral input (like pi or euler's number)
4. Sliding values for functions (like a user-interactable slider that adjusts a variable in the function, like desmos)
5. Fix integral display
6. Better handling of panics and errors to display to the user
7. Turn Dynamic Iterator functions into traits
8. Better handling of roots and extrema finding
9. Add closing animation for function entry
10. Create actual icon(s) for PWA/favicon (using placeholder from eframe_template)
11. Fix mobile text input

View File

@ -7,17 +7,18 @@
"- PI is available through 'pi' or 'π'"
],
"help_panel": [
"- The 'Panel' button on the top bar toggles if the side bar should be shown or not. This can also be accomplished by pressing the 'h' key.",
"- The 'Add Function' button on the top panel adds a new function to be graphed. You can then configure that function in the side panel.",
"- The 'Help' button on the top bar opens and closes this window!",
"- The 'Panel' button toggles if the side bar should be shown or not. This can also be accomplished by pressing the 'h' key.",
"- The 'Add Function' button adds a new function to be graphed. You can then configure that function in the side panel.",
"- The 'Help' button opens and closes this window!",
"- The 'Info' button provides information on the build currently running.",
"- The Sun/Moon button toggles Dark and Light mode."
],
"help_function": [
"- The '✖' button before the '∫' button allows you to delete the function in question. Deleting a function is prevented if only 1 function exists.",
"- The ∫ button (between the '✖' and 'd/dx' buttons) indicates whether to integrate the function in question.",
"- The 'd/dx' button next to the gear icon indicates whether or not calculating the derivative is enabled or not.",
"- The gear icon next to the function input allows you to tweak settings in relation to the selected function."
"(From Left to Right)",
"- The `✖` allows you to delete the function in question. Deleting a function is prevented if only 1 function exists.",
"- The `∫` indicates whether to integrate the function in question.",
"- The `d/dx` toggles the calculation of derivatives.",
"- The `⚙` opens a window to tweak function options."
],
"help_other": [
"- Extrema (local minimums and maximums) and Roots (intersections with the x-axis) are displayed though yellow and light blue points on the graph. You can toggle these in the Side Panel.",

View File

@ -18,3 +18,6 @@ lazy_static = "1.4.0"
[build-dependencies]
phf_codegen = "0.10.0"
[package.metadata.cargo-all-features]
skip_optional_dependencies = true #don't test optional dependencies, only features

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,9 +270,7 @@ 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| {
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| {
@ -314,8 +290,7 @@ impl MathApp {
"Right",
);
});
},
);
});
let riemann_changed = prev_sum != self.settings.riemann_sum;
@ -379,24 +354,10 @@ 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:");
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);
}
function.settings_window(ctx);
}
// Remove function if the user requests it
if let Some(remove_i_unwrap) = remove_i {
self.functions.remove(remove_i_unwrap);
}
self.functions.display_entries(ui);
// 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
@ -412,6 +373,7 @@ impl MathApp {
"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,9 +575,7 @@ 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)| {
dyn_mut_iter(self.functions.get_entries_mut()).for_each(|(_, function)| {
function.calculate(
&minx_bounds,
&maxx_bounds,
@ -625,9 +586,10 @@ impl epi::App for MathApp {
area_list = self
.functions
.get_entries()
.iter()
.enumerate()
.map(|(i, function)| {
.map(|(i, (_, function))| {
function.display(plot_ui, &self.settings, COLORS[i])
})
.collect();