UI: various improvements mostly relating to centering and WorldScreenTopBar (#12673)

* (rebase)

* centering pt. 1

* (rebase)

* centering pt. 2

* world screen top bar fixes:
– standardise icon-label order
– add ad-hoc spacing to the turn count label
– add clean and distinctive new style for 'per turn' stats

* centering pt. 3

* –final
This commit is contained in:
Toxile 2024-12-22 08:10:07 +00:00 committed by GitHub
parent 997648e174
commit f9e69fb4f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 102 additions and 48 deletions

View File

@ -58,6 +58,22 @@ object Fonts {
.sortedWith(compareBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.localName })
}
/**
* Helper for v-centering the text of Icon Label -type components:
*
* Normal vertical centering uses the entire font height. In reality,
* it is customary to align the centre from the baseline to the ascent
* with the centre of the other element. This function estimates the
* correct amount to shift the text element.
*/
fun getDescenderHeight(fontSize: Int): Float {
val ratio = fontImplementation.getMetrics().run {
descent / height }
// For whatever reason, undershooting the adjustment slightly
// causes rounding to work better
return ratio * fontSize.toFloat() + 2.25f
}
/**
* Turn a TextureRegion into a Pixmap.
*

View File

@ -7,8 +7,11 @@ import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.ui.components.fonts.Fonts
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.screens.basescreen.BaseScreen
import kotlin.math.floor
import kotlin.math.ceil
/**
* Translate a [String] and make a [Button] widget from it, with control over font size, font colour, an optional icon, and custom formatting.
@ -32,10 +35,15 @@ open class IconTextButton(
val size = fontSize.toFloat()
icon.setSize(size, size)
icon.setOrigin(Align.center)
add(icon).size(size).padRight(size / 3)
add(icon).size(size).padRight(size / 3.0f)
} else {
add()
add().padRight(fontSize / 2f)
}
/** Table cell instance containing the [label]. */
val labelCell: Cell<Label> = add(label)
init {
pad(10f)
labelCell.padTop(10f - Fonts.getDescenderHeight(fontSize));
}
}

View File

@ -4,8 +4,10 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.Container
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.logic.civilization.Civilization
import com.unciv.models.translations.tr
import com.unciv.ui.components.extensions.darken
@ -198,20 +200,13 @@ class WorldScreenTopBar(internal val worldScreen: WorldScreen) : Table() {
private var selectedCivIcon = Group()
private val selectedCivIconCell: Cell<Group>
private val selectedCivLabel = "".toLabel()
private val menuButton = ImageGetter.getImage("OtherIcons/MenuIcon")
private val menuButtonWrapper = Container(menuButton)
init {
// vertically align the Nation name by ascender height without descender:
// Normal vertical centering uses the entire font height, but that looks off here because there's
// few descenders in the typical Nation name. So we calculate an estimate of the descender height
// in world coordinates (25 is the Label font size set below), then, since the cells themselves
// have no default padding, we remove that much padding from the top of this entire Table, and
// give the Label that much top padding in return. Approximated since we're ignoring 'leading'.
val descenderHeight = Fonts.fontImplementation.getMetrics().run { descent / height } * 25f
left()
pad(10f)
padTop((10f - descenderHeight).coerceAtLeast(0f))
menuButton.color = Color.WHITE
menuButton.onActivation(binding = KeyboardBinding.Menu) { WorldScreenMenuPopup(worldScreen) }
@ -221,13 +216,17 @@ class WorldScreenTopBar(internal val worldScreen: WorldScreen) : Table() {
worldScreen.openCivilopedia(worldScreen.selectedCiv.nation.makeLink())
}
selectedCivLabel.setFontSize(25)
selectedCivLabel.setFontSize(Constants.headingFontSize)
selectedCivLabel.onClick(onNationClick)
selectedCivIcon.onClick(onNationClick)
add(menuButton).size(50f)
selectedCivIconCell = add(selectedCivIcon).padLeft(10f)
add(selectedCivLabel).padTop(descenderHeight)
menuButtonWrapper.size(Constants.headingFontSize * 1.5f);
menuButtonWrapper.center()
add(menuButtonWrapper)
selectedCivIconCell = add(selectedCivIcon).padLeft(Constants.defaultFontSize / 1.5f)
add(selectedCivLabel).padTop(10f - Fonts.getDescenderHeight(Constants.headingFontSize))
.padLeft(Constants.defaultFontSize / 2.0f)
pack()
}

View File

@ -84,7 +84,7 @@ internal class WorldScreenTopBarResources(topbar: WorldScreenTopBar) : ScalingTa
val yearText = YearTextUtil.toYearText(
civInfo.gameInfo.getYear(), civInfo.isLongCountDisplay()
)
turnsLabel.setText(Fonts.turn + "" + civInfo.gameInfo.turns.tr() + " | " + yearText)
turnsLabel.setText(Fonts.turn + "" + civInfo.gameInfo.turns.tr() + "|" + yearText)
resourcesWrapper.clearChildren()
val civResources = civInfo.getCivResourcesByName()

View File

@ -1,7 +1,9 @@
package com.unciv.ui.screens.worldscreen.topbar
import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.unciv.Constants
import com.unciv.logic.civilization.Civilization
import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr
@ -22,11 +24,18 @@ import kotlin.math.ceil
import kotlin.math.roundToInt
internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableWrapper() {
private val goldLabel = "0".toLabel(colorFromRGB(225, 217, 71))
private val scienceLabel = "0".toLabel(colorFromRGB(78, 140, 151))
private val goldLabel = "0".toLabel(colorFromRGB(225, 217, 71)) // #ed1947
private val goldPerTurnLabel = "+0"
.toLabel(colorFromRGB(225, 217, 71), 12)
private val scienceLabel = "0".toLabel(colorFromRGB(78, 140, 151)) // #4e8c97
private val happinessLabel = "0".toLabel()
private val cultureLabel = "0".toLabel(colorFromRGB(210, 94, 210))
private val faithLabel = "0".toLabel(colorFromRGB(168, 196, 241))
private val cultureLabel = "0".toLabel(colorFromRGB(210, 94, 210)) // #d25ed2
private val faithLabel = "0".toLabel(colorFromRGB(168, 196, 241)) // #a8c4f1
private val faithPerTurnLabel = "+0"
.toLabel(colorFromRGB(168, 196, 241), 12)
private val happinessContainer = Group()
@ -51,8 +60,14 @@ internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableW
init {
isTransform = false
fun addStat(label: Label, icon: String, isLast: Boolean = false, screenFactory: ()-> BaseScreen?) {
defaults().pad(defaultTopPad, defaultHorizontalPad, defaultBottomPad, defaultHorizontalPad)
fun addStat(
icon: String,
label: Label,
noPad: Boolean = false,
screenFactory: () -> BaseScreen?
) {
val image = ImageGetter.getStatIcon(icon)
val action = {
val screen = screenFactory()
@ -60,47 +75,59 @@ internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableW
}
label.onClick(action)
image.onClick(action)
add(label)
add(image).padBottom(defaultImageBottomPad).size(defaultImageSize).apply {
if (!isLast) padRight(padRightBetweenStats)
}
add(image).padBottom(defaultImageBottomPad).size(defaultImageSize)
add(label).padRight(if (noPad) 0f else padRightBetweenStats)
}
fun addStat(label: Label, icon: String, overviewPage: EmpireOverviewCategories, isLast: Boolean = false) =
addStat(label, icon, isLast) { EmpireOverviewScreen(worldScreen.selectedCiv, overviewPage) }
fun addStat(
icon: String,
label: Label,
overviewPage: EmpireOverviewCategories,
noPad: Boolean = false
) = addStat(icon, label, noPad) {
EmpireOverviewScreen(worldScreen.selectedCiv, overviewPage)
}
defaults().pad(defaultTopPad, defaultHorizontalPad, defaultBottomPad, defaultHorizontalPad)
addStat(goldLabel, "Gold", EmpireOverviewCategories.Stats)
addStat(scienceLabel, "Science") { TechPickerScreen(worldScreen.selectedCiv) }
fun addPerTurnLabel(label: Label) {
add(label).padRight(padRightBetweenStats)
.height(Constants.defaultFontSize.toFloat()).top()
}
addStat("Gold", goldLabel, EmpireOverviewCategories.Stats, true)
addPerTurnLabel(goldPerTurnLabel);
addStat("Science", scienceLabel) { TechPickerScreen(worldScreen.selectedCiv) }
add(happinessContainer).padBottom(defaultImageBottomPad).size(defaultImageSize)
add(happinessLabel).padRight(padRightBetweenStats)
val invokeResourcesPage = {
worldScreen.openEmpireOverview(EmpireOverviewCategories.Resources)
}
happinessContainer.onClick(invokeResourcesPage)
happinessLabel.onClick(invokeResourcesPage)
add(happinessContainer).padBottom(defaultImageBottomPad).size(defaultImageSize)
add(happinessLabel).padRight(padRightBetweenStats)
addStat(cultureLabel, "Culture") {
addStat("Culture", cultureLabel) {
if (worldScreen.gameInfo.ruleset.policyBranches.isEmpty()) null
else PolicyPickerScreen(worldScreen.selectedCiv, worldScreen.canChangeState)
}
if (worldScreen.gameInfo.isReligionEnabled()) {
addStat(faithLabel, "Faith", EmpireOverviewCategories.Religion, isLast = true)
} else {
add("Religion: Off".toLabel())
}
addStat("Faith", faithLabel, EmpireOverviewCategories.Religion, true)
addPerTurnLabel(faithPerTurnLabel)
} else add("Religion: Off".toLabel())
}
private fun rateLabel(value: Float) = value.roundToInt().toStringSigned()
fun update(civInfo: Civilization) {
resetScale()
val nextTurnStats = civInfo.stats.statsForNextTurn
val goldPerTurn = " (" + rateLabel(nextTurnStats.gold) + ")"
goldLabel.setText(civInfo.gold.tr() + goldPerTurn)
goldLabel.setText(civInfo.gold.tr())
goldPerTurnLabel.setText(rateLabel(nextTurnStats.gold))
scienceLabel.setText(rateLabel(nextTurnStats.science))
@ -117,9 +144,9 @@ internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableW
}
cultureLabel.setText(getCultureText(civInfo, nextTurnStats))
faithLabel.setText(
civInfo.religionManager.storedFaith.tr() + " (" + rateLabel(nextTurnStats.faith) + ")"
)
faithLabel.setText(civInfo.religionManager.storedFaith.tr())
faithPerTurnLabel.setText(rateLabel(nextTurnStats.faith))
scaleTo(worldScreen.stage.width)
}
@ -129,9 +156,9 @@ internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableW
// kotlin Float division by Zero produces `Float.POSITIVE_INFINITY`, not an exception
val turnsToNextPolicy = (civInfo.policies.getCultureNeededForNextPolicy() - civInfo.policies.storedCulture) / nextTurnStats.culture
cultureString += when {
turnsToNextPolicy <= 0f -> " (!)" // Can choose policy right now
nextTurnStats.culture <= 0 -> " (${Fonts.infinity})" // when you start the game, you're not producing any culture
else -> " (" + ceil(turnsToNextPolicy).toInt().tr() + ")"
turnsToNextPolicy <= 0f -> "(!)" // Can choose policy right now
nextTurnStats.culture <= 0 -> "(${Fonts.infinity})" // when you start the game, you're not producing any culture
else -> "(" + Fonts.turn + "" + ceil(turnsToNextPolicy).toInt().tr() + ")"
}
return cultureString
}
@ -146,4 +173,8 @@ internal class WorldScreenTopBarStats(topbar: WorldScreenTopBar) : ScalingTableW
" (${goldenAges.storedHappiness.tr()}/${goldenAges.happinessRequiredForNextGoldenAge().tr()})"
return happinessText
}
private fun rateLabel(value: Float): String {
return if (value.roundToInt() == 0) "±0" else value.roundToInt().toStringSigned()
}
}