feat: support pre-edit text display for IME (#35535)

* feat: support pre-edit text display for IME

Signed-off-by: DK Liao <dklassic@gmail.com>

* enable ime by show_ime

Signed-off-by: DK Liao <dklassic@gmail.com>

---------

Signed-off-by: DK Liao <dklassic@gmail.com>
This commit is contained in:
DK Liao 2025-02-19 20:22:57 +09:00 committed by GitHub
parent 42e873e9d9
commit 720bc725b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 91 additions and 4 deletions

View file

@ -2652,8 +2652,6 @@ impl VirtualMethods for HTMLInputElement {
event.type_() == atom!("compositionend")) && event.type_() == atom!("compositionend")) &&
self.input_type().is_textual_or_password() 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::<CompositionEvent>() { if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
if event.type_() == atom!("compositionend") { if event.type_() == atom!("compositionend") {
let _ = self let _ = self
@ -2661,6 +2659,12 @@ impl VirtualMethods for HTMLInputElement {
.borrow_mut() .borrow_mut()
.handle_compositionend(compositionevent); .handle_compositionend(compositionevent);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
} else if event.type_() == atom!("compositionupdate") {
let _ = self
.textinput
.borrow_mut()
.handle_compositionupdate(compositionevent);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
} }
event.mark_as_handled(); event.mark_as_handled();
} }

View file

@ -668,8 +668,6 @@ impl VirtualMethods for HTMLTextAreaElement {
event.type_() == atom!("compositionupdate") || event.type_() == atom!("compositionupdate") ||
event.type_() == atom!("compositionend") event.type_() == atom!("compositionend")
{ {
// TODO: Update DOM on start and continue
// and generally do proper CompositionEvent handling.
if let Some(compositionevent) = event.downcast::<CompositionEvent>() { if let Some(compositionevent) = event.downcast::<CompositionEvent>() {
if event.type_() == atom!("compositionend") { if event.type_() == atom!("compositionend") {
let _ = self let _ = self
@ -677,6 +675,12 @@ impl VirtualMethods for HTMLTextAreaElement {
.borrow_mut() .borrow_mut()
.handle_compositionend(compositionevent); .handle_compositionend(compositionevent);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
} else if event.type_() == atom!("compositionupdate") {
let _ = self
.textinput
.borrow_mut()
.handle_compositionupdate(compositionevent);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
} }
event.mark_as_handled(); event.mark_as_handled();
} }

View file

@ -988,6 +988,17 @@ impl<T: ClipboardProvider> TextInput<T> {
KeyReaction::DispatchInput 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. /// Whether the content is empty.
pub(crate) fn is_empty(&self) -> bool { pub(crate) fn is_empty(&self) -> bool {
self.lines.len() <= 1 && self.lines.first().map_or(true, |line| line.is_empty()) self.lines.len() <= 1 && self.lines.first().map_or(true, |line| line.is_empty())

View file

@ -527,6 +527,22 @@ impl WebViewDelegate for RunningAppState {
}; };
let _ = haptic_stop_sender.send(stopped); 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")] #[cfg(target_os = "linux")]

View file

@ -631,6 +631,35 @@ impl WindowPortsMethods for Window {
WindowEvent::Moved(_new_position) => { WindowEvent::Moved(_new_position) => {
webview.notify_embedder_window_moved(); 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<dyn RenderingContext> { fn rendering_context(&self) -> Rc<dyn RenderingContext> {
self.rendering_context.clone() 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 { impl WindowMethods for Window {

View file

@ -47,4 +47,14 @@ pub trait WindowPortsMethods: WindowMethods {
fn toolbar_height(&self) -> Length<f32, DeviceIndependentPixel>; fn toolbar_height(&self) -> Length<f32, DeviceIndependentPixel>;
fn set_toolbar_height(&self, height: Length<f32, DeviceIndependentPixel>); fn set_toolbar_height(&self, height: Length<f32, DeviceIndependentPixel>);
fn rendering_context(&self) -> Rc<dyn RenderingContext>; fn rendering_context(&self) -> Rc<dyn RenderingContext>;
fn show_ime(
&self,
_input_type: servo::InputMethodType,
_text: Option<(String, i32)>,
_multiline: bool,
_position: servo::webrender_api::units::DeviceIntRect,
) {
}
fn hide_ime(&self) {}
} }