diff --git a/src/input.rs b/src/input.rs deleted file mode 100644 index 04b2c24..0000000 --- a/src/input.rs +++ /dev/null @@ -1,325 +0,0 @@ -#[cfg(any(feature = "crossterm", feature = "tuirs-crossterm"))] -use crate::crossterm::event::{ - Event as CrosstermEvent, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, - MouseEventKind as CrosstermMouseEventKind, -}; -#[cfg(feature = "arbitrary")] -use arbitrary::Arbitrary; -#[cfg(any(feature = "termion", feature = "tuirs-termion"))] -use termion::event::{Event as TermionEvent, Key as TermionKey, MouseEvent as TermionMouseEvent}; -#[cfg(feature = "termwiz")] -use termwiz::input::{ - InputEvent as TermwizInputEvent, KeyEvent as TermwizKeyEvent, - MouseButtons as TermwizMouseButtons, MouseEvent as TermwizMouseEvent, PixelMouseEvent, -}; - -/// Backend-agnostic key input kind. -/// -/// This type is marked as `#[non_exhaustive]` since more keys may be supported in the future. -#[non_exhaustive] -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] -pub enum Key { - /// Normal letter key input. - Char(char), - /// F1, F2, F3, ... keys. - F(u8), - Backspace, - Enter, - Left, - Right, - Up, - Down, - Tab, - Delete, - Home, - End, - 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, -} - -/// Backend-agnostic key input type. -/// -/// When `crossterm`, `termion`, `termwiz` features are enabled, converting respective key input types into this -/// `Input` type is defined. -/// ```no_run -/// use tui_textarea::{TextArea, Input, Key}; -/// use crossterm::event::{Event, read}; -/// -/// let event = read().unwrap(); -/// -/// // `Input::from` can convert backend-native event into `Input` -/// let input = Input::from(event.clone()); -/// // or `Into::into` -/// let input: Input = event.clone().into(); -/// // Conversion from `KeyEvent` value is also available -/// if let Event::Key(key) = event { -/// let input = Input::from(key); -/// } -/// ``` -/// -/// Creating `Input` instance directly can cause backend-agnostic input as follows. -/// -/// ``` -/// use tui_textarea::{TextArea, Input, Key}; -/// -/// let mut textarea = TextArea::default(); -/// -/// // Input Ctrl+A -/// textarea.input(Input { -/// key: Key::Char('a'), -/// ctrl: true, -/// alt: false, -/// }); -/// ``` -#[derive(Debug, Clone)] -#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] -pub struct Input { - /// Typed key. - pub key: Key, - /// Ctrl modifier key. `true` means Ctrl key was pressed. - pub ctrl: bool, - /// Alt modifier key. `true` means Alt key was pressed. - pub alt: bool, -} - -impl Default for Input { - /// The default input is [`Key::Null`] without pressing any modifier keys, which means invalid input. - fn default() -> Self { - Input { - key: Key::Null, - ctrl: false, - alt: false, - } - } -} - -#[cfg(any(feature = "crossterm", feature = "tuirs-crossterm"))] -impl From for Input { - /// Convert [`crossterm::event::Event`] to [`Input`]. - fn from(event: CrosstermEvent) -> Self { - match event { - CrosstermEvent::Key(key) if key.kind != KeyEventKind::Release => Self::from(key), - CrosstermEvent::Mouse(mouse) => Self::from(mouse), - _ => Self::default(), - } - } -} - -#[cfg(any(feature = "crossterm", feature = "tuirs-crossterm"))] -impl From for Input { - /// Convert [`crossterm::event::KeyEvent`] to [`Input`]. - fn from(key: KeyEvent) -> Self { - let ctrl = key.modifiers.contains(KeyModifiers::CONTROL); - let alt = key.modifiers.contains(KeyModifiers::ALT); - let key = match key.code { - KeyCode::Char(c) => Key::Char(c), - KeyCode::Backspace => Key::Backspace, - KeyCode::Enter => Key::Enter, - KeyCode::Left => Key::Left, - KeyCode::Right => Key::Right, - KeyCode::Up => Key::Up, - KeyCode::Down => Key::Down, - KeyCode::Tab => Key::Tab, - KeyCode::Delete => Key::Delete, - KeyCode::Home => Key::Home, - KeyCode::End => Key::End, - KeyCode::PageUp => Key::PageUp, - KeyCode::PageDown => Key::PageDown, - KeyCode::Esc => Key::Esc, - KeyCode::F(x) => Key::F(x), - _ => Key::Null, - }; - Self { key, ctrl, alt } - } -} - -#[cfg(any(feature = "crossterm", feature = "tuirs-crossterm"))] -impl From for Input { - /// Convert [`crossterm::event::MouseEvent`] to [`Input`]. - fn from(mouse: MouseEvent) -> 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(any(feature = "termion", feature = "tuirs-termion"))] -impl From for Input { - /// Convert [`termion::event::Event`] to [`Input`]. - fn from(event: TermionEvent) -> Self { - match event { - TermionEvent::Key(key) => Self::from(key), - TermionEvent::Mouse(mouse) => Self::from(mouse), - _ => Self::default(), - } - } -} - -#[cfg(any(feature = "termion", feature = "tuirs-termion"))] -impl From for Input { - /// Convert [`termion::event::Key`] to [`Input`]. - fn from(key: TermionKey) -> Self { - use TermionKey::*; - - let mut ctrl = false; - let mut alt = false; - let key = match key { - Char('\n' | '\r') => Key::Enter, - Char(c) => Key::Char(c), - Ctrl(c) => { - ctrl = true; - Key::Char(c) - } - Alt(c) => { - alt = true; - Key::Char(c) - } - Backspace => Key::Backspace, - Left => Key::Left, - Right => Key::Right, - Up => Key::Up, - Down => Key::Down, - Home => Key::Home, - End => Key::End, - PageUp => Key::PageUp, - PageDown => Key::PageDown, - BackTab => Key::Tab, - Delete => Key::Delete, - Esc => Key::Esc, - F(x) => Key::F(x), - _ => Key::Null, - }; - - Input { key, ctrl, alt } - } -} - -#[cfg(any(feature = "termion", feature = "tuirs-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, - } - } -} - -#[cfg(feature = "termwiz")] -impl From for Input { - /// Convert [`termwiz::input::InputEvent`] to [`Input`]. - fn from(input: TermwizInputEvent) -> Self { - match input { - TermwizInputEvent::Key(key) => Self::from(key), - TermwizInputEvent::Mouse(mouse) => Self::from(mouse), - TermwizInputEvent::PixelMouse(mouse) => Self::from(mouse), - _ => Self::default(), - } - } -} - -#[cfg(feature = "termwiz")] -impl From for Input { - /// Convert [`termwiz::input::KeyEvent`] to [`Input`]. - fn from(key: TermwizKeyEvent) -> Self { - use termwiz::input::{KeyCode, Modifiers}; - - let TermwizKeyEvent { key, modifiers } = key; - let key = match key { - KeyCode::Char(c) => Key::Char(c), - KeyCode::Backspace => Key::Backspace, - KeyCode::Tab => Key::Tab, - KeyCode::Enter => Key::Enter, - KeyCode::Escape => Key::Esc, - KeyCode::PageUp => Key::PageUp, - KeyCode::PageDown => Key::PageDown, - KeyCode::End => Key::End, - KeyCode::Home => Key::Home, - KeyCode::LeftArrow => Key::Left, - KeyCode::RightArrow => Key::Right, - KeyCode::UpArrow => Key::Up, - KeyCode::DownArrow => Key::Down, - KeyCode::Delete => Key::Delete, - KeyCode::Function(x) => Key::F(x), - _ => Key::Null, - }; - let ctrl = modifiers.contains(Modifiers::CTRL); - let alt = modifiers.contains(Modifiers::ALT); - - Self { key, ctrl, alt } - } -} - -#[cfg(feature = "termwiz")] -impl From for Key { - /// Convert [`termwiz::input::MouseButtons`] to [`Key`]. - fn from(buttons: TermwizMouseButtons) -> Self { - if buttons.contains(TermwizMouseButtons::VERT_WHEEL) { - if buttons.contains(TermwizMouseButtons::WHEEL_POSITIVE) { - Key::MouseScrollUp - } else { - Key::MouseScrollDown - } - } else { - Key::Null - } - } -} - -#[cfg(feature = "termwiz")] -impl From for Input { - /// Convert [`termwiz::input::MouseEvent`] to [`Input`]. - fn from(mouse: TermwizMouseEvent) -> Self { - use termwiz::input::Modifiers; - - let TermwizMouseEvent { - mouse_buttons, - modifiers, - .. - } = mouse; - let key = Key::from(mouse_buttons); - let ctrl = modifiers.contains(Modifiers::CTRL); - let alt = modifiers.contains(Modifiers::ALT); - - Self { key, ctrl, alt } - } -} - -#[cfg(feature = "termwiz")] -impl From for Input { - /// Convert [`termwiz::input::PixelMouseEvent`] to [`Input`]. - fn from(mouse: PixelMouseEvent) -> Self { - use termwiz::input::Modifiers; - - let PixelMouseEvent { - mouse_buttons, - modifiers, - .. - } = mouse; - let key = Key::from(mouse_buttons); - let ctrl = modifiers.contains(Modifiers::CTRL); - let alt = modifiers.contains(Modifiers::ALT); - - Self { key, ctrl, alt } - } -} diff --git a/src/input/crossterm.rs b/src/input/crossterm.rs new file mode 100644 index 0000000..1dbde0b --- /dev/null +++ b/src/input/crossterm.rs @@ -0,0 +1,56 @@ +use super::{Input, Key}; +use crate::crossterm::event::{ + Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind, +}; + +impl From for Input { + /// Convert [`crossterm::event::Event`] to [`Input`]. + fn from(event: Event) -> Self { + match event { + Event::Key(key) if key.kind != KeyEventKind::Release => Self::from(key), + Event::Mouse(mouse) => Self::from(mouse), + _ => Self::default(), + } + } +} + +impl From for Input { + /// Convert [`crossterm::event::KeyEvent`] to [`Input`]. + fn from(key: KeyEvent) -> Self { + let ctrl = key.modifiers.contains(KeyModifiers::CONTROL); + let alt = key.modifiers.contains(KeyModifiers::ALT); + let key = match key.code { + KeyCode::Char(c) => Key::Char(c), + KeyCode::Backspace => Key::Backspace, + KeyCode::Enter => Key::Enter, + KeyCode::Left => Key::Left, + KeyCode::Right => Key::Right, + KeyCode::Up => Key::Up, + KeyCode::Down => Key::Down, + KeyCode::Tab => Key::Tab, + KeyCode::Delete => Key::Delete, + KeyCode::Home => Key::Home, + KeyCode::End => Key::End, + KeyCode::PageUp => Key::PageUp, + KeyCode::PageDown => Key::PageDown, + KeyCode::Esc => Key::Esc, + KeyCode::F(x) => Key::F(x), + _ => Key::Null, + }; + Self { key, ctrl, alt } + } +} + +impl From for Input { + /// Convert [`crossterm::event::MouseEvent`] to [`Input`]. + fn from(mouse: MouseEvent) -> Self { + let key = match mouse.kind { + MouseEventKind::ScrollDown => Key::MouseScrollDown, + MouseEventKind::ScrollUp => Key::MouseScrollUp, + _ => return Self::default(), + }; + let ctrl = mouse.modifiers.contains(KeyModifiers::CONTROL); + let alt = mouse.modifiers.contains(KeyModifiers::ALT); + Self { key, ctrl, alt } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 0000000..c9d879c --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,105 @@ +#[cfg(any(feature = "crossterm", feature = "tuirs-crossterm"))] +mod crossterm; +#[cfg(any(feature = "termion", feature = "tuirs-termion"))] +mod termion; +#[cfg(feature = "termwiz")] +mod termwiz; + +#[cfg(feature = "arbitrary")] +use arbitrary::Arbitrary; + +/// Backend-agnostic key input kind. +/// +/// This type is marked as `#[non_exhaustive]` since more keys may be supported in the future. +#[non_exhaustive] +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum Key { + /// Normal letter key input + Char(char), + /// F1, F2, F3, ... keys + F(u8), + /// Backspace key + Backspace, + /// Enter or return key + Enter, + /// Left arrow key + Left, + /// Right arrow key + Right, + /// Up arrow key + Up, + /// Down arrow key + Down, + /// Tab key + Tab, + /// Delete key + Delete, + /// Home key + Home, + /// End key + End, + /// Page up key + PageUp, + /// Page down key + PageDown, + /// Escape key + 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, +} + +impl Default for Key { + fn default() -> Self { + Key::Null + } +} + +/// Backend-agnostic key input type. +/// +/// When `crossterm`, `termion`, `termwiz` features are enabled, converting respective key input types into this +/// `Input` type is defined. +/// ```no_run +/// use tui_textarea::{TextArea, Input, Key}; +/// use crossterm::event::{Event, read}; +/// +/// let event = read().unwrap(); +/// +/// // `Input::from` can convert backend-native event into `Input` +/// let input = Input::from(event.clone()); +/// // or `Into::into` +/// let input: Input = event.clone().into(); +/// // Conversion from `KeyEvent` value is also available +/// if let Event::Key(key) = event { +/// let input = Input::from(key); +/// } +/// ``` +/// +/// Creating `Input` instance directly can cause backend-agnostic input as follows. +/// +/// ``` +/// use tui_textarea::{TextArea, Input, Key}; +/// +/// let mut textarea = TextArea::default(); +/// +/// // Input Ctrl+A +/// textarea.input(Input { +/// key: Key::Char('a'), +/// ctrl: true, +/// alt: false, +/// }); +/// ``` +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub struct Input { + /// Typed key. + pub key: Key, + /// Ctrl modifier key. `true` means Ctrl key was pressed. + pub ctrl: bool, + /// Alt modifier key. `true` means Alt key was pressed. + pub alt: bool, +} diff --git a/src/input/termion.rs b/src/input/termion.rs new file mode 100644 index 0000000..9a95a42 --- /dev/null +++ b/src/input/termion.rs @@ -0,0 +1,68 @@ +use super::{Input, Key}; +use termion::event::{Event, Key as KeyEvent, MouseEvent}; + +impl From for Input { + /// Convert [`termion::event::Event`] to [`Input`]. + fn from(event: Event) -> Self { + match event { + Event::Key(key) => Self::from(key), + Event::Mouse(mouse) => Self::from(mouse), + _ => Self::default(), + } + } +} + +impl From for Input { + /// Convert [`termion::event::Key`] to [`Input`]. + fn from(key: KeyEvent) -> Self { + use KeyEvent::*; + + let mut ctrl = false; + let mut alt = false; + let key = match key { + Char('\n' | '\r') => Key::Enter, + Char(c) => Key::Char(c), + Ctrl(c) => { + ctrl = true; + Key::Char(c) + } + Alt(c) => { + alt = true; + Key::Char(c) + } + Backspace => Key::Backspace, + Left => Key::Left, + Right => Key::Right, + Up => Key::Up, + Down => Key::Down, + Home => Key::Home, + End => Key::End, + PageUp => Key::PageUp, + PageDown => Key::PageDown, + BackTab => Key::Tab, + Delete => Key::Delete, + Esc => Key::Esc, + F(x) => Key::F(x), + _ => Key::Null, + }; + + Input { key, ctrl, alt } + } +} + +impl From for Input { + /// Convert [`termion::event::MouseEvent`] to [`Input`]. + fn from(mouse: MouseEvent) -> Self { + use termion::event::MouseButton; + let key = match mouse { + MouseEvent::Press(MouseButton::WheelUp, ..) => Key::MouseScrollUp, + MouseEvent::Press(MouseButton::WheelDown, ..) => Key::MouseScrollDown, + _ => return Self::default(), + }; + Self { + key, + ctrl: false, + alt: false, + } + } +} diff --git a/src/input/termwiz.rs b/src/input/termwiz.rs new file mode 100644 index 0000000..9e31e15 --- /dev/null +++ b/src/input/termwiz.rs @@ -0,0 +1,96 @@ +use super::{Input, Key}; +use termwiz::input::{InputEvent, KeyEvent, MouseButtons, MouseEvent, PixelMouseEvent}; + +impl From for Input { + /// Convert [`termwiz::input::InputEvent`] to [`Input`]. + fn from(input: InputEvent) -> Self { + match input { + InputEvent::Key(key) => Self::from(key), + InputEvent::Mouse(mouse) => Self::from(mouse), + InputEvent::PixelMouse(mouse) => Self::from(mouse), + _ => Self::default(), + } + } +} + +impl From for Input { + /// Convert [`termwiz::input::KeyEvent`] to [`Input`]. + fn from(key: KeyEvent) -> Self { + use termwiz::input::{KeyCode, Modifiers}; + + let KeyEvent { key, modifiers } = key; + let key = match key { + KeyCode::Char(c) => Key::Char(c), + KeyCode::Backspace => Key::Backspace, + KeyCode::Tab => Key::Tab, + KeyCode::Enter => Key::Enter, + KeyCode::Escape => Key::Esc, + KeyCode::PageUp => Key::PageUp, + KeyCode::PageDown => Key::PageDown, + KeyCode::End => Key::End, + KeyCode::Home => Key::Home, + KeyCode::LeftArrow => Key::Left, + KeyCode::RightArrow => Key::Right, + KeyCode::UpArrow => Key::Up, + KeyCode::DownArrow => Key::Down, + KeyCode::Delete => Key::Delete, + KeyCode::Function(x) => Key::F(x), + _ => Key::Null, + }; + let ctrl = modifiers.contains(Modifiers::CTRL); + let alt = modifiers.contains(Modifiers::ALT); + + Self { key, ctrl, alt } + } +} + +impl From for Key { + /// Convert [`termwiz::input::MouseButtons`] to [`Key`]. + fn from(buttons: MouseButtons) -> Self { + if buttons.contains(MouseButtons::VERT_WHEEL) { + if buttons.contains(MouseButtons::WHEEL_POSITIVE) { + Key::MouseScrollUp + } else { + Key::MouseScrollDown + } + } else { + Key::Null + } + } +} + +impl From for Input { + /// Convert [`termwiz::input::MouseEvent`] to [`Input`]. + fn from(mouse: MouseEvent) -> Self { + use termwiz::input::Modifiers; + + let MouseEvent { + mouse_buttons, + modifiers, + .. + } = mouse; + let key = Key::from(mouse_buttons); + let ctrl = modifiers.contains(Modifiers::CTRL); + let alt = modifiers.contains(Modifiers::ALT); + + Self { key, ctrl, alt } + } +} + +impl From for Input { + /// Convert [`termwiz::input::PixelMouseEvent`] to [`Input`]. + fn from(mouse: PixelMouseEvent) -> Self { + use termwiz::input::Modifiers; + + let PixelMouseEvent { + mouse_buttons, + modifiers, + .. + } = mouse; + let key = Key::from(mouse_buttons); + let ctrl = modifiers.contains(Modifiers::CTRL); + let alt = modifiers.contains(Modifiers::ALT); + + Self { key, ctrl, alt } + } +}