From b54fe5c01d42cc0ba1f546c5c7233264d79d39ae Mon Sep 17 00:00:00 2001 From: rhysd Date: Sat, 23 Jul 2022 20:09:26 +0900 Subject: [PATCH] implement vertial mouse scroll with both crossterm and termion supports --- examples/termion.rs | 22 +++++++++------- src/input.rs | 64 ++++++++++++++++++++++++++++++++++++--------- src/textarea.rs | 14 ++++++++++ 3 files changed, 78 insertions(+), 22 deletions(-) diff --git a/examples/termion.rs b/examples/termion.rs index 9a1ebea..3d7e2b4 100644 --- a/examples/termion.rs +++ b/examples/termion.rs @@ -3,17 +3,17 @@ use std::io; use std::sync::mpsc; use std::thread; use std::time::Duration; -use termion::event::Key; +use termion::event::Event as TermEvent; use termion::input::{MouseTerminal, TermRead}; use termion::raw::IntoRawMode; use termion::screen::AlternateScreen; use tui::backend::TermionBackend; use tui::widgets::{Block, Borders}; use tui::Terminal; -use tui_textarea::TextArea; +use tui_textarea::{Input, Key, TextArea}; enum Event { - Key(Key), + Term(TermEvent), Tick, } @@ -25,12 +25,12 @@ fn main() -> Result<(), Box> { let mut term = Terminal::new(backend)?; let events = { - let keys = io::stdin().keys(); + let events = io::stdin().events(); let (tx, rx) = mpsc::channel(); let keys_tx = tx.clone(); thread::spawn(move || { - for key in keys.flatten() { - keys_tx.send(Event::Key(key)).unwrap(); + for event in events.flatten() { + keys_tx.send(Event::Term(event)).unwrap(); } }); thread::spawn(move || loop { @@ -49,10 +49,12 @@ fn main() -> Result<(), Box> { loop { match events.recv()? { - Event::Key(Key::Esc) => break, - Event::Key(key) => { - textarea.input(key); - } + Event::Term(event) => match event.into() { + Input { key: Key::Esc, .. } => break, + input => { + textarea.input(input); + } + }, Event::Tick => {} } term.draw(|f| { diff --git a/src/input.rs b/src/input.rs index 60b9c7b..5eccc21 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,9 +1,12 @@ #[cfg(feature = "arbitrary")] use arbitrary::Arbitrary; #[cfg(feature = "crossterm")] -use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::{ + Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers, MouseEvent as CrosstermMouseEvent, + MouseEventKind as CrosstermMouseEventKind, +}; #[cfg(feature = "termion")] -use termion::event::{Event as TerimonEvent, Key as TermionKey}; +use termion::event::{Event as TermionEvent, Key as TermionKey, MouseEvent as TermionMouseEvent}; /// Backend-agnostic key input kind. #[non_exhaustive] @@ -27,6 +30,10 @@ pub enum Key { PageUp, PageDown, Esc, + /// Virtual key to scroll down by mouse + MouseScrollDown, + /// Virtual key to scroll up by mouse + MouseScrollUp, /// An invalid key input (this key is always ignored by [`TextArea`](crate::TextArea)). Null, } @@ -90,10 +97,10 @@ impl Default for Input { impl From for Input { /// Convert [`crossterm::event::Event`] to [`Input`]. fn from(event: CrosstermEvent) -> Self { - if let CrosstermEvent::Key(key) = event { - Self::from(key) - } else { - Self::default() + match event { + CrosstermEvent::Key(key) => Self::from(key), + CrosstermEvent::Mouse(mouse) => Self::from(mouse), + _ => Self::default(), } } } @@ -126,14 +133,29 @@ impl From for Input { } } +#[cfg(feature = "crossterm")] +impl From for Input { + /// Convert [`crossterm::event::MouseEvent`] to [`Input`]. + fn from(mouse: CrosstermMouseEvent) -> Self { + let key = match mouse.kind { + CrosstermMouseEventKind::ScrollDown => Key::MouseScrollDown, + CrosstermMouseEventKind::ScrollUp => Key::MouseScrollUp, + _ => return Self::default(), + }; + let ctrl = mouse.modifiers.contains(KeyModifiers::CONTROL); + let alt = mouse.modifiers.contains(KeyModifiers::ALT); + Self { key, ctrl, alt } + } +} + #[cfg(feature = "termion")] -impl From for Input { +impl From for Input { /// Convert [`termion::event::Event`] to [`Input`]. - fn from(event: TerimonEvent) -> Self { - if let TerimonEvent::Key(key) = event { - Self::from(key) - } else { - Self::default() + fn from(event: TermionEvent) -> Self { + match event { + TermionEvent::Key(key) => Self::from(key), + TermionEvent::Mouse(mouse) => Self::from(mouse), + _ => Self::default(), } } } @@ -176,3 +198,21 @@ impl From for Input { Input { key, ctrl, alt } } } + +#[cfg(feature = "termion")] +impl From for Input { + /// Convert [`termion::event::MouseEvent`] to [`Input`]. + fn from(mouse: TermionMouseEvent) -> Self { + use termion::event::MouseButton; + let key = match mouse { + TermionMouseEvent::Press(MouseButton::WheelUp, ..) => Key::MouseScrollUp, + TermionMouseEvent::Press(MouseButton::WheelDown, ..) => Key::MouseScrollDown, + _ => return Self::default(), + }; + Self { + key, + ctrl: false, + alt: false, + } + } +} diff --git a/src/textarea.rs b/src/textarea.rs index b14aff2..f8f682c 100644 --- a/src/textarea.rs +++ b/src/textarea.rs @@ -441,6 +441,20 @@ impl<'a> TextArea<'a> { ctrl: true, alt: false, } => self.paste(), + Input { + key: Key::MouseScrollDown, + .. + } => { + self.scroll(1, 0); + false + } + Input { + key: Key::MouseScrollUp, + .. + } => { + self.scroll(-1, 0); + false + } _ => false, };