diff --git a/src/app.rs b/src/app.rs index 7096df0..7e10329 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,11 +1,16 @@ -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; -use anyhow::Result; +use anyhow::{Context as _, Result}; use eframe::{CreationContext, Frame}; -use egui::{CentralPanel, Context, Ui}; -use egui_async::EguiAsyncPlugin; +use egui::{ + Button, CentralPanel, Color32, Context, DragValue, Key, RichText, TextEdit, Ui, Widget, +}; +use egui_async::{Bind, EguiAsyncPlugin, StateWithData}; +use tracing::info; -pub struct App {} +pub struct App { + number_parser: LazyNumberParser, +} impl App { pub fn new(cc: &CreationContext) -> Result { @@ -15,7 +20,7 @@ impl App { Arc::new(move || ctx.request_repaint()) }); - Ok(App {}) + Ok(App { number_parser: LazyNumberParser::default() }) } fn main_logic(&mut self, ctx: &Context, _frame: &mut Frame) { @@ -23,7 +28,7 @@ impl App { } fn main_ui(&mut self, ui: &mut Ui, _frame: &mut Frame) { - _ = ui; + self.number_parser.ui(ui); } } @@ -43,3 +48,75 @@ impl eframe::App for App { }); } } + +struct LazyNumberParser { + text_field: String, + response: Bind, + err_context_lines: usize, +} + +impl Default for LazyNumberParser { + fn default() -> Self { + LazyNumberParser { + text_field: String::new(), + response: Bind::new(true), + err_context_lines: 1, + } + } +} + +impl LazyNumberParser { + pub fn ui(&mut self, ui: &mut Ui) { + ui.vertical(|ui| { + let text_resp = TextEdit::singleline(&mut self.text_field).ui(ui); + let btn_resp = ui.add_enabled(!self.response.is_pending(), Button::new("Parse!")); + ui.horizontal(|ui| { + ui.label("Show error context: "); + DragValue::new(&mut self.err_context_lines).speed(0.02).ui(ui); + }); + + match self.response.state() { + StateWithData::Idle => {} + StateWithData::Pending => { + ui.spinner(); + } + StateWithData::Finished(val) => { + ui.label( + RichText::new(format!("Parsing successful! The number is: {val}")) + .color(Color32::GREEN), + ); + } + StateWithData::Failed(err) => { + ui.label(RichText::new("An error occurred while parsing!").color(Color32::RED)); + + let mut chain = err.chain(); + ui.indent("errors", |ui| { + for e in chain.by_ref().take(self.err_context_lines) { + ui.label(RichText::new(e.to_string()).color(Color32::ORANGE)); + } + if let count @ 1.. = chain.len() { + ui.label( + RichText::new(format!("...and {count} others")) + .color(Color32::ORANGE), + ); + } + }); + } + } + + if btn_resp.clicked() + || (text_resp.lost_focus() && ui.input_mut(|i| i.key_pressed(Key::Enter))) + { + self.response.request(Self::parse_input(self.text_field.clone())); + } + }); + } + + #[tracing::instrument(ret)] + async fn parse_input(text: String) -> Result { + info!("Parsing number!"); + // Parsing is tough work! Let's rest up a little first + tokio::time::sleep(Duration::from_millis(800)).await; + text.parse().context("Error parsing input!") + } +}