terminput_web_sys/
mapping.rs

1use terminput::{
2    Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, MediaKeyCode,
3    ModifierDirection, ModifierKeyCode, MouseButton, MouseEvent, MouseEventKind, ScrollDirection,
4    UnsupportedEvent,
5};
6use web_sys::wasm_bindgen::JsValue;
7#[cfg(feature = "web_sys_0_3")]
8use web_sys_0_3 as web_sys;
9
10/// Converts the [`web_sys`] [`MouseEvent`](web_sys::MouseEvent) to a terminput [`MouseEvent`].
11pub fn to_terminput_mouse(
12    mouse_event: web_sys::MouseEvent,
13) -> Result<MouseEvent, UnsupportedEvent> {
14    let event_kind = mouse_event.type_();
15    let mouse_button = to_terminput_mouse_button(mouse_event.button());
16    Ok(MouseEvent {
17        kind: to_terminput_mouse_kind(event_kind.as_str(), mouse_button)?,
18        modifiers: to_terminput_mouse_modifiers(&mouse_event),
19        column: mouse_event.client_x() as u16,
20        row: mouse_event.client_y() as u16,
21    })
22}
23
24/// Converts the [`web_sys`] [`DragEvent`](web_sys::DragEvent) to a terminput [`MouseEvent`].
25///
26/// **NOTE:** this should be used with the `dragover` event to ensure the row/column values are
27/// populated.
28pub fn to_terminput_mouse_drag(drag_event: web_sys::DragEvent) -> MouseEvent {
29    let mouse_button = to_terminput_mouse_button(drag_event.button());
30
31    MouseEvent {
32        kind: MouseEventKind::Drag(mouse_button),
33        modifiers: to_terminput_drag_modifiers(&drag_event),
34        column: drag_event.client_x() as u16,
35        row: drag_event.client_y() as u16,
36    }
37}
38
39/// Converts the [`web_sys`] [`WheelEvent`](`web_sys::WheelEvent`) to a terminput [`MouseEvent`].
40pub fn to_terminput_mouse_scroll(event: web_sys::WheelEvent) -> MouseEvent {
41    let direction = to_terminput_scroll_direction(&event);
42    MouseEvent {
43        kind: MouseEventKind::Scroll(direction),
44        modifiers: KeyModifiers::empty(),
45        column: event.client_x() as u16,
46        row: event.client_y() as u16,
47    }
48}
49
50/// Converts the [`web_sys`] [`KeyboardEvent`](`web_sys::KeyboardEvent`) to a terminput
51/// [`KeyEvent`].
52pub fn to_terminput_key(key_event: web_sys::KeyboardEvent) -> Result<KeyEvent, UnsupportedEvent> {
53    Ok(KeyEvent {
54        code: to_terminput_key_code(
55            &key_event.key(),
56            to_terminput_modifier_direction(key_event.location()),
57        )?,
58        modifiers: to_terminput_key_modifiers(&key_event),
59        state: key_state_to_terminput(&key_event),
60        kind: to_terminput_key_kind(&key_event),
61    })
62}
63
64/// Converts the [`web_sys`] [`ClipboardEvent`](`web_sys::ClipboardEvent`) to a terminput paste
65/// [`Event`].
66pub fn to_terminput_paste(
67    clipboard_event: web_sys::ClipboardEvent,
68) -> Result<Event, UnsupportedEvent> {
69    if let Some(data) = clipboard_event.clipboard_data() {
70        let content = data
71            .get_data("text")
72            .map_err(|e| map_js_error("failed to read clipboard data: ", e))?;
73        Ok(Event::Paste(content))
74    } else {
75        Err(UnsupportedEvent("no clipboard data available".to_string()))
76    }
77}
78
79/// Converts the [`web_sys`] window size to a terminput resize [`Event`].
80pub fn to_terminput_resize(window: &web_sys::Window) -> Result<Event, UnsupportedEvent> {
81    let height = window
82        .inner_height()
83        .map_err(|e| map_js_error("failed to read height: ", e))?;
84    let width = window
85        .inner_width()
86        .map_err(|e| map_js_error("failed to read width: ", e))?;
87    let height = height.as_f64().unwrap_or_default();
88    let width = width.as_f64().unwrap_or_default();
89    Ok(Event::Resize {
90        rows: height as u32,
91        cols: width as u32,
92    })
93}
94
95fn map_js_error(message: &str, error: JsValue) -> UnsupportedEvent {
96    UnsupportedEvent(message.to_string() + &error.as_string().unwrap_or_default())
97}
98
99fn to_terminput_scroll_direction(event: &web_sys::WheelEvent) -> ScrollDirection {
100    if event.delta_x() > 0.0 {
101        ScrollDirection::Right
102    } else if event.delta_x() < 0.0 {
103        ScrollDirection::Left
104    } else if event.delta_y() > 0.0 {
105        ScrollDirection::Down
106    } else {
107        ScrollDirection::Up
108    }
109}
110
111fn to_terminput_mouse_kind(
112    event_kind: &str,
113    mouse_button: MouseButton,
114) -> Result<MouseEventKind, UnsupportedEvent> {
115    Ok(match event_kind {
116        "mousemove" => MouseEventKind::Moved,
117        "mousedown" => MouseEventKind::Down(mouse_button),
118        "mouseup" => MouseEventKind::Up(mouse_button),
119        kind => return Err(UnsupportedEvent(kind.to_string())),
120    })
121}
122
123fn to_terminput_mouse_button(button: i16) -> MouseButton {
124    match button {
125        0 => MouseButton::Left,
126        1 => MouseButton::Middle,
127        2 => MouseButton::Right,
128        _ => MouseButton::Unknown,
129    }
130}
131
132fn to_terminput_mouse_modifiers(event: &web_sys::MouseEvent) -> KeyModifiers {
133    let mut modifiers = KeyModifiers::empty();
134    if event.ctrl_key() {
135        modifiers |= KeyModifiers::CTRL;
136    }
137    if event.shift_key() {
138        modifiers |= KeyModifiers::SHIFT;
139    }
140    if event.alt_key() {
141        modifiers |= KeyModifiers::ALT;
142    }
143    if event.meta_key() {
144        modifiers |= KeyModifiers::META;
145    }
146
147    modifiers
148}
149
150fn to_terminput_drag_modifiers(event: &web_sys::DragEvent) -> KeyModifiers {
151    let mut modifiers = KeyModifiers::empty();
152    if event.ctrl_key() {
153        modifiers |= KeyModifiers::CTRL;
154    }
155    if event.shift_key() {
156        modifiers |= KeyModifiers::SHIFT;
157    }
158    if event.alt_key() {
159        modifiers |= KeyModifiers::ALT;
160    }
161    if event.meta_key() {
162        modifiers |= KeyModifiers::META;
163    }
164
165    modifiers
166}
167
168fn to_terminput_key_kind(key_event: &web_sys::KeyboardEvent) -> KeyEventKind {
169    if key_event.repeat() {
170        KeyEventKind::Repeat
171    } else if key_event.type_() == "keyup" {
172        KeyEventKind::Release
173    } else {
174        KeyEventKind::Press
175    }
176}
177
178fn to_terminput_key_modifiers(event: &web_sys::KeyboardEvent) -> KeyModifiers {
179    let mut modifiers = KeyModifiers::empty();
180    if event.ctrl_key() {
181        modifiers |= KeyModifiers::CTRL;
182    }
183    if event.shift_key() {
184        modifiers |= KeyModifiers::SHIFT;
185    }
186    if event.alt_key() {
187        modifiers |= KeyModifiers::ALT;
188    }
189    if event.meta_key() {
190        modifiers |= KeyModifiers::META;
191    }
192
193    modifiers
194}
195
196fn to_terminput_modifier_direction(location: u32) -> ModifierDirection {
197    match location {
198        1 => ModifierDirection::Left,
199        2 => ModifierDirection::Right,
200        _ => ModifierDirection::Unknown,
201    }
202}
203
204fn key_state_to_terminput(event: &web_sys::KeyboardEvent) -> KeyEventState {
205    let mut state = KeyEventState::empty();
206    if event.location() == 3 {
207        state |= KeyEventState::KEYPAD;
208    }
209
210    if event.get_modifier_state("CapsLock") {
211        state |= KeyEventState::CAPS_LOCK;
212    }
213    if event.get_modifier_state("NumLock") {
214        state |= KeyEventState::NUM_LOCK;
215    }
216
217    state
218}
219
220fn to_terminput_key_code(
221    key: &str,
222    direction: ModifierDirection,
223) -> Result<KeyCode, UnsupportedEvent> {
224    let key = key.to_ascii_lowercase();
225    if key.len() == 1 {
226        let key_char = key.chars().next().expect("length checked");
227        if key_char.is_alphanumeric() {
228            return Ok(KeyCode::Char(key_char));
229        }
230    }
231    Ok(match key.as_str() {
232        "f1" => KeyCode::F(1),
233        "f2" => KeyCode::F(2),
234        "f3" => KeyCode::F(3),
235        "f4" => KeyCode::F(4),
236        "f5" => KeyCode::F(5),
237        "f6" => KeyCode::F(6),
238        "f7" => KeyCode::F(7),
239        "f8" => KeyCode::F(8),
240        "f9" => KeyCode::F(9),
241        "f10" => KeyCode::F(10),
242        "f11" => KeyCode::F(11),
243        "f12" => KeyCode::F(12),
244        "backspace" => KeyCode::Backspace,
245        "enter" => KeyCode::Enter,
246        "arrowleft" => KeyCode::Left,
247        "arrowright" => KeyCode::Right,
248        "arrowup" => KeyCode::Up,
249        "arrowdown" => KeyCode::Down,
250        "tab" => KeyCode::Tab,
251        "delete" => KeyCode::Delete,
252        "home" => KeyCode::Home,
253        "end" => KeyCode::End,
254        "pageup" => KeyCode::PageUp,
255        "pagedown" => KeyCode::PageDown,
256        "capslock" => KeyCode::CapsLock,
257        "scrolllock" => KeyCode::ScrollLock,
258        "numlock" => KeyCode::NumLock,
259        "printscreen" => KeyCode::PrintScreen,
260        "alt" => KeyCode::Modifier(ModifierKeyCode::Alt, direction),
261        "control" => KeyCode::Modifier(ModifierKeyCode::Control, direction),
262        "hyper" => KeyCode::Modifier(ModifierKeyCode::Hyper, direction),
263        "meta" => KeyCode::Modifier(ModifierKeyCode::Meta, direction),
264        "super" => KeyCode::Modifier(ModifierKeyCode::Super, direction),
265        "shift" => KeyCode::Modifier(ModifierKeyCode::Shift, direction),
266        "mediaplay" => KeyCode::Media(MediaKeyCode::Play),
267        "mediapause" => KeyCode::Media(MediaKeyCode::Pause),
268        "mediaplaypause" => KeyCode::Media(MediaKeyCode::PlayPause),
269        "mediastop" => KeyCode::Media(MediaKeyCode::Stop),
270        "mediatracknext" => KeyCode::Media(MediaKeyCode::TrackNext),
271        "mediatrackprevious" => KeyCode::Media(MediaKeyCode::TrackPrevious),
272        "mediafastforward" => KeyCode::Media(MediaKeyCode::FastForward),
273        "mediarewind" => KeyCode::Media(MediaKeyCode::Rewind),
274        "mediarecord" => KeyCode::Media(MediaKeyCode::Record),
275        key => return Err(UnsupportedEvent(key.to_string())),
276    })
277}