You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +00:00
Address API 35 UI behavior changes
- Fix issue on foldable where the embedded window would obscure the main window when launching - Fix edge-to-edge support for non-immersive apps / games - Add edge-to-edge export option to allow non-immersive apps / games to extend edge to edge
This commit is contained in:
@@ -598,6 +598,10 @@
|
||||
<member name="permissions/write_user_dictionary" type="bool" setter="" getter="">
|
||||
Allows an application to write to the user dictionary.
|
||||
</member>
|
||||
<member name="screen/edge_to_edge" type="bool" setter="" getter="">
|
||||
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.
|
||||
</member>
|
||||
<member name="screen/immersive_mode" type="bool" setter="" getter="">
|
||||
If [code]true[/code], hides the navigation and status bar. Set [method DisplayServer.window_set_mode] to change this at runtime.
|
||||
</member>
|
||||
|
||||
@@ -1054,7 +1054,6 @@ void EditorExportPlatformAndroid::_fix_themes_xml(const Ref<EditorExportPreset>
|
||||
|
||||
// Default/Reserved theme attributes.
|
||||
Dictionary main_theme_attributes;
|
||||
main_theme_attributes["android:windowDrawsSystemBarBackgrounds"] = "false";
|
||||
main_theme_attributes["android:windowSwipeToDismiss"] = bool_to_string(p_preset->get("gesture/swipe_to_dismiss"));
|
||||
main_theme_attributes["android:windowIsTranslucent"] = bool_to_string(should_be_transparent);
|
||||
if (should_be_transparent) {
|
||||
@@ -2163,6 +2162,7 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio
|
||||
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "screen/support_normal"), true));
|
||||
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::BOOL, "user_data_backup/allow"), false));
|
||||
|
||||
@@ -3078,6 +3078,11 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
|
||||
command_line_strings.push_back("--fullscreen");
|
||||
}
|
||||
|
||||
bool edge_to_edge = p_preset->get("screen/edge_to_edge");
|
||||
if (edge_to_edge) {
|
||||
command_line_strings.push_back("--edge_to_edge");
|
||||
}
|
||||
|
||||
bool debug_opengl = p_preset->get("graphics/opengl_debug");
|
||||
if (debug_opengl) {
|
||||
command_line_strings.push_back("--debug_opengl");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,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"
|
||||
|
||||
@@ -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
|
||||
@@ -215,9 +215,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
|
||||
@@ -273,16 +273,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) ||
|
||||
@@ -365,7 +378,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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user