City detailed stats popup - fixed header (#8959)

* DetailedStatsPopup fixed header

* DetailedStatsPopup formatters optimized

* DetailedStatsPopup keyboard
This commit is contained in:
SomeTroglodyte
2023-03-19 16:14:39 +01:00
committed by GitHub
parent 3820c5efa4
commit fccfef8412
3 changed files with 64 additions and 47 deletions

View File

@ -59,7 +59,7 @@ private class ConstructionButtonDTO(
/**
* 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.
* - Available constructions display, scrolling, grouped with expanders and therefore of dynamic height.
*/

View File

@ -59,15 +59,15 @@ class CityScreen(
// Clockwise from the top-left
/** Displays current production, production queue and available productions list
* Not a widget, but manages two: construction queue, info toggle button, buy buttons
* in a Table holder on upper LEFT, and available constructions in a ScrollPane lower LEFT.
* Not a widget, but manages two: construction queue + buy buttons
* in a Table holder on TOP LEFT, and available constructions in a ScrollPane BOTTOM LEFT.
*/
private var constructionsTable = CityConstructionsTable(this)
/** Displays raze city button - sits on TOP CENTER */
private var razeCityButtonHolder = Table()
/** Displays city stats info */
/** Displays city stats, population management, religion, built buildings info - TOP RIGHT */
private var cityStatsTable = CityStatsTable(this)
/** Displays tile info, alternate with selectedConstructionTable - sits on BOTTOM RIGHT */

View File

@ -1,7 +1,7 @@
package com.unciv.ui.screens.cityscreen
import com.badlogic.gdx.Input
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.Table
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.Stats
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.screens.basescreen.BaseScreen
import com.unciv.ui.components.KeyCharAndCode
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.brighten
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.packIfNeeded
import com.unciv.ui.components.extensions.pad
import com.unciv.ui.components.extensions.surroundWithCircle
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 kotlin.math.max
class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Popup(
stageToShowOn = stageToShowOn,
scrollable = false) {
constructor(screen: CityScreen) : this(screen, screen.stage)
class DetailedStatsPopup(
private val cityScreen: CityScreen
) : Popup(stageToShowOn = cityScreen.stage, scrollable = false) {
private val headerTable = Table()
private val totalTable = Table()
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 colorSelector: Color = Color.GREEN.darken(0.5f)
private val percentFormatter = DecimalFormat("0.#%").apply { positivePrefix = "+"; multiplier = 1 }
private val decimalFormatter = DecimalFormat("0.#")
init {
headerTable.defaults().pad(3f, 0f)
add(headerTable).padBottom(0f).row()
totalTable.defaults().pad(3f, 0f)
val scrollPane = AutoScrollPane(totalTable)
scrollPane.setOverscroll(false, false)
val scrollPaneCell = add(scrollPane)
val scrollPaneCell = add(scrollPane).padTop(0f)
scrollPaneCell.maxHeight(cityScreen.stage.height *3 / 4)
row()
addCloseButton("Cancel", KeyCharAndCode('n'))
addCloseButton(additionalKey = KeyCharAndCode.SPACE)
update()
}
private fun update() {
headerTable.clear()
totalTable.clear()
val cityStats = cityScreen.city.cityStats
@ -61,12 +71,10 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
!showFaith -> Stat.values().filter { it != Stat.Faith }
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)
totalTable.add(getToggleButton(isDetailed).onClick {
isDetailed = !isDetailed
update() }).minWidth(150f).grow()
headerTable.add(getToggleButton(isDetailed)).minWidth(150f).grow()
for (stat in stats) {
val label = stat.name.toLabel()
@ -74,26 +82,26 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
onlyWithStat = if (onlyWithStat == null) stat else null
update()
}
totalTable.add(wrapInTable(label, if (onlyWithStat == stat) colorSelector else null))
.minWidth(if (onlyWithStat == stat) 150f else 110f).grow()
headerTable.add(wrapInTable(label, if (onlyWithStat == stat) colorSelector else null))
.minWidth(statColMinWidth).grow()
}
totalTable.row()
headerTable.row()
headerTable.addSeparator().padBottom(2f)
totalTable.addSeparator().padBottom(2f)
totalTable.add("Base values".toLabel().apply { setAlignment(Align.center) })
.colspan(totalTable.columns).padLeft(0f).padRight(0f).growX().row()
totalTable.addSeparator().padTop(2f)
.colspan(columnCount).growX().row()
totalTable.addSeparator(colSpan = columnCount).padTop(2f)
traverseTree(totalTable, stats, cityStats.baseStatTree, mergeHappiness = true, percentage = false)
totalTable.addSeparator().padBottom(2f)
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)
traverseTree(totalTable, stats, cityStats.statPercentBonusTree, percentage = true)
totalTable.addSeparator().padBottom(2f)
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)
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) {
if (!map.containsKey(key)) {
map[key] = Stats()
map[key]!![Stat.Happiness] = value
map[key] = Stats(happiness = value)
} else if (map[key]!![Stat.Happiness] == 0f) {
map[key]!![Stat.Happiness] = value
}
@ -110,7 +117,7 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
for ((source, finalStats) in map) {
if (finalStats.all { it.value == 0f })
if (finalStats.isEmpty())
continue
if (onlyWithStat != null && finalStats[onlyWithStat!!] == 0f)
@ -124,11 +131,7 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
}
}
var color: Color? = null
if (sourceHighlighted == source)
color = colorSelector
val color = colorSelector.takeIf { sourceHighlighted == source }
totalTable.add(wrapInTable(label, color, Align.left)).grow()
for (stat in stats) {
@ -152,17 +155,36 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
totalTable.add(wrapInTable("Total".toLabel(), colorTotal)).grow()
for (stat in stats) {
totalTable.add(wrapInTable(final[stat]?.toOneDecimalLabel(), colorTotal)).grow()
totalTable.add(wrapInTable(final[stat]?.toOneDecimalLabel(), colorTotal))
.minWidth(statColMinWidth).grow()
}
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 {
val label = (if (showDetails) "-" else "+").toLabel()
label.setAlignment(Align.center)
return label
val button = label
.surroundWithCircle(25f, color = BaseScreen.skinStrings.skinConfig.baseColor)
.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(
@ -263,11 +285,6 @@ class DetailedStatsPopup(val cityScreen: CityScreen, stageToShowOn: Stage) : Pop
return tbl
}
companion object {
private fun Float.toPercentLabel() =
"${if (this>0f) "+" else ""}${DecimalFormat("0.#").format(this)}%".toLabel()
private fun Float.toOneDecimalLabel() =
DecimalFormat("0.#").format(this).toLabel()
}
private fun Float.toPercentLabel() = percentFormatter.format(this).toLabel()
private fun Float.toOneDecimalLabel() = decimalFormatter.format(this).toLabel()
}