From 835ca58b4ea74d365c47e4e85494c702cf9353b1 Mon Sep 17 00:00:00 2001 From: sulai <6741822+sulai@users.noreply.github.com> Date: Thu, 24 Oct 2024 19:38:26 +0200 Subject: [PATCH] optimized city screen for smart phone (#12315) * optimized city screen for smart phone - moved "buy" button to city info table - removed "add to queue" button - expand icon changed to android defaults - CityStatsTable: big scrollable area, expandable * made CityStatsTable collapsible * Extracted BuyButtonFactory and re-added buy button close to construction queue --------- Co-authored-by: M. Rittweger Co-authored-by: Yair Morgenstern --- .../ui/components/widgets/ExpanderTab.kt | 8 +- .../ui/screens/cityscreen/BuyButtonFactory.kt | 200 +++++++++++++++++ .../cityscreen/CityConstructionsTable.kt | 209 +----------------- .../unciv/ui/screens/cityscreen/CityScreen.kt | 5 +- .../ui/screens/cityscreen/CityStatsTable.kt | 49 ++-- .../cityscreen/ConstructionInfoTable.kt | 22 +- 6 files changed, 272 insertions(+), 221 deletions(-) create mode 100644 core/src/com/unciv/ui/screens/cityscreen/BuyButtonFactory.kt diff --git a/core/src/com/unciv/ui/components/widgets/ExpanderTab.kt b/core/src/com/unciv/ui/components/widgets/ExpanderTab.kt index ae5e091d90..ef910355ef 100644 --- a/core/src/com/unciv/ui/components/widgets/ExpanderTab.kt +++ b/core/src/com/unciv/ui/components/widgets/ExpanderTab.kt @@ -74,7 +74,7 @@ class ExpanderTab( header.defaults().pad(headerPad) headerIcon.setSize(arrowSize, arrowSize) headerIcon.setOrigin(Align.center) - headerIcon.rotation = 180f + headerIcon.rotation = 0f headerIcon.color = arrowColor header.background( BaseScreen.skinStrings.getUiBackground( @@ -83,7 +83,7 @@ class ExpanderTab( ) ) if (icon != null) header.add(icon) - header.add(headerLabel) + header.add(headerLabel).expandX() header.add(headerIcon).size(arrowSize).align(Align.center) header.touchable= Touchable.enabled header.onActivation { toggle() } @@ -111,11 +111,11 @@ class ExpanderTab( if (noAnimation || !UncivGame.Current.settings.continuousRendering) { contentWrapper.clear() if (isOpen) contentWrapper.add(innerTable) - headerIcon.rotation = if (isOpen) 90f else 180f + headerIcon.rotation = if (isOpen) 90f else 0f if (!noAnimation) onChange?.invoke() return } - val action = object: FloatAction ( 90f, 180f, animationDuration, Interpolation.linear) { + val action = object: FloatAction ( 90f, 0f, animationDuration, Interpolation.linear) { override fun update(percent: Float) { super.update(percent) headerIcon.rotation = this.value diff --git a/core/src/com/unciv/ui/screens/cityscreen/BuyButtonFactory.kt b/core/src/com/unciv/ui/screens/cityscreen/BuyButtonFactory.kt new file mode 100644 index 0000000000..2057050c4b --- /dev/null +++ b/core/src/com/unciv/ui/screens/cityscreen/BuyButtonFactory.kt @@ -0,0 +1,200 @@ +package com.unciv.ui.screens.cityscreen + +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.scenes.scene2d.ui.Cell +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextButton +import com.unciv.Constants +import com.unciv.logic.city.CityConstructions +import com.unciv.logic.map.tile.Tile +import com.unciv.models.Religion +import com.unciv.models.ruleset.Building +import com.unciv.models.ruleset.IConstruction +import com.unciv.models.ruleset.INonPerpetualConstruction +import com.unciv.models.ruleset.PerpetualConstruction +import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.models.stats.Stat +import com.unciv.models.translations.tr +import com.unciv.ui.audio.SoundPlayer +import com.unciv.ui.components.extensions.disable +import com.unciv.ui.components.extensions.isEnabled +import com.unciv.ui.components.extensions.toTextButton +import com.unciv.ui.components.input.KeyboardBinding +import com.unciv.ui.components.input.onActivation +import com.unciv.ui.popups.Popup +import com.unciv.ui.popups.closeAllPopups +import com.unciv.ui.screens.basescreen.BaseScreen + +/** + * Use [addBuyButtons] to add buy buttons to a table. + * This class handles everything related to buying constructions. This includes + * showing and handling [ConfirmBuyPopup] and the actual purchase in [purchaseConstruction]. + */ +class BuyButtonFactory(val cityScreen: CityScreen) { + + private var preferredBuyStat = Stat.Gold // Used for keyboard buy + + fun addBuyButtons(table: Table, construction: IConstruction?, onButtonAdded: (Cell) -> Unit) { + for (button in getBuyButtons(construction)) { + onButtonAdded(table.add(button)) + } + } + + fun hasBuyButtons(construction: IConstruction?): Boolean { + return getBuyButtons(construction).isNotEmpty() + } + + private fun getBuyButtons(construction: IConstruction?): List { + val selection = cityScreen.selectedConstruction!=null || cityScreen.selectedQueueEntry >= 0 + if (selection && construction != null && construction !is PerpetualConstruction) + return Stat.statsUsableToBuy.mapNotNull { + getBuyButton(construction as INonPerpetualConstruction, it) + } + return emptyList() + } + + private fun getBuyButton(construction: INonPerpetualConstruction?, stat: Stat = Stat.Gold): TextButton? { + if (stat !in Stat.statsUsableToBuy || construction == null) + return null + + val city = cityScreen.city + val button = "".toTextButton() + + if (!isConstructionPurchaseShown(construction, stat)) { + // This can't ever be bought with the given currency. + // We want one disabled "buy" button without a price for "priceless" buildings such as wonders + // We don't want such a button when the construction can be bought using a different currency + if (stat != Stat.Gold || construction.canBePurchasedWithAnyStat(city)) + return null + button.setText("Buy".tr()) + button.disable() + } else { + val constructionBuyCost = construction.getStatBuyCost(city, stat)!! + button.setText("Buy".tr() + " " + constructionBuyCost.tr() + stat.character) + + button.onActivation(binding = KeyboardBinding.BuyConstruction) { + button.disable() + buyButtonOnClick(construction, stat) + } + button.isEnabled = cityScreen.canCityBeChanged() && + city.cityConstructions.isConstructionPurchaseAllowed(construction, stat, constructionBuyCost) + preferredBuyStat = stat // Not very intelligent, but the least common currency "wins" + } + + button.labelCell.pad(5f) + + return button + } + + private fun buyButtonOnClick(construction: INonPerpetualConstruction, stat: Stat = preferredBuyStat) { + if (construction !is Building || !construction.hasCreateOneImprovementUnique()) + return askToBuyConstruction(construction, stat) + if (cityScreen.selectedQueueEntry < 0) + return cityScreen.startPickTileForCreatesOneImprovement(construction, stat, true) + // Buying a UniqueType.CreatesOneImprovement building from queue must pass down + // the already selected tile, otherwise a new one is chosen from Automation code. + val improvement = construction.getImprovementToCreate( + cityScreen.city.getRuleset(), cityScreen.city.civ)!! + val tileForImprovement = cityScreen.city.cityConstructions.getTileForImprovement(improvement.name) + askToBuyConstruction(construction, stat, tileForImprovement) + } + + /** Ask whether user wants to buy [construction] for [stat]. + * + * Used from onClick and keyboard dispatch, thus only minimal parameters are passed, + * and it needs to do all checks and the sound as appropriate. + */ + fun askToBuyConstruction( + construction: INonPerpetualConstruction, + stat: Stat = preferredBuyStat, + tile: Tile? = null + ) { + if (!isConstructionPurchaseShown(construction, stat)) return + val city = cityScreen.city + val constructionStatBuyCost = construction.getStatBuyCost(city, stat)!! + if (!city.cityConstructions.isConstructionPurchaseAllowed(construction, stat, constructionStatBuyCost)) return + + cityScreen.closeAllPopups() + ConfirmBuyPopup(construction, stat,constructionStatBuyCost, tile) + } + + private inner class ConfirmBuyPopup( + construction: INonPerpetualConstruction, + stat: Stat, + constructionStatBuyCost: Int, + tile: Tile? + ) : Popup(cityScreen.stage) { + init { + val city = cityScreen.city + val balance = city.getStatReserve(stat) + val majorityReligion = city.religion.getMajorityReligion() + val yourReligion = city.civ.religionManager.religion + val isBuyingWithFaithForForeignReligion = construction.hasUnique(UniqueType.ReligiousUnit) + && !construction.hasUnique(UniqueType.TakeReligionOverBirthCity) + && majorityReligion != yourReligion + + addGoodSizedLabel("Currently you have [$balance] [${stat.name}].").padBottom(10f).row() + if (isBuyingWithFaithForForeignReligion) { + // Earlier tests should forbid this Popup unless both religions are non-null, but to be safe: + fun Religion?.getName() = this?.getReligionDisplayName() ?: Constants.unknownCityName + addGoodSizedLabel("You are buying a religious unit in a city that doesn't follow the religion you founded ([${yourReligion.getName()}]). " + + "This means that the unit is tied to that foreign religion ([${majorityReligion.getName()}]) and will be less useful.").row() + addGoodSizedLabel("Are you really sure you want to purchase this unit?", Constants.headingFontSize).run { + actor.color = Color.FIREBRICK + padBottom(10f) + row() + } + } + addGoodSizedLabel("Would you like to purchase [${construction.name}] for [$constructionStatBuyCost] [${stat.character}]?").row() + + addCloseButton(Constants.cancel, KeyboardBinding.Cancel) { cityScreen.update() } + val confirmStyle = BaseScreen.skin.get("positive", TextButton.TextButtonStyle::class.java) + addOKButton("Purchase", KeyboardBinding.Confirm, confirmStyle) { + purchaseConstruction(construction, stat, tile) + } + equalizeLastTwoButtonWidths() + open(true) + } + } + + /** This tests whether the buy button should be _shown_ */ + private fun isConstructionPurchaseShown(construction: INonPerpetualConstruction, stat: Stat): Boolean { + val city = cityScreen.city + return construction.canBePurchasedWithStat(city, stat) + } + + /** Called only by askToBuyConstruction's Yes answer - not to be confused with [CityConstructions.purchaseConstruction] + * @param tile supports [UniqueType.CreatesOneImprovement] + */ + private fun purchaseConstruction( + construction: INonPerpetualConstruction, + stat: Stat = Stat.Gold, + tile: Tile? = null + ) { + SoundPlayer.play(stat.purchaseSound) + val city = cityScreen.city + if (!city.cityConstructions.purchaseConstruction(construction, cityScreen.selectedQueueEntry, false, stat, tile)) { + Popup(cityScreen).apply { + add("No space available to place [${construction.name}] near [${city.name}]".tr()).row() + addCloseButton() + open() + } + return + } + if (cityScreen.selectedQueueEntry>=0 || cityScreen.selectedConstruction?.isBuildable(city.cityConstructions) != true) { + cityScreen.selectedQueueEntry = -1 + cityScreen.clearSelection() + + // Allow buying next queued or auto-assigned construction right away + city.cityConstructions.chooseNextConstruction() + if (city.cityConstructions.currentConstructionFromQueue.isNotEmpty()) { + val newConstruction = city.cityConstructions.getCurrentConstruction() + if (newConstruction is INonPerpetualConstruction) + cityScreen.selectConstruction(newConstruction) + } + } + cityScreen.city.reassignPopulation() + cityScreen.update() + } + +} diff --git a/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt b/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt index 83cd2e0be4..1ff36e369d 100644 --- a/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt +++ b/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt @@ -5,14 +5,11 @@ import com.badlogic.gdx.scenes.scene2d.Group import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.ui.Cell import com.badlogic.gdx.scenes.scene2d.ui.Table -import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.GUI import com.unciv.logic.city.City import com.unciv.logic.city.CityConstructions -import com.unciv.logic.map.tile.Tile -import com.unciv.models.Religion import com.unciv.models.UncivSound import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.IConstruction @@ -32,13 +29,10 @@ import com.unciv.ui.components.extensions.addCell 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.disable import com.unciv.ui.components.extensions.getConsumesAmountString -import com.unciv.ui.components.extensions.isEnabled import com.unciv.ui.components.extensions.packIfNeeded import com.unciv.ui.components.extensions.surroundWithCircle import com.unciv.ui.components.extensions.toLabel -import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.components.fonts.Fonts import com.unciv.ui.components.input.KeyboardBinding import com.unciv.ui.components.input.keyShortcuts @@ -49,8 +43,6 @@ import com.unciv.ui.components.widgets.ColorMarkupLabel import com.unciv.ui.components.widgets.ExpanderTab import com.unciv.ui.images.ImageGetter import com.unciv.ui.popups.CityScreenConstructionMenu -import com.unciv.ui.popups.Popup -import com.unciv.ui.popups.closeAllPopups import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.utils.Concurrency import com.unciv.utils.launchOnGLThread @@ -66,19 +58,19 @@ private class ConstructionButtonDTO( /** * Manager to hold and coordinate two widgets for the city screen left side: - * - Construction queue with the enqueue / buy buttons. + * - Construction queue with the buy button. * The queue is scrollable, limited to one third of the stage height. * - Available constructions display, scrolling, grouped with expanders and therefore of dynamic height. */ class CityConstructionsTable(private val cityScreen: CityScreen) { /* -1 = Nothing, >= 0 queue entry (0 = current construction) */ - private var selectedQueueEntry = -1 // None - private var preferredBuyStat = Stat.Gold // Used for keyboard buy + var selectedQueueEntry = -1 // None private val upperTable = Table(BaseScreen.skin) private val constructionsQueueScrollPane: ScrollPane private val constructionsQueueTable = Table() - private val buyButtonsTable = Table() + private val buttonsTable = Table() + private val buyButtonFactory = BuyButtonFactory(cityScreen) private val lowerTable = Table() private val availableConstructionsScrollPane: ScrollPane @@ -109,7 +101,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { upperTable.add(constructionsQueueScrollPane) .maxHeight(stageHeight / 3 - 10f) .padBottom(pad).row() - upperTable.add(buyButtonsTable).padBottom(pad).row() + upperTable.add(buttonsTable).padBottom(pad).row() availableConstructionsScrollPane = ScrollPane(availableConstructionsTable.addBorder(2f, Color.WHITE)) availableConstructionsScrollPane.setOverscroll(false, false) @@ -149,14 +141,13 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { } private fun updateButtons(construction: IConstruction?) { - buyButtonsTable.clear() if (!cityScreen.canChangeState) return /** [UniqueType.MayBuyConstructionsInPuppets] support - we need a buy button for civs that could buy items in puppets */ if (cityScreen.city.isPuppet && !cityScreen.city.getMatchingUniques(UniqueType.MayBuyConstructionsInPuppets).any()) return - buyButtonsTable.add(getQueueButton(construction)).padRight(5f) - if (construction != null && construction !is PerpetualConstruction) - for (button in getBuyButtons(construction as INonPerpetualConstruction)) - buyButtonsTable.add(button).padRight(5f) + buttonsTable.clear() + buyButtonFactory.addBuyButtons(buttonsTable, construction) { + it.padRight(5f) + } } private fun updateConstructionQueue() { @@ -553,38 +544,6 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { || city.isPuppet } - private fun getQueueButton(construction: IConstruction?): TextButton { - val city = cityScreen.city - val cityConstructions = city.cityConstructions - val button: TextButton - - if (isSelectedQueueEntry()) { - button = "Remove from queue".toTextButton() - button.onActivation(binding = KeyboardBinding.AddConstruction) { - cityConstructions.removeFromQueue(selectedQueueEntry, false) - cityScreen.clearSelection() - selectedQueueEntry = -1 - cityScreen.city.reassignPopulation() - cityScreen.update() - } - if (city.isPuppet) - button.disable() - } else { - button = "Add to queue".toTextButton() - if (construction == null - || cannotAddConstructionToQueue(construction, city, cityConstructions)) { - button.disable() - } else { - button.onActivation(binding = KeyboardBinding.AddConstruction, sound = UncivSound.Silent) { - addConstructionToQueue(construction, cityConstructions) - } - } - } - - button.labelCell.pad(5f) - return button - } - private fun addConstructionToQueue(construction: IConstruction, cityConstructions: CityConstructions) { // Some evil person decided to double tap real fast - #4977 if (cannotAddConstructionToQueue(construction, cityScreen.city, cityConstructions)) @@ -618,155 +577,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { else -> UncivSound.Click } } - - private fun getBuyButtons(construction: INonPerpetualConstruction?): List { - return Stat.statsUsableToBuy.mapNotNull { getBuyButton(construction, it) } - } - - private fun getBuyButton(construction: INonPerpetualConstruction?, stat: Stat = Stat.Gold): TextButton? { - if (stat !in Stat.statsUsableToBuy || construction == null) - return null - - val city = cityScreen.city - val button = "".toTextButton() - - if (!isConstructionPurchaseShown(construction, stat)) { - // This can't ever be bought with the given currency. - // We want one disabled "buy" button without a price for "priceless" buildings such as wonders - // We don't want such a button when the construction can be bought using a different currency - if (stat != Stat.Gold || construction.canBePurchasedWithAnyStat(city)) - return null - button.setText("Buy".tr()) - button.disable() - } else { - val constructionBuyCost = construction.getStatBuyCost(city, stat)!! - button.setText("Buy".tr() + " " + constructionBuyCost.tr() + stat.character) - - button.onActivation(binding = KeyboardBinding.BuyConstruction) { - button.disable() - buyButtonOnClick(construction, stat) - } - button.isEnabled = cityScreen.canCityBeChanged() && - city.cityConstructions.isConstructionPurchaseAllowed(construction, stat, constructionBuyCost) - preferredBuyStat = stat // Not very intelligent, but the least common currency "wins" - } - - button.labelCell.pad(5f) - - return button - } - - private fun buyButtonOnClick(construction: INonPerpetualConstruction, stat: Stat = preferredBuyStat) { - if (construction !is Building || !construction.hasCreateOneImprovementUnique()) - return askToBuyConstruction(construction, stat) - if (selectedQueueEntry < 0) - return cityScreen.startPickTileForCreatesOneImprovement(construction, stat, true) - // Buying a UniqueType.CreatesOneImprovement building from queue must pass down - // the already selected tile, otherwise a new one is chosen from Automation code. - val improvement = construction.getImprovementToCreate( - cityScreen.city.getRuleset(), cityScreen.city.civ)!! - val tileForImprovement = cityScreen.city.cityConstructions.getTileForImprovement(improvement.name) - askToBuyConstruction(construction, stat, tileForImprovement) - } - - /** Ask whether user wants to buy [construction] for [stat]. - * - * Used from onClick and keyboard dispatch, thus only minimal parameters are passed, - * and it needs to do all checks and the sound as appropriate. - */ - fun askToBuyConstruction( - construction: INonPerpetualConstruction, - stat: Stat = preferredBuyStat, - tile: Tile? = null - ) { - if (!isConstructionPurchaseShown(construction, stat)) return - val city = cityScreen.city - val constructionStatBuyCost = construction.getStatBuyCost(city, stat)!! - if (!city.cityConstructions.isConstructionPurchaseAllowed(construction, stat, constructionStatBuyCost)) return - - cityScreen.closeAllPopups() - ConfirmBuyPopup(construction, stat,constructionStatBuyCost, tile) - } - - private inner class ConfirmBuyPopup( - construction: INonPerpetualConstruction, - stat: Stat, - constructionStatBuyCost: Int, - tile: Tile? - ) : Popup(cityScreen.stage) { - init { - val city = cityScreen.city - val balance = city.getStatReserve(stat) - val majorityReligion = city.religion.getMajorityReligion() - val yourReligion = city.civ.religionManager.religion - val isBuyingWithFaithForForeignReligion = construction.hasUnique(UniqueType.ReligiousUnit) - && !construction.hasUnique(UniqueType.TakeReligionOverBirthCity) - && majorityReligion != yourReligion - - addGoodSizedLabel("Currently you have [$balance] [${stat.name}].").padBottom(10f).row() - if (isBuyingWithFaithForForeignReligion) { - // Earlier tests should forbid this Popup unless both religions are non-null, but to be safe: - fun Religion?.getName() = this?.getReligionDisplayName() ?: Constants.unknownCityName - addGoodSizedLabel("You are buying a religious unit in a city that doesn't follow the religion you founded ([${yourReligion.getName()}]). " + - "This means that the unit is tied to that foreign religion ([${majorityReligion.getName()}]) and will be less useful.").row() - addGoodSizedLabel("Are you really sure you want to purchase this unit?", Constants.headingFontSize).run { - actor.color = Color.FIREBRICK - padBottom(10f) - row() - } - } - addGoodSizedLabel("Would you like to purchase [${construction.name}] for [$constructionStatBuyCost] [${stat.character}]?").row() - - addCloseButton(Constants.cancel, KeyboardBinding.Cancel) { cityScreen.update() } - val confirmStyle = BaseScreen.skin.get("positive", TextButton.TextButtonStyle::class.java) - addOKButton("Purchase", KeyboardBinding.Confirm, confirmStyle) { - purchaseConstruction(construction, stat, tile) - } - equalizeLastTwoButtonWidths() - open(true) - } - } - - /** This tests whether the buy button should be _shown_ */ - private fun isConstructionPurchaseShown(construction: INonPerpetualConstruction, stat: Stat): Boolean { - val city = cityScreen.city - return construction.canBePurchasedWithStat(city, stat) - } - - /** Called only by askToBuyConstruction's Yes answer - not to be confused with [CityConstructions.purchaseConstruction] - * @param tile supports [UniqueType.CreatesOneImprovement] - */ - private fun purchaseConstruction( - construction: INonPerpetualConstruction, - stat: Stat = Stat.Gold, - tile: Tile? = null - ) { - SoundPlayer.play(stat.purchaseSound) - val city = cityScreen.city - if (!city.cityConstructions.purchaseConstruction(construction, selectedQueueEntry, false, stat, tile)) { - Popup(cityScreen).apply { - add("No space available to place [${construction.name}] near [${city.name}]".tr()).row() - addCloseButton() - open() - } - return - } - if (isSelectedQueueEntry() || cityScreen.selectedConstruction?.isBuildable(city.cityConstructions) != true) { - selectedQueueEntry = -1 - cityScreen.clearSelection() - - // Allow buying next queued or auto-assigned construction right away - city.cityConstructions.chooseNextConstruction() - if (city.cityConstructions.currentConstructionFromQueue.isNotEmpty()) { - val newConstruction = city.cityConstructions.getCurrentConstruction() - if (newConstruction is INonPerpetualConstruction) - cityScreen.selectConstruction(newConstruction) - } - } - cityScreen.city.reassignPopulation() - cityScreen.update() - } - + private fun getMovePriorityButton( arrowDirection: Int, binding: KeyboardBinding, diff --git a/core/src/com/unciv/ui/screens/cityscreen/CityScreen.kt b/core/src/com/unciv/ui/screens/cityscreen/CityScreen.kt index 104533bf33..df3dadf17f 100644 --- a/core/src/com/unciv/ui/screens/cityscreen/CityScreen.kt +++ b/core/src/com/unciv/ui/screens/cityscreen/CityScreen.kt @@ -133,6 +133,9 @@ class CityScreen( var pickTileData: PickTileForImprovementData? = null /** A [Building] with [UniqueType.CreatesOneImprovement] has been selected _in the queue_: show the tile it will place the improvement on */ private var selectedQueueEntryTargetTile: Tile? = null + var selectedQueueEntry + get() = constructionsTable.selectedQueueEntry + set(value) { constructionsTable.selectedQueueEntry = value } /** Cached city.expansion.chooseNewTileToOwn() */ // val should be OK as buying tiles is what changes this, and that would re-create the whole CityScreen private val nextTileToOwn = city.expansion.chooseNewTileToOwn() @@ -452,7 +455,7 @@ class CityScreen( val improvement = pickTileData.improvement if (tileInfo.improvementFunctions.canBuildImprovement(improvement, city.civ)) { if (pickTileData.isBuying) { - constructionsTable.askToBuyConstruction(pickTileData.building, pickTileData.buyStat, tileInfo) + BuyButtonFactory(this).askToBuyConstruction(pickTileData.building, pickTileData.buyStat, tileInfo) } else { // This way to store where the improvement a CreatesOneImprovement Building will create goes // might get a bit fragile if several buildings constructing the same improvement type diff --git a/core/src/com/unciv/ui/screens/cityscreen/CityStatsTable.kt b/core/src/com/unciv/ui/screens/cityscreen/CityStatsTable.kt index adb2476ca1..ca6840c595 100644 --- a/core/src/com/unciv/ui/screens/cityscreen/CityStatsTable.kt +++ b/core/src/com/unciv/ui/screens/cityscreen/CityStatsTable.kt @@ -2,7 +2,7 @@ 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.ui.Cell +import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align @@ -32,8 +32,14 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() { private val lowerTable = Table() // table that will be in the ScrollPane private val lowerPane: ScrollPane private val city = cityScreen.city - private val lowerCell: Cell - + private val headerIcon = ImageGetter.getImage("OtherIcons/BackArrow").apply { + setSize(18f, 18f) + setOrigin(Align.center) + rotation = 90f + } + private var headerIconClickArea = Table() + private var isOpen = true + private val detailedStatsButton = "Stats".toTextButton().apply { labelCell.pad(10f) onActivation(binding = KeyboardBinding.ShowStats) { @@ -53,16 +59,22 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() { "CityScreen/CityStatsTable/InnerTable", tintColor = Color.BLACK.cpy().apply { a = 0.8f } ) - innerTable.add(upperTable).row() upperTable.defaults().pad(2f) lowerTable.defaults().pad(2f) lowerPane = ScrollPane(lowerTable) lowerPane.setOverscroll(false, false) lowerPane.setScrollingDisabled(true, false) - lowerCell = innerTable.add(lowerPane) - add(innerTable) + 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() + } } fun update(height: Float) { @@ -93,10 +105,10 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() { val valueToDisplay = if (stat == Stat.Happiness) city.cityStats.happinessList.values.sum() else amount miniStatsTable.add(round(valueToDisplay).toInt().toLabel()).padRight(5f) } - upperTable.add(miniStatsTable) - + upperTable.add(miniStatsTable).expandX() upperTable.addSeparator() - upperTable.add(detailedStatsButton).row() + + lowerTable.add(detailedStatsButton).row() addText() // begin lowerTable @@ -110,11 +122,20 @@ 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 - upperTable.height - 8f) // 2 on each side of each cell in innerTable innerTable.pack() // update innerTable pack() // update self last @@ -158,9 +179,9 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() { }.tr() turnsToPopString += " (${city.population.foodStored}${Fonts.food}/${city.population.getFoodToNextPopulation()}${Fonts.food})" - upperTable.add(unassignedPopLabel).row() - upperTable.add(turnsToExpansionString.toLabel()).row() - upperTable.add(turnsToPopString.toLabel()).row() + lowerTable.add(unassignedPopLabel).row() + lowerTable.add(turnsToExpansionString.toLabel()).row() + lowerTable.add(turnsToPopString.toLabel()).row() val tableWithIcons = Table() tableWithIcons.defaults().pad(2f) @@ -200,7 +221,7 @@ class CityStatsTable(private val cityScreen: CityScreen) : Table() { tableWithIcons.add(wltkLabel).row() } - upperTable.add(tableWithIcons).row() + lowerTable.add(tableWithIcons).row() } private fun addCitizenManagement() { diff --git a/core/src/com/unciv/ui/screens/cityscreen/ConstructionInfoTable.kt b/core/src/com/unciv/ui/screens/cityscreen/ConstructionInfoTable.kt index 2d3803c868..36a626d134 100644 --- a/core/src/com/unciv/ui/screens/cityscreen/ConstructionInfoTable.kt +++ b/core/src/com/unciv/ui/screens/cityscreen/ConstructionInfoTable.kt @@ -25,6 +25,7 @@ import com.unciv.ui.screens.basescreen.BaseScreen class ConstructionInfoTable(val cityScreen: CityScreen) : Table() { private val selectedConstructionTable = Table() + private val buyButtonFactory = BuyButtonFactory(cityScreen) init { selectedConstructionTable.background = BaseScreen.skinStrings.getUiBackground( @@ -89,9 +90,23 @@ class ConstructionInfoTable(val cityScreen: CityScreen) : Table() { descriptionLabel.wrap = true add(descriptionLabel).colspan(2).width(stage.width / 4) - // Show sell button if construction is a currently sellable building - if (construction is Building && cityConstructions.isBuilt(construction.name) - && construction.isSellable()) { + if (cityConstructions.isBuilt(construction.name)) { + showSellButton(construction) + } else if (buyButtonFactory.hasBuyButtons(construction)) { + row() + buyButtonFactory.addBuyButtons(selectedConstructionTable, construction) { + it.padTop(5f).colspan(2).center() + } + } + } + } + + // Show sell button if construction is a currently sellable building + private fun showSellButton( + construction: IConstruction + ) { + if (construction is Building && construction.isSellable()) { + selectedConstructionTable.run { val sellAmount = cityScreen.city.getGoldForSellingBuilding(construction.name) val sellText = "{Sell} $sellAmount " + Fonts.gold val sellBuildingButton = sellText.toTextButton() @@ -138,4 +153,5 @@ class ConstructionInfoTable(val cityScreen: CityScreen) : Table() { cityScreen.clearSelection() cityScreen.update() } + }