This commit is contained in:
Simon Gardling
2023-03-07 10:05:37 -05:00
parent 9a8f8a6539
commit b37a6223bc
15 changed files with 919 additions and 862 deletions

View File

@@ -1,7 +1,7 @@
use crate::math_app::AppSettings;
use crate::misc::*;
use egui::{
plot::{BarChart, PlotUi, Value},
plot::{BarChart, PlotPoint, PlotUi},
widgets::plot::Bar,
Checkbox, Context,
};
@@ -48,12 +48,12 @@ pub struct FunctionEntry {
pub nth_derviative: bool,
pub back_data: Vec<Value>,
pub back_data: Vec<PlotPoint>,
pub integral_data: Option<(Vec<Bar>, f64)>,
pub derivative_data: Vec<Value>,
pub extrema_data: Vec<Value>,
pub root_data: Vec<Value>,
nth_derivative_data: Option<Vec<Value>>,
pub derivative_data: Vec<PlotPoint>,
pub extrema_data: Vec<PlotPoint>,
pub root_data: Vec<PlotPoint>,
nth_derivative_data: Option<Vec<PlotPoint>>,
pub autocomplete: AutoComplete<'static>,
@@ -240,7 +240,7 @@ impl FunctionEntry {
/// Helps with processing newton's method depending on level of derivative
fn newtons_method_helper(
&self, threshold: f64, derivative_level: usize, range: &std::ops::Range<f64>,
) -> Vec<Value> {
) -> Vec<PlotPoint> {
let newtons_method_output: Vec<f64> = match derivative_level {
0 => newtons_method_helper(
threshold,
@@ -261,7 +261,7 @@ impl FunctionEntry {
newtons_method_output
.into_iter()
.map(|x| Value::new(x, self.function.get(x)))
.map(|x| PlotPoint::new(x, self.function.get(x)))
.collect()
}
@@ -292,10 +292,10 @@ impl FunctionEntry {
}
if self.back_data.is_empty() {
let data: Vec<Value> = resolution_iter
let data: Vec<PlotPoint> = resolution_iter
.clone()
.into_iter()
.map(|x| Value::new(x, self.function.get(x)))
.map(|x| PlotPoint::new(x, self.function.get(x)))
.collect();
debug_assert_eq!(data.len(), settings.plot_width + 1);
@@ -303,19 +303,19 @@ impl FunctionEntry {
}
if self.derivative_data.is_empty() {
let data: Vec<Value> = resolution_iter
let data: Vec<PlotPoint> = resolution_iter
.clone()
.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();
debug_assert_eq!(data.len(), settings.plot_width + 1);
self.derivative_data = data;
}
if self.nth_derviative && self.nth_derivative_data.is_none() {
let data: Vec<Value> = resolution_iter
let data: Vec<PlotPoint> = resolution_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();
debug_assert_eq!(data.len(), settings.plot_width + 1);
self.nth_derivative_data = Some(data);
@@ -380,9 +380,9 @@ impl FunctionEntry {
&& (settings.integral_max_x > value.x)
})
.cloned()
.collect::<Vec<Value>>()
.collect::<Vec<PlotPoint>>()
.to_line()
.stroke(epaint::Stroke::none())
.stroke(epaint::Stroke::NONE)
.color(Color32::from_rgb(4, 4, 255))
.fill(0.0),
);

View File

@@ -1,5 +1,8 @@
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 emath::vec2;
@@ -19,7 +22,7 @@ impl Default for FunctionManager {
fn default() -> Self {
let mut vec: Functions = Vec::with_capacity(COLORS.len());
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,
));
Self { functions: vec }
@@ -37,7 +40,7 @@ impl Serialize for FunctionManager {
&self
.functions
.iter()
.map(|(id, func)| (id.0, func.clone()))
.map(|(id, func)| (get_u64_id(*id), func.clone()))
.collect::<Vec<(u64, FunctionEntry)>>(),
)?;
s.end()
@@ -59,7 +62,7 @@ impl<'de> Deserialize<'de> for FunctionManager {
.0
.iter()
.cloned()
.map(|(id, func)| (Id(id), func))
.map(|(id, func)| (create_id(id), func))
.collect::<Vec<(Id, FunctionEntry)>>(),
})
}
@@ -86,22 +89,23 @@ impl FunctionManager {
let mut remove_i: Option<usize> = None;
let target_size = vec2(available_width, crate::data::FONT_SIZE);
for (i, (te_id, function)) in self.functions.iter_mut().enumerate() {
let te_id = *te_id;
let mut new_string = function.autocomplete.string.clone();
function.update_string(&new_string);
let mut movement: Movement = Movement::default();
let size_multiplier = vec2(1.0, {
let had_focus = ui.ctx().memory().has_focus(*te_id);
(ui.ctx().animate_bool(*te_id, had_focus) * 1.5) + 1.0
let had_focus = ui.ctx().memory(|x| x.has_focus(te_id));
(ui.ctx().animate_bool(te_id, had_focus) * 1.5) + 1.0
});
let re = ui.add_sized(
target_size * size_multiplier,
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)
.id(*te_id) // Set widget's id to `te_id`
.id(te_id) // Set widget's id to `te_id`
.hint_text({
// 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() {
@@ -115,24 +119,31 @@ impl FunctionManager {
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
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 {
function.autocomplete.update_string(&new_string);
if function.autocomplete.hint.is_some() {
// only register up and down arrow movements if hint is type `Hint::Many`
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;
} else if ui.input().key_pressed(Key::ArrowUp) {
} else if arrow_up {
movement = Movement::Up;
}
}
// 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 tab_pressed = ui.input_mut().consume_key(Modifiers::NONE, Key::Tab);
if enter_pressed | tab_pressed | ui.input().key_pressed(Key::ArrowRight) {
let (enter_pressed, tab_pressed) = ui.input_mut(|x| {
(
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;
}
@@ -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
// hashed "autocomplete_popup"
const POPUP_ID: Id = Id(7574801616484505465);
const POPUP_ID: Id = create_id(7574801616484505465);
let mut clicked = false;
@@ -164,17 +175,17 @@ impl FunctionManager {
movement = Movement::Complete;
} else {
ui.memory().open_popup(POPUP_ID);
ui.memory_mut(|x| x.open_popup(POPUP_ID));
}
}
// Push cursor to end if needed
if movement == Movement::Complete {
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());
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 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| {
// There's more than 1 function! Functions can now be deleted
if ui
@@ -244,7 +255,7 @@ impl FunctionManager {
/// Create and push new empty function entry
pub fn push_empty(&mut self) {
self.functions.push((
Id(random_u64().expect("unable to generate random id")),
create_id(random_u64().expect("unable to generate random id")),
FunctionEntry::EMPTY,
));
}

View File

@@ -20,7 +20,6 @@ mod function_entry;
mod function_manager;
mod math_app;
mod misc;
mod style;
mod unicode_helper;
mod widgets;

View File

@@ -20,7 +20,6 @@ mod function_entry;
mod function_manager;
mod math_app;
mod misc;
mod style;
mod unicode_helper;
mod widgets;

View File

@@ -248,7 +248,7 @@ impl MathApp {
cc.egui_ctx.set_fonts(data.fonts);
// Set dark mode by default
cc.egui_ctx.set_visuals(crate::style::style());
// cc.egui_ctx.set_visuals(crate::style::style());
// Set 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
self.opened
.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

View File

@@ -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 itertools::Itertools;
/// Implements traits that are useful when dealing with Vectors of egui's `Value`
pub trait EguiHelper {
/// Converts to `egui::plot::Values`
fn to_values(self) -> Values;
fn to_values(self) -> PlotPoints;
/// Converts to `egui::plot::Line`
fn to_line(self) -> Line;
@@ -17,9 +19,13 @@ pub trait EguiHelper {
fn to_tuple(self) -> Vec<(f64, f64)>;
}
impl EguiHelper for Vec<Value> {
impl EguiHelper for Vec<PlotPoint> {
#[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)]
fn to_line(self) -> Line { Line::new(self.to_values()) }
@@ -29,11 +35,35 @@ impl EguiHelper for Vec<Value> {
#[inline(always)]
fn to_tuple(self) -> Vec<(f64, f64)> {
// self.iter().map(|ele| (ele.x, ele.y)).collect()
unsafe { std::mem::transmute::<Vec<Value>, Vec<(f64, f64)>>(self) }
unsafe { std::mem::transmute::<Vec<PlotPoint>, 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
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)
/// The function returns a Vector of `x` values where roots occur
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,
) -> Vec<f64> {
data.iter()

View File

@@ -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,
}
}

View File

@@ -1,9 +1,10 @@
use egui::InnerResponse;
use crate::misc::Offset;
use egui::{Id, InnerResponse};
use std::hash::Hash;
/// Creates an area ontop of a widget with an y offset
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,
) -> InnerResponse<R> {
let area = egui::Area::new(id)