From f693a558c5990c22fb983b5f6fa20a58aff78f69 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 30 Jul 2020 11:54:42 +0200 Subject: [PATCH] [web/glium] better text input filtering CapsLock and function keys (F1, F2, ...) are now ignored on web. All special keys are ignored on glium. --- egui/src/input.rs | 6 +++-- egui/src/widgets/text_edit.rs | 14 +++++++++++- egui_glium/src/lib.rs | 38 +++++++++++--------------------- egui_web/src/lib.rs | 41 +++++++++++++++++++++++++---------- 4 files changed, 60 insertions(+), 39 deletions(-) diff --git a/egui/src/input.rs b/egui/src/input.rs index c05ff7f7..e541251b 100644 --- a/egui/src/input.rs +++ b/egui/src/input.rs @@ -155,7 +155,8 @@ impl Default for MouseInput { pub enum Event { Copy, 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), Key { key: Key, @@ -179,7 +180,8 @@ pub enum Key { Logo, PageDown, PageUp, - Return, + /// Enter/Return key + Enter, Right, Shift, // Space, diff --git a/egui/src/widgets/text_edit.rs b/egui/src/widgets/text_edit.rs index e27d5663..39eadb52 100644 --- a/egui/src/widgets/text_edit.rs +++ b/egui/src/widgets/text_edit.rs @@ -97,7 +97,18 @@ impl<'t> Widget for TextEdit<'t> { ui.ctx().output().copied_text = text.clone(); } 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 } => { 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)); *text = new_text; } + Key::Enter => {} // handled earlier Key::Home => { // To start of paragraph: let pos = line_col_from_char_idx(text, *cursor); diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 371a9d56..435eb601 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -57,12 +57,8 @@ pub fn input_to_egui( raw_input.mouse_pos = None; } ReceivedCharacter(ch) => { - if !should_ignore_char(ch) { - if ch == '\r' { - raw_input.events.push(Event::Text("\n".to_owned())); - } else { - raw_input.events.push(Event::Text(ch.to_string())); - } + if printable_char(ch) { + raw_input.events.push(Event::Text(ch.to_string())); } } KeyboardInput { input, .. } => { @@ -116,24 +112,16 @@ pub fn input_to_egui( } } -fn should_ignore_char(chr: char) -> bool { - // Glium sends some keys as chars: - match chr { - '\u{7f}' | // backspace - '\u{f728}' | // delete - '\u{f700}' | // up - '\u{f701}' | // down - '\u{f702}' | // left - '\u{f703}' | // right - '\u{f729}' | // home - '\u{f72b}' | // end - '\u{f72c}' | // page up - '\u{f72d}' | // page down - '\u{f710}' | // print screen - '\u{f704}' | '\u{f705}' // F1, F2, ... - => true, - _ => false, - } +/// Glium sends special keys (backspace, delete, F1, ...) as characters. +/// Ignore those. +/// We also ignore '\r', '\n', '\t'. +/// Newlines are handled by the `Key::Enter` event. +fn printable_char(chr: char) -> bool { + let is_in_private_use_area = '\u{e000}' <= chr && chr <= '\u{f8ff}' + || '\u{f0000}' <= chr && chr <= '\u{ffffd}' + || '\u{100000}' <= chr && chr <= '\u{10fffd}'; + + !is_in_private_use_area && !chr.is_ascii_control() } pub fn translate_virtual_key_code(key: VirtualKeyCode) -> Option { @@ -152,7 +140,7 @@ pub fn translate_virtual_key_code(key: VirtualKeyCode) -> Option { Right => Key::Right, Down => Key::Down, Back => Key::Backspace, - Return => Key::Return, + Return => Key::Enter, // Space => Key::Space, Tab => Key::Tab, diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 5485b43b..6c49bf08 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -184,6 +184,19 @@ pub fn location_hash() -> Option { 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 { match key { "Alt" => Some(egui::Key::Alt), @@ -192,14 +205,14 @@ pub fn translate_key(key: &str) -> Option { "Delete" => Some(egui::Key::Delete), "ArrowDown" => Some(egui::Key::Down), "End" => Some(egui::Key::End), - "Escape" => Some(egui::Key::Escape), + "Esc" | "Escape" => Some(egui::Key::Escape), "Home" => Some(egui::Key::Home), - "Help" => Some(egui::Key::Insert), + "Help" | "Insert" => Some(egui::Key::Insert), "ArrowLeft" => Some(egui::Key::Left), "Meta" => Some(egui::Key::Logo), "PageDown" => Some(egui::Key::PageDown), "PageUp" => Some(egui::Key::PageUp), - "Enter" => Some(egui::Key::Return), + "Enter" => Some(egui::Key::Enter), "ArrowRight" => Some(egui::Key::Right), "Shift" => Some(egui::Key::Shift), "Tab" => Some(egui::Key::Tab), @@ -247,17 +260,23 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { // keydown let runner_ref = runner_ref.clone(); 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 key = event.key(); - if let Some(key) = translate_key(&key) { - runner_lock - .web_input - .events - .push(egui::Event::Key { key, pressed: true }); - } else { - runner_lock.web_input.events.push(egui::Event::Text(key)); + if !should_ignore_key(&key) { + if let Some(key) = translate_key(&key) { + runner_lock + .web_input + .events + .push(egui::Event::Key { key, pressed: true }); + } else { + runner_lock.web_input.events.push(egui::Event::Text(key)); + } + runner_lock.needs_repaint = true; } - runner_lock.needs_repaint = true; }) as Box); document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?; closure.forget();