Android: "Screen Mode" option (#8785)

* Android: "Screen Mode" option

* Fix broken tests, code cleanup

* Fix broken tests, code cleanup

* Fix broken tests, code cleanup

---------

Co-authored-by: vegeta1k95 <vfylfhby>
This commit is contained in:
vegeta1k95 2023-03-04 18:22:09 +01:00 committed by GitHub
parent c40b6159df
commit f55e010451
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 227 additions and 65 deletions

View File

@ -1380,7 +1380,7 @@ Movement cost =
for = for =
Missing translations: = Missing translations: =
Screen Size = Screen Size =
Screen Window = Screen Mode =
Windowed = Windowed =
Fullscreen = Fullscreen =
Tileset = Tileset =

View File

@ -0,0 +1,83 @@
package com.unciv.app
import android.app.Activity
import android.os.Build
import android.view.Display
import android.view.Display.Mode
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
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
private var displayModes: HashMap<Int, ScreenMode> = hashMapOf()
init {
// Fetch current display
display = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> activity.display
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> activity.windowManager.defaultDisplay
else -> null
}
// Add default mode
displayModes[0] = AndroidScreenMode(0)
// Add other supported modes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
fetchScreenModes()
}
@RequiresApi(Build.VERSION_CODES.M)
private fun fetchScreenModes() {
val display = display ?: return
for (mode in display.supportedModes)
displayModes[mode.modeId] = AndroidScreenMode(mode)
}
override fun getScreenModes(): Map<Int, ScreenMode> {
return displayModes
}
override fun getDefaultMode(): ScreenMode {
return displayModes[0]!!
}
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
}
}
}
}

View File

@ -25,13 +25,14 @@ import com.unciv.logic.event.EventBus
import com.unciv.ui.components.Fonts import com.unciv.ui.components.Fonts
import com.unciv.ui.screens.basescreen.UncivStage import com.unciv.ui.screens.basescreen.UncivStage
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.utils.Display
import com.unciv.utils.Log import com.unciv.utils.Log
import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.Concurrency
import java.io.File import java.io.File
open class AndroidLauncher : AndroidApplication() { open class AndroidLauncher : AndroidApplication() {
private var game: UncivGame? = null private var game: AndroidGame? = null
private var deepLinkedMultiplayerGame: String? = null private var deepLinkedMultiplayerGame: String? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -40,6 +41,9 @@ open class AndroidLauncher : AndroidApplication() {
// Setup Android logging // Setup Android logging
Log.backend = AndroidLogBackend() Log.backend = AndroidLogBackend()
// Setup Android display
Display.platform = AndroidDisplay(this)
// Setup Android fonts // Setup Android fonts
Fonts.fontImplementation = AndroidFont() Fonts.fontImplementation = AndroidFont()
@ -67,9 +71,6 @@ open class AndroidLauncher : AndroidApplication() {
val glView = (Gdx.graphics as AndroidGraphics).view as GLSurfaceView val glView = (Gdx.graphics as AndroidGraphics).view as GLSurfaceView
addScreenObscuredListener(glView) addScreenObscuredListener(glView)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
addScreenRefreshRateListener(glView)
} }
fun allowPortrait(allow: Boolean) { fun allowPortrait(allow: Boolean) {
@ -89,29 +90,6 @@ open class AndroidLauncher : AndroidApplication() {
} }
} }
/** Request the best available device frame rate for
* the game, as soon as OpenGL surface is created */
private fun addScreenRefreshRateListener(surfaceView: GLSurfaceView) {
surfaceView.holder.addCallback(object: SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val modes = display?.supportedModes ?: return
val bestRefreshRate = modes.maxOf { it.refreshRate }
holder.surface.setFrameRate(bestRefreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val display = windowManager.defaultDisplay
val modes = display?.supportedModes ?: return
val bestMode = modes.maxBy { it.refreshRate }
val params = window.attributes
params.preferredDisplayModeId = bestMode.modeId
window.attributes = params
}
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}
override fun surfaceDestroyed(holder: SurfaceHolder) {}
})
}
private fun addScreenObscuredListener(surfaceView: GLSurfaceView) { private fun addScreenObscuredListener(surfaceView: GLSurfaceView) {
surfaceView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { surfaceView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
/** [onGlobalLayout] gets triggered not only when the [windowVisibleDisplayFrame][View.getWindowVisibleDisplayFrame] changes, but also on other things. /** [onGlobalLayout] gets triggered not only when the [windowVisibleDisplayFrame][View.getWindowVisibleDisplayFrame] changes, but also on other things.

View File

@ -42,6 +42,7 @@ import com.unciv.ui.screens.worldscreen.WorldMapHolder
import com.unciv.ui.screens.worldscreen.WorldScreen import com.unciv.ui.screens.worldscreen.WorldScreen
import com.unciv.ui.screens.worldscreen.unit.UnitTable import com.unciv.ui.screens.worldscreen.unit.UnitTable
import com.unciv.utils.DebugUtils import com.unciv.utils.DebugUtils
import com.unciv.utils.Display
import com.unciv.utils.Log import com.unciv.utils.Log
import com.unciv.utils.PlatformSpecific import com.unciv.utils.PlatformSpecific
import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.Concurrency
@ -158,7 +159,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
* - Font (hence Fonts.resetFont() inside setSkin()) * - Font (hence Fonts.resetFont() inside setSkin())
*/ */
settings = files.getGeneralSettings() // needed for the screen settings = files.getGeneralSettings() // needed for the screen
settings.refreshScreenMode() Display.setScreenMode(settings.screenMode, settings)
setAsRootScreen(GameStartScreen()) // NOT dependent on any atlas or skin setAsRootScreen(GameStartScreen()) // NOT dependent on any atlas or skin
GameSounds.init() GameSounds.init()

View File

@ -9,6 +9,8 @@ import com.unciv.logic.multiplayer.FriendList
import com.unciv.models.UncivSound import com.unciv.models.UncivSound
import com.unciv.ui.components.FontFamilyData import com.unciv.ui.components.FontFamilyData
import com.unciv.ui.components.Fonts import com.unciv.ui.components.Fonts
import com.unciv.utils.Display
import com.unciv.utils.ScreenMode
import java.text.Collator import java.text.Collator
import java.time.Duration import java.time.Duration
import java.util.* import java.util.*
@ -25,11 +27,6 @@ enum class ScreenSize(val virtualWidth:Float, val virtualHeight:Float){
Huge(1500f,1000f) Huge(1500f,1000f)
} }
enum class ScreenWindow {
Windowed,
Fullscreen
}
class GameSettings { class GameSettings {
var mapAutoScroll: Boolean = false var mapAutoScroll: Boolean = false
@ -47,7 +44,7 @@ class GameSettings {
@Deprecated("Since 4.3.6 - replaces with screenSize") @Deprecated("Since 4.3.6 - replaces with screenSize")
var resolution: String = "900x600" var resolution: String = "900x600"
var screenSize:ScreenSize = ScreenSize.Small var screenSize:ScreenSize = ScreenSize.Small
var screenWindow: ScreenWindow = ScreenWindow.Windowed var screenMode: Int = 0
var tutorialsShown = HashSet<String>() var tutorialsShown = HashSet<String>()
var tutorialTasksCompleted = HashSet<String>() var tutorialTasksCompleted = HashSet<String>()
@ -154,24 +151,6 @@ class GameSettings {
fun getCollatorFromLocale(): Collator { fun getCollatorFromLocale(): Collator {
return Collator.getInstance(getCurrentLocale()) return Collator.getInstance(getCurrentLocale())
} }
fun refreshScreenMode() {
if (Gdx.app.type != Application.ApplicationType.Desktop)
return
when (screenWindow) {
ScreenWindow.Windowed -> {
Gdx.graphics.setWindowedMode(
windowState.width.coerceAtLeast(120),
windowState.height.coerceAtLeast(80))
}
ScreenWindow.Fullscreen -> {
Gdx.graphics.setFullscreenMode(Gdx.graphics.displayMode)
}
}
}
} }
enum class LocaleCode(var language: String, var country: String) { enum class LocaleCode(var language: String, var country: String) {

View File

@ -10,7 +10,6 @@ import com.unciv.GUI
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.metadata.GameSettings import com.unciv.models.metadata.GameSettings
import com.unciv.models.metadata.ScreenSize import com.unciv.models.metadata.ScreenSize
import com.unciv.models.metadata.ScreenWindow
import com.unciv.models.skins.SkinCache import com.unciv.models.skins.SkinCache
import com.unciv.models.tilesets.TileSetCache import com.unciv.models.tilesets.TileSetCache
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
@ -25,8 +24,8 @@ import com.unciv.ui.components.extensions.onChange
import com.unciv.ui.components.extensions.onClick import com.unciv.ui.components.extensions.onClick
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.components.extensions.toTextButton
import com.unciv.utils.Display
private val resolutionArray = com.badlogic.gdx.utils.Array(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000")) import com.unciv.utils.ScreenMode
fun displayTab( fun displayTab(
optionsPopup: OptionsPopup, optionsPopup: OptionsPopup,
@ -37,8 +36,9 @@ fun displayTab(
val settings = optionsPopup.settings val settings = optionsPopup.settings
addScreenModeSelectBox(this, settings, optionsPopup.selectBoxMinWidth)
if (Gdx.app.type == Application.ApplicationType.Desktop) { if (Gdx.app.type == Application.ApplicationType.Desktop) {
addFullscreenSelectBox(this, settings, optionsPopup.selectBoxMinWidth)
optionsPopup.addCheckbox(this, "Map mouse auto-scroll", settings.mapAutoScroll, true) { optionsPopup.addCheckbox(this, "Map mouse auto-scroll", settings.mapAutoScroll, true) {
settings.mapAutoScroll = it settings.mapAutoScroll = it
if (GUI.isWorldLoaded()) if (GUI.isWorldLoaded())
@ -140,16 +140,22 @@ private fun addUnitIconAlphaSlider(table: Table, settings: GameSettings, selectB
table.add(unitIconAlphaSlider).minWidth(selectBoxMinWidth).pad(10f).row() table.add(unitIconAlphaSlider).minWidth(selectBoxMinWidth).pad(10f).row()
} }
private fun addFullscreenSelectBox(table: Table, settings: GameSettings, selectBoxMinWidth: Float) { private fun addScreenModeSelectBox(table: Table, settings: GameSettings, selectBoxMinWidth: Float) {
table.add("Screen Window".toLabel()).left().fillX() table.add("Screen Mode".toLabel()).left().fillX()
val screenSizeSelectBox = TranslatedSelectBox(ScreenWindow.values().map { it.name }, settings.screenWindow.name,table.skin) val modes = Display.getScreenModes()
table.add(screenSizeSelectBox).minWidth(selectBoxMinWidth).pad(10f).row() val current: ScreenMode? = modes[settings.screenMode]
screenSizeSelectBox.onChange { val selectBox = SelectBox<ScreenMode>(table.skin)
settings.screenWindow = ScreenWindow.valueOf(screenSizeSelectBox.selected.value) selectBox.items = Array(modes.values.toTypedArray())
settings.refreshScreenMode() selectBox.selected = current
selectBox.onChange {
val mode = selectBox.selected
settings.screenMode = mode.getId()
Display.setScreenMode(mode.getId(), settings)
} }
table.add(selectBox).minWidth(selectBoxMinWidth).pad(10f).row()
} }
private fun addScreenSizeSelectBox(table: Table, settings: GameSettings, selectBoxMinWidth: Float, onResolutionChange: () -> Unit) { private fun addScreenSizeSelectBox(table: Table, settings: GameSettings, selectBoxMinWidth: Float, onResolutionChange: () -> Unit) {

View File

@ -0,0 +1,33 @@
package com.unciv.utils
import com.unciv.models.metadata.GameSettings
interface ScreenMode {
fun getId(): Int
}
interface PlatformDisplay {
fun setScreenMode(id: Int, settings: GameSettings) {}
fun getScreenModes(): Map<Int, ScreenMode> { return hashMapOf() }
fun getDefaultMode(): ScreenMode
}
object Display {
lateinit var platform: PlatformDisplay
fun getDefaultMode(): ScreenMode {
return platform.getDefaultMode()
}
fun getScreenModes(): Map<Int, ScreenMode> {
return platform.getScreenModes()
}
fun setScreenMode(id: Int, settings: GameSettings) {
platform.setScreenMode(id, settings)
}
}

View File

@ -0,0 +1,79 @@
package com.unciv.app.desktop
import com.badlogic.gdx.Gdx
import com.unciv.models.metadata.GameSettings
import com.unciv.models.translations.tr
import com.unciv.utils.PlatformDisplay
import com.unciv.utils.ScreenMode
enum class ScreenWindowType {
Windowed,
Borderless,
Fullscreen
}
class DesktopScreenMode(
private val modeId: Int,
val windowType: ScreenWindowType) : ScreenMode {
override fun getId(): Int {
return modeId
}
override fun toString(): String {
return when (windowType) {
ScreenWindowType.Windowed -> "Windowed".tr()
ScreenWindowType.Borderless -> "Borderless".tr()
ScreenWindowType.Fullscreen -> "Fullscreen".tr()
}
}
}
class DesktopDisplay : PlatformDisplay {
private val modes = HashMap<Int, DesktopScreenMode>()
init {
modes[0] = DesktopScreenMode(0, ScreenWindowType.Windowed)
modes[1] = DesktopScreenMode(1, ScreenWindowType.Fullscreen)
modes[2] = DesktopScreenMode(2, ScreenWindowType.Borderless)
}
override fun getDefaultMode(): ScreenMode {
return modes[0]!!
}
override fun getScreenModes(): Map<Int, ScreenMode> {
return modes
}
override fun setScreenMode(id: Int, settings: GameSettings) {
val mode = modes[id] ?: return
when (mode.windowType) {
ScreenWindowType.Fullscreen -> {
Gdx.graphics.setFullscreenMode(Gdx.graphics.displayMode)
}
ScreenWindowType.Windowed -> {
Gdx.graphics.setUndecorated(false)
Gdx.graphics.setWindowedMode(
settings.windowState.width.coerceAtLeast(120),
settings.windowState.height.coerceAtLeast(80)
)
}
ScreenWindowType.Borderless -> {
Gdx.graphics.setUndecorated(true)
Gdx.graphics.setWindowedMode(
settings.windowState.width.coerceAtLeast(120),
settings.windowState.height.coerceAtLeast(80)
)
}
}
}
}

View File

@ -10,6 +10,7 @@ import com.unciv.logic.files.UncivFiles
import com.unciv.models.metadata.ScreenSize import com.unciv.models.metadata.ScreenSize
import com.unciv.models.metadata.WindowState import com.unciv.models.metadata.WindowState
import com.unciv.ui.components.Fonts import com.unciv.ui.components.Fonts
import com.unciv.utils.Display
import com.unciv.utils.Log import com.unciv.utils.Log
import java.awt.GraphicsEnvironment import java.awt.GraphicsEnvironment
@ -21,6 +22,9 @@ internal object DesktopLauncher {
// Setup Desktop logging // Setup Desktop logging
Log.backend = DesktopLogBackend() Log.backend = DesktopLogBackend()
// Setup Desktop display
Display.platform = DesktopDisplay()
// Setup Desktop font // Setup Desktop font
Fonts.fontImplementation = DesktopFont() Fonts.fontImplementation = DesktopFont()
@ -63,7 +67,6 @@ internal object DesktopLauncher {
FileHandle(SETTINGS_FILE_NAME).writeString(json().toJson(settings), false) // so when we later open the game we get fullscreen FileHandle(SETTINGS_FILE_NAME).writeString(json().toJson(settings), false) // so when we later open the game we get fullscreen
} }
if (!isRunFromJAR) { if (!isRunFromJAR) {
UniqueDocsWriter().write() UniqueDocsWriter().write()
UiElementDocsWriter().write() UiElementDocsWriter().write()