mirror of
https://github.com/ratatui-org/ratatui
synced 2024-11-10 07:04:17 +00:00
refactor(examples): add input modes to user input examples
This commit is contained in:
parent
9085c81e76
commit
e6ce0ab9a7
2 changed files with 66 additions and 20 deletions
|
@ -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<String>,
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -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<I> {
|
|||
pub struct Events {
|
||||
rx: mpsc::Receiver<Event<Key>>,
|
||||
input_handle: thread::JoinHandle<()>,
|
||||
ignore_exit_key: Arc<AtomicBool>,
|
||||
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<Event<Key>, 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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue