mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-14 09:48:12 +07:00
City detailed stats popup - fixed header (#8959)
* DetailedStatsPopup fixed header * DetailedStatsPopup formatters optimized * DetailedStatsPopup keyboard
This commit is contained in:
@ -59,7 +59,7 @@ private class ConstructionButtonDTO(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Manager to hold and coordinate two widgets for the city screen left side:
|
* Manager to hold and coordinate two widgets for the city screen left side:
|
||||||
* - Construction queue with switch to [ConstructionInfoTable] button and the enqueue / buy buttons.
|
* - Construction queue with the enqueue / buy buttons.
|
||||||
* The queue is scrollable, limited to one third of the stage height.
|
* The queue is scrollable, limited to one third of the stage height.
|
||||||
* - Available constructions display, scrolling, grouped with expanders and therefore of dynamic height.
|
* - Available constructions display, scrolling, grouped with expanders and therefore of dynamic height.
|
||||||
*/
|
*/
|
||||||
|
@ -59,15 +59,15 @@ class CityScreen(
|
|||||||
// Clockwise from the top-left
|
// Clockwise from the top-left
|
||||||
|
|
||||||
/** Displays current production, production queue and available productions list
|
/** Displays current production, production queue and available productions list
|
||||||
* Not a widget, but manages two: construction queue, info toggle button, buy buttons
|
* Not a widget, but manages two: construction queue + buy buttons
|
||||||
* in a Table holder on upper LEFT, and available constructions in a ScrollPane lower LEFT.
|
* in a Table holder on TOP LEFT, and available constructions in a ScrollPane BOTTOM LEFT.
|
||||||
*/
|
*/
|
||||||
private var constructionsTable = CityConstructionsTable(this)
|
private var constructionsTable = CityConstructionsTable(this)
|
||||||
|
|
||||||
/** Displays raze city button - sits on TOP CENTER */
|
/** Displays raze city button - sits on TOP CENTER */
|
||||||
private var razeCityButtonHolder = Table()
|
private var razeCityButtonHolder = Table()
|
||||||
|
|
||||||
/** Displays city stats info */
|
/** Displays city stats, population management, religion, built buildings info - TOP RIGHT */
|
||||||
private var cityStatsTable = CityStatsTable(this)
|
private var cityStatsTable = CityStatsTable(this)
|
||||||
|
|
||||||
/** Displays tile info, alternate with selectedConstructionTable - sits on BOTTOM RIGHT */
|
/** Displays tile info, alternate with selectedConstructionTable - sits on BOTTOM RIGHT */
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.unciv.ui.screens.cityscreen
|
package com.unciv.ui.screens.cityscreen
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Input
|
||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
@ -9,25 +9,28 @@ import com.unciv.logic.city.StatTreeNode
|
|||||||
import com.unciv.models.stats.Stat
|
import com.unciv.models.stats.Stat
|
||||||
import com.unciv.models.stats.Stats
|
import com.unciv.models.stats.Stats
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.images.IconCircleGroup
|
|
||||||
import com.unciv.ui.popups.Popup
|
|
||||||
import com.unciv.ui.components.AutoScrollPane
|
import com.unciv.ui.components.AutoScrollPane
|
||||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
|
||||||
import com.unciv.ui.components.KeyCharAndCode
|
import com.unciv.ui.components.KeyCharAndCode
|
||||||
import com.unciv.ui.components.extensions.addSeparator
|
import com.unciv.ui.components.extensions.addSeparator
|
||||||
import com.unciv.ui.components.extensions.brighten
|
import com.unciv.ui.components.extensions.brighten
|
||||||
import com.unciv.ui.components.extensions.darken
|
import com.unciv.ui.components.extensions.darken
|
||||||
|
import com.unciv.ui.components.extensions.keyShortcuts
|
||||||
|
import com.unciv.ui.components.extensions.onActivation
|
||||||
import com.unciv.ui.components.extensions.onClick
|
import com.unciv.ui.components.extensions.onClick
|
||||||
|
import com.unciv.ui.components.extensions.packIfNeeded
|
||||||
|
import com.unciv.ui.components.extensions.pad
|
||||||
import com.unciv.ui.components.extensions.surroundWithCircle
|
import com.unciv.ui.components.extensions.surroundWithCircle
|
||||||
import com.unciv.ui.components.extensions.toLabel
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
|
import com.unciv.ui.images.IconCircleGroup
|
||||||
|
import com.unciv.ui.popups.Popup
|
||||||
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Popup(
|
class DetailedStatsPopup(
|
||||||
stageToShowOn = stageToShowOn,
|
private val cityScreen: CityScreen
|
||||||
scrollable = false) {
|
) : Popup(stageToShowOn = cityScreen.stage, scrollable = false) {
|
||||||
|
private val headerTable = Table()
|
||||||
constructor(screen: CityScreen) : this(screen, screen.stage)
|
|
||||||
|
|
||||||
private val totalTable = Table()
|
private val totalTable = Table()
|
||||||
|
|
||||||
private var sourceHighlighted: String? = null
|
private var sourceHighlighted: String? = null
|
||||||
@ -37,20 +40,27 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
|
|||||||
private val colorTotal: Color = Color.BLUE.brighten(0.5f)
|
private val colorTotal: Color = Color.BLUE.brighten(0.5f)
|
||||||
private val colorSelector: Color = Color.GREEN.darken(0.5f)
|
private val colorSelector: Color = Color.GREEN.darken(0.5f)
|
||||||
|
|
||||||
|
private val percentFormatter = DecimalFormat("0.#%").apply { positivePrefix = "+"; multiplier = 1 }
|
||||||
|
private val decimalFormatter = DecimalFormat("0.#")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
headerTable.defaults().pad(3f, 0f)
|
||||||
|
add(headerTable).padBottom(0f).row()
|
||||||
|
|
||||||
|
totalTable.defaults().pad(3f, 0f)
|
||||||
|
|
||||||
val scrollPane = AutoScrollPane(totalTable)
|
val scrollPane = AutoScrollPane(totalTable)
|
||||||
scrollPane.setOverscroll(false, false)
|
scrollPane.setOverscroll(false, false)
|
||||||
val scrollPaneCell = add(scrollPane)
|
val scrollPaneCell = add(scrollPane).padTop(0f)
|
||||||
scrollPaneCell.maxHeight(cityScreen.stage.height *3 / 4)
|
scrollPaneCell.maxHeight(cityScreen.stage.height *3 / 4)
|
||||||
|
|
||||||
row()
|
row()
|
||||||
addCloseButton("Cancel", KeyCharAndCode('n'))
|
addCloseButton(additionalKey = KeyCharAndCode.SPACE)
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun update() {
|
private fun update() {
|
||||||
|
headerTable.clear()
|
||||||
totalTable.clear()
|
totalTable.clear()
|
||||||
|
|
||||||
val cityStats = cityScreen.city.cityStats
|
val cityStats = cityScreen.city.cityStats
|
||||||
@ -61,12 +71,10 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
|
|||||||
!showFaith -> Stat.values().filter { it != Stat.Faith }
|
!showFaith -> Stat.values().filter { it != Stat.Faith }
|
||||||
else -> Stat.values().toList()
|
else -> Stat.values().toList()
|
||||||
}
|
}
|
||||||
|
val columnCount = stats.size + 1
|
||||||
|
val statColMinWidth = if (onlyWithStat != null) 150f else 110f
|
||||||
|
|
||||||
totalTable.defaults().pad(3f).padLeft(0f).padRight(0f)
|
headerTable.add(getToggleButton(isDetailed)).minWidth(150f).grow()
|
||||||
|
|
||||||
totalTable.add(getToggleButton(isDetailed).onClick {
|
|
||||||
isDetailed = !isDetailed
|
|
||||||
update() }).minWidth(150f).grow()
|
|
||||||
|
|
||||||
for (stat in stats) {
|
for (stat in stats) {
|
||||||
val label = stat.name.toLabel()
|
val label = stat.name.toLabel()
|
||||||
@ -74,26 +82,26 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
|
|||||||
onlyWithStat = if (onlyWithStat == null) stat else null
|
onlyWithStat = if (onlyWithStat == null) stat else null
|
||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
totalTable.add(wrapInTable(label, if (onlyWithStat == stat) colorSelector else null))
|
headerTable.add(wrapInTable(label, if (onlyWithStat == stat) colorSelector else null))
|
||||||
.minWidth(if (onlyWithStat == stat) 150f else 110f).grow()
|
.minWidth(statColMinWidth).grow()
|
||||||
}
|
}
|
||||||
totalTable.row()
|
headerTable.row()
|
||||||
|
headerTable.addSeparator().padBottom(2f)
|
||||||
|
|
||||||
totalTable.addSeparator().padBottom(2f)
|
|
||||||
totalTable.add("Base values".toLabel().apply { setAlignment(Align.center) })
|
totalTable.add("Base values".toLabel().apply { setAlignment(Align.center) })
|
||||||
.colspan(totalTable.columns).padLeft(0f).padRight(0f).growX().row()
|
.colspan(columnCount).growX().row()
|
||||||
totalTable.addSeparator().padTop(2f)
|
totalTable.addSeparator(colSpan = columnCount).padTop(2f)
|
||||||
traverseTree(totalTable, stats, cityStats.baseStatTree, mergeHappiness = true, percentage = false)
|
traverseTree(totalTable, stats, cityStats.baseStatTree, mergeHappiness = true, percentage = false)
|
||||||
|
|
||||||
totalTable.addSeparator().padBottom(2f)
|
totalTable.addSeparator().padBottom(2f)
|
||||||
totalTable.add("Bonuses".toLabel().apply { setAlignment(Align.center) })
|
totalTable.add("Bonuses".toLabel().apply { setAlignment(Align.center) })
|
||||||
.colspan(totalTable.columns).padLeft(0f).padRight(0f).growX().row()
|
.colspan(columnCount).growX().row()
|
||||||
totalTable.addSeparator().padTop(2f)
|
totalTable.addSeparator().padTop(2f)
|
||||||
traverseTree(totalTable, stats, cityStats.statPercentBonusTree, percentage = true)
|
traverseTree(totalTable, stats, cityStats.statPercentBonusTree, percentage = true)
|
||||||
|
|
||||||
totalTable.addSeparator().padBottom(2f)
|
totalTable.addSeparator().padBottom(2f)
|
||||||
totalTable.add("Final".toLabel().apply { setAlignment(Align.center) })
|
totalTable.add("Final".toLabel().apply { setAlignment(Align.center) })
|
||||||
.colspan(totalTable.columns).padLeft(0f).padRight(0f).growX().row()
|
.colspan(columnCount).growX().row()
|
||||||
totalTable.addSeparator().padTop(2f)
|
totalTable.addSeparator().padTop(2f)
|
||||||
|
|
||||||
val final = LinkedHashMap<Stat, Float>()
|
val final = LinkedHashMap<Stat, Float>()
|
||||||
@ -101,8 +109,7 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
|
|||||||
|
|
||||||
for ((key, value) in cityScreen.city.cityStats.happinessList) {
|
for ((key, value) in cityScreen.city.cityStats.happinessList) {
|
||||||
if (!map.containsKey(key)) {
|
if (!map.containsKey(key)) {
|
||||||
map[key] = Stats()
|
map[key] = Stats(happiness = value)
|
||||||
map[key]!![Stat.Happiness] = value
|
|
||||||
} else if (map[key]!![Stat.Happiness] == 0f) {
|
} else if (map[key]!![Stat.Happiness] == 0f) {
|
||||||
map[key]!![Stat.Happiness] = value
|
map[key]!![Stat.Happiness] = value
|
||||||
}
|
}
|
||||||
@ -110,7 +117,7 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
|
|||||||
|
|
||||||
for ((source, finalStats) in map) {
|
for ((source, finalStats) in map) {
|
||||||
|
|
||||||
if (finalStats.all { it.value == 0f })
|
if (finalStats.isEmpty())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (onlyWithStat != null && finalStats[onlyWithStat!!] == 0f)
|
if (onlyWithStat != null && finalStats[onlyWithStat!!] == 0f)
|
||||||
@ -124,11 +131,7 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var color: Color? = null
|
val color = colorSelector.takeIf { sourceHighlighted == source }
|
||||||
|
|
||||||
if (sourceHighlighted == source)
|
|
||||||
color = colorSelector
|
|
||||||
|
|
||||||
totalTable.add(wrapInTable(label, color, Align.left)).grow()
|
totalTable.add(wrapInTable(label, color, Align.left)).grow()
|
||||||
|
|
||||||
for (stat in stats) {
|
for (stat in stats) {
|
||||||
@ -152,17 +155,36 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
|
|||||||
|
|
||||||
totalTable.add(wrapInTable("Total".toLabel(), colorTotal)).grow()
|
totalTable.add(wrapInTable("Total".toLabel(), colorTotal)).grow()
|
||||||
for (stat in stats) {
|
for (stat in stats) {
|
||||||
totalTable.add(wrapInTable(final[stat]?.toOneDecimalLabel(), colorTotal)).grow()
|
totalTable.add(wrapInTable(final[stat]?.toOneDecimalLabel(), colorTotal))
|
||||||
|
.minWidth(statColMinWidth).grow()
|
||||||
}
|
}
|
||||||
totalTable.row()
|
totalTable.row()
|
||||||
|
|
||||||
|
// Mini version of EmpireOverviewTab.equalizeColumns - the number columns work thanks to statColMinWidth
|
||||||
|
headerTable.packIfNeeded()
|
||||||
|
totalTable.packIfNeeded()
|
||||||
|
val firstColumnWidth = max(totalTable.getColumnWidth(0), headerTable.getColumnWidth(0))
|
||||||
|
headerTable.cells.first().minWidth(firstColumnWidth)
|
||||||
|
totalTable.cells.first().minWidth(firstColumnWidth)
|
||||||
|
headerTable.invalidate()
|
||||||
|
totalTable.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getToggleButton(showDetails: Boolean): IconCircleGroup {
|
private fun getToggleButton(showDetails: Boolean): IconCircleGroup {
|
||||||
val label = (if (showDetails) "-" else "+").toLabel()
|
val label = (if (showDetails) "-" else "+").toLabel()
|
||||||
label.setAlignment(Align.center)
|
label.setAlignment(Align.center)
|
||||||
return label
|
val button = label
|
||||||
.surroundWithCircle(25f, color = BaseScreen.skinStrings.skinConfig.baseColor)
|
.surroundWithCircle(25f, color = BaseScreen.skinStrings.skinConfig.baseColor)
|
||||||
.surroundWithCircle(27f, false)
|
.surroundWithCircle(27f, false)
|
||||||
|
button.keyShortcuts.run {
|
||||||
|
add(Input.Keys.PLUS)
|
||||||
|
add(Input.Keys.NUMPAD_ADD)
|
||||||
|
}
|
||||||
|
button.onActivation {
|
||||||
|
isDetailed = !isDetailed
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
return button
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun traverseTree(
|
private fun traverseTree(
|
||||||
@ -263,11 +285,6 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
|
|||||||
return tbl
|
return tbl
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
private fun Float.toPercentLabel() = percentFormatter.format(this).toLabel()
|
||||||
private fun Float.toPercentLabel() =
|
private fun Float.toOneDecimalLabel() = decimalFormatter.format(this).toLabel()
|
||||||
"${if (this>0f) "+" else ""}${DecimalFormat("0.#").format(this)}%".toLabel()
|
|
||||||
private fun Float.toOneDecimalLabel() =
|
|
||||||
DecimalFormat("0.#").format(this).toLabel()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user