diff --git a/src/main.rs b/src/main.rs index b5ccc11..aec712b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ use std::{ + future::poll_fn, io::{stdout, Read, Write}, num::NonZeroUsize, - path::PathBuf + path::PathBuf, + task::Poll }; use converter::{run_conversion_loop, ConvertedPage, ConverterMsg}; use crossterm::{ + event::{Event, EventStream, MouseEvent, MouseEventKind}, execute, terminal::{ disable_raw_mode, enable_raw_mode, window_size, EndSynchronizedUpdate, @@ -14,7 +17,7 @@ use crossterm::{ }; use futures_util::{stream::StreamExt, FutureExt}; use glib::{LogField, LogLevel, LogWriterOutput}; -use notify::{Event, EventKind, RecursiveMode, Watcher}; +use notify::{EventKind, RecursiveMode, Watcher}; use ratatui::{backend::CrosstermBackend, Terminal}; use ratatui_image::picker::Picker; use renderer::{RenderError, RenderInfo, RenderNotif}; @@ -123,7 +126,7 @@ async fn main() -> Result<(), Box> { // We need to create `picker` on this thread because if we create it on the `renderer` thread, // it messes up something with user input. Input never makes it to the crossterm thing - let picker = Picker::from_query_stdio()?; + let picker = Picker::from_query_stdio()?; // then we want to spawn off the rendering task // We need to use the thread::spawn API so that this exists in a thread not owned by tokio, @@ -132,7 +135,7 @@ async fn main() -> Result<(), Box> { renderer::start_rendering(&file_path, render_tx, render_rx, window_size) }); - let mut ev_stream = crossterm::event::EventStream::new(); + let mut ev_stream = EventStream::new(); let (to_converter, from_main) = flume::unbounded(); let (to_main, from_converter) = flume::unbounded(); @@ -175,6 +178,9 @@ async fn main() -> Result<(), Box> { // If we can't get user input, just crash. let ev = ev.expect("Couldn't get any user input"); + flush_if_mouse(&ev, &mut ev_stream).await; + + // Some people have high mouse sensitivity match tui.handle_event(&ev) { None => needs_redraw = false, Some(action) => match action { @@ -244,7 +250,7 @@ async fn main() -> Result<(), Box> { fn on_notify_ev( to_tui_tx: flume::Sender>, to_render_tx: flume::Sender -) -> impl Fn(notify::Result) { +) -> impl Fn(notify::Result) { move |res| match res { // If we get an error here, and then an error sending, everything's going wrong. Just give // up lol. @@ -267,3 +273,35 @@ fn on_notify_ev( fn noop(_: LogLevel, _: &[LogField<'_>]) -> LogWriterOutput { LogWriterOutput::Handled } + +async fn flush_if_mouse(ev: &Event, ev_stream: &mut EventStream) { + // If you have a high scroll sensitivity, on some platforms, crossterm sends 2+ mouse events per + // scroll. However, because tdf scrolls pages once per mouse event, that means that they can't + // scroll the pages of their PDF as they would expect - it jumps multiple pages per one scroll. + // So this just flushes the event queue once we detect a scroll event to make sure those extra + // scrolls are ignored. + // + // Theoretically, this introduces a race condition where events that arrive in between the + // processing of `ev` and the time we call `poll_next_unpin` can get ignored, but that's a very + // very small amount of time, so unlikely to happen. However, if that does become an issue, we + // can just flush the event queue until we see a non-scroll event, then store that event in a + // future that will return it next time we call `tokio::select!` in the main loop. That'll make + // sure we don't miss anything. + if let Event::Mouse(MouseEvent { + kind: + MouseEventKind::ScrollUp + | MouseEventKind::ScrollDown + | MouseEventKind::ScrollLeft + | MouseEventKind::ScrollRight, + .. + }) = ev + { + poll_fn(|ctx| { + while ev_stream.poll_next_unpin(ctx).is_ready() { + println!("got another mouse event!"); + } + Poll::Ready(()) + }) + .await; + } +}