Flush EventStream when receiving a scroll event to work around high scroll sensitivities

This commit is contained in:
itsjunetime
2024-11-17 17:33:43 -07:00
parent 1aa26b8e8c
commit 5e7ec97b43
+43 -5
View File
@@ -1,11 +1,14 @@
use std::{ use std::{
future::poll_fn,
io::{stdout, Read, Write}, io::{stdout, Read, Write},
num::NonZeroUsize, num::NonZeroUsize,
path::PathBuf path::PathBuf,
task::Poll
}; };
use converter::{run_conversion_loop, ConvertedPage, ConverterMsg}; use converter::{run_conversion_loop, ConvertedPage, ConverterMsg};
use crossterm::{ use crossterm::{
event::{Event, EventStream, MouseEvent, MouseEventKind},
execute, execute,
terminal::{ terminal::{
disable_raw_mode, enable_raw_mode, window_size, EndSynchronizedUpdate, disable_raw_mode, enable_raw_mode, window_size, EndSynchronizedUpdate,
@@ -14,7 +17,7 @@ use crossterm::{
}; };
use futures_util::{stream::StreamExt, FutureExt}; use futures_util::{stream::StreamExt, FutureExt};
use glib::{LogField, LogLevel, LogWriterOutput}; use glib::{LogField, LogLevel, LogWriterOutput};
use notify::{Event, EventKind, RecursiveMode, Watcher}; use notify::{EventKind, RecursiveMode, Watcher};
use ratatui::{backend::CrosstermBackend, Terminal}; use ratatui::{backend::CrosstermBackend, Terminal};
use ratatui_image::picker::Picker; use ratatui_image::picker::Picker;
use renderer::{RenderError, RenderInfo, RenderNotif}; use renderer::{RenderError, RenderInfo, RenderNotif};
@@ -123,7 +126,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// We need to create `picker` on this thread because if we create it on the `renderer` thread, // 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 // 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 // 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, // 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<dyn std::error::Error>> {
renderer::start_rendering(&file_path, render_tx, render_rx, window_size) 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_converter, from_main) = flume::unbounded();
let (to_main, from_converter) = flume::unbounded(); let (to_main, from_converter) = flume::unbounded();
@@ -175,6 +178,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// If we can't get user input, just crash. // If we can't get user input, just crash.
let ev = ev.expect("Couldn't get any user input"); 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) { match tui.handle_event(&ev) {
None => needs_redraw = false, None => needs_redraw = false,
Some(action) => match action { Some(action) => match action {
@@ -244,7 +250,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
fn on_notify_ev( fn on_notify_ev(
to_tui_tx: flume::Sender<Result<RenderInfo, RenderError>>, to_tui_tx: flume::Sender<Result<RenderInfo, RenderError>>,
to_render_tx: flume::Sender<RenderNotif> to_render_tx: flume::Sender<RenderNotif>
) -> impl Fn(notify::Result<Event>) { ) -> impl Fn(notify::Result<notify::Event>) {
move |res| match res { move |res| match res {
// If we get an error here, and then an error sending, everything's going wrong. Just give // If we get an error here, and then an error sending, everything's going wrong. Just give
// up lol. // up lol.
@@ -267,3 +273,35 @@ fn on_notify_ev(
fn noop(_: LogLevel, _: &[LogField<'_>]) -> LogWriterOutput { fn noop(_: LogLevel, _: &[LogField<'_>]) -> LogWriterOutput {
LogWriterOutput::Handled 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;
}
}