@@ -6,7 +6,9 @@ use std::sync::atomic::{AtomicBool, Ordering};
66use std:: sync:: { mpsc, Arc , Mutex } ;
77use std:: time:: { Duration , Instant } ;
88
9- use android_activity:: input:: { InputEvent , KeyAction , Keycode , MotionAction } ;
9+ use android_activity:: input:: {
10+ InputEvent , KeyAction , Keycode , MotionAction , TextInputAction , TextInputState , TextSpan ,
11+ } ;
1012use android_activity:: {
1113 AndroidApp , AndroidAppWaker , ConfigurationRef , InputStatus , MainEvent , Rect ,
1214} ;
@@ -131,9 +133,14 @@ impl RedrawRequester {
131133#[ derive( Debug , Clone , Eq , PartialEq , Hash ) ]
132134pub struct KeyEventExtra { }
133135
136+ struct ImeState {
137+ ime_allowed : AtomicBool ,
138+ }
139+
134140pub struct EventLoop < T : ' static > {
135141 pub ( crate ) android_app : AndroidApp ,
136142 window_target : event_loop:: ActiveEventLoop ,
143+ ime_state : Arc < ImeState > ,
137144 redraw_flag : SharedFlag ,
138145 user_events_sender : mpsc:: Sender < T > ,
139146 user_events_receiver : PeekableReceiver < T > , // must wake looper whenever something gets sent
@@ -169,6 +176,8 @@ impl<T: 'static> EventLoop<T> {
169176 ) ;
170177 let redraw_flag = SharedFlag :: new ( ) ;
171178
179+ let ime_state = Arc :: new ( ImeState { ime_allowed : AtomicBool :: new ( false ) } ) ;
180+
172181 Ok ( Self {
173182 android_app : android_app. clone ( ) ,
174183 window_target : event_loop:: ActiveEventLoop {
@@ -180,9 +189,11 @@ impl<T: 'static> EventLoop<T> {
180189 & redraw_flag,
181190 android_app. create_waker ( ) ,
182191 ) ,
192+ ime_state : Arc :: clone ( & ime_state) ,
183193 } ,
184194 _marker : PhantomData ,
185195 } ,
196+ ime_state,
186197 redraw_flag,
187198 user_events_sender,
188199 user_events_receiver : PeekableReceiver :: from_recv ( user_events_receiver) ,
@@ -466,6 +477,94 @@ impl<T: 'static> EventLoop<T> {
466477 } ,
467478 }
468479 } ,
480+ InputEvent :: TextEvent ( input_state) => {
481+ trace ! ( "Received IME text event: {:?}" , input_state) ;
482+ if self . ime_state . ime_allowed . load ( Ordering :: SeqCst ) == false {
483+ trace ! ( "IME input not enabled, ignoring spurious text event" ) ;
484+ return InputStatus :: Handled ;
485+ }
486+ // Note: Winit does not support surrounding text or tracking a selection/cursor that
487+ // may span within the surrounding text and the preedit text.
488+ //
489+ // Since there's no API to specify surrounding text, set_ime_allowed() will reset
490+ // the text to an empty string and we will treat all the text as preedit text.
491+ //
492+ // We map Android's composing region to winit's preedit selection region.
493+ //
494+ // This seems a little odd, since Android's notion of a "composing region" would
495+ // normally be equated with winit's "preedit" text but conceptually we're mapping
496+ // Android's surrounding text + composing region into winit's preedit text +
497+ // selection region.
498+ //
499+ // We ignore the separate selection region that Android supports.
500+
501+ let selection = if let Some ( compose_region) = input_state. compose_region {
502+ // Note: Winit uses byte offsets for the preedit selection region and Android
503+ // uses char offsets.
504+ let selection_0 = input_state
505+ . text
506+ . char_indices ( )
507+ . enumerate ( )
508+ . find ( |( _, ( byte_offset, _) ) | * byte_offset >= compose_region. start )
509+ . map ( |( char_idx, _) | char_idx) ;
510+ let selection_1 = input_state
511+ . text
512+ . char_indices ( )
513+ . enumerate ( )
514+ . find ( |( _, ( byte_offset, _) ) | * byte_offset >= compose_region. end )
515+ . map ( |( char_idx, _) | char_idx) ;
516+ let selection_0 = selection_0. unwrap_or ( input_state. text . len ( ) ) ;
517+ let selection_1 = selection_1. unwrap_or ( input_state. text . len ( ) ) ;
518+ Some ( ( selection_0, selection_1) )
519+ } else {
520+ let len = input_state. text . len ( ) ;
521+ Some ( ( 0 , len) )
522+ } ;
523+
524+ let event = event:: Event :: WindowEvent {
525+ window_id : window:: WindowId ( WindowId ) ,
526+ event : event:: WindowEvent :: Ime ( event:: Ime :: Preedit (
527+ input_state. text . clone ( ) ,
528+ selection,
529+ ) ) ,
530+ } ;
531+ callback ( event, self . window_target ( ) ) ;
532+ } ,
533+ InputEvent :: TextAction ( action) => {
534+ trace ! ( "Received IME text action event: {:?}" , action) ;
535+ if self . ime_state . ime_allowed . load ( Ordering :: SeqCst ) == false {
536+ trace ! ( "IME input not enabled, ignoring spurious text event" ) ;
537+ return InputStatus :: Handled ;
538+ }
539+
540+ // We don't have a way to convey the semantics of the action, so we just
541+ // map them all (except 'None') to a commit of the current text.
542+ if * action != TextInputAction :: None {
543+ let latest_ime_state = self . android_app . text_input_state ( ) ;
544+
545+ // The API docs say that a commit is preceded by an empty Preedit event
546+ let event = event:: Event :: WindowEvent {
547+ window_id : window:: WindowId ( WindowId ) ,
548+ event : event:: WindowEvent :: Ime ( event:: Ime :: Preedit ( String :: new ( ) , None ) ) ,
549+ } ;
550+ self . android_app . set_text_input_state ( TextInputState {
551+ text : String :: new ( ) ,
552+ selection : TextSpan { start : 0 , end : 0 } ,
553+ compose_region : None ,
554+ } ) ;
555+ self . android_app . hide_soft_input ( true ) ;
556+ callback ( event, self . window_target ( ) ) ;
557+
558+ let event = event:: Event :: WindowEvent {
559+ window_id : window:: WindowId ( WindowId ) ,
560+ event : event:: WindowEvent :: Ime ( event:: Ime :: Commit (
561+ latest_ime_state. text . clone ( ) ,
562+ ) ) ,
563+ } ;
564+
565+ callback ( event, self . window_target ( ) ) ;
566+ }
567+ } ,
469568 _ => {
470569 warn ! ( "Unknown android_activity input event {event:?}" )
471570 } ,
@@ -650,6 +749,7 @@ pub struct ActiveEventLoop {
650749 control_flow : Cell < ControlFlow > ,
651750 exit : Cell < bool > ,
652751 redraw_requester : RedrawRequester ,
752+ ime_state : Arc < ImeState > ,
653753}
654754
655755impl ActiveEventLoop {
@@ -770,6 +870,7 @@ pub struct PlatformSpecificWindowAttributes;
770870pub ( crate ) struct Window {
771871 app : AndroidApp ,
772872 redraw_requester : RedrawRequester ,
873+ ime_state : Arc < ImeState > ,
773874}
774875
775876impl Window {
@@ -779,7 +880,11 @@ impl Window {
779880 ) -> Result < Self , error:: OsError > {
780881 // FIXME this ignores requested window attributes
781882
782- Ok ( Self { app : el. app . clone ( ) , redraw_requester : el. redraw_requester . clone ( ) } )
883+ Ok ( Self {
884+ app : el. app . clone ( ) ,
885+ redraw_requester : el. redraw_requester . clone ( ) ,
886+ ime_state : Arc :: clone ( & el. ime_state ) ,
887+ } )
783888 }
784889
785890 pub ( crate ) fn maybe_queue_on_main ( & self , f : impl FnOnce ( & Self ) + Send + ' static ) {
@@ -909,11 +1014,24 @@ impl Window {
9091014 pub fn set_ime_cursor_area ( & self , _position : Position , _size : Size ) { }
9101015
9111016 pub fn set_ime_allowed ( & self , allowed : bool ) {
1017+ // Request a show/hide regardless of whether the state has changed, since
1018+ // the keyboard may have been dismissed by the user manually while in the
1019+ // middle of text input
9121020 if allowed {
9131021 self . app . show_soft_input ( true ) ;
9141022 } else {
9151023 self . app . hide_soft_input ( true ) ;
9161024 }
1025+
1026+ if self . ime_state . ime_allowed . swap ( allowed, Ordering :: SeqCst ) == allowed {
1027+ return ;
1028+ }
1029+
1030+ self . app . set_text_input_state ( TextInputState {
1031+ text : String :: new ( ) ,
1032+ selection : TextSpan { start : 0 , end : 0 } ,
1033+ compose_region : Some ( TextSpan { start : 0 , end : 0 } ) ,
1034+ } ) ;
9171035 }
9181036
9191037 pub fn set_ime_purpose ( & self , _purpose : ImePurpose ) { }
0 commit comments