mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-11 11:28:03 +07:00
Expander tab persist (#4905)
* ExpanderTabs remember state * ExpanderTabs remember state - city constructions dynamic * ExpanderTabs remember state - city screen portrait
This commit is contained in:
parent
0f5f3366ed
commit
af92fdc1d2
@ -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> {
|
||||
@ -127,7 +164,7 @@ class CityConstructionsTable(val cityScreen: CityScreen) : Table(CameraStageBase
|
||||
var buttonText = entry.name.tr() + cityConstructions.getTurnsToConstructionString(entry.name, useStoredProduction)
|
||||
for ((resource, amount) in entry.getResourceRequirements()) {
|
||||
buttonText += "\n" + (if (amount == 1) "Consumes 1 [$resource]"
|
||||
else "Consumes [$amount] [$resource]").tr()
|
||||
else "Consumes [$amount] [$resource]").tr()
|
||||
}
|
||||
|
||||
constructionButtonDTOList.add(ConstructionButtonDTO(entry, buttonText,
|
||||
@ -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)
|
||||
}
|
||||
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
|
||||
val units = ArrayList<Table>()
|
||||
val buildableWonders = ArrayList<Table>()
|
||||
val buildableNationalWonders = ArrayList<Table>()
|
||||
val buildableBuildings = ArrayList<Table>()
|
||||
val specialConstructions = ArrayList<Table>()
|
||||
|
||||
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)
|
||||
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()
|
||||
}
|
||||
|
||||
availableConstructionsScrollPane.layout()
|
||||
availableConstructionsScrollPane.scrollY = constrScrollY
|
||||
availableConstructionsScrollPane.updateVisualScroll()
|
||||
val usedHeight = showCityInfoTableButton.height + constructionsQueueScrollPane.height + buttons.height + 3f * pad + 10f
|
||||
getCell(availableConstructionsScrollPane).maxHeight(stage.height - usedHeight)
|
||||
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()
|
||||
|
@ -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()
|
||||
|
@ -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() {
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user