-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
413 additions
and
569 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,6 @@ license = "MIT" | |
edition = "2021" | ||
|
||
[dependencies] | ||
crossterm = "0.28.1" | ||
ratatui = "0.28.1" | ||
color-eyre = "0.6.3" |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,87 +1,116 @@ | ||
use crate::app::AppResult; | ||
use ratatui::crossterm::event::{ | ||
self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent, | ||
}; | ||
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent}; | ||
use std::sync::mpsc; | ||
use std::thread; | ||
use std::time::{Duration, Instant}; | ||
|
||
/// Terminal events. | ||
#[derive(Clone, Copy, Debug)] | ||
#[derive(Debug)] | ||
pub enum Event { | ||
/// Terminal tick. | ||
/// The tick event is sent at a regular interval and can be used to trigger application state | ||
/// updates or animations. | ||
Tick, | ||
|
||
/// Key press. | ||
Key(KeyEvent), | ||
|
||
/// Mouse click/scroll. | ||
Mouse(MouseEvent), | ||
|
||
/// Terminal resize. | ||
/// | ||
/// This event may be fired multiple times in quick succession, so it is recommended not to | ||
/// render the UI on every resize event. | ||
Resize(u16, u16), | ||
|
||
/// An error occurred. | ||
Error(std::io::Error), | ||
} | ||
|
||
/// Terminal event handler. | ||
#[allow(dead_code)] | ||
/// | ||
/// This struct is responsible for listening to terminal events and sending them to the main thread. | ||
/// | ||
/// It sends a tick event at a regular interval. The tick rate is specified by the `tick_rate` | ||
/// parameter. If an error occurs, it sends an error event to the main thread and then stops | ||
/// running. | ||
#[derive(Debug)] | ||
pub struct EventHandler { | ||
/// Event sender channel. | ||
sender: mpsc::Sender<Event>, | ||
pub struct EventSource { | ||
/// Event receiver channel. | ||
receiver: mpsc::Receiver<Event>, | ||
/// Event handler thread. | ||
handler: thread::JoinHandle<()>, | ||
} | ||
|
||
impl EventHandler { | ||
impl EventSource { | ||
/// Constructs a new instance of [`EventHandler`]. | ||
pub fn new(tick_rate: u64) -> Self { | ||
let tick_rate = Duration::from_millis(tick_rate); | ||
pub fn new(tick_rate: Duration) -> Self { | ||
let (sender, receiver) = mpsc::channel(); | ||
let handler = { | ||
let sender = sender.clone(); | ||
thread::spawn(move || { | ||
let mut last_tick = Instant::now(); | ||
loop { | ||
let timeout = tick_rate | ||
.checked_sub(last_tick.elapsed()) | ||
.unwrap_or(tick_rate); | ||
|
||
if event::poll(timeout).expect("failed to poll new events") { | ||
match event::read().expect("unable to read event") { | ||
CrosstermEvent::Key(e) => { | ||
if e.kind == KeyEventKind::Press { | ||
sender.send(Event::Key(e)) | ||
} else { | ||
Ok(()) | ||
} | ||
} | ||
CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)), | ||
CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)), | ||
CrosstermEvent::FocusGained => Ok(()), | ||
CrosstermEvent::FocusLost => Ok(()), | ||
CrosstermEvent::Paste(_) => unimplemented!(), | ||
} | ||
.expect("failed to send terminal event") | ||
} | ||
|
||
if last_tick.elapsed() >= tick_rate { | ||
sender.send(Event::Tick).expect("failed to send tick event"); | ||
last_tick = Instant::now(); | ||
} | ||
} | ||
}) | ||
}; | ||
Self { | ||
sender, | ||
receiver, | ||
handler, | ||
} | ||
thread::spawn(move || event_thread(sender, tick_rate)); | ||
Self { receiver } | ||
} | ||
|
||
/// Receive the next event from the handler thread. | ||
/// | ||
/// This function will always block the current thread if | ||
/// there is no data available and it's possible for more data to be sent. | ||
pub fn next(&self) -> AppResult<Event> { | ||
Ok(self.receiver.recv()?) | ||
/// This function will always block the current thread if there is no data available unless the | ||
/// event source has been closed. | ||
pub fn next(&self) -> color_eyre::Result<Event> { | ||
let event = self.receiver.recv().wrap_err("failed to ")?; | ||
Ok(event) | ||
} | ||
} | ||
|
||
/// An event thread that listens for terminal events. | ||
/// | ||
/// This function is responsible for listening to terminal events and sending them to the main | ||
/// thread. It sends a tick event at a regular interval. The tick rate is specified by the | ||
/// `tick_rate` parameter. If an error occurs, it sends an error event to the main thread and then | ||
/// stops running. | ||
fn event_thread(sender: mpsc::Sender<Event>, tick_rate: Duration) { | ||
let mut last_tick: Option<Instant> = None; | ||
loop { | ||
if last_tick | ||
.map(|tick| tick.elapsed() >= tick_rate) | ||
.unwrap_or(true) | ||
{ | ||
sender.send(Event::Tick).expect("failed to send tick event"); | ||
last_tick = Some(Instant::now()); | ||
} | ||
|
||
let timeout = last_tick.map_or(Duration::ZERO, |tick| { | ||
tick_rate.saturating_sub(tick.elapsed()) | ||
}); | ||
|
||
match event::poll(timeout) { | ||
Ok(false) => { | ||
// no new events waiting | ||
continue; | ||
} | ||
Ok(true) => {} | ||
Err(e) => { | ||
let _ = sender.send(Event::Error(e)); | ||
break; | ||
} | ||
} | ||
|
||
match event::read() { | ||
Err(err) => { | ||
let _ = sender.send(Event::Error(err)); | ||
break; | ||
} | ||
Ok(event) => match event { | ||
CrosstermEvent::Key(e) if e.kind == KeyEventKind::Press => { | ||
// ignore key release / repeat events | ||
let _ = sender.send(Event::Key(e)); | ||
} | ||
CrosstermEvent::Key(_) => {} | ||
CrosstermEvent::Mouse(e) => { | ||
let _ = sender.send(Event::Mouse(e)); | ||
} | ||
CrosstermEvent::Resize(w, h) => { | ||
let _ = sender.send(Event::Resize(w, h)); | ||
} | ||
CrosstermEvent::FocusGained => {} | ||
CrosstermEvent::FocusLost => {} | ||
CrosstermEvent::Paste(_) => {} | ||
}, | ||
} | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.