Browse Source

[Android] Implement native input dialog support

Anish Mishra 9 months ago
parent
commit
be5d7f757d

+ 1 - 1
doc/classes/DisplayServer.xml

@@ -105,7 +105,7 @@
 			<param index="3" name="callback" type="Callable" />
 			<description>
 				Shows a text input dialog which uses the operating system's native look-and-feel. [param callback] should accept a single [String] parameter which contains the text field's contents.
-				[b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_INPUT] feature. Supported platforms include macOS and Windows.
+				[b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG_INPUT] feature. Supported platforms include macOS, Windows, and Android.
 			</description>
 		</method>
 		<method name="dialog_show">

+ 14 - 1
platform/android/display_server_android.cpp

@@ -71,7 +71,7 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const {
 		case FEATURE_MOUSE:
 		//case FEATURE_MOUSE_WARP:
 		//case FEATURE_NATIVE_DIALOG:
-		//case FEATURE_NATIVE_DIALOG_INPUT:
+		case FEATURE_NATIVE_DIALOG_INPUT:
 		//case FEATURE_NATIVE_DIALOG_FILE:
 		//case FEATURE_NATIVE_ICON:
 		//case FEATURE_WINDOW_TRANSPARENCY:
@@ -176,6 +176,19 @@ bool DisplayServerAndroid::clipboard_has() const {
 	}
 }
 
+Error DisplayServerAndroid::dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) {
+	GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java();
+	ERR_FAIL_NULL_V(godot_java, FAILED);
+	input_dialog_callback = p_callback;
+	return godot_java->show_input_dialog(p_title, p_description, p_partial);
+}
+
+void DisplayServerAndroid::emit_input_dialog_callback(String p_text) {
+	if (input_dialog_callback.is_valid()) {
+		input_dialog_callback.call_deferred(p_text);
+	}
+}
+
 TypedArray<Rect2> DisplayServerAndroid::get_display_cutouts() const {
 	GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
 	ERR_FAIL_NULL_V(godot_io_java, Array());

+ 5 - 0
platform/android/display_server_android.h

@@ -87,6 +87,8 @@ class DisplayServerAndroid : public DisplayServer {
 
 	Callable system_theme_changed;
 
+	Callable input_dialog_callback;
+
 	void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const;
 
 	static void _dispatch_input_events(const Ref<InputEvent> &p_event);
@@ -116,6 +118,9 @@ public:
 	virtual String clipboard_get() const override;
 	virtual bool clipboard_has() const override;
 
+	virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override;
+	void emit_input_dialog_callback(String p_text);
+
 	virtual TypedArray<Rect2> get_display_cutouts() const override;
 	virtual Rect2i get_display_safe_area() const override;
 

+ 2 - 0
platform/android/java/lib/res/values/dimens.xml

@@ -1,4 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
 	<dimen name="text_edit_height">48dp</dimen>
+	<dimen name="input_dialog_padding_horizontal">10dp</dimen>
+	<dimen name="input_dialog_padding_vertical">5dp</dimen>
 </resources>

+ 3 - 0
platform/android/java/lib/res/values/strings.xml

@@ -55,4 +55,7 @@
     <string name="kilobytes_per_second">%1$s KB/s</string>
     <string name="time_remaining">Time remaining: %1$s</string>
     <string name="time_remaining_notification">%1$s left</string>
+
+	<!-- Labels for the dialog action buttons -->
+	<string name="dialog_ok">OK</string>
 </resources>

+ 28 - 1
platform/android/java/lib/src/org/godotengine/godot/Godot.kt

@@ -44,6 +44,7 @@ import android.os.*
 import android.util.Log
 import android.util.TypedValue
 import android.view.*
+import android.widget.EditText
 import android.widget.FrameLayout
 import androidx.annotation.Keep
 import androidx.annotation.StringRes
@@ -81,6 +82,7 @@ import java.util.*
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicReference
 
+
 /**
  * Core component used to interface with the native layer of the engine.
  *
@@ -772,7 +774,7 @@ class Godot(private val context: Context) {
 			val builder = AlertDialog.Builder(activity)
 			builder.setMessage(message).setTitle(title)
 			builder.setPositiveButton(
-				"OK"
+				R.string.dialog_ok
 			) { dialog: DialogInterface, id: Int ->
 				okCallback?.run()
 				dialog.cancel()
@@ -876,6 +878,31 @@ class Godot(private val context: Context) {
 		mClipboard.setPrimaryClip(clip)
 	}
 
+	/**
+	 * Popup a dialog to input text.
+	 */
+	@Keep
+	private fun showInputDialog(title: String, message: String, existingText: String) {
+		val activity: Activity = getActivity() ?: return
+		val inputField = EditText(activity)
+		val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.input_dialog_padding_horizontal)
+		val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.input_dialog_padding_vertical)
+		inputField.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
+		inputField.setText(existingText)
+		runOnUiThread {
+			val builder = AlertDialog.Builder(activity)
+			builder.setMessage(message).setTitle(title).setView(inputField)
+			builder.setPositiveButton(R.string.dialog_ok) {
+				dialog: DialogInterface, id: Int ->
+				GodotLib.inputDialogCallback(inputField.text.toString())
+				dialog.dismiss()
+			}
+			val dialog = builder.create()
+			dialog.show()
+		}
+	}
+
+
 	/**
 	 * Destroys the Godot Engine and kill the process it's running in.
 	 */

+ 5 - 0
platform/android/java/lib/src/org/godotengine/godot/GodotLib.java

@@ -224,6 +224,11 @@ public class GodotLib {
 	 */
 	public static native void onNightModeChanged();
 
+	/**
+	 * Invoked on the input dialog submitted.
+	 */
+	public static native void inputDialogCallback(String p_text);
+
 	/**
 	 * Invoked on the GL thread to configure the height of the virtual keyboard.
 	 */

+ 8 - 0
platform/android/java_godot_lib_jni.cpp

@@ -540,6 +540,14 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JN
 	}
 }
 
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text) {
+	DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton();
+	if (ds) {
+		String text = jstring_to_string(p_text, env);
+		ds->emit_input_dialog_callback(text);
+	}
+}
+
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) {
 	String permission = jstring_to_string(p_permission, env);
 	if (permission == "android.permission.RECORD_AUDIO" && p_result) {

+ 1 - 0
platform/android/java_godot_lib_jni.h

@@ -67,6 +67,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHeight(JNIEnv *env, jclass clazz, jint p_height);
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result);
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz);
+JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text);
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz);
 JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz);
 JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz);

+ 18 - 0
platform/android/java_godot_wrapper.cpp

@@ -67,6 +67,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
 	_get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;");
 	_set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V");
 	_has_clipboard = p_env->GetMethodID(godot_class, "hasClipboard", "()Z");
+	_show_input_dialog = p_env->GetMethodID(godot_class, "showInputDialog", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
 	_request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z");
 	_request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z");
 	_get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;");
@@ -268,6 +269,23 @@ bool GodotJavaWrapper::has_clipboard() {
 	}
 }
 
+Error GodotJavaWrapper::show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text) {
+	if (_show_input_dialog) {
+		JNIEnv *env = get_jni_env();
+		ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED);
+		jstring jStrTitle = env->NewStringUTF(p_title.utf8().get_data());
+		jstring jStrMessage = env->NewStringUTF(p_message.utf8().get_data());
+		jstring jStrExistingText = env->NewStringUTF(p_existing_text.utf8().get_data());
+		env->CallVoidMethod(godot_instance, _show_input_dialog, jStrTitle, jStrMessage, jStrExistingText);
+		env->DeleteLocalRef(jStrTitle);
+		env->DeleteLocalRef(jStrMessage);
+		env->DeleteLocalRef(jStrExistingText);
+		return OK;
+	} else {
+		return ERR_UNCONFIGURED;
+	}
+}
+
 bool GodotJavaWrapper::request_permission(const String &p_name) {
 	if (_request_permission) {
 		JNIEnv *env = get_jni_env();

+ 2 - 0
platform/android/java_godot_wrapper.h

@@ -58,6 +58,7 @@ private:
 	jmethodID _get_clipboard = nullptr;
 	jmethodID _set_clipboard = nullptr;
 	jmethodID _has_clipboard = nullptr;
+	jmethodID _show_input_dialog = nullptr;
 	jmethodID _request_permission = nullptr;
 	jmethodID _request_permissions = nullptr;
 	jmethodID _get_granted_permissions = nullptr;
@@ -103,6 +104,7 @@ public:
 	void set_clipboard(const String &p_text);
 	bool has_has_clipboard();
 	bool has_clipboard();
+	Error show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text);
 	bool request_permission(const String &p_name);
 	bool request_permissions();
 	Vector<String> get_granted_permissions() const;