Sfoglia il codice sorgente

Restore 'Toggle fullscreen' menu for the Android editor and clean up the immersive mode logic

Fredia Huya-Kouadio 11 mesi fa
parent
commit
923b0f2e56

+ 0 - 2
editor/editor_node.cpp

@@ -7358,11 +7358,9 @@ EditorNode::EditorNode() {
 
 	settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the user data folder (\"user://\")."));
 
-#ifndef ANDROID_ENABLED
 	ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11);
 	ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F);
 	settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN);
-#endif
 	settings_menu->add_separator();
 
 #ifndef ANDROID_ENABLED

+ 6 - 2
platform/android/display_server_android.cpp

@@ -455,11 +455,15 @@ Size2i DisplayServerAndroid::window_get_size_with_decorations(DisplayServer::Win
 }
 
 void DisplayServerAndroid::window_set_mode(DisplayServer::WindowMode p_mode, DisplayServer::WindowID p_window) {
-	// Not supported on Android.
+	OS_Android::get_singleton()->get_godot_java()->enable_immersive_mode(p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN);
 }
 
 DisplayServer::WindowMode DisplayServerAndroid::window_get_mode(DisplayServer::WindowID p_window) const {
-	return WINDOW_MODE_FULLSCREEN;
+	if (OS_Android::get_singleton()->get_godot_java()->is_in_immersive_mode()) {
+		return WINDOW_MODE_FULLSCREEN;
+	} else {
+		return WINDOW_MODE_MAXIMIZED;
+	}
 }
 
 bool DisplayServerAndroid::window_is_maximize_allowed(DisplayServer::WindowID p_window) const {

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

@@ -2700,7 +2700,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP
 
 	bool immersive = p_preset->get("screen/immersive_mode");
 	if (immersive) {
-		command_line_strings.push_back("--use_immersive");
+		command_line_strings.push_back("--fullscreen");
 	}
 
 	bool debug_opengl = p_preset->get("graphics/opengl_debug");

+ 2 - 2
platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt

@@ -176,7 +176,7 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) {
 		registerMessenger(senderId, senderMessenger)
 
 		// Register ourselves to the sender so that it can communicate with us.
-		registerSelfTo(pm, senderMessenger, editor.getEditorId())
+		registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId)
 	}
 
 	/**
@@ -185,7 +185,7 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) {
 	 */
 	fun getMessageDispatcherPayload(): Bundle {
 		return Bundle().apply {
-			putInt(KEY_EDITOR_ID, editor.getEditorId())
+			putInt(KEY_EDITOR_ID, editor.getEditorWindowInfo().windowId)
 			putParcelable(KEY_EDITOR_MESSENGER, Messenger(dispatcherHandler))
 		}
 	}

+ 25 - 3
platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt

@@ -40,6 +40,7 @@ import android.content.pm.PackageManager
 import android.os.*
 import android.util.Log
 import android.view.View
+import android.view.WindowManager
 import android.widget.Toast
 import androidx.annotation.CallSuper
 import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -78,6 +79,8 @@ open class GodotEditor : GodotActivity() {
 		protected val EXTRA_LAUNCH_IN_PIP = "launch_in_pip_requested"
 
 		// Command line arguments
+		private const val FULLSCREEN_ARG = "--fullscreen"
+		private const val FULLSCREEN_ARG_SHORT = "-f"
 		private const val EDITOR_ARG = "--editor"
 		private const val EDITOR_ARG_SHORT = "-e"
 		private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager"
@@ -116,11 +119,16 @@ open class GodotEditor : GodotActivity() {
 
 	override fun getGodotAppLayout() = R.layout.godot_editor_layout
 
-	internal open fun getEditorId() = EDITOR_MAIN_INFO.windowId
+	internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO
 
 	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
+		}
+
 		// We exclude certain permissions from the set we request at startup, as they'll be
 		// requested on demand based on use-cases.
 		PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
@@ -213,10 +221,24 @@ open class GodotEditor : GodotActivity() {
 	}
 
 	protected fun getNewGodotInstanceIntent(editorWindowInfo: EditorWindowInfo, args: Array<String>): Intent {
+		val updatedArgs = if (editorWindowInfo == EDITOR_MAIN_INFO &&
+			godot?.isInImmersiveMode() == true &&
+			!args.contains(FULLSCREEN_ARG) &&
+			!args.contains(FULLSCREEN_ARG_SHORT)
+		) {
+			// If we're launching an editor window (project manager or editor) and we're in
+			// 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.
+			args + FULLSCREEN_ARG
+		} else {
+			args
+		}
+
 		val newInstance = Intent()
 			.setComponent(ComponentName(this, editorWindowInfo.windowClassName))
 			.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-			.putExtra(EXTRA_COMMAND_LINE_PARAMS, args)
+			.putExtra(EXTRA_COMMAND_LINE_PARAMS, updatedArgs)
 
 		val launchPolicy = resolveLaunchPolicyIfNeeded(editorWindowInfo.launchPolicy)
 		val isPiPAvailable = if (editorWindowInfo.supportsPiPMode && hasPiPSystemFeature()) {
@@ -235,7 +257,7 @@ open class GodotEditor : GodotActivity() {
 			}
 		} else if (launchPolicy == LaunchPolicy.SAME) {
 			if (isPiPAvailable &&
-				(args.contains(BREAKPOINTS_ARG) || args.contains(BREAKPOINTS_ARG_SHORT))) {
+				(updatedArgs.contains(BREAKPOINTS_ARG) || updatedArgs.contains(BREAKPOINTS_ARG_SHORT))) {
 				Log.v(TAG, "Launching in PiP mode because of breakpoints")
 				newInstance.putExtra(EXTRA_LAUNCH_IN_PIP, true)
 			}

+ 1 - 1
platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt

@@ -128,7 +128,7 @@ class GodotGame : GodotEditor() {
 
 	override fun getGodotAppLayout() = R.layout.godot_game_layout
 
-	override fun getEditorId() = RUN_GAME_INFO.windowId
+	override fun getEditorWindowInfo() = RUN_GAME_INFO
 
 	override fun overrideOrientationRequest() = false
 

+ 68 - 46
platform/android/java/lib/src/org/godotengine/godot/Godot.kt

@@ -42,13 +42,16 @@ import android.hardware.Sensor
 import android.hardware.SensorManager
 import android.os.*
 import android.util.Log
+import android.util.TypedValue
 import android.view.*
 import android.widget.FrameLayout
 import androidx.annotation.Keep
 import androidx.annotation.StringRes
 import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsAnimationCompat
 import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
 import com.google.android.vending.expansion.downloader.*
 import org.godotengine.godot.error.Error
 import org.godotengine.godot.input.GodotEditText
@@ -105,36 +108,26 @@ class Godot(private val context: Context) {
 		GodotPluginRegistry.getPluginRegistry()
 	}
 
-	private val accelerometer_enabled = AtomicBoolean(false)
+	private val accelerometerEnabled = AtomicBoolean(false)
 	private val mAccelerometer: Sensor? by lazy {
 		mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
 	}
 
-	private val gravity_enabled = AtomicBoolean(false)
+	private val gravityEnabled = AtomicBoolean(false)
 	private val mGravity: Sensor? by lazy {
 		mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
 	}
 
-	private val magnetometer_enabled = AtomicBoolean(false)
+	private val magnetometerEnabled = AtomicBoolean(false)
 	private val mMagnetometer: Sensor? by lazy {
 		mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
 	}
 
-	private val gyroscope_enabled = AtomicBoolean(false)
+	private val gyroscopeEnabled = AtomicBoolean(false)
 	private val mGyroscope: Sensor? by lazy {
 		mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
 	}
 
-	private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int ->
-		if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) {
-			val decorView = requireActivity().window.decorView
-			decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
-					View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
-					View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
-					View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
-					View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-		}}
-
 	val tts = GodotTTS(context)
 	val directoryAccessHandler = DirectoryAccessHandler(context)
 	val fileAccessHandler = FileAccessHandler(context)
@@ -185,7 +178,7 @@ class Godot(private val context: Context) {
 	private var xrMode = XRMode.REGULAR
 	private var expansionPackPath: String = ""
 	private var useApkExpansion = false
-	private var useImmersive = false
+	private val useImmersive = AtomicBoolean(false)
 	private var useDebugOpengl = false
 	private var darkMode = false
 
@@ -254,15 +247,9 @@ class Godot(private val context: Context) {
 					xrMode = XRMode.OPENXR
 				} else if (commandLine[i] == "--debug_opengl") {
 					useDebugOpengl = true
-				} else if (commandLine[i] == "--use_immersive") {
-					useImmersive = true
-					window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
-							View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
-							View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
-							View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or  // hide nav bar
-							View.SYSTEM_UI_FLAG_FULLSCREEN or  // hide status bar
-							View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-					registerUiChangeListener()
+				} else if (commandLine[i] == "--fullscreen") {
+					useImmersive.set(true)
+					newArgs.add(commandLine[i])
 				} else if (commandLine[i] == "--use_apk_expansion") {
 					useApkExpansion = true
 				} else if (hasExtra && commandLine[i] == "--apk_expansion_md5") {
@@ -335,6 +322,54 @@ class Godot(private val context: Context) {
 		}
 	}
 
+	/**
+	 * Toggle immersive mode.
+	 * Must be called from the UI thread.
+	 */
+	private fun enableImmersiveMode(enabled: Boolean, override: Boolean = false) {
+		val activity = getActivity() ?: return
+		val window = activity.window ?: return
+
+		if (!useImmersive.compareAndSet(!enabled, enabled) && !override) {
+			return
+		}
+
+		WindowCompat.setDecorFitsSystemWindows(window, !enabled)
+		val controller = WindowInsetsControllerCompat(window, window.decorView)
+		if (enabled) {
+			controller.hide(WindowInsetsCompat.Type.systemBars())
+			controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+		} else {
+			val fullScreenThemeValue = TypedValue()
+			val hasStatusBar = if (activity.theme.resolveAttribute(android.R.attr.windowFullscreen, fullScreenThemeValue, true) && fullScreenThemeValue.type == TypedValue.TYPE_INT_BOOLEAN) {
+				fullScreenThemeValue.data == 0
+			} else {
+				// Fallback to checking the editor build
+				!isEditorBuild()
+			}
+
+			val types = if (hasStatusBar) {
+				WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.statusBars()
+			} else {
+				WindowInsetsCompat.Type.navigationBars()
+			}
+			controller.show(types)
+		}
+	}
+
+	/**
+	 * Invoked from the render thread to toggle the immersive mode.
+	 */
+	@Keep
+	private fun nativeEnableImmersiveMode(enabled: Boolean) {
+		runOnUiThread {
+			enableImmersiveMode(enabled)
+		}
+	}
+
+	@Keep
+	fun isInImmersiveMode() = useImmersive.get()
+
 	/**
 	 * Initializes the native layer of the Godot engine.
 	 *
@@ -552,15 +587,7 @@ class Godot(private val context: Context) {
 
 		renderView?.onActivityResumed()
 		registerSensorsIfNeeded()
-		if (useImmersive) {
-			val window = requireActivity().window
-			window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
-					View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
-					View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
-					View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or  // hide nav bar
-					View.SYSTEM_UI_FLAG_FULLSCREEN or  // hide status bar
-					View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-		}
+		enableImmersiveMode(useImmersive.get(), true)
 		for (plugin in pluginRegistry.allPlugins) {
 			plugin.onMainResume()
 		}
@@ -571,16 +598,16 @@ class Godot(private val context: Context) {
 			return
 		}
 
-		if (accelerometer_enabled.get() && mAccelerometer != null) {
+		if (accelerometerEnabled.get() && mAccelerometer != null) {
 			mSensorManager.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME)
 		}
-		if (gravity_enabled.get() && mGravity != null) {
+		if (gravityEnabled.get() && mGravity != null) {
 			mSensorManager.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME)
 		}
-		if (magnetometer_enabled.get() && mMagnetometer != null) {
+		if (magnetometerEnabled.get() && mMagnetometer != null) {
 			mSensorManager.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME)
 		}
-		if (gyroscope_enabled.get() && mGyroscope != null) {
+		if (gyroscopeEnabled.get() && mGyroscope != null) {
 			mSensorManager.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME)
 		}
 	}
@@ -696,10 +723,10 @@ class Godot(private val context: Context) {
 		Log.v(TAG, "OnGodotMainLoopStarted")
 		godotMainLoopStarted.set(true)
 
-		accelerometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer")))
-		gravity_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity")))
-		gyroscope_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
-		magnetometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
+		accelerometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer")))
+		gravityEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity")))
+		gyroscopeEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope")))
+		magnetometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer")))
 
 		runOnUiThread {
 			registerSensorsIfNeeded()
@@ -724,11 +751,6 @@ class Godot(private val context: Context) {
 		primaryHost?.onGodotRestartRequested(this)
 	}
 
-	private fun registerUiChangeListener() {
-		val decorView = requireActivity().window.decorView
-		decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener)
-	}
-
 	fun alert(
 		@StringRes messageResId: Int,
 		@StringRes titleResId: Int,

+ 20 - 0
platform/android/java_godot_wrapper.cpp

@@ -86,6 +86,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
 	_has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z");
 	_sign_apk = p_env->GetMethodID(godot_class, "nativeSignApk", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I");
 	_verify_apk = p_env->GetMethodID(godot_class, "nativeVerifyApk", "(Ljava/lang/String;)I");
+	_enable_immersive_mode = p_env->GetMethodID(godot_class, "nativeEnableImmersiveMode", "(Z)V");
+	_is_in_immersive_mode = p_env->GetMethodID(godot_class, "isInImmersiveMode", "()Z");
 }
 
 GodotJavaWrapper::~GodotJavaWrapper() {
@@ -465,3 +467,21 @@ Error GodotJavaWrapper::verify_apk(const String &p_apk_path) {
 		return ERR_UNCONFIGURED;
 	}
 }
+
+void GodotJavaWrapper::enable_immersive_mode(bool p_enabled) {
+	if (_enable_immersive_mode) {
+		JNIEnv *env = get_jni_env();
+		ERR_FAIL_NULL(env);
+		env->CallVoidMethod(godot_instance, _enable_immersive_mode, p_enabled);
+	}
+}
+
+bool GodotJavaWrapper::is_in_immersive_mode() {
+	if (_is_in_immersive_mode) {
+		JNIEnv *env = get_jni_env();
+		ERR_FAIL_NULL_V(env, false);
+		return env->CallBooleanMethod(godot_instance, _is_in_immersive_mode);
+	} else {
+		return false;
+	}
+}

+ 5 - 0
platform/android/java_godot_wrapper.h

@@ -77,6 +77,8 @@ private:
 	jmethodID _has_feature = nullptr;
 	jmethodID _sign_apk = nullptr;
 	jmethodID _verify_apk = nullptr;
+	jmethodID _enable_immersive_mode = nullptr;
+	jmethodID _is_in_immersive_mode = nullptr;
 
 public:
 	GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
@@ -122,6 +124,9 @@ public:
 	// Sign and verify apks
 	Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password);
 	Error verify_apk(const String &p_apk_path);
+
+	void enable_immersive_mode(bool p_enabled);
+	bool is_in_immersive_mode();
 };
 
 #endif // JAVA_GODOT_WRAPPER_H