From 15d91847889b0e38ff6a1b2cb9bd6bd58713aecb Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Wed, 13 Apr 2022 11:47:01 -0400 Subject: [PATCH] refactoring (and failing at some ui redesign) --- src/function.rs | 147 +++++++++++++++++++++++++++++++++++++++++++++--- src/math_app.rs | 43 +------------- src/widgets.rs | 96 +------------------------------ 3 files changed, 142 insertions(+), 144 deletions(-) diff --git a/src/function.rs b/src/function.rs index ebed83e..b94bfdd 100644 --- a/src/function.rs +++ b/src/function.rs @@ -3,15 +3,22 @@ use crate::math_app::AppSettings; use crate::misc::*; use crate::parsing::{process_func_str, BackingFunction}; -use crate::widgets::AutoComplete; +use crate::suggestions::Hint; +use crate::widgets::{AutoComplete, Movement}; use eframe::{egui, epaint}; use egui::{ plot::{BarChart, PlotUi, Value}, + text::CCursor, + text_edit::CursorRange, widgets::plot::Bar, - Checkbox, Context, + Button, Checkbox, Context, Key, Modifiers, TextEdit, Widget, +}; +use epaint::{ + text::cursor::{Cursor, PCursor, RCursor}, + Color32, }; -use epaint::Color32; use std::fmt::{self, Debug}; +use std::ops::BitXorAssign; #[cfg(threading)] use rayon::iter::ParallelIterator; @@ -96,13 +103,135 @@ impl Default for FunctionEntry { } impl FunctionEntry { - /// Create autocomplete ui and handle user input - pub fn auto_complete(&mut self, ui: &mut egui::Ui, i: usize) { - self.autocomplete.update_string(&self.raw_func_str); - self.autocomplete.ui(ui, i); + pub fn function_entry( + &mut self, ui: &mut egui::Ui, remove_i: &mut Option, can_remove: bool, i: usize, + ) { + ui.horizontal(|ui| { + // There's more than 1 function! Functions can now be deleted + if ui + .add_enabled(can_remove, Button::new("X").frame(true)) + .on_hover_text("Delete Function") + .clicked() + { + *remove_i = Some(i); + } - let output_string = self.autocomplete.string.clone(); - self.update_string(&output_string); + // Toggle integral being enabled or not + self.integral.bitxor_assign( + ui.add(Button::new("∫")) + .on_hover_text(match self.integral { + true => "Don't integrate", + false => "Integrate", + }) + .clicked(), + ); + + // Toggle showing the derivative (even though it's already calculated this option just toggles if it's displayed or not) + self.derivative.bitxor_assign( + ui.add(Button::new("d/dx")) + .on_hover_text(match self.derivative { + true => "Don't Differentiate", + false => "Differentiate", + }) + .clicked(), + ); + + self.settings_opened.bitxor_assign( + ui.add(Button::new("⚙")) + .on_hover_text(match self.settings_opened { + true => "Close Settings", + false => "Open Settings", + }) + .clicked(), + ); + + // Contains the function string in a text box that the user can edit + // self.autocomplete.update_string(&self.raw_func_str); + let mut movement: Movement = Movement::default(); + + let mut new_string = self.autocomplete.string.clone(); + + let te_id = ui.make_persistent_id(format!("text_edit_ac_{}", i)); + let re = egui::TextEdit::singleline(&mut new_string) + .hint_forward(true) // Make the hint appear after the last text in the textbox + .lock_focus(true) + .id(te_id) + .hint_text({ + if let Hint::Single(single_hint) = self.autocomplete.hint { + *single_hint + } else { + "" + } + }) + .ui(ui); + + self.autocomplete.update_string(&new_string); + + if !self.autocomplete.hint.is_none() { + if ui.input().key_pressed(Key::ArrowDown) { + movement = Movement::Down; + } else if ui.input().key_pressed(Key::ArrowUp) { + 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) { + movement = Movement::Complete; + } + + self.autocomplete.register_movement(&movement); + + if movement != Movement::Complete && let Hint::Many(hints) = self.autocomplete.hint { + // Doesn't need to have a number in id as there should only be 1 autocomplete popup in the entire gui + let popup_id = ui.make_persistent_id("autocomplete_popup"); + + let mut clicked = false; + + egui::popup_below_widget(ui, popup_id, &re, |ui| { + hints.iter().enumerate().for_each(|(i, candidate)| { + if ui.selectable_label(i == self.autocomplete.i, *candidate).clicked() { + clicked = true; + self.autocomplete.i = i; + } + }); + }); + + if clicked { + self.autocomplete.apply_hint(hints[self.autocomplete.i]); + + // don't need this here as it simply won't be display next frame + // ui.memory().close_popup(); + + movement = Movement::Complete; + } else { + ui.memory().open_popup(popup_id); + } + } + + // Push cursor to end if needed + if movement == Movement::Complete { + 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 output_string = self.autocomplete.string.clone(); + self.update_string(&output_string); + }); } pub fn settings_window(&mut self, ctx: &Context) { diff --git a/src/math_app.rs b/src/math_app.rs index b57fc6e..9f498fb 100644 --- a/src/math_app.rs +++ b/src/math_app.rs @@ -461,49 +461,8 @@ impl MathApp { ui.label("Functions:"); for (i, function) in self.functions.iter_mut().enumerate() { // Entry for a function - ui.horizontal(|ui| { - // There's more than 1 function! Functions can now be deleted - if ui - .add_enabled(functions_len > 1, Button::new("X")) - .on_hover_text("Delete Function") - .clicked() - { - remove_i = Some(i); - } + function.function_entry(ui, &mut remove_i, functions_len > 1, i); - // Toggle integral being enabled or not - function.integral.bitxor_assign( - ui.add(Button::new("∫")) - .on_hover_text(match function.integral { - true => "Don't integrate", - false => "Integrate", - }) - .clicked(), - ); - - // Toggle showing the derivative (even though it's already calculated this - // option just toggles if it's displayed or not) - function.derivative.bitxor_assign( - ui.add(Button::new("d/dx")) - .on_hover_text(match function.derivative { - true => "Don't Differentiate", - false => "Differentiate", - }) - .clicked(), - ); - - function.settings_opened.bitxor_assign( - ui.add(Button::new("⚙")) - .on_hover_text(match function.settings_opened { - true => "Close Settings", - false => "Open Settings", - }) - .clicked(), - ); - - // Contains the function string in a text box that the user can edit - function.auto_complete(ui, i) - }); function.settings_window(ctx); } diff --git a/src/widgets.rs b/src/widgets.rs index 9bd6ad5..a10e12e 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -1,10 +1,7 @@ use crate::suggestions::{generate_hint, Hint}; -use eframe::{egui, epaint}; -use egui::{text::CCursor, text_edit::CursorRange, Key, Modifiers, TextEdit, Widget}; -use epaint::text::cursor::{Cursor, PCursor, RCursor}; #[derive(PartialEq, Debug)] -enum Movement { +pub enum Movement { Complete, Down, Up, @@ -41,7 +38,7 @@ impl<'a> AutoComplete<'a> { } } - fn register_movement(&mut self, movement: &Movement) { + pub fn register_movement(&mut self, movement: &Movement) { if movement == &Movement::None { return; } @@ -75,97 +72,10 @@ impl<'a> AutoComplete<'a> { } } - fn apply_hint(&mut self, hint: &str) { + pub fn apply_hint(&mut self, hint: &str) { let new_string = self.string.clone() + hint; self.update_string(&new_string); } - - pub fn ui(&mut self, ui: &mut egui::Ui, func_i: usize) { - let mut movement: Movement = Movement::default(); - - let mut new_string = self.string.clone(); - - let te_id = ui.make_persistent_id(format!("text_edit_ac_{}", func_i)); - - let re = egui::TextEdit::singleline(&mut new_string) - .hint_forward(true) // Make the hint appear after the last text in the textbox - .lock_focus(true) - .id(te_id) - .hint_text({ - if let Hint::Single(single_hint) = self.hint { - *single_hint - } else { - "" - } - }) - .ui(ui); - - self.update_string(&new_string); - - if self.hint.is_none() { - return; - } - - if ui.input().key_pressed(Key::ArrowDown) { - movement = Movement::Down; - } else if ui.input().key_pressed(Key::ArrowUp) { - 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) { - movement = Movement::Complete; - } - - self.register_movement(&movement); - - if movement != Movement::Complete && let Hint::Many(hints) = self.hint { - // Doesn't need to have a number in id as there should only be 1 autocomplete popup in the entire gui - let popup_id = ui.make_persistent_id("autocomplete_popup"); - - let mut clicked = false; - - egui::popup_below_widget(ui, popup_id, &re, |ui| { - hints.iter().enumerate().for_each(|(i, candidate)| { - if ui.selectable_label(i == self.i, *candidate).clicked() { - clicked = true; - self.i = i; - } - }); - }); - - if clicked { - self.apply_hint(hints[self.i]); - - // don't need this here as it simply won't be display next frame - // ui.memory().close_popup(); - - movement = Movement::Complete; - } else { - ui.memory().open_popup(popup_id); - } - } - - // Push cursor to end if needed - if movement == Movement::Complete { - 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); - } - } } #[cfg(test)]