From 2e11db09bcd75bac74b1356888e10740a5272d6c Mon Sep 17 00:00:00 2001 From: Michael Gattozzi Date: Thu, 19 Dec 2019 11:53:24 -0500 Subject: Switch from termion to crossterm for tui --- ticket/Cargo.toml | 4 +- ticket/src/tui.rs | 134 +++++++++++++++++++++--------------------------------- 2 files changed, 54 insertions(+), 84 deletions(-) (limited to 'ticket') diff --git a/ticket/Cargo.toml b/ticket/Cargo.toml index 574251a..6c260d7 100644 --- a/ticket/Cargo.toml +++ b/ticket/Cargo.toml @@ -25,5 +25,5 @@ toml = "0.5" uuid = { version = "0.8", features = ["serde", "v1"] } log = "0.4" pretty_env_logger = "0.3" -tui = "0.7" -termion = "1.5" +crossterm = "0.14" +tui = { version = "0.8", default-features = false, features = ['crossterm'] } diff --git a/ticket/src/tui.rs b/ticket/src/tui.rs index 57c8aca..85f60aa 100644 --- a/ticket/src/tui.rs +++ b/ticket/src/tui.rs @@ -7,24 +7,30 @@ use crate::{ Ticket, }; use anyhow::Result; +use crossterm::{ + cursor::Hide, + event::{ + self, + DisableMouseCapture, + EnableMouseCapture, + Event as CEvent, + KeyCode, + }, + queue, + terminal::*, +}; use std::{ collections::BTreeMap, - io, + io::{ + self, + Write, + }, sync::mpsc, thread, time::Duration, }; -use termion::{ - event::Key, - input::{ - MouseTerminal, - TermRead, - }, - raw::IntoRawMode, - screen::AlternateScreen, -}; use tui::{ - backend::TermionBackend, + backend::CrosstermBackend, layout::{ Alignment, Constraint, @@ -110,88 +116,31 @@ impl TicketState { } } } -/// A small event handler that wrap termion input and tick events. Each event -/// type is handled in its own thread and returned to a common `Receiver` -#[allow(dead_code)] -pub struct Events { - rx: mpsc::Receiver>, - input_handle: thread::JoinHandle<()>, - tick_handle: thread::JoinHandle<()>, -} - struct App<'a> { tabs: TabsState<'a>, tickets: TicketState, + should_quit: bool, } #[derive(Debug, Clone, Copy)] pub struct Config { - pub exit_key: Key, + pub exit_key: KeyCode, pub tick_rate: Duration, } impl Default for Config { fn default() -> Self { Self { - exit_key: Key::Char('q'), + exit_key: KeyCode::Char('q'), tick_rate: Duration::from_millis(250), } } } - -impl Events { - pub fn new() -> Self { - Self::with_config(Config::default()) - } - - pub fn with_config(config: Config) -> Self { - let (tx, rx) = mpsc::channel(); - let input_handle = { - let tx = tx.clone(); - thread::spawn(move || { - let stdin = io::stdin(); - for evt in stdin.keys() { - if let Ok(key) = evt { - if tx.send(Event::Input(key)).is_err() { - return; - } - if key == config.exit_key { - return; - } - } - } - }) - }; - let tick_handle = { - let tx = tx.clone(); - thread::spawn(move || { - let tx = tx.clone(); - loop { - tx.send(Event::Tick).unwrap(); - thread::sleep(config.tick_rate); - } - }) - }; - Self { - rx, - input_handle, - tick_handle, - } - } - - pub fn next(&self) -> Result, mpsc::RecvError> { - self.rx.recv() - } -} pub fn run() -> Result<()> { // Terminal initialization - let stdout = io::stdout().into_raw_mode()?; - let stdout = MouseTerminal::from(stdout); - let stdout = AlternateScreen::from(stdout); - let backend = TermionBackend::new(stdout); + enable_raw_mode()?; + queue!(io::stdout(), EnterAlternateScreen, EnableMouseCapture, Hide)?; + let backend = CrosstermBackend::new(io::stdout()); let mut terminal = Terminal::new(backend)?; - terminal.hide_cursor()?; - - let events = Events::new(); // App let mut app = App { @@ -202,8 +151,24 @@ pub fn run() -> Result<()> { let _ = map.insert("Closed".into(), get_closed_tickets()?); TicketState::new(map) }, + should_quit: false, }; + terminal.clear()?; + let (tx, rx) = mpsc::channel(); + let _ = thread::spawn(move || { + loop { + // poll for tick rate duration, if no events, sent tick event. + if event::poll(Duration::from_millis(250)).unwrap() { + if let CEvent::Key(key) = event::read().unwrap() { + tx.send(Event::Input(key)).unwrap(); + } + } + + tx.send(Event::Tick).unwrap(); + } + }); + // Main loop loop { terminal.draw(|mut f| { @@ -251,32 +216,37 @@ pub fn run() -> Result<()> { } })?; - match events.next()? { - Event::Input(input) => match input { - Key::Char('q') => { - break; + match rx.recv()? { + Event::Input(event) => match event.code { + KeyCode::Char('q') => { + app.should_quit = true; } - Key::Right => { + KeyCode::Right => { if app.tabs.index == 0 { app.tickets.status = Status::Closed; app.tickets.index = 0; } app.tabs.next(); } - Key::Left => { + KeyCode::Left => { if app.tabs.index != 0 { app.tickets.status = Status::Open; app.tickets.index = 0; } app.tabs.previous(); } - Key::Up => app.tickets.previous(), - Key::Down => app.tickets.next(), + KeyCode::Up => app.tickets.previous(), + KeyCode::Down => app.tickets.next(), _ => {} }, Event::Tick => continue, } + if app.should_quit { + break; + } } + queue!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; + disable_raw_mode()?; Ok(()) } -- cgit v1.2.3