4545 E : AsRef < web_sys:: Event > + wasm_bindgen:: JsCast ,
4646{
4747 let closure = wasm_bindgen:: closure:: Closure :: new ( move |event : web_sys:: Event | {
48- let mut state = state_cell. borrow_mut ( ) ;
4948 if !super :: has_panicked ( ) {
50- listener ( & mut state , event. unchecked_into ( ) ) ;
49+ listener ( & mut state_cell . borrow_mut ( ) , event. unchecked_into ( ) ) ;
5150 }
5251 } ) ;
5352
@@ -142,6 +141,7 @@ pub(super) fn register_events(
142141 output_rx : flume:: Receiver < super :: Output > ,
143142 panic_rx : oneshot:: Receiver < ( ) > ,
144143 window : web_sys:: Window ,
144+ document : web_sys:: Document ,
145145) -> Result < ( ) , wasm_bindgen:: JsValue > {
146146 // Set up panic handler to unsubscribe all event handlers on panic
147147 wasm_bindgen_futures:: spawn_local ( async move {
@@ -156,10 +156,11 @@ pub(super) fn register_events(
156156 } ) ;
157157 } ) ;
158158
159- let document = window. document ( ) . unwrap ( ) ;
160-
161159 let state_cell = std:: rc:: Rc :: new ( std:: cell:: RefCell :: new ( state) ) ;
162- let canvas = state_cell. borrow ( ) . canvas . clone ( ) ;
160+ let state = state_cell. borrow ( ) ;
161+ let canvas = state. canvas . clone ( ) ;
162+ let input = state. input . clone ( ) ;
163+ drop ( state) ;
163164
164165 // Register event listener for screen resizing
165166 let listener = {
@@ -451,14 +452,13 @@ pub(super) fn register_events(
451452 // Register event listener for keyboard key presses and releases
452453 let listener_builder = |pressed| {
453454 move |state : & mut super :: MainState , event : web_sys:: KeyboardEvent | {
454- // TODO: handle input method editors
455455 let modifiers = event. egui_modifiers ( ) ;
456456 state
457457 . event_tx
458458 . send ( super :: Event :: Modifiers ( modifiers) )
459459 . unwrap ( ) ;
460460 let key = event. key ( ) ;
461- if pressed && !modifiers. command && key. len ( ) == 1 {
461+ if pressed && !modifiers. command && state . ime . is_none ( ) && key. len ( ) == 1 {
462462 state
463463 . event_tx
464464 . send ( super :: Event :: Egui ( egui:: Event :: Text ( key. clone ( ) ) ) )
@@ -516,6 +516,68 @@ pub(super) fn register_events(
516516 listener_builder ( false ) ,
517517 ) ?;
518518
519+ // Register event listener for inputting text while the egui app wants text input
520+ let listener = move |state : & mut super :: MainState , event : web_sys:: InputEvent | {
521+ if state. ime . is_none ( ) || event. is_composing ( ) {
522+ return ;
523+ }
524+ let text = state. input . value ( ) ;
525+ if text. is_empty ( ) {
526+ return ;
527+ }
528+ state
529+ . event_tx
530+ . send ( super :: Event :: Egui ( egui:: Event :: Text ( text) ) )
531+ . unwrap ( ) ;
532+ state. input . set_value ( "" ) ;
533+ } ;
534+ add_event_listener ( state_cell. clone ( ) , & input, "input" , listener) ?;
535+
536+ // Register event listener for starting to compose text an input method editor while the egui
537+ // app wants text input
538+ let listener = move |state : & mut super :: MainState , _event : web_sys:: CompositionEvent | {
539+ state
540+ . event_tx
541+ . send ( super :: Event :: Egui ( egui:: Event :: Ime (
542+ egui:: ImeEvent :: Enabled ,
543+ ) ) )
544+ . unwrap ( ) ;
545+ state. input . set_value ( "" ) ;
546+ } ;
547+ add_event_listener ( state_cell. clone ( ) , & input, "compositionstart" , listener) ?;
548+
549+ // Register event listener for submitting composed text from an input method editor while the
550+ // egui app wants text input
551+ let listener = move |state : & mut super :: MainState , event : web_sys:: CompositionEvent | {
552+ let Some ( text) = event. data ( ) else {
553+ return ;
554+ } ;
555+ state
556+ . event_tx
557+ . send ( super :: Event :: Egui ( egui:: Event :: Ime (
558+ egui:: ImeEvent :: Commit ( text) ,
559+ ) ) )
560+ . unwrap ( ) ;
561+ state. input . set_value ( "" ) ;
562+ } ;
563+ add_event_listener ( state_cell. clone ( ) , & input, "compositionend" , listener) ?;
564+
565+ // Register event listener for when the preview of the text in an input method editor updates
566+ // while the egui wapp wants text input
567+ let listener = move |state : & mut super :: MainState , event : web_sys:: CompositionEvent | {
568+ let Some ( text) = event. data ( ) else {
569+ return ;
570+ } ;
571+ state
572+ . event_tx
573+ . send ( super :: Event :: Egui ( egui:: Event :: Ime (
574+ egui:: ImeEvent :: Preedit ( text) ,
575+ ) ) )
576+ . unwrap ( ) ;
577+ state. input . set_value ( "" ) ;
578+ } ;
579+ add_event_listener ( state_cell. clone ( ) , & input, "compositionupdate" , listener) ?;
580+
519581 // Register event listener for text pasting
520582 let listener = move |state : & mut super :: MainState , event : web_sys:: ClipboardEvent | {
521583 if let Some ( data) = event. clipboard_data ( ) {
@@ -608,6 +670,7 @@ pub(super) fn register_events(
608670 zoom_factor,
609671 screen_reader_enabled,
610672 } => {
673+ // Update the cursor icon to the one specified by the app for this frame
611674 let _ = body_style. set_property (
612675 "cursor" ,
613676 match output. cursor_icon {
@@ -654,8 +717,47 @@ pub(super) fn register_events(
654717 } ,
655718 ) ;
656719
657- // TODO: handle input method editors and screen readers here
658- let _ = screen_reader_enabled;
720+ // Speak the text description output by the app, if applicable
721+ if screen_reader_enabled {
722+ let text = output. events_description ( ) ;
723+ if !text. is_empty ( ) {
724+ if let ( Ok ( speech_synthesis) , Ok ( utterance) ) = (
725+ window. speech_synthesis ( ) ,
726+ web_sys:: SpeechSynthesisUtterance :: new_with_text ( & text) ,
727+ ) {
728+ speech_synthesis. cancel ( ) ;
729+ speech_synthesis. speak ( & utterance) ;
730+ }
731+ }
732+ }
733+
734+ {
735+ let mut state = state_cell. borrow_mut ( ) ;
736+
737+ // Focus the input handler if the app wants keyboard input, otherwise focus
738+ // the canvas
739+ if let Some ( ime) = output. ime {
740+ if state. ime != Some ( ime) {
741+ // Move the input handler to the position the app wants input method
742+ // editors to show up at
743+ let pos = ime. cursor_rect . center ( ) ;
744+ let style = state. input . style ( ) ;
745+ let _ = style. set_property ( "left" , & format ! ( "{}px" , pos. x) ) ;
746+ let _ = style. set_property ( "top" , & format ! ( "{}px" , pos. y) ) ;
747+ // Workaround for the input method editor sometimes opening in the
748+ // location the input handler was previously located at instead of
749+ // the new location we just moved it to when using Firefox
750+ let _ = state. input . focus ( ) ;
751+ let _ = state. input . blur ( ) ;
752+ }
753+ let _ = state. input . focus ( ) ;
754+ } else {
755+ let _ = state. canvas . focus ( ) ;
756+ }
757+ state. ime = output. ime ;
758+
759+ state. zoom_factor = zoom_factor;
760+ }
659761
660762 if !output. copied_text . is_empty ( ) {
661763 if let Err ( e) = wasm_bindgen_futures:: JsFuture :: from (
@@ -684,8 +786,6 @@ pub(super) fn register_events(
684786 ) ;
685787 }
686788 }
687-
688- state_cell. borrow_mut ( ) . zoom_factor = zoom_factor;
689789 }
690790
691791 super :: Output :: StorageGet { key, oneshot_tx } => {
0 commit comments