1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-06 12:20:30 +00:00

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.
This commit is contained in:
Anish Mishra
2025-01-03 09:24:38 +05:30
parent bdf625bd54
commit cb9ee099ac
14 changed files with 347 additions and 37 deletions

View File

@@ -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<String>) {
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

View File

@@ -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.
*/

View File

@@ -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<String>) {
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()
}
}
}
}