Skip to content

Commit 734e8af

Browse files
committed
Add basic IME support for Android
This adds basic support for Ime events to the Android backend. Note that this will only work when running with the game-activity backend, which uses AGDK GameText to forward Android IME events: https://developer.android.com/games/agdk/add-support-for-text-input Normally on Android, input methods track three things: - Surrounding text - A compose region - A selection Since Winit (0.30) doesn't track surrounding text and therefore also wouldn't be able to handle orthogonal compose + selection regions within some surrounding text, we can treat the whole text region that we edit as the "preedit" string, and we can then treat the compose region as the optional selection within the preedit string. I've tested this with Egui 0.33 I've seem some quirky cases when testing with Egui (such as if you try and move the cursor in the Egui widget while you're in the middle of entering text via a soft keyboard) but I think those are related to general shortcomings of the winit 0.30 IME API and Egui's support for IMEs (there's no way for Egui to notify through Winit that the cursor position has changed).
1 parent f6893a4 commit 734e8af

File tree

3 files changed

+55
-3
lines changed

3 files changed

+55
-3
lines changed

src/changelog/v0.30.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
2+
## 0.30.13
3+
4+
### Added
5+
6+
- On Android, added support for Ime events, for soft keyboard input.
7+
18
## 0.30.12
29

310
### Fixed

src/event.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ pub enum WindowEvent {
227227
///
228228
/// ## Platform-specific
229229
///
230-
/// - **iOS / Android / Web / Orbital:** Unsupported.
230+
/// - **iOS / Web / Orbital:** Unsupported.
231231
Ime(Ime),
232232

233233
/// The cursor has moved on the window.

src/platform_impl/android/mod.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use std::sync::atomic::{AtomicBool, Ordering};
66
use std::sync::{mpsc, Arc, Mutex};
77
use std::time::{Duration, Instant};
88

9-
use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction};
9+
use android_activity::input::{
10+
InputEvent, KeyAction, Keycode, MotionAction, TextInputState, TextSpan,
11+
};
1012
use android_activity::{
1113
AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect,
1214
};
@@ -466,6 +468,31 @@ impl<T: 'static> EventLoop<T> {
466468
},
467469
}
468470
},
471+
InputEvent::TextEvent(ime_state) => {
472+
// Note: Winit does not support surrounding text or tracking a selection/cursor that
473+
// may span within the surrounding text and the preedit text.
474+
//
475+
// Since there's no API to specify surrounding text, set_ime_allowed() will reset
476+
// the text to an empty string and we will treat all the text as preedit text.
477+
//
478+
// We map Android's composing region to winit's preedit selection region.
479+
//
480+
// This seems a little odd, since Android's notion of a "composing region" would
481+
// normally be equated with winit's "preedit" text but conceptually we're mapping
482+
// Android's surrounding text + composing region into winit's preedit text +
483+
// selection region.
484+
//
485+
// We ignore the separate selection region that Android supports.
486+
let event = event::Event::WindowEvent {
487+
window_id: window::WindowId(WindowId),
488+
event: event::WindowEvent::Ime(event::Ime::Preedit(
489+
ime_state.text.clone(),
490+
ime_state.compose_region.map(|span| (span.start, span.end)),
491+
)),
492+
};
493+
494+
callback(event, self.window_target());
495+
},
469496
_ => {
470497
warn!("Unknown android_activity input event {event:?}")
471498
},
@@ -770,6 +797,7 @@ pub struct PlatformSpecificWindowAttributes;
770797
pub(crate) struct Window {
771798
app: AndroidApp,
772799
redraw_requester: RedrawRequester,
800+
ime_allowed: AtomicBool,
773801
}
774802

775803
impl Window {
@@ -779,7 +807,11 @@ impl Window {
779807
) -> Result<Self, error::OsError> {
780808
// FIXME this ignores requested window attributes
781809

782-
Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() })
810+
Ok(Self {
811+
app: el.app.clone(),
812+
redraw_requester: el.redraw_requester.clone(),
813+
ime_allowed: AtomicBool::new(false),
814+
})
783815
}
784816

785817
pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) {
@@ -909,11 +941,24 @@ impl Window {
909941
pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {}
910942

911943
pub fn set_ime_allowed(&self, allowed: bool) {
944+
// Request a show/hide regardless of whether the state has changed, since
945+
// the keyboard may have been dismissed by the user manually while in the
946+
// middle of text input
912947
if allowed {
913948
self.app.show_soft_input(true);
914949
} else {
915950
self.app.hide_soft_input(true);
916951
}
952+
953+
if self.ime_allowed.swap(allowed, Ordering::SeqCst) == allowed {
954+
return;
955+
}
956+
957+
self.app.set_text_input_state(TextInputState {
958+
text: String::new(),
959+
selection: TextSpan { start: 0, end: 0 },
960+
compose_region: None,
961+
});
917962
}
918963

919964
pub fn set_ime_purpose(&self, _purpose: ImePurpose) {}

0 commit comments

Comments
 (0)