From 9a3ff64c313a3e0e28cdb7b63e668fa31878fcaf Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sun, 5 Dec 2021 17:31:43 -0500 Subject: basic search implementation --- src/bin/ttyplay/display.rs | 161 +++++++++++++++++++++++++++++---------------- src/bin/ttyplay/event.rs | 47 ++++++++++++- src/bin/ttyplay/frames.rs | 9 +++ src/bin/ttyplay/input.rs | 133 ++++++++++++++++++++++++++----------- src/bin/ttyplay/timer.rs | 8 +++ 5 files changed, 261 insertions(+), 97 deletions(-) diff --git a/src/bin/ttyplay/display.rs b/src/bin/ttyplay/display.rs index 3ae84e9..c1871cd 100644 --- a/src/bin/ttyplay/display.rs +++ b/src/bin/ttyplay/display.rs @@ -1,26 +1,34 @@ use textmode::Textmode as _; pub struct Display { + screen: vt100::Screen, current_frame: usize, total_frames: usize, done_loading: bool, paused: bool, show_ui: bool, show_help: bool, + active_search: Option, } impl Display { pub fn new() -> Self { Self { + screen: vt100::Parser::default().screen().clone(), current_frame: 0, total_frames: 0, done_loading: false, paused: false, show_ui: true, show_help: false, + active_search: None, } } + pub fn screen(&mut self, screen: vt100::Screen) { + self.screen = screen; + } + pub fn current_frame(&mut self, idx: usize) { self.current_frame = idx; } @@ -45,75 +53,116 @@ impl Display { self.show_help = !self.show_help; } + pub fn active_search(&mut self, s: String) { + self.active_search = Some(s); + } + + pub fn clear_search(&mut self) { + self.active_search = None; + } + pub async fn render( &self, - screen: &vt100::Screen, output: &mut textmode::Output, ) -> anyhow::Result<()> { - output.clear(); - output.move_to(0, 0); - output.write(&screen.contents_formatted()); - if self.paused && self.show_ui { - let pos = output.screen().cursor_position(); + let pos = output.screen().cursor_position(); - output.move_to(0, 0); - output.reset_attributes(); - output.set_fgcolor(textmode::color::BLACK); - if self.done_loading { - output.set_bgcolor(textmode::color::CYAN); - } else { - output.set_bgcolor(textmode::color::RED); - } - output.write_str(&format!( - " {}/{} ", - self.current_frame + 1, - self.total_frames - )); + self.render_screen(output); - let size = output.screen().size(); - output.move_to(0, size.1 - 1); - output.reset_attributes(); - output.set_fgcolor(textmode::color::BLACK); - output.set_bgcolor(textmode::color::RED); - output.write_str("\u{23f8}"); + if self.paused && self.show_ui { + self.render_frame_count(output); + self.render_pause_symbol(output); if self.show_help { - output.reset_attributes(); - output.set_fgcolor(textmode::color::BLACK); - output.set_bgcolor(textmode::color::CYAN); - - output.move_to(size.0 - 12, size.1 - 23); - output.write_str(" keys "); - output.move_to(size.0 - 11, size.1 - 23); - output.write_str(" q: quit "); - output.move_to(size.0 - 10, size.1 - 23); - output.write_str(" space: pause/unpause "); - output.move_to(size.0 - 9, size.1 - 23); - output.write_str(" tab: hide/show ui "); - output.move_to(size.0 - 8, size.1 - 23); - output.write_str(" h/p: previous frame "); - output.move_to(size.0 - 7, size.1 - 23); - output.write_str(" l/n: next frame "); - output.move_to(size.0 - 6, size.1 - 23); - output.write_str(" g/0: first frame "); - output.move_to(size.0 - 5, size.1 - 23); - output.write_str(" G/$: last frame "); - output.move_to(size.0 - 4, size.1 - 23); - output.write_str(" +: increase speed "); - output.move_to(size.0 - 3, size.1 - 23); - output.write_str(" -: decrease speed "); - output.move_to(size.0 - 2, size.1 - 23); - output.write_str(" =: normal speed "); - output.move_to(size.0 - 1, size.1 - 23); - output.write_str(" ?: hide/show help "); + self.render_help(output); } - - output.reset_attributes(); - output.move_to(pos.0, pos.1); } + self.render_search(output); + + output.reset_attributes(); + output.move_to(pos.0, pos.1); output.refresh().await?; Ok(()) } + + fn render_screen(&self, output: &mut textmode::Output) { + output.clear(); + output.move_to(0, 0); + output.write(&self.screen.contents_formatted()); + } + + fn render_frame_count(&self, output: &mut textmode::Output) { + output.move_to(0, 0); + output.reset_attributes(); + output.set_fgcolor(textmode::color::BLACK); + if self.done_loading { + output.set_bgcolor(textmode::color::CYAN); + } else { + output.set_bgcolor(textmode::color::RED); + } + output.write_str(&format!( + " {}/{} ", + self.current_frame + 1, + self.total_frames + )); + } + + #[allow(clippy::unused_self)] + fn render_pause_symbol(&self, output: &mut textmode::Output) { + let size = output.screen().size(); + output.move_to(0, size.1 - 1); + output.reset_attributes(); + output.set_fgcolor(textmode::color::BLACK); + output.set_bgcolor(textmode::color::RED); + output.write_str("\u{23f8}"); + } + + #[allow(clippy::unused_self)] + fn render_help(&self, output: &mut textmode::Output) { + let size = output.screen().size(); + output.reset_attributes(); + output.set_fgcolor(textmode::color::BLACK); + output.set_bgcolor(textmode::color::CYAN); + + output.move_to(size.0 - 13, size.1 - 23); + output.write_str(" keys "); + output.move_to(size.0 - 12, size.1 - 23); + output.write_str(" q: quit "); + output.move_to(size.0 - 11, size.1 - 23); + output.write_str(" space: pause/unpause "); + output.move_to(size.0 - 10, size.1 - 23); + output.write_str(" tab: hide/show ui "); + output.move_to(size.0 - 9, size.1 - 23); + output.write_str(" h/p: previous frame "); + output.move_to(size.0 - 8, size.1 - 23); + output.write_str(" l/n: next frame "); + output.move_to(size.0 - 7, size.1 - 23); + output.write_str(" g/0: first frame "); + output.move_to(size.0 - 6, size.1 - 23); + output.write_str(" G/$: last frame "); + output.move_to(size.0 - 5, size.1 - 23); + output.write_str(" +: increase speed "); + output.move_to(size.0 - 4, size.1 - 23); + output.write_str(" -: decrease speed "); + output.move_to(size.0 - 3, size.1 - 23); + output.write_str(" =: normal speed "); + output.move_to(size.0 - 2, size.1 - 23); + output.write_str(" ?: hide/show help "); + } + + fn render_search(&self, output: &mut textmode::Output) { + if let Some(search) = &self.active_search { + let size = output.screen().size(); + output.reset_attributes(); + output.set_fgcolor(textmode::color::BLACK); + output.set_bgcolor(textmode::color::CYAN); + + output.move_to(size.0 - 1, 0); + output.write_str("/"); + output.write_str(search); + output.write_str(&" ".repeat(size.1 as usize - search.len() - 1)); + } + } } diff --git a/src/bin/ttyplay/event.rs b/src/bin/ttyplay/event.rs index 68900cd..de81220 100644 --- a/src/bin/ttyplay/event.rs +++ b/src/bin/ttyplay/event.rs @@ -5,6 +5,9 @@ pub enum Event { TimerAction(TimerAction), ToggleUi, ToggleHelp, + ActiveSearch(String), + CancelSearch, + RunSearch(String), Quit, } @@ -17,6 +20,7 @@ pub enum TimerAction { SpeedUp, SlowDown, DefaultSpeed, + Search(String), Quit, } @@ -72,6 +76,9 @@ struct Pending { timer_actions: std::collections::VecDeque, toggle_ui: bool, toggle_help: bool, + active_search: Option, + cancel_search: bool, + run_search: Option, quit: bool, } @@ -104,6 +111,21 @@ impl Pending { Event::ToggleHelp => { self.toggle_help = !self.toggle_help; } + Event::ActiveSearch(s) => { + self.active_search = Some(s); + self.cancel_search = false; + self.run_search = None; + } + Event::CancelSearch => { + self.active_search = None; + self.cancel_search = true; + self.run_search = None; + } + Event::RunSearch(s) => { + self.active_search = None; + self.cancel_search = false; + self.run_search = Some(s); + } Event::Quit => { self.quit = true; } @@ -118,6 +140,9 @@ impl Pending { || !self.timer_actions.is_empty() || self.toggle_ui || self.toggle_help + || self.active_search.is_some() + || self.cancel_search + || self.run_search.is_some() || self.quit } @@ -127,6 +152,13 @@ impl Pending { Some(Event::Quit) } else if let Some(action) = self.timer_actions.pop_front() { Some(Event::TimerAction(action)) + } else if let Some(active_search) = self.active_search.take() { + Some(Event::ActiveSearch(active_search)) + } else if self.cancel_search { + self.cancel_search = false; + Some(Event::CancelSearch) + } else if let Some(run_search) = self.run_search.take() { + Some(Event::RunSearch(run_search)) } else if self.toggle_ui { self.toggle_ui = false; Some(Event::ToggleUi) @@ -154,7 +186,6 @@ pub async fn handle_events( mut output: textmode::Output, ) -> anyhow::Result<()> { let mut display = crate::display::Display::new(); - let mut current_screen = vt100::Parser::default().screen().clone(); let events = Reader::new(event_r); while let Some(event) = events.read().await { match event { @@ -163,7 +194,7 @@ pub async fn handle_events( continue; } Event::FrameTransition((idx, screen)) => { - current_screen = screen; + display.screen(screen); display.current_frame(idx); } Event::FrameLoaded(n) => { @@ -182,11 +213,21 @@ pub async fn handle_events( Event::ToggleHelp => { display.toggle_help(); } + Event::ActiveSearch(s) => { + display.active_search(s); + } + Event::CancelSearch => { + display.clear_search(); + } + Event::RunSearch(s) => { + display.clear_search(); + timer_w.send(TimerAction::Search(s)).await?; + } Event::Quit => { break; } } - display.render(¤t_screen, &mut output).await?; + display.render(&mut output).await?; } Ok(()) diff --git a/src/bin/ttyplay/frames.rs b/src/bin/ttyplay/frames.rs index cea05fa..0808c58 100644 --- a/src/bin/ttyplay/frames.rs +++ b/src/bin/ttyplay/frames.rs @@ -44,6 +44,15 @@ impl FrameData { self.frames.len() } + pub fn search(&self, start: usize, query: &str) -> Option { + for (idx, frame) in self.frames.iter().enumerate().skip(start) { + if frame.screen.contents().contains(query) { + return Some(idx); + } + } + None + } + pub async fn add_frame(&mut self, frame: Frame) { self.frames.push(frame); self.new_frame_w diff --git a/src/bin/ttyplay/input.rs b/src/bin/ttyplay/input.rs index d061e24..f46bd0a 100644 --- a/src/bin/ttyplay/input.rs +++ b/src/bin/ttyplay/input.rs @@ -3,46 +3,103 @@ pub fn spawn_task( mut input: textmode::Input, ) { async_std::task::spawn(async move { + let mut search: Option = None; while let Some(key) = input.read_key().await.unwrap() { - let event = match key { - textmode::Key::Char('g' | '0' | ')') => { - crate::event::Event::TimerAction( - crate::event::TimerAction::FirstFrame, - ) + if let Some(ref mut search_contents) = search { + match key { + textmode::Key::Char(c) => { + search_contents.push(c); + event_w + .send(crate::event::Event::ActiveSearch( + search_contents.clone(), + )) + .await + .unwrap(); + } + textmode::Key::Backspace => { + search_contents.pop(); + event_w + .send(crate::event::Event::ActiveSearch( + search_contents.clone(), + )) + .await + .unwrap(); + } + textmode::Key::Ctrl(b'm') => { + event_w + .send(crate::event::Event::RunSearch( + search_contents.clone(), + )) + .await + .unwrap(); + search = None; + } + textmode::Key::Escape => { + event_w + .send(crate::event::Event::CancelSearch) + .await + .unwrap(); + search = None; + } + _ => {} } - textmode::Key::Char('G' | '$') => { - crate::event::Event::TimerAction( - crate::event::TimerAction::LastFrame, - ) - } - textmode::Key::Char('l' | 'n') => { - crate::event::Event::TimerAction( - crate::event::TimerAction::NextFrame, - ) - } - textmode::Key::Char('h' | 'p') => { - crate::event::Event::TimerAction( - crate::event::TimerAction::PreviousFrame, - ) - } - textmode::Key::Char('q') => crate::event::Event::Quit, - textmode::Key::Char(' ') => crate::event::Event::TimerAction( - crate::event::TimerAction::Pause, - ), - textmode::Key::Ctrl(b'i') => crate::event::Event::ToggleUi, - textmode::Key::Char('?') => crate::event::Event::ToggleHelp, - textmode::Key::Char('+') => crate::event::Event::TimerAction( - crate::event::TimerAction::SpeedUp, - ), - textmode::Key::Char('-') => crate::event::Event::TimerAction( - crate::event::TimerAction::SlowDown, - ), - textmode::Key::Char('=') => crate::event::Event::TimerAction( - crate::event::TimerAction::DefaultSpeed, - ), - _ => continue, - }; - event_w.send(event).await.unwrap(); + } else { + let event = match key { + textmode::Key::Char('g' | '0' | ')') => { + crate::event::Event::TimerAction( + crate::event::TimerAction::FirstFrame, + ) + } + textmode::Key::Char('G' | '$') => { + crate::event::Event::TimerAction( + crate::event::TimerAction::LastFrame, + ) + } + textmode::Key::Char('l' | 'n') => { + crate::event::Event::TimerAction( + crate::event::TimerAction::NextFrame, + ) + } + textmode::Key::Char('h' | 'p') => { + crate::event::Event::TimerAction( + crate::event::TimerAction::PreviousFrame, + ) + } + textmode::Key::Char('q') => crate::event::Event::Quit, + textmode::Key::Char(' ') => { + crate::event::Event::TimerAction( + crate::event::TimerAction::Pause, + ) + } + textmode::Key::Ctrl(b'i') => { + crate::event::Event::ToggleUi + } + textmode::Key::Char('?') => { + crate::event::Event::ToggleHelp + } + textmode::Key::Char('+') => { + crate::event::Event::TimerAction( + crate::event::TimerAction::SpeedUp, + ) + } + textmode::Key::Char('-') => { + crate::event::Event::TimerAction( + crate::event::TimerAction::SlowDown, + ) + } + textmode::Key::Char('=') => { + crate::event::Event::TimerAction( + crate::event::TimerAction::DefaultSpeed, + ) + } + textmode::Key::Char('/') => { + search = Some("".to_string()); + crate::event::Event::ActiveSearch("".to_string()) + } + _ => continue, + }; + event_w.send(event).await.unwrap(); + } } }); } diff --git a/src/bin/ttyplay/timer.rs b/src/bin/ttyplay/timer.rs index e8e4ce9..eb60185 100644 --- a/src/bin/ttyplay/timer.rs +++ b/src/bin/ttyplay/timer.rs @@ -138,6 +138,14 @@ pub fn spawn_task( - (((now - start_time) * 16) / playback_ratio); playback_ratio = 16; } + crate::event::TimerAction::Search(s) => { + if let Some(new_idx) = + frames.lock_arc().await.search(idx + 1, &s) + { + idx = new_idx; + force_update_time = true; + } + } crate::event::TimerAction::Quit => break, }, Res::TimerAction(Err(e)) => panic!("{}", e), -- cgit v1.2.3-54-g00ecf