From 0506299c21abaac161c82c36bcaa1695aedf0ad7 Mon Sep 17 00:00:00 2001 From: Anish Mishra Date: Sat, 12 Jul 2025 22:16:29 +0530 Subject: [PATCH] Android: Add option to change background color and fix system bar regression --- .../EditorExportPlatformAndroid.xml | 3 ++ platform/android/export/export_plugin.cpp | 10 ++++++ .../java/app/src/com/godot/game/GodotApp.java | 7 ++-- .../org/godotengine/editor/BaseGodotEditor.kt | 7 ++-- .../lib/src/org/godotengine/godot/Godot.kt | 33 +++++++++++++++++++ 5 files changed, 54 insertions(+), 6 deletions(-) diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index 5acd121bd69..2b18cde3570 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -598,6 +598,9 @@ Allows an application to write to the user dictionary. + + The background color used for the root window. Default is [code]black[/code]. + If [code]true[/code], this makes the navigation and status bars translucent and allows the application content to extend edge to edge. [b]Note:[/b] You should ensure that none of the application content is occluded by system elements by using the [method DisplayServer.get_display_safe_area] and [method DisplayServer.get_display_cutouts] methods. diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 3e33706f372..8a650e4bb49 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1070,6 +1070,8 @@ void EditorExportPlatformAndroid::_fix_themes_xml(const Ref main_theme_attributes["android:windowIsTranslucent"] = bool_to_string(transparency_allowed); if (transparency_allowed) { main_theme_attributes["android:windowBackground"] = "@android:color/transparent"; + } else { + main_theme_attributes["android:windowBackground"] = "#" + p_preset->get("screen/background_color").operator Color().to_html(false); } Dictionary splash_theme_attributes; @@ -2179,6 +2181,7 @@ void EditorExportPlatformAndroid::get_export_options(List *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_large"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_xlarge"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/edge_to_edge"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "screen/background_color", PROPERTY_HINT_COLOR_NO_ALPHA), Color())); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data_backup/allow"), false)); @@ -3099,6 +3102,13 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Refget("screen/background_color").operator Color().to_html(false); + if (_is_transparency_allowed(p_preset) && p_preset->get("gradle_build/use_gradle_build")) { + background_color = "#00000000"; + } + command_line_strings.push_back("--background_color"); + command_line_strings.push_back(background_color); + bool debug_opengl = p_preset->get("graphics/opengl_debug"); if (debug_opengl) { command_line_strings.push_back("--debug_opengl"); diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java index d83741ddb62..dac80282726 100644 --- a/platform/android/java/app/src/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/com/godot/game/GodotApp.java @@ -56,11 +56,12 @@ public class GodotApp extends GodotActivity { } } - private final Runnable updateImmersiveAndEdgeToEdgeModes = () -> { + private final Runnable updateWindowAppearance = () -> { Godot godot = getGodot(); if (godot != null) { godot.enableImmersiveMode(godot.isInImmersiveMode(), true); godot.enableEdgeToEdge(godot.isInEdgeToEdgeMode(), true); + godot.setSystemBarsAppearance(); } }; @@ -74,12 +75,12 @@ public class GodotApp extends GodotActivity { @Override public void onResume() { super.onResume(); - updateImmersiveAndEdgeToEdgeModes.run(); + updateWindowAppearance.run(); } @Override public void onGodotMainLoopStarted() { super.onGodotMainLoopStarted(); - runOnUiThread(updateImmersiveAndEdgeToEdgeModes); + runOnUiThread(updateWindowAppearance); } } diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt index 622433db063..934c4827ae6 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt @@ -324,12 +324,13 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe } } - private fun updateImmersiveAndEdgeToEdgeModes() { + private fun updateWindowAppearance() { val editorWindowInfo = getEditorWindowInfo() if (editorWindowInfo == EDITOR_MAIN_INFO || editorWindowInfo == RUN_GAME_INFO) { godot?.apply { enableImmersiveMode(isInImmersiveMode(), true) enableEdgeToEdge(isInEdgeToEdgeMode(), true) + setSystemBarsAppearance() } } } @@ -339,13 +340,13 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe runOnUiThread { // Hide the loading indicator editorLoadingIndicator?.visibility = View.GONE - updateImmersiveAndEdgeToEdgeModes() + updateWindowAppearance() } } override fun onResume() { super.onResume() - updateImmersiveAndEdgeToEdgeModes() + updateWindowAppearance() if (getEditorWindowInfo() == EDITOR_MAIN_INFO && godot?.isEditorHint() == true && 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 55480677136..3311e7733de 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -38,6 +38,7 @@ import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Resources import android.graphics.Color +import android.graphics.drawable.ColorDrawable import android.hardware.Sensor import android.hardware.SensorManager import android.os.* @@ -47,6 +48,8 @@ import android.view.* import android.widget.FrameLayout import androidx.annotation.Keep import androidx.annotation.StringRes +import androidx.core.graphics.ColorUtils +import androidx.core.graphics.toColorInt import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsAnimationCompat @@ -187,6 +190,7 @@ class Godot private constructor(val context: Context) { private val isEdgeToEdge = AtomicBoolean(false) private var useDebugOpengl = false private var darkMode = false + private var backgroundColor: Int = Color.BLACK internal var containerLayout: FrameLayout? = null var renderView: GodotRenderView? = null @@ -255,6 +259,17 @@ class Godot private constructor(val context: Context) { } else if (commandLine[i] == "--fullscreen") { useImmersive.set(true) newArgs.add(commandLine[i]) + } else if (commandLine[i] == "--background_color") { + val colorStr = commandLine[i + 1] + try { + backgroundColor = colorStr.toColorInt() + Log.d(TAG, "background color = $backgroundColor") + } catch (e: java.lang.IllegalArgumentException) { + Log.d(TAG, "Failed to parse background color: $colorStr") + } + runOnHostThread { + getActivity()?.window?.decorView?.setBackgroundColor(backgroundColor) + } } else if (commandLine[i] == "--use_apk_expansion") { useApkExpansion = true } else if (hasExtra && commandLine[i] == "--apk_expansion_md5") { @@ -459,6 +474,24 @@ class Godot private constructor(val context: Context) { @Keep fun isInEdgeToEdgeMode() = isEdgeToEdge.get() + fun setSystemBarsAppearance() { + val window = getActivity()?.window ?: return + val isLight = ColorUtils.calculateLuminance(getWindowBackgroundColor(window)) > 0.5 + + val controller = WindowInsetsControllerCompat(window, window.decorView) + controller.isAppearanceLightNavigationBars = isLight + controller.isAppearanceLightStatusBars = isLight + } + + private fun getWindowBackgroundColor(window: Window): Int { + val background = window.decorView.background + return if (background is ColorDrawable) { + background.color + } else { + backgroundColor + } + } + /** * Used to complete initialization of the view used by the engine for rendering. *