From 20bf09560e530590a47999e3bd5adda782e0b33e Mon Sep 17 00:00:00 2001 From: Lin Han Date: Tue, 13 Apr 2021 02:57:14 +0800 Subject: [PATCH] IME: Handle composition events to show suggestion on web (#278) * Handle composition message to show suggestion. * CI check * Apply suggestions from code review Co-authored-by: Emil Ernerfeldt * Some minor changes Co-authored-by: Emil Ernerfeldt --- egui/src/data/input.rs | 7 +++++++ egui/src/widgets/text_edit.rs | 39 +++++++++++++++++++++++++++++++++++ egui_web/src/lib.rs | 21 ++++++++++++------- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/egui/src/data/input.rs b/egui/src/data/input.rs index d6d08eed..ae6d55da 100644 --- a/egui/src/data/input.rs +++ b/egui/src/data/input.rs @@ -116,6 +116,13 @@ pub enum Event { /// /// On touch-up first send `PointerButton{pressed: false, …}` followed by `PointerLeft`. PointerGone, + + /// IME composition start. + CompositionStart, + /// A new IME candidate is being suggested. + CompositionUpdate(String), + /// IME composition ended with this final result. + CompositionEnd(String), } /// Mouse button (or similar for touch input) diff --git a/egui/src/widgets/text_edit.rs b/egui/src/widgets/text_edit.rs index 85ba62f4..5d322578 100644 --- a/egui/src/widgets/text_edit.rs +++ b/egui/src/widgets/text_edit.rs @@ -9,6 +9,10 @@ pub(crate) struct State { #[cfg_attr(feature = "persistence", serde(skip))] undoer: Undoer<(CCursorPair, String)>, + + // If IME candidate window is shown on this text edit. + #[cfg_attr(feature = "persistence", serde(skip))] + has_ime: bool, } #[derive(Clone, Copy, Debug, Default)] @@ -503,6 +507,41 @@ impl<'t> TextEdit<'t> { modifiers, } => on_key_press(&mut cursorp, text, &galley, *key, modifiers), + Event::CompositionStart => { + state.has_ime = true; + None + } + + Event::CompositionUpdate(text_mark) => { + if !text_mark.is_empty() + && text_mark != "\n" + && text_mark != "\r" + && state.has_ime + { + let mut ccursor = delete_selected(text, &cursorp); + let start_cursor = ccursor; + insert_text(&mut ccursor, text, text_mark); + Some(CCursorPair::two(start_cursor, ccursor)) + } else { + None + } + } + + Event::CompositionEnd(prediction) => { + if !prediction.is_empty() + && prediction != "\n" + && prediction != "\r" + && state.has_ime + { + state.has_ime = false; + let mut ccursor = delete_selected(text, &cursorp); + insert_text(&mut ccursor, text, prediction); + Some(CCursorPair::one(ccursor)) + } else { + None + } + } + _ => None, }; diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index a11021ad..78629c61 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -708,22 +708,27 @@ fn install_text_agent(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let input_clone = input.clone(); let runner_ref = runner_ref.clone(); let on_compositionend = Closure::wrap(Box::new(move |event: web_sys::CompositionEvent| { - match event.type_().as_ref() { + let mut runner_lock = runner_ref.0.lock(); + let opt_event = match event.type_().as_ref() { "compositionstart" => { is_composing.set(true); input_clone.set_value(""); + Some(egui::Event::CompositionStart) } "compositionend" => { is_composing.set(false); input_clone.set_value(""); - if let Some(text) = event.data() { - let mut runner_lock = runner_ref.0.lock(); - runner_lock.input.raw.events.push(egui::Event::Text(text)); - runner_lock.needs_repaint.set_true(); - } + event.data().map(egui::Event::CompositionEnd) } - "compositionupdate" => {} - _s => panic!("Unknown type"), + "compositionupdate" => event.data().map(egui::Event::CompositionUpdate), + s => { + console_error(format!("Unknown composition event type: {:?}", s)); + None + } + }; + if let Some(event) = opt_event { + runner_lock.input.raw.events.push(event); + runner_lock.needs_repaint.set_true(); } }) as Box); let f = on_compositionend.as_ref().unchecked_ref();