Expander tab persist (#4905)

* ExpanderTabs remember state

* ExpanderTabs remember state - city constructions dynamic

* ExpanderTabs remember state - city screen portrait
This commit is contained in:
SomeTroglodyte 2021-08-19 09:06:52 +02:00 committed by GitHub
parent 0f5f3366ed
commit af92fdc1d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 217 additions and 108 deletions

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
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
@ -16,26 +17,44 @@ import com.unciv.models.translations.tr
import com.unciv.ui.utils.*
import kotlin.concurrent.thread
import kotlin.math.max
import kotlin.math.min
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBaseScreen.skin) {
/**
* Manager to hold and coordinate two widgets for the city screen left side:
* - Construction queue with switch to [ConstructionInfoTable] button and the enqueue / buy buttons.
* 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 val showCityInfoTableButton: TextButton
private val constructionsQueueScrollPane: ScrollPane
private val availableConstructionsScrollPane: ScrollPane
private val constructionsQueueTable = Table()
private val availableConstructionsTable = Table()
private val buttons = Table()
private val pad = 10f
var improvementBuildingToConstruct: Building? = null
private val upperTable = Table(CameraStageBaseScreen.skin)
private val showCityInfoTableButton = "Show stats drilldown".toTextButton()
private val constructionsQueueScrollPane: ScrollPane
private val constructionsQueueTable = Table()
private val buyButtonsTable = Table()
private val lowerTable = Table()
private val availableConstructionsScrollPane: ScrollPane
private val availableConstructionsTable = Table()
private val lowerTableScrollCell: Cell<ScrollPane>
private val pad = 10f
private val posFromEdge = CityScreen.posFromEdge
private val stageHeight = cityScreen.stage.height
/** Gets or sets visibility of [both widgets][CityConstructionsTable] */
var isVisible: Boolean
get() = upperTable.isVisible
set(value) {
upperTable.isVisible = value
lowerTable.isVisible = value
}
init {
showCityInfoTableButton = "Show stats drilldown".toTextButton()
showCityInfoTableButton.onClick {
cityScreen.showConstructionsTable = false
cityScreen.update()
@ -43,33 +62,52 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
constructionsQueueScrollPane = ScrollPane(constructionsQueueTable.addBorder(2f, Color.WHITE))
constructionsQueueScrollPane.setOverscroll(false, false)
constructionsQueueTable.background = ImageGetter.getBackground(Color.BLACK)
upperTable.defaults().left().top()
upperTable.add(showCityInfoTableButton).padLeft(pad).padBottom(pad).row()
upperTable.add(constructionsQueueScrollPane)
.maxHeight(stageHeight / 3 - 10f)
.padBottom(pad).row()
upperTable.add(buyButtonsTable).padBottom(pad).row()
availableConstructionsScrollPane = ScrollPane(availableConstructionsTable.addBorder(2f, Color.WHITE))
availableConstructionsScrollPane.setOverscroll(false, false)
constructionsQueueTable.background = ImageGetter.getBackground(Color.BLACK)
availableConstructionsTable.background = ImageGetter.getBackground(Color.BLACK)
lowerTableScrollCell = lowerTable.add(availableConstructionsScrollPane).bottom()
lowerTable.row()
}
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()
/** Forces layout calculation and returns the upper Table's (construction queue) width */
fun getUpperWidth() = upperTable.packIfNeeded().width
/** Forces layout calculation and returns the lower Table's (available constructions) width
* - or - the upper Table's width, whichever is greater (in case the former only contains "Loading...")
*/
fun getLowerWidth() = max(lowerTable.packIfNeeded().width, getUpperWidth()) //
fun addActorsToStage() {
cityScreen.stage.addActor(upperTable)
cityScreen.stage.addActor(lowerTable)
lowerTable.setPosition(posFromEdge, posFromEdge, Align.bottomLeft)
}
fun update(selectedConstruction: IConstruction?) {
updateButtons(selectedConstruction)
updateConstructionQueue()
pack() // Need to pack before computing space left for bottom panel
upperTable.pack()
// This should work when set once only in addActorsToStage, but it doesn't (table invisible - why?)
upperTable.setPosition(posFromEdge, stageHeight - posFromEdge, Align.topLeft)
updateAvailableConstructions()
pack()
lowerTableScrollCell.maxHeight(stageHeight - upperTable.height - 2 * posFromEdge)
}
private fun updateButtons(construction: IConstruction?) {
buttons.clear()
buttons.add(getQueueButton(construction)).padRight(5f)
buyButtonsTable.clear()
buyButtonsTable.add(getQueueButton(construction)).padRight(5f)
if (construction != null && construction !is PerpetualConstruction)
for (button in getBuyButtons(construction as INonPerpetualConstruction))
buttons.add(button).padRight(5f)
buyButtonsTable.add(button).padRight(5f)
}
private fun updateConstructionQueue() {
@ -111,7 +149,6 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
constructionsQueueScrollPane.layout()
constructionsQueueScrollPane.scrollY = queueScrollY
constructionsQueueScrollPane.updateVisualScroll()
getCell(constructionsQueueScrollPane).maxHeight(stage.height / 3 - 10f)
}
private fun getConstructionButtonDTOs(): ArrayList<ConstructionButtonDTO> {
@ -145,23 +182,23 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
}
private fun updateAvailableConstructions() {
val constrScrollY = availableConstructionsScrollPane.scrollY
val constructionsScrollY = availableConstructionsScrollPane.scrollY
if (!availableConstructionsTable.hasChildren()) { //
availableConstructionsTable.add("Loading...".toLabel()).pad(10f)
}
thread {
// Since this can be a heavy operation and leads to many ANRs on older phones we put the metadata-gathering in another thread.
val constructionButtonDTOList = getConstructionButtonDTOs()
Gdx.app.postRunnable {
val units = ArrayList<Table>()
val buildableWonders = ArrayList<Table>()
val buildableNationalWonders = ArrayList<Table>()
val buildableBuildings = ArrayList<Table>()
val specialConstructions = ArrayList<Table>()
thread {
// Since this can be a heavy operation and leads to many ANRs on older phones we put the metadata-gathering in another thread.
val constructionButtonDTOList = getConstructionButtonDTOs()
Gdx.app.postRunnable {
availableConstructionsTable.clear()
var maxWidth = constructionsQueueTable.width
var maxButtonWidth = constructionsQueueTable.width
for (dto in constructionButtonDTOList) {
val constructionButton = getConstructionButton(dto)
when (dto.construction) {
@ -175,24 +212,27 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
}
is PerpetualConstruction -> specialConstructions.add(constructionButton)
}
if (constructionButton.needsLayout()) constructionButton.pack()
maxWidth = max(maxWidth, constructionButton.width)
maxButtonWidth = max(maxButtonWidth, constructionButton.packIfNeeded().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
availableConstructionsScrollPane.updateVisualScroll()
val usedHeight = showCityInfoTableButton.height + constructionsQueueScrollPane.height + buttons.height + 3f * pad + 10f
getCell(availableConstructionsScrollPane).maxHeight(stage.height - usedHeight)
availableConstructionsTable.apply {
clear()
defaults().left().bottom()
addCategory("Units", units, maxButtonWidth)
addCategory("Wonders", buildableWonders, maxButtonWidth)
addCategory("National Wonders", buildableNationalWonders, maxButtonWidth)
addCategory("Buildings", buildableBuildings, maxButtonWidth)
addCategory("Other", specialConstructions, maxButtonWidth)
pack()
}
setPosition(5f, stage.height - 5f, Align.topLeft)
availableConstructionsScrollPane.apply {
setSize(maxButtonWidth, min(availableConstructionsTable.prefHeight, lowerTableScrollCell.maxHeight))
layout()
scrollY = constructionsScrollY
updateVisualScroll()
}
lowerTable.pack()
}
}
}
@ -216,9 +256,8 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
val constructionResource = cityConstructions.getConstruction(constructionName).getResourceRequirements()
for ((resource, amount) in constructionResource)
if (amount == 1) text += "\n" + "Consumes 1 [$resource]".tr()
else text += "\n" + "Consumes [$amount] [$resource]".tr()
text += if (amount == 1) "\n" + "Consumes 1 [$resource]".tr()
else "\n" + "Consumes [$amount] [$resource]".tr()
table.defaults().pad(2f).minWidth(40f)
if (isFirstConstructionOfItsKind) table.add(getProgressBar(constructionName)).minWidth(5f)
@ -244,7 +283,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
return table
}
fun getProgressBar(constructionName: String): Group {
private fun getProgressBar(constructionName: String): Group {
val cityConstructions = cityScreen.city.cityConstructions
val construction = cityConstructions.getConstruction(constructionName)
if (construction is PerpetualConstruction) return Table()
@ -256,7 +295,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
Color.BROWN.cpy().lerp(Color.WHITE, 0.5f), Color.WHITE)
}
class ConstructionButtonDTO(val construction: IConstruction, val buttonText: String, val rejectionReason: String = "")
private class ConstructionButtonDTO(val construction: IConstruction, val buttonText: String, val rejectionReason: String = "")
private fun getConstructionButton(constructionButtonDTO: ConstructionButtonDTO): Table {
val construction = constructionButtonDTO.construction
@ -301,7 +340,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
private fun isSelectedQueueEntry(): Boolean = selectedQueueEntry >= 0
fun cannotAddConstructionToQueue(construction: IConstruction, city: CityInfo, cityConstructions: CityConstructions): Boolean {
private fun cannotAddConstructionToQueue(construction: IConstruction, city: CityInfo, cityConstructions: CityConstructions): Boolean {
return cityConstructions.isQueueFull()
|| !cityConstructions.getConstruction(construction.name).isBuildable(cityConstructions)
|| !cityScreen.canChangeState
@ -342,7 +381,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
return button
}
fun addConstructionToQueue(construction: IConstruction, cityConstructions: CityConstructions) {
private fun addConstructionToQueue(construction: IConstruction, cityConstructions: CityConstructions) {
if (construction is Building && construction.uniqueObjects.any { it.placeholderText == "Creates a [] improvement on a specific tile" }) {
cityScreen.selectedTile
improvementBuildingToConstruct = construction
@ -356,7 +395,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
cityScreen.game.settings.addCompletedTutorialTask("Pick construction")
}
fun getConstructionSound(construction: IConstruction): UncivSound {
private fun getConstructionSound(construction: IConstruction): UncivSound {
return when(construction) {
is Building -> UncivSound.Construction
is BaseUnit -> UncivSound.Promote
@ -493,11 +532,22 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
.pad(4f)
}
private fun resizeAvailableConstructionsScrollPane() {
availableConstructionsScrollPane.height = min(availableConstructionsTable.prefHeight, lowerTableScrollCell.maxHeight)
lowerTable.pack()
}
private fun Table.addCategory(title: String, list: ArrayList<Table>, prefWidth: Float) {
if (list.isEmpty()) return
if (rows > 0) addSeparator()
val expander = ExpanderTab(title, defaultPad = 0f, expanderWidth = prefWidth) {
val expander = ExpanderTab(
title,
defaultPad = 0f,
expanderWidth = prefWidth,
persistenceID = "CityConstruction.$title",
onChange = { resizeAvailableConstructionsScrollPane() }
) {
for (table in list) {
it.addSeparator(colSpan = 1)
it.add(table).left().row()

View File

@ -52,9 +52,9 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS
pack()
}
private fun Table.addCategory(str: String, showHideTable: Table) {
private fun Table.addCategory(category: String, showHideTable: Table) {
val categoryWidth = cityScreen.stage.width / 4
val expander = ExpanderTab(str) {
val expander = ExpanderTab(category, persistenceID = "CityInfo") {
it.add(showHideTable).minWidth(categoryWidth)
}
addSeparator()

View File

@ -12,9 +12,13 @@ import com.unciv.ui.map.TileGroupMap
import com.unciv.ui.tilegroups.TileSetStrings
import com.unciv.ui.utils.*
import java.util.*
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
companion object {
/** Distance from stage edges to floating widgets */
const val posFromEdge = 5f
}
var selectedTile: TileInfo? = null
var selectedConstruction: IConstruction? = null
@ -26,7 +30,10 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
// Clockwise from the top-left
/** Displays current production, production queue and available productions list - sits on LEFT */
/** Displays current production, production queue and available productions list
* Not a widget, but manages two: construction queue, info toggle button, buy buttons
* in a Table holder on upper LEFT, and available constructions in a ScrollPane lower LEFT.
*/
private var constructionsTable = CityConstructionsTable(this)
/** Displays stats, buildings, specialists and stats drilldown - sits on TOP LEFT, can be toggled to */
@ -56,6 +63,9 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
/** Holds City tiles group*/
private var tileGroups = ArrayList<CityTileGroup>()
/** The ScrollPane for the background map view of the city surroundings */
private val mapScrollPane = ZoomableScrollPane()
init {
onBackButtonClicked { game.setWorldScreen() }
UncivGame.Current.settings.addCompletedTutorialTask("Enter city screen")
@ -64,12 +74,12 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
//stage.setDebugTableUnderMouse(true)
stage.addActor(cityStatsTable)
stage.addActor(constructionsTable)
stage.addActor(tileTable)
stage.addActor(selectedConstructionTable)
stage.addActor(cityPickerTable)
stage.addActor(exitCityButton)
constructionsTable.addActorsToStage()
stage.addActor(cityInfoTable)
stage.addActor(selectedConstructionTable)
stage.addActor(tileTable)
stage.addActor(cityPickerTable) // add late so it's top in Z-order and doesn't get covered in cramped portrait
stage.addActor(exitCityButton)
update()
keyPressDispatcher[Input.Keys.LEFT] = { page(-1) }
@ -77,39 +87,61 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
}
internal fun update() {
// Recalculate Stats
city.cityStats.update()
// Left side, top and bottom: Construction queue / details
if (showConstructionsTable) {
constructionsTable.isVisible = true
cityInfoTable.isVisible = false
constructionsTable.update(selectedConstruction)
} else {
constructionsTable.isVisible = false
cityInfoTable.isVisible = true
cityInfoTable.update()
cityInfoTable.setPosition(posFromEdge, stage.height - posFromEdge, Align.topLeft)
}
city.cityStats.update()
constructionsTable.update(selectedConstruction)
constructionsTable.setPosition(5f, stage.height - 5f, Align.topLeft)
cityInfoTable.update()
cityInfoTable.setPosition(5f, stage.height - 5f, Align.topLeft)
exitCityButton.centerX(stage)
exitCityButton.y = 10f
cityPickerTable.update()
cityPickerTable.centerX(stage)
cityPickerTable.setY(exitCityButton.top + 10f, Align.bottom)
// Bottom right: Tile or selected construction info
tileTable.update(selectedTile)
tileTable.setPosition(stage.width - 5f, 5f, Align.bottomRight)
tileTable.setPosition(stage.width - posFromEdge, posFromEdge, Align.bottomRight)
selectedConstructionTable.update(selectedConstruction)
selectedConstructionTable.setPosition(stage.width - 5f, 5f, Align.bottomRight)
selectedConstructionTable.setPosition(stage.width - posFromEdge, posFromEdge, Align.bottomRight)
// In portrait mode only: calculate already occupied horizontal space
val rightMargin = when {
!isPortrait() -> 0f
selectedTile != null -> tileTable.packIfNeeded().width
selectedConstruction != null -> selectedConstructionTable.packIfNeeded().width
else -> posFromEdge
}
val leftMargin = when {
!isPortrait() -> 0f
showConstructionsTable -> constructionsTable.getLowerWidth()
else -> cityInfoTable.packIfNeeded().width
}
// Bottom center: Name, paging, exit city button
val centeredX = (stage.width - leftMargin - rightMargin) / 2 + leftMargin
exitCityButton.setPosition(centeredX, 10f, Align.bottom)
cityPickerTable.update()
cityPickerTable.setPosition(centeredX, exitCityButton.top + 10f, Align.bottom)
// Top right of screen: Stats / Specialists
cityStatsTable.update()
cityStatsTable.setPosition(stage.width - 5f, stage.height - 5f, Align.topRight)
cityStatsTable.setPosition(stage.width - posFromEdge, stage.height - posFromEdge, Align.topRight)
// Top center: Annex/Raze button
updateAnnexAndRazeCityButton()
// Rest of screen: Map of surroundings
updateTileGroups()
if (isPortrait()) mapScrollPane.apply {
// center scrolling so city center sits more to the bottom right
scrollX = (maxX - constructionsTable.getLowerWidth() - posFromEdge) / 2
scrollY = (maxY - cityStatsTable.packIfNeeded().height - posFromEdge + cityPickerTable.top) / 2
updateVisualScroll()
}
}
private fun updateTileGroups() {
@ -159,9 +191,9 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
razeCityButtonHolder.add(stopRazingCityButton).colspan(cityPickerTable.columns)
}
razeCityButtonHolder.pack()
//goToWorldButton.setSize(goToWorldButton.prefWidth, goToWorldButton.prefHeight)
razeCityButtonHolder.centerX(stage)
razeCityButtonHolder.y = stage.height - razeCityButtonHolder.height - 20
val centerX = if (!isPortrait()) stage.width / 2
else constructionsTable.getUpperWidth().let { it + (stage.width - cityStatsTable.width - it) / 2 }
razeCityButtonHolder.setPosition(centerX, stage.height - 20f, Align.top)
stage.addActor(razeCityButtonHolder)
}
@ -222,16 +254,16 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
}
val tileMapGroup = TileGroupMap(tileGroups, stage.width / 2, stage.height / 2, tileGroupsToUnwrap = tilesToUnwrap)
val scrollPane = ScrollPane(tileMapGroup)
scrollPane.setSize(stage.width, stage.height)
scrollPane.setOrigin(stage.width / 2, stage.height / 2)
scrollPane.center(stage)
stage.addActor(scrollPane)
mapScrollPane.actor = tileMapGroup
mapScrollPane.setSize(stage.width, stage.height)
mapScrollPane.setOrigin(stage.width / 2, stage.height / 2)
mapScrollPane.center(stage)
stage.addActor(mapScrollPane)
scrollPane.layout() // center scrolling
scrollPane.scrollPercentX = 0.5f
scrollPane.scrollPercentY = 0.5f
scrollPane.updateVisualScroll()
mapScrollPane.layout() // center scrolling
mapScrollPane.scrollPercentX = 0.5f
mapScrollPane.scrollPercentY = 0.5f
mapScrollPane.updateVisualScroll()
}
fun exit() {

View File

@ -35,7 +35,7 @@ class ModCheckboxTable(
val padTop = if (isPortrait) 0f else 16f
if (baseRulesetCheckboxes.any()) {
add(ExpanderTab("Base ruleset mods:") {
add(ExpanderTab("Base ruleset mods:", persistenceID = "NewGameBaseMods") {
it.defaults().pad(5f,0f)
for (checkbox in baseRulesetCheckboxes) it.add(checkbox).row()
}).padTop(padTop).growX().row()
@ -45,7 +45,7 @@ class ModCheckboxTable(
addSeparator(Color.DARK_GRAY, height = 1f)
if (extensionRulesetModButtons.any()) {
add(ExpanderTab("Extension mods:") {
add(ExpanderTab("Extension mods:", persistenceID = "NewGameExpansionMods") {
it.defaults().pad(5f,0f)
for (checkbox in extensionRulesetModButtons) it.add(checkbox).row()
}).padTop(padTop).growX().row()

View File

@ -18,19 +18,19 @@ class OfferColumnsTable(private val tradeLogic: TradeLogic, val screen: Diplomac
onChange()
}
private val ourAvailableOffersTable = OffersListScroll {
private val ourAvailableOffersTable = OffersListScroll("OurAvail") {
if (it.type == TradeType.Gold) openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization)
else addOffer(it, tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers)
}
private val ourOffersTable = OffersListScroll {
private val ourOffersTable = OffersListScroll("OurTrade") {
if (it.type == TradeType.Gold) openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization)
else addOffer(it.copy(amount = -it.amount), tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers)
}
private val theirOffersTable = OffersListScroll {
private val theirOffersTable = OffersListScroll("TheirTrade") {
if (it.type == TradeType.Gold) openGoldSelectionPopup(it, tradeLogic.currentTrade.theirOffers, tradeLogic.otherCivilization)
else addOffer(it.copy(amount = -it.amount), tradeLogic.currentTrade.theirOffers, tradeLogic.currentTrade.ourOffers)
}
private val theirAvailableOffersTable = OffersListScroll {
private val theirAvailableOffersTable = OffersListScroll("TheirAvail") {
if (it.type == TradeType.Gold) openGoldSelectionPopup(it, tradeLogic.currentTrade.theirOffers, tradeLogic.otherCivilization)
else addOffer(it, tradeLogic.currentTrade.theirOffers, tradeLogic.currentTrade.ourOffers)
}

View File

@ -13,15 +13,23 @@ import com.unciv.ui.utils.*
import kotlin.math.min
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
class OffersListScroll(val onOfferClicked: (TradeOffer) -> Unit) : ScrollPane(null) {
/**
* Widget for one fourth of an [OfferColumnsTable] - instantiated for ours/theirs × available/traded
* @param persistenceID Part of ID added to [ExpanderTab.persistenceID] to distinguish the four usecases
* @param onOfferClicked What to do when a tradeButton is clicked
*/
class OffersListScroll(
private val persistenceID: String,
private val onOfferClicked: (TradeOffer) -> Unit
) : ScrollPane(null) {
val table = Table(CameraStageBaseScreen.skin).apply { defaults().pad(5f) }
private val expanderTabs = HashMap<TradeType, ExpanderTab>()
/**
* offersToDisplay - the offers which should be displayed as buttons
* otherOffers - the list of other side's offers to compare with whether these offers are unique
* @param offersToDisplay The offers which should be displayed as buttons
* @param otherOffers The list of other side's offers to compare with whether these offers are unique
*/
fun update(offersToDisplay:TradeOffersList, otherOffers: TradeOffersList) {
table.clear()
@ -38,7 +46,7 @@ class OffersListScroll(val onOfferClicked: (TradeOffer) -> Unit) : ScrollPane(nu
}
val offersOfType = offersToDisplay.filter { it.type == offerType }
if (labelName.isNotEmpty() && offersOfType.any()) {
expanderTabs[offerType] = ExpanderTab(labelName) {
expanderTabs[offerType] = ExpanderTab(labelName, persistenceID = "Trade.$persistenceID.$offerType") {
it.defaults().pad(5f)
}
}

View File

@ -17,6 +17,8 @@ import com.unciv.UncivGame
* @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 persistenceID If specified, the ExpanderTab will remember its open/closed state for the duration of one app run
* @param onChange If specified, this will be called after the visual change for a change in [isOpen] completes (e.g. to react to changed size)
* @param initContent Optional lambda with [innerTable] as parameter, to help initialize content.
*/
class ExpanderTab(
@ -26,6 +28,8 @@ class ExpanderTab(
startsOutOpened: Boolean = true,
defaultPad: Float = 10f,
expanderWidth: Float = 0f,
private val persistenceID: String? = null,
private val onChange: (() -> Unit)? = null,
initContent: ((Table) -> Unit)? = null
): Table(CameraStageBaseScreen.skin) {
private companion object {
@ -33,6 +37,8 @@ class ExpanderTab(
const val arrowImage = "OtherIcons/BackArrow"
val arrowColor = Color(1f,0.96f,0.75f,1f)
const val animationDuration = 0.2f
val persistedStates = HashMap<String, Boolean>()
}
private val header = Table(skin) // Header with label and icon, touchable to show/hide
@ -44,7 +50,8 @@ class ExpanderTab(
val innerTable = Table()
/** Indicates whether the contents are currently shown, changing this will animate the widget */
var isOpen = startsOutOpened
// This works because a HashMap _could_ store an entry for the null key but we cannot actually store one when declaring as HashMap<String, Boolean>
var isOpen = persistedStates[persistenceID] ?: startsOutOpened
private set(value) {
if (value == field) return
field = value
@ -81,10 +88,13 @@ class ExpanderTab(
}
private fun update(noAnimation: Boolean = false) {
if (persistenceID != null)
persistedStates[persistenceID] = isOpen
if (noAnimation || !UncivGame.Current.settings.continuousRendering) {
contentWrapper.clear()
if (isOpen) contentWrapper.add(innerTable)
headerIcon.rotation = if (isOpen) 90f else 180f
if (!noAnimation) onChange?.invoke()
return
}
val action = object: FloatAction ( 90f, 180f, animationDuration, Interpolation.linear) {
@ -94,6 +104,7 @@ class ExpanderTab(
if (this.isComplete) {
contentWrapper.clear()
if (isOpen) contentWrapper.add(innerTable)
onChange?.invoke()
}
}
}.apply { isReverse = isOpen }

View File

@ -223,6 +223,14 @@ fun Label.setFontSize(size:Int): Label {
return this
}
/** [pack][WidgetGroup.pack] a [WidgetGroup] if its [needsLayout][WidgetGroup.needsLayout] is true.
* @return the receiver to allow chaining
*/
fun WidgetGroup.packIfNeeded(): WidgetGroup {
if (needsLayout()) pack()
return this
}
/** Get one random element of a given List.
*
* The probability for each element is proportional to the value of its corresponding element in the [weights] List.