mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-18 03:38:55 +07:00
Allow EmpireOverview persistence across game launches (#11725)
* Remove blank space between header and list in ResourceOverviewTab vertical mode * Allow EmpireOverview persistence across game launches
This commit is contained in:
@ -98,7 +98,10 @@ class GameSettings {
|
||||
var autoPlay = GameSettingsAutoPlay()
|
||||
|
||||
// This is a string not an enum so if tabs change it won't screw up the json serialization
|
||||
//TODO remove line in a future update
|
||||
var lastOverviewPage = EmpireOverviewCategories.Cities.name
|
||||
/** Holds EmpireOverviewScreen per-page persistable states */
|
||||
val overview = OverviewPersistableData()
|
||||
|
||||
/** Orientation for mobile platforms */
|
||||
var displayOrientation = ScreenOrientation.Landscape
|
||||
|
@ -0,0 +1,61 @@
|
||||
package com.unciv.models.metadata
|
||||
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.badlogic.gdx.utils.JsonValue
|
||||
import com.badlogic.gdx.utils.SerializationException
|
||||
import com.unciv.ui.screens.overviewscreen.EmpireOverviewCategories
|
||||
import com.unciv.ui.screens.overviewscreen.EmpireOverviewTab
|
||||
|
||||
class OverviewPersistableData(
|
||||
private val map: LinkedHashMap<EmpireOverviewCategories, EmpireOverviewTab.EmpireOverviewTabPersistableData> = linkedMapOf()
|
||||
) : Json.Serializable,
|
||||
Map<EmpireOverviewCategories, EmpireOverviewTab.EmpireOverviewTabPersistableData> by map
|
||||
{
|
||||
//todo so far this is saved but not used, should replace [GameSettings.lastOverviewPage] in a future update.
|
||||
var last: EmpireOverviewCategories = EmpireOverviewCategories.Cities
|
||||
|
||||
fun update(pageObjects: Map<EmpireOverviewCategories, EmpireOverviewTab>) {
|
||||
for ((category, page) in pageObjects)
|
||||
map[category] = page.persistableData
|
||||
}
|
||||
|
||||
/**
|
||||
* Gdx Serialize
|
||||
* - Intended format: `"overview":{"Cities":{"sortedBy":"Population","direction":"Descending"},...,"Resources":{"vertical":true}}`
|
||||
* - Outer field name and Object markers are already cared for, so we can begin directly with our fields
|
||||
*/
|
||||
override fun write(json: Json) {
|
||||
if (last != EmpireOverviewCategories.Cities)
|
||||
json.writeValue("last", last.name, String::class.java)
|
||||
for ((category, data) in map) {
|
||||
val clazz = category.getPersistDataClass() ?: continue
|
||||
if (data.isEmpty()) continue
|
||||
json.writeValue(category.name, data, clazz)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gdx Deserialize (format see [write])
|
||||
* - Gdx architecture means this operates on a default instance Gdx just created using the zero-args constructor
|
||||
* - Should be tolerant against Enum values that do not exist in current code
|
||||
*/
|
||||
override fun read(json: Json, jsonData: JsonValue) {
|
||||
val lastName = jsonData.get("last")?.asString() // Nullable, benign if field missing - getString() is not.
|
||||
EmpireOverviewCategories.values().firstOrNull { it.name == lastName }?.let { last = it }
|
||||
if (jsonData.isObject && jsonData.notEmpty())
|
||||
for (element in jsonData) readEntry(json, element)
|
||||
}
|
||||
|
||||
private fun readEntry(json: Json, element: JsonValue) {
|
||||
val name = element.name()
|
||||
val category = EmpireOverviewCategories.values().firstOrNull { it.name == name }
|
||||
?: return // Guards against downgrading to an Unciv missing a category, but also skips "last" here
|
||||
val clazz = category.getPersistDataClass()
|
||||
try {
|
||||
val data = json.readValue(clazz, element)
|
||||
map[category] = data
|
||||
} catch (ex: SerializationException) {
|
||||
// Unknown Enum values inside an EmpireOverviewTabPersistableData subclass end up here
|
||||
}
|
||||
}
|
||||
}
|
@ -16,12 +16,10 @@ class CityOverviewTab(
|
||||
overviewScreen: EmpireOverviewScreen,
|
||||
persistedData: EmpireOverviewTabPersistableData? = null
|
||||
) : EmpireOverviewTab(viewingPlayer, overviewScreen) {
|
||||
class CityTabPersistableData(
|
||||
override var sortedBy: CityOverviewTabColumn = CityOverviewTabColumn.CityColumn,
|
||||
|
||||
) : EmpireOverviewTabPersistableData(), SortableGrid.ISortState<CityOverviewTabColumn> {
|
||||
override fun isEmpty() = sortedBy == CityOverviewTabColumn.CityColumn
|
||||
override var direction = SortableGrid.SortDirection.None
|
||||
class CityTabPersistableData : EmpireOverviewTabPersistableData(), SortableGrid.ISortState<CityOverviewTabColumn> {
|
||||
override var sortedBy: CityOverviewTabColumn = CityOverviewTabColumn.CityColumn
|
||||
override var direction: SortableGrid.SortDirection = SortableGrid.SortDirection.None
|
||||
override fun isEmpty() = sortedBy == CityOverviewTabColumn.CityColumn && direction != SortableGrid.SortDirection.Descending
|
||||
}
|
||||
|
||||
override val persistableData = (persistedData as? CityTabPersistableData) ?: CityTabPersistableData()
|
||||
|
@ -23,6 +23,7 @@ enum class EmpireOverviewCategories(
|
||||
override fun createTab(viewingPlayer: Civilization, overviewScreen: EmpireOverviewScreen, persistedData: EmpireOverviewTabPersistableData?) =
|
||||
CityOverviewTab(viewingPlayer, overviewScreen, persistedData)
|
||||
override fun showDisabled(viewingPlayer: Civilization) = viewingPlayer.cities.isEmpty()
|
||||
override fun getPersistDataClass() = CityOverviewTab.CityTabPersistableData::class.java
|
||||
},
|
||||
Stats("StatIcons/Gold", 'S', Align.top) {
|
||||
override fun createTab(viewingPlayer: Civilization, overviewScreen: EmpireOverviewScreen, persistedData: EmpireOverviewTabPersistableData?) =
|
||||
@ -42,16 +43,19 @@ enum class EmpireOverviewCategories(
|
||||
override fun createTab(viewingPlayer: Civilization, overviewScreen: EmpireOverviewScreen, persistedData: EmpireOverviewTabPersistableData?) =
|
||||
UnitOverviewTab(viewingPlayer, overviewScreen, persistedData)
|
||||
override fun showDisabled(viewingPlayer: Civilization) = viewingPlayer.units.getCivUnits().none()
|
||||
override fun getPersistDataClass() = UnitOverviewTab.UnitTabPersistableData::class.java
|
||||
},
|
||||
Politics("OtherIcons/Politics", 'P', Align.top) {
|
||||
override fun createTab(viewingPlayer: Civilization, overviewScreen: EmpireOverviewScreen, persistedData: EmpireOverviewTabPersistableData?) =
|
||||
GlobalPoliticsOverviewTable(viewingPlayer, overviewScreen, persistedData)
|
||||
override fun showDisabled(viewingPlayer: Civilization) = viewingPlayer.diplomacy.isEmpty()
|
||||
override fun getPersistDataClass() = GlobalPoliticsOverviewTable.DiplomacyTabPersistableData::class.java
|
||||
},
|
||||
Resources("StatIcons/Happiness", 'R', Align.topLeft) {
|
||||
override fun createTab(viewingPlayer: Civilization, overviewScreen: EmpireOverviewScreen, persistedData: EmpireOverviewTabPersistableData?) =
|
||||
ResourcesOverviewTab(viewingPlayer, overviewScreen, persistedData)
|
||||
override fun showDisabled(viewingPlayer: Civilization) = viewingPlayer.detailedCivResources.none { it.resource.resourceType != ResourceType.Bonus }
|
||||
override fun getPersistDataClass() = ResourcesOverviewTab.ResourcesTabPersistableData::class.java
|
||||
},
|
||||
Religion("StatIcons/Faith", 'F', Align.top) {
|
||||
override fun createTab(viewingPlayer: Civilization, overviewScreen: EmpireOverviewScreen, persistedData: EmpireOverviewTabPersistableData?) =
|
||||
@ -85,4 +89,9 @@ enum class EmpireOverviewCategories(
|
||||
open fun testState(viewingPlayer: Civilization) =
|
||||
if (showDisabled(viewingPlayer)) EmpireOverviewTabState.Disabled
|
||||
else EmpireOverviewTabState.Normal
|
||||
|
||||
/** Get Java class of persistable data for Json serialization
|
||||
* - only needed if the data should actually be saved to GameSettings.json: Leaving it at `null` means any specific state persists only through one game run
|
||||
*/
|
||||
open fun getPersistDataClass(): Class<out EmpireOverviewTabPersistableData>? = null
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.basescreen.RecreateOnResize
|
||||
import com.unciv.ui.screens.overviewscreen.EmpireOverviewCategories.EmpireOverviewTabState
|
||||
import com.unciv.ui.screens.overviewscreen.EmpireOverviewTab.EmpireOverviewTabPersistableData
|
||||
|
||||
class EmpireOverviewScreen(
|
||||
private var viewingPlayer: Civilization,
|
||||
@ -25,25 +24,19 @@ class EmpireOverviewScreen(
|
||||
private val tabbedPager: TabbedPager
|
||||
private val pageObjects = HashMap<EmpireOverviewCategories, EmpireOverviewTab>()
|
||||
|
||||
companion object {
|
||||
// This is what keeps per-tab states between overview invocations
|
||||
var persistState: Map<EmpireOverviewCategories, EmpireOverviewTabPersistableData>? = null
|
||||
|
||||
private fun updatePersistState(pageObjects: HashMap<EmpireOverviewCategories, EmpireOverviewTab>) {
|
||||
persistState = pageObjects.mapValues { it.value.persistableData }.filterNot { it.value.isEmpty() }
|
||||
}
|
||||
}
|
||||
internal val persistState by game.settings::overview
|
||||
|
||||
override fun dispose() {
|
||||
tabbedPager.selectPage(-1)
|
||||
updatePersistState(pageObjects)
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
override fun getCivilopediaRuleset() = viewingPlayer.gameInfo.ruleset
|
||||
|
||||
init {
|
||||
val selectCategory = defaultCategory ?: EmpireOverviewCategories.values().firstOrNull { it.name == game.settings.lastOverviewPage }
|
||||
val selectCategory = defaultCategory
|
||||
//TODO replace with `?: persistState.last` in a future update
|
||||
?: EmpireOverviewCategories.values().firstOrNull { it.name == game.settings.lastOverviewPage }
|
||||
val iconSize = Constants.defaultFontSize.toFloat()
|
||||
|
||||
tabbedPager = TabbedPager(
|
||||
@ -56,7 +49,7 @@ class EmpireOverviewScreen(
|
||||
val tabState = category.testState(viewingPlayer)
|
||||
if (tabState == EmpireOverviewTabState.Hidden) continue
|
||||
val icon = if (category.iconName.isEmpty()) null else ImageGetter.getImage(category.iconName)
|
||||
val pageObject = category.createTab(viewingPlayer, this, persistState?.get(category))
|
||||
val pageObject = category.createTab(viewingPlayer, this, persistState[category])
|
||||
pageObject.pad(10f, 0f, 10f, 0f)
|
||||
pageObjects[category] = pageObject
|
||||
val index = tabbedPager.addPage(
|
||||
@ -72,21 +65,19 @@ class EmpireOverviewScreen(
|
||||
select(pageObject, selection)
|
||||
}
|
||||
}
|
||||
persistState.update(pageObjects)
|
||||
|
||||
val closeButton = getCloseButton { game.popScreen() }
|
||||
tabbedPager.decorateHeader(closeButton)
|
||||
|
||||
tabbedPager.setFillParent(true)
|
||||
stage.addActor(tabbedPager)
|
||||
|
||||
// closeButton.setPosition(stage.width - 10f, stage.height - 10f, Align.topRight)
|
||||
// stage.addActor(closeButton)
|
||||
}
|
||||
|
||||
override fun recreate(): BaseScreen {
|
||||
tabbedPager.selectPage(-1) // trigger deselect on _old_ instance so the tabs can persist their stuff
|
||||
updatePersistState(pageObjects)
|
||||
return EmpireOverviewScreen(viewingPlayer,
|
||||
//TODO replace with `persistState.last)` in a future update
|
||||
EmpireOverviewCategories.values().firstOrNull { it.name == game.settings.lastOverviewPage })
|
||||
}
|
||||
|
||||
|
@ -10,14 +10,22 @@ abstract class EmpireOverviewTab (
|
||||
val overviewScreen: EmpireOverviewScreen,
|
||||
persistedData: EmpireOverviewTabPersistableData? = null
|
||||
) : Table(BaseScreen.skin), TabbedPager.IPageExtensions {
|
||||
/** Abstract container for persistable data
|
||||
* - default class does nothing
|
||||
* - If persistence should end when quitting the game - do not override [isEmpty] and [EmpireOverviewCategories.getPersistDataClass].
|
||||
* - For persistence in GameSettings.json, override **both**.
|
||||
*/
|
||||
open class EmpireOverviewTabPersistableData {
|
||||
/** Used by serialization to detect when a default state can be omitted */
|
||||
open fun isEmpty() = true
|
||||
}
|
||||
open val persistableData = persistedData ?: EmpireOverviewTabPersistableData()
|
||||
|
||||
override fun activated(index: Int, caption: String, pager: TabbedPager) {
|
||||
if (caption.isEmpty()) return
|
||||
if (caption.isEmpty()) return // called from EmpireOverviewScreen.resume()
|
||||
//TODO remove line in a future update
|
||||
overviewScreen.game.settings.lastOverviewPage = caption
|
||||
overviewScreen.persistState.last = EmpireOverviewCategories.values()[index] // Change this if categories are ever reordered or filtered
|
||||
}
|
||||
|
||||
/** Override if the tab can _select_ something specific.
|
||||
|
@ -19,10 +19,8 @@ class NotificationsOverviewTable(
|
||||
persistedData: EmpireOverviewTabPersistableData? = null
|
||||
) : EmpireOverviewTab(viewingPlayer, overviewScreen) {
|
||||
class NotificationsTabPersistableData(
|
||||
var scrollY: Float? = null
|
||||
) : EmpireOverviewTabPersistableData() {
|
||||
override fun isEmpty() = scrollY == null
|
||||
}
|
||||
var scrollY: Float? = null
|
||||
) : EmpireOverviewTabPersistableData()
|
||||
override val persistableData = (persistedData as? NotificationsTabPersistableData) ?: NotificationsTabPersistableData()
|
||||
override fun activated(index: Int, caption: String, pager: TabbedPager) {
|
||||
if (persistableData.scrollY != null)
|
||||
|
@ -29,9 +29,7 @@ class ReligionOverviewTab(
|
||||
) : EmpireOverviewTab(viewingPlayer, overviewScreen) {
|
||||
class ReligionTabPersistableData(
|
||||
var selectedReligion: String? = null
|
||||
) : EmpireOverviewTabPersistableData() {
|
||||
override fun isEmpty() = selectedReligion == null
|
||||
}
|
||||
) : EmpireOverviewTabPersistableData()
|
||||
override val persistableData = (persistedData as? ReligionTabPersistableData) ?: ReligionTabPersistableData()
|
||||
|
||||
private val civStatsTable = Table()
|
||||
|
@ -129,6 +129,7 @@ class ResourcesOverviewTab(
|
||||
|
||||
init {
|
||||
defaults().pad(defaultPad)
|
||||
top()
|
||||
fixedContent.defaults().pad(defaultPad)
|
||||
|
||||
turnImageH.onClick {
|
||||
|
@ -20,10 +20,12 @@ class UnitOverviewTab(
|
||||
persistedData: EmpireOverviewTabPersistableData? = null
|
||||
) : EmpireOverviewTab(viewingPlayer, overviewScreen) {
|
||||
class UnitTabPersistableData : EmpireOverviewTabPersistableData(), SortableGrid.ISortState<UnitOverviewTabColumn> {
|
||||
@Transient
|
||||
var scrollY: Float? = null
|
||||
|
||||
override var sortedBy: UnitOverviewTabColumn = UnitOverviewTabColumn.Name
|
||||
override var direction = SortableGrid.SortDirection.None
|
||||
override fun isEmpty() = scrollY == null && sortedBy == UnitOverviewTabColumn.Name && direction != SortableGrid.SortDirection.Descending
|
||||
override fun isEmpty() = sortedBy == UnitOverviewTabColumn.Name && direction != SortableGrid.SortDirection.Descending
|
||||
}
|
||||
override val persistableData = (persistedData as? UnitTabPersistableData) ?: UnitTabPersistableData()
|
||||
|
||||
|
Reference in New Issue
Block a user