From e6ce0ab9a7f4f71c42f53fc7c34eda0d78eb46aa Mon Sep 17 00:00:00 2001 From: Florian Dehau Date: Sun, 19 Jan 2020 18:41:00 +0100 Subject: [PATCH] refactor(examples): add input modes to user input examples --- examples/user_input.rs | 71 +++++++++++++++++++++++++++++++----------- examples/util/event.rs | 15 ++++++++- 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/examples/user_input.rs b/examples/user_input.rs index 6f62cea6..7c6a7dd7 100644 --- a/examples/user_input.rs +++ b/examples/user_input.rs @@ -29,10 +29,17 @@ use unicode_width::UnicodeWidthStr; use crate::util::event::{Event, Events}; +enum InputMode { + Normal, + Editing, +} + /// App holds the state of the application struct App { /// Current value of the input box input: String, + /// Current input mode + input_mode: InputMode, /// History of recorded messages messages: Vec, } @@ -41,6 +48,7 @@ impl Default for App { fn default() -> App { App { input: String::new(), + input_mode: InputMode::Normal, messages: Vec::new(), } } @@ -55,7 +63,7 @@ fn main() -> Result<(), failure::Error> { let mut terminal = Terminal::new(backend)?; // Setup event handlers - let events = Events::new(); + let mut events = Events::new(); // Create default app state let mut app = App::default(); @@ -66,12 +74,24 @@ fn main() -> Result<(), failure::Error> { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) - .constraints([Constraint::Length(3), Constraint::Min(1)].as_ref()) + .constraints( + [ + Constraint::Length(1), + Constraint::Length(3), + Constraint::Min(1), + ] + .as_ref(), + ) .split(f.size()); + let help_message = match app.input_mode { + InputMode::Normal => "Press q to exit, e to start editing.", + InputMode::Editing => "Press Esc to stop editing, Enter to record the message", + }; + Paragraph::new([Text::raw(help_message)].iter()).render(&mut f, chunks[0]); Paragraph::new([Text::raw(&app.input)].iter()) .style(Style::default().fg(Color::Yellow)) .block(Block::default().borders(Borders::ALL).title("Input")) - .render(&mut f, chunks[0]); + .render(&mut f, chunks[1]); let messages = app .messages .iter() @@ -79,34 +99,47 @@ fn main() -> Result<(), failure::Error> { .map(|(i, m)| Text::raw(format!("{}: {}", i, m))); List::new(messages) .block(Block::default().borders(Borders::ALL).title("Messages")) - .render(&mut f, chunks[1]); + .render(&mut f, chunks[2]); })?; // Put the cursor back inside the input box write!( terminal.backend_mut(), "{}", - Goto(4 + app.input.width() as u16, 4) + Goto(4 + app.input.width() as u16, 5) )?; // stdout is buffered, flush it to see the effect immediately when hitting backspace io::stdout().flush().ok(); // Handle input match events.next()? { - Event::Input(input) => match input { - Key::Char('q') => { - break; - } - Key::Char('\n') => { - app.messages.push(app.input.drain(..).collect()); - } - Key::Char(c) => { - app.input.push(c); - } - Key::Backspace => { - app.input.pop(); - } - _ => {} + Event::Input(input) => match app.input_mode { + InputMode::Normal => match input { + Key::Char('e') => { + app.input_mode = InputMode::Editing; + events.disable_exit_key(); + } + Key::Char('q') => { + break; + } + _ => {} + }, + InputMode::Editing => match input { + Key::Char('\n') => { + app.messages.push(app.input.drain(..).collect()); + } + Key::Char(c) => { + app.input.push(c); + } + Key::Backspace => { + app.input.pop(); + } + Key::Esc => { + app.input_mode = InputMode::Normal; + events.enable_exit_key(); + } + _ => {} + }, }, _ => {} } diff --git a/examples/util/event.rs b/examples/util/event.rs index e58fe434..2a4fd318 100644 --- a/examples/util/event.rs +++ b/examples/util/event.rs @@ -2,6 +2,7 @@ use std::io; use std::sync::mpsc; use std::thread; use std::time::Duration; +use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; use termion::event::Key; use termion::input::TermRead; @@ -16,6 +17,7 @@ pub enum Event { pub struct Events { rx: mpsc::Receiver>, input_handle: thread::JoinHandle<()>, + ignore_exit_key: Arc, tick_handle: thread::JoinHandle<()>, } @@ -41,8 +43,10 @@ impl Events { pub fn with_config(config: Config) -> Events { let (tx, rx) = mpsc::channel(); + let ignore_exit_key = Arc::new(AtomicBool::new(false)); let input_handle = { let tx = tx.clone(); + let ignore_exit_key = ignore_exit_key.clone(); thread::spawn(move || { let stdin = io::stdin(); for evt in stdin.keys() { @@ -51,7 +55,7 @@ impl Events { if let Err(_) = tx.send(Event::Input(key)) { return; } - if key == config.exit_key { + if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key { return; } } @@ -72,6 +76,7 @@ impl Events { }; Events { rx, + ignore_exit_key, input_handle, tick_handle, } @@ -80,4 +85,12 @@ impl Events { pub fn next(&self) -> Result, mpsc::RecvError> { self.rx.recv() } + + pub fn disable_exit_key(&mut self) { + self.ignore_exit_key.store(true, Ordering::Relaxed); + } + + pub fn enable_exit_key(&mut self) { + self.ignore_exit_key.store(false, Ordering::Relaxed); + } }