From 0130f145627c1e4e37b4506707a58947e8b35adc Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Fri, 1 Apr 2022 11:21:48 -0400 Subject: [PATCH] lots of refactoring --- src/function.rs | 153 +++--------------------------------------------- src/lib.rs | 1 + src/main.rs | 1 + src/math_app.rs | 42 ++++++------- src/widgets.rs | 142 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 172 insertions(+), 167 deletions(-) create mode 100644 src/widgets.rs diff --git a/src/function.rs b/src/function.rs index c9d474c..e55a66d 100644 --- a/src/function.rs +++ b/src/function.rs @@ -3,19 +3,13 @@ use crate::math_app::AppSettings; use crate::misc::*; use crate::parsing::{process_func_str, BackingFunction}; -use crate::suggestions::{generate_hint, HintEnum}; +use crate::widgets::AutoComplete; use eframe::{egui, epaint}; use egui::{ plot::{BarChart, PlotUi, Value}, - text::CCursor, - text_edit::CursorRange, widgets::plot::Bar, - Key, TextEdit, Widget, -}; -use epaint::{ - text::cursor::{Cursor, PCursor, RCursor}, - Color32, }; +use epaint::Color32; use std::fmt::{self, Debug}; #[cfg(threading)] @@ -38,37 +32,6 @@ lazy_static::lazy_static! { pub static ref DEFAULT_FUNCTION_ENTRY: FunctionEntry = FunctionEntry::default(); } -#[derive(Clone)] -struct AutoComplete { - pub i: usize, - pub hint: HintEnum<'static>, - pub func_str: Option, - pub changed: bool, -} - -impl Default for AutoComplete { - fn default() -> AutoComplete { - AutoComplete { - i: 0, - hint: HintEnum::None, - func_str: None, - changed: true, - } - } -} - -impl AutoComplete { - fn changed(&mut self, string: String) { - if self.func_str != Some(string.clone()) { - self.changed = true; - self.func_str = Some(string.clone()); - self.hint = generate_hint(string); - } else { - self.changed = false; - } - } -} - /// `FunctionEntry` is a function that can calculate values, integrals, /// derivatives, etc etc #[derive(Clone)] @@ -130,117 +93,15 @@ impl FunctionEntry { pub fn auto_complete( &mut self, ui: &mut egui::Ui, string: &mut String, ) -> (bool, bool, Option) { - // Put here so these key presses don't interact with other elements - let enter_pressed = ui - .input_mut() - .consume_key(egui::Modifiers::NONE, Key::Enter); - let tab_pressed = ui.input_mut().consume_key(egui::Modifiers::NONE, Key::Tab); + let (output_string, in_focus) = self.autocomplete.ui(ui, string.to_string()); - let te_id = ui.make_persistent_id("text_edit_ac".to_string()); - - // update self.autocomplete - self.autocomplete.changed(string.clone()); - - let mut func_edit = egui::TextEdit::singleline(string) - .hint_forward(true) - .lock_focus(true); - - if self.autocomplete.hint.is_none() { - func_edit.id(te_id).ui(ui); - return (false, false, self.get_test_result()); - } - - if let Some(single_hint) = self.autocomplete.hint.get_single() { - let func_edit_2 = func_edit; - func_edit = func_edit_2.hint_text(&single_hint); - } - - let re = func_edit.id(te_id).ui(ui); - - let func_edit_focus = re.has_focus(); - - // If in focus and right arrow key was pressed, apply hint - if func_edit_focus { - let mut push_cursor: bool = false; - let apply_key = ui.input().key_pressed(Key::ArrowRight) | enter_pressed | tab_pressed; - - if apply_key && let Some(single_hint) = self.autocomplete.hint.get_single() { - push_cursor = true; - *string = string.clone() + &single_hint; - } else if self.autocomplete.hint.is_multi() { - let selections = self.autocomplete.hint.ensure_many(); - - let max_i = selections.len() as i16 - 1; - - let mut i = self.autocomplete.i as i16; - - if ui.input().key_pressed(Key::ArrowDown) { - i += 1; - if i > max_i { - i = 0; - } - } else if ui.input().key_pressed(Key::ArrowUp) { - i -= 1; - if 0 > i { - i = max_i - } - } - - self.autocomplete.i = i as usize; - - let popup_id = ui.make_persistent_id("autocomplete_popup"); - - let mut clicked = false; - - egui::popup_below_widget(ui, popup_id, &re, |ui| { - for (i, candidate) in selections.iter().enumerate() { - if ui - .selectable_label(i == self.autocomplete.i, *candidate) - .clicked() - { - clicked = true; - self.autocomplete.i = i; - } - } - }); - - if clicked | apply_key { - *string += selections[self.autocomplete.i]; - push_cursor = true; - - - // don't need this here as it simply won't be display next frame in `math_app.rs` - // ui.memory().close_popup(); - } else { - ui.memory().open_popup(popup_id); - } - } - - // Push cursor to end if needed - if push_cursor { - let mut state = TextEdit::load_state(ui.ctx(), te_id).unwrap(); - state.set_cursor_range(Some(CursorRange::one(Cursor { - ccursor: CCursor { - index: 0, - prefer_next_row: false, - }, - rcursor: RCursor { row: 0, column: 0 }, - pcursor: PCursor { - paragraph: 0, - offset: 10000, - prefer_next_row: false, - }, - }))); - TextEdit::store_state(ui.ctx(), te_id, state); - } - } - - let changed = *string != self.get_func_raw(); + let changed = output_string != *string; if changed { - self.update_string(&*string); + *string = output_string.clone(); + self.update_string(&output_string); } - (func_edit_focus, changed, self.get_test_result()) + (in_focus, changed, self.get_test_result()) } pub fn get_test_result(&self) -> Option { self.test_result.clone() } diff --git a/src/lib.rs b/src/lib.rs index 90753a0..c48208b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod math_app; mod misc; mod parsing; mod suggestions; +mod widgets; cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { diff --git a/src/main.rs b/src/main.rs index ac93905..bbe4f48 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ mod math_app; mod misc; mod parsing; mod suggestions; +mod widgets; // For running the program natively! (Because why not?) #[cfg(not(target_arch = "wasm32"))] diff --git a/src/math_app.rs b/src/math_app.rs index 1bca6c0..6877e09 100644 --- a/src/math_app.rs +++ b/src/math_app.rs @@ -7,6 +7,7 @@ use egui::{ FontFamily, Key, RichText, SidePanel, Slider, TopBottomPanel, Vec2, Visuals, Window, }; use instant::Duration; +use std::collections::HashMap; use std::{collections::BTreeMap, io::Read, ops::BitXorAssign, str}; #[cfg(threading)] @@ -291,20 +292,14 @@ pub struct MathApp { /// took for the last frame (the Duration). Stored in a Tuple. last_info: (Vec>, Duration), - /// Stores whether or not the Help window is open - help_open: bool, - - /// Stores whether or not the Info window is open - info_open: bool, - /// Stores whether or not dark mode is enabled dark_mode: bool, /// Stores whether or not the text boxes are focused text_boxes_focused: bool, - /// Stores whether or not the side panel is shown or not - show_side_panel: bool, + /// Stores opened windows/elements for later reference + opened: HashMap<&'static str, bool>, /// Stores settings (pretty self-explanatory) settings: AppSettings, @@ -318,11 +313,9 @@ impl Default for MathApp { func_errors: vec![None], exists_error: false, last_info: (vec![None], Duration::ZERO), - help_open: true, - info_open: false, dark_mode: true, text_boxes_focused: false, - show_side_panel: true, + opened: HashMap::from([("help", true), ("info", false), ("side_panel", true)]), settings: AppSettings::default(), } } @@ -356,6 +349,10 @@ impl MathApp { Self::default() // initialize `MathApp` } + fn get_opened_mut(&mut self, id: &str) -> &mut bool { self.opened.get_mut(id).unwrap() } + + fn get_opened(&self, id: &str) -> bool { *self.opened.get(id).unwrap() } + /// Creates SidePanel which contains configuration options fn side_panel(&mut self, ctx: &Context) { // Side Panel which contains vital options to the operation of the application @@ -555,7 +552,7 @@ impl epi::App for MathApp { // this is behind this check as if it wasn't, it would trigger if the user // presses the h key in a text box as well if !self.text_boxes_focused { - self.show_side_panel + self.get_opened_mut("side_panel") .bitxor_assign(ctx.input().key_down(Key::H)); } @@ -566,9 +563,10 @@ impl epi::App for MathApp { TopBottomPanel::top("top_bar").show(ctx, |ui| { ui.horizontal(|ui| { // Button in top bar to toggle showing the side panel - self.show_side_panel.bitxor_assign( + let side_curr_open = self.get_opened("help"); + self.get_opened_mut("side_panel").bitxor_assign( ui.add(Button::new("Panel")) - .on_hover_text(match self.show_side_panel { + .on_hover_text(match side_curr_open { true => "Hide Side Panel", false => "Show Side Panel", }) @@ -587,9 +585,10 @@ impl epi::App for MathApp { } // Toggles opening the Help window - self.help_open.bitxor_assign( + let help_curr_open = self.get_opened("help"); + self.get_opened_mut("help").bitxor_assign( ui.add(Button::new("Help")) - .on_hover_text(match self.help_open { + .on_hover_text(match help_curr_open { true => "Close Help Window", false => "Open Help Window", }) @@ -597,9 +596,10 @@ impl epi::App for MathApp { ); // Toggles opening the Info window - self.info_open.bitxor_assign( + let info_curr_open = self.get_opened("info"); + self.get_opened_mut("info").bitxor_assign( ui.add(Button::new("Info")) - .on_hover_text(match self.info_open { + .on_hover_text(match info_curr_open { true => "Close Info Window", false => "Open Info Window", }) @@ -631,7 +631,7 @@ impl epi::App for MathApp { // Help window with information for users Window::new("Help") .default_pos([200.0, 200.0]) - .open(&mut self.help_open) + .open(self.get_opened_mut("help")) .resizable(false) .collapsible(false) .show(ctx, |ui| { @@ -661,7 +661,7 @@ impl epi::App for MathApp { // Window with information about the build and current commit Window::new("Info") .default_pos([200.0, 200.0]) - .open(&mut self.info_open) + .open(self.get_opened_mut("info")) .resizable(false) .collapsible(false) .show(ctx, |ui| { @@ -669,7 +669,7 @@ impl epi::App for MathApp { }); // If side panel is enabled, show it. - if self.show_side_panel { + if self.get_opened("side_panel") { self.side_panel(ctx); } else { self.text_boxes_focused = false; diff --git a/src/widgets.rs b/src/widgets.rs new file mode 100644 index 0000000..bfc7050 --- /dev/null +++ b/src/widgets.rs @@ -0,0 +1,142 @@ +use crate::suggestions::{generate_hint, HintEnum}; +use eframe::{egui, epaint}; +use egui::{text::CCursor, text_edit::CursorRange, Key, Modifiers, TextEdit, Widget}; +use epaint::text::cursor::{Cursor, PCursor, RCursor}; + +#[derive(Clone)] +pub struct AutoComplete { + pub i: usize, + pub hint: HintEnum<'static>, + pub func_str: Option, + pub changed: bool, +} + +impl Default for AutoComplete { + fn default() -> AutoComplete { + AutoComplete { + i: 0, + hint: HintEnum::None, + func_str: None, + changed: true, + } + } +} + +impl AutoComplete { + fn changed(&mut self, string: String) { + if self.func_str != Some(string.clone()) { + self.changed = true; + self.func_str = Some(string.clone()); + self.hint = generate_hint(string); + } else { + self.changed = false; + } + } + + pub fn ui(&mut self, ui: &mut egui::Ui, string: String) -> (String, bool) { + let mut new_string = string.clone(); + // 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); + + let te_id = ui.make_persistent_id("text_edit_ac".to_string()); + + // update self + self.changed(string.clone()); + + let mut func_edit = egui::TextEdit::singleline(&mut new_string) + .hint_forward(true) + .lock_focus(true); + + if self.hint.is_none() { + return (string, func_edit.id(te_id).ui(ui).has_focus()); + } + + if let Some(single_hint) = self.hint.get_single() { + let func_edit_2 = func_edit; + func_edit = func_edit_2.hint_text(&single_hint); + } + + let re = func_edit.id(te_id).ui(ui); + + let func_edit_focus = re.has_focus(); + + // If in focus and right arrow key was pressed, apply hint + if func_edit_focus { + let mut push_cursor: bool = false; + let apply_key = ui.input().key_pressed(Key::ArrowRight) | enter_pressed | tab_pressed; + + if apply_key && let Some(single_hint) = self.hint.get_single() { + push_cursor = true; + new_string = string + &single_hint; + } else if self.hint.is_multi() { + let selections = self.hint.ensure_many(); + + let max_i = selections.len() as i16 - 1; + + let mut i = self.i as i16; + + if ui.input().key_pressed(Key::ArrowDown) { + i += 1; + if i > max_i { + i = 0; + } + } else if ui.input().key_pressed(Key::ArrowUp) { + i -= 1; + if 0 > i { + i = max_i + } + } + + self.i = i as usize; + + let popup_id = ui.make_persistent_id("autocomplete_popup"); + + let mut clicked = false; + + egui::popup_below_widget(ui, popup_id, &re, |ui| { + for (i, candidate) in selections.iter().enumerate() { + if ui + .selectable_label(i == self.i, *candidate) + .clicked() + { + clicked = true; + self.i = i; + } + } + }); + + if clicked | apply_key { + new_string += selections[self.i]; + push_cursor = true; + + + // don't need this here as it simply won't be display next frame in `math_app.rs` + // ui.memory().close_popup(); + } else { + ui.memory().open_popup(popup_id); + } + } + + // Push cursor to end if needed + if push_cursor { + let mut state = TextEdit::load_state(ui.ctx(), te_id).unwrap(); + state.set_cursor_range(Some(CursorRange::one(Cursor { + ccursor: CCursor { + index: 0, + prefer_next_row: false, + }, + rcursor: RCursor { row: 0, column: 0 }, + pcursor: PCursor { + paragraph: 0, + offset: 10000, + prefer_next_row: false, + }, + }))); + TextEdit::store_state(ui.ctx(), te_id, state); + } + return (new_string, true); + } + (string, false) + } +}