Allow controlling Android fullscreen from options (#10518)

This commit is contained in:
SomeTroglodyte 2023-11-18 21:38:36 +01:00 committed by GitHub
parent 0d077ee099
commit 98c3c446c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 136 additions and 76 deletions

View File

@ -812,23 +812,21 @@ Screen orientation =
Landscape (fixed) =
Portrait (fixed) =
Auto (sensor adjusted) =
Enable display cutout (requires restart) =
Max zoom out =
Enable using display cutout areas =
Hide system status and navigation bars =
Font family =
Font size multiplier =
Default Font =
Max zoom out =
Enable Easter Eggs =
Enlarge selected notifications =
Generate translation files =
Translation files are generated successfully. =
Fastlane files are generated successfully. =
Update Mod categories =
Enable Easter Eggs =
Enlarge selected notifications =
## Keys tab
Keys =
Please see the Tutorial. =

View File

@ -2,41 +2,19 @@ package com.unciv.app
import android.app.Activity
import android.content.pm.ActivityInfo
import android.database.ContentObserver
import android.os.Build
import android.os.Handler
import android.provider.Settings
import android.view.Display
import android.view.Display.Mode
import android.view.View
import android.view.WindowManager
import androidx.annotation.RequiresApi
import com.unciv.models.metadata.GameSettings
import com.unciv.models.translations.tr
import com.unciv.utils.Log
import com.unciv.utils.PlatformDisplay
import com.unciv.utils.ScreenMode
import com.unciv.utils.ScreenOrientation
class AndroidScreenMode(
private val modeId: Int) : ScreenMode {
private var name: String = "Default"
@RequiresApi(Build.VERSION_CODES.M)
constructor(mode: Mode) : this(mode.modeId) {
name = "${mode.physicalWidth}x${mode.physicalHeight} (${mode.refreshRate.toInt()}HZ)"
}
override fun getId(): Int {
return modeId
}
override fun toString(): String {
return name.tr()
}
}
class AndroidDisplay(private val activity: Activity) : PlatformDisplay {
private var display: Display? = null
@ -45,6 +23,7 @@ class AndroidDisplay(private val activity: Activity) : PlatformDisplay {
init {
// Fetch current display
@Suppress("DEPRECATION") // M..P should use the deprecated API
display = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> activity.display
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> activity.windowManager.defaultDisplay
@ -52,7 +31,7 @@ class AndroidDisplay(private val activity: Activity) : PlatformDisplay {
}
// Add default mode
displayModes[0] = AndroidScreenMode(0)
displayModes[AndroidScreenMode.defaultId] = AndroidScreenMode.default
// Add other supported modes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
@ -71,14 +50,31 @@ class AndroidDisplay(private val activity: Activity) : PlatformDisplay {
}
override fun setScreenMode(id: Int, settings: GameSettings) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activity.runOnUiThread {
val params = activity.window.attributes
params.preferredDisplayModeId = id
activity.window.attributes = params
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
activity.runOnUiThread {
val params = activity.window.attributes
params.preferredDisplayModeId = id
activity.window.attributes = params
}
}
override fun hasSystemUiVisibility() = true
override fun setSystemUiVisibility(hide: Boolean) {
activity.runOnUiThread {
setSystemUiVisibilityFromUiThread(hide)
}
}
internal fun setSystemUiVisibilityFromUiThread(hide: Boolean) {
@Suppress("DEPRECATION") // Avoids @RequiresApi(Build.VERSION_CODES.R)
activity.window.decorView.systemUiVisibility =
if (hide)
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
else
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
}
override fun hasCutout(): Boolean {
@ -95,15 +91,19 @@ class AndroidDisplay(private val activity: Activity) : PlatformDisplay {
}
override fun setCutout(enabled: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val params = activity.window.attributes
params.layoutInDisplayCutoutMode = when {
enabled -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
else -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
}
activity.window.attributes = params
activity.runOnUiThread {
setCutoutFromUiThread(enabled)
}
}
internal fun setCutoutFromUiThread(enabled: Boolean) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return
val params = activity.window.attributes
params.layoutInDisplayCutoutMode = when {
enabled -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
else -> WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
}
activity.window.attributes = params // This is the only line to actually need to be running on the Ui Thread
}
/*
@ -121,7 +121,6 @@ class AndroidDisplay(private val activity: Activity) : PlatformDisplay {
}
override fun setOrientation(orientation: ScreenOrientation) {
val mode = when (orientation) {
ScreenOrientation.Landscape -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
ScreenOrientation.Portrait -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
@ -133,4 +132,25 @@ class AndroidDisplay(private val activity: Activity) : PlatformDisplay {
activity.requestedOrientation = mode
}
class AndroidScreenMode private constructor(
private val modeId: Int,
private val name: String
) : ScreenMode {
@RequiresApi(Build.VERSION_CODES.M)
constructor(mode: Mode) : this(mode.modeId, "${mode.physicalWidth}x${mode.physicalHeight} (${mode.refreshRate.toInt()}Hz)")
override fun getId(): Int {
return modeId
}
override fun toString(): String {
return name.tr()
}
companion object {
const val defaultId = 0
val default: AndroidScreenMode
get() = AndroidScreenMode(defaultId, "Default")
}
}
}

View File

@ -23,7 +23,8 @@ open class AndroidLauncher : AndroidApplication() {
Log.backend = AndroidLogBackend(this)
// Setup Android display
Display.platform = AndroidDisplay(this)
val displayImpl = AndroidDisplay(this)
Display.platform = displayImpl
// Setup Android fonts
Fonts.fontImplementation = AndroidFont()
@ -32,18 +33,19 @@ open class AndroidLauncher : AndroidApplication() {
UncivFiles.saverLoader = AndroidSaverLoader(this)
UncivFiles.preferExternalStorage = true
val config = AndroidApplicationConfiguration().apply { useImmersiveMode = false }
val settings = UncivFiles.getSettingsForPlatformLaunchers(filesDir.path)
// Setup orientation, immersive mode and display cutout
displayImpl.setOrientation(settings.displayOrientation)
displayImpl.setCutoutFromUiThread(settings.androidCutout)
displayImpl.setSystemUiVisibilityFromUiThread(settings.androidHideSystemUi)
// Create notification channels for Multiplayer notificator
MultiplayerTurnCheckWorker.createNotificationChannels(applicationContext)
copyMods()
val config = AndroidApplicationConfiguration().apply { useImmersiveMode = true }
val settings = UncivFiles.getSettingsForPlatformLaunchers(filesDir.path)
// Setup orientation and display cutout
Display.setOrientation(settings.displayOrientation)
Display.setCutout(settings.androidCutout)
game = AndroidGame(this)
initialize(game, config)

View File

@ -100,6 +100,7 @@ class GameSettings {
var showAutosaves: Boolean = false
var androidCutout: Boolean = false
var androidHideSystemUi = true
var multiplayer = GameSettingsMultiplayer()

View File

@ -22,6 +22,7 @@ import com.unciv.models.metadata.ScreenSize
import com.unciv.models.translations.TranslationFileWriter
import com.unciv.models.translations.tr
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.disable
import com.unciv.ui.components.extensions.setFontColor
import com.unciv.ui.components.extensions.toCheckBox
@ -57,34 +58,48 @@ fun advancedTab(
val settings = optionsPopup.settings
addAutosaveTurnsSelectBox(this, settings)
addSeparator(Color.GRAY)
if (Display.hasOrientation()) {
if (Display.hasOrientation())
addOrientationSelectBox(this, optionsPopup)
}
if (Display.hasCutout()) {
if (Display.hasCutout())
addCutoutCheckbox(this, optionsPopup)
}
addMaxZoomSlider(this, settings)
if (Display.hasSystemUiVisibility())
addHideSystemUiCheckbox(this, optionsPopup)
addFontFamilySelect(this, settings, optionsPopup.selectBoxMinWidth, onFontChange)
addFontSizeMultiplier(this, settings, onFontChange)
addSeparator(Color.GRAY)
addTranslationGeneration(this, optionsPopup)
addSetUserId(this, settings)
addMaxZoomSlider(this, settings)
addEasterEggsCheckBox(this, settings)
addEnlargeNotificationsCheckBox(this, settings)
addSeparator(Color.GRAY)
addSetUserId(this, settings)
addTranslationGeneration(this, optionsPopup)
}
private fun addCutoutCheckbox(table: Table, optionsPopup: OptionsPopup) {
optionsPopup.addCheckbox(table, "Enable display cutout (requires restart)", optionsPopup.settings.androidCutout)
optionsPopup.addCheckbox(table, "Enable using display cutout areas", optionsPopup.settings.androidCutout)
{
optionsPopup.settings.androidCutout = it
Display.setCutout(it)
optionsPopup.reopenAfterDiplayLayoutChange()
}
}
private fun addHideSystemUiCheckbox(table: Table, optionsPopup: OptionsPopup) {
optionsPopup.addCheckbox(table, "Hide system status and navigation bars", optionsPopup.settings.androidHideSystemUi)
{
optionsPopup.settings.androidHideSystemUi = it
Display.setSystemUiVisibility(hide = it)
optionsPopup.reopenAfterDiplayLayoutChange()
}
}
@ -101,6 +116,7 @@ private fun addOrientationSelectBox(table: Table, optionsPopup: OptionsPopup) {
val orientation = selectBox.selected
settings.displayOrientation = orientation
Display.setOrientation(orientation)
optionsPopup.reopenAfterDiplayLayoutChange()
}
table.add(selectBox).minWidth(optionsPopup.selectBoxMinWidth).pad(10f).row()
@ -202,7 +218,7 @@ private fun addFontSizeMultiplier(
settings: GameSettings,
onFontChange: () -> Unit
) {
table.add("Font size multiplier".toLabel()).left().fillX()
table.add("Font size multiplier".toLabel()).left().fillX().padTop(5f)
val fontSizeSlider = UncivSlider(
0.7f, 1.5f, 0.05f,
@ -214,11 +230,11 @@ private fun addFontSizeMultiplier(
if (!fontSizeSlider.isDragging)
onFontChange()
}
table.add(fontSizeSlider).pad(5f).row()
table.add(fontSizeSlider).pad(5f).padTop(10f).row()
}
private fun addMaxZoomSlider(table: Table, settings: GameSettings) {
table.add("Max zoom out".tr()).left().fillX()
table.add("Max zoom out".tr()).left().fillX().padTop(5f)
val maxZoomSlider = UncivSlider(
2f, 6f, 1f,
initial = settings.maxWorldZoomOut
@ -227,7 +243,7 @@ private fun addMaxZoomSlider(table: Table, settings: GameSettings) {
if (GUI.isWorldLoaded())
GUI.getMap().reloadMaxZoom()
}
table.add(maxZoomSlider).pad(5f).row()
table.add(maxZoomSlider).pad(5f).padTop(10f).row()
}
private fun addTranslationGeneration(table: Table, optionsPopup: OptionsPopup) {

View File

@ -13,11 +13,14 @@ import com.unciv.ui.components.extensions.toCheckBox
import com.unciv.ui.components.widgets.TabbedPager
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.Popup
import com.unciv.ui.popups.hasOpenPopups
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.basescreen.RecreateOnResize
import com.unciv.ui.screens.mainmenuscreen.MainMenuScreen
import com.unciv.ui.screens.worldscreen.WorldScreen
import com.unciv.utils.Concurrency
import com.unciv.utils.withGLContext
import kotlinx.coroutines.delay
import kotlin.reflect.KMutableProperty0
/**
@ -160,6 +163,22 @@ class OptionsPopup(
}
}
/** Call if an option change might trigger a Screen.resize
*
* Does nothing if any Popup (which can only be this one) is still open after a short delay and context yield.
* Reason: A resize might relaunch the parent screen ([MainMenuScreen] is [RecreateOnResize]) and thus close this Popup.
*/
fun reopenAfterDiplayLayoutChange() {
Concurrency.run("Reload from options") {
delay(100)
withGLContext {
val screen = UncivGame.Current.screen ?: return@withGLContext
if (screen.hasOpenPopups()) return@withGLContext // e.g. Orientation auto to fixed while auto is already the new orientation
screen.openOptionsPopup(tabs.activePage)
}
}
}
fun addCheckbox(table: Table, text: String, initialState: Boolean, updateWorld: Boolean = false, newRow: Boolean = true, action: ((Boolean) -> Unit)) {
val checkbox = text.toCheckBox(initialState) {
action(it)

View File

@ -19,31 +19,35 @@ interface ScreenMode {
}
interface PlatformDisplay {
fun setScreenMode(id: Int, settings: GameSettings) {}
fun getScreenModes(): Map<Int, ScreenMode> { return hashMapOf() }
fun getScreenModes(): Map<Int, ScreenMode> = hashMapOf()
fun hasCutout(): Boolean { return false }
fun hasCutout(): Boolean = false
fun setCutout(enabled: Boolean) {}
fun hasOrientation(): Boolean { return false }
fun hasOrientation(): Boolean = false
fun setOrientation(orientation: ScreenOrientation) {}
fun hasUserSelectableSize(id: Int): Boolean = false
fun hasSystemUiVisibility(): Boolean = false
fun setSystemUiVisibility(hide: Boolean) {}
}
object Display {
lateinit var platform: PlatformDisplay
fun hasOrientation(): Boolean { return platform.hasOrientation() }
fun hasOrientation() = platform.hasOrientation()
fun setOrientation(orientation: ScreenOrientation) { platform.setOrientation(orientation) }
fun hasCutout(): Boolean { return platform.hasCutout() }
fun hasCutout() = platform.hasCutout()
fun setCutout(enabled: Boolean) { platform.setCutout(enabled) }
fun getScreenModes(): Map<Int, ScreenMode> { return platform.getScreenModes() }
fun getScreenModes() = platform.getScreenModes()
fun setScreenMode(id: Int, settings: GameSettings) { platform.setScreenMode(id, settings) }
fun hasUserSelectableSize(id: Int) = platform.hasUserSelectableSize(id)
fun hasSystemUiVisibility() = platform.hasSystemUiVisibility()
fun setSystemUiVisibility(hide: Boolean) = platform.setSystemUiVisibility(hide)
}