mirror of
https://github.com/yairm210/Unciv.git
synced 2025-08-03 12:37:42 -04:00
Make CityScreen's top-right widget use an ExpanderTab (#13186)
* Make CityScreen's top-right widget use an ExpanderTab * No nasty tricks please
This commit is contained in:
parent
adfaacb0f6
commit
a5a148cc51
@ -5,13 +5,17 @@ import com.badlogic.gdx.math.Interpolation
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.FloatAction
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Cell
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Value
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.input.ActivationTypes
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.clearActivationActions
|
||||
import com.unciv.ui.components.input.keyShortcuts
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.images.IconCircleGroup
|
||||
@ -25,6 +29,7 @@ import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
* @param fontSize Size applied to header text (only)
|
||||
* @param icon Optional icon - please use [Image][com.badlogic.gdx.scenes.scene2d.ui.Image] or [IconCircleGroup]
|
||||
* @param defaultPad Padding between content and wrapper.
|
||||
* @param topPad Padding between content top and wrapper.
|
||||
* @param headerPad Default padding for the header Table.
|
||||
* @param expanderWidth If set initializes header width
|
||||
* @param expanderHeight If set initializes header height
|
||||
@ -38,6 +43,7 @@ class ExpanderTab(
|
||||
icon: Actor? = null,
|
||||
startsOutOpened: Boolean = true,
|
||||
defaultPad: Float = 10f,
|
||||
topPad: Float = defaultPad,
|
||||
headerPad: Float = 10f,
|
||||
expanderWidth: Float = 0f,
|
||||
expanderHeight: Float = 0f,
|
||||
@ -46,13 +52,15 @@ class ExpanderTab(
|
||||
private val onChange: (() -> Unit)? = null,
|
||||
initContent: ((Table) -> Unit)? = null
|
||||
): Table(BaseScreen.skin) {
|
||||
private companion object {
|
||||
const val arrowSize = 18f
|
||||
const val arrowImage = "OtherIcons/BackArrow"
|
||||
val arrowColor = Color(1f,0.96f,0.75f,1f)
|
||||
const val animationDuration = 0.2f
|
||||
companion object {
|
||||
private const val arrowSize = 18f
|
||||
private const val arrowImage = "OtherIcons/BackArrow"
|
||||
private val arrowColor = Color(1f,0.96f,0.75f,1f)
|
||||
private const val animationDuration = 0.2f
|
||||
|
||||
val persistedStates = HashMap<String, Boolean>()
|
||||
private val persistedStates = HashMap<String, Boolean>()
|
||||
|
||||
fun wasOpen(persistenceID: String) = persistedStates[persistenceID]
|
||||
}
|
||||
|
||||
/** Header with label, [headerContent] and icon, touchable to show/hide.
|
||||
@ -62,9 +70,9 @@ class ExpanderTab(
|
||||
|
||||
/** Additional elements can be added to the `ExpanderTab`'s header using this container, empty by default. */
|
||||
val headerContent = Table()
|
||||
|
||||
|
||||
private val headerLabel = title.toLabel(fontSize = fontSize, hideIcons = true)
|
||||
private val headerIcon = ImageGetter.getImage(arrowImage)
|
||||
val headerIcon = ImageGetter.getImage(arrowImage)
|
||||
private val contentWrapper = Table() // Wrapper for innerTable, this is what will be shown/hidden
|
||||
|
||||
/** The container where the client should add the content to toggle */
|
||||
@ -107,7 +115,7 @@ class ExpanderTab(
|
||||
if (expanderWidth != 0f)
|
||||
defaults().minWidth(expanderWidth)
|
||||
defaults().growX()
|
||||
contentWrapper.defaults().growX().pad(defaultPad)
|
||||
contentWrapper.defaults().growX().pad(topPad, defaultPad, defaultPad, defaultPad)
|
||||
innerTable.defaults().growX()
|
||||
add(header).fill().row()
|
||||
add(contentWrapper)
|
||||
@ -189,4 +197,24 @@ class ExpanderTab(
|
||||
fun setText(text: String) {
|
||||
headerLabel.setText(text)
|
||||
}
|
||||
|
||||
fun toggleOnIconOnly() {
|
||||
header.clearActivationActions(ActivationTypes.Tap)
|
||||
headerIcon.onActivation { toggle() }
|
||||
}
|
||||
|
||||
private fun Cell<Actor>.resetFixedSize(): Cell<Actor> {
|
||||
minHeight(Value.minHeight)
|
||||
prefHeight(Value.prefHeight)
|
||||
maxHeight(Value.maxHeight)
|
||||
minWidth(Value.minWidth)
|
||||
prefWidth(Value.prefHeight)
|
||||
maxWidth(Value.maxWidth)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDynamicHeaderSize() {
|
||||
header.cells[0]!!.resetFixedSize().center()
|
||||
header.cells[1]!!.resetFixedSize().center()
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,10 @@ package com.unciv.ui.screens.cityscreen
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Cell
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Value
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.city.*
|
||||
@ -16,30 +17,27 @@ import com.unciv.models.stats.Stat
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.components.extensions.*
|
||||
import com.unciv.ui.components.fonts.Fonts
|
||||
import com.unciv.ui.components.input.ActivationTypes
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.clearActivationActions
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||
import com.unciv.ui.components.widgets.ExpanderTab
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.round
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane as ScrollPane
|
||||
|
||||
class CityStatsTable(private val cityScreen: CityScreen) : Table() {
|
||||
private val innerTable = Table() // table within this Table. Slightly smaller creates border
|
||||
private val upperTable = Table() // fixed position table
|
||||
private val lowerTable = Table() // table that will be in the ScrollPane
|
||||
private val lowerPane: ScrollPane
|
||||
private val city = cityScreen.city
|
||||
private val headerIcon = ImageGetter.getImage("OtherIcons/BackArrow").apply {
|
||||
setSize(18f, 18f)
|
||||
setOrigin(Align.center)
|
||||
rotation = 90f
|
||||
}
|
||||
private var headerIconClickArea = Table()
|
||||
private var isOpen = !cityScreen.isCrampedPortrait()
|
||||
|
||||
private val expander: ExpanderTab
|
||||
// table within this Table. Slightly smaller creates border
|
||||
private val miniStatsTable = MiniStatsTable(ExpanderTab.wasOpen("CityStatsTable"))
|
||||
private val lowerTable = Table() // table that will be in the ScrollPane
|
||||
private val lowerPane = AutoScrollPane(lowerTable)
|
||||
private var lowerCell: Cell<AutoScrollPane>? = null
|
||||
|
||||
private val detailedStatsButton = "Stats".toTextButton().apply {
|
||||
labelCell.pad(10f)
|
||||
onActivation(binding = KeyboardBinding.ShowStats) {
|
||||
@ -54,61 +52,43 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() {
|
||||
tintColor = colorFromRGB(194, 180, 131)
|
||||
)
|
||||
|
||||
innerTable.pad(5f)
|
||||
innerTable.background = BaseScreen.skinStrings.getUiBackground(
|
||||
expander = ExpanderTab("",
|
||||
startsOutOpened = !cityScreen.isCrampedPortrait(),
|
||||
persistenceID = "CityStatsTable",
|
||||
defaultPad = 7f,
|
||||
headerPad = if (cityScreen.isCrampedPortrait()) 7f else 6f,
|
||||
topPad = 0f, // remove space between miniStatsTable and detailedStatsButton
|
||||
expanderWidth = miniStatsTable.width,
|
||||
expanderHeight = miniStatsTable.height,
|
||||
onChange = {
|
||||
cityScreen.updateWithoutConstructionAndMap()
|
||||
}
|
||||
) {
|
||||
lowerCell = it.add(lowerPane).grow()
|
||||
}
|
||||
expander.headerContent.add(miniStatsTable).growX()
|
||||
expander.background = BaseScreen.skinStrings.getUiBackground(
|
||||
"CityScreen/CityStatsTable/InnerTable",
|
||||
tintColor = ImageGetter.CHARCOAL.cpy().apply { a = 0.8f }
|
||||
)
|
||||
expander.header.background = null // Make header transparent
|
||||
/** Without this, the expander will keep initial header height, even when the
|
||||
* row count of miniStatsTable changes when opening/closing in portrait mode */
|
||||
expander.setDynamicHeaderSize()
|
||||
// Don't toggle expander when clicking the stats icons
|
||||
expander.toggleOnIconOnly()
|
||||
|
||||
upperTable.defaults().pad(2f)
|
||||
lowerTable.defaults().pad(2f)
|
||||
lowerPane = ScrollPane(lowerTable)
|
||||
lowerPane.setOverscroll(false, false)
|
||||
lowerPane.setScrollingDisabled(true, false)
|
||||
lowerPane.setScrollingDisabled(x = true, y = false)
|
||||
lowerTable.defaults().space(4f)
|
||||
|
||||
add(innerTable).growX()
|
||||
|
||||
// collapse icon with larger click area
|
||||
headerIconClickArea.add(headerIcon).size(headerIcon.width).pad(6f+2f, 12f, 6f, 2f )
|
||||
headerIconClickArea.touchable = Touchable.enabled
|
||||
headerIconClickArea.onClick {
|
||||
isOpen = !isOpen
|
||||
cityScreen.updateWithoutConstructionAndMap()
|
||||
}
|
||||
add(expander).growX()
|
||||
}
|
||||
|
||||
fun update(height: Float) {
|
||||
upperTable.clear()
|
||||
lowerTable.clear()
|
||||
miniStatsTable.update()
|
||||
|
||||
val miniStatsTable = Table()
|
||||
val selected = BaseScreen.skin.getColor("selection")
|
||||
for ((stat, amount) in city.cityStats.currentCityStats) {
|
||||
if (stat == Stat.Faith && !city.civ.gameInfo.isReligionEnabled()) continue
|
||||
val icon = Table()
|
||||
val focus = CityFocus.safeValueOf(stat)
|
||||
val toggledFocus = if (focus == city.getCityFocus()) {
|
||||
icon.add(ImageGetter.getStatIcon(stat.name).surroundWithCircle(27f, false, color = selected))
|
||||
CityFocus.NoFocus
|
||||
} else {
|
||||
icon.add(ImageGetter.getStatIcon(stat.name).surroundWithCircle(27f, false, color = Color.CLEAR))
|
||||
focus
|
||||
}
|
||||
if (cityScreen.canCityBeChanged()) {
|
||||
icon.onActivation(binding = toggledFocus.binding) {
|
||||
city.setCityFocus(toggledFocus)
|
||||
city.reassignPopulation()
|
||||
cityScreen.update()
|
||||
}
|
||||
}
|
||||
miniStatsTable.add(icon).size(27f).padRight(3f)
|
||||
val valueToDisplay = if (stat == Stat.Happiness) city.cityStats.happinessList.values.sum() else amount
|
||||
miniStatsTable.add(round(valueToDisplay).toInt().toLabel()).padRight(5f)
|
||||
if (cityScreen.isCrampedPortrait() && !isOpen && stat == Stat.Gold) {
|
||||
miniStatsTable.row()
|
||||
}
|
||||
}
|
||||
upperTable.add(miniStatsTable).expandX()
|
||||
lowerTable.clear()
|
||||
|
||||
lowerTable.add(detailedStatsButton).row()
|
||||
addText()
|
||||
@ -124,22 +104,12 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() {
|
||||
|
||||
addBuildingsInfo()
|
||||
|
||||
headerIcon.rotation = if(isOpen) 90f else 0f
|
||||
|
||||
innerTable.clear()
|
||||
innerTable.add(upperTable).expandX()
|
||||
innerTable.add(headerIconClickArea).row()
|
||||
val lowerCell = if (isOpen) {
|
||||
innerTable.add(lowerPane).colspan(2)
|
||||
} else null
|
||||
|
||||
upperTable.pack()
|
||||
lowerTable.pack()
|
||||
lowerPane.layout()
|
||||
lowerPane.updateVisualScroll()
|
||||
lowerCell?.maxHeight(height - upperTable.height - 8f) // 2 on each side of each cell in innerTable
|
||||
lowerCell?.maxHeight(height - expander.header.height - 8f) // 2 on each side of each cell in expander
|
||||
|
||||
innerTable.pack() // update innerTable
|
||||
expander.pack() // update expander
|
||||
pack() // update self last
|
||||
}
|
||||
|
||||
@ -417,4 +387,43 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() {
|
||||
lowerTable.addCategory("Great People", greatPeopleTable, KeyboardBinding.GreatPeopleDetail)
|
||||
}
|
||||
|
||||
private inner class MiniStatsTable(wasOpen: Boolean?) : Table() {
|
||||
// Challenge: we want this measured before instantating the ExpanderTab.
|
||||
// Ergo: update() must not access expander until _after_ init
|
||||
init {
|
||||
update(wasOpen)
|
||||
pack()
|
||||
}
|
||||
|
||||
fun update() = update(expander.isOpen)
|
||||
private fun update(expanderIsOpen: Boolean?) {
|
||||
clear()
|
||||
val selected = BaseScreen.skin.getColor("selection")
|
||||
for ((stat, amount) in city.cityStats.currentCityStats) {
|
||||
if (stat == Stat.Faith && !city.civ.gameInfo.isReligionEnabled()) continue
|
||||
val icon = Table()
|
||||
val focus = CityFocus.safeValueOf(stat)
|
||||
val toggledFocus = if (focus == city.getCityFocus()) {
|
||||
icon.add(ImageGetter.getStatIcon(stat.name).surroundWithCircle(27f, false, color = selected))
|
||||
CityFocus.NoFocus
|
||||
} else {
|
||||
icon.add(ImageGetter.getStatIcon(stat.name).surroundWithCircle(27f, false, color = Color.CLEAR))
|
||||
focus
|
||||
}
|
||||
if (cityScreen.canCityBeChanged()) {
|
||||
icon.onActivation(binding = toggledFocus.binding) {
|
||||
city.setCityFocus(toggledFocus)
|
||||
city.reassignPopulation()
|
||||
cityScreen.update()
|
||||
}
|
||||
}
|
||||
add(icon).size(27f).padRight(3f)
|
||||
val valueToDisplay = if (stat == Stat.Happiness) city.cityStats.happinessList.values.sum() else amount
|
||||
add(round(valueToDisplay).toInt().toLabel()).padRight(5f)
|
||||
if (cityScreen.isCrampedPortrait() && (expanderIsOpen == null || !expanderIsOpen) && stat == Stat.Gold) {
|
||||
row()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user