From eb256a6ddd314db73de99fdf38aeecc59983e60b Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Sat, 8 Jul 2023 12:55:25 +0200 Subject: [PATCH 1/3] Add android ime support --- src/event.rs | 26 ++++++++++ src/platform_impl/android/mod.rs | 50 +++++++++++++++++++ src/platform_impl/ios/window.rs | 7 +++ src/platform_impl/linux/mod.rs | 10 ++++ src/platform_impl/linux/wayland/window/mod.rs | 1 + src/platform_impl/macos/window.rs | 10 ++++ src/platform_impl/orbital/window.rs | 10 ++++ src/platform_impl/web/window.rs | 14 ++++++ src/platform_impl/windows/window.rs | 10 ++++ src/window.rs | 18 +++++++ 10 files changed, 156 insertions(+) diff --git a/src/event.rs b/src/event.rs index e0afca44fa..39d67ee7db 100644 --- a/src/event.rs +++ b/src/event.rs @@ -378,6 +378,8 @@ pub enum WindowEvent { /// - **iOS / Android / Web / Orbital:** Unsupported. Ime(Ime), + TextInputState(TextInputState), + /// The cursor has moved on the window. /// /// ## Platform-specific @@ -1095,3 +1097,27 @@ impl PartialEq for InnerSizeWriter { self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr() } } + +/// This struct holds a span within a region of text from `start` (inclusive) to +/// `end` (exclusive). +/// +/// An empty span or cursor position is specified with `start == end`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TextSpan { + /// The start of the span (inclusive) + pub start: usize, + + /// The end of the span (exclusive) + pub end: usize, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct TextInputState { + pub text: String, + /// A selection defined on the text. + pub selection: TextSpan, + /// A composing region defined on the text. + pub compose_region: Option, +} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 6d8f1e188f..e3aefa9e64 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -469,6 +469,32 @@ impl EventLoop { } } } + InputEvent::TextEvent(ime_state) => { + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::TextInputState( + TextInputState { + text: ime_state.text.to_owned(), + selection: TextSpan { + start: ime_state.selection.start, + end: ime_state.selection.end, + }, + compose_region: ime_state + .compose_region + .map(|region| TextSpan { + start: region.start, + end: region.end, + }), + } + ) + }; + sticky_exit_callback( + event, + self.window_target(), + control_flow, + callback + ); + } _ => { warn!("Unknown android_activity input event {event:?}") } @@ -905,6 +931,28 @@ impl Window { pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} + pub fn begin_ime_input(&self) { + self.app.show_soft_input(true); + } + + pub fn end_ime_input(&self) { + self.app.hide_soft_input(true); + } + + pub fn set_text_input_state(&self, state: TextInputState) { + self.app.set_text_input_state(android_activity::input::TextInputState { + text: state.text, + selection: android_activity::input::TextSpan { + start: state.selection.start, + end: state.selection.end, + }, + compose_region: state.compose_region.map(|region| android_activity::input::TextSpan { + start: region.start, + end: region.end, + }), + }); + } + pub fn focus_window(&self) {} pub fn request_user_attention(&self, _request_type: Option) {} @@ -987,6 +1035,8 @@ impl Window { pub struct OsError; use std::fmt::{self, Display, Formatter}; +use crate::event::{TextInputState, TextSpan}; + impl Display for OsError { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { write!(fmt, "Android OS Error") diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 71875be61a..4cd43cb110 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -30,6 +30,7 @@ use crate::{ WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel, }, }; +use crate::event::TextInputState; pub struct Inner { pub(crate) window: Id, @@ -306,6 +307,12 @@ impl Inner { warn!("`Window::set_ime_allowed` is ignored on iOS") } + pub fn begin_ime_input(&self) {} + + pub fn end_ime_input(&self) {} + + pub fn set_text_input_state(&self, state: TextInputState) {} + pub fn focus_window(&self) { warn!("`Window::set_focus` is ignored on iOS") } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index ae6d762f28..d11e118749 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -46,6 +46,7 @@ use crate::{ UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }, }; +use crate::event::TextInputState; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; @@ -545,6 +546,15 @@ impl Window { x11_or_wayland!(match self; Window(w) => w.set_ime_purpose(purpose)) } + #[inline] + pub fn set_text_input_state(&self, state: TextInputState) {} + + #[inline] + pub fn begin_ime_input(&self) {} + + #[inline] + pub fn end_ime_input(&self) {} + #[inline] pub fn focus_window(&self) { match self { diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 3b332aca99..eaeef835e4 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -31,6 +31,7 @@ use crate::window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, }; +use crate::event::TextInputState; use super::event_loop::sink::EventSink; use super::output::MonitorHandle; diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index f174753a84..2abda08c1f 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -42,6 +42,7 @@ use icrate::Foundation::{ use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::{autoreleasepool, Id}; use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType}; +use crate::event::TextInputState; use super::appkit::{ NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType, @@ -1199,6 +1200,15 @@ impl WinitWindow { #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} + #[inline] + pub fn begin_ime_input(&self) {} + + #[inline] + pub fn end_ime_input(&self) {} + + #[inline] + pub fn set_text_input_state(&self, state: TextInputState) {} + #[inline] pub fn focus_window(&self) { let is_minimized = self.isMiniaturized(); diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index 11ec6c7ee4..0d465359f4 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -14,6 +14,7 @@ use crate::{ window, window::ImePurpose, }; +use crate::event::TextInputState; use super::{ EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket, @@ -333,6 +334,15 @@ impl Window { #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} + #[inline] + pub fn begin_ime_input(&self) {} + + #[inline] + pub fn end_ime_input(&self) {} + + #[inline] + pub fn set_text_input_state(&self, state: TextInputState) {} + #[inline] pub fn focus_window(&self) {} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index c267f8d6ff..5296bfba78 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -5,6 +5,7 @@ use crate::window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel, }; +use crate::event::TextInputState; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle}; use web_sys::{Document, HtmlCanvasElement}; @@ -366,6 +367,19 @@ impl Window { // Currently not implemented } + #[inline] + pub fn begin_ime_input(&self) { + // Currently not implemented + } + + #[inline] + pub fn end_ime_input(&self) { + // Currently not implemented + } + + #[inline] + pub fn set_text_input_state(&self, state: TextInputState) {} + #[inline] pub fn focus_window(&self) { self.inner.dispatch(|inner| { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 1a11b2b667..ae93687f66 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -79,6 +79,7 @@ use crate::{ WindowAttributes, WindowButtons, WindowLevel, }, }; +use crate::event::TextInputState; /// The Win32 implementation of the main `Window` object. pub(crate) struct Window { @@ -761,6 +762,15 @@ impl Window { #[inline] pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} + #[inline] + pub fn begin_ime_input(&self) {} + + #[inline] + pub fn end_ime_input(&self) {} + + #[inline] + pub fn set_text_input_state(&self, state: TextInputState) {} + #[inline] pub fn request_user_attention(&self, request_type: Option) { let window = self.window.clone(); diff --git a/src/window.rs b/src/window.rs index 017501f6b1..b064cc495b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -12,6 +12,7 @@ use crate::{ monitor::{MonitorHandle, VideoMode}, platform_impl, }; +use crate::event::TextInputState; pub use crate::icon::{BadIcon, Icon}; @@ -1111,6 +1112,23 @@ impl Window { self.window.set_ime_purpose(purpose); } + /// Opens the IME input (soft keyboard) if the platform supports it. + /// Currently only supported on Android. + #[inline] + pub fn begin_ime_input(&self) { + self.window.begin_ime_input(); + } + + /// Hides the IME input (soft keyboard). + #[inline] + pub fn end_ime_input(&self) { + self.window.end_ime_input(); + } + + pub fn set_text_input_state(&self, state: TextInputState) { + self.window.set_text_input_state(state); + } + /// Brings the window to the front and sets input focus. Has no effect if the window is /// already in focus, minimized, or not visible. /// From 9248fd4ea8d5bb2d073cd50808bc3251310ef642 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Wed, 2 Aug 2023 00:32:45 +0200 Subject: [PATCH 2/3] Refactor android ime support, use Ime::Replace instead of separate event --- src/event.rs | 39 +++++-------- src/platform_impl/android/mod.rs | 57 +++++++------------ src/platform_impl/ios/window.rs | 9 +-- src/platform_impl/linux/mod.rs | 9 +-- src/platform_impl/linux/wayland/window/mod.rs | 1 - src/platform_impl/macos/window.rs | 9 +-- src/platform_impl/orbital/window.rs | 9 +-- src/platform_impl/web/window.rs | 13 +---- src/platform_impl/windows/window.rs | 9 +-- src/window.rs | 24 +++----- 10 files changed, 51 insertions(+), 128 deletions(-) diff --git a/src/event.rs b/src/event.rs index 39d67ee7db..3af77d1823 100644 --- a/src/event.rs +++ b/src/event.rs @@ -378,8 +378,6 @@ pub enum WindowEvent { /// - **iOS / Android / Web / Orbital:** Unsupported. Ime(Ime), - TextInputState(TextInputState), - /// The cursor has moved on the window. /// /// ## Platform-specific @@ -890,6 +888,19 @@ pub enum Ime { /// Right before this event winit will send empty [`Self::Preedit`] event. Commit(String), + /// Notifies when the complete text should be replaced. + /// This event should be used in combination with + /// [`Window::set_ime_surrounding_text`] to set the initial text of the + /// currently selected input field. + /// + /// ## Platform-specific + /// This will only be fired on **Android**. + Replace { + text: String, + selection: (usize, usize), + compose_region: Option<(usize, usize)>, + }, + /// Notifies when the IME was disabled. /// /// After receiving this event you won't get any more [`Preedit`](Self::Preedit) or @@ -1097,27 +1108,3 @@ impl PartialEq for InnerSizeWriter { self.new_inner_size.as_ptr() == other.new_inner_size.as_ptr() } } - -/// This struct holds a span within a region of text from `start` (inclusive) to -/// `end` (exclusive). -/// -/// An empty span or cursor position is specified with `start == end`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct TextSpan { - /// The start of the span (inclusive) - pub start: usize, - - /// The end of the span (exclusive) - pub end: usize, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct TextInputState { - pub text: String, - /// A selection defined on the text. - pub selection: TextSpan, - /// A composing region defined on the text. - pub compose_region: Option, -} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index e3aefa9e64..4d6b6ab85f 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -472,19 +472,11 @@ impl EventLoop { InputEvent::TextEvent(ime_state) => { let event = event::Event::WindowEvent { window_id: window::WindowId(WindowId), - event: event::WindowEvent::TextInputState( - TextInputState { - text: ime_state.text.to_owned(), - selection: TextSpan { - start: ime_state.selection.start, - end: ime_state.selection.end, - }, - compose_region: ime_state - .compose_region - .map(|region| TextSpan { - start: region.start, - end: region.end, - }), + event: event::WindowEvent::Ime( + event::Ime::Replace { + text: ime_state.text.to_string(), + selection: (ime_state.selection.start, ime_state.selection.end), + compose_region: ime_state.compose_region.map(|region| (region.start, region.end)) } ) }; @@ -927,30 +919,26 @@ impl Window { pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} - pub fn set_ime_allowed(&self, _allowed: bool) {} - - pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} - - pub fn begin_ime_input(&self) { - self.app.show_soft_input(true); + pub fn set_ime_allowed(&self, allowed: bool) { + if allowed { + self.app.show_soft_input(true); + } else { + self.app.hide_soft_input(true); + } } - pub fn end_ime_input(&self) { - self.app.hide_soft_input(true); - } + pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} - pub fn set_text_input_state(&self, state: TextInputState) { - self.app.set_text_input_state(android_activity::input::TextInputState { - text: state.text, - selection: android_activity::input::TextSpan { - start: state.selection.start, - end: state.selection.end, - }, - compose_region: state.compose_region.map(|region| android_activity::input::TextSpan { - start: region.start, - end: region.end, - }), - }); + pub fn set_ime_surrounding_text(&self, text: String, selection: (usize, usize)) { + self.app + .set_text_input_state(android_activity::input::TextInputState { + text, + selection: android_activity::input::TextSpan { + start: selection.0, + end: selection.1, + }, + compose_region: None, + }); } pub fn focus_window(&self) {} @@ -1035,7 +1023,6 @@ impl Window { pub struct OsError; use std::fmt::{self, Display, Formatter}; -use crate::event::{TextInputState, TextSpan}; impl Display for OsError { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 4cd43cb110..3cebf15411 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -30,7 +30,6 @@ use crate::{ WindowAttributes, WindowButtons, WindowId as RootWindowId, WindowLevel, }, }; -use crate::event::TextInputState; pub struct Inner { pub(crate) window: Id, @@ -307,11 +306,9 @@ impl Inner { warn!("`Window::set_ime_allowed` is ignored on iOS") } - pub fn begin_ime_input(&self) {} - - pub fn end_ime_input(&self) {} - - pub fn set_text_input_state(&self, state: TextInputState) {} + pub fn set_ime_surrounding_text(&self, _text: String, _selection: (usize, usize)) { + warn!("`Window::set_ime_surrounding_text` is ignored on iOS") + } pub fn focus_window(&self) { warn!("`Window::set_focus` is ignored on iOS") diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index d11e118749..ec3a056757 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -46,7 +46,6 @@ use crate::{ UserAttentionType, WindowAttributes, WindowButtons, WindowLevel, }, }; -use crate::event::TextInputState; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; pub(crate) use crate::platform_impl::Fullscreen; @@ -547,13 +546,7 @@ impl Window { } #[inline] - pub fn set_text_input_state(&self, state: TextInputState) {} - - #[inline] - pub fn begin_ime_input(&self) {} - - #[inline] - pub fn end_ime_input(&self) {} + pub fn set_ime_surrounding_text(&self, _text: String, _selection: (usize, usize)) {} #[inline] pub fn focus_window(&self) { diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index eaeef835e4..3b332aca99 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -31,7 +31,6 @@ use crate::window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, }; -use crate::event::TextInputState; use super::event_loop::sink::EventSink; use super::output::MonitorHandle; diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 2abda08c1f..4b2fe89392 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -42,7 +42,6 @@ use icrate::Foundation::{ use objc2::declare::{Ivar, IvarDrop}; use objc2::rc::{autoreleasepool, Id}; use objc2::{declare_class, msg_send, msg_send_id, mutability, sel, ClassType}; -use crate::event::TextInputState; use super::appkit::{ NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType, @@ -1201,13 +1200,7 @@ impl WinitWindow { pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] - pub fn begin_ime_input(&self) {} - - #[inline] - pub fn end_ime_input(&self) {} - - #[inline] - pub fn set_text_input_state(&self, state: TextInputState) {} + pub fn set_ime_surrounding_text(&self, _text: String, _selection: (usize, usize)) {} #[inline] pub fn focus_window(&self) { diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index 0d465359f4..e64c3c51da 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -14,7 +14,6 @@ use crate::{ window, window::ImePurpose, }; -use crate::event::TextInputState; use super::{ EventLoopWindowTarget, MonitorHandle, PlatformSpecificWindowBuilderAttributes, RedoxSocket, @@ -335,13 +334,7 @@ impl Window { pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] - pub fn begin_ime_input(&self) {} - - #[inline] - pub fn end_ime_input(&self) {} - - #[inline] - pub fn set_text_input_state(&self, state: TextInputState) {} + pub fn set_ime_surrounding_text(&self, _text: String, _selection: (usize, usize)) {} #[inline] pub fn focus_window(&self) {} diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 5296bfba78..11db77c441 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -5,7 +5,6 @@ use crate::window::{ CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId as RootWI, WindowLevel, }; -use crate::event::TextInputState; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle}; use web_sys::{Document, HtmlCanvasElement}; @@ -368,17 +367,7 @@ impl Window { } #[inline] - pub fn begin_ime_input(&self) { - // Currently not implemented - } - - #[inline] - pub fn end_ime_input(&self) { - // Currently not implemented - } - - #[inline] - pub fn set_text_input_state(&self, state: TextInputState) {} + pub fn set_ime_surrounding_text(&self, _text: String, _selection: (usize, usize)) {} #[inline] pub fn focus_window(&self) { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index ae93687f66..0c9e4a3577 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -79,7 +79,6 @@ use crate::{ WindowAttributes, WindowButtons, WindowLevel, }, }; -use crate::event::TextInputState; /// The Win32 implementation of the main `Window` object. pub(crate) struct Window { @@ -763,13 +762,7 @@ impl Window { pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} #[inline] - pub fn begin_ime_input(&self) {} - - #[inline] - pub fn end_ime_input(&self) {} - - #[inline] - pub fn set_text_input_state(&self, state: TextInputState) {} + pub fn set_ime_surrounding_text(&self, _text: String, _selection: (usize, usize)) {} #[inline] pub fn request_user_attention(&self, request_type: Option) { diff --git a/src/window.rs b/src/window.rs index b064cc495b..2f274f4b92 100644 --- a/src/window.rs +++ b/src/window.rs @@ -12,7 +12,6 @@ use crate::{ monitor::{MonitorHandle, VideoMode}, platform_impl, }; -use crate::event::TextInputState; pub use crate::icon::{BadIcon, Icon}; @@ -1112,21 +1111,14 @@ impl Window { self.window.set_ime_purpose(purpose); } - /// Opens the IME input (soft keyboard) if the platform supports it. - /// Currently only supported on Android. - #[inline] - pub fn begin_ime_input(&self) { - self.window.begin_ime_input(); - } - - /// Hides the IME input (soft keyboard). - #[inline] - pub fn end_ime_input(&self) { - self.window.end_ime_input(); - } - - pub fn set_text_input_state(&self, state: TextInputState) { - self.window.set_text_input_state(state); + /// Sets the surrounding text for IME. + /// + /// ## Platform-specific + /// - **Android**: should be set when a textfield is focused, so the keyboard has context + /// for autocomplete. If this is not set, the text will be cleared when the user starts typing. + /// - **iOS / Web / Windows / X11 / macOS / Orbital:** Unsupported. + pub fn set_ime_surrounding_text(&self, text: String, selection: (usize, usize)) { + self.window.set_ime_surrounding_text(text, selection); } /// Brings the window to the front and sets input focus. Has no effect if the window is From 14981b2b27b766e10a1c6ad08dc0939edaf66c4e Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Sat, 8 Jul 2023 12:55:25 +0200 Subject: [PATCH 3/3] Use new Ime::DeleteSurroundingText instead of Ime::Replace --- src/event.rs | 26 +++++------ src/platform_impl/android/mod.rs | 43 ++++++++++++------- .../linux/wayland/seat/text_input/mod.rs | 11 +++-- .../linux/x11/event_processor.rs | 6 ++- src/platform_impl/macos/view.rs | 6 ++- src/platform_impl/orbital/event_loop.rs | 6 ++- src/platform_impl/windows/event_loop.rs | 12 +++++- 7 files changed, 74 insertions(+), 36 deletions(-) diff --git a/src/event.rs b/src/event.rs index 3af77d1823..ad16ac19a4 100644 --- a/src/event.rs +++ b/src/event.rs @@ -886,21 +886,23 @@ pub enum Ime { /// Notifies when text should be inserted into the editor widget. /// /// Right before this event winit will send empty [`Self::Preedit`] event. - Commit(String), - - /// Notifies when the complete text should be replaced. - /// This event should be used in combination with - /// [`Window::set_ime_surrounding_text`] to set the initial text of the - /// currently selected input field. - /// - /// ## Platform-specific - /// This will only be fired on **Android**. - Replace { - text: String, - selection: (usize, usize), + Commit { + content: String, + /// If selection is Some, the selection / cursor position should be updated after + /// placing the `content`. + selection: Option<(usize, usize)>, + /// If compose_region is Some, the compose region should be updated after replacing the + /// text. compose_region: Option<(usize, usize)>, }, + /// Notifies when the text around the cursor should be deleted. + /// If `before_length` and `after_length` are [usize::MAX], the entire text should be deleted. + DeleteSurroundingText { + before_length: usize, + after_length: usize, + }, + /// Notifies when the IME was disabled. /// /// After receiving this event you won't get any more [`Preedit`](Self::Preedit) or diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 4d6b6ab85f..faa455d2f7 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -470,22 +470,33 @@ impl EventLoop { } } InputEvent::TextEvent(ime_state) => { - let event = event::Event::WindowEvent { - window_id: window::WindowId(WindowId), - event: event::WindowEvent::Ime( - event::Ime::Replace { - text: ime_state.text.to_string(), - selection: (ime_state.selection.start, ime_state.selection.end), - compose_region: ime_state.compose_region.map(|region| (region.start, region.end)) - } - ) - }; - sticky_exit_callback( - event, - self.window_target(), - control_flow, - callback - ); + let events = [ + // Send a preedit event so the application knows to expect a commit event + event::Ime::Preedit("".to_string(), None), + // Delete all of the current text + event::Ime::DeleteSurroundingText { + before_length: usize::MAX, + after_length: usize::MAX, + }, + // Replace the previously deleted text with our updated text, and set the cursor and compose region + event::Ime::Commit { + content: ime_state.text.to_string(), + selection: Some((ime_state.selection.start, ime_state.selection.end)), + compose_region: ime_state.compose_region.map(|region| (region.start, region.end)), + } + ]; + + events.into_iter().for_each(|event| { + sticky_exit_callback( + event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::Ime(event), + }, + self.window_target(), + control_flow, + callback, + ); + }); } _ => { warn!("Unknown android_activity input event {event:?}") diff --git a/src/platform_impl/linux/wayland/seat/text_input/mod.rs b/src/platform_impl/linux/wayland/seat/text_input/mod.rs index cff18dffeb..51ba851d82 100644 --- a/src/platform_impl/linux/wayland/seat/text_input/mod.rs +++ b/src/platform_impl/linux/wayland/seat/text_input/mod.rs @@ -143,9 +143,14 @@ impl Dispatch for TextInputState { // Send `Commit`. if let Some(text) = text_input_data.pending_commit.take() { - state - .events_sink - .push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id); + state.events_sink.push_window_event( + WindowEvent::Ime(Ime::Commit { + content: text, + selection: None, + compose_region: None, + }), + window_id, + ); } // Send preedit. diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 5d030f89d2..c2704f7814 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -653,7 +653,11 @@ impl EventProcessor { let event = Event::WindowEvent { window_id, - event: WindowEvent::Ime(Ime::Commit(written)), + event: WindowEvent::Ime(Ime::Commit { + content: written, + selection: None, + compose_region: None, + }), }; self.is_composing = false; diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index eba06c19a7..6a64d89c24 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -436,7 +436,11 @@ declare_class!( // Commit only if we have marked text. if self.hasMarkedText() && self.is_ime_enabled() && !is_control { self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); - self.queue_event(WindowEvent::Ime(Ime::Commit(string))); + self.queue_event(WindowEvent::Ime(Ime::Commit { + content: string, + selection: None, + compose_region: None, + })); self.state.ime_state.set(ImeState::Commited); } } diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index 3cfc2b86f6..89e17e39f5 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -354,7 +354,11 @@ impl EventLoop { }); event_handler(event::Event::WindowEvent { window_id: RootWindowId(window_id), - event: event::WindowEvent::Ime(Ime::Commit(character.into())), + event: event::WindowEvent::Ime(Ime::Commit { + content: character.into(), + selection: None, + compose_region: None, + }), }); } EventOption::Mouse(MouseEvent { x, y }) => { diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 9856fca362..baa14422b6 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1323,7 +1323,11 @@ unsafe fn public_window_callback_inner( }); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: WindowEvent::Ime(Ime::Commit(text)), + event: WindowEvent::Ime(Ime::Commit { + content: text, + selection: None, + compose_region: None, + }), }); } } @@ -1363,7 +1367,11 @@ unsafe fn public_window_callback_inner( }); userdata.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), - event: WindowEvent::Ime(Ime::Commit(text)), + event: WindowEvent::Ime(Ime::Commit { + content: text, + selection: None, + compose_region: None, + }), }); } }