[web/glium] better text input filtering

CapsLock and function keys (F1, F2, ...) are now ignored on web.

All special keys are ignored on glium.
This commit is contained in:
Emil Ernerfeldt 2020-07-30 11:54:42 +02:00
parent 554e6e7120
commit f693a558c5
4 changed files with 60 additions and 39 deletions

View file

@ -155,7 +155,8 @@ impl Default for MouseInput {
pub enum Event { pub enum Event {
Copy, Copy,
Cut, Cut,
/// Text input, e.g. via keyboard or paste action /// Text input, e.g. via keyboard or paste action.
/// Do not pass '\n', '\r' here, but send `Key::Enter` instead.
Text(String), Text(String),
Key { Key {
key: Key, key: Key,
@ -179,7 +180,8 @@ pub enum Key {
Logo, Logo,
PageDown, PageDown,
PageUp, PageUp,
Return, /// Enter/Return key
Enter,
Right, Right,
Shift, Shift,
// Space, // Space,

View file

@ -97,7 +97,18 @@ impl<'t> Widget for TextEdit<'t> {
ui.ctx().output().copied_text = text.clone(); ui.ctx().output().copied_text = text.clone();
} }
Event::Text(text_to_insert) => { Event::Text(text_to_insert) => {
insert_text(&mut cursor, text, text_to_insert); // newlines are handled by `Key::Enter`.
if text_to_insert != "\n" && text_to_insert != "\r" {
insert_text(&mut cursor, text, text_to_insert);
}
}
Event::Key {
key: Key::Enter,
pressed: true,
} => {
if multiline {
insert_text(&mut cursor, text, "\n");
}
} }
Event::Key { key, pressed: true } => { Event::Key { key, pressed: true } => {
on_key_press(&mut cursor, text, *key); on_key_press(&mut cursor, text, *key);
@ -190,6 +201,7 @@ fn on_key_press(cursor: &mut usize, text: &mut String, key: Key) {
new_text.extend(char_it.skip(1)); new_text.extend(char_it.skip(1));
*text = new_text; *text = new_text;
} }
Key::Enter => {} // handled earlier
Key::Home => { Key::Home => {
// To start of paragraph: // To start of paragraph:
let pos = line_col_from_char_idx(text, *cursor); let pos = line_col_from_char_idx(text, *cursor);

View file

@ -57,12 +57,8 @@ pub fn input_to_egui(
raw_input.mouse_pos = None; raw_input.mouse_pos = None;
} }
ReceivedCharacter(ch) => { ReceivedCharacter(ch) => {
if !should_ignore_char(ch) { if printable_char(ch) {
if ch == '\r' { raw_input.events.push(Event::Text(ch.to_string()));
raw_input.events.push(Event::Text("\n".to_owned()));
} else {
raw_input.events.push(Event::Text(ch.to_string()));
}
} }
} }
KeyboardInput { input, .. } => { KeyboardInput { input, .. } => {
@ -116,24 +112,16 @@ pub fn input_to_egui(
} }
} }
fn should_ignore_char(chr: char) -> bool { /// Glium sends special keys (backspace, delete, F1, ...) as characters.
// Glium sends some keys as chars: /// Ignore those.
match chr { /// We also ignore '\r', '\n', '\t'.
'\u{7f}' | // backspace /// Newlines are handled by the `Key::Enter` event.
'\u{f728}' | // delete fn printable_char(chr: char) -> bool {
'\u{f700}' | // up let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}'
'\u{f701}' | // down || '\u{f0000}' <= chr && chr <= '\u{ffffd}'
'\u{f702}' | // left || '\u{100000}' <= chr && chr <= '\u{10fffd}';
'\u{f703}' | // right
'\u{f729}' | // home !is_in_private_use_area && !chr.is_ascii_control()
'\u{f72b}' | // end
'\u{f72c}' | // page up
'\u{f72d}' | // page down
'\u{f710}' | // print screen
'\u{f704}' | '\u{f705}' // F1, F2, ...
=> true,
_ => false,
}
} }
pub fn translate_virtual_key_code(key: VirtualKeyCode) -> Option<egui::Key> { pub fn translate_virtual_key_code(key: VirtualKeyCode) -> Option<egui::Key> {
@ -152,7 +140,7 @@ pub fn translate_virtual_key_code(key: VirtualKeyCode) -> Option<egui::Key> {
Right => Key::Right, Right => Key::Right,
Down => Key::Down, Down => Key::Down,
Back => Key::Backspace, Back => Key::Backspace,
Return => Key::Return, Return => Key::Enter,
// Space => Key::Space, // Space => Key::Space,
Tab => Key::Tab, Tab => Key::Tab,

View file

@ -184,6 +184,19 @@ pub fn location_hash() -> Option<String> {
web_sys::window()?.location().hash().ok() web_sys::window()?.location().hash().ok()
} }
/// Web sends all all keys as strings, so it is up to us to figure out if it is
/// a real text input or the name of a key.
fn should_ignore_key(key: &str) -> bool {
let is_function_key = key.starts_with("F") && key.len() > 1;
is_function_key
|| matches!(
key,
"CapsLock" | "ContextMenu" | "NumLock" | "Pause" | "ScrollLock"
)
}
/// Web sends all all keys as strings, so it is up to us to figure out if it is
/// a real text input or the name of a key.
pub fn translate_key(key: &str) -> Option<egui::Key> { pub fn translate_key(key: &str) -> Option<egui::Key> {
match key { match key {
"Alt" => Some(egui::Key::Alt), "Alt" => Some(egui::Key::Alt),
@ -192,14 +205,14 @@ pub fn translate_key(key: &str) -> Option<egui::Key> {
"Delete" => Some(egui::Key::Delete), "Delete" => Some(egui::Key::Delete),
"ArrowDown" => Some(egui::Key::Down), "ArrowDown" => Some(egui::Key::Down),
"End" => Some(egui::Key::End), "End" => Some(egui::Key::End),
"Escape" => Some(egui::Key::Escape), "Esc" | "Escape" => Some(egui::Key::Escape),
"Home" => Some(egui::Key::Home), "Home" => Some(egui::Key::Home),
"Help" => Some(egui::Key::Insert), "Help" | "Insert" => Some(egui::Key::Insert),
"ArrowLeft" => Some(egui::Key::Left), "ArrowLeft" => Some(egui::Key::Left),
"Meta" => Some(egui::Key::Logo), "Meta" => Some(egui::Key::Logo),
"PageDown" => Some(egui::Key::PageDown), "PageDown" => Some(egui::Key::PageDown),
"PageUp" => Some(egui::Key::PageUp), "PageUp" => Some(egui::Key::PageUp),
"Enter" => Some(egui::Key::Return), "Enter" => Some(egui::Key::Enter),
"ArrowRight" => Some(egui::Key::Right), "ArrowRight" => Some(egui::Key::Right),
"Shift" => Some(egui::Key::Shift), "Shift" => Some(egui::Key::Shift),
"Tab" => Some(egui::Key::Tab), "Tab" => Some(egui::Key::Tab),
@ -247,17 +260,23 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
// keydown // keydown
let runner_ref = runner_ref.clone(); let runner_ref = runner_ref.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| { let closure = Closure::wrap(Box::new(move |event: web_sys::KeyboardEvent| {
if event.is_composing() || event.key_code() == 229 {
// https://www.fxsitecompat.dev/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/
return;
}
let mut runner_lock = runner_ref.0.lock(); let mut runner_lock = runner_ref.0.lock();
let key = event.key(); let key = event.key();
if let Some(key) = translate_key(&key) { if !should_ignore_key(&key) {
runner_lock if let Some(key) = translate_key(&key) {
.web_input runner_lock
.events .web_input
.push(egui::Event::Key { key, pressed: true }); .events
} else { .push(egui::Event::Key { key, pressed: true });
runner_lock.web_input.events.push(egui::Event::Text(key)); } else {
runner_lock.web_input.events.push(egui::Event::Text(key));
}
runner_lock.needs_repaint = true;
} }
runner_lock.needs_repaint = true;
}) as Box<dyn FnMut(_)>); }) as Box<dyn FnMut(_)>);
document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?; document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?;
closure.forget(); closure.forget();