mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-28 22:37:02 -04:00
Make ExpanderTab "expand" properly (#9522)
* Make ExpanderTab "expand" properly * Make ExpanderTab "expand" properly - patch1 * Make ExpanderTab "expand" properly - new signature * Make ExpanderTab "expand" properly - enable dynamic content * Minor WorldScreenMusicPopup visual tweaks * Make ExpanderTab "expand" properly - tweaks * Make ExpanderTab "expand" properly - Kdoc and types review * Post-merge fixes
This commit is contained in:
parent
8d2af7af78
commit
ae74dca074
@ -1,31 +1,41 @@
|
|||||||
package com.unciv.ui.components
|
package com.unciv.ui.components
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
import com.badlogic.gdx.math.Interpolation
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||||
import com.badlogic.gdx.scenes.scene2d.actions.FloatAction
|
import com.badlogic.gdx.scenes.scene2d.actions.FloatAction
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Cell
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Container
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Image
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.utils.Layout
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.models.metadata.GameSettings
|
||||||
import com.unciv.ui.components.input.onClick
|
|
||||||
import com.unciv.ui.components.extensions.toLabel
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
|
import com.unciv.ui.components.input.onClick
|
||||||
|
import com.unciv.ui.images.IconCircleGroup
|
||||||
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A widget with a header that when clicked shows/hides a sub-Table.
|
* A widget with a header that when clicked shows/hides a sub-Table.
|
||||||
*
|
*
|
||||||
* @param title The header text, automatically translated.
|
* @param title The header text, automatically translated.
|
||||||
* @param fontSize Size applied to header text (only)
|
* @param fontSize Size applied to header text (only)
|
||||||
* @param icon Optional icon - please use [Image][com.badlogic.gdx.scenes.scene2d.ui.Image] or [IconCircleGroup]
|
* @param icon Optional icon - please use [Image] or [IconCircleGroup] and make sure size is set
|
||||||
|
* @param startsOutOpened Default initial "open" state if no [persistenceID] set or no persistes state found
|
||||||
* @param defaultPad Padding between content and wrapper.
|
* @param defaultPad Padding between content and wrapper.
|
||||||
* @param headerPad Default padding for the header Table.
|
* @param headerPad Default padding for the header Table.
|
||||||
* @param expanderWidth If set initializes header width
|
* @param headerAlign How the header content aligns - use [Align] constants.
|
||||||
|
* @param expanderWidth If set initializes cell minWidth and wrapper width
|
||||||
* @param persistenceID If specified, the ExpanderTab will remember its open/closed state for the duration of one app run
|
* @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 animated Controls whether opening/closing is animated, defaults to the [continuousRendering][GameSettings.continuousRendering] setting.
|
||||||
* @param initContent Optional lambda with [innerTable] as parameter, to help initialize content.
|
* @param content An [Actor] supporting [Layout] with the content to display in expanded state. Will be `pack()`ed!
|
||||||
|
* @param onChange If specified, this will be called on any visual change: repeatedly during animation if enabled, otherwise once after each change to [isOpen]. (e.g. to react to changed size)
|
||||||
*/
|
*/
|
||||||
class ExpanderTab(
|
class ExpanderTab(
|
||||||
title: String,
|
title: String,
|
||||||
@ -34,27 +44,64 @@ class ExpanderTab(
|
|||||||
startsOutOpened: Boolean = true,
|
startsOutOpened: Boolean = true,
|
||||||
defaultPad: Float = 10f,
|
defaultPad: Float = 10f,
|
||||||
headerPad: Float = 10f,
|
headerPad: Float = 10f,
|
||||||
expanderWidth: Float = 0f,
|
headerAlign: Int = Align.center,
|
||||||
|
private val expanderWidth: Float = 0f,
|
||||||
private val persistenceID: String? = null,
|
private val persistenceID: String? = null,
|
||||||
private val onChange: (() -> Unit)? = null,
|
animated: Boolean? = null,
|
||||||
initContent: ((Table) -> Unit)? = null
|
private val content: WidgetGroup,
|
||||||
): Table(BaseScreen.skin) {
|
private val onChange: (() -> Unit)? = null
|
||||||
private companion object {
|
) : Table(BaseScreen.skin) {
|
||||||
const val arrowSize = 18f
|
/** Alternate builder-style constructor for an [ExpanderTab]
|
||||||
const val arrowImage = "OtherIcons/BackArrow"
|
*
|
||||||
val arrowColor = Color(1f,0.96f,0.75f,1f)
|
* @param initContent A lambda with the future [content] as parameter, to help initialize. Will be `pack()`ed when done!
|
||||||
const val animationDuration = 0.2f
|
*/
|
||||||
|
constructor(
|
||||||
|
title: String,
|
||||||
|
fontSize: Int = Constants.headingFontSize,
|
||||||
|
icon: Actor? = null,
|
||||||
|
startsOutOpened: Boolean = true,
|
||||||
|
defaultPad: Float = 10f,
|
||||||
|
headerPad: Float = 10f,
|
||||||
|
headerAlign: Int = Align.center,
|
||||||
|
expanderWidth: Float = 0f,
|
||||||
|
persistenceID: String? = null,
|
||||||
|
animated: Boolean? = null,
|
||||||
|
onChange: (() -> Unit)? = null,
|
||||||
|
initContent: ((Table) -> Unit)
|
||||||
|
) : this (
|
||||||
|
title, fontSize, icon, startsOutOpened, defaultPad,
|
||||||
|
headerPad, headerAlign, expanderWidth, persistenceID, animated,
|
||||||
|
Table(BaseScreen.skin).apply {
|
||||||
|
defaults().growX()
|
||||||
|
initContent(this)
|
||||||
|
},
|
||||||
|
onChange
|
||||||
|
)
|
||||||
|
|
||||||
val persistedStates = HashMap<String, Boolean>()
|
companion object {
|
||||||
|
private const val arrowSize = 18f
|
||||||
|
private const val arrowImage = "OtherIcons/BackArrow"
|
||||||
|
private val arrowColor = Color(1f,0.96f,0.75f,1f)
|
||||||
|
private const val animationDurationForStageHeight = 0.5f // also serves as maximum
|
||||||
|
|
||||||
|
private val persistedStates = HashMap<String, Boolean>()
|
||||||
}
|
}
|
||||||
|
|
||||||
val header = Table(skin) // Header with label and icon, touchable to show/hide
|
// _Please_ don't make header, wrapper or content public. Makes tweaking this widget harder.
|
||||||
|
// If more control is needed and the parameter count gets too high, consider using a Style class
|
||||||
|
// or open class / protected fun createHeader() or dedicated setters instead.
|
||||||
|
private val header = Table(skin) // Header with label and icon, touchable to show/hide
|
||||||
private val headerLabel = title.toLabel(fontSize = fontSize)
|
private val headerLabel = title.toLabel(fontSize = fontSize)
|
||||||
private val headerIcon = ImageGetter.getImage(arrowImage)
|
private val arrowIcon = ImageGetter.getImage(arrowImage)
|
||||||
private val contentWrapper = Table() // Wrapper for innerTable, this is what will be shown/hidden
|
private val headerCell: Cell<Table>
|
||||||
|
|
||||||
/** The container where the client should add the content to toggle */
|
private val wrapper: Container<WidgetGroup>
|
||||||
val innerTable = Table()
|
private val wrapperCell: Cell<Container<WidgetGroup>>
|
||||||
|
private var wrapperWidth: Float = 0f
|
||||||
|
private var wrapperHeight: Float = 0f
|
||||||
|
|
||||||
|
private var currentPercent = 0f
|
||||||
|
private val noAnimation = !(animated ?: UncivGame.Current.settings.continuousRendering)
|
||||||
|
|
||||||
/** Indicates whether the contents are currently shown, changing this will animate the widget */
|
/** Indicates whether the contents are currently shown, changing this will animate the widget */
|
||||||
// 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>
|
// 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>
|
||||||
@ -66,11 +113,14 @@ class ExpanderTab(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
setLayoutEnabled(false)
|
||||||
|
|
||||||
|
header.align(headerAlign)
|
||||||
header.defaults().pad(headerPad)
|
header.defaults().pad(headerPad)
|
||||||
headerIcon.setSize(arrowSize, arrowSize)
|
arrowIcon.setSize(arrowSize, arrowSize)
|
||||||
headerIcon.setOrigin(Align.center)
|
arrowIcon.setOrigin(Align.center)
|
||||||
headerIcon.rotation = 180f
|
arrowIcon.rotation = 180f
|
||||||
headerIcon.color = arrowColor
|
arrowIcon.color = arrowColor
|
||||||
header.background(
|
header.background(
|
||||||
BaseScreen.skinStrings.getUiBackground(
|
BaseScreen.skinStrings.getUiBackground(
|
||||||
"General/ExpanderTab",
|
"General/ExpanderTab",
|
||||||
@ -79,48 +129,78 @@ class ExpanderTab(
|
|||||||
)
|
)
|
||||||
if (icon != null) header.add(icon)
|
if (icon != null) header.add(icon)
|
||||||
header.add(headerLabel)
|
header.add(headerLabel)
|
||||||
header.add(headerIcon).size(arrowSize).align(Align.center)
|
header.add(arrowIcon).size(arrowSize).align(Align.center)
|
||||||
header.touchable= Touchable.enabled
|
header.touchable= Touchable.enabled
|
||||||
header.onClick { toggle() }
|
header.onClick { toggle() }
|
||||||
if (expanderWidth != 0f)
|
|
||||||
defaults().minWidth(expanderWidth)
|
content.pack()
|
||||||
|
measureContent()
|
||||||
|
|
||||||
|
wrapper = Container(content).apply {
|
||||||
|
setRound(false)
|
||||||
|
bottom() // controls what is seen first on opening!
|
||||||
|
setSize(wrapperWidth, 0f)
|
||||||
|
}
|
||||||
|
|
||||||
defaults().growX()
|
defaults().growX()
|
||||||
contentWrapper.defaults().growX().pad(defaultPad)
|
headerCell = add(header).minWidth(wrapperWidth)
|
||||||
innerTable.defaults().growX()
|
row()
|
||||||
add(header).fillY().row()
|
wrapperCell = add(wrapper).size(wrapperWidth, 0f).pad(defaultPad)
|
||||||
add(contentWrapper)
|
|
||||||
contentWrapper.add(innerTable) // update will revert this
|
setLayoutEnabled(true)
|
||||||
initContent?.invoke(innerTable)
|
update(fromInit = true)
|
||||||
if (expanderWidth == 0f) {
|
|
||||||
// Measure content width incl. pad, set header to same width
|
|
||||||
if (innerTable.needsLayout()) contentWrapper.pack()
|
|
||||||
getCell(header).minWidth(contentWrapper.width)
|
|
||||||
}
|
|
||||||
update(noAnimation = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun update(noAnimation: Boolean = false) {
|
override fun getPrefHeight() = header.prefHeight + wrapperHeight * currentPercent
|
||||||
|
|
||||||
|
override fun layout() {
|
||||||
|
// Critical magic here! Key to allow dynamic content.
|
||||||
|
// However, I can't explain why an invalidated header also needs to trigger it. Without, the
|
||||||
|
// WorldScreenMusicPopup's expanders, which are width-controlled by their outer cell's fillX/expandX,
|
||||||
|
// start aligned and same width, but will slightly misalign by some 10f on opening/closing some of them.
|
||||||
|
if (content.needsLayout() || header.needsLayout())
|
||||||
|
contentHasChanged()
|
||||||
|
super.layout()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun contentHasChanged() {
|
||||||
|
val oldWidth = wrapperWidth
|
||||||
|
val oldHeight = wrapperHeight
|
||||||
|
content.pack()
|
||||||
|
measureContent()
|
||||||
|
if (wrapperWidth == oldWidth && wrapperHeight == oldHeight) return
|
||||||
|
headerCell.minWidth(wrapperWidth)
|
||||||
|
currentPercent *= oldHeight / wrapperHeight // to animate smoothly to new height, >1f should work too
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun measureContent() {
|
||||||
|
wrapperWidth = if (expanderWidth > 0f) expanderWidth else content.width
|
||||||
|
wrapperHeight = content.height
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun update(fromInit: Boolean = false) {
|
||||||
if (persistenceID != null)
|
if (persistenceID != null)
|
||||||
persistedStates[persistenceID] = isOpen
|
persistedStates[persistenceID] = isOpen
|
||||||
if (noAnimation || !UncivGame.Current.settings.continuousRendering) {
|
|
||||||
contentWrapper.clear()
|
if (noAnimation || fromInit) {
|
||||||
if (isOpen) contentWrapper.add(innerTable)
|
updateContentVisibility(if (isOpen) 1f else 0f)
|
||||||
headerIcon.rotation = if (isOpen) 90f else 180f
|
wrapper.isVisible = isOpen
|
||||||
if (!noAnimation) onChange?.invoke()
|
if (!fromInit) onChange?.invoke()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val action = object: FloatAction ( 90f, 180f, animationDuration, Interpolation.linear) {
|
|
||||||
override fun update(percent: Float) {
|
clearActions()
|
||||||
super.update(percent)
|
addAction(ExpandAction())
|
||||||
headerIcon.rotation = this.value
|
}
|
||||||
if (this.isComplete) {
|
|
||||||
contentWrapper.clear()
|
private fun updateContentVisibility(percent: Float) {
|
||||||
if (isOpen) contentWrapper.add(innerTable)
|
currentPercent = percent
|
||||||
onChange?.invoke()
|
val height = percent * wrapperHeight
|
||||||
}
|
wrapperCell.size(wrapperWidth, height) // needed for layout
|
||||||
}
|
wrapper.setSize(wrapperWidth, height) // needed for clipping
|
||||||
}.apply { isReverse = isOpen }
|
arrowIcon.rotation = 90f * (2f - percent)
|
||||||
addAction(action)
|
invalidateHierarchy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Toggle [isOpen], animated */
|
/** Toggle [isOpen], animated */
|
||||||
@ -128,8 +208,38 @@ class ExpanderTab(
|
|||||||
isOpen = !isOpen
|
isOpen = !isOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Change header label text after initialization */
|
/** Change header label text after initialization - **no** auto-translation! */
|
||||||
fun setText(text: String) {
|
fun setText(text: String) {
|
||||||
headerLabel.setText(text)
|
headerLabel.setText(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inner class ExpandAction : FloatAction() {
|
||||||
|
init {
|
||||||
|
start = currentPercent // start from wherever we were if turned around midway
|
||||||
|
end = if (isOpen) 1f else 0f
|
||||||
|
// Duration: shorter if less content height...
|
||||||
|
val heightFactor = stage?.run { wrapperHeight.coerceAtMost(height) / height } ?: 0.5f
|
||||||
|
// ... and shorter if turned around midway
|
||||||
|
val distanceFactor = abs(end - currentPercent)
|
||||||
|
duration = (animationDurationForStageHeight * heightFactor)
|
||||||
|
.coerceAtLeast(0.15f) * distanceFactor
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun begin() {
|
||||||
|
super.begin()
|
||||||
|
wrapper.clip(true)
|
||||||
|
wrapper.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(percent: Float) {
|
||||||
|
super.update(percent)
|
||||||
|
updateContentVisibility(value)
|
||||||
|
onChange?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun end() {
|
||||||
|
wrapper.clip(false)
|
||||||
|
wrapper.isVisible = isOpen // allows turning clip off in closed state
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ class ModCheckTab(
|
|||||||
.apply { color = Color.BLACK }
|
.apply { color = Color.BLACK }
|
||||||
.surroundWithCircle(30f, color = iconColor)
|
.surroundWithCircle(30f, color = iconColor)
|
||||||
|
|
||||||
val expanderTab = ExpanderTab(mod.name, icon = icon, startsOutOpened = false) {
|
val expanderTab = ExpanderTab(mod.name, icon = icon, startsOutOpened = false, headerAlign = Align.left) {
|
||||||
it.defaults().align(Align.left)
|
it.defaults().align(Align.left)
|
||||||
if (!noProblem && mod.folderLocation != null) {
|
if (!noProblem && mod.folderLocation != null) {
|
||||||
val replaceableUniques = getDeprecatedReplaceableUniques(mod)
|
val replaceableUniques = getDeprecatedReplaceableUniques(mod)
|
||||||
@ -143,7 +143,6 @@ class ModCheckTab(
|
|||||||
.joinToString("\n") { line -> line.text }
|
.joinToString("\n") { line -> line.text }
|
||||||
}).row()
|
}).row()
|
||||||
}
|
}
|
||||||
expanderTab.header.left()
|
|
||||||
|
|
||||||
val loadingLabel = modCheckResultTable.children.last()
|
val loadingLabel = modCheckResultTable.children.last()
|
||||||
modCheckResultTable.removeActor(loadingLabel)
|
modCheckResultTable.removeActor(loadingLabel)
|
||||||
|
@ -83,16 +83,15 @@ class CitizenManagementTable(val cityScreen: CityScreen) : Table(BaseScreen.skin
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun asExpander(onChange: (() -> Unit)?): ExpanderTab {
|
fun asExpander(onChange: (() -> Unit)?): ExpanderTab {
|
||||||
|
update()
|
||||||
return ExpanderTab(
|
return ExpanderTab(
|
||||||
title = "{Citizen Management}",
|
title = "{Citizen Management}",
|
||||||
fontSize = Constants.defaultFontSize,
|
fontSize = Constants.defaultFontSize,
|
||||||
persistenceID = "CityStatsTable.CitizenManagement",
|
persistenceID = "CityStatsTable.CitizenManagement",
|
||||||
startsOutOpened = false,
|
startsOutOpened = false,
|
||||||
|
content = this,
|
||||||
onChange = onChange
|
onChange = onChange
|
||||||
) {
|
)
|
||||||
it.add(this)
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,7 @@ class CityReligionInfoTable(
|
|||||||
|
|
||||||
fun asExpander(onChange: (()->Unit)?): ExpanderTab {
|
fun asExpander(onChange: (()->Unit)?): ExpanderTab {
|
||||||
val (icon, label) = getIconAndLabel(religionManager.getMajorityReligion())
|
val (icon, label) = getIconAndLabel(religionManager.getMajorityReligion())
|
||||||
|
defaults().center().pad(5f)
|
||||||
return ExpanderTab(
|
return ExpanderTab(
|
||||||
title = "Majority Religion: [$label]",
|
title = "Majority Religion: [$label]",
|
||||||
fontSize = Constants.defaultFontSize,
|
fontSize = Constants.defaultFontSize,
|
||||||
@ -103,10 +104,8 @@ class CityReligionInfoTable(
|
|||||||
defaultPad = 0f,
|
defaultPad = 0f,
|
||||||
persistenceID = "CityStatsTable.Religion",
|
persistenceID = "CityStatsTable.Religion",
|
||||||
startsOutOpened = false,
|
startsOutOpened = false,
|
||||||
|
content = this,
|
||||||
onChange = onChange
|
onChange = onChange
|
||||||
) {
|
)
|
||||||
defaults().center().pad(5f)
|
|
||||||
it.add(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,7 +232,6 @@ class CityStatsTable(private val cityScreen: CityScreen): Table() {
|
|||||||
otherBuildings.sortBy { it.name }
|
otherBuildings.sortBy { it.name }
|
||||||
|
|
||||||
val totalTable = Table()
|
val totalTable = Table()
|
||||||
lowerTable.addCategory("Buildings", totalTable, false)
|
|
||||||
|
|
||||||
if (specialistBuildings.isNotEmpty()) {
|
if (specialistBuildings.isNotEmpty()) {
|
||||||
val specialistBuildingsTable = Table()
|
val specialistBuildingsTable = Table()
|
||||||
@ -261,6 +260,8 @@ class CityStatsTable(private val cityScreen: CityScreen): Table() {
|
|||||||
for (building in otherBuildings) addBuildingButton(building, regularBuildingsTable)
|
for (building in otherBuildings) addBuildingButton(building, regularBuildingsTable)
|
||||||
totalTable.add(regularBuildingsTable).growX().right().row()
|
totalTable.add(regularBuildingsTable).growX().right().row()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lowerTable.addCategory("Buildings", totalTable, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addBuildingButton(building: Building, destinationTable: Table) {
|
private fun addBuildingButton(building: Building, destinationTable: Table) {
|
||||||
@ -312,17 +313,15 @@ class CityStatsTable(private val cityScreen: CityScreen): Table() {
|
|||||||
destinationTable.add(button).pad(1f).padBottom(2f).padTop(2f).expandX().right().row()
|
destinationTable.add(button).pad(1f).padBottom(2f).padTop(2f).expandX().right().row()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Table.addCategory(category: String, showHideTable: Table, startsOpened: Boolean = true, innerPadding: Float = 10f) : ExpanderTab {
|
private fun Table.addCategory(category: String, showHideTable: Table, startsOpened: Boolean = true) : ExpanderTab {
|
||||||
val expanderTab = ExpanderTab(
|
val expanderTab = ExpanderTab(
|
||||||
title = category,
|
title = category,
|
||||||
fontSize = Constants.defaultFontSize,
|
fontSize = Constants.defaultFontSize,
|
||||||
persistenceID = "CityInfo.$category",
|
persistenceID = "CityInfo.$category",
|
||||||
startsOutOpened = startsOpened,
|
startsOutOpened = startsOpened,
|
||||||
defaultPad = innerPadding,
|
content = showHideTable,
|
||||||
onChange = { onContentResize() }
|
onChange = { onContentResize() }
|
||||||
) {
|
)
|
||||||
it.add(showHideTable).fillX().right()
|
|
||||||
}
|
|
||||||
add(expanderTab).growX().row()
|
add(expanderTab).growX().row()
|
||||||
return expanderTab
|
return expanderTab
|
||||||
}
|
}
|
||||||
|
@ -136,16 +136,15 @@ class SpecialistAllocationTable(private val cityScreen: CityScreen) : Table(Base
|
|||||||
|
|
||||||
|
|
||||||
fun asExpander(onChange: (() -> Unit)?): ExpanderTab {
|
fun asExpander(onChange: (() -> Unit)?): ExpanderTab {
|
||||||
|
update()
|
||||||
return ExpanderTab(
|
return ExpanderTab(
|
||||||
title = "{Specialists}:",
|
title = "{Specialists}:",
|
||||||
fontSize = Constants.defaultFontSize,
|
fontSize = Constants.defaultFontSize,
|
||||||
persistenceID = "CityStatsTable.Specialists",
|
persistenceID = "CityStatsTable.Specialists",
|
||||||
startsOutOpened = true,
|
startsOutOpened = true,
|
||||||
|
content = this,
|
||||||
onChange = onChange
|
onChange = onChange
|
||||||
) {
|
)
|
||||||
it.add(this)
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import com.unciv.models.ruleset.tile.ResourceSupplyList
|
|||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.components.ExpanderTab
|
import com.unciv.ui.components.ExpanderTab
|
||||||
import com.unciv.ui.components.extensions.disable
|
import com.unciv.ui.components.extensions.disable
|
||||||
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
import com.unciv.ui.components.input.onClick
|
import com.unciv.ui.components.input.onClick
|
||||||
import com.unciv.ui.images.IconTextButton
|
import com.unciv.ui.images.IconTextButton
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
@ -41,8 +42,11 @@ class OffersListScroll(
|
|||||||
) : ScrollPane(null) {
|
) : ScrollPane(null) {
|
||||||
val table = Table(BaseScreen.skin).apply { defaults().pad(5f) }
|
val table = Table(BaseScreen.skin).apply { defaults().pad(5f) }
|
||||||
|
|
||||||
|
private data class ExpanderData(
|
||||||
private val expanderTabs = HashMap<TradeType, ExpanderTab>()
|
val label: String,
|
||||||
|
val content: Table = Table().apply { defaults().pad(5f) }
|
||||||
|
)
|
||||||
|
private val expanderContents = HashMap<TradeType, ExpanderData>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param offersToDisplay The offers which should be displayed as buttons
|
* @param offersToDisplay The offers which should be displayed as buttons
|
||||||
@ -55,10 +59,10 @@ class OffersListScroll(
|
|||||||
untradableOffers: ResourceSupplyList = ResourceSupplyList.emptyList
|
untradableOffers: ResourceSupplyList = ResourceSupplyList.emptyList
|
||||||
) {
|
) {
|
||||||
table.clear()
|
table.clear()
|
||||||
expanderTabs.clear()
|
expanderContents.clear()
|
||||||
|
|
||||||
for (offerType in values()) {
|
for (offerType in values()) {
|
||||||
val labelName = when(offerType){
|
val labelName = when(offerType) {
|
||||||
Gold, Gold_Per_Turn, Treaty, Agreement, Introduction -> ""
|
Gold, Gold_Per_Turn, Treaty, Agreement, Introduction -> ""
|
||||||
Luxury_Resource -> "Luxury resources"
|
Luxury_Resource -> "Luxury resources"
|
||||||
Strategic_Resource -> "Strategic resources"
|
Strategic_Resource -> "Strategic resources"
|
||||||
@ -68,11 +72,12 @@ class OffersListScroll(
|
|||||||
}
|
}
|
||||||
val offersOfType = offersToDisplay.filter { it.type == offerType }
|
val offersOfType = offersToDisplay.filter { it.type == offerType }
|
||||||
if (labelName.isNotEmpty() && offersOfType.any()) {
|
if (labelName.isNotEmpty() && offersOfType.any()) {
|
||||||
expanderTabs[offerType] = ExpanderTab(labelName, persistenceID = "Trade.$persistenceID.$offerType") {
|
expanderContents[offerType] = ExpanderData(labelName)
|
||||||
it.defaults().pad(5f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val expanderWidth = (expanderContents.values.maxByOrNull { it.label.length }
|
||||||
|
?.run { label.toLabel(fontSize = Constants.headingFontSize).prefWidth }
|
||||||
|
?: 0f) + 50f // 50 for Expander header pad and arrow
|
||||||
|
|
||||||
for (offerType in values()) {
|
for (offerType in values()) {
|
||||||
val offersOfType = offersToDisplay.filter { it.type == offerType }
|
val offersOfType = offersToDisplay.filter { it.type == offerType }
|
||||||
@ -81,11 +86,6 @@ class OffersListScroll(
|
|||||||
{ if (it.type==City) it.getOfferText() else it.name.tr() }
|
{ if (it.type==City) it.getOfferText() else it.name.tr() }
|
||||||
))
|
))
|
||||||
|
|
||||||
if (expanderTabs.containsKey(offerType)) {
|
|
||||||
expanderTabs[offerType]!!.innerTable.clear()
|
|
||||||
table.add(expanderTabs[offerType]!!).row()
|
|
||||||
}
|
|
||||||
|
|
||||||
for (offer in offersOfType) {
|
for (offer in offersOfType) {
|
||||||
val tradeLabel = offer.getOfferText(untradableOffers.sumBy(offer.name))
|
val tradeLabel = offer.getOfferText(untradableOffers.sumBy(offer.name))
|
||||||
val tradeIcon = when (offer.type) {
|
val tradeIcon = when (offer.type) {
|
||||||
@ -122,11 +122,18 @@ class OffersListScroll(
|
|||||||
else tradeButton.disable() // for instance we have negative gold
|
else tradeButton.disable() // for instance we have negative gold
|
||||||
|
|
||||||
|
|
||||||
if (expanderTabs.containsKey(offerType))
|
if (expanderContents.containsKey(offerType))
|
||||||
expanderTabs[offerType]!!.innerTable.add(tradeButton).row()
|
expanderContents[offerType]!!.content.add(tradeButton).row()
|
||||||
else
|
else
|
||||||
table.add(tradeButton).row()
|
table.add(tradeButton).row()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expanderContents[offerType]?.run {
|
||||||
|
table.add(
|
||||||
|
ExpanderTab(label, expanderWidth = expanderWidth,
|
||||||
|
persistenceID = "Trade.$persistenceID.$offerType", content = content)
|
||||||
|
).row()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
actor = table
|
actor = table
|
||||||
}
|
}
|
||||||
|
@ -120,12 +120,11 @@ class MapEditorViewTab(
|
|||||||
"{Natural Wonders} (${naturalWonders.size})",
|
"{Natural Wonders} (${naturalWonders.size})",
|
||||||
fontSize = 21,
|
fontSize = 21,
|
||||||
startsOutOpened = false,
|
startsOutOpened = false,
|
||||||
headerPad = 5f
|
headerPad = 5f,
|
||||||
) {
|
content = MarkupRenderer.render(lines, iconDisplay = IconDisplay.NoLink) {
|
||||||
it.add(MarkupRenderer.render(lines, iconDisplay = IconDisplay.NoLink) { name->
|
scrollToWonder(it)
|
||||||
scrollToWonder(name)
|
}
|
||||||
})
|
)).row()
|
||||||
}).row()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starting locations not cached like natural wonders - storage is already compact
|
// Starting locations not cached like natural wonders - storage is already compact
|
||||||
@ -136,12 +135,11 @@ class MapEditorViewTab(
|
|||||||
"{Starting locations} (${tileMap.startingLocationsByNation.size})",
|
"{Starting locations} (${tileMap.startingLocationsByNation.size})",
|
||||||
fontSize = 21,
|
fontSize = 21,
|
||||||
startsOutOpened = false,
|
startsOutOpened = false,
|
||||||
headerPad = 5f
|
headerPad = 5f,
|
||||||
) {
|
content = MarkupRenderer.render(lines.asIterable(), iconDisplay = IconDisplay.NoLink) {
|
||||||
it.add(MarkupRenderer.render(lines.asIterable(), iconDisplay = IconDisplay.NoLink) { name ->
|
scrollToStartOfNation(it)
|
||||||
scrollToStartOfNation(name)
|
}
|
||||||
})
|
)).row()
|
||||||
}).row()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addSeparator()
|
addSeparator()
|
||||||
|
@ -244,23 +244,20 @@ class NewGameScreen(
|
|||||||
private fun initPortrait() {
|
private fun initPortrait() {
|
||||||
scrollPane.setScrollingDisabled(false,false)
|
scrollPane.setScrollingDisabled(false,false)
|
||||||
|
|
||||||
topTable.add(ExpanderTab("Game Options") {
|
topTable.add(ExpanderTab("Game Options", content = newGameOptionsTable))
|
||||||
it.add(newGameOptionsTable).row()
|
.expandX().fillX().row()
|
||||||
}).expandX().fillX().row()
|
|
||||||
topTable.addSeparator(Color.DARK_GRAY, height = 1f)
|
topTable.addSeparator(Color.DARK_GRAY, height = 1f)
|
||||||
|
|
||||||
topTable.add(newGameOptionsTable.modCheckboxes).expandX().fillX().row()
|
topTable.add(newGameOptionsTable.modCheckboxes).expandX().fillX().row()
|
||||||
topTable.addSeparator(Color.DARK_GRAY, height = 1f)
|
topTable.addSeparator(Color.DARK_GRAY, height = 1f)
|
||||||
|
|
||||||
topTable.add(ExpanderTab("Map Options") {
|
topTable.add(ExpanderTab("Map Options", content = mapOptionsTable))
|
||||||
it.add(mapOptionsTable).row()
|
.expandX().fillX().row()
|
||||||
}).expandX().fillX().row()
|
|
||||||
topTable.addSeparator(Color.DARK_GRAY, height = 1f)
|
topTable.addSeparator(Color.DARK_GRAY, height = 1f)
|
||||||
|
|
||||||
(playerPickerTable.playerListTable.parent as ScrollPane).setScrollingDisabled(true,true)
|
(playerPickerTable.playerListTable.parent as ScrollPane).setScrollingDisabled(true,true)
|
||||||
topTable.add(ExpanderTab("Civilizations") {
|
topTable.add(ExpanderTab("Civilizations", content = playerPickerTable))
|
||||||
it.add(playerPickerTable).row()
|
.expandX().fillX().row()
|
||||||
}).expandX().fillX().row()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkConnectionToMultiplayerServer(): Boolean {
|
private fun checkConnectionToMultiplayerServer(): Boolean {
|
||||||
|
@ -158,21 +158,16 @@ class ModManagementScreen(
|
|||||||
|
|
||||||
topTable.add(optionsManager.expander).top().growX().row()
|
topTable.add(optionsManager.expander).top().growX().row()
|
||||||
|
|
||||||
installedExpanderTab = ExpanderTab(optionsManager.getInstalledHeader(), expanderWidth = stage.width) {
|
installedExpanderTab = ExpanderTab(optionsManager.getInstalledHeader(), expanderWidth = stage.width, content = scrollInstalledMods)
|
||||||
it.add(scrollInstalledMods).growX()
|
|
||||||
}
|
|
||||||
topTable.add(installedExpanderTab).top().growX().row()
|
topTable.add(installedExpanderTab).top().growX().row()
|
||||||
|
|
||||||
onlineExpanderTab = ExpanderTab(optionsManager.getOnlineHeader(), expanderWidth = stage.width) {
|
onlineExpanderTab = ExpanderTab(optionsManager.getOnlineHeader(), expanderWidth = stage.width, content = scrollOnlineMods)
|
||||||
it.add(scrollOnlineMods).growX()
|
|
||||||
}
|
|
||||||
topTable.add(onlineExpanderTab).top().padTop(10f).growX().row()
|
topTable.add(onlineExpanderTab).top().padTop(10f).growX().row()
|
||||||
|
|
||||||
topTable.add().expandY().row() // helps with top() being ignored
|
topTable.add().expandY().row() // helps with top() being ignored
|
||||||
|
|
||||||
topTable.add(ExpanderTab("Mod info and options", expanderWidth = stage.width) {
|
topTable.add(ExpanderTab("Mod info and options", expanderWidth = stage.width, content = modActionTable))
|
||||||
it.add(modActionTable).growX()
|
.bottom().padTop(10f).growX().row()
|
||||||
}).bottom().padTop(10f).growX().row()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initLandscape() {
|
private fun initLandscape() {
|
||||||
|
@ -34,7 +34,7 @@ class WorldScreenMusicPopup(
|
|||||||
|
|
||||||
private val musicController = UncivGame.Current.musicController
|
private val musicController = UncivGame.Current.musicController
|
||||||
private val trackStyle: TextButton.TextButtonStyle
|
private val trackStyle: TextButton.TextButtonStyle
|
||||||
private val historyExpander: ExpanderTab
|
private val historyTable = Table()
|
||||||
private val visualMods = worldScreen.game.settings.visualMods
|
private val visualMods = worldScreen.game.settings.visualMods
|
||||||
private val mods = worldScreen.gameInfo.gameParameters.mods
|
private val mods = worldScreen.gameInfo.gameParameters.mods
|
||||||
|
|
||||||
@ -58,13 +58,19 @@ class WorldScreenMusicPopup(
|
|||||||
trackStyle.disabledFontColor = Color.LIGHT_GRAY
|
trackStyle.disabledFontColor = Color.LIGHT_GRAY
|
||||||
|
|
||||||
addMusicMods(settings)
|
addMusicMods(settings)
|
||||||
historyExpander = addHistory()
|
addHistory()
|
||||||
addMusicControls(bottomTable, settings, musicController)
|
addMusicControls(bottomTable, settings, musicController)
|
||||||
addCloseButton().colspan(2)
|
addCloseButton().padTop(10f).padBottom(0f).colspan(2)
|
||||||
|
|
||||||
|
getScrollPane()?.run {
|
||||||
|
fadeScrollBars = false
|
||||||
|
if (bottomTable.prefWidth < prefWidth)
|
||||||
|
bottomTable.width = prefWidth
|
||||||
|
}
|
||||||
|
|
||||||
musicController.onChange {
|
musicController.onChange {
|
||||||
historyExpander.innerTable.clear()
|
historyTable.clear()
|
||||||
historyExpander.innerTable.updateTrackList(musicController.getHistory())
|
historyTable.updateTrackList(musicController.getHistory())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,24 +94,24 @@ class WorldScreenMusicPopup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addHistory() = addTrackList("—History—", musicController.getHistory())
|
private fun addHistory() = addTrackList("—History—", musicController.getHistory(), historyTable)
|
||||||
|
|
||||||
private fun addTrackList(title: String, tracks: Sequence<MusicController.MusicTrackInfo>): ExpanderTab {
|
private fun addTrackList(title: String, tracks: Sequence<MusicController.MusicTrackInfo>, table: Table? = null) {
|
||||||
// Note title is either a mod name or something that cannot be a mod name (thanks to the em-dashes)
|
// Note title is either a mod name or something that cannot be a mod name (thanks to the em-dashes)
|
||||||
val icon = when (title) {
|
val icon = when (title) {
|
||||||
in mods -> "OtherIcons/Mods"
|
in mods -> "OtherIcons/Mods"
|
||||||
in visualMods -> "UnitPromotionIcons/Scouting"
|
in visualMods -> "UnitPromotionIcons/Scouting"
|
||||||
else -> null
|
else -> null
|
||||||
}?.let { ImageGetter.getImage(it).apply { setSize(18f) } }
|
}?.let { ImageGetter.getImage(it).apply { setSize(18f) } }
|
||||||
|
val content = table ?: Table()
|
||||||
|
content.defaults().growX()
|
||||||
|
content.updateTrackList(tracks)
|
||||||
val expander = ExpanderTab(title, Constants.defaultFontSize, icon,
|
val expander = ExpanderTab(title, Constants.defaultFontSize, icon,
|
||||||
startsOutOpened = false, defaultPad = 0f, headerPad = 5f,
|
startsOutOpened = false, defaultPad = 0f, headerPad = 5f,
|
||||||
persistenceID = "MusicPopup.$title",
|
persistenceID = "MusicPopup.$title",
|
||||||
) {
|
content = content
|
||||||
it.updateTrackList(tracks)
|
)
|
||||||
}
|
|
||||||
add(expander).colspan(2).growX().row()
|
add(expander).colspan(2).growX().row()
|
||||||
|
|
||||||
return expander
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Table.updateTrackList(tracks: Sequence<MusicController.MusicTrackInfo>) {
|
private fun Table.updateTrackList(tracks: Sequence<MusicController.MusicTrackInfo>) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user