initial function management refactoring
This commit is contained in:
parent
22d1be59f5
commit
2172f3da61
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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
28
TODO.md
@ -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
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
55
src/function_manager.rs
Normal 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 }
|
||||
}
|
||||
@ -7,6 +7,7 @@ extern crate static_assertions;
|
||||
|
||||
mod consts;
|
||||
mod function_entry;
|
||||
mod function_manager;
|
||||
mod math_app;
|
||||
mod misc;
|
||||
mod widgets;
|
||||
|
||||
@ -7,6 +7,7 @@ extern crate static_assertions;
|
||||
|
||||
mod consts;
|
||||
mod function_entry;
|
||||
mod function_manager;
|
||||
mod math_app;
|
||||
mod misc;
|
||||
mod widgets;
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user