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

Merge pull request #107742 from m4gr3d/address_api_35_ui_issues

Android: Address API 35 UI behavior changes
This commit is contained in:
Rémi Verschelde
2025-06-22 11:59:12 +02:00
9 changed files with 97 additions and 18 deletions

View File

@@ -3,7 +3,6 @@
<!-- GodotAppMainTheme is auto-generated during export. Manual changes will be overwritten.
To add custom attributes, use the "gradle_build/custom_theme_attributes" Android export option. -->
<style name="GodotAppMainTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowSwipeToDismiss">false</item>
<item name="android:windowIsTranslucent">false</item>
</style>

View File

@@ -30,11 +30,13 @@
package com.godot.game;
import org.godotengine.godot.Godot;
import org.godotengine.godot.GodotActivity;
import android.os.Bundle;
import android.util.Log;
import androidx.activity.EdgeToEdge;
import androidx.core.splashscreen.SplashScreen;
/**
@@ -54,9 +56,30 @@ public class GodotApp extends GodotActivity {
}
}
private final Runnable updateImmersiveAndEdgeToEdgeModes = () -> {
Godot godot = getGodot();
if (godot != null) {
godot.enableImmersiveMode(godot.isInImmersiveMode(), true);
godot.enableEdgeToEdge(godot.isInEdgeToEdgeMode(), true);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
SplashScreen.installSplashScreen(this);
EdgeToEdge.enable(this);
super.onCreate(savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
updateImmersiveAndEdgeToEdgeModes.run();
}
@Override
public void onGodotMainLoopStarted() {
super.onGodotMainLoopStarted();
runOnUiThread(updateImmersiveAndEdgeToEdgeModes);
}
}

View File

@@ -104,8 +104,7 @@
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:process=":EmbeddedGodotGame"
android:supportsPictureInPicture="true"
android:screenOrientation="userLandscape" />
android:supportsPictureInPicture="true" />
<activity
android:name=".GodotXRGame"
android:configChanges="layoutDirection|locale|orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode"

View File

@@ -45,9 +45,9 @@ import android.os.Process
import android.preference.PreferenceManager
import android.util.Log
import android.view.View
import android.view.WindowManager
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.annotation.CallSuper
import androidx.core.content.edit
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -222,9 +222,9 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
// Prevent the editor window from showing in the display cutout
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && getEditorWindowInfo() == EDITOR_MAIN_INFO) {
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
val editorWindowInfo = getEditorWindowInfo()
if (editorWindowInfo == EDITOR_MAIN_INFO || editorWindowInfo == RUN_GAME_INFO) {
enableEdgeToEdge()
}
// We exclude certain permissions from the set we request at startup, as they'll be
@@ -324,16 +324,29 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
}
}
private fun updateImmersiveAndEdgeToEdgeModes() {
val editorWindowInfo = getEditorWindowInfo()
if (editorWindowInfo == EDITOR_MAIN_INFO || editorWindowInfo == RUN_GAME_INFO) {
godot?.apply {
enableImmersiveMode(isInImmersiveMode(), true)
enableEdgeToEdge(isInEdgeToEdgeMode(), true)
}
}
}
override fun onGodotMainLoopStarted() {
super.onGodotMainLoopStarted()
runOnUiThread {
// Hide the loading indicator
editorLoadingIndicator?.visibility = View.GONE
updateImmersiveAndEdgeToEdgeModes()
}
}
override fun onResume() {
super.onResume()
updateImmersiveAndEdgeToEdgeModes()
if (getEditorWindowInfo() == EDITOR_MAIN_INFO &&
godot?.isEditorHint() == true &&
(editorMessageDispatcher.hasEditorConnection(EMBEDDED_RUN_GAME_INFO) ||
@@ -431,7 +444,7 @@ abstract class BaseGodotEditor : GodotActivity(), GameMenuFragment.GameMenuListe
// fullscreen mode, we want to remain in fullscreen mode.
// This doesn't apply to the play / game window since for that window fullscreen is
// controlled by the game logic.
val updatedArgs = if (editorWindowInfo == EDITOR_MAIN_INFO &&
val updatedArgs = if ((editorWindowInfo == EDITOR_MAIN_INFO || editorWindowInfo == RUN_GAME_INFO) &&
godot?.isInImmersiveMode() == true &&
!args.contains(FULLSCREEN_ARG) &&
!args.contains(FULLSCREEN_ARG_SHORT)

View File

@@ -87,8 +87,8 @@ class EmbeddedGodotGame : GodotGame() {
override fun setRequestedOrientation(requestedOrientation: Int) {
// Allow orientation change only if fullscreen mode is active
// or if the requestedOrientation is landscape (i.e switching to default).
if (isFullscreen || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE) {
// or if the requestedOrientation is unspecified (i.e switching to default).
if (isFullscreen || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
super.setRequestedOrientation(requestedOrientation)
} else {
// Cache the requestedOrientation to apply when switching to fullscreen.
@@ -155,7 +155,7 @@ class EmbeddedGodotGame : GodotGame() {
// Cache the last used orientation in fullscreen to reapply when re-entering fullscreen.
gameRequestedOrientation = requestedOrientation
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
updateWindowDimensions(layoutWidthInPx, layoutHeightInPx)
}

View File

@@ -1,9 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="GodotEditorTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="GodotGameTheme" parent="GodotEditorTheme">

View File

@@ -172,6 +172,7 @@ class Godot private constructor(val context: Context) {
private var commandLine : MutableList<String> = ArrayList<String>()
private var xrMode = XRMode.REGULAR
private val useImmersive = AtomicBoolean(false)
private val isEdgeToEdge = AtomicBoolean(false)
private var useDebugOpengl = false
private var darkMode = false
@@ -235,6 +236,8 @@ class Godot private constructor(val context: Context) {
xrMode = XRMode.OPENXR
} else if (commandLine[i] == "--debug_opengl") {
useDebugOpengl = true
} else if (commandLine[i] == "--edge_to_edge") {
isEdgeToEdge.set(true)
} else if (commandLine[i] == "--fullscreen") {
useImmersive.set(true)
newArgs.add(commandLine[i])
@@ -332,10 +335,45 @@ class Godot private constructor(val context: Context) {
return isNativeInitialized()
}
/**
* Enable edge-to-edge.
*
* Must be called from the UI thread.
*/
@JvmOverloads
fun enableEdgeToEdge(enabled: Boolean, override: Boolean = false) {
val window = getActivity()?.window ?: return
if (!isEdgeToEdge.compareAndSet(!enabled, enabled) && !override) {
return
}
val rootView = window.decorView
WindowCompat.setDecorFitsSystemWindows(window, !(isEdgeToEdge.get() || useImmersive.get()))
if (enabled) {
ViewCompat.setOnApplyWindowInsetsListener(rootView, null)
rootView.setPadding(0, 0, 0, 0)
} else {
val insetType = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
if (rootView.rootWindowInsets != null) {
val windowInsets = WindowInsetsCompat.toWindowInsetsCompat(rootView.rootWindowInsets)
val insets = windowInsets.getInsets(insetType)
rootView.setPadding(insets.left, insets.top, insets.right, insets.bottom)
}
ViewCompat.setOnApplyWindowInsetsListener(rootView) { v: View, insets: WindowInsetsCompat ->
val windowInsets = insets.getInsets(insetType)
v.setPadding(windowInsets.left, windowInsets.top, windowInsets.right, windowInsets.bottom)
WindowInsetsCompat.CONSUMED
}
}
}
/**
* Toggle immersive mode.
* Must be called from the UI thread.
*/
@JvmOverloads
fun enableImmersiveMode(enabled: Boolean, override: Boolean = false) {
val activity = getActivity() ?: return
val window = activity.window ?: return
@@ -344,7 +382,7 @@ class Godot private constructor(val context: Context) {
return
}
WindowCompat.setDecorFitsSystemWindows(window, !enabled)
WindowCompat.setDecorFitsSystemWindows(window, !(isEdgeToEdge.get() || useImmersive.get()))
val controller = WindowInsetsControllerCompat(window, window.decorView)
if (enabled) {
controller.hide(WindowInsetsCompat.Type.systemBars())
@@ -380,6 +418,9 @@ class Godot private constructor(val context: Context) {
@Keep
fun isInImmersiveMode() = useImmersive.get()
@Keep
fun isInEdgeToEdgeMode() = isEdgeToEdge.get()
/**
* Used to complete initialization of the view used by the engine for rendering.
*
@@ -551,7 +592,6 @@ class Godot private constructor(val context: Context) {
renderView?.onActivityResumed()
registerSensorsIfNeeded()
enableImmersiveMode(useImmersive.get(), true)
for (plugin in pluginRegistry.allPlugins) {
plugin.onMainResume()
}
@@ -704,7 +744,6 @@ class Godot private constructor(val context: Context) {
runOnHostThread {
registerSensorsIfNeeded()
enableImmersiveMode(useImmersive.get(), true)
}
for (plugin in pluginRegistry.allPlugins) {