diff --git a/Cargo.lock b/Cargo.lock index 80ef731..14235cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/assets/text.json b/assets/text.json index 31c0238..a4f0f0a 100644 --- a/assets/text.json +++ b/assets/text.json @@ -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)" ] } diff --git a/src/lib.rs b/src/lib.rs index 120e415..de7c4ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ mod function_entry; mod function_manager; mod math_app; mod misc; +mod style; mod widgets; pub use crate::{ diff --git a/src/main.rs b/src/main.rs index 4950971..858bb28 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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?) diff --git a/src/math_app.rs b/src/math_app.rs index 70d252e..bc13fe7 100644 --- a/src/math_app.rs +++ b/src/math_app.rs @@ -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, Option), - /// 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::(); + 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::(); + + 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> = 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> = 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())); diff --git a/src/style.rs b/src/style.rs new file mode 100644 index 0000000..4309776 --- /dev/null +++ b/src/style.rs @@ -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, + } +}