mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-08 03:27:51 -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.Actor
|
||||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||||
import com.badlogic.gdx.scenes.scene2d.actions.FloatAction
|
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.ScrollPane
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Value
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.ui.components.extensions.toLabel
|
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.KeyboardBinding
|
||||||
|
import com.unciv.ui.components.input.clearActivationActions
|
||||||
import com.unciv.ui.components.input.keyShortcuts
|
import com.unciv.ui.components.input.keyShortcuts
|
||||||
import com.unciv.ui.components.input.onActivation
|
import com.unciv.ui.components.input.onActivation
|
||||||
import com.unciv.ui.images.IconCircleGroup
|
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 fontSize Size applied to header text (only)
|
||||||
* @param icon Optional icon - please use [Image][com.badlogic.gdx.scenes.scene2d.ui.Image] or [IconCircleGroup]
|
* @param icon Optional icon - please use [Image][com.badlogic.gdx.scenes.scene2d.ui.Image] or [IconCircleGroup]
|
||||||
* @param defaultPad Padding between content and wrapper.
|
* @param defaultPad Padding between content and wrapper.
|
||||||
|
* @param topPad Padding between content top and wrapper.
|
||||||
* @param headerPad Default padding for the header Table.
|
* @param headerPad Default padding for the header Table.
|
||||||
* @param expanderWidth If set initializes header width
|
* @param expanderWidth If set initializes header width
|
||||||
* @param expanderHeight If set initializes header height
|
* @param expanderHeight If set initializes header height
|
||||||
@ -38,6 +43,7 @@ class ExpanderTab(
|
|||||||
icon: Actor? = null,
|
icon: Actor? = null,
|
||||||
startsOutOpened: Boolean = true,
|
startsOutOpened: Boolean = true,
|
||||||
defaultPad: Float = 10f,
|
defaultPad: Float = 10f,
|
||||||
|
topPad: Float = defaultPad,
|
||||||
headerPad: Float = 10f,
|
headerPad: Float = 10f,
|
||||||
expanderWidth: Float = 0f,
|
expanderWidth: Float = 0f,
|
||||||
expanderHeight: Float = 0f,
|
expanderHeight: Float = 0f,
|
||||||
@ -46,13 +52,15 @@ class ExpanderTab(
|
|||||||
private val onChange: (() -> Unit)? = null,
|
private val onChange: (() -> Unit)? = null,
|
||||||
initContent: ((Table) -> Unit)? = null
|
initContent: ((Table) -> Unit)? = null
|
||||||
): Table(BaseScreen.skin) {
|
): Table(BaseScreen.skin) {
|
||||||
private companion object {
|
companion object {
|
||||||
const val arrowSize = 18f
|
private const val arrowSize = 18f
|
||||||
const val arrowImage = "OtherIcons/BackArrow"
|
private const val arrowImage = "OtherIcons/BackArrow"
|
||||||
val arrowColor = Color(1f,0.96f,0.75f,1f)
|
private val arrowColor = Color(1f,0.96f,0.75f,1f)
|
||||||
const val animationDuration = 0.2f
|
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.
|
/** 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. */
|
/** Additional elements can be added to the `ExpanderTab`'s header using this container, empty by default. */
|
||||||
val headerContent = Table()
|
val headerContent = Table()
|
||||||
|
|
||||||
private val headerLabel = title.toLabel(fontSize = fontSize, hideIcons = true)
|
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
|
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 */
|
/** The container where the client should add the content to toggle */
|
||||||
@ -107,7 +115,7 @@ class ExpanderTab(
|
|||||||
if (expanderWidth != 0f)
|
if (expanderWidth != 0f)
|
||||||
defaults().minWidth(expanderWidth)
|
defaults().minWidth(expanderWidth)
|
||||||
defaults().growX()
|
defaults().growX()
|
||||||
contentWrapper.defaults().growX().pad(defaultPad)
|
contentWrapper.defaults().growX().pad(topPad, defaultPad, defaultPad, defaultPad)
|
||||||
innerTable.defaults().growX()
|
innerTable.defaults().growX()
|
||||||
add(header).fill().row()
|
add(header).fill().row()
|
||||||
add(contentWrapper)
|
add(contentWrapper)
|
||||||
@ -189,4 +197,24 @@ class ExpanderTab(
|
|||||||
fun setText(text: String) {
|
fun setText(text: String) {
|
||||||
headerLabel.setText(text)
|
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.graphics.Color
|
||||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
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.Label
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Value
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.city.*
|
import com.unciv.logic.city.*
|
||||||
@ -16,30 +17,27 @@ import com.unciv.models.stats.Stat
|
|||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.components.extensions.*
|
import com.unciv.ui.components.extensions.*
|
||||||
import com.unciv.ui.components.fonts.Fonts
|
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.KeyboardBinding
|
||||||
|
import com.unciv.ui.components.input.clearActivationActions
|
||||||
import com.unciv.ui.components.input.onActivation
|
import com.unciv.ui.components.input.onActivation
|
||||||
import com.unciv.ui.components.input.onClick
|
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.components.widgets.ExpanderTab
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
import com.unciv.ui.components.widgets.AutoScrollPane as ScrollPane
|
|
||||||
|
|
||||||
class CityStatsTable(private val cityScreen: CityScreen) : Table() {
|
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 city = cityScreen.city
|
||||||
private val headerIcon = ImageGetter.getImage("OtherIcons/BackArrow").apply {
|
private val expander: ExpanderTab
|
||||||
setSize(18f, 18f)
|
// table within this Table. Slightly smaller creates border
|
||||||
setOrigin(Align.center)
|
private val miniStatsTable = MiniStatsTable(ExpanderTab.wasOpen("CityStatsTable"))
|
||||||
rotation = 90f
|
private val lowerTable = Table() // table that will be in the ScrollPane
|
||||||
}
|
private val lowerPane = AutoScrollPane(lowerTable)
|
||||||
private var headerIconClickArea = Table()
|
private var lowerCell: Cell<AutoScrollPane>? = null
|
||||||
private var isOpen = !cityScreen.isCrampedPortrait()
|
|
||||||
|
|
||||||
private val detailedStatsButton = "Stats".toTextButton().apply {
|
private val detailedStatsButton = "Stats".toTextButton().apply {
|
||||||
labelCell.pad(10f)
|
labelCell.pad(10f)
|
||||||
onActivation(binding = KeyboardBinding.ShowStats) {
|
onActivation(binding = KeyboardBinding.ShowStats) {
|
||||||
@ -54,61 +52,43 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() {
|
|||||||
tintColor = colorFromRGB(194, 180, 131)
|
tintColor = colorFromRGB(194, 180, 131)
|
||||||
)
|
)
|
||||||
|
|
||||||
innerTable.pad(5f)
|
expander = ExpanderTab("",
|
||||||
innerTable.background = BaseScreen.skinStrings.getUiBackground(
|
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",
|
"CityScreen/CityStatsTable/InnerTable",
|
||||||
tintColor = ImageGetter.CHARCOAL.cpy().apply { a = 0.8f }
|
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.setOverscroll(false, false)
|
||||||
lowerPane.setScrollingDisabled(true, false)
|
lowerPane.setScrollingDisabled(x = true, y = false)
|
||||||
|
lowerTable.defaults().space(4f)
|
||||||
|
|
||||||
add(innerTable).growX()
|
add(expander).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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(height: Float) {
|
fun update(height: Float) {
|
||||||
upperTable.clear()
|
miniStatsTable.update()
|
||||||
lowerTable.clear()
|
|
||||||
|
|
||||||
val miniStatsTable = Table()
|
lowerTable.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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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.add(detailedStatsButton).row()
|
lowerTable.add(detailedStatsButton).row()
|
||||||
addText()
|
addText()
|
||||||
@ -124,22 +104,12 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() {
|
|||||||
|
|
||||||
addBuildingsInfo()
|
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()
|
lowerTable.pack()
|
||||||
lowerPane.layout()
|
lowerPane.layout()
|
||||||
lowerPane.updateVisualScroll()
|
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
|
pack() // update self last
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,4 +387,43 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() {
|
|||||||
lowerTable.addCategory("Great People", greatPeopleTable, KeyboardBinding.GreatPeopleDetail)
|
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