use crossterm::event::{
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, ModifierKeyCode, MouseButton,
MouseEventKind,
};
use dioxus_native_core::prelude::*;
use dioxus_native_core::real_dom::NodeImmutable;
use rustc_hash::{FxHashMap, FxHashSet};
use dioxus_html::geometry::euclid::{Point2D, Rect, Size2D};
use dioxus_html::geometry::{
ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint, WheelDelta,
};
use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers};
use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, WheelData};
use std::any::Any;
use std::collections::HashMap;
use std::{
cell::{RefCell, RefMut},
rc::Rc,
time::{Duration, Instant},
};
use taffy::geometry::{Point, Size};
use taffy::{prelude::Layout, Taffy};
use crate::focus::{Focus, Focused};
use crate::layout::TaffyLayout;
use crate::{get_abs_layout, layout_to_screen_space, FocusState};
#[derive(Debug, Clone, PartialEq)]
pub struct Event {
pub id: NodeId,
pub name: &'static str,
pub data: EventData,
pub bubbles: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum EventData {
Mouse(MouseData),
Keyboard(KeyboardData),
Focus(FocusData),
Wheel(WheelData),
Form(FormData),
}
impl EventData {
pub fn into_any(self) -> Rc<dyn Any> {
match self {
EventData::Mouse(m) => Rc::new(m),
EventData::Keyboard(k) => Rc::new(k),
EventData::Focus(f) => Rc::new(f),
EventData::Wheel(w) => Rc::new(w),
EventData::Form(f) => Rc::new(f.into_html()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FormData {
pub value: String,
pub values: HashMap<String, Vec<String>>,
pub files: Option<Files>,
}
impl FormData {
fn into_html(self) -> dioxus_html::FormData {
dioxus_html::FormData {
value: self.value,
values: self.values,
files: None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Files {
files: FxHashMap<String, File>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct File {}
type EventCore = (&'static str, EventData);
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
pub struct InnerInputState {
mouse: Option<MouseData>,
wheel: Option<WheelData>,
last_key_pressed: Option<(KeyboardData, Instant)>,
pub(crate) focus_state: FocusState,
}
impl InnerInputState {
fn create(rdom: &mut RealDom) -> Self {
Self {
mouse: None,
wheel: None,
last_key_pressed: None,
focus_state: FocusState::create(rdom),
}
}
fn apply_event(&mut self, evt: &mut EventCore) {
match evt.1 {
EventData::Mouse(ref mut m) => {
let mut held_buttons = match &self.mouse {
Some(previous_data) => previous_data.held_buttons(),
None => MouseButtonSet::empty(),
};
match evt.0 {
"mousedown" => {
held_buttons.insert(
m.trigger_button()
.expect("No trigger button for mousedown event"),
);
}
"mouseup" => {
held_buttons.remove(
m.trigger_button()
.expect("No trigger button for mouseup event"),
);
}
_ => {}
}
let new_mouse_data = MouseData::new(
m.coordinates(),
m.trigger_button(),
held_buttons,
m.modifiers(),
);
self.mouse = Some(new_mouse_data.clone());
*m = new_mouse_data;
}
EventData::Wheel(ref w) => self.wheel = Some(w.clone()),
EventData::Keyboard(ref mut k) => {
let is_repeating = self
.last_key_pressed
.as_ref()
.filter(|(last_data, last_instant)| {
last_data.key() == k.key() && last_instant.elapsed() < MAX_REPEAT_TIME
})
.is_some();
if is_repeating {
*k = KeyboardData::new(k.key(), k.code(), k.location(), true, k.modifiers());
}
self.last_key_pressed = Some((k.clone(), Instant::now()));
}
_ => {}
}
}
fn update(
&mut self,
evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<Event>,
layout: &Taffy,
dom: &mut RealDom,
) {
let previous_mouse = self.mouse.clone();
self.wheel = None;
let old_focus = self.focus_state.last_focused_id;
evts.retain(|e| match &e.1 {
EventData::Keyboard(k) => match k.code() {
Code::Tab => !self
.focus_state
.progress(dom, !k.modifiers().contains(Modifiers::SHIFT)),
_ => true,
},
_ => true,
});
for e in evts.iter_mut() {
self.apply_event(e);
}
self.resolve_mouse_events(previous_mouse, resolved_events, layout, dom);
if old_focus != self.focus_state.last_focused_id {
if let Some(id) = self.focus_state.last_focused_id {
resolved_events.push(Event {
name: "focus",
id,
data: EventData::Focus(FocusData {}),
bubbles: event_bubbles("focus"),
});
resolved_events.push(Event {
name: "focusin",
id,
data: EventData::Focus(FocusData {}),
bubbles: event_bubbles("focusin"),
});
}
if let Some(id) = old_focus {
resolved_events.push(Event {
name: "focusout",
id,
data: EventData::Focus(FocusData {}),
bubbles: event_bubbles("focusout"),
});
}
}
}
fn resolve_mouse_events(
&mut self,
previous_mouse: Option<MouseData>,
resolved_events: &mut Vec<Event>,
layout: &Taffy,
dom: &mut RealDom,
) {
fn layout_contains_point(layout: &Layout, point: ScreenPoint) -> bool {
let Point { x, y } = layout.location;
let (x, y) = (
layout_to_screen_space(x).round(),
layout_to_screen_space(y).round(),
);
let Size { width, height } = layout.size;
let (width, height) = (
layout_to_screen_space(width).round(),
layout_to_screen_space(height).round(),
);
let layout_rect = Rect::new(Point2D::new(x, y), Size2D::new(width, height));
layout_rect.contains(point.cast())
}
fn try_create_event(
name: &'static str,
data: EventData,
will_bubble: &mut FxHashSet<NodeId>,
resolved_events: &mut Vec<Event>,
node: NodeRef,
dom: &RealDom,
) {
let id = node.id();
if will_bubble.insert(id) {
let mut parent = Some(node);
while let Some(current_parent) = parent {
let parent_id = current_parent.id();
will_bubble.insert(parent_id);
parent = current_parent.parent_id().and_then(|id| dom.get(id));
}
resolved_events.push(Event {
name,
id,
data,
bubbles: event_bubbles(name),
})
}
}
fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
let Point { x, y } = layout.location;
let node_origin = ClientPoint::new(
layout_to_screen_space(x).into(),
layout_to_screen_space(y).into(),
);
let new_client_coordinates = (mouse_data.client_coordinates() - node_origin)
.to_point()
.cast_unit();
let coordinates = Coordinates::new(
mouse_data.screen_coordinates(),
mouse_data.client_coordinates(),
new_client_coordinates,
mouse_data.page_coordinates(),
);
MouseData::new(
coordinates,
mouse_data.trigger_button(),
mouse_data.held_buttons(),
mouse_data.modifiers(),
)
}
if let Some(mouse_data) = &self.mouse {
let new_pos = mouse_data.screen_coordinates();
let old_pos = previous_mouse.as_ref().map(|m| m.screen_coordinates());
let previous_buttons = previous_mouse
.map_or(MouseButtonSet::empty(), |previous_data| {
previous_data.held_buttons()
});
let was_pressed = !(mouse_data.held_buttons() - previous_buttons).is_empty();
let was_released = !(previous_buttons - mouse_data.held_buttons()).is_empty();
let was_scrolled = self
.wheel
.as_ref()
.map_or(false, |data| !data.delta().is_zero());
let wheel_data = &self.wheel;
{
if old_pos != Some(new_pos) {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mousemove") {
let node_layout = get_abs_layout(node, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains && previously_contained {
try_create_event(
"mousemove",
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
{
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseenter") {
let node_layout = get_abs_layout(node, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains && !previously_contained {
try_create_event(
"mouseenter",
EventData::Mouse(mouse_data.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseover") {
let node_layout = get_abs_layout(node, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains && !previously_contained {
try_create_event(
"mouseover",
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
if was_pressed {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mousedown") {
let node_layout = get_abs_layout(node, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"mousedown",
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
if was_released {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseup") {
let node_layout = get_abs_layout(node, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"mouseup",
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
{
if mouse_data.trigger_button() == Some(DioxusMouseButton::Primary) && was_released {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("click") {
let node_layout = get_abs_layout(node, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"click",
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
{
if mouse_data.trigger_button() == Some(DioxusMouseButton::Secondary) && was_released
{
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("contextmenu") {
let node_layout = get_abs_layout(node, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"contextmenu",
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
{
if let Some(w) = wheel_data {
if was_scrolled {
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("wheel") {
let node_layout = get_abs_layout(node, layout);
let currently_contains = layout_contains_point(&node_layout, new_pos);
if currently_contains {
try_create_event(
"wheel",
EventData::Wheel(w.clone()),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
}
}
{
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseleave") {
let node_layout = get_abs_layout(node, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(&node_layout, new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseleave",
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
{
let mut will_bubble = FxHashSet::default();
for node in dom.get_listening_sorted("mouseout") {
let node_layout = get_abs_layout(node, layout);
let previously_contained = old_pos
.filter(|pos| layout_contains_point(&node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(&node_layout, new_pos);
if !currently_contains && previously_contained {
try_create_event(
"mouseout",
EventData::Mouse(prepare_mouse_data(mouse_data, &node_layout)),
&mut will_bubble,
resolved_events,
node,
dom,
);
}
}
}
if was_released {
let mut focus_id = None;
dom.traverse_depth_first(|node| {
let node_layout = layout
.layout(node.get::<TaffyLayout>().unwrap().node.unwrap())
.unwrap();
let currently_contains = layout_contains_point(node_layout, new_pos);
if currently_contains && node.get::<Focus>().unwrap().level.focusable() {
focus_id = Some(node.id());
}
});
if let Some(id) = focus_id {
self.focus_state.set_focus(dom, id);
}
}
}
}
}
pub struct RinkInputHandler {
state: Rc<RefCell<InnerInputState>>,
queued_events: Rc<RefCell<Vec<EventCore>>>,
}
impl RinkInputHandler {
pub fn create(rdom: &mut RealDom) -> (Self, impl FnMut(crossterm::event::Event)) {
let queued_events = Rc::new(RefCell::new(Vec::new()));
let queued_events2 = Rc::downgrade(&queued_events);
let regester_event = move |evt: crossterm::event::Event| {
if let Some(evt) = get_event(evt) {
if let Some(v) = queued_events2.upgrade() {
(*v).borrow_mut().push(evt);
}
}
};
let state = Rc::new(RefCell::new(InnerInputState::create(rdom)));
(
Self {
state,
queued_events,
},
regester_event,
)
}
pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut RealDom) -> Vec<Event> {
let mut resolved_events = Vec::new();
(*self.state).borrow_mut().update(
&mut (*self.queued_events).borrow_mut(),
&mut resolved_events,
layout,
dom,
);
let events = self
.queued_events
.replace(Vec::new())
.into_iter()
.filter(|e| {
![
"mouseenter",
"mouseover",
"mouseleave",
"mouseout",
"mousedown",
"mouseup",
"mousemove",
"drag",
"wheel",
"click",
"contextmenu",
]
.contains(&e.0)
})
.map(|evt| (evt.0, evt.1));
let mut hm: FxHashMap<&'static str, Vec<EventData>> = FxHashMap::default();
for (event, data) in events {
if let Some(v) = hm.get_mut(event) {
v.push(data);
} else {
hm.insert(event, vec![data]);
}
}
for (event, datas) in hm {
for node in dom.get_listening_sorted(event) {
for data in &datas {
let focused = node.get::<Focused>();
if focused.is_some() && focused.unwrap().0 {
resolved_events.push(Event {
name: event,
id: node.id(),
data: data.clone(),
bubbles: event_bubbles(event),
});
}
}
}
}
resolved_events
}
pub(crate) fn state(&self) -> RefMut<InnerInputState> {
self.state.borrow_mut()
}
}
fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
let (name, data): (&str, EventData) = match evt {
TermEvent::Key(k) => ("keydown", translate_key_event(k)?),
TermEvent::Mouse(m) => {
let (x, y) = (m.column.into(), m.row.into());
let alt = m.modifiers.contains(KeyModifiers::ALT);
let shift = m.modifiers.contains(KeyModifiers::SHIFT);
let ctrl = m.modifiers.contains(KeyModifiers::CONTROL);
let meta = false;
let get_mouse_data = |crossterm_button: Option<MouseButton>| {
let button = crossterm_button.map(|b| match b {
MouseButton::Left => DioxusMouseButton::Primary,
MouseButton::Right => DioxusMouseButton::Secondary,
MouseButton::Middle => DioxusMouseButton::Auxiliary,
});
let coordinates = Coordinates::new(
ScreenPoint::new(x, y),
ClientPoint::new(x, y),
ElementPoint::new(0., 0.),
PagePoint::new(x, y),
);
let mut modifiers = Modifiers::empty();
if shift {
modifiers.insert(Modifiers::SHIFT);
}
if ctrl {
modifiers.insert(Modifiers::CONTROL);
}
if meta {
modifiers.insert(Modifiers::META);
}
if alt {
modifiers.insert(Modifiers::ALT);
}
EventData::Mouse(MouseData::new(
coordinates,
button,
DioxusMouseButtons::empty(),
modifiers,
))
};
let get_wheel_data = |up| {
let y = if up { -1.0 } else { 1.0 };
EventData::Wheel(WheelData::new(WheelDelta::lines(0., y, 0.)))
};
match m.kind {
MouseEventKind::Down(b) => ("mousedown", get_mouse_data(Some(b))),
MouseEventKind::Up(b) => ("mouseup", get_mouse_data(Some(b))),
MouseEventKind::Drag(b) => ("drag", get_mouse_data(Some(b))),
MouseEventKind::Moved => ("mousemove", get_mouse_data(None)),
MouseEventKind::ScrollDown => ("wheel", get_wheel_data(false)),
MouseEventKind::ScrollUp => ("wheel", get_wheel_data(true)),
}
}
_ => return None,
};
Some((name, data))
}
fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
let key = key_from_crossterm_key_code(event.code);
let code = guess_code_from_crossterm_key_code(event.code)?;
let modifiers = modifiers_from_crossterm_modifiers(event.modifiers);
Some(EventData::Keyboard(KeyboardData::new(
key,
code,
Location::Standard,
false,
modifiers,
)))
}
fn key_from_crossterm_key_code(key_code: TermKeyCode) -> Key {
match key_code {
TermKeyCode::Backspace => Key::Backspace,
TermKeyCode::Enter => Key::Enter,
TermKeyCode::Left => Key::ArrowLeft,
TermKeyCode::Right => Key::ArrowRight,
TermKeyCode::Up => Key::ArrowUp,
TermKeyCode::Down => Key::ArrowDown,
TermKeyCode::Home => Key::Home,
TermKeyCode::End => Key::End,
TermKeyCode::PageUp => Key::PageUp,
TermKeyCode::PageDown => Key::PageDown,
TermKeyCode::Tab => Key::Tab,
TermKeyCode::BackTab => Key::Tab,
TermKeyCode::Delete => Key::Delete,
TermKeyCode::Insert => Key::Insert,
TermKeyCode::F(1) => Key::F1,
TermKeyCode::F(2) => Key::F2,
TermKeyCode::F(3) => Key::F3,
TermKeyCode::F(4) => Key::F4,
TermKeyCode::F(5) => Key::F5,
TermKeyCode::F(6) => Key::F6,
TermKeyCode::F(7) => Key::F7,
TermKeyCode::F(8) => Key::F8,
TermKeyCode::F(9) => Key::F9,
TermKeyCode::F(10) => Key::F10,
TermKeyCode::F(11) => Key::F11,
TermKeyCode::F(12) => Key::F12,
TermKeyCode::F(13) => Key::F13,
TermKeyCode::F(14) => Key::F14,
TermKeyCode::F(15) => Key::F15,
TermKeyCode::F(16) => Key::F16,
TermKeyCode::F(17) => Key::F17,
TermKeyCode::F(18) => Key::F18,
TermKeyCode::F(19) => Key::F19,
TermKeyCode::F(20) => Key::F20,
TermKeyCode::F(21) => Key::F21,
TermKeyCode::F(22) => Key::F22,
TermKeyCode::F(23) => Key::F23,
TermKeyCode::F(24) => Key::F24,
TermKeyCode::F(other) => {
panic!("Unexpected function key: {other:?}")
}
TermKeyCode::Char(c) => Key::Character(c.to_string()),
TermKeyCode::Null => Key::Unidentified,
TermKeyCode::Esc => Key::Escape,
TermKeyCode::CapsLock => Key::CapsLock,
TermKeyCode::ScrollLock => Key::ScrollLock,
TermKeyCode::NumLock => Key::NumLock,
TermKeyCode::PrintScreen => Key::PrintScreen,
TermKeyCode::Pause => Key::Pause,
TermKeyCode::Media(crossterm::event::MediaKeyCode::FastForward) => Key::MediaFastForward,
TermKeyCode::Media(crossterm::event::MediaKeyCode::PlayPause) => Key::MediaPlayPause,
TermKeyCode::Media(crossterm::event::MediaKeyCode::TrackPrevious) => {
Key::MediaTrackPrevious
}
TermKeyCode::Media(crossterm::event::MediaKeyCode::TrackNext) => Key::MediaTrackNext,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Stop) => Key::MediaStop,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Play) => Key::MediaPlay,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Pause) => Key::MediaPause,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Record) => Key::MediaRecord,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Rewind) => Key::MediaRewind,
TermKeyCode::Modifier(modifier) => match modifier {
ModifierKeyCode::IsoLevel3Shift
| ModifierKeyCode::IsoLevel5Shift
| ModifierKeyCode::RightShift
| ModifierKeyCode::LeftShift => Key::Shift,
ModifierKeyCode::RightControl | ModifierKeyCode::LeftControl => Key::Control,
ModifierKeyCode::RightAlt | ModifierKeyCode::LeftAlt => Key::Alt,
ModifierKeyCode::RightSuper | ModifierKeyCode::LeftSuper => Key::Super,
ModifierKeyCode::RightHyper | ModifierKeyCode::LeftHyper => Key::Hyper,
ModifierKeyCode::LeftMeta | ModifierKeyCode::RightMeta => Key::Meta,
},
_ => Key::Unidentified,
}
}
fn guess_code_from_crossterm_key_code(key_code: TermKeyCode) -> Option<Code> {
let code = match key_code {
TermKeyCode::Backspace => Code::Backspace,
TermKeyCode::Enter => Code::Enter,
TermKeyCode::Left => Code::ArrowLeft,
TermKeyCode::Right => Code::ArrowRight,
TermKeyCode::Up => Code::ArrowUp,
TermKeyCode::Down => Code::ArrowDown,
TermKeyCode::Home => Code::Home,
TermKeyCode::End => Code::End,
TermKeyCode::PageUp => Code::PageUp,
TermKeyCode::PageDown => Code::PageDown,
TermKeyCode::Tab => Code::Tab,
TermKeyCode::BackTab => Code::Tab,
TermKeyCode::Delete => Code::Delete,
TermKeyCode::Insert => Code::Insert,
TermKeyCode::F(1) => Code::F1,
TermKeyCode::F(2) => Code::F2,
TermKeyCode::F(3) => Code::F3,
TermKeyCode::F(4) => Code::F4,
TermKeyCode::F(5) => Code::F5,
TermKeyCode::F(6) => Code::F6,
TermKeyCode::F(7) => Code::F7,
TermKeyCode::F(8) => Code::F8,
TermKeyCode::F(9) => Code::F9,
TermKeyCode::F(10) => Code::F10,
TermKeyCode::F(11) => Code::F11,
TermKeyCode::F(12) => Code::F12,
TermKeyCode::F(13) => Code::F13,
TermKeyCode::F(14) => Code::F14,
TermKeyCode::F(15) => Code::F15,
TermKeyCode::F(16) => Code::F16,
TermKeyCode::F(17) => Code::F17,
TermKeyCode::F(18) => Code::F18,
TermKeyCode::F(19) => Code::F19,
TermKeyCode::F(20) => Code::F20,
TermKeyCode::F(21) => Code::F21,
TermKeyCode::F(22) => Code::F22,
TermKeyCode::F(23) => Code::F23,
TermKeyCode::F(24) => Code::F24,
TermKeyCode::F(other) => {
panic!("Unexpected function key: {other:?}")
}
TermKeyCode::Char(c) => match c {
'A'..='Z' | 'a'..='z' => match c.to_ascii_uppercase() {
'A' => Code::KeyA,
'B' => Code::KeyB,
'C' => Code::KeyC,
'D' => Code::KeyD,
'E' => Code::KeyE,
'F' => Code::KeyF,
'G' => Code::KeyG,
'H' => Code::KeyH,
'I' => Code::KeyI,
'J' => Code::KeyJ,
'K' => Code::KeyK,
'L' => Code::KeyL,
'M' => Code::KeyM,
'N' => Code::KeyN,
'O' => Code::KeyO,
'P' => Code::KeyP,
'Q' => Code::KeyQ,
'R' => Code::KeyR,
'S' => Code::KeyS,
'T' => Code::KeyT,
'U' => Code::KeyU,
'V' => Code::KeyV,
'W' => Code::KeyW,
'X' => Code::KeyX,
'Y' => Code::KeyY,
'Z' => Code::KeyZ,
_ => unreachable!("Exhaustively checked all characters in range A..Z"),
},
' ' => Code::Space,
'[' | '{' => Code::BracketLeft,
']' | '}' => Code::BracketRight,
';' => Code::Semicolon,
':' => Code::Semicolon,
',' => Code::Comma,
'<' => Code::Comma,
'.' => Code::Period,
'>' => Code::Period,
'1' => Code::Digit1,
'2' => Code::Digit2,
'3' => Code::Digit3,
'4' => Code::Digit4,
'5' => Code::Digit5,
'6' => Code::Digit6,
'7' => Code::Digit7,
'8' => Code::Digit8,
'9' => Code::Digit9,
'0' => Code::Digit0,
'!' => Code::Digit1,
'@' => Code::Digit2,
'#' => Code::Digit3,
'$' => Code::Digit4,
'%' => Code::Digit5,
'^' => Code::Digit6,
'&' => Code::Digit7,
'*' => Code::Digit8,
'(' => Code::Digit9,
')' => Code::Digit0,
'+' => Code::Equal,
'-' | '_' => Code::Minus,
'\'' => Code::Quote,
'"' => Code::Quote,
'\\' => Code::Backslash,
'|' => Code::Backslash,
'/' => Code::Slash,
'?' => Code::Slash,
'=' => Code::Equal,
'`' => Code::Backquote,
'~' => Code::Backquote,
_ => return None,
},
TermKeyCode::Esc => Code::Escape,
TermKeyCode::CapsLock => Code::CapsLock,
TermKeyCode::ScrollLock => Code::ScrollLock,
TermKeyCode::NumLock => Code::NumLock,
TermKeyCode::PrintScreen => Code::PrintScreen,
TermKeyCode::Pause => Code::Pause,
TermKeyCode::Menu => Code::ContextMenu,
TermKeyCode::Media(crossterm::event::MediaKeyCode::FastForward) => Code::MediaFastForward,
TermKeyCode::Media(crossterm::event::MediaKeyCode::PlayPause) => Code::MediaPlayPause,
TermKeyCode::Media(crossterm::event::MediaKeyCode::TrackPrevious) => {
Code::MediaTrackPrevious
}
TermKeyCode::Media(crossterm::event::MediaKeyCode::TrackNext) => Code::MediaTrackNext,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Stop) => Code::MediaStop,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Play) => Code::MediaPlay,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Pause) => Code::MediaPause,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Record) => Code::MediaRecord,
TermKeyCode::Media(crossterm::event::MediaKeyCode::Rewind) => Code::MediaRewind,
TermKeyCode::Modifier(modifier) => match modifier {
ModifierKeyCode::IsoLevel3Shift
| ModifierKeyCode::IsoLevel5Shift
| ModifierKeyCode::LeftShift => Code::ShiftLeft,
ModifierKeyCode::RightShift => Code::ShiftRight,
ModifierKeyCode::RightControl => Code::ControlRight,
ModifierKeyCode::LeftControl => Code::ControlLeft,
ModifierKeyCode::RightAlt => Code::AltRight,
ModifierKeyCode::LeftAlt => Code::AltLeft,
ModifierKeyCode::RightSuper | ModifierKeyCode::LeftSuper => Code::Super,
ModifierKeyCode::RightHyper | ModifierKeyCode::LeftHyper => Code::Hyper,
ModifierKeyCode::LeftMeta => Code::MetaLeft,
ModifierKeyCode::RightMeta => Code::MetaRight,
},
_ => return None,
};
Some(code)
}
fn modifiers_from_crossterm_modifiers(src: KeyModifiers) -> Modifiers {
let mut modifiers = Modifiers::empty();
if src.contains(KeyModifiers::SHIFT) {
modifiers.insert(Modifiers::SHIFT);
}
if src.contains(KeyModifiers::ALT) {
modifiers.insert(Modifiers::ALT);
}
if src.contains(KeyModifiers::CONTROL) {
modifiers.insert(Modifiers::CONTROL);
}
modifiers
}