user interface improvements

This commit is contained in:
Simon Gardling 2022-05-18 18:43:52 -04:00
parent 782d567302
commit 8ed749ef72
6 changed files with 190 additions and 109 deletions

12
Cargo.lock generated
View File

@ -659,7 +659,7 @@ checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
[[package]]
name = "eframe"
version = "0.18.0"
source = "git+https://github.com/Titaniumtown/egui.git#ea25cc1a991610099ada8fe02f1c11ba708f77fd"
source = "git+https://github.com/Titaniumtown/egui.git#b5adf3b7ec16255873e7a610573a645719d02e89"
dependencies = [
"bytemuck",
"egui",
@ -679,7 +679,7 @@ dependencies = [
[[package]]
name = "egui"
version = "0.18.1"
source = "git+https://github.com/Titaniumtown/egui.git#ea25cc1a991610099ada8fe02f1c11ba708f77fd"
source = "git+https://github.com/Titaniumtown/egui.git#b5adf3b7ec16255873e7a610573a645719d02e89"
dependencies = [
"ahash",
"epaint",
@ -691,7 +691,7 @@ dependencies = [
[[package]]
name = "egui-winit"
version = "0.18.0"
source = "git+https://github.com/Titaniumtown/egui.git#ea25cc1a991610099ada8fe02f1c11ba708f77fd"
source = "git+https://github.com/Titaniumtown/egui.git#b5adf3b7ec16255873e7a610573a645719d02e89"
dependencies = [
"arboard",
"egui",
@ -705,7 +705,7 @@ dependencies = [
[[package]]
name = "egui_glow"
version = "0.18.1"
source = "git+https://github.com/Titaniumtown/egui.git#ea25cc1a991610099ada8fe02f1c11ba708f77fd"
source = "git+https://github.com/Titaniumtown/egui.git#b5adf3b7ec16255873e7a610573a645719d02e89"
dependencies = [
"bytemuck",
"egui",
@ -725,7 +725,7 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "emath"
version = "0.18.0"
source = "git+https://github.com/Titaniumtown/egui.git#ea25cc1a991610099ada8fe02f1c11ba708f77fd"
source = "git+https://github.com/Titaniumtown/egui.git#b5adf3b7ec16255873e7a610573a645719d02e89"
dependencies = [
"bytemuck",
"serde",
@ -734,7 +734,7 @@ dependencies = [
[[package]]
name = "epaint"
version = "0.18.1"
source = "git+https://github.com/Titaniumtown/egui.git#ea25cc1a991610099ada8fe02f1c11ba708f77fd"
source = "git+https://github.com/Titaniumtown/egui.git#b5adf3b7ec16255873e7a610573a645719d02e89"
dependencies = [
"ab_glyph",
"ahash",

View File

@ -10,8 +10,7 @@
"- 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."
"- The 'Info' button provides information on the build currently running."
],
"help_function": [
"(From Left to Right)",
@ -25,6 +24,7 @@
],
"welcome": [
"Welcome to the (Yet-to-be-named) Graphing Software!",
"",
"This project aims to provide an intuitive experience graphing mathematical functions with features such as Integration, Differentiation, Extrema, Roots, and much more! (see the Help Window for more details)"
]
}

View File

@ -22,6 +22,7 @@ mod function_entry;
mod function_manager;
mod math_app;
mod misc;
mod style;
mod widgets;
pub use crate::{

View File

@ -22,6 +22,7 @@ mod function_entry;
mod function_manager;
mod math_app;
mod misc;
mod style;
mod widgets;
// For running the program natively! (Because why not?)

View File

@ -6,7 +6,7 @@ use crate::misc::{dyn_mut_iter, option_vec_printer};
use eframe::App;
use egui::{
plot::Plot, style::Margin, Button, CentralPanel, ComboBox, Context, Frame, Key, Layout,
SidePanel, Slider, TopBottomPanel, Vec2, Visuals, Window,
SidePanel, Slider, TopBottomPanel, Vec2, Window,
};
use emath::{Align, Align2};
use epaint::Rounding;
@ -93,9 +93,6 @@ pub struct MathApp {
/// 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: (Option<String>, Option<String>),
/// Whether or not dark mode is enabled
dark_mode: bool,
/// Stores opened windows/elements for later reference
opened: Opened,
@ -242,7 +239,12 @@ impl MathApp {
cc.egui_ctx.set_fonts(data.fonts);
// Set dark mode by default
cc.egui_ctx.set_visuals(Visuals::dark());
cc.egui_ctx.set_visuals(crate::style::STYLE);
// Set spacing
let mut style: egui::Style = (*cc.egui_ctx.style()).clone();
style.spacing = crate::style::SPACING;
cc.egui_ctx.set_style(style);
tracing::info!("Initialized! Took: {:?}", start.elapsed());
@ -254,7 +256,6 @@ impl MathApp {
functions: FunctionManager::default(),
last_info: (None, None),
dark_mode: true, // dark mode is default and is previously set
text: data.text,
opened: Opened::default(),
settings: Default::default(),
@ -440,25 +441,6 @@ impl App for MathApp {
.clicked(),
);
// Toggles dark/light mode
if ui
.add(Button::new(match self.dark_mode {
true => "🌞",
false => "🌙",
}))
.on_hover_text(match self.dark_mode {
true => "Turn the Lights on!",
false => "Turn the Lights off.",
})
.clicked()
{
ctx.set_visuals(match self.dark_mode {
true => Visuals::light(),
false => Visuals::dark(),
});
self.dark_mode.bitxor_assign(true);
}
// Display Area and time of last frame
if let Some(ref area) = self.last_info.0 {
ui.label(area);
@ -495,14 +477,23 @@ impl App for MathApp {
});
// Welcome window
Window::new("Welcome!")
.open(&mut self.opened.welcome)
.anchor(Align2::CENTER_CENTER, Vec2::ZERO)
.resizable(false)
.collapsible(false)
.show(ctx, |ui| {
ui.label(self.text.welcome.clone());
});
if self.opened.welcome {
let welcome_response = Window::new("Welcome!")
.open(&mut self.opened.welcome)
.anchor(Align2::CENTER_CENTER, Vec2::ZERO)
.resizable(false)
.collapsible(false)
.show(ctx, |ui| {
ui.label(self.text.welcome.clone());
});
if let Some(response) = welcome_response {
// if user clicks off welcome window, close it
if response.response.clicked_elsewhere() {
self.opened.welcome = false;
}
}
}
// Window with information about the build and current commit
Window::new("Info")
@ -523,78 +514,75 @@ impl App for MathApp {
self.side_panel(ctx);
}
// Central panel which contains the central plot (or an error created when
// parsing)
CentralPanel::default()
.frame(Frame {
inner_margin: Margin::symmetric(0.0, 0.0),
rounding: Rounding::none(),
fill: ctx.style().visuals.window_fill(),
..Default::default()
})
.show(ctx, |ui| {
// Display an error if it exists
let errors_formatted: String = self
.functions
.get_entries()
.iter()
.map(|(_, func)| func.get_test_result())
.enumerate()
.filter(|(_, error)| error.is_some())
.map(|(i, error)| {
// use unwrap_unchecked as None Errors are already filtered out
unsafe {
format!("(Function #{}) {}\n", i, error.as_ref().unwrap_unchecked())
}
})
.collect::<String>();
const EMPTY_FRAME: Frame = Frame {
inner_margin: Margin::symmetric(0.0, 0.0),
rounding: Rounding::none(),
fill: crate::style::STYLE.window_fill(),
..Frame::none()
};
if !errors_formatted.is_empty() {
ui.centered_and_justified(|ui| {
ui.heading(errors_formatted);
// Central panel which contains the central plot (or an error created when parsing)
CentralPanel::default().frame(EMPTY_FRAME).show(ctx, |ui| {
// Display an error if it exists
let errors_formatted: String = self
.functions
.get_entries()
.iter()
.map(|(_, func)| func.get_test_result())
.enumerate()
.filter(|(_, error)| error.is_some())
.map(|(i, error)| {
// use unwrap_unchecked as None Errors are already filtered out
unsafe { format!("(Function #{}) {}\n", i, error.as_ref().unwrap_unchecked()) }
})
.collect::<String>();
if !errors_formatted.is_empty() {
ui.centered_and_justified(|ui| {
ui.heading(errors_formatted);
});
return;
}
let available_width: usize = (ui.available_width() as usize) + 1; // Used in later logic
let width_changed = available_width != self.settings.plot_width;
self.settings.plot_width = available_width;
// Create and setup plot
Plot::new("plot")
.set_margin_fraction(Vec2::ZERO)
.data_aspect(1.0)
.include_y(0)
.show(ui, |plot_ui| {
let bounds = plot_ui.plot_bounds();
let min_x: f64 = bounds.min()[0];
let max_x: f64 = bounds.max()[0];
let min_max_changed =
(min_x != self.settings.min_x) | (max_x != self.settings.max_x);
self.settings.min_x = min_x;
self.settings.max_x = max_x;
dyn_mut_iter(self.functions.get_entries_mut()).for_each(|(_, function)| {
function.calculate(width_changed, min_max_changed, &self.settings)
});
return;
}
let available_width: usize = (ui.available_width() as usize) + 1; // Used in later logic
let width_changed = available_width != self.settings.plot_width;
self.settings.plot_width = available_width;
let area: Vec<Option<f64>> = self
.functions
.get_entries()
.iter()
.enumerate()
.map(|(i, (_, function))| {
function.display(plot_ui, &self.settings, COLORS[i])
})
.collect();
// Create and setup plot
Plot::new("plot")
.set_margin_fraction(Vec2::ZERO)
.data_aspect(1.0)
.include_y(0)
.show(ui, |plot_ui| {
let bounds = plot_ui.plot_bounds();
let min_x: f64 = bounds.min()[0];
let max_x: f64 = bounds.max()[0];
let min_max_changed =
(min_x != self.settings.min_x) | (max_x != self.settings.max_x);
self.settings.min_x = min_x;
self.settings.max_x = max_x;
dyn_mut_iter(self.functions.get_entries_mut()).for_each(|(_, function)| {
function.calculate(width_changed, min_max_changed, &self.settings)
});
let area: Vec<Option<f64>> = self
.functions
.get_entries()
.iter()
.enumerate()
.map(|(i, (_, function))| {
function.display(plot_ui, &self.settings, COLORS[i])
})
.collect();
self.last_info.0 = if area.iter().any(|e| e.is_some()) {
Some(format!("Area: {}", option_vec_printer(area.as_slice())))
} else {
None
};
});
});
self.last_info.0 = if area.iter().any(|e| e.is_some()) {
Some(format!("Area: {}", option_vec_printer(area.as_slice())))
} else {
None
};
});
});
// Calculate and store the last time it took to draw the frame
self.last_info.1 = start.map(|a| format!("Took: {:?}", a.elapsed()));

91
src/style.rs Normal file
View File

@ -0,0 +1,91 @@
use egui::{
style::{Margin, Selection, Spacing, WidgetVisuals, Widgets},
Visuals,
};
use emath::vec2;
use epaint::{Color32, Rounding, Shadow, Stroke};
const 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 const STYLE: Visuals = dark();
pub const fn dark() -> 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 const SPACING: Spacing = spacing();
pub const 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,
}
}