updated
This commit is contained in:
parent
9a8f8a6539
commit
b37a6223bc
1414
Cargo.lock
generated
1414
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@ -33,14 +33,14 @@ strip = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
parsing = { path = "./parsing" }
|
parsing = { path = "./parsing" }
|
||||||
eframe = { git = "https://github.com/Titaniumtown/egui.git", default-features = false, features = [
|
eframe = { git = "https://github.com/emilk/egui.git", default-features = false, features = [
|
||||||
"glow",
|
"glow",
|
||||||
] }
|
] }
|
||||||
egui = { git = "https://github.com/Titaniumtown/egui.git", default-features = false, features = [
|
egui = { git = "https://github.com/emilk/egui.git", default-features = false, features = [
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
epaint = { git = "https://github.com/Titaniumtown/egui.git", default-features = false }
|
epaint = { git = "https://github.com/emilk/egui.git", default-features = false }
|
||||||
emath = { git = "https://github.com/Titaniumtown/egui.git", default-features = false }
|
emath = { git = "https://github.com/emilk/egui.git", default-features = false }
|
||||||
|
|
||||||
shadow-rs = { version = "0.12", default-features = false }
|
shadow-rs = { version = "0.12", default-features = false }
|
||||||
const_format = { version = "0.2", default-features = false, features = ["fmt"] }
|
const_format = { version = "0.2", default-features = false, features = ["fmt"] }
|
||||||
@ -57,8 +57,8 @@ benchmarks = { path = "./benchmarks" }
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = "0.12"
|
shadow-rs = "0.12"
|
||||||
epaint = { git = "https://github.com/Titaniumtown/egui.git", default-features = false }
|
epaint = { git = "https://github.com/emilk/egui.git", default-features = false }
|
||||||
egui = { git = "https://github.com/Titaniumtown/egui.git", default-features = false, features = [
|
egui = { git = "https://github.com/emilk/egui.git", default-features = false, features = [
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
bincode = "1.3"
|
bincode = "1.3"
|
||||||
|
|||||||
5
build.rs
5
build.rs
@ -146,10 +146,7 @@ fn main() {
|
|||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = bincode::serialize(&TotalData {
|
let data = bincode::serialize(&TotalData { fonts }).unwrap();
|
||||||
fonts,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let zstd_levels = zstd::compression_level_range();
|
let zstd_levels = zstd::compression_level_range();
|
||||||
let data_compressed =
|
let data_compressed =
|
||||||
|
|||||||
@ -10,13 +10,13 @@ description = "Parsing library for YTBN-Graphing-Software"
|
|||||||
[lib]
|
[lib]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
phf = { version = "0.10", no-default-features = true }
|
phf = { version = "0.11", no-default-features = true }
|
||||||
exmex = { git = "https://github.com/bertiqwerty/exmex.git", branch = "main", features = [
|
exmex = { git = "https://github.com/bertiqwerty/exmex.git", branch = "main", features = [
|
||||||
"partial",
|
"partial",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
phf_codegen = { version = "0.10", no-default-features = true }
|
phf_codegen = { version = "0.11", no-default-features = true }
|
||||||
|
|
||||||
[package.metadata.cargo-all-features]
|
[package.metadata.cargo-all-features]
|
||||||
skip_optional_dependencies = true #don't test optional dependencies, only features
|
skip_optional_dependencies = true #don't test optional dependencies, only features
|
||||||
|
|||||||
@ -25,7 +25,7 @@ pub fn compile_hashmap(data: Vec<String>) -> Vec<(String, String)> {
|
|||||||
let mut seen_3: HashSet<String> = HashSet::new();
|
let mut seen_3: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
for (key, value) in tuple_list_1.iter() {
|
for (key, value) in tuple_list_1.iter() {
|
||||||
if seen_3.contains(&*key) {
|
if seen_3.contains(key) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,18 +33,20 @@ pub fn compile_hashmap(data: Vec<String>) -> Vec<(String, String)> {
|
|||||||
|
|
||||||
let count_keys = keys.iter().filter(|a| a == &&key).count();
|
let count_keys = keys.iter().filter(|a| a == &&key).count();
|
||||||
|
|
||||||
if count_keys == 1 {
|
match count_keys.cmp(&1usize) {
|
||||||
output.push((key.clone(), format!(r#"Hint::Single("{}")"#, value)));
|
Ordering::Less => {
|
||||||
} else if count_keys > 1 {
|
panic!("Number of values for {key} is 0!");
|
||||||
let mut multi_data = tuple_list_1
|
}
|
||||||
.iter()
|
Ordering::Greater => {
|
||||||
.filter(|(a, _)| a == key)
|
let mut multi_data = tuple_list_1
|
||||||
.map(|(_, b)| b)
|
.iter()
|
||||||
.collect::<Vec<&String>>();
|
.filter(|(a, _)| a == key)
|
||||||
multi_data.sort_unstable_by(|a, b| compare_len_reverse_alpha(a, b));
|
.map(|(_, b)| b)
|
||||||
output.push((key.clone(), format!("Hint::Many(&{:?})", multi_data)));
|
.collect::<Vec<&String>>();
|
||||||
} else {
|
multi_data.sort_unstable_by(|a, b| compare_len_reverse_alpha(a, b));
|
||||||
panic!("Number of values for {key} is 0!");
|
output.push((key.clone(), format!("Hint::Many(&{:?})", multi_data)));
|
||||||
|
}
|
||||||
|
Ordering::Equal => output.push((key.clone(), format!(r#"Hint::Single("{}")"#, value))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,12 +26,7 @@ impl FlatExWrapper {
|
|||||||
fn partial(&self, x: usize) -> Self {
|
fn partial(&self, x: usize) -> Self {
|
||||||
self.func
|
self.func
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|f| {
|
.map(|f| f.clone().partial(x).map(Self::new).unwrap_or(Self::EMPTY))
|
||||||
f.clone()
|
|
||||||
.partial(x)
|
|
||||||
.map(|a| Self::new(a))
|
|
||||||
.unwrap_or(Self::EMPTY)
|
|
||||||
})
|
|
||||||
.unwrap_or(Self::EMPTY)
|
.unwrap_or(Self::EMPTY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,8 +39,8 @@ impl FlatExWrapper {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|f| {
|
.map(|f| {
|
||||||
f.clone()
|
f.clone()
|
||||||
.partial_iter((0..=n).map(|_| 0).into_iter())
|
.partial_iter((0..=n).map(|_| 0))
|
||||||
.map(|a| Self::new(a))
|
.map(Self::new)
|
||||||
.unwrap_or(Self::EMPTY)
|
.unwrap_or(Self::EMPTY)
|
||||||
})
|
})
|
||||||
.unwrap_or(Self::EMPTY)
|
.unwrap_or(Self::EMPTY)
|
||||||
@ -151,8 +146,7 @@ impl BackingFunction {
|
|||||||
|
|
||||||
/// Get string relating to the nth derivative
|
/// Get string relating to the nth derivative
|
||||||
pub fn get_nth_derivative_str(&self) -> &str {
|
pub fn get_nth_derivative_str(&self) -> &str {
|
||||||
&self
|
self.nth_derivative
|
||||||
.nth_derivative
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|a| a.2.as_str())
|
.map(|a| a.2.as_str())
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
@ -207,6 +201,6 @@ pub fn process_func_str(function_in: &str) -> String {
|
|||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::suggestions::split_function(&function_in, crate::suggestions::SplitType::Multiplication)
|
crate::suggestions::split_function(function_in, crate::suggestions::SplitType::Multiplication)
|
||||||
.join("*")
|
.join("*")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,11 +25,11 @@ pub fn split_function(input: &str, split: SplitType) -> Vec<String> {
|
|||||||
split,
|
split,
|
||||||
)
|
)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.replace("\u{1fc93}", "exp")) // Convert back to `exp` text
|
.map(|x| x.replace('\u{1fc93}', "exp")) // Convert back to `exp` text
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum SplitType {
|
pub enum SplitType {
|
||||||
Multiplication,
|
Multiplication,
|
||||||
Term,
|
Term,
|
||||||
@ -108,7 +108,7 @@ pub fn split_function_chars(chars: &[char], split: SplitType) -> Vec<String> {
|
|||||||
|
|
||||||
const fn splitable(&self, c: &char, other: &BoolSlice, split: &SplitType) -> bool {
|
const fn splitable(&self, c: &char, other: &BoolSlice, split: &SplitType) -> bool {
|
||||||
if (*c == '*') | (matches!(split, &SplitType::Term) && other.open_parens) {
|
if (*c == '*') | (matches!(split, &SplitType::Term) && other.open_parens) {
|
||||||
return true;
|
true
|
||||||
} else if other.closing_parens {
|
} else if other.closing_parens {
|
||||||
// Cases like `)x`, `)2`, and `)(`
|
// Cases like `)x`, `)2`, and `)(`
|
||||||
return (*c == '(')
|
return (*c == '(')
|
||||||
@ -131,11 +131,8 @@ pub fn split_function_chars(chars: &[char], split: SplitType) -> Vec<String> {
|
|||||||
&& (other.is_unmasked_number() | other.letter)
|
&& (other.is_unmasked_number() | other.letter)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
} else if self.is_unmasked_number() && other.is_unmasked_variable() {
|
|
||||||
// Cases like `x2`
|
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return self.is_unmasked_number() && other.is_unmasked_variable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,7 +170,7 @@ pub fn split_function_chars(chars: &[char], split: SplitType) -> Vec<String> {
|
|||||||
/// Generate a hint based on the input `input`, returns an `Option<String>`
|
/// Generate a hint based on the input `input`, returns an `Option<String>`
|
||||||
pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> {
|
pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> {
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
return &HINT_EMPTY;
|
&HINT_EMPTY
|
||||||
} else {
|
} else {
|
||||||
let chars: Vec<char> = input.chars().collect::<Vec<char>>();
|
let chars: Vec<char> = input.chars().collect::<Vec<char>>();
|
||||||
|
|
||||||
@ -181,8 +178,16 @@ pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> {
|
|||||||
assume(!chars.is_empty());
|
assume(!chars.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(hint) = COMPLETION_HASHMAP.get(get_last_term(&chars).as_str()) {
|
let key = get_last_term(&chars);
|
||||||
return hint;
|
match key {
|
||||||
|
Some(key) => {
|
||||||
|
if let Some(hint) = COMPLETION_HASHMAP.get(&key) {
|
||||||
|
return hint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return &Hint::None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut open_parens: usize = 0;
|
let mut open_parens: usize = 0;
|
||||||
@ -197,21 +202,17 @@ pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> {
|
|||||||
return &HINT_CLOSED_PARENS;
|
return &HINT_CLOSED_PARENS;
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Hint::None;
|
&Hint::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_last_term(chars: &[char]) -> String {
|
pub fn get_last_term(chars: &[char]) -> Option<String> {
|
||||||
assert!(!chars.is_empty());
|
if chars.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
let mut result = split_function_chars(chars, SplitType::Term);
|
let mut result = split_function_chars(chars, SplitType::Term);
|
||||||
unsafe {
|
result.pop()
|
||||||
assume(!result.is_empty());
|
|
||||||
assume(result.len() > 0);
|
|
||||||
let output = result.pop();
|
|
||||||
assume(output.is_some());
|
|
||||||
output.unwrap_unchecked()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Copy)]
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
@ -225,13 +226,13 @@ impl<'a> std::fmt::Display for Hint<'a> {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Hint::Single(single_data) => {
|
Hint::Single(single_data) => {
|
||||||
return write!(f, "{}", single_data);
|
write!(f, "{}", single_data)
|
||||||
}
|
}
|
||||||
Hint::Many(multi_data) => {
|
Hint::Many(multi_data) => {
|
||||||
return write!(f, "{:?}", multi_data);
|
write!(f, "{:?}", multi_data)
|
||||||
}
|
}
|
||||||
Hint::None => {
|
Hint::None => {
|
||||||
return write!(f, "None");
|
write!(f, "None")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,3 +278,33 @@ impl<'a> Hint<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
|
include!(concat!(env!("OUT_DIR"), "/codegen.rs"));
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn assert_test(input: &str, expected: &[&str], split: SplitType) {
|
||||||
|
let output = split_function("sin(x)cos(x)", SplitType::Multiplication);
|
||||||
|
let expected_owned = expected
|
||||||
|
.iter()
|
||||||
|
.map(|&x| x.to_owned())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
if output != expected_owned {
|
||||||
|
panic!(
|
||||||
|
"split type: {:?} of {} resulted in {:?} not {:?}",
|
||||||
|
split, input, output, expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn split_function_test() {
|
||||||
|
assert_test(
|
||||||
|
"sin(x)cos(x)",
|
||||||
|
&["sin(x)", "cos(x)"],
|
||||||
|
SplitType::Multiplication,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_test(
|
||||||
|
"tanh(cos(x)xx)cos(x)",
|
||||||
|
&["tanh(cos(x)", "x", "x)", "cos(x)"],
|
||||||
|
SplitType::Multiplication,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::math_app::AppSettings;
|
use crate::math_app::AppSettings;
|
||||||
use crate::misc::*;
|
use crate::misc::*;
|
||||||
use egui::{
|
use egui::{
|
||||||
plot::{BarChart, PlotUi, Value},
|
plot::{BarChart, PlotPoint, PlotUi},
|
||||||
widgets::plot::Bar,
|
widgets::plot::Bar,
|
||||||
Checkbox, Context,
|
Checkbox, Context,
|
||||||
};
|
};
|
||||||
@ -48,12 +48,12 @@ pub struct FunctionEntry {
|
|||||||
|
|
||||||
pub nth_derviative: bool,
|
pub nth_derviative: bool,
|
||||||
|
|
||||||
pub back_data: Vec<Value>,
|
pub back_data: Vec<PlotPoint>,
|
||||||
pub integral_data: Option<(Vec<Bar>, f64)>,
|
pub integral_data: Option<(Vec<Bar>, f64)>,
|
||||||
pub derivative_data: Vec<Value>,
|
pub derivative_data: Vec<PlotPoint>,
|
||||||
pub extrema_data: Vec<Value>,
|
pub extrema_data: Vec<PlotPoint>,
|
||||||
pub root_data: Vec<Value>,
|
pub root_data: Vec<PlotPoint>,
|
||||||
nth_derivative_data: Option<Vec<Value>>,
|
nth_derivative_data: Option<Vec<PlotPoint>>,
|
||||||
|
|
||||||
pub autocomplete: AutoComplete<'static>,
|
pub autocomplete: AutoComplete<'static>,
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ impl FunctionEntry {
|
|||||||
/// Helps with processing newton's method depending on level of derivative
|
/// Helps with processing newton's method depending on level of derivative
|
||||||
fn newtons_method_helper(
|
fn newtons_method_helper(
|
||||||
&self, threshold: f64, derivative_level: usize, range: &std::ops::Range<f64>,
|
&self, threshold: f64, derivative_level: usize, range: &std::ops::Range<f64>,
|
||||||
) -> Vec<Value> {
|
) -> Vec<PlotPoint> {
|
||||||
let newtons_method_output: Vec<f64> = match derivative_level {
|
let newtons_method_output: Vec<f64> = match derivative_level {
|
||||||
0 => newtons_method_helper(
|
0 => newtons_method_helper(
|
||||||
threshold,
|
threshold,
|
||||||
@ -261,7 +261,7 @@ impl FunctionEntry {
|
|||||||
|
|
||||||
newtons_method_output
|
newtons_method_output
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| Value::new(x, self.function.get(x)))
|
.map(|x| PlotPoint::new(x, self.function.get(x)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,10 +292,10 @@ impl FunctionEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.back_data.is_empty() {
|
if self.back_data.is_empty() {
|
||||||
let data: Vec<Value> = resolution_iter
|
let data: Vec<PlotPoint> = resolution_iter
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| Value::new(x, self.function.get(x)))
|
.map(|x| PlotPoint::new(x, self.function.get(x)))
|
||||||
.collect();
|
.collect();
|
||||||
debug_assert_eq!(data.len(), settings.plot_width + 1);
|
debug_assert_eq!(data.len(), settings.plot_width + 1);
|
||||||
|
|
||||||
@ -303,19 +303,19 @@ impl FunctionEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.derivative_data.is_empty() {
|
if self.derivative_data.is_empty() {
|
||||||
let data: Vec<Value> = resolution_iter
|
let data: Vec<PlotPoint> = resolution_iter
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| Value::new(x, self.function.get_derivative_1(x)))
|
.map(|x| PlotPoint::new(x, self.function.get_derivative_1(x)))
|
||||||
.collect();
|
.collect();
|
||||||
debug_assert_eq!(data.len(), settings.plot_width + 1);
|
debug_assert_eq!(data.len(), settings.plot_width + 1);
|
||||||
self.derivative_data = data;
|
self.derivative_data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.nth_derviative && self.nth_derivative_data.is_none() {
|
if self.nth_derviative && self.nth_derivative_data.is_none() {
|
||||||
let data: Vec<Value> = resolution_iter
|
let data: Vec<PlotPoint> = resolution_iter
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| Value::new(x, self.function.get_nth_derivative(self.curr_nth, x)))
|
.map(|x| PlotPoint::new(x, self.function.get_nth_derivative(self.curr_nth, x)))
|
||||||
.collect();
|
.collect();
|
||||||
debug_assert_eq!(data.len(), settings.plot_width + 1);
|
debug_assert_eq!(data.len(), settings.plot_width + 1);
|
||||||
self.nth_derivative_data = Some(data);
|
self.nth_derivative_data = Some(data);
|
||||||
@ -380,9 +380,9 @@ impl FunctionEntry {
|
|||||||
&& (settings.integral_max_x > value.x)
|
&& (settings.integral_max_x > value.x)
|
||||||
})
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<Value>>()
|
.collect::<Vec<PlotPoint>>()
|
||||||
.to_line()
|
.to_line()
|
||||||
.stroke(epaint::Stroke::none())
|
.stroke(epaint::Stroke::NONE)
|
||||||
.color(Color32::from_rgb(4, 4, 255))
|
.color(Color32::from_rgb(4, 4, 255))
|
||||||
.fill(0.0),
|
.fill(0.0),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
consts::COLORS, function_entry::FunctionEntry, misc::random_u64, widgets::widgets_ontop,
|
consts::COLORS,
|
||||||
|
function_entry::FunctionEntry,
|
||||||
|
misc::{create_id, get_u64_id, random_u64},
|
||||||
|
widgets::widgets_ontop,
|
||||||
};
|
};
|
||||||
use egui::{Button, Id, Key, Modifiers, TextEdit, WidgetText};
|
use egui::{Button, Id, Key, Modifiers, TextEdit, WidgetText};
|
||||||
use emath::vec2;
|
use emath::vec2;
|
||||||
@ -19,7 +22,7 @@ impl Default for FunctionManager {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let mut vec: Functions = Vec::with_capacity(COLORS.len());
|
let mut vec: Functions = Vec::with_capacity(COLORS.len());
|
||||||
vec.push((
|
vec.push((
|
||||||
Id(11414819524356497634), // Random number here to avoid call to crate::misc::random_u64()
|
create_id(11414819524356497634), // Random number here to avoid call to crate::misc::random_u64()
|
||||||
FunctionEntry::EMPTY,
|
FunctionEntry::EMPTY,
|
||||||
));
|
));
|
||||||
Self { functions: vec }
|
Self { functions: vec }
|
||||||
@ -37,7 +40,7 @@ impl Serialize for FunctionManager {
|
|||||||
&self
|
&self
|
||||||
.functions
|
.functions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, func)| (id.0, func.clone()))
|
.map(|(id, func)| (get_u64_id(*id), func.clone()))
|
||||||
.collect::<Vec<(u64, FunctionEntry)>>(),
|
.collect::<Vec<(u64, FunctionEntry)>>(),
|
||||||
)?;
|
)?;
|
||||||
s.end()
|
s.end()
|
||||||
@ -59,7 +62,7 @@ impl<'de> Deserialize<'de> for FunctionManager {
|
|||||||
.0
|
.0
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|(id, func)| (Id(id), func))
|
.map(|(id, func)| (create_id(id), func))
|
||||||
.collect::<Vec<(Id, FunctionEntry)>>(),
|
.collect::<Vec<(Id, FunctionEntry)>>(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -86,22 +89,23 @@ impl FunctionManager {
|
|||||||
let mut remove_i: Option<usize> = None;
|
let mut remove_i: Option<usize> = None;
|
||||||
let target_size = vec2(available_width, crate::data::FONT_SIZE);
|
let target_size = vec2(available_width, crate::data::FONT_SIZE);
|
||||||
for (i, (te_id, function)) in self.functions.iter_mut().enumerate() {
|
for (i, (te_id, function)) in self.functions.iter_mut().enumerate() {
|
||||||
|
let te_id = *te_id;
|
||||||
let mut new_string = function.autocomplete.string.clone();
|
let mut new_string = function.autocomplete.string.clone();
|
||||||
function.update_string(&new_string);
|
function.update_string(&new_string);
|
||||||
|
|
||||||
let mut movement: Movement = Movement::default();
|
let mut movement: Movement = Movement::default();
|
||||||
|
|
||||||
let size_multiplier = vec2(1.0, {
|
let size_multiplier = vec2(1.0, {
|
||||||
let had_focus = ui.ctx().memory().has_focus(*te_id);
|
let had_focus = ui.ctx().memory(|x| x.has_focus(te_id));
|
||||||
(ui.ctx().animate_bool(*te_id, had_focus) * 1.5) + 1.0
|
(ui.ctx().animate_bool(te_id, had_focus) * 1.5) + 1.0
|
||||||
});
|
});
|
||||||
|
|
||||||
let re = ui.add_sized(
|
let re = ui.add_sized(
|
||||||
target_size * size_multiplier,
|
target_size * size_multiplier,
|
||||||
egui::TextEdit::singleline(&mut new_string)
|
egui::TextEdit::singleline(&mut new_string)
|
||||||
.hint_forward(true) // Make the hint appear after the last text in the textbox
|
// .hint_forward(true) // Make the hint appear after the last text in the textbox
|
||||||
.lock_focus(true)
|
.lock_focus(true)
|
||||||
.id(*te_id) // Set widget's id to `te_id`
|
.id(te_id) // Set widget's id to `te_id`
|
||||||
.hint_text({
|
.hint_text({
|
||||||
// If there's a single hint, go ahead and apply the hint here, if not, set the hint to an empty string
|
// If there's a single hint, go ahead and apply the hint here, if not, set the hint to an empty string
|
||||||
match function.autocomplete.hint.single() {
|
match function.autocomplete.hint.single() {
|
||||||
@ -115,24 +119,31 @@ impl FunctionManager {
|
|||||||
new_string.retain(|c| crate::misc::is_valid_char(&c));
|
new_string.retain(|c| crate::misc::is_valid_char(&c));
|
||||||
|
|
||||||
// If not fully open, return here as buttons cannot yet be displayed, therefore the user is inable to mark it for deletion
|
// 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());
|
let animate_bool = ui.ctx().animate_bool(te_id, re.has_focus());
|
||||||
if animate_bool == 1.0 {
|
if animate_bool == 1.0 {
|
||||||
function.autocomplete.update_string(&new_string);
|
function.autocomplete.update_string(&new_string);
|
||||||
|
|
||||||
if function.autocomplete.hint.is_some() {
|
if function.autocomplete.hint.is_some() {
|
||||||
// only register up and down arrow movements if hint is type `Hint::Many`
|
// only register up and down arrow movements if hint is type `Hint::Many`
|
||||||
if !function.autocomplete.hint.is_single() {
|
if !function.autocomplete.hint.is_single() {
|
||||||
if ui.input().key_pressed(Key::ArrowDown) {
|
let (arrow_down, arrow_up) = ui.input(|x| {
|
||||||
|
(x.key_pressed(Key::ArrowDown), x.key_pressed(Key::ArrowUp))
|
||||||
|
});
|
||||||
|
if arrow_down {
|
||||||
movement = Movement::Down;
|
movement = Movement::Down;
|
||||||
} else if ui.input().key_pressed(Key::ArrowUp) {
|
} else if arrow_up {
|
||||||
movement = Movement::Up;
|
movement = Movement::Up;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put here so these key presses don't interact with other elements
|
// Put here so these key presses don't interact with other elements
|
||||||
let enter_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Enter);
|
let (enter_pressed, tab_pressed) = ui.input_mut(|x| {
|
||||||
let tab_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Tab);
|
(
|
||||||
if enter_pressed | tab_pressed | ui.input().key_pressed(Key::ArrowRight) {
|
x.consume_key(Modifiers::NONE, Key::Enter),
|
||||||
|
x.consume_key(Modifiers::NONE, Key::Tab),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
if enter_pressed | tab_pressed | ui.input(|x| x.key_pressed(Key::ArrowRight)) {
|
||||||
movement = Movement::Complete;
|
movement = Movement::Complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +154,7 @@ impl FunctionManager {
|
|||||||
// Doesn't need to have a number in id as there should only be 1 autocomplete popup in the entire gui
|
// Doesn't need to have a number in id as there should only be 1 autocomplete popup in the entire gui
|
||||||
|
|
||||||
// hashed "autocomplete_popup"
|
// hashed "autocomplete_popup"
|
||||||
const POPUP_ID: Id = Id(7574801616484505465);
|
const POPUP_ID: Id = create_id(7574801616484505465);
|
||||||
|
|
||||||
let mut clicked = false;
|
let mut clicked = false;
|
||||||
|
|
||||||
@ -164,17 +175,17 @@ impl FunctionManager {
|
|||||||
|
|
||||||
movement = Movement::Complete;
|
movement = Movement::Complete;
|
||||||
} else {
|
} else {
|
||||||
ui.memory().open_popup(POPUP_ID);
|
ui.memory_mut(|x| x.open_popup(POPUP_ID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push cursor to end if needed
|
// Push cursor to end if needed
|
||||||
if movement == Movement::Complete {
|
if movement == Movement::Complete {
|
||||||
let mut state =
|
let mut state =
|
||||||
unsafe { TextEdit::load_state(ui.ctx(), *te_id).unwrap_unchecked() };
|
unsafe { TextEdit::load_state(ui.ctx(), te_id).unwrap_unchecked() };
|
||||||
let ccursor = egui::text::CCursor::new(function.autocomplete.string.len());
|
let ccursor = egui::text::CCursor::new(function.autocomplete.string.len());
|
||||||
state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor)));
|
state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor)));
|
||||||
TextEdit::store_state(ui.ctx(), *te_id, state);
|
TextEdit::store_state(ui.ctx(), te_id, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +193,7 @@ impl FunctionManager {
|
|||||||
const BUTTONS_Y_OFFSET: f32 = 1.32;
|
const BUTTONS_Y_OFFSET: f32 = 1.32;
|
||||||
const Y_OFFSET: f32 = crate::data::FONT_SIZE * BUTTONS_Y_OFFSET;
|
const Y_OFFSET: f32 = crate::data::FONT_SIZE * BUTTONS_Y_OFFSET;
|
||||||
|
|
||||||
widgets_ontop(ui, i, &re, Y_OFFSET, |ui| {
|
widgets_ontop(ui, create_id(i as u64), &re, Y_OFFSET, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
// There's more than 1 function! Functions can now be deleted
|
// There's more than 1 function! Functions can now be deleted
|
||||||
if ui
|
if ui
|
||||||
@ -244,7 +255,7 @@ impl FunctionManager {
|
|||||||
/// Create and push new empty function entry
|
/// Create and push new empty function entry
|
||||||
pub fn push_empty(&mut self) {
|
pub fn push_empty(&mut self) {
|
||||||
self.functions.push((
|
self.functions.push((
|
||||||
Id(random_u64().expect("unable to generate random id")),
|
create_id(random_u64().expect("unable to generate random id")),
|
||||||
FunctionEntry::EMPTY,
|
FunctionEntry::EMPTY,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,6 @@ mod function_entry;
|
|||||||
mod function_manager;
|
mod function_manager;
|
||||||
mod math_app;
|
mod math_app;
|
||||||
mod misc;
|
mod misc;
|
||||||
mod style;
|
|
||||||
mod unicode_helper;
|
mod unicode_helper;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,6 @@ mod function_entry;
|
|||||||
mod function_manager;
|
mod function_manager;
|
||||||
mod math_app;
|
mod math_app;
|
||||||
mod misc;
|
mod misc;
|
||||||
mod style;
|
|
||||||
mod unicode_helper;
|
mod unicode_helper;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
|
|
||||||
|
|||||||
@ -248,7 +248,7 @@ impl MathApp {
|
|||||||
cc.egui_ctx.set_fonts(data.fonts);
|
cc.egui_ctx.set_fonts(data.fonts);
|
||||||
|
|
||||||
// Set dark mode by default
|
// Set dark mode by default
|
||||||
cc.egui_ctx.set_visuals(crate::style::style());
|
// cc.egui_ctx.set_visuals(crate::style::style());
|
||||||
|
|
||||||
// Set spacing
|
// Set spacing
|
||||||
// cc.egui_ctx.set_spacing(crate::style::SPACING);
|
// cc.egui_ctx.set_spacing(crate::style::SPACING);
|
||||||
@ -433,7 +433,7 @@ impl App for MathApp {
|
|||||||
// If `H` key is pressed, toggle Side Panel
|
// If `H` key is pressed, toggle Side Panel
|
||||||
self.opened
|
self.opened
|
||||||
.side_panel
|
.side_panel
|
||||||
.bitxor_assign(ctx.input_mut().consume_key(egui::Modifiers::NONE, Key::H));
|
.bitxor_assign(ctx.input_mut(|x| x.consume_key(egui::Modifiers::NONE, Key::H)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates Top bar that contains some general options
|
// Creates Top bar that contains some general options
|
||||||
|
|||||||
44
src/misc.rs
44
src/misc.rs
@ -1,11 +1,13 @@
|
|||||||
use egui::plot::{Line, Points, Value, Values};
|
use egui::plot::{Line, PlotPoint, PlotPoints, Points};
|
||||||
|
use egui::Id;
|
||||||
|
use emath::Pos2;
|
||||||
use getrandom::getrandom;
|
use getrandom::getrandom;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
/// Implements traits that are useful when dealing with Vectors of egui's `Value`
|
/// Implements traits that are useful when dealing with Vectors of egui's `Value`
|
||||||
pub trait EguiHelper {
|
pub trait EguiHelper {
|
||||||
/// Converts to `egui::plot::Values`
|
/// Converts to `egui::plot::Values`
|
||||||
fn to_values(self) -> Values;
|
fn to_values(self) -> PlotPoints;
|
||||||
|
|
||||||
/// Converts to `egui::plot::Line`
|
/// Converts to `egui::plot::Line`
|
||||||
fn to_line(self) -> Line;
|
fn to_line(self) -> Line;
|
||||||
@ -17,9 +19,13 @@ pub trait EguiHelper {
|
|||||||
fn to_tuple(self) -> Vec<(f64, f64)>;
|
fn to_tuple(self) -> Vec<(f64, f64)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EguiHelper for Vec<Value> {
|
impl EguiHelper for Vec<PlotPoint> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn to_values(self) -> Values { Values::from_values(self) }
|
fn to_values(self) -> PlotPoints {
|
||||||
|
let a: Vec<[f64; 2]> =
|
||||||
|
unsafe { std::mem::transmute::<Vec<PlotPoint>, Vec<[f64; 2]>>(self) };
|
||||||
|
PlotPoints::from(a)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn to_line(self) -> Line { Line::new(self.to_values()) }
|
fn to_line(self) -> Line { Line::new(self.to_values()) }
|
||||||
@ -29,11 +35,35 @@ impl EguiHelper for Vec<Value> {
|
|||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn to_tuple(self) -> Vec<(f64, f64)> {
|
fn to_tuple(self) -> Vec<(f64, f64)> {
|
||||||
// self.iter().map(|ele| (ele.x, ele.y)).collect()
|
unsafe { std::mem::transmute::<Vec<PlotPoint>, Vec<(f64, f64)>>(self) }
|
||||||
unsafe { std::mem::transmute::<Vec<Value>, Vec<(f64, f64)>>(self) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Offset {
|
||||||
|
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_x(self, x_offset: f32) -> Pos2 {
|
||||||
|
Pos2 {
|
||||||
|
x: self.x + x_offset,
|
||||||
|
y: self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn create_id(x: u64) -> Id { unsafe { std::mem::transmute::<u64, Id>(x) } }
|
||||||
|
|
||||||
|
pub const fn get_u64_id(id: Id) -> u64 { unsafe { std::mem::transmute::<Id, u64>(id) } }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
/// Rounds f64 to `n` decimal places
|
/// Rounds f64 to `n` decimal places
|
||||||
pub fn decimal_round(x: f64, n: usize) -> f64 {
|
pub fn decimal_round(x: f64, n: usize) -> f64 {
|
||||||
@ -51,7 +81,7 @@ pub fn decimal_round(x: f64, n: usize) -> f64 {
|
|||||||
/// `f_1` is f'(x) aka the derivative of f(x)
|
/// `f_1` is f'(x) aka the derivative of f(x)
|
||||||
/// The function returns a Vector of `x` values where roots occur
|
/// The function returns a Vector of `x` values where roots occur
|
||||||
pub fn newtons_method_helper(
|
pub fn newtons_method_helper(
|
||||||
threshold: f64, range: &std::ops::Range<f64>, data: &[Value], f: &dyn Fn(f64) -> f64,
|
threshold: f64, range: &std::ops::Range<f64>, data: &[PlotPoint], f: &dyn Fn(f64) -> f64,
|
||||||
f_1: &dyn Fn(f64) -> f64,
|
f_1: &dyn Fn(f64) -> f64,
|
||||||
) -> Vec<f64> {
|
) -> Vec<f64> {
|
||||||
data.iter()
|
data.iter()
|
||||||
|
|||||||
87
src/style.rs
87
src/style.rs
@ -1,87 +0,0 @@
|
|||||||
use egui::{
|
|
||||||
style::{Margin, Selection, Spacing, WidgetVisuals, Widgets},
|
|
||||||
Visuals,
|
|
||||||
};
|
|
||||||
use emath::vec2;
|
|
||||||
use epaint::{Color32, Rounding, Shadow, Stroke};
|
|
||||||
|
|
||||||
fn widgets_dark() -> Widgets {
|
|
||||||
Widgets {
|
|
||||||
noninteractive: WidgetVisuals {
|
|
||||||
bg_fill: Color32::from_gray(27), // window background
|
|
||||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines, windows outlines
|
|
||||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color
|
|
||||||
rounding: Rounding::same(2.0),
|
|
||||||
expansion: 0.0,
|
|
||||||
},
|
|
||||||
inactive: WidgetVisuals {
|
|
||||||
bg_fill: Color32::from_gray(60), // button background
|
|
||||||
bg_stroke: Stroke::default(),
|
|
||||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text
|
|
||||||
rounding: Rounding::same(2.0),
|
|
||||||
expansion: 0.0,
|
|
||||||
},
|
|
||||||
hovered: WidgetVisuals {
|
|
||||||
bg_fill: Color32::from_gray(70),
|
|
||||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
|
|
||||||
fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
|
|
||||||
rounding: Rounding::same(3.0),
|
|
||||||
expansion: 1.0,
|
|
||||||
},
|
|
||||||
active: WidgetVisuals {
|
|
||||||
bg_fill: Color32::from_gray(55),
|
|
||||||
bg_stroke: Stroke::new(1.0, Color32::WHITE),
|
|
||||||
fg_stroke: Stroke::new(2.0, Color32::WHITE),
|
|
||||||
rounding: Rounding::same(2.0),
|
|
||||||
expansion: 1.0,
|
|
||||||
},
|
|
||||||
open: WidgetVisuals {
|
|
||||||
bg_fill: Color32::from_gray(27),
|
|
||||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(60)),
|
|
||||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(210)),
|
|
||||||
rounding: Rounding::same(2.0),
|
|
||||||
expansion: 0.0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn style() -> Visuals {
|
|
||||||
Visuals {
|
|
||||||
dark_mode: true,
|
|
||||||
override_text_color: None,
|
|
||||||
widgets: widgets_dark(),
|
|
||||||
selection: Selection::default(),
|
|
||||||
hyperlink_color: Color32::from_rgb(90, 170, 255),
|
|
||||||
faint_bg_color: Color32::from_gray(35),
|
|
||||||
extreme_bg_color: Color32::from_gray(10), // e.g. TextEdit background
|
|
||||||
code_bg_color: Color32::from_gray(64),
|
|
||||||
window_rounding: Rounding::same(1.5),
|
|
||||||
window_shadow: Shadow::default(), // no shadow
|
|
||||||
popup_shadow: Shadow::default(), // no shadow
|
|
||||||
resize_corner_size: 12.0,
|
|
||||||
text_cursor_width: 2.0,
|
|
||||||
text_cursor_preview: false,
|
|
||||||
clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
|
|
||||||
button_frame: true,
|
|
||||||
collapsing_header_frame: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spacing() -> Spacing {
|
|
||||||
Spacing {
|
|
||||||
item_spacing: vec2(8.0, 3.0),
|
|
||||||
window_margin: Margin::same(6.0),
|
|
||||||
button_padding: vec2(4.0, 1.0),
|
|
||||||
indent: 18.0, // match checkbox/radio-button with `button_padding.x + icon_width + icon_spacing`
|
|
||||||
interact_size: vec2(40.0, 18.0),
|
|
||||||
slider_width: 100.0,
|
|
||||||
text_edit_width: 280.0,
|
|
||||||
icon_width: 14.0,
|
|
||||||
icon_width_inner: 8.0,
|
|
||||||
icon_spacing: 4.0,
|
|
||||||
tooltip_width: 600.0,
|
|
||||||
combo_height: 200.0,
|
|
||||||
scroll_bar_width: 8.0,
|
|
||||||
indent_ends_with_horizontal_line: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,10 @@
|
|||||||
use egui::InnerResponse;
|
use crate::misc::Offset;
|
||||||
|
use egui::{Id, InnerResponse};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
/// Creates an area ontop of a widget with an y offset
|
/// Creates an area ontop of a widget with an y offset
|
||||||
pub fn widgets_ontop<R>(
|
pub fn widgets_ontop<R>(
|
||||||
ui: &mut egui::Ui, id: impl Hash, re: &egui::Response, y_offset: f32,
|
ui: &mut egui::Ui, id: Id, re: &egui::Response, y_offset: f32,
|
||||||
add_contents: impl FnOnce(&mut egui::Ui) -> R,
|
add_contents: impl FnOnce(&mut egui::Ui) -> R,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
let area = egui::Area::new(id)
|
let area = egui::Area::new(id)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user