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

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