diff --git a/core/src/com/unciv/ui/worldscreen/BackgroundActor.kt b/core/src/com/unciv/ui/worldscreen/BackgroundActor.kt new file mode 100644 index 0000000000..2b8e37c1d8 --- /dev/null +++ b/core/src/com/unciv/ui/worldscreen/BackgroundActor.kt @@ -0,0 +1,61 @@ +package com.unciv.ui.worldscreen + +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.g2d.Batch +import com.badlogic.gdx.graphics.g2d.NinePatch +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.Touchable +import com.badlogic.gdx.scenes.scene2d.utils.Drawable +import com.badlogic.gdx.scenes.scene2d.utils.NinePatchDrawable +import com.badlogic.gdx.utils.Align +import com.unciv.ui.images.ImageGetter + +/** An Actor that just draws a Drawable [background], preferably a [NinePatchDrawable] created + * by [BackgroundActor.getRoundedEdgeRectangle], meant to work in Table Cells and to be overlaid with other Widgets. + * The drawable's center can be moved to any of the corners or vertex centers using `align`, which will also scale the + * drawable up by factor 2 and clip to the original rectangle. This can be used to draw rectangles with one or two rounded corners. + * @param align An [Align] constant - In which corner of the [BackgroundActor] rectangle the center of the [background] should be. + */ +class BackgroundActor(val background: Drawable, align: Int) : Actor() { + private val widthMultiplier = if (Align.isCenterHorizontal(align)) 1f else 2f + private val heightMultiplier = if (Align.isCenterVertical(align)) 1f else 2f + private val noClip = Align.isCenterHorizontal(align) && Align.isCenterVertical(align) + private val xOffset = if (Align.isLeft(align)) 0.5f else 0f + private val yOffset = if (Align.isBottom(align)) 0.5f else 0f + + init { + touchable = Touchable.disabled + } + + override fun hit(x: Float, y: Float, touchable: Boolean): Actor? = null + + override fun draw(batch: Batch?, parentAlpha: Float) { + if (batch == null) return + if (noClip) return drawBackground(batch, parentAlpha) + + batch.flush() + if (!clipBegin()) return + val w = width * widthMultiplier + val h = height * heightMultiplier + drawBackground(batch, parentAlpha, x - xOffset * w, y - yOffset * h, w, h) + batch.flush() + clipEnd() + } + + private fun drawBackground(batch: Batch, parentAlpha: Float) = + drawBackground(batch, parentAlpha, x, y, width, height) + private fun drawBackground(batch: Batch, parentAlpha: Float, x: Float, y: Float, w: Float, h: Float) { + val color = color + batch.setColor(color.r, color.g, color.b, color.a * parentAlpha) + background.draw(batch, x, y, w, h) + } + + companion object { + fun getRoundedEdgeRectangle(tintColor: Color): NinePatchDrawable { + val region = ImageGetter.getDrawable("Skin/roundedEdgeRectangle").region + val drawable = NinePatchDrawable(NinePatch(region, 25, 25, 24, 24)) + drawable.setPadding(15f, 15f, 15f, 15f) + return drawable.tint(tintColor) + } + } +} diff --git a/core/src/com/unciv/ui/worldscreen/NotificationsScroll.kt b/core/src/com/unciv/ui/worldscreen/NotificationsScroll.kt index b1cd911eca..8a00afe253 100644 --- a/core/src/com/unciv/ui/worldscreen/NotificationsScroll.kt +++ b/core/src/com/unciv/ui/worldscreen/NotificationsScroll.kt @@ -14,8 +14,7 @@ import kotlin.math.min import com.unciv.ui.utils.AutoScrollPane as ScrollPane class NotificationsScroll( - private val worldScreen: WorldScreen, - private val maxNotificationsHeight: Float + private val worldScreen: WorldScreen ) : ScrollPane(null) { private companion object { /** Scale the entire ScrollPane by this factor */ @@ -41,15 +40,26 @@ class NotificationsScroll( setScale(scaleFactor) } - internal fun update(notifications: MutableList, tileInfoTableHeight: Float) { + /** + * Update widget contents if necessary and recalculate layout + * @param notifications Data to display + * @param maxNotificationsHeight Total height in world screen coordinates + * @param tileInfoTableHeight Height of the portion that may be covered on the bottom - make sure we can scroll up far enough so the bottom entry is visible above this + */ + internal fun update( + notifications: MutableList, + maxNotificationsHeight: Float, + tileInfoTableHeight: Float + ) { + updateContent(notifications) + updateLayout(maxNotificationsHeight, tileInfoTableHeight) + } + private fun updateContent(notifications: MutableList) { // no news? - keep our list as it is, especially don't reset scroll position - if (notificationsHash == notifications.hashCode()) { - sizeScrollingSpacer(tileInfoTableHeight) - layout() - return - } - notificationsHash = notifications.hashCode() + val newHash = notifications.hashCode() + if (notificationsHash == newHash) return + notificationsHash = newHash notificationsTable.clearChildren() endOfTableSpacerCell = null @@ -93,12 +103,17 @@ class NotificationsScroll( } notificationsTable.pack() // needed to get height - prefHeight is set and close but not quite the same value - val filledHeight = notificationsTable.height + } + + private fun updateLayout(maxNotificationsHeight: Float, tileInfoTableHeight: Float) { + val newHeight = min(notificationsTable.height, maxNotificationsHeight * inverseScaleFactor) sizeScrollingSpacer(tileInfoTableHeight) pack() - height = min(filledHeight, maxNotificationsHeight * inverseScaleFactor) // after this, maxY is still incorrect until layout() + if (height == newHeight) return + height = newHeight // after this, maxY is still incorrect until layout() + invalidateHierarchy() } /** Add some empty space that can be scrolled under the TileInfoTable which is covering our lower part */ diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 622dd4fcfe..6474d0eec4 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -16,7 +16,6 @@ import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.MainMenuScreen import com.unciv.UncivGame -import com.unciv.utils.debug import com.unciv.logic.GameInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.ReligionState @@ -58,7 +57,6 @@ import com.unciv.ui.trade.DiplomacyScreen import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.KeyCharAndCode -import com.unciv.ui.utils.UncivDateFormat.formatDate import com.unciv.ui.utils.centerX import com.unciv.ui.utils.colorFromRGB import com.unciv.ui.utils.darken @@ -79,8 +77,8 @@ import com.unciv.ui.worldscreen.status.NextTurnButton import com.unciv.ui.worldscreen.status.StatusButtons import com.unciv.ui.worldscreen.unit.UnitActionsTable import com.unciv.ui.worldscreen.unit.UnitTable +import com.unciv.utils.debug import kotlinx.coroutines.Job -import java.util.* /** * Unciv's world screen @@ -122,8 +120,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas private val statusButtons = StatusButtons(nextTurnButton) private val tutorialTaskTable = Table().apply { background = ImageGetter.getBackground( ImageGetter.getBlue().darken(0.5f)) } + private val notificationsScroll = NotificationsScroll(this) - private val notificationsScroll: NotificationsScroll var shouldUpdate = false private val zoomController = ZoomButtonPair(mapHolder) @@ -134,12 +132,6 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas init { - topBar.setPosition(0f, stage.height - topBar.height) - topBar.width = stage.width - - val maxNotificationsHeight = topBar.y - nextTurnButton.height - - (if (game.settings.showMinimap) minimapWrapper.height else 0f) - 25f - notificationsScroll = NotificationsScroll(this, maxNotificationsHeight) // notifications are right-aligned, they take up only as much space as necessary. notificationsScroll.width = stage.width / 2 @@ -443,8 +435,6 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas techPolicyAndVictoryHolder.setPosition(10f, topBar.y - techPolicyAndVictoryHolder.height - 5f) updateDiplomacyButton(viewingCiv) - topBar.unitSupplyImage.isVisible = selectedCiv.stats().getUnitSupplyDeficit() > 0 - if (!hasOpenPopups() && isPlayersTurn) { when { viewingCiv.shouldShowDiplomaticVotingResults() -> @@ -465,8 +455,12 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas } } } + updateGameplayButtons() - notificationsScroll.update(viewingCiv.notifications, bottomTileInfoTable.height) + + val maxNotificationsHeight = statusButtons.y - + (if (game.settings.showMinimap) minimapWrapper.height else 0f) - 5f + notificationsScroll.update(viewingCiv.notifications, maxNotificationsHeight, bottomTileInfoTable.height) notificationsScroll.setTopRight(stage.width - 10f, statusButtons.y - 5f) val posZoomFromRight = if (game.settings.showMinimap) minimapWrapper.width diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreenTopBar.kt b/core/src/com/unciv/ui/worldscreen/WorldScreenTopBar.kt index 4318b5a5bc..f28a00483b 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreenTopBar.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreenTopBar.kt @@ -3,9 +3,15 @@ package com.unciv.ui.worldscreen 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.* +import com.badlogic.gdx.scenes.scene2d.Touchable +import com.badlogic.gdx.scenes.scene2d.ui.Cell +import com.badlogic.gdx.scenes.scene2d.ui.Container +import com.badlogic.gdx.scenes.scene2d.ui.Label +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.utils.Align import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.ruleset.tile.ResourceType +import com.unciv.models.ruleset.tile.TileResource import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaCategories @@ -15,31 +21,39 @@ import com.unciv.ui.overviewscreen.EmpireOverviewScreen import com.unciv.ui.pickerscreens.PolicyPickerScreen import com.unciv.ui.pickerscreens.TechPickerScreen import com.unciv.ui.popup.popups -import com.unciv.ui.utils.* +import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.MayaCalendar import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import com.unciv.ui.utils.colorFromRGB +import com.unciv.ui.utils.darken +import com.unciv.ui.utils.onClick +import com.unciv.ui.utils.setFontColor +import com.unciv.ui.utils.setFontSize +import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.toTextButton import com.unciv.ui.victoryscreen.VictoryScreen import com.unciv.ui.worldscreen.mainmenu.WorldScreenMenuPopup import kotlin.math.abs import kotlin.math.ceil +import kotlin.math.max import kotlin.math.roundToInt /** * Table consisting of the menu button, current civ, some stats and the overview button for the top of [WorldScreen] */ +//region Fields class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() { - private var selectedCivLabel = worldScreen.selectedCiv.civName.toLabel() - private var selectedCivIconHolder = Container() - private val turnsLabel = "Turns: 0/400".toLabel() private val goldLabel = "0".toLabel(colorFromRGB(225, 217, 71)) private val scienceLabel = "0".toLabel(colorFromRGB(78, 140, 151)) private val happinessLabel = "0".toLabel() private val cultureLabel = "0".toLabel(colorFromRGB(210, 94, 210)) - private val faithLabel = "0".toLabel(colorFromRGB(210, 94, 210)) // TODO: This colour should be changed at some point - private val resourceLabels = HashMap() - private val resourceImages = HashMap() + private val faithLabel = "0".toLabel(colorFromRGB(168, 196, 241)) + private data class ResourceActors(val resource: TileResource, val Label: Label, val icon: Group) + private val resourceActors = ArrayList(12) private val happinessImage = Group() // These are all to improve performance IE reduce update time (was 150 ms on my phone, which is a lot!) @@ -48,27 +62,81 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() { private val malcontentGroup = ImageGetter.getStatIcon("Malcontent") private val happinessGroup = ImageGetter.getStatIcon("Happiness") - val unitSupplyImage = ImageGetter.getImage("OtherIcons/ExclamationMark") - .apply { color = Color.FIREBRICK } + private val statsTable = getStatsTable() + private val resourcesWrapper = Table() + private val resourceTable = getResourceTable() + private val selectedCivTable = SelectedCivilizationTable(worldScreen) + private val overviewButton = OverviewAndSupplyTable(worldScreen) + private val leftFillerCell: Cell + private val rightFillerCell: Cell + + //endregion init { - background = ImageGetter.getBackground(ImageGetter.getBlue().darken(0.5f)) - - add(getStatsTable()).row() - add(getResourceTable()) - - pad(5f) + // Not the Table, the Cells (all except one) have the background. To avoid gaps, _no_ + // padding except inside the cell actors, and all actors need to _fill_ their cell. + val backColor = ImageGetter.getBlue().darken(0.5f) + val backgroundDrawable = ImageGetter.getBackground(backColor) + statsTable.background = backgroundDrawable + resourceTable.background = backgroundDrawable + add(statsTable).colspan(3).growX().row() + add(resourceTable).colspan(3).growX().row() + val leftFillerBG = BackgroundActor.getRoundedEdgeRectangle(backColor) + leftFillerCell = add(BackgroundActor(leftFillerBG, Align.topLeft)) + add().growX() + val rightFillerBG = BackgroundActor.getRoundedEdgeRectangle(backColor) + rightFillerCell = add(BackgroundActor(rightFillerBG, Align.topRight)) pack() - addActor(getMenuButton()) // needs to be after pack + } - addActor(getSelectedCivilizationTable()) + private fun getStatsTable(): Table { + val statsTable = Table() + statsTable.defaults().pad(8f, 3f, 3f, 3f) - addActor(getOverviewAndSupplyButton()) + fun addStat(label: Label, icon: String, isLast: Boolean = false, screenFactory: ()->BaseScreen) { + val image = ImageGetter.getStatIcon(icon) + val action = { + worldScreen.game.setScreen(screenFactory()) + } + label.onClick(action) + image.onClick(action) + statsTable.add(label) + statsTable.add(image).padBottom(6f).size(20f).apply { + if (!isLast) padRight(20f) + } + } + fun addStat(label: Label, icon: String, overviewPage: String, isLast: Boolean = false) = + addStat(label, icon, isLast) { EmpireOverviewScreen(worldScreen.selectedCiv, overviewPage) } + + addStat(goldLabel, "Gold", "Stats") + addStat(scienceLabel, "Science") { TechPickerScreen(worldScreen.selectedCiv) } + + statsTable.add(happinessImage).padBottom(6f).size(20f) + statsTable.add(happinessLabel).padRight(20f) + val invokeResourcesPage = { + worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv, "Resources")) + } + happinessImage.onClick(invokeResourcesPage) + happinessLabel.onClick(invokeResourcesPage) + + addStat(cultureLabel, "Culture") { PolicyPickerScreen(worldScreen, worldScreen.selectedCiv) } + if (worldScreen.gameInfo.isReligionEnabled()) { + addStat(faithLabel, "Faith", "Religion", isLast = true) + } else { + statsTable.add("Religion: Off".toLabel()) + } + + statsTable.pack() + return statsTable } private fun getResourceTable(): Table { + // Since cells with invisible actors still occupy the full actor dimensions, we only prepare + // the future contents for resourcesWrapper here, they're added to the Table in updateResourcesTable val resourceTable = Table() - resourceTable.defaults().pad(5f) + resourcesWrapper.defaults().pad(5f, 5f, 10f, 5f) + resourcesWrapper.touchable = Touchable.enabled + turnsLabel.onClick { if (worldScreen.selectedCiv.isLongCountDisplay()) { val gameInfo = worldScreen.selectedCiv.gameInfo @@ -77,169 +145,171 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() { worldScreen.game.setScreen(VictoryScreen(worldScreen)) } } - resourceTable.add(turnsLabel).padRight(20f) - val revealedStrategicResources = worldScreen.gameInfo.ruleSet.tileResources.values - .filter { it.resourceType == ResourceType.Strategic } // && currentPlayerCivInfo.tech.isResearched(it.revealedBy!!) } - for (resource in revealedStrategicResources) { - val resourceImage = ImageGetter.getResourceImage(resource.name, 20f) - resourceImages[resource.name] = resourceImage - resourceTable.add(resourceImage).padRight(0f) - val resourceLabel = "0".toLabel() - resourceLabels[resource.name] = resourceLabel - resourceTable.add(resourceLabel) - val invokeResourcesPage = { - worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv, "Resources")) - } - resourceLabel.onClick(invokeResourcesPage) - resourceImage.onClick(invokeResourcesPage) + resourcesWrapper.onClick { + worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv, "Resources")) } - resourceTable.pack() + val strategicResources = worldScreen.gameInfo.ruleSet.tileResources.values + .filter { it.resourceType == ResourceType.Strategic } + for (resource in strategicResources) { + val resourceImage = ImageGetter.getResourceImage(resource.name, 20f) + val resourceLabel = "0".toLabel() + resourceActors += ResourceActors(resource, resourceLabel, resourceImage) + } + + // in case the icons are configured higher than a label, we add a dummy - height will be measured once before it's updated + resourcesWrapper.add(resourceActors[0].icon) + resourceTable.add(turnsLabel).pad(5f, 5f, 10f, 5f) + resourceTable.add(resourcesWrapper) return resourceTable } - private fun getStatsTable(): Table { - val statsTable = Table() - statsTable.defaults().pad(3f)//.align(Align.top) + private class OverviewAndSupplyTable(worldScreen: WorldScreen) : Table(BaseScreen.skin) { + val unitSupplyImage = ImageGetter.getImage("OtherIcons/ExclamationMark") + .apply { color = Color.FIREBRICK } + val unitSupplyCell: Cell - statsTable.add(goldLabel) - val goldImage = ImageGetter.getStatIcon("Gold") - statsTable.add(goldImage).padRight(20f).padBottom(6f).size(20f) - val invokeStatsPage = { - worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv, "Stats")) - } - goldLabel.onClick(invokeStatsPage) - goldImage.onClick(invokeStatsPage) - - statsTable.add(scienceLabel) //.apply { setAlignment(Align.center) }).align(Align.top) - val scienceImage = ImageGetter.getStatIcon("Science") - statsTable.add(scienceImage).padRight(20f).padBottom(6f).size(20f) - val invokeTechScreen = { - worldScreen.game.setScreen(TechPickerScreen(worldScreen.selectedCiv)) - } - scienceLabel.onClick(invokeTechScreen) - scienceImage.onClick(invokeTechScreen) - - statsTable.add(happinessImage).padBottom(6f).size(20f) - statsTable.add(happinessLabel).padRight(20f) - val invokeResourcesPage = { - worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv, "Resources")) - } - happinessImage.onClick(invokeResourcesPage) - happinessLabel.onClick(invokeResourcesPage) - - statsTable.add(cultureLabel) - val cultureImage = ImageGetter.getStatIcon("Culture") - statsTable.add(cultureImage).padBottom(6f).size(20f) - val invokePoliciesPage = { - worldScreen.game.setScreen(PolicyPickerScreen(worldScreen, worldScreen.selectedCiv)) - } - cultureLabel.onClick(invokePoliciesPage) - cultureImage.onClick(invokePoliciesPage) - - if(worldScreen.gameInfo.isReligionEnabled()) { - statsTable.add(faithLabel).padLeft(20f) - val faithImage = ImageGetter.getStatIcon("Faith") - statsTable.add(faithImage).padBottom(6f).size(20f) - - val invokeFaithOverview = { - worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv, "Religion")) + init { + unitSupplyImage.onClick { + worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv, "Units")) } - - faithLabel.onClick(invokeFaithOverview) - faithImage.onClick(invokeFaithOverview) - } else { - statsTable.add("Religion: Off".toLabel()).padLeft(20f) + + val overviewButton = "Overview".toTextButton() + overviewButton.addTooltip('e') + overviewButton.onClick { worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv)) } + + unitSupplyCell = add() + add(overviewButton).pad(10f) + pack() } - statsTable.pack() - statsTable.width = worldScreen.stage.width - 20 - return statsTable + fun update(worldScreen: WorldScreen) { + val newVisible = worldScreen.selectedCiv.stats().getUnitSupplyDeficit() > 0 + if (newVisible == unitSupplyCell.hasActor()) return + if (newVisible) unitSupplyCell.setActor(unitSupplyImage) + .size(50f).padLeft(10f) + else unitSupplyCell.setActor(null).size(0f).pad(0f) + invalidate() + pack() + } } - private fun getMenuButton(): Image { - val menuButton = ImageGetter.getImage("OtherIcons/MenuIcon") - .apply { setSize(50f, 50f) } - menuButton.color = Color.WHITE - menuButton.onClick { - val worldScreenMenuPopup = worldScreen.popups.firstOrNull { it is WorldScreenMenuPopup } - if(worldScreenMenuPopup!=null) - worldScreenMenuPopup.close() - else WorldScreenMenuPopup(worldScreen).open(force = true) + private class SelectedCivilizationTable(worldScreen: WorldScreen) : Table(BaseScreen.skin) { + private var selectedCiv = "" + private val selectedCivLabel = "".toLabel() + private val selectedCivIconHolder = Container() + private val menuButton = ImageGetter.getImage("OtherIcons/MenuIcon") + + init { + left() + defaults().pad(10f) + + menuButton.color = Color.WHITE + menuButton.onClick { + val worldScreenMenuPopup = worldScreen.popups.firstOrNull { it is WorldScreenMenuPopup } + if (worldScreenMenuPopup != null) worldScreenMenuPopup.close() + else WorldScreenMenuPopup(worldScreen).open(force = true) + } + + selectedCivLabel.setFontSize(25) + selectedCivLabel.onClick { + val civilopediaScreen = CivilopediaScreen( + worldScreen.selectedCiv.gameInfo.ruleSet, + worldScreen, + CivilopediaCategories.Nation, + worldScreen.selectedCiv.civName + ) + worldScreen.game.setScreen(civilopediaScreen) + } + + selectedCivIconHolder.onClick { + worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv)) + } + + add(menuButton).size(50f).padRight(0f) + add(selectedCivLabel).padRight(0f) + add(selectedCivIconHolder).size(35f) + pack() + } + + fun update(worldScreen: WorldScreen) { + val newCiv = worldScreen.selectedCiv.civName + if (this.selectedCiv == newCiv) return + this.selectedCiv = newCiv + + selectedCivLabel.setText(newCiv.tr()) + val nation = worldScreen.gameInfo.ruleSet.nations[worldScreen.selectedCiv.civName]!! + val selectedCivIcon = ImageGetter.getNationIndicator(nation, 35f) + selectedCivIconHolder.actor = selectedCivIcon + invalidate() + pack() } - menuButton.centerY(this) - menuButton.x = menuButton.y - return menuButton } - private fun getOverviewAndSupplyButton(): Table { - val rightTable = Table(BaseScreen.skin).apply{ defaults().pad(10f) } + private fun layoutButtons() { + removeActor(selectedCivTable) + removeActor(overviewButton) + validate() - unitSupplyImage.onClick { - worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv, "Units")) - } - unitSupplyImage.isVisible = worldScreen.selectedCiv.stats().getUnitSupplyDeficit() > 0 + val statsWidth = statsTable.minWidth + val resourceWidth = resourceTable.minWidth + val overviewWidth = overviewButton.minWidth + val selectedCivWidth = selectedCivTable.minWidth + val leftRightNeeded = max(selectedCivWidth, overviewWidth) + val statsRowHeight = getRowHeight(0) + val baseHeight = statsRowHeight + getRowHeight(1) - val overviewButton = "Overview".toTextButton() - overviewButton.addTooltip('e') - overviewButton.onClick { worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv)) } - - rightTable.add(unitSupplyImage).size(50f) - rightTable.add(overviewButton) - - rightTable.pack() - rightTable.centerY(this) - rightTable.x = worldScreen.stage.width - rightTable.width - 10 - - return rightTable - } - - private fun getSelectedCivilizationTable(): Table { - val selectedCivTable = Table() - selectedCivTable.centerY(this) - selectedCivTable.left() - selectedCivTable.x = getMenuButton().width + 20f - - selectedCivLabel.setFontSize(25) - - selectedCivLabel.onClick { - val civilopeidaScreen = CivilopediaScreen( - worldScreen.selectedCiv.gameInfo.ruleSet, - worldScreen, - CivilopediaCategories.Nation, - worldScreen.selectedCiv.civName - ) - worldScreen.game.setScreen(civilopeidaScreen) + // Check whether it gets cramped on narrow aspect ratios + val fillerHeight: Float // Height of the background filler cells + val buttonY: Float // Vertical center of Civ+Overview buttons relative to this.y + when { + leftRightNeeded * 2f > stage.width - resourceWidth -> { + // Need to shift buttons down to below both stats and resources + fillerHeight = baseHeight + buttonY = overviewButton.minHeight / 2f + } + leftRightNeeded * 2f > stage.width - statsWidth -> { + // Shifting buttons down to below stats row is enough + fillerHeight = statsRowHeight + buttonY = overviewButton.minHeight / 2f + } + else -> { + // Enough space to keep buttons to the left and right of stats and resources + fillerHeight = 0f + buttonY = baseHeight / 2f + } } - val nation = worldScreen.gameInfo.ruleSet.nations[worldScreen.selectedCiv.civName]!! - val selectedCivIcon = ImageGetter.getNationIndicator(nation, 35f) - selectedCivIconHolder.actor = selectedCivIcon - selectedCivIconHolder.onClick { worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv)) } + val leftFillerWidth = if (fillerHeight > 0f) selectedCivWidth else 0f + val rightFillerWidth = if (fillerHeight > 0f) overviewWidth else 0f + if (leftFillerCell.minHeight != fillerHeight + || leftFillerCell.minWidth != leftFillerWidth + || rightFillerCell.minWidth != rightFillerWidth) { + // Gdx fail: containing Table isn't invalidated when setting Cell size + leftFillerCell.width(leftFillerWidth).height(fillerHeight) + rightFillerCell.width(rightFillerWidth).height(fillerHeight) + invalidate() // Without this all attempts to get a recalculated height are doomed + pack() // neither validate nor layout will include the new row height in height + } - selectedCivTable.add(selectedCivLabel).padRight(10f) - selectedCivTable.add(selectedCivIconHolder) - return selectedCivTable + width = stage.width + setPosition(0f, stage.height, Align.topLeft) + selectedCivTable.setPosition(1f, buttonY, Align.left) + overviewButton.setPosition(stage.width, buttonY, Align.right) + addActor(selectedCivTable) // needs to be after pack + addActor(overviewButton) } internal fun update(civInfo: CivilizationInfo) { - val revealedStrategicResources = civInfo.gameInfo.ruleSet.tileResources.values - .filter { it.resourceType == ResourceType.Strategic } - val civResources = civInfo.getCivResources() - for (resource in revealedStrategicResources) { - val isRevealed = resource.revealedBy == null || civInfo.tech.isResearched(resource.revealedBy!!) - resourceLabels[resource.name]!!.isVisible = isRevealed - resourceImages[resource.name]!!.isVisible = isRevealed - val amountText = (civResources.get(resource, "All")?.amount ?: 0).toString() - resourceLabels[resource.name]!!.setText(amountText) - } - - val year = civInfo.gameInfo.getYear() - val yearText = if (civInfo.isLongCountDisplay()) MayaCalendar.yearToMayaDate(year) - else "[" + abs(year) + "] " + (if (year < 0) "BC" else "AD") - turnsLabel.setText(Fonts.turn + "" + civInfo.gameInfo.turns + " | " + yearText.tr()) + updateStatsTable(civInfo) + updateResourcesTable(civInfo) + selectedCivTable.update(worldScreen) + overviewButton.update(worldScreen) + layoutButtons() + } + private fun updateStatsTable(civInfo: CivilizationInfo) { val nextTurnStats = civInfo.statsForNextTurn val goldPerTurn = "(" + (if (nextTurnStats.gold > 0) "+" else "") + nextTurnStats.gold.roundToInt() + ")" goldLabel.setText(civInfo.gold.toString() + goldPerTurn) @@ -260,19 +330,28 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() { cultureLabel.setText(getCultureText(civInfo, nextTurnStats)) faithLabel.setText(civInfo.religionManager.storedFaith.toString() + "(+" + nextTurnStats.faith.roundToInt() + ")") - - updateSelectedCivTable() } - private fun updateSelectedCivTable() { - if (selectedCivLabel.text.toString() == worldScreen.selectedCiv.civName.tr()) return + private fun updateResourcesTable(civInfo: CivilizationInfo) { + val year = civInfo.gameInfo.getYear() + val yearText = if (civInfo.isLongCountDisplay()) MayaCalendar.yearToMayaDate(year) + else "[" + abs(year) + "] " + (if (year < 0) "BC" else "AD") + turnsLabel.setText(Fonts.turn + "" + civInfo.gameInfo.turns + " | " + yearText.tr()) - selectedCivLabel.setText(worldScreen.selectedCiv.civName.tr()) + resourcesWrapper.clearChildren() + var firstPadLeft = 20f // We want a distance from the turns entry to the first resource, but only if any resource is displayed + val civResources = civInfo.getCivResources() + for ((resource, label, icon) in resourceActors) { + if (resource.revealedBy != null && !civInfo.tech.isResearched(resource.revealedBy!!)) + continue + resourcesWrapper.add(icon).padLeft(firstPadLeft).padRight(0f) + firstPadLeft = 5f + val amount = civResources.get(resource, "All")?.amount ?: 0 + label.setText(amount) + resourcesWrapper.add(label).padTop(8f) // digits don't have descenders, so push them down a little + } - val nation = worldScreen.gameInfo.ruleSet.nations[worldScreen.selectedCiv.civName]!! - val selectedCivIcon = ImageGetter.getNationIndicator(nation, 35f) - selectedCivIconHolder.actor = selectedCivIcon - selectedCivIconHolder.onClick { worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv)) } + resourceTable.pack() } private fun getCultureText(civInfo: CivilizationInfo, nextTurnStats: Stats): String { @@ -288,7 +367,7 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() { private fun getHappinessText(civInfo: CivilizationInfo): String { var happinessText = civInfo.getHappiness().toString() val goldenAges = civInfo.goldenAges - happinessText += + happinessText += if (goldenAges.isGoldenAge()) " {GOLDEN AGE}(${goldenAges.turnsLeftForCurrentGoldenAge})".tr() else