From cb9ee099ac4661a47efe8e7f3a5a08e3ef8c51c2 Mon Sep 17 00:00:00 2001 From: Anish Mishra Date: Fri, 3 Jan 2025 09:24:38 +0530 Subject: [PATCH] Android: Implement support for native dialog This adds support for DisplayServer::dialog_show() on Android, aligning it with the functionality already available on macOS and Windows. --- doc/classes/DisplayServer.xml | 2 +- platform/android/SCsub | 1 + platform/android/dialog_utils_jni.cpp | 52 +++++ platform/android/dialog_utils_jni.h | 41 ++++ platform/android/display_server_android.cpp | 15 +- platform/android/display_server_android.h | 5 + .../android/java/lib/res/values/dimens.xml | 6 +- .../lib/src/org/godotengine/godot/Godot.kt | 38 ++-- .../src/org/godotengine/godot/GodotLib.java | 5 - .../godotengine/godot/utils/DialogUtils.kt | 185 ++++++++++++++++++ platform/android/java_godot_lib_jni.cpp | 8 - platform/android/java_godot_lib_jni.h | 1 - platform/android/java_godot_wrapper.cpp | 23 +++ platform/android/java_godot_wrapper.h | 2 + 14 files changed, 347 insertions(+), 37 deletions(-) create mode 100644 platform/android/dialog_utils_jni.cpp create mode 100644 platform/android/dialog_utils_jni.h create mode 100644 platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 1f495b87117..f4b8523b01f 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -123,7 +123,7 @@ Shows a text dialog which uses the operating system's native look-and-feel. [param callback] should accept a single [int] parameter which corresponds to the index of the pressed button. - [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include macOS and Windows. + [b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature. Supported platforms include macOS, Windows, and Android. diff --git a/platform/android/SCsub b/platform/android/SCsub index 66c955252bd..d0928a937b3 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -29,6 +29,7 @@ android_files = [ "plugin/godot_plugin_jni.cpp", "rendering_context_driver_vulkan_android.cpp", "variant/callable_jni.cpp", + "dialog_utils_jni.cpp", ] env_android = env.Clone() diff --git a/platform/android/dialog_utils_jni.cpp b/platform/android/dialog_utils_jni.cpp new file mode 100644 index 00000000000..c1f8cea06d6 --- /dev/null +++ b/platform/android/dialog_utils_jni.cpp @@ -0,0 +1,52 @@ +/**************************************************************************/ +/* dialog_utils_jni.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "dialog_utils_jni.h" + +#include "display_server_android.h" +#include "jni_utils.h" + +extern "C" { + +JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_dialogCallback(JNIEnv *env, jclass clazz, jint p_button_index) { + DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton(); + if (ds) { + ds->emit_dialog_callback(p_button_index); + } +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_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); + } +} +} diff --git a/platform/android/dialog_utils_jni.h b/platform/android/dialog_utils_jni.h new file mode 100644 index 00000000000..7b2da46dc57 --- /dev/null +++ b/platform/android/dialog_utils_jni.h @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* dialog_utils_jni.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DIALOG_UTILS_JNI_H +#define DIALOG_UTILS_JNI_H + +#include + +extern "C" { +JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_dialogCallback(JNIEnv *env, jclass clazz, jint p_button_index); +JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_DialogUtils_inputDialogCallback(JNIEnv *env, jclass clazz, jstring p_text); +} + +#endif // DIALOG_UTILS_JNI_H diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index d624bbbeb2c..3a0323176d9 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -70,7 +70,7 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const { //case FEATURE_IME: case FEATURE_MOUSE: //case FEATURE_MOUSE_WARP: - //case FEATURE_NATIVE_DIALOG: + case FEATURE_NATIVE_DIALOG: case FEATURE_NATIVE_DIALOG_INPUT: case FEATURE_NATIVE_DIALOG_FILE: //case FEATURE_NATIVE_DIALOG_FILE_EXTRA: @@ -177,6 +177,19 @@ bool DisplayServerAndroid::clipboard_has() const { } } +Error DisplayServerAndroid::dialog_show(String p_title, String p_description, Vector p_buttons, const Callable &p_callback) { + GodotJavaWrapper *godot_java = OS_Android::get_singleton()->get_godot_java(); + ERR_FAIL_NULL_V(godot_java, FAILED); + dialog_callback = p_callback; + return godot_java->show_dialog(p_title, p_description, p_buttons); +} + +void DisplayServerAndroid::emit_dialog_callback(int p_button_index) { + if (dialog_callback.is_valid()) { + dialog_callback.call_deferred(p_button_index); + } +} + 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); diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 582f722fa5b..0d2caa07b7c 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -87,7 +87,9 @@ class DisplayServerAndroid : public DisplayServer { Callable system_theme_changed; + Callable dialog_callback; Callable input_dialog_callback; + Callable file_picker_callback; void _window_callback(const Callable &p_callable, const Variant &p_arg, bool p_deferred = false) const; @@ -119,6 +121,9 @@ public: virtual String clipboard_get() const override; virtual bool clipboard_has() const override; + virtual Error dialog_show(String p_title, String p_description, Vector p_buttons, const Callable &p_callback) override; + void emit_dialog_callback(int p_button_index); + 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); diff --git a/platform/android/java/lib/res/values/dimens.xml b/platform/android/java/lib/res/values/dimens.xml index 287d1c89204..cc0f0788aff 100644 --- a/platform/android/java/lib/res/values/dimens.xml +++ b/platform/android/java/lib/res/values/dimens.xml @@ -1,6 +1,8 @@ 48dp - 10dp - 5dp + 48dp + 10dp + 16dp + 8dp diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index fcbb8830f90..3fc3caed911 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -44,7 +44,6 @@ 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 @@ -65,6 +64,7 @@ import org.godotengine.godot.plugin.GodotPlugin import org.godotengine.godot.plugin.GodotPluginRegistry import org.godotengine.godot.tts.GodotTTS import org.godotengine.godot.utils.CommandLineFileParser +import org.godotengine.godot.utils.DialogUtils import org.godotengine.godot.utils.GodotNetUtils import org.godotengine.godot.utils.PermissionsUtil import org.godotengine.godot.utils.PermissionsUtil.requestPermission @@ -903,27 +903,27 @@ class Godot(private val context: Context) { } /** - * Popup a dialog to input text. + * This method shows a dialog with multiple buttons. + * + * @param title The title of the dialog. + * @param message The message displayed in the dialog. + * @param buttons An array of button labels to display. + */ + @Keep + private fun showDialog(title: String, message: String, buttons: Array) { + getActivity()?.let { DialogUtils.showDialog(it, title, message, buttons) } + } + + /** + * This method shows a dialog with a text input field, allowing the user to input text. + * + * @param title The title of the input dialog. + * @param message The message displayed in the input dialog. + * @param existingText The existing text that will be pre-filled in the input field. */ @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() - } + getActivity()?.let { DialogUtils.showInputDialog(it, title, message, existingText) } } @Keep diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 9af91924704..67aa3283be5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -235,11 +235,6 @@ 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 file picker closed. */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt new file mode 100644 index 00000000000..45ec72b7433 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/DialogUtils.kt @@ -0,0 +1,185 @@ +/**************************************************************************/ +/* DialogUtils.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.godotengine.godot.utils + +import android.app.Activity +import android.app.AlertDialog +import android.content.DialogInterface +import android.widget.Button +import android.widget.EditText +import android.widget.LinearLayout + +import org.godotengine.godot.R + +/** + * Utility class for managing dialogs. + */ +internal class DialogUtils { + companion object { + private val TAG = DialogUtils::class.java.simpleName + + /** + * Invoked on dialog button press. + */ + @JvmStatic + private external fun dialogCallback(buttonIndex: Int) + + /** + * Invoked on the input dialog submitted. + */ + @JvmStatic + private external fun inputDialogCallback(text: String) + + /** + * Displays a dialog with dynamically arranged buttons based on their text length. + * + * The buttons are laid out in rows, with a maximum of 2 buttons per row. If a button's text + * is too long to fit within half the screen width, it occupies the entire row. + * + * @param activity The activity where the dialog will be displayed. + * @param title The title of the dialog. + * @param message The message displayed in the dialog. + * @param buttons An array of button labels to display. + */ + fun showDialog(activity: Activity, title: String, message: String, buttons: Array) { + var dismissDialog: () -> Unit = {} // Helper to dismiss the Dialog when a button is clicked. + activity.runOnUiThread { + val builder = AlertDialog.Builder(activity) + builder.setTitle(title) + builder.setMessage(message) + + val buttonHeight = activity.resources.getDimensionPixelSize(R.dimen.button_height) + val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_horizontal) + val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_vertical) + val buttonPadding = activity.resources.getDimensionPixelSize(R.dimen.button_padding) + + // Create a vertical parent layout to hold all rows of buttons. + val parentLayout = LinearLayout(activity) + parentLayout.orientation = LinearLayout.VERTICAL + parentLayout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical) + + // Horizontal row layout for arranging buttons. + var rowLayout = LinearLayout(activity) + rowLayout.orientation = LinearLayout.HORIZONTAL + rowLayout.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + + // Calculate the maximum width for a button to allow two buttons per row. + val screenWidth = activity.resources.displayMetrics.widthPixels + val availableWidth = screenWidth - (2 * paddingHorizontal) + val maxButtonWidth = availableWidth / 2 + + buttons.forEachIndexed { index, buttonLabel -> + val button = Button(activity) + button.text = buttonLabel + button.isSingleLine = true + button.setPadding(buttonPadding, buttonPadding, buttonPadding, buttonPadding) + + // Measure the button to determine its width. + button.measure(0, 0) + val buttonWidth = button.measuredWidth + + val params = LinearLayout.LayoutParams( + if (buttonWidth > maxButtonWidth) LinearLayout.LayoutParams.MATCH_PARENT else 0, + buttonHeight + ) + params.weight = if (buttonWidth > maxButtonWidth) 0f else 1f + button.layoutParams = params + + // Handle full-width buttons by finalizing the current row, if needed. + if (buttonWidth > maxButtonWidth) { + if (rowLayout.childCount > 0) { + parentLayout.addView(rowLayout) + rowLayout = LinearLayout(activity) + rowLayout.orientation = LinearLayout.HORIZONTAL + } + // Add the full-width button directly to the parent layout. + parentLayout.addView(button) + } else { + rowLayout.addView(button) + + // Finalize the row if it reaches 2 buttons. + if (rowLayout.childCount == 2) { + parentLayout.addView(rowLayout) + rowLayout = LinearLayout(activity) + rowLayout.orientation = LinearLayout.HORIZONTAL + } + + // Handle the last button with incomplete row. + if (index == buttons.size - 1 && rowLayout.childCount > 0) { + parentLayout.addView(rowLayout) + } + } + + button.setOnClickListener { + dialogCallback(index) + dismissDialog() + } + } + + // Attach the parent layout to the dialog. + builder.setView(parentLayout) + val dialog = builder.create() + dismissDialog = {dialog.dismiss()} + dialog.show() + } + } + + /** + * This method shows a dialog with a text input field, allowing the user to input text. + * + * @param activity The activity where the input dialog will be displayed. + * @param title The title of the input dialog. + * @param message The message displayed in the input dialog. + * @param existingText The existing text that will be pre-filled in the input field. + */ + fun showInputDialog(activity: Activity, title: String, message: String, existingText: String) { + val inputField = EditText(activity) + val paddingHorizontal = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_horizontal) + val paddingVertical = activity.resources.getDimensionPixelSize(R.dimen.dialog_padding_vertical) + inputField.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical) + inputField.setText(existingText) + activity.runOnUiThread { + val builder = AlertDialog.Builder(activity) + builder.setMessage(message).setTitle(title).setView(inputField) + builder.setPositiveButton(R.string.dialog_ok) { + dialog: DialogInterface, id: Int -> + inputDialogCallback(inputField.text.toString()) + dialog.dismiss() + } + val dialog = builder.create() + dialog.show() + } + } + } +} diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 023832a2d34..b291473e29d 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -494,14 +494,6 @@ 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_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths) { DisplayServerAndroid *ds = (DisplayServerAndroid *)DisplayServer::get_singleton(); if (ds) { diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 48d91795c7b..6feaec47c59 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -65,7 +65,6 @@ JNIEXPORT jstring JNICALL Java_org_godotengine_godot_GodotLib_getEditorSetting(J 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_filePickerCallback(JNIEnv *env, jclass clazz, jboolean p_ok, jobjectArray p_selected_paths); 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); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 69d7ac37ea6..19d31547975 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -69,6 +69,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_dialog = p_env->GetMethodID(godot_class, "showDialog", "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V"); _show_input_dialog = p_env->GetMethodID(godot_class, "showInputDialog", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); _show_file_picker = p_env->GetMethodID(godot_class, "showFilePicker", "(Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/String;)V"); _request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z"); @@ -303,6 +304,28 @@ bool GodotJavaWrapper::has_clipboard() { } } +Error GodotJavaWrapper::show_dialog(const String &p_title, const String &p_description, const Vector &p_buttons) { + if (_show_input_dialog) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, ERR_UNCONFIGURED); + jstring j_title = env->NewStringUTF(p_title.utf8().get_data()); + jstring j_description = env->NewStringUTF(p_description.utf8().get_data()); + jobjectArray j_buttons = env->NewObjectArray(p_buttons.size(), env->FindClass("java/lang/String"), nullptr); + for (int i = 0; i < p_buttons.size(); ++i) { + jstring j_button = env->NewStringUTF(p_buttons[i].utf8().get_data()); + env->SetObjectArrayElement(j_buttons, i, j_button); + env->DeleteLocalRef(j_button); + } + env->CallVoidMethod(godot_instance, _show_dialog, j_title, j_description, j_buttons); + env->DeleteLocalRef(j_title); + env->DeleteLocalRef(j_description); + env->DeleteLocalRef(j_buttons); + return OK; + } else { + return ERR_UNCONFIGURED; + } +} + 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(); diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 50612abf94a..1c74072e1a8 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -60,6 +60,7 @@ private: jmethodID _get_clipboard = nullptr; jmethodID _set_clipboard = nullptr; jmethodID _has_clipboard = nullptr; + jmethodID _show_dialog = nullptr; jmethodID _show_input_dialog = nullptr; jmethodID _show_file_picker = nullptr; jmethodID _request_permission = nullptr; @@ -109,6 +110,7 @@ public: void set_clipboard(const String &p_text); bool has_has_clipboard(); bool has_clipboard(); + Error show_dialog(const String &p_title, const String &p_description, const Vector &p_buttons); Error show_input_dialog(const String &p_title, const String &p_message, const String &p_existing_text); Error show_file_picker(const String &p_current_directory, const String &p_filename, int p_mode, const Vector &p_filters); bool request_permission(const String &p_name);