Fix Return key events in LineEdit & TextEdit on Android

Depending on the device implementation, editor actions could be
received with different action ids or not at all for multi-line.

Added a parameter to virtual keyboards to properly handle single-line
and multi-line cases in all situations.

Single-line:
Input type set to text without multiline to make sure actions are sent.
IME options are set to DONE action to force action id consistency.

Multi-line:
Input type set to text and multiline to make sure enter triggers new lines.
Actions are disabled by the multiline flag, so '\n' characters are
handled in text changed callbacks.
This commit is contained in:
PouleyKetchoupp 2020-07-17 17:44:13 +02:00
parent 89f57ae122
commit c0b394572f
18 changed files with 49 additions and 30 deletions

View File

@ -1068,8 +1068,8 @@ bool _OS::has_virtual_keyboard() const {
return OS::get_singleton()->has_virtual_keyboard(); return OS::get_singleton()->has_virtual_keyboard();
} }
void _OS::show_virtual_keyboard(const String &p_existing_text) { void _OS::show_virtual_keyboard(const String &p_existing_text, bool p_multiline) {
OS::get_singleton()->show_virtual_keyboard(p_existing_text, Rect2()); OS::get_singleton()->show_virtual_keyboard(p_existing_text, Rect2(), p_multiline);
} }
void _OS::hide_virtual_keyboard() { void _OS::hide_virtual_keyboard() {
@ -1372,7 +1372,7 @@ void _OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("dump_memory_to_file", "file"), &_OS::dump_memory_to_file); ClassDB::bind_method(D_METHOD("dump_memory_to_file", "file"), &_OS::dump_memory_to_file);
ClassDB::bind_method(D_METHOD("dump_resources_to_file", "file"), &_OS::dump_resources_to_file); ClassDB::bind_method(D_METHOD("dump_resources_to_file", "file"), &_OS::dump_resources_to_file);
ClassDB::bind_method(D_METHOD("has_virtual_keyboard"), &_OS::has_virtual_keyboard); ClassDB::bind_method(D_METHOD("has_virtual_keyboard"), &_OS::has_virtual_keyboard);
ClassDB::bind_method(D_METHOD("show_virtual_keyboard", "existing_text"), &_OS::show_virtual_keyboard, DEFVAL("")); ClassDB::bind_method(D_METHOD("show_virtual_keyboard", "existing_text", "multiline"), &_OS::show_virtual_keyboard, DEFVAL(""), DEFVAL(false));
ClassDB::bind_method(D_METHOD("hide_virtual_keyboard"), &_OS::hide_virtual_keyboard); ClassDB::bind_method(D_METHOD("hide_virtual_keyboard"), &_OS::hide_virtual_keyboard);
ClassDB::bind_method(D_METHOD("get_virtual_keyboard_height"), &_OS::get_virtual_keyboard_height); ClassDB::bind_method(D_METHOD("get_virtual_keyboard_height"), &_OS::get_virtual_keyboard_height);
ClassDB::bind_method(D_METHOD("print_resources_in_use", "short"), &_OS::print_resources_in_use, DEFVAL(false)); ClassDB::bind_method(D_METHOD("print_resources_in_use", "short"), &_OS::print_resources_in_use, DEFVAL(false));

View File

@ -256,7 +256,7 @@ public:
void dump_resources_to_file(const String &p_file); void dump_resources_to_file(const String &p_file);
bool has_virtual_keyboard() const; bool has_virtual_keyboard() const;
void show_virtual_keyboard(const String &p_existing_text = ""); void show_virtual_keyboard(const String &p_existing_text = "", bool p_multiline = false);
void hide_virtual_keyboard(); void hide_virtual_keyboard();
int get_virtual_keyboard_height(); int get_virtual_keyboard_height();

View File

@ -226,7 +226,7 @@ bool OS::has_virtual_keyboard() const {
return false; return false;
} }
void OS::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) { void OS::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
} }
void OS::hide_virtual_keyboard() { void OS::hide_virtual_keyboard() {

View File

@ -388,7 +388,7 @@ public:
}; };
virtual bool has_virtual_keyboard() const; virtual bool has_virtual_keyboard() const;
virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard(); virtual void hide_virtual_keyboard();
// returns height of the currently shown virtual keyboard (0 if keyboard is hidden) // returns height of the currently shown virtual keyboard (0 if keyboard is hidden)

View File

@ -985,8 +985,12 @@
</return> </return>
<argument index="0" name="existing_text" type="String" default="&quot;&quot;"> <argument index="0" name="existing_text" type="String" default="&quot;&quot;">
</argument> </argument>
<argument index="1" name="multiline" type="bool" default="false">
</argument>
<description> <description>
Shows the virtual keyboard if the platform has one. The [code]existing_text[/code] parameter is useful for implementing your own LineEdit, as it tells the virtual keyboard what text has already been typed (the virtual keyboard uses it for auto-correct and predictions). Shows the virtual keyboard if the platform has one.
The [code]existing_text[/code] parameter is useful for implementing your own [LineEdit] or [TextEdit], as it tells the virtual keyboard what text has already been typed (the virtual keyboard uses it for auto-correct and predictions).
The [code]multiline[/code] parameter needs to be set to [code]true[/code] to be able to enter multiple lines of text, as in [TextEdit].
[b]Note:[/b] This method is implemented on Android, iOS and UWP. [b]Note:[/b] This method is implemented on Android, iOS and UWP.
</description> </description>
</method> </method>

View File

@ -493,9 +493,9 @@ public class GodotIO {
return (int)(metrics.density * 160f); return (int)(metrics.density * 160f);
} }
public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) { public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (edit != null) if (edit != null)
edit.showKeyboard(p_existing_text, p_max_input_length, p_cursor_start, p_cursor_end); edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
//InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE); //InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
//inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); //inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);

View File

@ -36,6 +36,7 @@ import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.text.InputFilter; import android.text.InputFilter;
import android.text.InputType;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
@ -58,7 +59,8 @@ public class GodotEditText extends EditText {
private GodotTextInputWrapper mInputWrapper; private GodotTextInputWrapper mInputWrapper;
private EditHandler sHandler = new EditHandler(this); private EditHandler sHandler = new EditHandler(this);
private String mOriginText; private String mOriginText;
private int mMaxInputLength; private int mMaxInputLength = Integer.MAX_VALUE;
private boolean mMultiline = false;
private static class EditHandler extends Handler { private static class EditHandler extends Handler {
private final WeakReference<GodotEditText> mEdit; private final WeakReference<GodotEditText> mEdit;
@ -95,7 +97,11 @@ public class GodotEditText extends EditText {
protected void initView() { protected void initView() {
this.setPadding(0, 0, 0, 0); this.setPadding(0, 0, 0, 0);
this.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); this.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE);
}
public boolean isMultiline() {
return mMultiline;
} }
private void handleMessage(final Message msg) { private void handleMessage(final Message msg) {
@ -115,6 +121,12 @@ public class GodotEditText extends EditText {
edit.mInputWrapper.setSelection(false); edit.mInputWrapper.setSelection(false);
} }
int inputType = InputType.TYPE_CLASS_TEXT;
if (edit.isMultiline()) {
inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE;
}
edit.setInputType(inputType);
edit.mInputWrapper.setOriginText(text); edit.mInputWrapper.setOriginText(text);
edit.addTextChangedListener(edit.mInputWrapper); edit.addTextChangedListener(edit.mInputWrapper);
final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); final InputMethodManager imm = (InputMethodManager)mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
@ -168,7 +180,7 @@ public class GodotEditText extends EditText {
// =========================================================== // ===========================================================
// Methods // Methods
// =========================================================== // ===========================================================
public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) { public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length; int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length;
if (p_cursor_start == -1) { // cursor position not given if (p_cursor_start == -1) { // cursor position not given
this.mOriginText = p_existing_text; this.mOriginText = p_existing_text;
@ -181,6 +193,8 @@ public class GodotEditText extends EditText {
this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end); this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end);
} }
this.mMultiline = p_multiline;
final Message msg = new Message(); final Message msg = new Message();
msg.what = HANDLER_OPEN_IME_KEYBOARD; msg.what = HANDLER_OPEN_IME_KEYBOARD;
msg.obj = this; msg.obj = this;

View File

@ -123,7 +123,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
public void run() { public void run() {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
int key = newChars[i]; int key = newChars[i];
if (key == '\n') { if ((key == '\n') && !mEdit.isMultiline()) {
// Return keys are handled through action events // Return keys are handled through action events
continue; continue;
} }
@ -151,7 +151,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
}); });
} }
if (pActionID == EditorInfo.IME_NULL) { if (pActionID == EditorInfo.IME_ACTION_DONE) {
// Enter key has been pressed // Enter key has been pressed
GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, true); GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, true);
GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, false); GodotLib.key(KeyEvent.KEYCODE_ENTER, 0, false);

View File

@ -53,7 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;"); _get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I"); _get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;"); _get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
_show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;III)V"); _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V");
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V"); _hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V"); _set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;"); _get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;");
@ -135,11 +135,11 @@ bool GodotIOJavaWrapper::has_vk() {
return (_show_keyboard != 0) && (_hide_keyboard != 0); return (_show_keyboard != 0) && (_hide_keyboard != 0);
} }
void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end) { void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (_show_keyboard) { if (_show_keyboard) {
JNIEnv *env = ThreadAndroid::get_env(); JNIEnv *env = ThreadAndroid::get_env();
jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_max_input_length, p_cursor_start, p_cursor_end); env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
} }
} }

View File

@ -73,7 +73,7 @@ public:
int get_screen_dpi(); int get_screen_dpi();
String get_unique_id(); String get_unique_id();
bool has_vk(); bool has_vk();
void show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end); void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end);
void hide_vk(); void hide_vk();
int get_vk_height(); int get_vk_height();
void set_vk_height(int p_height); void set_vk_height(int p_height);

View File

@ -558,10 +558,10 @@ int OS_Android::get_virtual_keyboard_height() const {
// return 0; // return 0;
} }
void OS_Android::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) { void OS_Android::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (godot_io_java->has_vk()) { if (godot_io_java->has_vk()) {
godot_io_java->show_vk(p_existing_text, p_max_input_length, p_cursor_start, p_cursor_end); godot_io_java->show_vk(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
} else { } else {
ERR_PRINT("Virtual keyboard not available"); ERR_PRINT("Virtual keyboard not available");

View File

@ -158,7 +158,7 @@ public:
virtual bool has_touchscreen_ui_hint() const; virtual bool has_touchscreen_ui_hint() const;
virtual bool has_virtual_keyboard() const; virtual bool has_virtual_keyboard() const;
virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard(); virtual void hide_virtual_keyboard();
virtual int get_virtual_keyboard_height() const; virtual int get_virtual_keyboard_height() const;

View File

@ -482,7 +482,7 @@ extern Error _shell_open(String p_uri);
extern void _set_keep_screen_on(bool p_enabled); extern void _set_keep_screen_on(bool p_enabled);
extern void _vibrate(); extern void _vibrate();
void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) { void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
_show_keyboard(p_existing_text); _show_keyboard(p_existing_text);
}; };

View File

@ -165,7 +165,7 @@ public:
virtual bool can_draw() const; virtual bool can_draw() const;
virtual bool has_virtual_keyboard() const; virtual bool has_virtual_keyboard() const;
virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard(); virtual void hide_virtual_keyboard();
virtual int get_virtual_keyboard_height() const; virtual int get_virtual_keyboard_height() const;

View File

@ -818,7 +818,7 @@ bool OS_UWP::has_virtual_keyboard() const {
return UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch; return UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch;
} }
void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) { void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
InputPane ^ pane = InputPane::GetForCurrentView(); InputPane ^ pane = InputPane::GetForCurrentView();
pane->TryShow(); pane->TryShow();
} }

View File

@ -239,7 +239,7 @@ public:
virtual bool has_touchscreen_ui_hint() const; virtual bool has_touchscreen_ui_hint() const;
virtual bool has_virtual_keyboard() const; virtual bool has_virtual_keyboard() const;
virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard(); virtual void hide_virtual_keyboard();
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);

View File

@ -129,9 +129,9 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
if (OS::get_singleton()->has_virtual_keyboard()) { if (OS::get_singleton()->has_virtual_keyboard()) {
if (selection.enabled) { if (selection.enabled) {
OS::get_singleton()->show_virtual_keyboard(text, get_global_rect(), max_length, selection.begin, selection.end); OS::get_singleton()->show_virtual_keyboard(text, get_global_rect(), false, max_length, selection.begin, selection.end);
} else { } else {
OS::get_singleton()->show_virtual_keyboard(text, get_global_rect(), max_length, cursor_pos); OS::get_singleton()->show_virtual_keyboard(text, get_global_rect(), false, max_length, cursor_pos);
} }
} }
} }
@ -326,6 +326,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
if (OS::get_singleton()->has_virtual_keyboard()) if (OS::get_singleton()->has_virtual_keyboard())
OS::get_singleton()->hide_virtual_keyboard(); OS::get_singleton()->hide_virtual_keyboard();
return;
} break; } break;
case KEY_BACKSPACE: { case KEY_BACKSPACE: {
@ -932,9 +933,9 @@ void LineEdit::_notification(int p_what) {
if (OS::get_singleton()->has_virtual_keyboard()) { if (OS::get_singleton()->has_virtual_keyboard()) {
if (selection.enabled) { if (selection.enabled) {
OS::get_singleton()->show_virtual_keyboard(text, get_global_rect(), max_length, selection.begin, selection.end); OS::get_singleton()->show_virtual_keyboard(text, get_global_rect(), false, max_length, selection.begin, selection.end);
} else { } else {
OS::get_singleton()->show_virtual_keyboard(text, get_global_rect(), max_length, cursor_pos); OS::get_singleton()->show_virtual_keyboard(text, get_global_rect(), false, max_length, cursor_pos);
} }
} }
} break; } break;

View File

@ -1741,7 +1741,7 @@ void TextEdit::_notification(int p_what) {
OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos); OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos);
if (OS::get_singleton()->has_virtual_keyboard()) if (OS::get_singleton()->has_virtual_keyboard())
OS::get_singleton()->show_virtual_keyboard(get_text(), get_global_rect()); OS::get_singleton()->show_virtual_keyboard(get_text(), get_global_rect(), true);
} break; } break;
case NOTIFICATION_FOCUS_EXIT: { case NOTIFICATION_FOCUS_EXIT: {