[code quality] Reorg, clean up and comment a few things (#10527)

* UncivGame is a pure class file again, GUI split off

* Purify GameSettings step 1 - non-multiplayer nested classes

* Purify GameSettings step 2 - multiplayer nested classes

* Purify GameParameters - BaseRuleset to own file

* Rework WindowState to centralize minimum/maximum treatment

* Rename MultiplayerTurnNotifierDesktop to UncivWindowListener

* Clarifications on what the WindowListener actually does (and now the attention-getting does something on non-Windows too)
This commit is contained in:
SomeTroglodyte
2023-11-19 22:52:15 +01:00
committed by GitHub
parent 7f426a8b6f
commit 987f67d9cd
18 changed files with 316 additions and 222 deletions

83
core/src/com/unciv/GUI.kt Normal file
View File

@ -0,0 +1,83 @@
package com.unciv
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.unciv.logic.civilization.Civilization
import com.unciv.models.metadata.GameSettings
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.worldscreen.UndoHandler.Companion.clearUndoCheckpoints
import com.unciv.ui.screens.worldscreen.WorldMapHolder
import com.unciv.ui.screens.worldscreen.WorldScreen
import com.unciv.ui.screens.worldscreen.unit.UnitTable
object GUI {
fun setUpdateWorldOnNextRender() {
UncivGame.Current.worldScreen?.shouldUpdate = true
}
fun pushScreen(screen: BaseScreen) {
UncivGame.Current.pushScreen(screen)
}
fun resetToWorldScreen() {
UncivGame.Current.resetToWorldScreen()
}
fun getSettings(): GameSettings {
return UncivGame.Current.settings
}
fun isWorldLoaded(): Boolean {
return UncivGame.Current.worldScreen != null
}
fun isMyTurn(): Boolean {
if (!UncivGame.isCurrentInitialized() || !isWorldLoaded()) return false
return UncivGame.Current.worldScreen!!.isPlayersTurn
}
fun isAllowedChangeState(): Boolean {
return UncivGame.Current.worldScreen!!.canChangeState
}
fun getWorldScreen(): WorldScreen {
return UncivGame.Current.worldScreen!!
}
fun getWorldScreenIfActive(): WorldScreen? {
return UncivGame.Current.getWorldScreenIfActive()
}
fun getMap(): WorldMapHolder {
return UncivGame.Current.worldScreen!!.mapHolder
}
fun getUnitTable(): UnitTable {
return UncivGame.Current.worldScreen!!.bottomUnitTable
}
fun getViewingPlayer(): Civilization {
return UncivGame.Current.worldScreen!!.viewingCiv
}
fun getSelectedPlayer(): Civilization {
return UncivGame.Current.worldScreen!!.selectedCiv
}
/** Disable Undo (as in: forget the way back, but allow future undo checkpoints) */
fun clearUndoCheckpoints() {
UncivGame.Current.worldScreen?.clearUndoCheckpoints()
}
private var keyboardAvailableCache: Boolean? = null
/** Tests availability of a physical keyboard */
val keyboardAvailable: Boolean
get() {
// defer decision if Gdx.input not yet initialized
if (keyboardAvailableCache == null && Gdx.input != null)
keyboardAvailableCache = Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)
return keyboardAvailableCache ?: false
}
}

View File

@ -11,7 +11,6 @@ import com.badlogic.gdx.utils.Align
import com.unciv.logic.GameInfo
import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.UncivShowableException
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.files.UncivFiles
import com.unciv.logic.multiplayer.OnlineMultiplayer
@ -38,10 +37,7 @@ import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.mainmenuscreen.MainMenuScreen
import com.unciv.ui.screens.savescreens.LoadGameScreen
import com.unciv.ui.screens.worldscreen.PlayerReadyScreen
import com.unciv.ui.screens.worldscreen.UndoHandler.Companion.clearUndoCheckpoints
import com.unciv.ui.screens.worldscreen.WorldMapHolder
import com.unciv.ui.screens.worldscreen.WorldScreen
import com.unciv.ui.screens.worldscreen.unit.UnitTable
import com.unciv.utils.Concurrency
import com.unciv.utils.DebugUtils
import com.unciv.utils.Display
@ -57,78 +53,6 @@ import java.util.UUID
import kotlinx.coroutines.CancellationException
import kotlin.system.exitProcess
object GUI {
fun setUpdateWorldOnNextRender() {
UncivGame.Current.worldScreen?.shouldUpdate = true
}
fun pushScreen(screen: BaseScreen) {
UncivGame.Current.pushScreen(screen)
}
fun resetToWorldScreen() {
UncivGame.Current.resetToWorldScreen()
}
fun getSettings(): GameSettings {
return UncivGame.Current.settings
}
fun isWorldLoaded(): Boolean {
return UncivGame.Current.worldScreen != null
}
fun isMyTurn(): Boolean {
if (!UncivGame.isCurrentInitialized() || !isWorldLoaded()) return false
return UncivGame.Current.worldScreen!!.isPlayersTurn
}
fun isAllowedChangeState(): Boolean {
return UncivGame.Current.worldScreen!!.canChangeState
}
fun getWorldScreen(): WorldScreen {
return UncivGame.Current.worldScreen!!
}
fun getWorldScreenIfActive(): WorldScreen? {
return UncivGame.Current.getWorldScreenIfActive()
}
fun getMap(): WorldMapHolder {
return UncivGame.Current.worldScreen!!.mapHolder
}
fun getUnitTable(): UnitTable {
return UncivGame.Current.worldScreen!!.bottomUnitTable
}
fun getViewingPlayer(): Civilization {
return UncivGame.Current.worldScreen!!.viewingCiv
}
fun getSelectedPlayer(): Civilization {
return UncivGame.Current.worldScreen!!.selectedCiv
}
/** Disable Undo (as in: forget the way back, but allow future undo checkpoints) */
fun clearUndoCheckpoints() {
UncivGame.Current.worldScreen?.clearUndoCheckpoints()
}
private var keyboardAvailableCache: Boolean? = null
/** Tests availability of a physical keyboard */
val keyboardAvailable: Boolean
get() {
// defer decision if Gdx.input not yet initialized
if (keyboardAvailableCache == null && Gdx.input != null)
keyboardAvailableCache = Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)
return keyboardAvailableCache ?: false
}
}
open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpecific {
var deepLinkedMultiplayerGame: String? = null

View File

@ -0,0 +1,7 @@
package com.unciv.models.metadata
@Suppress("EnumEntryName") // These merit unusual names
enum class BaseRuleset(val fullName:String) {
Civ_V_Vanilla("Civ V - Vanilla"),
Civ_V_GnK("Civ V - Gods & Kings"),
}

View File

@ -4,13 +4,6 @@ import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.civilization.PlayerType
import com.unciv.models.ruleset.Speed
@Suppress("EnumEntryName") // These merit unusual names
enum class BaseRuleset(val fullName:String) {
Civ_V_Vanilla("Civ V - Vanilla"),
Civ_V_GnK("Civ V - Gods & Kings"),
}
class GameParameters : IsPartOfGameInfoSerialization { // Default values are the default new game
var difficulty = "Prince"
var speed = Speed.DEFAULT

View File

@ -19,20 +19,6 @@ import java.util.Locale
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty0
data class WindowState (val width: Int = 900, val height: Int = 600)
enum class ScreenSize(
@Suppress("unused") // Actual width determined by screen aspect ratio, this as comment only
val virtualWidth: Float,
val virtualHeight: Float
) {
Tiny(750f,500f),
Small(900f,600f),
Medium(1050f,700f),
Large(1200f,800f),
Huge(1500f,1000f)
}
class GameSettings {
/** Allows panning the map by moving the pointer to the screen edges */
@ -132,7 +118,6 @@ class GameSettings {
var enlargeSelectedNotification = true
/** Whether the Nation Picker shows icons only or the horizontal "civBlocks" with leader/nation name */
enum class NationPickerListMode { Icons, List }
var nationPickerListMode = NationPickerListMode.List
/** Size of automatic display of UnitSet art in Civilopedia - 0 to disable */
@ -148,14 +133,17 @@ class GameSettings {
}
}
//region <Methods>
fun save() {
refreshWindowSize()
UncivGame.Current.files.setGeneralSettings(this)
}
fun refreshWindowSize() {
if (isFreshlyCreated || Gdx.app.type != ApplicationType.Desktop) return
if (!Display.hasUserSelectableSize(screenMode)) return
windowState = WindowState(Gdx.graphics.width, Gdx.graphics.height)
windowState = WindowState.current()
}
fun addCompletedTutorialTask(tutorialTask: String): Boolean {
@ -189,99 +177,170 @@ class GameSettings {
fun getCollatorFromLocale(): Collator {
return Collator.getInstance(getCurrentLocale())
}
}
enum class LocaleCode(var language: String, var country: String) {
Arabic("ar", "IQ"),
Belarusian("be", "BY"),
BrazilianPortuguese("pt", "BR"),
Bulgarian("bg", "BG"),
Catalan("ca", "ES"),
Croatian("hr", "HR"),
Czech("cs", "CZ"),
Danish("da", "DK"),
Dutch("nl", "NL"),
English("en", "US"),
Estonian("et", "EE"),
Finnish("fi", "FI"),
French("fr", "FR"),
German("de", "DE"),
Greek("el", "GR"),
Hindi("hi", "IN"),
Hungarian("hu", "HU"),
Indonesian("in", "ID"),
Italian("it", "IT"),
Japanese("ja", "JP"),
Korean("ko", "KR"),
Latvian("lv", "LV"),
Lithuanian("lt", "LT"),
Malay("ms", "MY"),
Norwegian("no", "NO"),
NorwegianNynorsk("nn", "NO"),
PersianPinglishDIN("fa", "IR"), // These might just fall back to default
PersianPinglishUN("fa", "IR"),
Polish("pl", "PL"),
Portuguese("pt", "PT"),
Romanian("ro", "RO"),
Russian("ru", "RU"),
Serbian("sr", "RS"),
SimplifiedChinese("zh", "CN"),
Slovak("sk", "SK"),
Spanish("es", "ES"),
Swedish("sv", "SE"),
Thai("th", "TH"),
TraditionalChinese("zh", "TW"),
Turkish("tr", "TR"),
Ukrainian("uk", "UA"),
Vietnamese("vi", "VN"),
Afrikaans("af", "ZA")
}
//endregion
//region <Nested classes>
class GameSettingsMultiplayer {
var userId = ""
var passwords = mutableMapOf<String, String>()
@Suppress("unused") // @GGuenni knows what he intended with this field
var userName: String = ""
var server = Constants.uncivXyzServer
var friendList: MutableList<FriendList.Friend> = mutableListOf()
var turnCheckerEnabled = true
var turnCheckerPersistentNotificationEnabled = true
var turnCheckerDelay: Duration = Duration.ofMinutes(5)
var statusButtonInSinglePlayer = false
var currentGameRefreshDelay: Duration = Duration.ofSeconds(10)
var allGameRefreshDelay: Duration = Duration.ofMinutes(5)
var currentGameTurnNotificationSound: UncivSound = UncivSound.Silent
var otherGameTurnNotificationSound: UncivSound = UncivSound.Silent
var hideDropboxWarning = false
/**
* Knowledge on Window "state", limited.
* - Size: Saved
* - Iconified, Maximized: Not saved
* - Position / Multimonitor display choice: Not saved
*
* Note: Useful on desktop only, on Android we do not explicitly support `Activity.isInMultiWindowMode` returning true.
* (On Android Display.hasUserSelectableSize will return false, and AndroidLauncher & co ignore it)
*
* Open to future enhancement - but:
* retrieving a valid position from our upstream libraries while the window is maximized or iconified has proven tricky so far.
*/
data class WindowState(val width: Int = 900, val height: Int = 600) {
constructor(bounds: java.awt.Rectangle) : this(bounds.width, bounds.height)
fun getAuthHeader(): String {
val serverPassword = passwords[server] ?: ""
val preEncodedAuthValue = "$userId:$serverPassword"
return "Basic ${Base64Coder.encodeString(preEncodedAuthValue)}"
companion object {
/** Our choice of minimum window width */
const val minimumWidth = 120
/** Our choice of minimum window height */
const val minimumHeight = 80
fun current() = WindowState(Gdx.graphics.width, Gdx.graphics.height)
}
/**
* Constrains the dimensions of `this` [WindowState] to be within [minimumWidth] x [minimumHeight] to [maximumWidth] x [maximumHeight].
* @param maximumWidth defaults to unlimited
* @param maximumHeight defaults to unlimited
* @return `this` unchanged if it is within valid limits, otherwise a new WindowState that is.
*/
fun coerceIn(maximumWidth: Int = Int.MAX_VALUE, maximumHeight: Int = Int.MAX_VALUE): WindowState {
if (width in minimumWidth..maximumWidth && height in minimumHeight..maximumHeight)
return this
return WindowState(
width.coerceIn(minimumWidth, maximumWidth),
height.coerceIn(minimumHeight, maximumHeight)
)
}
/**
* Constrains the dimensions of `this` [WindowState] to be within [minimumWidth] x [minimumHeight] to `maximumWidth` x `maximumHeight`.
* @param maximumWindowBounds provides maximum sizes
* @return `this` unchanged if it is within valid limits, otherwise a new WindowState that is.
* @see coerceIn
*/
fun coerceIn(maximumWindowBounds: java.awt.Rectangle) =
coerceIn(maximumWindowBounds.width, maximumWindowBounds.height)
}
}
@Suppress("SuspiciousCallableReferenceInLambda") // By @Azzurite, safe as long as that warning below is followed
enum class GameSetting(
val kClass: KClass<*>,
private val propertyGetter: (GameSettings) -> KMutableProperty0<*>
) {
// Uncomment these once they are refactored to send events on change
enum class ScreenSize(
@Suppress("unused") // Actual width determined by screen aspect ratio, this as comment only
val virtualWidth: Float,
val virtualHeight: Float
) {
Tiny(750f,500f),
Small(900f,600f),
Medium(1050f,700f),
Large(1200f,800f),
Huge(1500f,1000f)
}
enum class NationPickerListMode { Icons, List }
enum class LocaleCode(var language: String, var country: String) {
Arabic("ar", "IQ"),
Belarusian("be", "BY"),
BrazilianPortuguese("pt", "BR"),
Bulgarian("bg", "BG"),
Catalan("ca", "ES"),
Croatian("hr", "HR"),
Czech("cs", "CZ"),
Danish("da", "DK"),
Dutch("nl", "NL"),
English("en", "US"),
Estonian("et", "EE"),
Finnish("fi", "FI"),
French("fr", "FR"),
German("de", "DE"),
Greek("el", "GR"),
Hindi("hi", "IN"),
Hungarian("hu", "HU"),
Indonesian("in", "ID"),
Italian("it", "IT"),
Japanese("ja", "JP"),
Korean("ko", "KR"),
Latvian("lv", "LV"),
Lithuanian("lt", "LT"),
Malay("ms", "MY"),
Norwegian("no", "NO"),
NorwegianNynorsk("nn", "NO"),
PersianPinglishDIN("fa", "IR"), // These might just fall back to default
PersianPinglishUN("fa", "IR"),
Polish("pl", "PL"),
Portuguese("pt", "PT"),
Romanian("ro", "RO"),
Russian("ru", "RU"),
Serbian("sr", "RS"),
SimplifiedChinese("zh", "CN"),
Slovak("sk", "SK"),
Spanish("es", "ES"),
Swedish("sv", "SE"),
Thai("th", "TH"),
TraditionalChinese("zh", "TW"),
Turkish("tr", "TR"),
Ukrainian("uk", "UA"),
Vietnamese("vi", "VN"),
Afrikaans("af", "ZA")
}
//endregion
//region Multiplayer-specific
class GameSettingsMultiplayer {
var userId = ""
var passwords = mutableMapOf<String, String>()
@Suppress("unused") // @GGuenni knows what he intended with this field
var userName: String = ""
var server = Constants.uncivXyzServer
var friendList: MutableList<FriendList.Friend> = mutableListOf()
var turnCheckerEnabled = true
var turnCheckerPersistentNotificationEnabled = true
var turnCheckerDelay: Duration = Duration.ofMinutes(5)
var statusButtonInSinglePlayer = false
var currentGameRefreshDelay: Duration = Duration.ofSeconds(10)
var allGameRefreshDelay: Duration = Duration.ofMinutes(5)
var currentGameTurnNotificationSound: UncivSound = UncivSound.Silent
var otherGameTurnNotificationSound: UncivSound = UncivSound.Silent
var hideDropboxWarning = false
fun getAuthHeader(): String {
val serverPassword = passwords[server] ?: ""
val preEncodedAuthValue = "$userId:$serverPassword"
return "Basic ${Base64Coder.encodeString(preEncodedAuthValue)}"
}
}
@Suppress("SuspiciousCallableReferenceInLambda") // By @Azzurite, safe as long as that warning below is followed
enum class GameSetting(
val kClass: KClass<*>,
private val propertyGetter: (GameSettings) -> KMutableProperty0<*>
) {
// Uncomment these once they are refactored to send events on change
// MULTIPLAYER_USER_ID(String::class, { it.multiplayer::userId }),
// MULTIPLAYER_SERVER(String::class, { it.multiplayer::server }),
// MULTIPLAYER_STATUSBUTTON_IN_SINGLEPLAYER(Boolean::class, { it.multiplayer::statusButtonInSinglePlayer }),
// MULTIPLAYER_TURN_CHECKER_ENABLED(Boolean::class, { it.multiplayer::turnCheckerEnabled }),
// MULTIPLAYER_TURN_CHECKER_PERSISTENT_NOTIFICATION_ENABLED(Boolean::class, { it.multiplayer::turnCheckerPersistentNotificationEnabled }),
// MULTIPLAYER_HIDE_DROPBOX_WARNING(Boolean::class, { it.multiplayer::hideDropboxWarning }),
MULTIPLAYER_TURN_CHECKER_DELAY(Duration::class, { it.multiplayer::turnCheckerDelay }),
MULTIPLAYER_CURRENT_GAME_REFRESH_DELAY(Duration::class, { it.multiplayer::currentGameRefreshDelay }),
MULTIPLAYER_ALL_GAME_REFRESH_DELAY(Duration::class, { it.multiplayer::allGameRefreshDelay }),
MULTIPLAYER_CURRENT_GAME_TURN_NOTIFICATION_SOUND(UncivSound::class, { it.multiplayer::currentGameTurnNotificationSound }),
MULTIPLAYER_OTHER_GAME_TURN_NOTIFICATION_SOUND(UncivSound::class, { it.multiplayer::otherGameTurnNotificationSound });
MULTIPLAYER_TURN_CHECKER_DELAY(Duration::class, { it.multiplayer::turnCheckerDelay }),
MULTIPLAYER_CURRENT_GAME_REFRESH_DELAY(Duration::class, { it.multiplayer::currentGameRefreshDelay }),
MULTIPLAYER_ALL_GAME_REFRESH_DELAY(Duration::class, { it.multiplayer::allGameRefreshDelay }),
MULTIPLAYER_CURRENT_GAME_TURN_NOTIFICATION_SOUND(UncivSound::class, { it.multiplayer::currentGameTurnNotificationSound }),
MULTIPLAYER_OTHER_GAME_TURN_NOTIFICATION_SOUND(UncivSound::class, { it.multiplayer::otherGameTurnNotificationSound });
/** **Warning:** It is the obligation of the caller to select the same type [T] that the [kClass] of this property has */
fun <T> getProperty(settings: GameSettings): KMutableProperty0<T> {
@Suppress("UNCHECKED_CAST")
return propertyGetter(settings) as KMutableProperty0<T>
/** **Warning:** It is the obligation of the caller to select the same type [T] that the [kClass] of this property has */
fun <T> getProperty(settings: GameSettings): KMutableProperty0<T> {
@Suppress("UNCHECKED_CAST")
return propertyGetter(settings) as KMutableProperty0<T>
}
}
//endregion
}

View File

@ -2,6 +2,7 @@ package com.unciv.models.metadata
import com.unciv.logic.event.Event
import com.unciv.models.UncivSound
import com.unciv.models.metadata.GameSettings.GameSetting
/** **Warning:** this event is in the process of completion and **not** used for all settings yet! **Only the settings in [GameSetting] get events sent!** */
interface SettingsPropertyChanged : Event {

View File

@ -7,7 +7,7 @@ import com.unciv.json.json
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.models.SpyAction
import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.metadata.LocaleCode
import com.unciv.models.metadata.GameSettings.LocaleCode
import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.GlobalUniques

View File

@ -18,7 +18,7 @@ import com.unciv.GUI
import com.unciv.UncivGame
import com.unciv.models.metadata.GameSettings
import com.unciv.models.metadata.ModCategories
import com.unciv.models.metadata.ScreenSize
import com.unciv.models.metadata.GameSettings.ScreenSize
import com.unciv.models.translations.TranslationFileWriter
import com.unciv.models.translations.tr
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip

View File

@ -8,7 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Array
import com.unciv.GUI
import com.unciv.models.metadata.GameSettings
import com.unciv.models.metadata.ScreenSize
import com.unciv.models.metadata.GameSettings.ScreenSize
import com.unciv.models.skins.SkinCache
import com.unciv.models.tilesets.TileSetCache
import com.unciv.models.translations.tr

View File

@ -9,8 +9,8 @@ import com.unciv.logic.multiplayer.OnlineMultiplayer
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
import com.unciv.logic.multiplayer.storage.MultiplayerAuthException
import com.unciv.models.UncivSound
import com.unciv.models.metadata.GameSetting
import com.unciv.models.metadata.GameSettings
import com.unciv.models.metadata.GameSettings.GameSetting
import com.unciv.models.ruleset.RulesetCache
import com.unciv.ui.components.UncivTextField
import com.unciv.ui.components.extensions.addSeparator

View File

@ -7,8 +7,8 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
import com.badlogic.gdx.utils.Array
import com.unciv.logic.event.EventBus
import com.unciv.models.UncivSound
import com.unciv.models.metadata.GameSetting
import com.unciv.models.metadata.GameSettings
import com.unciv.models.metadata.GameSettings.GameSetting
import com.unciv.models.metadata.SettingsPropertyChanged
import com.unciv.models.metadata.SettingsPropertyUncivSoundChanged
import com.unciv.models.translations.tr

View File

@ -7,7 +7,7 @@ import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.models.metadata.GameSettings
import com.unciv.models.metadata.ScreenSize
import com.unciv.models.metadata.GameSettings.ScreenSize
import com.unciv.ui.audio.MusicController
import com.unciv.ui.components.extensions.setSize
import com.unciv.ui.components.fonts.Fonts