Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify simple template #74

Merged
merged 7 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions simple-generated/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ license = "MIT"
edition = "2021"

[dependencies]
crossterm = "0.28.1"
joshka marked this conversation as resolved.
Show resolved Hide resolved
ratatui = "0.28.1"
color-eyre = "0.6.3"
joshka marked this conversation as resolved.
Show resolved Hide resolved
49 changes: 0 additions & 49 deletions simple-generated/src/app.rs
joshka marked this conversation as resolved.
Show resolved Hide resolved

This file was deleted.

145 changes: 87 additions & 58 deletions simple-generated/src/event.rs
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)]
joshka marked this conversation as resolved.
Show resolved Hide resolved
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),
joshka marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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>,
joshka marked this conversation as resolved.
Show resolved Hide resolved
pub struct EventSource {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Source seemed like a better name to avoid making this sound like this is where the events get handled by the app. 70/30 on this change (would value thoughts on it).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe EventListener?

I actually like EventHandler, because it technically does handle getting the events and allows the user to retrieve them after it gets them.

I asked chatgpt for various names:

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike EventHandler here as the application code needs to handle events somewhere. Event handler would not be that place. The missing info in the prompt is that the event handler both handles the event and sends the tick event. I'm inclined to perhaps actually simplify this further and remove events as used here altogether.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the entire module in 6a76940

/// Event receiver channel.
receiver: mpsc::Receiver<Event>,
/// Event handler thread.
handler: thread::JoinHandle<()>,
joshka marked this conversation as resolved.
Show resolved Hide resolved
}

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> {
joshka marked this conversation as resolved.
Show resolved Hide resolved
let event = self.receiver.recv()?;
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) {
joshka marked this conversation as resolved.
Show resolved Hide resolved
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");
joshka marked this conversation as resolved.
Show resolved Hide resolved
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;
}
joshka marked this conversation as resolved.
Show resolved Hide resolved
Ok(event) => match event {
CrosstermEvent::Key(e) if e.kind == KeyEventKind::Press => {
// ignore key release / repeat events
let _ = sender.send(Event::Key(e));
joshka marked this conversation as resolved.
Show resolved Hide resolved
}
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(_) => {}
},
}
}
}
28 changes: 0 additions & 28 deletions simple-generated/src/handler.rs
joshka marked this conversation as resolved.
Show resolved Hide resolved

This file was deleted.

Loading