mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-26 21:35:14 -04:00
Unit upgrade menu can scroll (#11218)
* ScrollableAnimatedMenuPopup widget and base UnitUpgradeMenu off it * More lack of resources info in UnitUpgradeMenu * Make UnitUpgradeMenu available even if you can't afford any upgrade
This commit is contained in:
parent
8946ee8bfa
commit
83fb2d048c
@ -1,6 +1,5 @@
|
||||
package com.unciv.ui.objectdescriptions
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.GUI
|
||||
import com.unciv.logic.city.City
|
||||
@ -18,7 +17,6 @@ import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.components.extensions.getConsumesAmountString
|
||||
import com.unciv.ui.components.fonts.Fonts
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.civilopediascreen.FormattedLine
|
||||
import com.unciv.ui.screens.civilopediascreen.MarkupRenderer
|
||||
|
||||
@ -311,8 +309,6 @@ object BaseUnitDescriptions {
|
||||
val info = sequenceOf(FormattedLine(title, color = "#FDA", icon = unitToUpgradeTo.makeLink(), header = 5)) +
|
||||
getDifferences(ruleset, unitUpgrading, unitToUpgradeTo)
|
||||
.map { FormattedLine(it.first, icon = it.second ?: "") }
|
||||
val infoTable = MarkupRenderer.render(info.asIterable(), 400f)
|
||||
infoTable.background = BaseScreen.skinStrings.getUiBackground("General/Tooltip", BaseScreen.skinStrings.roundedEdgeRectangleShape, Color.DARK_GRAY)
|
||||
return infoTable
|
||||
return MarkupRenderer.render(info.asIterable(), 400f)
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ import com.unciv.utils.Concurrency
|
||||
*
|
||||
* You must provide content by overriding [createContentTable] - see its doc.
|
||||
*
|
||||
* The Popup opens automatically once created. Meant to be used for small menus.
|
||||
* The Popup opens automatically once created.
|
||||
* **Meant to be used for small menus.** - otherwise use [ScrollableAnimatedMenuPopup].
|
||||
* No default close button - recommended to simply use "click-behind".
|
||||
*
|
||||
* The "click-behind" semi-transparent covering of the rest of the stage is much darker than a normal
|
||||
|
77
core/src/com/unciv/ui/popups/ScrollableAnimatedMenuPopup.kt
Normal file
77
core/src/com/unciv/ui/popups/ScrollableAnimatedMenuPopup.kt
Normal file
@ -0,0 +1,77 @@
|
||||
package com.unciv.ui.popups
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||
|
||||
/**
|
||||
* Adds (partial) scrollability to [AnimatedMenuPopup]. See its doc for details.
|
||||
*
|
||||
* Provide content by implementing [createScrollableContent] and [createFixedContent].
|
||||
* If you need to modify outer wrapper styling, override [createWrapperTable].
|
||||
*/
|
||||
abstract class ScrollableAnimatedMenuPopup(
|
||||
stage: Stage,
|
||||
position: Vector2
|
||||
) : AnimatedMenuPopup(stage, position) {
|
||||
|
||||
/** The API of this Widget is moved to [createScrollableContent], [createFixedContent], [createWrapperTable]. */
|
||||
final override fun createContentTable(): Table? {
|
||||
val top = createScrollableContent()
|
||||
?: return null
|
||||
|
||||
// Build content by wrapping scrollable and fixed parts
|
||||
val table = createWrapperTable()
|
||||
val scroll = AutoScrollPane(top)
|
||||
val scrollCell = table.add(scroll).growX()
|
||||
table.row()
|
||||
val bottom = createFixedContent()
|
||||
if (bottom != null) table.add(bottom)
|
||||
|
||||
// ScrollBars need to be told their size
|
||||
val paddedMaxHeight = maxPopupHeight()
|
||||
val desiredTotalHeight = table.prefHeight
|
||||
val desiredScrollHeight = table.getRowPrefHeight(0)
|
||||
val scrollHeight = if (desiredTotalHeight <= paddedMaxHeight) desiredScrollHeight
|
||||
else paddedMaxHeight - (desiredTotalHeight - desiredScrollHeight)
|
||||
|
||||
val paddedMaxWidth = maxPopupWidth()
|
||||
val desiredTotalWidth = table.prefWidth
|
||||
val desiredContentWidth = table.getColumnPrefWidth(0)
|
||||
val scrollWidth = if (desiredTotalWidth <= paddedMaxHeight) desiredContentWidth
|
||||
else paddedMaxWidth - (desiredTotalWidth - desiredContentWidth)
|
||||
|
||||
scrollCell.size(scrollWidth, scrollHeight)
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
/** Provides an empty wrapper Table.
|
||||
*
|
||||
* Override only to change styling.
|
||||
* By default, a rounded edge dark gray background and 5f vertical / 15f horizontal padding for the two halves is used. */
|
||||
open fun createWrapperTable(): Table = super.createContentTable()!!
|
||||
|
||||
/** Provides the scrollable top part
|
||||
* @return `null` to abort opening the entire Popup */
|
||||
abstract fun createScrollableContent(): Table?
|
||||
|
||||
/** Provides the fixed bottom part
|
||||
* @return `null` to make the entire Popup scrollable (so the fixed part takes no vertical space, not even the default padding) */
|
||||
abstract fun createFixedContent(): Table?
|
||||
|
||||
/** Determines maximum usable width
|
||||
*
|
||||
* Use [stageToShowOn] to measure the Stage (from the underlying [Popup]).
|
||||
* Do not use [Actor.stage][stage], it is uninitialized at this point.
|
||||
*/
|
||||
open fun maxPopupWidth() = 0.95f * stageToShowOn.width - 5f
|
||||
|
||||
/** Determines maximum usable height
|
||||
*
|
||||
* Use [stageToShowOn] to measure the Stage (from the underlying [Popup]).
|
||||
* Do not use [Actor.stage][stage], it is uninitialized at this point.
|
||||
*/
|
||||
open fun maxPopupHeight() = 0.95f * stageToShowOn.height - 5f
|
||||
}
|
@ -1,13 +1,17 @@
|
||||
package com.unciv.ui.popups
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.map.mapunit.MapUnit
|
||||
import com.unciv.models.Counter
|
||||
import com.unciv.models.UpgradeUnitAction
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.audio.SoundPlayer
|
||||
import com.unciv.ui.components.extensions.pad
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.widgets.ColorMarkupLabel
|
||||
import com.unciv.ui.objectdescriptions.BaseUnitDescriptions
|
||||
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsUpgrade
|
||||
|
||||
@ -16,9 +20,10 @@ import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsUpgrade
|
||||
* similar units.
|
||||
*
|
||||
* @param stage The stage this will be shown on, passed to Popup and used for clamping **`position`**
|
||||
* @param position stage coordinates to show this centered over - clamped so that nothing is clipped outside the [stage]
|
||||
* @param positionNextTo stage coordinates to show this centered over - clamped so that nothing is clipped outside the [stage]
|
||||
* @param unit Who is ready to upgrade?
|
||||
* @param unitAction Holds pre-calculated info like unitToUpgradeTo, cost or resource requirements. Its action is mapped to the Upgrade button.
|
||||
* @param enable Whether the buttons should be enabled - allows use to display benefits when you can't actually afford them.
|
||||
* @param callbackAfterAnimation If true the following will be delayed until the Popup is actually closed (Stage.hasOpenPopups returns false).
|
||||
* @param onButtonClicked A callback after one or several upgrades have been performed (and the menu is about to close)
|
||||
*/
|
||||
@ -33,9 +38,10 @@ class UnitUpgradeMenu(
|
||||
positionNextTo: Actor,
|
||||
private val unit: MapUnit,
|
||||
private val unitAction: UpgradeUnitAction,
|
||||
private val enable: Boolean,
|
||||
private val callbackAfterAnimation: Boolean = false,
|
||||
private val onButtonClicked: () -> Unit
|
||||
) : AnimatedMenuPopup(stage, getActorTopRight(positionNextTo)) {
|
||||
) : ScrollableAnimatedMenuPopup(stage, getActorTopRight(positionNextTo)) {
|
||||
|
||||
private val unitToUpgradeTo by lazy { unitAction.unitToUpgradeTo }
|
||||
|
||||
@ -58,16 +64,17 @@ class UnitUpgradeMenu(
|
||||
else closeListeners.add(action)
|
||||
}
|
||||
|
||||
override fun createContentTable(): Table {
|
||||
val newInnerTable = BaseUnitDescriptions.getUpgradeInfoTable(
|
||||
unitAction.title, unit.baseUnit, unitToUpgradeTo
|
||||
)
|
||||
newInnerTable.row()
|
||||
newInnerTable.add(getButton("Upgrade", KeyboardBinding.Upgrade, ::doUpgrade))
|
||||
.pad(15f, 15f, 5f, 15f).growX().row()
|
||||
override fun createScrollableContent() =
|
||||
BaseUnitDescriptions.getUpgradeInfoTable(unitAction.title, unit.baseUnit, unitToUpgradeTo)
|
||||
|
||||
override fun createFixedContent() = Table().apply {
|
||||
val singleButton = getButton("Upgrade", KeyboardBinding.Upgrade, ::doUpgrade)
|
||||
// Using Gdx standard here, not our extension `isEnabled`: These have full styling
|
||||
singleButton.isDisabled = !enable
|
||||
add(singleButton).growX().row()
|
||||
|
||||
val allCount = allUpgradableUnits.count()
|
||||
if (allCount <= 1) return newInnerTable
|
||||
if (allCount <= 1) return@apply
|
||||
|
||||
// Note - all same-baseunit units cost the same to upgrade? What if a mod says e.g. 50% discount on Oasis?
|
||||
// - As far as I can see the rest of the upgrading code doesn't support such conditions at the moment.
|
||||
@ -75,15 +82,26 @@ class UnitUpgradeMenu(
|
||||
val allResources = unitAction.newResourceRequirements * allCount
|
||||
val upgradeAllText = "Upgrade all [$allCount] [${unit.name}] ([$allCost] gold)"
|
||||
val upgradeAllButton = getButton(upgradeAllText, KeyboardBinding.UpgradeAll, ::doAllUpgrade)
|
||||
upgradeAllButton.isDisabled = unit.civ.gold < allCost ||
|
||||
allResources.isNotEmpty() &&
|
||||
unit.civ.getCivResourcesByName().run {
|
||||
allResources.any {
|
||||
it.value > (this[it.key] ?: 0)
|
||||
}
|
||||
}
|
||||
newInnerTable.add(upgradeAllButton).pad(2f, 15f).growX().row()
|
||||
return newInnerTable
|
||||
val insufficientGold = unit.civ.gold < allCost
|
||||
val insufficientResources = getInsufficientResourcesMessage(allResources, unit.civ)
|
||||
upgradeAllButton.isDisabled = insufficientGold || insufficientResources.isNotEmpty()
|
||||
add(upgradeAllButton).padTop(7f).growX().row()
|
||||
if (insufficientResources.isEmpty()) return@apply
|
||||
val label = ColorMarkupLabel(insufficientResources, Color.SCARLET)
|
||||
add(label).center()
|
||||
}
|
||||
|
||||
private fun getInsufficientResourcesMessage(requiredResources: Counter<String>, civ: Civilization): String {
|
||||
if (requiredResources.isEmpty()) return ""
|
||||
val available = civ.getCivResourcesByName()
|
||||
val sb = StringBuilder()
|
||||
for ((name, amount) in requiredResources) {
|
||||
val difference = amount - (available[name] ?: 0)
|
||||
if (difference <= 0) continue
|
||||
if (sb.isEmpty()) sb.append('\n')
|
||||
sb.append("Need [$difference] more [$name]".tr())
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun doUpgrade() {
|
||||
@ -95,7 +113,7 @@ class UnitUpgradeMenu(
|
||||
SoundPlayer.playRepeated(unitAction.uncivSound)
|
||||
for (unit in allUpgradableUnits) {
|
||||
val otherAction = UnitActionsUpgrade.getUpgradeActions(unit)
|
||||
.firstOrNull{ (it as UpgradeUnitAction).unitToUpgradeTo == unitToUpgradeTo &&
|
||||
.firstOrNull{ (it as UpgradeUnitAction).unitToUpgradeTo == unitToUpgradeTo &&
|
||||
it.action != null }
|
||||
otherAction?.action?.invoke()
|
||||
}
|
||||
|
@ -281,8 +281,8 @@ class UnitOverviewTab(
|
||||
val selectKey = getUnitIdentifier(unit, unitToUpgradeTo)
|
||||
val upgradeIcon = ImageGetter.getUnitIcon(unitToUpgradeTo.name,
|
||||
if (enable) Color.GREEN else Color.GREEN.darken(0.5f))
|
||||
if (enable) upgradeIcon.onClick {
|
||||
UnitUpgradeMenu(overviewScreen.stage, upgradeIcon, unit, unitAction) {
|
||||
upgradeIcon.onClick {
|
||||
UnitUpgradeMenu(overviewScreen.stage, upgradeIcon, unit, unitAction, enable) {
|
||||
unitListTable.updateUnitListTable()
|
||||
select(selectKey)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.unciv.ui.screens.worldscreen.unit.actions
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Button
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.UncivGame
|
||||
@ -102,8 +103,13 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
|
||||
for (unitAction in pageActionBuckets[currentPage]) {
|
||||
val button = getUnitActionButton(unit, unitAction)
|
||||
if (unitAction is UpgradeUnitAction) {
|
||||
// This is bound even when the button is disabled, but Actor.activate in ActivationExtensions will block any activation for disabled actors...
|
||||
// But the menu is built to be useful even when you can't upgrade - so **hack** it to get the handler through.
|
||||
// Works because our disable() extension also changes style, and because the normal click is ignored due to unitAction.action being null.
|
||||
button.isDisabled = false
|
||||
button.touchable = Touchable.enabled
|
||||
button.onRightClick {
|
||||
UnitUpgradeMenu(worldScreen.stage, button, unit, unitAction, callbackAfterAnimation = true) {
|
||||
UnitUpgradeMenu(worldScreen.stage, button, unit, unitAction, enable = unitAction.action != null, callbackAfterAnimation = true) {
|
||||
worldScreen.shouldUpdate = true
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user