Skip to content

Commit 7806d28

Browse files
committed
Break apart the web lib.rs into various files
This separates all of the console implementation out of the web's lib.rs file and moves the xterm.js-related code to a new xterm.js and the input handling code to a new input.js. Keeps the main file more readable and will make it easier to swap out the console implementation for a new one.
1 parent cc1b0c1 commit 7806d28

3 files changed

Lines changed: 315 additions & 237 deletions

File tree

web/src/input.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// EndBASIC
2+
// Copyright 2021 Julio Merino
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
// use this file except in compliance with the License. You may obtain a copy
6+
// of the License at:
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
// License for the specific language governing permissions and limitations
14+
// under the License.
15+
16+
//! Keyboard input tools for the web UI.
17+
18+
use async_channel::{self, Receiver, Sender, TryRecvError};
19+
use endbasic_std::console::Key;
20+
use std::io;
21+
use wasm_bindgen::prelude::*;
22+
use xterm_js_rs::OnKeyEvent;
23+
24+
/// Converts an xterm.js key event into our own `Key` representation.
25+
fn on_key_event_into_key(event: OnKeyEvent) -> Key {
26+
let dom_event = event.dom_event();
27+
match dom_event.key_code() as u8 {
28+
8 => Key::Backspace,
29+
9 => Key::Tab,
30+
10 => Key::NewLine,
31+
13 => Key::CarriageReturn,
32+
27 => Key::Escape,
33+
35 => Key::End,
34+
36 => Key::Home,
35+
37 => Key::ArrowLeft,
36+
38 => Key::ArrowUp,
37+
39 => Key::ArrowRight,
38+
40 => Key::ArrowDown,
39+
b'A' if dom_event.ctrl_key() => Key::Home,
40+
b'B' if dom_event.ctrl_key() => Key::ArrowLeft,
41+
b'C' if dom_event.ctrl_key() => Key::Interrupt,
42+
b'D' if dom_event.ctrl_key() => Key::Eof,
43+
b'E' if dom_event.ctrl_key() => Key::End,
44+
b'F' if dom_event.ctrl_key() => Key::ArrowRight,
45+
b'J' if dom_event.ctrl_key() => Key::NewLine,
46+
b'M' if dom_event.ctrl_key() => Key::NewLine,
47+
b'N' if dom_event.ctrl_key() => Key::ArrowDown,
48+
b'P' if dom_event.ctrl_key() => Key::ArrowUp,
49+
_ => {
50+
let printable = !dom_event.alt_key() && !dom_event.ctrl_key() && !dom_event.meta_key();
51+
let chars = event.key().chars().collect::<Vec<char>>();
52+
if printable && chars.len() == 1 {
53+
Key::Char(chars[0])
54+
} else {
55+
Key::Unknown(format!("<keycode={}>", dom_event.key_code()))
56+
}
57+
}
58+
}
59+
}
60+
61+
/// Interface to implement an on-screen keyboard to provide keys that may not be available on
62+
/// mobile keyboards.
63+
#[wasm_bindgen]
64+
pub struct OnScreenKeyboard {
65+
on_key_tx: Sender<Key>,
66+
}
67+
68+
#[wasm_bindgen]
69+
impl OnScreenKeyboard {
70+
/// Generates a fake Escape key press.
71+
pub fn press_escape(&self) {
72+
self.on_key_tx.try_send(Key::Escape).expect("Send to unbounded channel must succeed")
73+
}
74+
75+
/// Generates a fake arrow up key press.
76+
pub fn press_arrow_up(&self) {
77+
self.on_key_tx.try_send(Key::ArrowUp).expect("Send to unbounded channel must succeed")
78+
}
79+
80+
/// Generates a fake arrow down key press.
81+
pub fn press_arrow_down(&self) {
82+
self.on_key_tx.try_send(Key::ArrowDown).expect("Send to unbounded channel must succeed")
83+
}
84+
85+
/// Generates a fake arrow left key press.
86+
pub fn press_arrow_left(&self) {
87+
self.on_key_tx.try_send(Key::ArrowLeft).expect("Send to unbounded channel must succeed")
88+
}
89+
90+
/// Generates a fake arrow up key press.
91+
pub fn press_arrow_right(&self) {
92+
self.on_key_tx.try_send(Key::ArrowRight).expect("Send to unbounded channel must succeed")
93+
}
94+
}
95+
96+
/// Interface to interact with the browser's input, be it via a real keyboard or our custom
97+
/// on-screen keyboard.
98+
pub struct WebInput {
99+
on_key_rx: Receiver<Key>,
100+
on_key_tx: Sender<Key>,
101+
}
102+
103+
impl Default for WebInput {
104+
fn default() -> Self {
105+
let (on_key_tx, on_key_rx) = async_channel::unbounded();
106+
Self { on_key_rx, on_key_tx }
107+
}
108+
}
109+
110+
impl WebInput {
111+
/// Generates a closure that xterm.js can use to handle key events.
112+
pub(crate) fn terminal_on_key(&self) -> Closure<dyn FnMut(OnKeyEvent)> {
113+
let on_key_tx = self.on_key_tx.clone();
114+
Closure::wrap(Box::new(move |e| {
115+
on_key_tx
116+
.try_send(on_key_event_into_key(e))
117+
.expect("Send to unbounded channel must succeed")
118+
}) as Box<dyn FnMut(OnKeyEvent)>)
119+
}
120+
121+
/// Generates a new `OnScreenKeyboard` that can inject key events.
122+
pub(crate) fn on_screen_keyboard(&self) -> OnScreenKeyboard {
123+
OnScreenKeyboard { on_key_tx: self.on_key_tx.clone() }
124+
}
125+
126+
/// Gets the next key event, if one is available.
127+
pub(crate) async fn try_recv(&mut self) -> io::Result<Option<Key>> {
128+
match self.on_key_rx.try_recv() {
129+
Ok(k) => Ok(Some(k)),
130+
Err(TryRecvError::Empty) => Ok(None),
131+
Err(TryRecvError::Closed) => panic!("Channel unexpectedly closed"),
132+
}
133+
}
134+
135+
/// Gets the next key event, waiting until one is available.
136+
pub(crate) async fn recv(&mut self) -> io::Result<Key> {
137+
Ok(self.on_key_rx.recv().await.unwrap())
138+
}
139+
}

0 commit comments

Comments
 (0)