From 720bc725b0c1cd5f01f9ff1d17793a3b1f32a480 Mon Sep 17 00:00:00 2001 From: DK Liao Date: Wed, 19 Feb 2025 20:22:57 +0900 Subject: [PATCH] feat: support pre-edit text display for IME (#35535) * feat: support pre-edit text display for IME Signed-off-by: DK Liao * enable ime by show_ime Signed-off-by: DK Liao --------- Signed-off-by: DK Liao --- components/script/dom/htmlinputelement.rs | 8 +++- components/script/dom/htmltextareaelement.rs | 8 +++- components/script/textinput.rs | 11 +++++ ports/servoshell/desktop/app_state.rs | 16 ++++++++ ports/servoshell/desktop/headed_window.rs | 42 ++++++++++++++++++++ ports/servoshell/desktop/window_trait.rs | 10 +++++ 6 files changed, 91 insertions(+), 4 deletions(-) diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 6402a3b623c..284207ae214 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -2652,8 +2652,6 @@ impl VirtualMethods for HTMLInputElement { event.type_() == atom!("compositionend")) && self.input_type().is_textual_or_password() { - // TODO: Update DOM on start and continue - // and generally do proper CompositionEvent handling. if let Some(compositionevent) = event.downcast::() { if event.type_() == atom!("compositionend") { let _ = self @@ -2661,6 +2659,12 @@ impl VirtualMethods for HTMLInputElement { .borrow_mut() .handle_compositionend(compositionevent); self.upcast::().dirty(NodeDamage::OtherNodeDamage); + } else if event.type_() == atom!("compositionupdate") { + let _ = self + .textinput + .borrow_mut() + .handle_compositionupdate(compositionevent); + self.upcast::().dirty(NodeDamage::OtherNodeDamage); } event.mark_as_handled(); } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index c18f245eb87..91a8cde54ff 100644 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -668,8 +668,6 @@ impl VirtualMethods for HTMLTextAreaElement { event.type_() == atom!("compositionupdate") || event.type_() == atom!("compositionend") { - // TODO: Update DOM on start and continue - // and generally do proper CompositionEvent handling. if let Some(compositionevent) = event.downcast::() { if event.type_() == atom!("compositionend") { let _ = self @@ -677,6 +675,12 @@ impl VirtualMethods for HTMLTextAreaElement { .borrow_mut() .handle_compositionend(compositionevent); self.upcast::().dirty(NodeDamage::OtherNodeDamage); + } else if event.type_() == atom!("compositionupdate") { + let _ = self + .textinput + .borrow_mut() + .handle_compositionupdate(compositionevent); + self.upcast::().dirty(NodeDamage::OtherNodeDamage); } event.mark_as_handled(); } diff --git a/components/script/textinput.rs b/components/script/textinput.rs index 4406984a34f..1a72c0d26f8 100644 --- a/components/script/textinput.rs +++ b/components/script/textinput.rs @@ -988,6 +988,17 @@ impl TextInput { KeyReaction::DispatchInput } + pub(crate) fn handle_compositionupdate(&mut self, event: &CompositionEvent) -> KeyReaction { + let start = self.selection_start_offset().0; + self.insert_string(event.data()); + self.set_selection_range( + start as u32, + (start + event.data().len_utf8().0) as u32, + SelectionDirection::Forward, + ); + KeyReaction::DispatchInput + } + /// Whether the content is empty. pub(crate) fn is_empty(&self) -> bool { self.lines.len() <= 1 && self.lines.first().map_or(true, |line| line.is_empty()) diff --git a/ports/servoshell/desktop/app_state.rs b/ports/servoshell/desktop/app_state.rs index 9ef17477c95..1d8ffa578c1 100644 --- a/ports/servoshell/desktop/app_state.rs +++ b/ports/servoshell/desktop/app_state.rs @@ -527,6 +527,22 @@ impl WebViewDelegate for RunningAppState { }; let _ = haptic_stop_sender.send(stopped); } + fn show_ime( + &self, + _webview: WebView, + input_type: servo::InputMethodType, + text: Option<(String, i32)>, + multiline: bool, + position: servo::webrender_api::units::DeviceIntRect, + ) { + self.inner() + .window + .show_ime(input_type, text, multiline, position); + } + + fn hide_ime(&self, _webview: WebView) { + self.inner().window.hide_ime(); + } } #[cfg(target_os = "linux")] diff --git a/ports/servoshell/desktop/headed_window.rs b/ports/servoshell/desktop/headed_window.rs index 68dfc519888..e7f5026309b 100644 --- a/ports/servoshell/desktop/headed_window.rs +++ b/ports/servoshell/desktop/headed_window.rs @@ -631,6 +631,35 @@ impl WindowPortsMethods for Window { WindowEvent::Moved(_new_position) => { webview.notify_embedder_window_moved(); }, + winit::event::WindowEvent::Ime(ime) => match ime { + winit::event::Ime::Enabled => { + webview.notify_input_event(InputEvent::Ime(servo::ImeEvent::Composition( + servo::CompositionEvent { + state: servo::CompositionState::Start, + data: String::new(), + }, + ))); + }, + winit::event::Ime::Preedit(text, _) => { + webview.notify_input_event(InputEvent::Ime(servo::ImeEvent::Composition( + servo::CompositionEvent { + state: servo::CompositionState::Update, + data: text, + }, + ))); + }, + winit::event::Ime::Commit(text) => { + webview.notify_input_event(InputEvent::Ime(servo::ImeEvent::Composition( + servo::CompositionEvent { + state: servo::CompositionState::End, + data: text, + }, + ))); + }, + winit::event::Ime::Disabled => { + webview.notify_input_event(InputEvent::Ime(servo::ImeEvent::Dismissed)); + }, + }, _ => {}, } } @@ -673,6 +702,19 @@ impl WindowPortsMethods for Window { fn rendering_context(&self) -> Rc { self.rendering_context.clone() } + fn show_ime( + &self, + _input_type: servo::InputMethodType, + _text: Option<(String, i32)>, + _multiline: bool, + _position: servo::webrender_api::units::DeviceIntRect, + ) { + self.winit_window.set_ime_allowed(true); + } + + fn hide_ime(&self) { + self.winit_window.set_ime_allowed(false); + } } impl WindowMethods for Window { diff --git a/ports/servoshell/desktop/window_trait.rs b/ports/servoshell/desktop/window_trait.rs index 915cd502eb4..4c7b794412a 100644 --- a/ports/servoshell/desktop/window_trait.rs +++ b/ports/servoshell/desktop/window_trait.rs @@ -47,4 +47,14 @@ pub trait WindowPortsMethods: WindowMethods { fn toolbar_height(&self) -> Length; fn set_toolbar_height(&self, height: Length); fn rendering_context(&self) -> Rc; + fn show_ime( + &self, + _input_type: servo::InputMethodType, + _text: Option<(String, i32)>, + _multiline: bool, + _position: servo::webrender_api::units::DeviceIntRect, + ) { + } + + fn hide_ime(&self) {} }