refactor(examples): add input modes to user input examples

pull/217/head
Florian Dehau 4 years ago
parent 9085c81e76
commit e6ce0ab9a7

@ -29,10 +29,17 @@ use unicode_width::UnicodeWidthStr;
use crate::util::event::{Event, Events}; use crate::util::event::{Event, Events};
enum InputMode {
Normal,
Editing,
}
/// App holds the state of the application /// App holds the state of the application
struct App { struct App {
/// Current value of the input box /// Current value of the input box
input: String, input: String,
/// Current input mode
input_mode: InputMode,
/// History of recorded messages /// History of recorded messages
messages: Vec<String>, messages: Vec<String>,
} }
@ -41,6 +48,7 @@ impl Default for App {
fn default() -> App { fn default() -> App {
App { App {
input: String::new(), input: String::new(),
input_mode: InputMode::Normal,
messages: Vec::new(), messages: Vec::new(),
} }
} }
@ -55,7 +63,7 @@ fn main() -> Result<(), failure::Error> {
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
// Setup event handlers // Setup event handlers
let events = Events::new(); let mut events = Events::new();
// Create default app state // Create default app state
let mut app = App::default(); let mut app = App::default();
@ -66,12 +74,24 @@ fn main() -> Result<(), failure::Error> {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.margin(2) .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()); .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()) Paragraph::new([Text::raw(&app.input)].iter())
.style(Style::default().fg(Color::Yellow)) .style(Style::default().fg(Color::Yellow))
.block(Block::default().borders(Borders::ALL).title("Input")) .block(Block::default().borders(Borders::ALL).title("Input"))
.render(&mut f, chunks[0]); .render(&mut f, chunks[1]);
let messages = app let messages = app
.messages .messages
.iter() .iter()
@ -79,34 +99,47 @@ fn main() -> Result<(), failure::Error> {
.map(|(i, m)| Text::raw(format!("{}: {}", i, m))); .map(|(i, m)| Text::raw(format!("{}: {}", i, m)));
List::new(messages) List::new(messages)
.block(Block::default().borders(Borders::ALL).title("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 // Put the cursor back inside the input box
write!( write!(
terminal.backend_mut(), 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 // stdout is buffered, flush it to see the effect immediately when hitting backspace
io::stdout().flush().ok(); io::stdout().flush().ok();
// Handle input // Handle input
match events.next()? { match events.next()? {
Event::Input(input) => match input { Event::Input(input) => match app.input_mode {
Key::Char('q') => { InputMode::Normal => match input {
break; Key::Char('e') => {
} app.input_mode = InputMode::Editing;
Key::Char('\n') => { events.disable_exit_key();
app.messages.push(app.input.drain(..).collect()); }
} Key::Char('q') => {
Key::Char(c) => { break;
app.input.push(c); }
} _ => {}
Key::Backspace => { },
app.input.pop(); 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();
}
_ => {}
},
}, },
_ => {} _ => {}
} }

@ -2,6 +2,7 @@ use std::io;
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use termion::event::Key; use termion::event::Key;
use termion::input::TermRead; use termion::input::TermRead;
@ -16,6 +17,7 @@ pub enum Event<I> {
pub struct Events { pub struct Events {
rx: mpsc::Receiver<Event<Key>>, rx: mpsc::Receiver<Event<Key>>,
input_handle: thread::JoinHandle<()>, input_handle: thread::JoinHandle<()>,
ignore_exit_key: Arc<AtomicBool>,
tick_handle: thread::JoinHandle<()>, tick_handle: thread::JoinHandle<()>,
} }
@ -41,8 +43,10 @@ impl Events {
pub fn with_config(config: Config) -> Events { pub fn with_config(config: Config) -> Events {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let ignore_exit_key = Arc::new(AtomicBool::new(false));
let input_handle = { let input_handle = {
let tx = tx.clone(); let tx = tx.clone();
let ignore_exit_key = ignore_exit_key.clone();
thread::spawn(move || { thread::spawn(move || {
let stdin = io::stdin(); let stdin = io::stdin();
for evt in stdin.keys() { for evt in stdin.keys() {
@ -51,7 +55,7 @@ impl Events {
if let Err(_) = tx.send(Event::Input(key)) { if let Err(_) = tx.send(Event::Input(key)) {
return; return;
} }
if key == config.exit_key { if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key {
return; return;
} }
} }
@ -72,6 +76,7 @@ impl Events {
}; };
Events { Events {
rx, rx,
ignore_exit_key,
input_handle, input_handle,
tick_handle, tick_handle,
} }
@ -80,4 +85,12 @@ impl Events {
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> { pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
self.rx.recv() 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);
}
} }

Loading…
Cancel
Save