123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998 |
- use crossterm::event::{
- Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
- };
- use dioxus_core::*;
- use dioxus_native_core::tree::TreeView;
- use dioxus_native_core::NodeId;
- 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,
- cell::{RefCell, RefMut},
- rc::Rc,
- time::{Duration, Instant},
- };
- use taffy::geometry::{Point, Size};
- use taffy::{prelude::Layout, Taffy};
- use crate::{layout_to_screen_space, FocusState};
- use crate::{TuiDom, TuiNode};
- pub(crate) struct Event {
- pub id: ElementId,
- pub name: &'static str,
- pub data: Rc<dyn Any>,
- pub bubbles: bool,
- }
- // a wrapper around the input state for easier access
- // todo: fix loop
- // pub struct InputState(Rc<Rc<RefCell<InnerInputState>>>);
- // impl InputState {
- // pub fn get(cx: &ScopeState) -> InputState {
- // let inner = cx
- // .consume_context::<Rc<RefCell<InnerInputState>>>()
- // .expect("Rink InputState can only be used in Rink apps!");
- // (**inner).borrow_mut().subscribe(cx.schedule_update());
- // InputState(inner)
- // }
- // pub fn mouse(&self) -> Option<MouseData> {
- // let data = (**self.0).borrow();
- // mouse.as_ref().map(|m| m.clone())
- // }
- // pub fn wheel(&self) -> Option<WheelData> {
- // let data = (**self.0).borrow();
- // wheel.as_ref().map(|w| w.clone())
- // }
- // pub fn screen(&self) -> Option<(u16, u16)> {
- // let data = (**self.0).borrow();
- // screen.as_ref().map(|m| m.clone())
- // }
- // pub fn last_key_pressed(&self) -> Option<KeyboardData> {
- // let data = (**self.0).borrow();
- // last_key_pressed
- // .as_ref()
- // .map(|k| &k.0.clone())
- // }
- // }
- type EventCore = (&'static str, EventData);
- #[derive(Debug)]
- enum EventData {
- Mouse(MouseData),
- Wheel(WheelData),
- Screen((u16, u16)),
- Keyboard(KeyboardData),
- }
- impl EventData {
- fn into_any(self) -> Rc<dyn Any + Send + Sync> {
- match self {
- Self::Mouse(m) => Rc::new(m),
- Self::Wheel(w) => Rc::new(w),
- Self::Screen(s) => Rc::new(s),
- Self::Keyboard(k) => Rc::new(k),
- }
- }
- }
- const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
- pub struct InnerInputState {
- mouse: Option<MouseData>,
- wheel: Option<WheelData>,
- last_key_pressed: Option<(KeyboardData, Instant)>,
- screen: Option<(u16, u16)>,
- pub(crate) focus_state: FocusState,
- // subscribers: Vec<Rc<dyn Fn() + 'static>>,
- }
- impl InnerInputState {
- fn new() -> Self {
- Self {
- mouse: None,
- wheel: None,
- last_key_pressed: None,
- screen: None,
- // subscribers: Vec::new(),
- focus_state: FocusState::default(),
- }
- }
- // stores current input state and transforms events based on that state
- fn apply_event(&mut self, evt: &mut EventCore) {
- match evt.1 {
- // limitations: only two buttons may be held at once
- 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::Screen(ref s) => self.screen = Some(*s),
- EventData::Keyboard(ref mut k) => {
- let is_repeating = self
- .last_key_pressed
- .as_ref()
- // heuristic for guessing which presses are auto-repeating. not necessarily accurate
- .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 TuiDom,
- ) {
- 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 {
- // elements with listeners will always have a element id
- if let Some(id) = self.focus_state.last_focused_id {
- let element = dom.tree.get(id).unwrap();
- if let Some(id) = element.node_data.element_id {
- resolved_events.push(Event {
- name: "focus",
- id,
- data: Rc::new(FocusData {}),
- bubbles: event_bubbles("focus"),
- });
- resolved_events.push(Event {
- name: "focusin",
- id,
- data: Rc::new(FocusData {}),
- bubbles: event_bubbles("focusin"),
- });
- }
- }
- if let Some(id) = old_focus {
- let element = dom.tree.get(id).unwrap();
- if let Some(id) = element.node_data.element_id {
- resolved_events.push(Event {
- name: "focusout",
- id,
- data: Rc::new(FocusData {}),
- bubbles: event_bubbles("focusout"),
- });
- }
- }
- }
- // for s in &self.subscribers {
- // s();
- // }
- }
- fn resolve_mouse_events(
- &mut self,
- previous_mouse: Option<MouseData>,
- resolved_events: &mut Vec<Event>,
- layout: &Taffy,
- dom: &mut TuiDom,
- ) {
- 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: Rc<dyn Any>,
- will_bubble: &mut FxHashSet<NodeId>,
- resolved_events: &mut Vec<Event>,
- node: &TuiNode,
- dom: &TuiDom,
- ) {
- // only trigger event if the event was not triggered already by a child
- let id = node.node_data.node_id;
- if will_bubble.insert(id) {
- let mut parent = dom.parent(id);
- while let Some(current_parent) = parent {
- let parent_id = current_parent.node_data.node_id;
- will_bubble.insert(parent_id);
- parent = dom.parent(parent_id);
- }
- if let Some(id) = node.mounted_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());
- // a mouse button is pressed if a button was not down and is now down
- 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();
- // a mouse button is released if a button was down and is now not down
- 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;
- {
- // mousemove
- 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, dom, 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",
- Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- }
- {
- // mouseenter
- let mut will_bubble = FxHashSet::default();
- for node in dom.get_listening_sorted("mouseenter") {
- let node_layout = get_abs_layout(node, dom, 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",
- Rc::new(mouse_data.clone()),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- {
- // mouseover
- let mut will_bubble = FxHashSet::default();
- for node in dom.get_listening_sorted("mouseover") {
- let node_layout = get_abs_layout(node, dom, 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",
- Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- // mousedown
- if was_pressed {
- let mut will_bubble = FxHashSet::default();
- for node in dom.get_listening_sorted("mousedown") {
- let node_layout = get_abs_layout(node, dom, layout);
- let currently_contains = layout_contains_point(&node_layout, new_pos);
- if currently_contains {
- try_create_event(
- "mousedown",
- Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- {
- // mouseup
- if was_released {
- let mut will_bubble = FxHashSet::default();
- for node in dom.get_listening_sorted("mouseup") {
- let node_layout = get_abs_layout(node, dom, layout);
- let currently_contains = layout_contains_point(&node_layout, new_pos);
- if currently_contains {
- try_create_event(
- "mouseup",
- Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- }
- {
- // click
- 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, dom, layout);
- let currently_contains = layout_contains_point(&node_layout, new_pos);
- if currently_contains {
- try_create_event(
- "click",
- Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- }
- {
- // contextmenu
- 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, dom, layout);
- let currently_contains = layout_contains_point(&node_layout, new_pos);
- if currently_contains {
- try_create_event(
- "contextmenu",
- Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- }
- {
- // wheel
- 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, dom, layout);
- let currently_contains = layout_contains_point(&node_layout, new_pos);
- if currently_contains {
- try_create_event(
- "wheel",
- Rc::new(w.clone()),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- }
- }
- {
- // mouseleave
- let mut will_bubble = FxHashSet::default();
- for node in dom.get_listening_sorted("mouseleave") {
- let node_layout = get_abs_layout(node, dom, 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",
- Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- {
- // mouseout
- let mut will_bubble = FxHashSet::default();
- for node in dom.get_listening_sorted("mouseout") {
- let node_layout = get_abs_layout(node, dom, 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",
- Rc::new(prepare_mouse_data(mouse_data, &node_layout)),
- &mut will_bubble,
- resolved_events,
- node,
- dom,
- );
- }
- }
- }
- // update focus
- if was_released {
- let mut focus_id = None;
- dom.traverse_depth_first(|node| {
- let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
- let currently_contains = layout_contains_point(node_layout, new_pos);
- if currently_contains && node.state.focus.level.focusable() {
- focus_id = Some(node.node_data.node_id);
- }
- });
- if let Some(id) = focus_id {
- self.focus_state.set_focus(dom, id);
- }
- }
- }
- }
- // fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
- // self.subscribers.push(f)
- // }
- }
- fn get_abs_layout(node: &TuiNode, dom: &TuiDom, taffy: &Taffy) -> Layout {
- let mut node_layout = *taffy.layout(node.state.layout.node.unwrap()).unwrap();
- let mut current = node;
- while let Some(parent) = dom.parent(current.node_data.node_id) {
- current = parent;
- let parent_layout = taffy.layout(parent.state.layout.node.unwrap()).unwrap();
- node_layout.location.x += parent_layout.location.x;
- node_layout.location.y += parent_layout.location.y;
- }
- node_layout
- }
- pub struct RinkInputHandler {
- state: Rc<RefCell<InnerInputState>>,
- queued_events: Rc<RefCell<Vec<EventCore>>>,
- }
- impl RinkInputHandler {
- /// global context that handles events
- /// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once
- pub fn new() -> (
- Self,
- Rc<RefCell<InnerInputState>>,
- 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::new()));
- (
- Self {
- state: state.clone(),
- queued_events,
- },
- state,
- regester_event,
- )
- }
- pub(crate) fn prune(&self, mutations: &dioxus_core::Mutations, rdom: &TuiDom) {
- self.state.borrow_mut().focus_state.prune(mutations, rdom);
- }
- pub(crate) fn get_events(&self, layout: &Taffy, dom: &mut TuiDom) -> 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()
- // these events were added in the update stage
- .filter(|e| {
- ![
- "mouseenter",
- "mouseover",
- "mouseleave",
- "mouseout",
- "mousedown",
- "mouseup",
- "mousemove",
- "drag",
- "wheel",
- "click",
- "contextmenu",
- ]
- .contains(&e.0)
- })
- .map(|evt| (evt.0, evt.1.into_any()));
- let mut hm: FxHashMap<&'static str, Vec<Rc<dyn Any + Send + Sync>>> = 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 {
- if node.state.focused {
- if let Some(id) = node.mounted_id() {
- resolved_events.push(Event {
- name: event,
- id,
- data: data.clone(),
- bubbles: event_bubbles(event),
- });
- }
- }
- }
- }
- }
- resolved_events
- }
- pub(crate) fn state(&self) -> RefMut<InnerInputState> {
- self.state.borrow_mut()
- }
- }
- // translate crossterm events into dioxus events
- 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,
- });
- // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
- // The `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively.
- // todo?
- // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway.
- let coordinates = Coordinates::new(
- ScreenPoint::new(x, y),
- ClientPoint::new(x, y),
- // offset x/y are set when the origin of the event is assigned to an element
- 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);
- }
- // held mouse buttons get set later by maintaining state, as crossterm does not provide them
- 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)),
- }
- }
- TermEvent::Resize(x, y) => ("resize", EventData::Screen((x, y))),
- };
- Some((name, data))
- }
- fn translate_key_event(event: crossterm::event::KeyEvent) -> Option<EventData> {
- let key = key_from_crossterm_key_code(event.code);
- // crossterm does not provide code. we make a guess as to which key might have been pressed
- // this is probably garbage if the user has a custom keyboard layout
- 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,
- )))
- }
- /// The crossterm key_code nicely represents the meaning of the key and we can mostly convert it without any issues
- ///
- /// Exceptions:
- /// BackTab is converted to Key::Tab, and Null is converted to Key::Unidentified
- 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,
- // ? no corresponding Key
- 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,
- }
- }
- // Crossterm does not provide a way to get the `code` (physical key on keyboard)
- // So we make a guess based on their `key_code`, but this is probably going to break on anything other than a very standard european keyboard
- // It may look fine, but it's a horrible hack. But there's nothing better we can do.
- 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,
- // ? Apparently you get BackTab by pressing 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:?}")
- }
- // this is a horrible way for crossterm to represent keys but we have to deal with it
- 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,
- // numpad characters are ambiguous; we don't know which key was really pressed
- // it could be also:
- // '*' => Code::Multiply,
- // '/' => Code::Divide,
- // '-' => Code::Subtract,
- // '+' => Code::Add,
- '+' => 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::Null => return None,
- TermKeyCode::Esc => Code::Escape,
- };
- 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
- }
|