From 61d99f713324b56228c6b1d26b2fc354e5280fa9 Mon Sep 17 00:00:00 2001 From: ElnuDev Date: Sun, 21 May 2023 16:39:46 -0700 Subject: [PATCH] Implement text typeout --- renrs-gui/src/lib.rs | 98 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/renrs-gui/src/lib.rs b/renrs-gui/src/lib.rs index 50c7175..f7d8f03 100644 --- a/renrs-gui/src/lib.rs +++ b/renrs-gui/src/lib.rs @@ -1,4 +1,6 @@ -use std::path::PathBuf; +use std::{path::PathBuf, sync::{Arc, Mutex}}; +use std::thread; +use std::time::Duration; use eframe::egui; use egui::*; @@ -23,38 +25,104 @@ pub fn run(file: PathBuf) -> Result<(), eframe::Error> { struct App { state: State, text: String, + chars: Arc>, + typing_kill: Option>>, + typing_done: Arc>, } impl App { fn from_state(state: State) -> Self { - let mut app = Self { + Self { state, text: "".to_owned(), - }; - app.next(); - app + chars: Arc::new(Mutex::new(0)), + typing_kill: None, + typing_done: Arc::new(Mutex::new(true)), + } } - fn next(&mut self) { + fn next(&mut self, ctx: &Context) { if let Some(Command::Say { name, text }) = self.state.next_command() { self.text = match name { Some(name) => format!("{name}: {text}"), None => text, + }; + self.start_typing(ctx); + } + } + + fn kill_typing(&mut self) { + if let Some(kill) = &self.typing_kill { + *kill.lock().unwrap() = true; + } + } + + fn interrupt_typing(&mut self) { + self.kill_typing(); + *self.chars.lock().unwrap() = self.text.len(); + *self.typing_done.lock().unwrap() = true; + } + + fn start_typing(&mut self, ctx: &Context) { + // Kill previous typing thread if exists + self.kill_typing(); + + *self.chars.lock().unwrap() = 0; + *self.typing_done.lock().unwrap() = false; + + // Set up references to be passed into thread + let ctx = ctx.clone(); + let kill = { + let kill = Arc::new(Mutex::new(false)); + self.typing_kill = Some(kill.clone()); + kill + }; + let done = self.typing_done.clone(); + let chars = self.chars.clone(); + let len = self.text.len(); + + thread::spawn(move || { + let mut complete = false; + for i in 0..len { + if *kill.lock().unwrap() { + break; + } + *chars.lock().unwrap() = i; + ctx.request_repaint(); + thread::sleep(Duration::from_millis(50)); + complete = true; + } + if complete { + *done.lock().unwrap() = true; } + }); + } + + fn handle_interaction(&mut self, ctx: &Context) { + if *self.typing_done.lock().unwrap() { + self.next(ctx); + } else { + self.interrupt_typing(); } } } impl eframe::App for App { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - egui::CentralPanel::default().show(ctx, |ui| { - if ctx.input(|i| - i.key_pressed(Key::Space) - || i.pointer.button_clicked(PointerButton::Primary) - ) { - self.next(); - } - ui.label(&self.text); - }); + egui::CentralPanel::default() + .frame(Frame { + fill: Color32::BLACK, + inner_margin: Margin::same(32.0), + ..Default::default() + }) + .show(ctx, |ui| { + if ctx.input(|i| + i.key_pressed(Key::Space) + || i.pointer.button_clicked(PointerButton::Primary) + ) { + self.handle_interaction(ctx); + } + ui.with_layout(egui::Layout::left_to_right(Align::Max), |ui| ui.heading(&self.text[0..*self.chars.lock().unwrap()])); + }); } } \ No newline at end of file