Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Implement native input dialog support #98574

Merged
merged 1 commit into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
15 changes: 14 additions & 1 deletion platform/android/display_server_android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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());
Expand Down
5 changes: 5 additions & 0 deletions platform/android/display_server_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -116,6 +118,9 @@ class DisplayServerAndroid : public DisplayServer {
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;

Expand Down
2 changes: 2 additions & 0 deletions platform/android/java/lib/res/values/dimens.xml
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions platform/android/java/lib/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
29 changes: 28 additions & 1 deletion platform/android/java/lib/src/org/godotengine/godot/Godot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ public static native boolean initialize(Activity activity,
*/
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.
*/
Expand Down
8 changes: 8 additions & 0 deletions platform/android/java_godot_lib_jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions platform/android/java_godot_lib_jni.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions platform/android/java_godot_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;");
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions platform/android/java_godot_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class GodotJavaWrapper {
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;
Expand Down Expand Up @@ -103,6 +104,7 @@ class GodotJavaWrapper {
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;
Expand Down
Loading