Browse Source

Merge pull request #106709 from m4gr3d/fix_transparency_flags

Fix transparency background issue on Android
Rémi Verschelde 1 month ago
parent
commit
ae33da972b

+ 2 - 2
doc/classes/DisplayServer.xml

@@ -2543,7 +2543,7 @@
 			Display server supports [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url], which is commonly used for inputting Chinese/Japanese/Korean text. This is handled by the operating system, rather than by Godot. [b]Windows, macOS, Linux (X11)[/b]
 			Display server supports [url=https://en.wikipedia.org/wiki/Input_method]Input Method Editor[/url], which is commonly used for inputting Chinese/Japanese/Korean text. This is handled by the operating system, rather than by Godot. [b]Windows, macOS, Linux (X11)[/b]
 		</constant>
 		</constant>
 		<constant name="FEATURE_WINDOW_TRANSPARENCY" value="11" enum="Feature">
 		<constant name="FEATURE_WINDOW_TRANSPARENCY" value="11" enum="Feature">
-			Display server supports windows can use per-pixel transparency to make windows behind them partially or fully visible. [b]Windows, macOS, Linux (X11/Wayland)[/b]
+			Display server supports windows can use per-pixel transparency to make windows behind them partially or fully visible. [b]Windows, macOS, Linux (X11/Wayland), Android[/b]
 		</constant>
 		</constant>
 		<constant name="FEATURE_HIDPI" value="12" enum="Feature">
 		<constant name="FEATURE_HIDPI" value="12" enum="Feature">
 			Display server supports querying the operating system's display scale factor. This allows automatically detecting the hiDPI display [i]reliably[/i], instead of guessing based on the screen resolution and the display's reported DPI (which might be unreliable due to broken monitor EDID). [b]Windows, Linux (Wayland), macOS[/b]
 			Display server supports querying the operating system's display scale factor. This allows automatically detecting the hiDPI display [i]reliably[/i], instead of guessing based on the screen resolution and the display's reported DPI (which might be unreliable due to broken monitor EDID). [b]Windows, Linux (Wayland), macOS[/b]
@@ -3073,7 +3073,7 @@
 		<constant name="WINDOW_FLAG_TRANSPARENT" value="3" enum="WindowFlags">
 		<constant name="WINDOW_FLAG_TRANSPARENT" value="3" enum="WindowFlags">
 			The window background can be transparent.
 			The window background can be transparent.
 			[b]Note:[/b] This flag has no effect if [method is_window_transparency_available] returns [code]false[/code].
 			[b]Note:[/b] This flag has no effect if [method is_window_transparency_available] returns [code]false[/code].
-			[b]Note:[/b] Transparency support is implemented on Linux (X11/Wayland), macOS, and Windows, but availability might vary depending on GPU driver, display manager, and compositor capabilities.
+			[b]Note:[/b] Transparency support is implemented on Android, Linux (X11/Wayland), macOS, and Windows, but availability might vary depending on GPU driver, display manager, and compositor capabilities.
 		</constant>
 		</constant>
 		<constant name="WINDOW_FLAG_NO_FOCUS" value="4" enum="WindowFlags">
 		<constant name="WINDOW_FLAG_NO_FOCUS" value="4" enum="WindowFlags">
 			The window can't be focused. No-focus window will ignore all input, except mouse clicks.
 			The window can't be focused. No-focus window will ignore all input, except mouse clicks.

+ 12 - 2
platform/android/display_server_android.cpp

@@ -76,7 +76,7 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const {
 		//case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
 		//case FEATURE_NATIVE_DIALOG_FILE_EXTRA:
 		case FEATURE_NATIVE_DIALOG_FILE_MIME:
 		case FEATURE_NATIVE_DIALOG_FILE_MIME:
 		//case FEATURE_NATIVE_ICON:
 		//case FEATURE_NATIVE_ICON:
-		//case FEATURE_WINDOW_TRANSPARENCY:
+		case FEATURE_WINDOW_TRANSPARENCY:
 		case FEATURE_CLIPBOARD:
 		case FEATURE_CLIPBOARD:
 		case FEATURE_KEEP_SCREEN_ON:
 		case FEATURE_KEEP_SCREEN_ON:
 		case FEATURE_ORIENTATION:
 		case FEATURE_ORIENTATION:
@@ -592,7 +592,13 @@ void DisplayServerAndroid::window_set_flag(DisplayServer::WindowFlags p_flag, bo
 }
 }
 
 
 bool DisplayServerAndroid::window_get_flag(DisplayServer::WindowFlags p_flag, DisplayServer::WindowID p_window) const {
 bool DisplayServerAndroid::window_get_flag(DisplayServer::WindowFlags p_flag, DisplayServer::WindowID p_window) const {
-	return false;
+	switch (p_flag) {
+		case WindowFlags::WINDOW_FLAG_TRANSPARENT:
+			return is_window_transparency_available();
+
+		default:
+			return false;
+	}
 }
 }
 
 
 void DisplayServerAndroid::window_request_attention(DisplayServer::WindowID p_window) {
 void DisplayServerAndroid::window_request_attention(DisplayServer::WindowID p_window) {
@@ -961,3 +967,7 @@ void DisplayServerAndroid::set_native_icon(const String &p_filename) {
 void DisplayServerAndroid::set_icon(const Ref<Image> &p_icon) {
 void DisplayServerAndroid::set_icon(const Ref<Image> &p_icon) {
 	// NOT SUPPORTED
 	// NOT SUPPORTED
 }
 }
+
+bool DisplayServerAndroid::is_window_transparency_available() const {
+	return GLOBAL_GET_CACHED(bool, "display/window/per_pixel_transparency/allowed");
+}

+ 2 - 0
platform/android/display_server_android.h

@@ -256,6 +256,8 @@ public:
 	virtual void set_native_icon(const String &p_filename) override;
 	virtual void set_native_icon(const String &p_filename) override;
 	virtual void set_icon(const Ref<Image> &p_icon) override;
 	virtual void set_icon(const Ref<Image> &p_icon) override;
 
 
+	virtual bool is_window_transparency_available() const override;
+
 	DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
 	DisplayServerAndroid(const String &p_rendering_driver, WindowMode p_mode, DisplayServer::VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, int64_t p_parent_window, Error &r_error);
 	~DisplayServerAndroid();
 	~DisplayServerAndroid();
 };
 };

+ 19 - 1
platform/android/export/export_plugin.cpp

@@ -1055,6 +1055,10 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
 	store_string_at_path(manifest_path, manifest_text);
 	store_string_at_path(manifest_path, manifest_text);
 }
 }
 
 
+bool EditorExportPlatformAndroid::_should_be_transparent(const Ref<EditorExportPreset> &p_preset) const {
+	return (bool)get_project_setting(p_preset, "display/window/per_pixel_transparency/allowed");
+}
+
 void EditorExportPlatformAndroid::_fix_themes_xml(const Ref<EditorExportPreset> &p_preset) {
 void EditorExportPlatformAndroid::_fix_themes_xml(const Ref<EditorExportPreset> &p_preset) {
 	const String themes_xml_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join("res/values/themes.xml");
 	const String themes_xml_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join("res/values/themes.xml");
 
 
@@ -1063,15 +1067,22 @@ void EditorExportPlatformAndroid::_fix_themes_xml(const Ref<EditorExportPreset>
 		return;
 		return;
 	}
 	}
 
 
+	bool should_be_transparent = _should_be_transparent(p_preset);
+
 	// Default/Reserved theme attributes.
 	// Default/Reserved theme attributes.
 	Dictionary main_theme_attributes;
 	Dictionary main_theme_attributes;
 	main_theme_attributes["android:windowDrawsSystemBarBackgrounds"] = "false";
 	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: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) {
+		main_theme_attributes["android:windowBackground"] = "@android:color/transparent";
+	}
 
 
 	Dictionary splash_theme_attributes;
 	Dictionary splash_theme_attributes;
 	splash_theme_attributes["android:windowSplashScreenBackground"] = "@mipmap/icon_background";
 	splash_theme_attributes["android:windowSplashScreenBackground"] = "@mipmap/icon_background";
 	splash_theme_attributes["windowSplashScreenAnimatedIcon"] = "@mipmap/icon_foreground";
 	splash_theme_attributes["windowSplashScreenAnimatedIcon"] = "@mipmap/icon_foreground";
 	splash_theme_attributes["postSplashScreenTheme"] = "@style/GodotAppMainTheme";
 	splash_theme_attributes["postSplashScreenTheme"] = "@style/GodotAppMainTheme";
+	splash_theme_attributes["android:windowIsTranslucent"] = bool_to_string(should_be_transparent);
 
 
 	Dictionary custom_theme_attributes = p_preset->get("gradle_build/custom_theme_attributes");
 	Dictionary custom_theme_attributes = p_preset->get("gradle_build/custom_theme_attributes");
 
 
@@ -2976,7 +2987,8 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit
 		valid = false;
 		valid = false;
 	}
 	}
 
 
-	if (p_preset->get("gradle_build/use_gradle_build")) {
+	bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
+	if (gradle_build_enabled) {
 		String build_version_path = ExportTemplateManager::get_android_build_directory(p_preset).get_base_dir().path_join(".build_version");
 		String build_version_path = ExportTemplateManager::get_android_build_directory(p_preset).get_base_dir().path_join(".build_version");
 		Ref<FileAccess> f = FileAccess::open(build_version_path, FileAccess::READ);
 		Ref<FileAccess> f = FileAccess::open(build_version_path, FileAccess::READ);
 		if (f.is_valid()) {
 		if (f.is_valid()) {
@@ -2987,6 +2999,12 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit
 				err += "\n";
 				err += "\n";
 			}
 			}
 		}
 		}
+	} else {
+		if (_should_be_transparent(p_preset)) {
+			// Warning only, so don't override `valid`.
+			err += vformat(TTR("\"Use Gradle Build\" is required for transparent background on Android"));
+			err += "\n";
+		}
 	}
 	}
 
 
 	String target_sdk_str = p_preset->get("gradle_build/target_sdk");
 	String target_sdk_str = p_preset->get("gradle_build/target_sdk");

+ 2 - 0
platform/android/export/export_plugin.h

@@ -163,6 +163,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
 
 
 	void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug);
 	void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug);
 
 
+	bool _should_be_transparent(const Ref<EditorExportPreset> &p_preset) const;
+
 	void _fix_themes_xml(const Ref<EditorExportPreset> &p_preset);
 	void _fix_themes_xml(const Ref<EditorExportPreset> &p_preset);
 
 
 	void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet);
 	void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet);

+ 2 - 0
platform/android/java/app/res/values/themes.xml

@@ -5,6 +5,7 @@
 	<style name="GodotAppMainTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
 	<style name="GodotAppMainTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
 		<item name="android:windowDrawsSystemBarBackgrounds">false</item>
 		<item name="android:windowDrawsSystemBarBackgrounds">false</item>
 		<item name="android:windowSwipeToDismiss">false</item>
 		<item name="android:windowSwipeToDismiss">false</item>
+		<item name="android:windowIsTranslucent">false</item>
 	</style>
 	</style>
 
 
 	<!-- GodotAppSplashTheme is auto-generated during export. Manual changes will be overwritten.
 	<!-- GodotAppSplashTheme is auto-generated during export. Manual changes will be overwritten.
@@ -13,5 +14,6 @@
 		<item name="android:windowSplashScreenBackground">@mipmap/icon_background</item>
 		<item name="android:windowSplashScreenBackground">@mipmap/icon_background</item>
 		<item name="windowSplashScreenAnimatedIcon">@mipmap/icon_foreground</item>
 		<item name="windowSplashScreenAnimatedIcon">@mipmap/icon_foreground</item>
 		<item name="postSplashScreenTheme">@style/GodotAppMainTheme</item>
 		<item name="postSplashScreenTheme">@style/GodotAppMainTheme</item>
+		<item name="android:windowIsTranslucent">false</item>
 	</style>
 	</style>
 </resources>
 </resources>

+ 1 - 0
platform/android/java/editor/src/main/AndroidManifest.xml

@@ -70,6 +70,7 @@
             android:launchMode="singleTask"
             android:launchMode="singleTask"
             android:process=":GodotGame"
             android:process=":GodotGame"
             android:autoRemoveFromRecents="true"
             android:autoRemoveFromRecents="true"
+            android:theme="@style/GodotGameTheme"
             android:supportsPictureInPicture="true"
             android:supportsPictureInPicture="true"
             android:screenOrientation="userLandscape">
             android:screenOrientation="userLandscape">
             <layout
             <layout

+ 5 - 1
platform/android/java/editor/src/main/res/values/themes.xml

@@ -2,10 +2,14 @@
 <resources>
 <resources>
 	<style name="GodotEditorTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
 	<style name="GodotEditorTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
 		<item name="android:statusBarColor">@android:color/transparent</item>
 		<item name="android:statusBarColor">@android:color/transparent</item>
-		<item name ="android:navigationBarColor">@android:color/transparent</item>
+		<item name="android:navigationBarColor">@android:color/transparent</item>
 		<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
 		<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
 	</style>
 	</style>
 
 
+	<style name="GodotGameTheme" parent="GodotEditorTheme">
+		<item name="android:windowIsTranslucent">true</item>
+	</style>
+
 	<style name="GodotEditorSplashScreenTheme" parent="Theme.SplashScreen.IconBackground">
 	<style name="GodotEditorSplashScreenTheme" parent="Theme.SplashScreen.IconBackground">
 		<!-- Set the theme of the Activity that directly follows your splash
 		<!-- Set the theme of the Activity that directly follows your splash
 		screen. This is required. -->
 		screen. This is required. -->

+ 8 - 3
platform/android/java/lib/src/org/godotengine/godot/Godot.kt

@@ -478,19 +478,24 @@ class Godot(private val context: Context) {
 			editText.setBackgroundColor(Color.TRANSPARENT)
 			editText.setBackgroundColor(Color.TRANSPARENT)
 			// ...add to FrameLayout
 			// ...add to FrameLayout
 			containerLayout?.addView(editText)
 			containerLayout?.addView(editText)
+
+			// Check whether the render view should be made transparent
+			val shouldBeTransparent =
+				!isProjectManagerHint() && !isEditorHint() && java.lang.Boolean.parseBoolean(GodotLib.getGlobal("display/window/per_pixel_transparency/allowed"))
+			Log.d(TAG, "Render view should be transparent: $shouldBeTransparent")
 			renderView = if (usesVulkan()) {
 			renderView = if (usesVulkan()) {
 				if (meetsVulkanRequirements(activity.packageManager)) {
 				if (meetsVulkanRequirements(activity.packageManager)) {
-					GodotVulkanRenderView(host, this, godotInputHandler)
+					GodotVulkanRenderView(host, this, godotInputHandler, shouldBeTransparent)
 				} else if (canFallbackToOpenGL()) {
 				} else if (canFallbackToOpenGL()) {
 					// Fallback to OpenGl.
 					// Fallback to OpenGl.
-					GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl)
+					GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent)
 				} else {
 				} else {
 					throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message))
 					throw IllegalStateException(activity.getString(R.string.error_missing_vulkan_requirements_message))
 				}
 				}
 
 
 			} else {
 			} else {
 				// Fallback to OpenGl.
 				// Fallback to OpenGl.
-				GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl)
+				GodotGLRenderView(host, this, godotInputHandler, xrMode, useDebugOpengl, shouldBeTransparent)
 			}
 			}
 
 
 			if (host == primaryHost) {
 			if (host == primaryHost) {

+ 2 - 3
platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java

@@ -46,7 +46,6 @@ import android.content.res.AssetManager;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory;
 import android.graphics.PixelFormat;
 import android.graphics.PixelFormat;
-import android.os.Build;
 import android.text.TextUtils;
 import android.text.TextUtils;
 import android.util.SparseArray;
 import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.KeyEvent;
@@ -83,7 +82,7 @@ class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
 	private final GodotRenderer godotRenderer;
 	private final GodotRenderer godotRenderer;
 	private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
 	private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
 
 
-	public GodotGLRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler, XRMode xrMode, boolean useDebugOpengl) {
+	public GodotGLRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler, XRMode xrMode, boolean useDebugOpengl, boolean shouldBeTranslucent) {
 		super(host.getActivity());
 		super(host.getActivity());
 
 
 		this.host = host;
 		this.host = host;
@@ -91,7 +90,7 @@ class GodotGLRenderView extends GLSurfaceView implements GodotRenderView {
 		this.inputHandler = inputHandler;
 		this.inputHandler = inputHandler;
 		this.godotRenderer = new GodotRenderer();
 		this.godotRenderer = new GodotRenderer();
 		setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
 		setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
-		init(xrMode, false, useDebugOpengl);
+		init(xrMode, shouldBeTranslucent, useDebugOpengl);
 	}
 	}
 
 
 	@Override
 	@Override

+ 6 - 2
platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java

@@ -38,7 +38,7 @@ import android.annotation.SuppressLint;
 import android.content.res.AssetManager;
 import android.content.res.AssetManager;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory;
-import android.os.Build;
+import android.graphics.PixelFormat;
 import android.text.TextUtils;
 import android.text.TextUtils;
 import android.util.SparseArray;
 import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.KeyEvent;
@@ -57,7 +57,7 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
 	private final VkRenderer mRenderer;
 	private final VkRenderer mRenderer;
 	private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
 	private final SparseArray<PointerIcon> customPointerIcons = new SparseArray<>();
 
 
-	public GodotVulkanRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler) {
+	public GodotVulkanRenderView(GodotHost host, Godot godot, GodotInputHandler inputHandler, boolean shouldBeTranslucent) {
 		super(host.getActivity());
 		super(host.getActivity());
 
 
 		this.host = host;
 		this.host = host;
@@ -67,6 +67,10 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView {
 		setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
 		setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT));
 		setFocusableInTouchMode(true);
 		setFocusableInTouchMode(true);
 		setClickable(false);
 		setClickable(false);
+
+		if (shouldBeTranslucent) {
+			this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+		}
 	}
 	}
 
 
 	@Override
 	@Override