Skip to content

Commit 7e8990f

Browse files
committed
Add support for InputEvent::TextAction events
This exposes IME actions via an InputEvent::TextAction event so that it's possible to recognise when text entry via an input method is finished. This adds a `TextInputAction` enum to represent the action key on a soft keyboard, such as "Done". For example, this makes it possible to emit Ime::Commit events in Winit.
1 parent fe2c50c commit 7e8990f

5 files changed

Lines changed: 70 additions & 9 deletions

File tree

android-activity/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
### Added
1010

11+
- input: TextInputAction enum representing action button types on soft keyboards.
12+
- input: InputEvent::TextAction event for handling action button presses from soft keyboards.
1113
- The `ndk` and `ndk-sys` crates are now re-exported under `android_activity::ndk` and `android_activity::ndk_sys` ([#194](https://github.com/rust-mobile/android-activity/pull/194))
1214

1315
### Changed

android-activity/src/game_activity/input.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub enum InputEvent<'a> {
2727
MotionEvent(MotionEvent<'a>),
2828
KeyEvent(KeyEvent<'a>),
2929
TextEvent(crate::input::TextInputState),
30+
TextAction(crate::input::TextInputAction),
3031
}
3132

3233
/// A motion event.

android-activity/src/game_activity/mod.rs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use ndk::configuration::Configuration;
2121
use ndk::native_window::NativeWindow;
2222

2323
use crate::error::InternalResult;
24-
use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding};
24+
use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding, TextInputAction};
2525
use crate::jni_utils::{self, CloneJavaVM};
2626
use crate::util::{abort_on_panic, forward_stdio_to_logcat, log_panic, try_get_path_from_ptr};
2727
use crate::{
@@ -174,9 +174,6 @@ impl NativeAppGlue {
174174
};
175175
let out_ptr = &mut out_state as *mut TextInputState;
176176

177-
let app_ptr = self.as_ptr();
178-
(*app_ptr).textInputState = 0;
179-
180177
// NEON WARNING:
181178
//
182179
// It's not clearly documented but the GameActivity API over the
@@ -204,6 +201,14 @@ impl NativeAppGlue {
204201
}
205202
}
206203

204+
pub fn take_text_input_state(&self) -> TextInputState {
205+
unsafe {
206+
let app_ptr = self.as_ptr();
207+
(*app_ptr).textInputState = 0;
208+
}
209+
self.text_input_state()
210+
}
211+
207212
// TODO: move into a trait
208213
pub fn set_text_input_state(&self, state: TextInputState) {
209214
unsafe {
@@ -247,6 +252,18 @@ impl NativeAppGlue {
247252
ffi::GameActivity_setTextInputState(activity, &ffi_state as *const _);
248253
}
249254
}
255+
256+
pub fn take_pending_editor_action(&self) -> Option<i32> {
257+
unsafe {
258+
let app_ptr = self.as_ptr();
259+
if (*app_ptr).pendingEditorAction {
260+
(*app_ptr).pendingEditorAction = false;
261+
Some((*app_ptr).editorAction)
262+
} else {
263+
None
264+
}
265+
}
266+
}
250267
}
251268

252269
#[derive(Debug)]
@@ -804,7 +821,8 @@ impl<'a> From<Arc<InputReceiver>> for InputIteratorInner<'a> {
804821
_receiver: receiver,
805822
buffered,
806823
native_app,
807-
text_event_checked: false,
824+
ime_text_input_state_checked: false,
825+
ime_editor_action_checked: false,
808826
}
809827
}
810828
}
@@ -821,7 +839,8 @@ pub(crate) struct InputIteratorInner<'a> {
821839

822840
buffered: Option<BufferedEvents<'a>>,
823841
native_app: NativeAppGlue,
824-
text_event_checked: bool,
842+
ime_text_input_state_checked: bool,
843+
ime_editor_action_checked: bool,
825844
}
826845

827846
impl InputIteratorInner<'_> {
@@ -841,8 +860,10 @@ impl InputIteratorInner<'_> {
841860
self.buffered = None;
842861
}
843862

844-
if !self.text_event_checked {
845-
self.text_event_checked = true;
863+
// We make sure any input state changes are sent before we check
864+
// for editor actions, so actions will apply to the latest state.
865+
if !self.ime_text_input_state_checked {
866+
self.ime_text_input_state_checked = true;
846867
unsafe {
847868
let app_ptr = self.native_app.as_ptr();
848869

@@ -854,12 +875,21 @@ impl InputIteratorInner<'_> {
854875
// the compiler isn't reordering code so this gets flagged
855876
// before the java main thread really updates the state.
856877
if (*app_ptr).textInputState != 0 {
857-
let state = self.native_app.text_input_state(); // Will clear .textInputState
878+
let state = self.native_app.take_text_input_state(); // Will clear .textInputState
858879
let _ = callback(&InputEvent::TextEvent(state));
859880
return true;
860881
}
861882
}
862883
}
884+
885+
if !self.ime_editor_action_checked {
886+
self.ime_editor_action_checked = true;
887+
if let Some(action) = self.native_app.take_pending_editor_action() {
888+
let _ = callback(&InputEvent::TextAction(TextInputAction::from(action)));
889+
return true;
890+
}
891+
}
892+
863893
false
864894
}
865895
}

android-activity/src/input.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,33 @@ pub struct TextInputState {
907907
pub compose_region: Option<TextSpan>,
908908
}
909909

910+
// Represents the action button on a soft keyboard.
911+
#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
912+
#[non_exhaustive]
913+
#[repr(i32)]
914+
pub enum TextInputAction {
915+
/// Let receiver decide what logical action to perform
916+
Unspecified = 0,
917+
/// No action - receiver could instead interpret as an "enter" key that inserts a newline character
918+
None = 1,
919+
/// Navigate to the input location (such as a URL)
920+
Go = 2,
921+
/// Search based on the input text
922+
Search = 3,
923+
/// Send the input to the target
924+
Send = 4,
925+
/// Move to the next input field
926+
Next = 5,
927+
/// Indicate that input is done
928+
Done = 6,
929+
/// Move to the previous input field
930+
Previous = 7,
931+
932+
#[doc(hidden)]
933+
#[num_enum(catch_all)]
934+
__Unknown(i32),
935+
}
936+
910937
/// An exclusive, lending iterator for input events
911938
pub struct InputIterator<'a> {
912939
pub(crate) inner: crate::activity_impl::InputIteratorInner<'a>,

android-activity/src/native_activity/input.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,4 +434,5 @@ pub enum InputEvent<'a> {
434434
MotionEvent(self::MotionEvent<'a>),
435435
KeyEvent(self::KeyEvent<'a>),
436436
TextEvent(crate::input::TextInputState),
437+
TextAction(crate::input::TextInputAction),
437438
}

0 commit comments

Comments
 (0)