diff --git a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt index 3cf8d7b12e..2f38026441 100644 --- a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt @@ -18,6 +18,7 @@ import com.unciv.models.stats.Stat import com.unciv.models.translations.tr import com.unciv.ui.utils.* import kotlin.concurrent.thread +import kotlin.math.max import com.unciv.ui.utils.AutoScrollPane as ScrollPane class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBaseScreen.skin) { @@ -53,6 +54,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase add(showCityInfoTableButton).left().padLeft(pad).padBottom(pad).row() add(constructionsQueueScrollPane).left().padBottom(pad).row() + add().expandY().row() // allow the bottom() below to open up the unneeded space add(buttons).left().bottom().padBottom(pad).row() add(availableConstructionsScrollPane).left().bottom().row() } @@ -81,7 +83,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase val queue = cityConstructions.constructionQueue constructionsQueueTable.defaults().pad(0f) - constructionsQueueTable.add(getHeader("Current construction".tr())).fillX() + constructionsQueueTable.add(getHeader("Current construction")).fillX() constructionsQueueTable.addSeparator() @@ -92,21 +94,20 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase constructionsQueueTable.add("Pick a construction".toLabel()).pad(2f).row() constructionsQueueTable.addSeparator() - constructionsQueueTable.add(getHeader("Construction queue".tr())).fillX() - constructionsQueueTable.addSeparator() - - if (queue.isNotEmpty()) { + if (queue.size > 1) { + constructionsQueueTable.add(getHeader("Construction queue")).fillX() + constructionsQueueTable.addSeparator() queue.forEachIndexed { i, constructionName -> - if (i != 0) // This is already displayed as "Current construction" + // The first entry is already displayed as "Current construction" + if (i != 0) { constructionsQueueTable.add(getQueueEntry(i, constructionName)) - .expandX().fillX().row() - if (i != queue.size - 1) - constructionsQueueTable.addSeparator() + .expandX().fillX().row() + if (i != queue.size - 1) + constructionsQueueTable.addSeparator() + } } - } else - constructionsQueueTable.add("Queue empty".toLabel()).pad(2f).row() - + } constructionsQueueScrollPane.layout() constructionsQueueScrollPane.scrollY = queueScrollY @@ -171,6 +172,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase val constructionButtonDTOList = getConstructionButtonDTOs() Gdx.app.postRunnable { availableConstructionsTable.clear() + var maxWidth = constructionsQueueTable.width for (dto in constructionButtonDTOList) { val constructionButton = getConstructionButton(dto) when (dto.construction) { @@ -184,14 +186,15 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase } is PerpetualConstruction -> specialConstructions.add(constructionButton) } + if (constructionButton.needsLayout()) constructionButton.pack() + maxWidth = max(maxWidth, constructionButton.width) } - availableConstructionsTable.addCategory("Units", units, constructionsQueueTable.width) - availableConstructionsTable.addCategory("Wonders", buildableWonders, constructionsQueueTable.width) - availableConstructionsTable.addCategory("National Wonders", buildableNationalWonders, constructionsQueueTable.width) - availableConstructionsTable.addCategory("Buildings", buildableBuildings, constructionsQueueTable.width) - availableConstructionsTable.addCategory("Other", specialConstructions, constructionsQueueTable.width) - + availableConstructionsTable.addCategory("Units", units, maxWidth) + availableConstructionsTable.addCategory("Wonders", buildableWonders, maxWidth) + availableConstructionsTable.addCategory("National Wonders", buildableNationalWonders, maxWidth) + availableConstructionsTable.addCategory("Buildings", buildableBuildings, maxWidth) + availableConstructionsTable.addCategory("Other", specialConstructions, maxWidth) availableConstructionsScrollPane.layout() availableConstructionsScrollPane.scrollY = constrScrollY @@ -489,13 +492,13 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase private fun Table.addCategory(title: String, list: ArrayList, prefWidth: Float) { if (list.isEmpty()) return - addSeparator() - add(getHeader(title)).prefWidth(prefWidth).fill().row() - addSeparator() - - for (table in list) { - add(table).fill().left().row() - if (table != list.last()) addSeparator() + if (rows > 0) addSeparator() + val expander = ExpanderTab(title, defaultPad = 0f, expanderWidth = prefWidth) { + for (table in list) { + it.addSeparator(colSpan = 1) + it.add(table).left().row() + } } + add(expander).prefWidth(prefWidth).growX().row() } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/cityscreen/CityInfoTable.kt b/core/src/com/unciv/ui/cityscreen/CityInfoTable.kt index 1170640708..ad7ba5be33 100644 --- a/core/src/com/unciv/ui/cityscreen/CityInfoTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityInfoTable.kt @@ -54,74 +54,49 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS } private fun Table.addCategory(str: String, showHideTable: Table) { - val width = cityScreen.stage.width / 4 - val showHideTableWrapper = Table() - .add(showHideTable) - .minWidth(width) - .table - - val titleTable = Table() - .background(ImageGetter.getBackground(ImageGetter.getBlue())) - .pad(4f) - .addCell(str.toLabel(fontSize = 24)) - .onClick { - if (showHideTableWrapper.hasChildren()) { - showHideTableWrapper.clear() - } else { - showHideTableWrapper.add(showHideTable).minWidth(width) - } - } + val categoryWidth = cityScreen.stage.width / 4 + val expander = ExpanderTab(str) { + it.add(showHideTable).minWidth(categoryWidth) + } addSeparator() - - add(titleTable).minWidth(width).row() - add(showHideTableWrapper).row() + add(expander).minWidth(categoryWidth).expandX().fillX().row() } - private fun addBuildingInfo(building: Building, wondersTable: Table) { - val wonderNameAndIconTable = Table() - wonderNameAndIconTable.touchable = Touchable.enabled - wonderNameAndIconTable.add(ImageGetter.getConstructionImage(building.name).surroundWithCircle(30f)) - wonderNameAndIconTable.add(building.name.toLabel()).pad(5f) - wondersTable.add(wonderNameAndIconTable).pad(5f).fillX().row() + private fun addBuildingInfo(building: Building, destinationTable: Table) { + val icon = ImageGetter.getConstructionImage(building.name).surroundWithCircle(30f) + val buildingNameAndIconTable = ExpanderTab(building.name, 18, icon, false, 5f) { + //todo: getDescription signature changes with civilopedia phase 5 + val detailsString = building.getDescription(true, + cityScreen.city, cityScreen.city.civInfo.gameInfo.ruleSet) + it.add(detailsString.toLabel().apply { wrap = true }) + .width(cityScreen.stage.width / 4 - 2 * pad).row() // when you set wrap, then you need to manually set the size of the label + if (building.isSellable()) { + val sellAmount = cityScreen.city.getGoldForSellingBuilding(building.name) + val sellBuildingButton = "Sell for [$sellAmount] gold".toTextButton() + it.add(sellBuildingButton).pad(5f).row() - val wonderDetailsTable = Table() - wondersTable.add(wonderDetailsTable).pad(5f).align(Align.left).row() + sellBuildingButton.onClick { + sellBuildingButton.disable() + cityScreen.closeAllPopups() - wonderNameAndIconTable.onClick { - if (wonderDetailsTable.hasChildren()) - wonderDetailsTable.clear() - else { - val detailsString = building.getDescription(true, - cityScreen.city, cityScreen.city.civInfo.gameInfo.ruleSet) - wonderDetailsTable.add(detailsString.toLabel().apply { wrap = true }) - .width(cityScreen.stage.width / 4 - 2 * pad).row() // when you set wrap, then you need to manually set the size of the label - if (building.isSellable()) { - val sellAmount = cityScreen.city.getGoldForSellingBuilding(building.name) - val sellBuildingButton = "Sell for [$sellAmount] gold".toTextButton() - wonderDetailsTable.add(sellBuildingButton).pad(5f).row() - - sellBuildingButton.onClick { - sellBuildingButton.disable() - cityScreen.closeAllPopups() - - YesNoPopup("Are you sure you want to sell this [${building.name}]?".tr(), - { - cityScreen.city.sellBuilding(building.name) - cityScreen.city.cityStats.update() - cityScreen.update() - }, cityScreen, - { - cityScreen.update() - }).open() - } - if (cityScreen.city.hasSoldBuildingThisTurn && !cityScreen.city.civInfo.gameInfo.gameParameters.godMode - || cityScreen.city.isPuppet - || !UncivGame.Current.worldScreen.isPlayersTurn || !cityScreen.canChangeState) - sellBuildingButton.disable() + YesNoPopup("Are you sure you want to sell this [${building.name}]?".tr(), + { + cityScreen.city.sellBuilding(building.name) + cityScreen.city.cityStats.update() + cityScreen.update() + }, cityScreen, + { + cityScreen.update() + }).open() } - wonderDetailsTable.addSeparator() + if (cityScreen.city.hasSoldBuildingThisTurn && !cityScreen.city.civInfo.gameInfo.gameParameters.godMode + || cityScreen.city.isPuppet + || !UncivGame.Current.worldScreen.isPlayersTurn || !cityScreen.canChangeState) + sellBuildingButton.disable() } + it.addSeparator() } + destinationTable.add(buildingNameAndIconTable).pad(5f).fillX().row() } private fun Table.addBuildingsInfo(cityInfo: CityInfo) { @@ -153,7 +128,7 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS specialistIcons.row().size(20f).pad(5f) for ((specialistName, amount) in building.newSpecialists()) { val specialist = cityInfo.getRuleset().specialists[specialistName] - if (specialist == null) continue // probably a mod that doesn't have the specialist defined yet + ?: continue // probably a mod that doesn't have the specialist defined yet for (i in 0 until amount) specialistIcons.add(ImageGetter.getSpecialistIcon(specialist.colorObject)).size(20f) } @@ -194,11 +169,11 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS val specificStatValue = entry.value sumOfAllBaseValues += specificStatValue statValuesTable.add(entry.key.toLabel()) - statValuesTable.add(DecimalFormat("0.#").format(specificStatValue).toLabel()).row() + statValuesTable.add(specificStatValue.toOneDecimalLabel()).row() } statValuesTable.addSeparator() statValuesTable.add("Total".toLabel()) - statValuesTable.add(DecimalFormat("0.#").format(sumOfAllBaseValues).toLabel()).row() + statValuesTable.add(sumOfAllBaseValues.toOneDecimalLabel()).row() val relevantBonuses = cityStats.statPercentBonusList.filter { it.value.get(stat) != 0f } if (relevantBonuses.isNotEmpty()) { @@ -208,15 +183,11 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS val specificStatValue = entry.value.get(stat) sumOfBonuses += specificStatValue statValuesTable.add(entry.key.toLabel()) - val decimal = DecimalFormat("0.#").format(specificStatValue) - if (specificStatValue > 0) statValuesTable.add("+$decimal%".toLabel()).row() - else statValuesTable.add("$decimal%".toLabel()).row() // negative bonus + statValuesTable.add(specificStatValue.toPercentLabel()).row() // negative bonus } statValuesTable.addSeparator() statValuesTable.add("Total".toLabel()) - val decimal = DecimalFormat("0.#").format(sumOfBonuses) - if (sumOfBonuses > 0) statValuesTable.add("+$decimal%".toLabel()).row() - else statValuesTable.add("$decimal%".toLabel()).row() // negative bonus + statValuesTable.add(sumOfBonuses.toPercentLabel()).row() // negative bonus } if (stat != Stat.Happiness) { @@ -227,11 +198,11 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS finalTotal += specificStatValue if (specificStatValue == 0f) continue statValuesTable.add(entry.key.toLabel()) - statValuesTable.add(DecimalFormat("0.#").format(specificStatValue).toLabel()).row() + statValuesTable.add(specificStatValue.toOneDecimalLabel()).row() } statValuesTable.addSeparator() statValuesTable.add("Total".toLabel()) - statValuesTable.add(DecimalFormat("0.#").format(finalTotal).toLabel()).row() + statValuesTable.add(finalTotal.toOneDecimalLabel()).row() } statValuesTable.padBottom(4f) @@ -252,13 +223,18 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS val value = entry.value.toHashMap()[stat]!! if (value == 0f) continue greatPersonTable.add(entry.key.toLabel()).padRight(10f) - greatPersonTable.add(DecimalFormat("0.#").format(value).toLabel()).row() + greatPersonTable.add(value.toOneDecimalLabel()).row() } } } companion object { private const val FONT_SIZE_STAT_INFO_HEADER = 22 + + private fun Float.toPercentLabel() = + "${if (this>0f) "+" else ""}${DecimalFormat("0.#").format(this)}%".toLabel() + private fun Float.toOneDecimalLabel() = + DecimalFormat("0.#").format(this).toLabel() } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/ui/trade/OffersListScroll.kt b/core/src/com/unciv/ui/trade/OffersListScroll.kt index f6d757dfa6..c402355a6f 100644 --- a/core/src/com/unciv/ui/trade/OffersListScroll.kt +++ b/core/src/com/unciv/ui/trade/OffersListScroll.kt @@ -9,10 +9,7 @@ import com.unciv.logic.trade.TradeOffersList import com.unciv.logic.trade.TradeType import com.unciv.logic.trade.TradeType.* import com.unciv.models.translations.tr -import com.unciv.ui.utils.CameraStageBaseScreen -import com.unciv.ui.utils.disable -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.toTextButton +import com.unciv.ui.utils.* import kotlin.math.min import com.unciv.ui.utils.AutoScrollPane as ScrollPane diff --git a/core/src/com/unciv/ui/trade/ExpanderTab.kt b/core/src/com/unciv/ui/utils/ExpanderTab.kt similarity index 76% rename from core/src/com/unciv/ui/trade/ExpanderTab.kt rename to core/src/com/unciv/ui/utils/ExpanderTab.kt index 195edd9478..83d5a43954 100644 --- a/core/src/com/unciv/ui/trade/ExpanderTab.kt +++ b/core/src/com/unciv/ui/utils/ExpanderTab.kt @@ -1,32 +1,34 @@ -package com.unciv.ui.trade +package com.unciv.ui.utils import com.badlogic.gdx.graphics.Color 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.Table import com.badlogic.gdx.utils.Align import com.unciv.UncivGame -import com.unciv.ui.utils.CameraStageBaseScreen -import com.unciv.ui.utils.ImageGetter -import com.unciv.ui.utils.onClick -import com.unciv.ui.utils.toLabel /** * A widget with a header that when clicked shows/hides a sub-Table. * * @param title The header text, automatically translated. + * @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. Header padding is currently not modifiable. + * @param expanderWidth If set initializes header width * @param initContent Optional lambda with [innerTable] as parameter, to help initialize content. */ class ExpanderTab( title: String, + fontSize: Int = 24, + icon: Actor? = null, startsOutOpened: Boolean = true, defaultPad: Float = 10f, + expanderWidth: Float = 0f, initContent: ((Table) -> Unit)? = null ): Table(CameraStageBaseScreen.skin) { private companion object { - const val fontSize = 24 const val arrowSize = 18f const val arrowImage = "OtherIcons/BackArrow" val arrowColor = Color(1f,0.96f,0.75f,1f) @@ -56,14 +58,25 @@ class ExpanderTab( headerIcon.rotation = 180f headerIcon.color = arrowColor header.background(ImageGetter.getBackground(ImageGetter.getBlue())) + if (icon != null) header.add(icon) header.add(headerLabel) header.add(headerIcon).size(arrowSize).align(Align.center) header.touchable= Touchable.enabled header.onClick { toggle() } - add(header).expandX().fill().row() - contentWrapper.defaults().pad(defaultPad) - add(contentWrapper).expandX() + if (expanderWidth != 0f) + defaults().minWidth(expanderWidth) + defaults().growX() + contentWrapper.defaults().growX().pad(defaultPad) + innerTable.defaults().growX() + add(header).fillY().row() + add(contentWrapper) + contentWrapper.add(innerTable) // update will revert this initContent?.invoke(innerTable) + if (expanderWidth == 0f) { + // Measure content width incl. pad, set header to same width + if (innerTable.needsLayout()) contentWrapper.pack() + getCell(header).minWidth(contentWrapper.width) + } update(noAnimation = true) }