mirror of
https://github.com/yairm210/Unciv.git
synced 2025-01-03 13:30:51 +07:00
Key bindings CityScreen (#9828)
* CityScreen keyboard bindings - ExpanderTab update * CityScreen keyboard bindings - Linting * CityScreen keyboard bindings - Main Keys * CityScreen keyboard bindings - Queue * CityScreen keyboard bindings - Fix Expander scroll-to
This commit is contained in:
parent
a5e1f6d800
commit
52e756e9fb
@ -9,7 +9,7 @@ import com.unciv.logic.civilization.AlertType
|
||||
import com.unciv.logic.civilization.NotificationCategory
|
||||
import com.unciv.logic.civilization.NotificationIcon
|
||||
import com.unciv.logic.civilization.PopupAlert
|
||||
import com.unciv.logic.map.mapunit.MapUnit
|
||||
import com.unciv.logic.map.mapunit.UnitTurnManager
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.logic.multiplayer.isUsersTurn
|
||||
import com.unciv.models.ruleset.Building
|
||||
@ -752,13 +752,15 @@ class CityConstructions : IsPartOfGameInfoSerialization {
|
||||
} else true // we're just continuing the regular queue
|
||||
}
|
||||
|
||||
fun raisePriority(constructionQueueIndex: Int) {
|
||||
fun raisePriority(constructionQueueIndex: Int): Int {
|
||||
constructionQueue.swap(constructionQueueIndex - 1, constructionQueueIndex)
|
||||
return constructionQueueIndex - 1
|
||||
}
|
||||
|
||||
// Lowering == Highering next element in queue
|
||||
fun lowerPriority(constructionQueueIndex: Int) {
|
||||
fun lowerPriority(constructionQueueIndex: Int): Int {
|
||||
raisePriority(constructionQueueIndex + 1)
|
||||
return constructionQueueIndex + 1
|
||||
}
|
||||
|
||||
private fun MutableList<String>.swap(idx1: Int, idx2: Int) {
|
||||
@ -778,7 +780,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
|
||||
val tileForImprovement = getTileForImprovement(improvement.name) ?: return
|
||||
tileForImprovement.stopWorkingOnImprovement() // clears mark
|
||||
if (removeOnly) return
|
||||
/**todo unify with [UnitActions.getImprovementConstructionActions] and [MapUnit.workOnImprovement] - this won't allow e.g. a building to place a road */
|
||||
/**todo unify with [UnitActions.getImprovementConstructionActions] and [UnitTurnManager.workOnImprovement] - this won't allow e.g. a building to place a road */
|
||||
tileForImprovement.changeImprovement(improvement.name)
|
||||
city.civ.lastSeenImprovement[tileForImprovement.position] = improvement.name
|
||||
city.cityStats.update()
|
||||
@ -794,11 +796,11 @@ class CityConstructions : IsPartOfGameInfoSerialization {
|
||||
*/
|
||||
fun removeCreateOneImprovementConstruction(improvement: String) {
|
||||
val ruleset = city.getRuleset()
|
||||
val indexToRemove = constructionQueue.withIndex().mapNotNull {
|
||||
val indexToRemove = constructionQueue.withIndex().firstNotNullOfOrNull {
|
||||
val construction = getConstruction(it.value)
|
||||
val buildingImprovement = (construction as? Building)?.getImprovementToCreate(ruleset)?.name
|
||||
it.index.takeIf { buildingImprovement == improvement }
|
||||
}.firstOrNull() ?: return
|
||||
} ?: return
|
||||
|
||||
constructionQueue.removeAt(indexToRemove)
|
||||
|
||||
|
@ -1,12 +1,29 @@
|
||||
package com.unciv.logic.city
|
||||
|
||||
import com.unciv.logic.IsPartOfGameInfoSerialization
|
||||
import com.unciv.logic.automation.Automation
|
||||
import com.unciv.logic.city.managers.CityPopulationManager
|
||||
import com.unciv.models.stats.Stat
|
||||
import com.unciv.models.stats.Stats
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.screens.cityscreen.CitizenManagementTable
|
||||
|
||||
// if tableEnabled == true, then Stat != null
|
||||
enum class CityFocus(val label: String, val tableEnabled: Boolean, val stat: Stat? = null) :
|
||||
IsPartOfGameInfoSerialization {
|
||||
/**
|
||||
* Controls automatic worker-to-tile assignment
|
||||
* @param label Display label, formatted for tr()
|
||||
* @param tableEnabled Whether to show or hide in CityScreen's [CitizenManagementTable]
|
||||
* @param stat Which stat the default [getStatMultiplier] emphasizes - unused if that is overridden w/o calling super
|
||||
* @param binding Bindable keyboard key in UI - this is an override, by default matching enum names in [KeyboardBinding] are assigned automatically
|
||||
* @see CityPopulationManager.autoAssignPopulation
|
||||
* @see Automation.rankStatsForCityWork
|
||||
*/
|
||||
enum class CityFocus(
|
||||
val label: String,
|
||||
val tableEnabled: Boolean,
|
||||
val stat: Stat? = null,
|
||||
binding: KeyboardBinding? = null
|
||||
) : IsPartOfGameInfoSerialization {
|
||||
// region Enum values
|
||||
NoFocus("Default Focus", true, null) {
|
||||
override fun getStatMultiplier(stat: Stat) = 1f // actually redundant, but that's two steps to see
|
||||
},
|
||||
@ -28,8 +45,16 @@ enum class CityFocus(val label: String, val tableEnabled: Boolean, val stat: Sta
|
||||
}
|
||||
},
|
||||
FaithFocus("[${Stat.Faith.name}] Focus", true, Stat.Faith),
|
||||
HappinessFocus("[${Stat.Happiness.name}] Focus", false, Stat.Happiness);
|
||||
//GreatPersonFocus;
|
||||
HappinessFocus("[${Stat.Happiness.name}] Focus", false, Stat.Happiness),
|
||||
//GreatPersonFocus
|
||||
|
||||
;
|
||||
// endregion Enum values
|
||||
|
||||
val binding: KeyboardBinding =
|
||||
binding ?:
|
||||
KeyboardBinding.values().firstOrNull { it.name == name } ?:
|
||||
KeyboardBinding.None
|
||||
|
||||
open fun getStatMultiplier(stat: Stat) = when (this.stat) {
|
||||
stat -> 3f
|
||||
@ -42,7 +67,9 @@ enum class CityFocus(val label: String, val tableEnabled: Boolean, val stat: Sta
|
||||
}
|
||||
}
|
||||
|
||||
fun safeValueOf(stat: Stat): CityFocus {
|
||||
return values().firstOrNull { it.stat == stat } ?: NoFocus
|
||||
companion object {
|
||||
fun safeValueOf(stat: Stat): CityFocus {
|
||||
return values().firstOrNull { it.stat == stat } ?: NoFocus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,17 @@ import com.badlogic.gdx.math.Interpolation
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.FloatAction
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.keyShortcuts
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.images.IconCircleGroup
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
|
||||
/**
|
||||
@ -36,6 +40,7 @@ class ExpanderTab(
|
||||
headerPad: Float = 10f,
|
||||
expanderWidth: Float = 0f,
|
||||
private val persistenceID: String? = null,
|
||||
toggleKey: KeyboardBinding = KeyboardBinding.None,
|
||||
private val onChange: (() -> Unit)? = null,
|
||||
initContent: ((Table) -> Unit)? = null
|
||||
): Table(BaseScreen.skin) {
|
||||
@ -81,7 +86,8 @@ class ExpanderTab(
|
||||
header.add(headerLabel)
|
||||
header.add(headerIcon).size(arrowSize).align(Align.center)
|
||||
header.touchable= Touchable.enabled
|
||||
header.onClick { toggle() }
|
||||
header.onActivation { toggle() }
|
||||
header.keyShortcuts.add(toggleKey) // Using the onActivation parameter adds a tooltip, which often does not look too good
|
||||
if (expanderWidth != 0f)
|
||||
defaults().minWidth(expanderWidth)
|
||||
defaults().growX()
|
||||
@ -126,9 +132,44 @@ class ExpanderTab(
|
||||
/** Toggle [isOpen], animated */
|
||||
fun toggle() {
|
||||
isOpen = !isOpen
|
||||
|
||||
// In the common case where the expander is hosted in a Table within a ScrollPane...
|
||||
// try scrolling our header so it is visible (when toggled by keyboard)
|
||||
if (parent is Table && parent.parent is ScrollPane)
|
||||
tryAutoScroll(parent.parent as ScrollPane)
|
||||
// But - our Actor.addBorder extension can ruin that, so cater for that special case too...
|
||||
else if (testForBorderedTable())
|
||||
tryAutoScroll(parent.parent.parent as ScrollPane)
|
||||
}
|
||||
|
||||
/** Change header label text after initialization */
|
||||
private fun testForBorderedTable(): Boolean {
|
||||
if (parent !is Table) return false
|
||||
val borderTable = parent.parent as? Table ?: return false
|
||||
if (parent.parent.parent !is ScrollPane) return false
|
||||
return borderTable.cells.size == 1 && borderTable.background != null && borderTable.padTop == 2f
|
||||
}
|
||||
|
||||
private fun tryAutoScroll(scrollPane: ScrollPane) {
|
||||
if (scrollPane.isScrollingDisabledY) return
|
||||
|
||||
// As the "opening" is animated, and right now the animation has just started,
|
||||
// a scroll-to-visible won't work, so limit it to showing the header for now.
|
||||
val heightToShow = header.height
|
||||
|
||||
// Coords as seen by "this" expander relative to parent and as seen by scrollPane may differ by the border size
|
||||
// Also make area to show relative to top
|
||||
val yToShow = this.y + this.height - heightToShow +
|
||||
(if (scrollPane.actor == this.parent) 0f else parent.y)
|
||||
|
||||
// If ever needed - how to check whether scrollTo would not need to scroll (without testing for heightToShow > scrollHeight)
|
||||
// val relativeY = scrollPane.actor.height - yToShow - scrollPane.scrollY
|
||||
// if (relativeY >= heightToShow && relativeY <= scrollPane.scrollHeight) return
|
||||
|
||||
// scrollTo does the y axis inversion for us, and also will do nothing if the requested area is already fully visible
|
||||
scrollPane.scrollTo(0f, yToShow, header.width, heightToShow)
|
||||
}
|
||||
|
||||
/** Change header label text after initialization (does not auto-translate) */
|
||||
fun setText(text: String) {
|
||||
headerLabel.setText(text)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.ui.components.input
|
||||
|
||||
import com.badlogic.gdx.Input
|
||||
import com.unciv.Constants
|
||||
import com.unciv.models.stats.Stat
|
||||
|
||||
|
||||
private val unCamelCaseRegex = Regex("([A-Z])([A-Z])([a-z])|([a-z])([A-Z])")
|
||||
@ -123,6 +124,38 @@ enum class KeyboardBinding(
|
||||
HideAdditionalActions(Category.UnitActions,"Back", Input.Keys.PAGE_UP),
|
||||
AddInCapital(Category.UnitActions, "Add in capital", 'g'),
|
||||
|
||||
// City Screen
|
||||
AddConstruction(Category.CityScreen, "Add to or remove from queue", KeyCharAndCode.RETURN),
|
||||
RaisePriority(Category.CityScreen, "Raise queue priority", Input.Keys.UP),
|
||||
LowerPriority(Category.CityScreen, "Lower queue priority", Input.Keys.DOWN),
|
||||
BuyConstruction(Category.CityScreen, 'b'),
|
||||
BuyTile(Category.CityScreen, 't'),
|
||||
BuildUnits(Category.CityScreen, "Buildable Units", 'u'),
|
||||
BuildBuildings(Category.CityScreen, "Buildable Buildings", 'l'),
|
||||
BuildWonders(Category.CityScreen, "Buildable Wonders", 'w'),
|
||||
BuildNationalWonders(Category.CityScreen, "Buildable National Wonders", 'n'),
|
||||
BuildOther(Category.CityScreen, "Other Constructions", 'o'),
|
||||
NextCity(Category.CityScreen, Input.Keys.RIGHT),
|
||||
PreviousCity(Category.CityScreen, Input.Keys.LEFT),
|
||||
ShowStats(Category.CityScreen, 's'),
|
||||
ShowStatDetails(Category.CityScreen, "Toggle Stat Details", Input.Keys.NUMPAD_ADD),
|
||||
CitizenManagement(Category.CityScreen, 'c'),
|
||||
GreatPeopleDetail(Category.CityScreen, 'g'),
|
||||
SpecialistDetail(Category.CityScreen, 'p'),
|
||||
ReligionDetail(Category.CityScreen, 'r'),
|
||||
BuildingsDetail(Category.CityScreen, 'd'),
|
||||
ResetCitizens(Category.CityScreen, KeyCharAndCode.ctrl('r')),
|
||||
AvoidGrowth(Category.CityScreen, KeyCharAndCode.ctrl('a')),
|
||||
// The following are automatically matched by enum name to CityFocus entries - if necessary override there
|
||||
// Note on label: copied from CityFocus to ensure same translatable is used - without we'd get "Food Focus", not the same as "[Food] Focus"
|
||||
NoFocus(Category.CityScreen, "Default Focus", KeyCharAndCode.ctrl('d')),
|
||||
FoodFocus(Category.CityScreen, "[${Stat.Food.name}] Focus", KeyCharAndCode.ctrl('f')),
|
||||
ProductionFocus(Category.CityScreen, "[${Stat.Production.name}] Focus", KeyCharAndCode.ctrl('p')),
|
||||
GoldFocus(Category.CityScreen, "[${Stat.Gold.name}] Focus", KeyCharAndCode.ctrl('g')),
|
||||
ScienceFocus(Category.CityScreen, "[${Stat.Science.name}] Focus", KeyCharAndCode.ctrl('s')),
|
||||
CultureFocus(Category.CityScreen, "[${Stat.Culture.name}] Focus", KeyCharAndCode.ctrl('c')),
|
||||
FaithFocus(Category.CityScreen, "[${Stat.Faith.name}] Focus", KeyCharAndCode.UNKNOWN),
|
||||
|
||||
// Popups
|
||||
Confirm(Category.Popups, "Confirm Dialog", 'y'),
|
||||
Cancel(Category.Popups, "Cancel Dialog", 'n'),
|
||||
@ -144,6 +177,7 @@ enum class KeyboardBinding(
|
||||
// Conflict checking within group disabled, but any key assigned on WorldScreen is a problem
|
||||
override fun checkConflictsIn() = sequenceOf(WorldScreen)
|
||||
},
|
||||
CityScreen,
|
||||
Popups
|
||||
;
|
||||
val label = unCamelCase(name)
|
||||
|
@ -4,10 +4,11 @@ import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.city.CityFocus
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.components.ExpanderTab
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
|
||||
class CitizenManagementTable(val cityScreen: CityScreen) : Table(BaseScreen.skin) {
|
||||
val city = cityScreen.city
|
||||
@ -24,7 +25,7 @@ class CitizenManagementTable(val cityScreen: CityScreen) : Table(BaseScreen.skin
|
||||
resetCell.add(resetLabel).pad(5f)
|
||||
if (cityScreen.canCityBeChanged()) {
|
||||
resetCell.touchable = Touchable.enabled
|
||||
resetCell.onClick {
|
||||
resetCell.onActivation(binding = KeyboardBinding.ResetCitizens) {
|
||||
city.reassignPopulation(true)
|
||||
cityScreen.update()
|
||||
}
|
||||
@ -41,7 +42,7 @@ class CitizenManagementTable(val cityScreen: CityScreen) : Table(BaseScreen.skin
|
||||
avoidCell.add(avoidLabel).pad(5f)
|
||||
if (cityScreen.canCityBeChanged()) {
|
||||
avoidCell.touchable = Touchable.enabled
|
||||
avoidCell.onClick {
|
||||
avoidCell.onActivation(binding = KeyboardBinding.AvoidGrowth) {
|
||||
city.avoidGrowth = !city.avoidGrowth
|
||||
city.reassignPopulation()
|
||||
cityScreen.update()
|
||||
@ -63,7 +64,10 @@ class CitizenManagementTable(val cityScreen: CityScreen) : Table(BaseScreen.skin
|
||||
cell.add(label).pad(5f)
|
||||
if (cityScreen.canCityBeChanged()) {
|
||||
cell.touchable = Touchable.enabled
|
||||
cell.onClick {
|
||||
// Note the binding here only works when visible, so the main one is on CityStatsTable.miniStatsTable
|
||||
// If we bind both, both are executed - so only add the one here that re-applies the current focus
|
||||
val binding = if (city.cityAIFocus == focus) focus.binding else KeyboardBinding.None
|
||||
cell.onActivation(binding = binding) {
|
||||
city.cityAIFocus = focus
|
||||
city.reassignPopulation()
|
||||
cityScreen.update()
|
||||
@ -88,6 +92,7 @@ class CitizenManagementTable(val cityScreen: CityScreen) : Table(BaseScreen.skin
|
||||
fontSize = Constants.defaultFontSize,
|
||||
persistenceID = "CityStatsTable.CitizenManagement",
|
||||
startsOutOpened = false,
|
||||
toggleKey = KeyboardBinding.CitizenManagement,
|
||||
onChange = onChange
|
||||
) {
|
||||
it.add(this)
|
||||
|
@ -34,13 +34,14 @@ import com.unciv.ui.components.extensions.darken
|
||||
import com.unciv.ui.components.extensions.disable
|
||||
import com.unciv.ui.components.extensions.getConsumesAmountString
|
||||
import com.unciv.ui.components.extensions.isEnabled
|
||||
import com.unciv.ui.components.input.keyShortcuts
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.extensions.packIfNeeded
|
||||
import com.unciv.ui.components.extensions.surroundWithCircle
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.keyShortcuts
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popups.ConfirmPopup
|
||||
import com.unciv.ui.popups.Popup
|
||||
@ -129,13 +130,16 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
}
|
||||
|
||||
fun update(selectedConstruction: IConstruction?) {
|
||||
updateButtons(selectedConstruction)
|
||||
updateQueueAndButtons(selectedConstruction)
|
||||
updateAvailableConstructions()
|
||||
}
|
||||
|
||||
private fun updateQueueAndButtons(construction: IConstruction?) {
|
||||
updateButtons(construction)
|
||||
updateConstructionQueue()
|
||||
upperTable.pack()
|
||||
// This should work when set once only in addActorsToStage, but it doesn't (table invisible - why?)
|
||||
// Need to reposition when height changes as setPosition's alignment does not persist, it's just a readability shortcut to calculate bottomLeft
|
||||
upperTable.setPosition(posFromEdge, stageHeight - posFromEdge, Align.topLeft)
|
||||
|
||||
updateAvailableConstructions()
|
||||
lowerTableScrollCell.maxHeight(stageHeight - upperTable.height - 2 * posFromEdge)
|
||||
}
|
||||
|
||||
@ -279,11 +283,11 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
availableConstructionsTable.apply {
|
||||
clear()
|
||||
defaults().left().bottom()
|
||||
addCategory("Units", units, maxButtonWidth)
|
||||
addCategory("Buildings", buildableBuildings, maxButtonWidth)
|
||||
addCategory("Wonders", buildableWonders, maxButtonWidth)
|
||||
addCategory("National Wonders", buildableNationalWonders, maxButtonWidth)
|
||||
addCategory("Other", specialConstructions, maxButtonWidth)
|
||||
addCategory("Units", units, maxButtonWidth, KeyboardBinding.BuildUnits)
|
||||
addCategory("Buildings", buildableBuildings, maxButtonWidth, KeyboardBinding.BuildBuildings)
|
||||
addCategory("Wonders", buildableWonders, maxButtonWidth, KeyboardBinding.BuildWonders)
|
||||
addCategory("National Wonders", buildableNationalWonders, maxButtonWidth, KeyboardBinding.BuildNationalWonders)
|
||||
addCategory("Other", specialConstructions, maxButtonWidth, KeyboardBinding.BuildOther)
|
||||
pack()
|
||||
}
|
||||
|
||||
@ -345,6 +349,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
cityScreen.selectConstruction(constructionName)
|
||||
selectedQueueEntry = constructionQueueIndex
|
||||
cityScreen.update()
|
||||
ensureQueueEntryVisible()
|
||||
}
|
||||
return table
|
||||
}
|
||||
@ -464,7 +469,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
|
||||
if (isSelectedQueueEntry()) {
|
||||
button = "Remove from queue".toTextButton()
|
||||
button.onClick {
|
||||
button.onActivation(binding = KeyboardBinding.AddConstruction) {
|
||||
cityConstructions.removeFromQueue(selectedQueueEntry, false)
|
||||
cityScreen.clearSelection()
|
||||
selectedQueueEntry = -1
|
||||
@ -476,7 +481,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
|| cannotAddConstructionToQueue(construction, city, cityConstructions)) {
|
||||
button.disable()
|
||||
} else {
|
||||
button.onClick(UncivSound.Silent) {
|
||||
button.onActivation(binding = KeyboardBinding.AddConstruction, sound = UncivSound.Silent) {
|
||||
addConstructionToQueue(construction, cityConstructions)
|
||||
}
|
||||
}
|
||||
@ -542,13 +547,11 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
val constructionBuyCost = construction.getStatBuyCost(city, stat)!!
|
||||
button.setText("Buy".tr() + " " + constructionBuyCost + stat.character)
|
||||
|
||||
button.onActivation {
|
||||
button.onActivation(binding = KeyboardBinding.BuyConstruction) {
|
||||
button.disable()
|
||||
buyButtonOnClick(construction, stat)
|
||||
}
|
||||
button.isEnabled = isConstructionPurchaseAllowed(construction, stat, constructionBuyCost)
|
||||
button.keyShortcuts.add('B')
|
||||
button.addTooltip('B') // The key binding is done in CityScreen constructor
|
||||
preferredBuyStat = stat // Not very intelligent, but the least common currency "wins"
|
||||
}
|
||||
|
||||
@ -651,34 +654,40 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
cityScreen.update()
|
||||
}
|
||||
|
||||
private fun getRaisePriorityButton(constructionQueueIndex: Int, name: String, city: City): Table {
|
||||
val tab = Table()
|
||||
tab.add(ImageGetter.getArrowImage(Align.top).apply { color = Color.BLACK }.surroundWithCircle(40f))
|
||||
tab.touchable = Touchable.enabled
|
||||
tab.onClick {
|
||||
tab.touchable = Touchable.disabled
|
||||
city.cityConstructions.raisePriority(constructionQueueIndex)
|
||||
private fun getMovePriorityButton(
|
||||
arrowDirection: Int,
|
||||
binding: KeyboardBinding,
|
||||
constructionQueueIndex: Int,
|
||||
name: String,
|
||||
movePriority: (Int) -> Int
|
||||
): Table {
|
||||
val button = Table()
|
||||
button.add(ImageGetter.getArrowImage(arrowDirection).apply { color = Color.BLACK }.surroundWithCircle(40f))
|
||||
button.touchable = Touchable.enabled
|
||||
// Don't bind the queue reordering keys here - those should affect only the selected entry, not all of them
|
||||
button.onActivation {
|
||||
button.touchable = Touchable.disabled
|
||||
selectedQueueEntry = movePriority(constructionQueueIndex)
|
||||
// No need to call entire cityScreen.update() as reordering doesn't influence Stat or Map,
|
||||
// nor does it need an expensive rebuild of the available constructions.
|
||||
// Selection display may need to update as I can click the button of a non-selected entry.
|
||||
cityScreen.selectConstruction(name)
|
||||
selectedQueueEntry = constructionQueueIndex - 1
|
||||
cityScreen.update()
|
||||
cityScreen.updateWithoutConstructionAndMap()
|
||||
updateQueueAndButtons(cityScreen.selectedConstruction)
|
||||
ensureQueueEntryVisible() // Not passing current button info - already outdated, our parent is already removed from the stage hierarchy and replaced
|
||||
}
|
||||
return tab
|
||||
if (selectedQueueEntry == constructionQueueIndex) {
|
||||
button.keyShortcuts.add(binding) // This binds without automatic tooltip
|
||||
button.addTooltip(binding)
|
||||
}
|
||||
return button
|
||||
}
|
||||
|
||||
private fun getLowerPriorityButton(constructionQueueIndex: Int, name: String, city: City): Table {
|
||||
val tab = Table()
|
||||
tab.add(ImageGetter.getArrowImage(Align.bottom).apply { color = Color.BLACK }.surroundWithCircle(40f))
|
||||
tab.touchable = Touchable.enabled
|
||||
tab.onClick {
|
||||
tab.touchable = Touchable.disabled
|
||||
city.cityConstructions.lowerPriority(constructionQueueIndex)
|
||||
cityScreen.selectConstruction(name)
|
||||
selectedQueueEntry = constructionQueueIndex + 1
|
||||
cityScreen.update()
|
||||
}
|
||||
private fun getRaisePriorityButton(constructionQueueIndex: Int, name: String, city: City) =
|
||||
getMovePriorityButton(Align.top, KeyboardBinding.RaisePriority, constructionQueueIndex, name, city.cityConstructions::raisePriority)
|
||||
|
||||
return tab
|
||||
}
|
||||
private fun getLowerPriorityButton(constructionQueueIndex: Int, name: String, city: City) =
|
||||
getMovePriorityButton(Align.bottom, KeyboardBinding.LowerPriority, constructionQueueIndex, name, city.cityConstructions::lowerPriority)
|
||||
|
||||
private fun getRemoveFromQueueButton(constructionQueueIndex: Int, city: City): Table {
|
||||
val tab = Table()
|
||||
@ -705,12 +714,24 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
.pad(4f)
|
||||
}
|
||||
|
||||
private fun ensureQueueEntryVisible() {
|
||||
// Ensure the selected queue entry stays visible, and if moved to the "current" top slot, that the header is visible too
|
||||
// This uses knowledge about how we build constructionsQueueTable without re-evaluating that stuff:
|
||||
// Every odd row is a separator, cells have no padding, and there's one header on top and another between selectedQueueEntries 0 and 1
|
||||
val button = constructionsQueueTable.cells[if (selectedQueueEntry == 0) 2 else 2 * selectedQueueEntry + 4].actor
|
||||
val buttonOrHeader = if (selectedQueueEntry == 0) constructionsQueueTable.cells[0].actor else button
|
||||
// The 4f includes the two separators on top/bottom of the entry/header (the y offset we'd need cancels out with constructionsQueueTable.y being 2f as well):
|
||||
val height = buttonOrHeader.y + buttonOrHeader.height - button.y + 4f
|
||||
// Alternatively, scrollTo(..., true, true) would keep the selection as centered as possible:
|
||||
constructionsQueueScrollPane.scrollTo(2f, button.y, button.width, height)
|
||||
}
|
||||
|
||||
private fun resizeAvailableConstructionsScrollPane() {
|
||||
availableConstructionsScrollPane.height = min(availableConstructionsTable.prefHeight, lowerTableScrollCell.maxHeight)
|
||||
lowerTable.pack()
|
||||
}
|
||||
|
||||
private fun Table.addCategory(title: String, list: ArrayList<Table>, prefWidth: Float) {
|
||||
private fun Table.addCategory(title: String, list: ArrayList<Table>, prefWidth: Float, toggleKey: KeyboardBinding) {
|
||||
if (list.isEmpty()) return
|
||||
|
||||
if (rows > 0) addSeparator()
|
||||
@ -719,6 +740,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
defaultPad = 0f,
|
||||
expanderWidth = prefWidth,
|
||||
persistenceID = "CityConstruction.$title",
|
||||
toggleKey = toggleKey,
|
||||
onChange = { resizeAvailableConstructionsScrollPane() }
|
||||
) {
|
||||
for (table in list) {
|
||||
|
@ -19,6 +19,7 @@ import com.unciv.ui.components.extensions.addSeparator
|
||||
import com.unciv.ui.components.extensions.addSeparatorVertical
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
|
||||
class CityReligionInfoTable(
|
||||
private val religionManager: CityReligionManager,
|
||||
@ -103,6 +104,7 @@ class CityReligionInfoTable(
|
||||
defaultPad = 0f,
|
||||
persistenceID = "CityStatsTable.Religion",
|
||||
startsOutOpened = false,
|
||||
toggleKey = KeyboardBinding.ReligionDetail,
|
||||
onChange = onChange
|
||||
) {
|
||||
defaults().center().pad(5f)
|
||||
|
@ -27,6 +27,7 @@ import com.unciv.ui.components.extensions.packIfNeeded
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.components.input.KeyCharAndCode
|
||||
import com.unciv.ui.components.input.KeyShortcutDispatcherVeto
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.keyShortcuts
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.components.input.onClick
|
||||
@ -139,8 +140,8 @@ class CityScreen(
|
||||
stage.addActor(exitCityButton)
|
||||
update()
|
||||
|
||||
globalShortcuts.add(Input.Keys.LEFT) { page(-1) }
|
||||
globalShortcuts.add(Input.Keys.RIGHT) { page(1) }
|
||||
globalShortcuts.add(KeyboardBinding.PreviousCity) { page(-1) }
|
||||
globalShortcuts.add(KeyboardBinding.NextCity) { page(1) }
|
||||
}
|
||||
|
||||
internal fun update() {
|
||||
@ -150,6 +151,19 @@ class CityScreen(
|
||||
constructionsTable.isVisible = true
|
||||
constructionsTable.update(selectedConstruction)
|
||||
|
||||
updateWithoutConstructionAndMap()
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun updateWithoutConstructionAndMap() {
|
||||
// Bottom right: Tile or selected construction info
|
||||
tileTable.update(selectedTile)
|
||||
tileTable.setPosition(stage.width - posFromEdge, posFromEdge, Align.bottomRight)
|
||||
@ -185,15 +199,6 @@ class CityScreen(
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
fun canCityBeChanged(): Boolean {
|
||||
|
@ -16,6 +16,7 @@ import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||
@ -57,13 +58,11 @@ class CityScreenTileTable(private val cityScreen: CityScreen): Table() {
|
||||
if (city.expansion.canBuyTile(selectedTile)) {
|
||||
val goldCostOfTile = city.expansion.getGoldCostOfTile(selectedTile)
|
||||
val buyTileButton = "Buy for [$goldCostOfTile] gold".toTextButton()
|
||||
buyTileButton.onActivation {
|
||||
buyTileButton.onActivation(binding = KeyboardBinding.BuyTile) {
|
||||
buyTileButton.disable()
|
||||
cityScreen.askToBuyTile(selectedTile)
|
||||
}
|
||||
buyTileButton.keyShortcuts.add('T')
|
||||
buyTileButton.isEnabled = cityScreen.canChangeState && city.civ.hasStatToBuy(Stat.Gold, goldCostOfTile)
|
||||
buyTileButton.addTooltip('T') // The key binding is done in CityScreen constructor
|
||||
innerTable.add(buyTileButton).padTop(5f).row()
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ import com.unciv.ui.components.extensions.surroundWithCircle
|
||||
import com.unciv.ui.components.extensions.toGroup
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
@ -45,7 +46,7 @@ class CityStatsTable(private val cityScreen: CityScreen): Table() {
|
||||
|
||||
private val detailedStatsButton = "Stats".toTextButton().apply {
|
||||
labelCell.pad(10f)
|
||||
onActivation {
|
||||
onActivation(binding = KeyboardBinding.ShowStats) {
|
||||
DetailedStatsPopup(cityScreen).open()
|
||||
}
|
||||
}
|
||||
@ -83,21 +84,19 @@ class CityStatsTable(private val cityScreen: CityScreen): Table() {
|
||||
for ((stat, amount) in city.cityStats.currentCityStats) {
|
||||
if (stat == Stat.Faith && !city.civ.gameInfo.isReligionEnabled()) continue
|
||||
val icon = Table()
|
||||
if (city.cityAIFocus.stat == stat) {
|
||||
val focus = CityFocus.safeValueOf(stat)
|
||||
val toggledFocus = if (focus == city.cityAIFocus) {
|
||||
icon.add(ImageGetter.getStatIcon(stat.name).surroundWithCircle(27f, false, color = selected))
|
||||
if (cityScreen.canCityBeChanged()) {
|
||||
icon.onClick {
|
||||
city.cityAIFocus = CityFocus.NoFocus
|
||||
city.reassignPopulation(); cityScreen.update()
|
||||
}
|
||||
}
|
||||
CityFocus.NoFocus
|
||||
} else {
|
||||
icon.add(ImageGetter.getStatIcon(stat.name).surroundWithCircle(27f, false, color = Color.CLEAR))
|
||||
if (cityScreen.canCityBeChanged()) {
|
||||
icon.onClick {
|
||||
city.cityAIFocus = city.cityAIFocus.safeValueOf(stat)
|
||||
city.reassignPopulation(); cityScreen.update()
|
||||
}
|
||||
focus
|
||||
}
|
||||
if (cityScreen.canCityBeChanged()) {
|
||||
icon.onActivation(binding = toggledFocus.binding) {
|
||||
city.cityAIFocus = toggledFocus
|
||||
city.reassignPopulation()
|
||||
cityScreen.update()
|
||||
}
|
||||
}
|
||||
miniStatsTable.add(icon).size(27f).padRight(3f)
|
||||
@ -247,7 +246,7 @@ class CityStatsTable(private val cityScreen: CityScreen): Table() {
|
||||
otherBuildings.sortBy { it.name }
|
||||
|
||||
val totalTable = Table()
|
||||
lowerTable.addCategory("Buildings", totalTable, false)
|
||||
lowerTable.addCategory("Buildings", totalTable, KeyboardBinding.BuildingsDetail, false)
|
||||
|
||||
if (specialistBuildings.isNotEmpty()) {
|
||||
val specialistBuildingsTable = Table()
|
||||
@ -327,13 +326,18 @@ class CityStatsTable(private val cityScreen: CityScreen): Table() {
|
||||
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,
|
||||
toggleKey: KeyboardBinding,
|
||||
startsOpened: Boolean = true
|
||||
) : ExpanderTab {
|
||||
val expanderTab = ExpanderTab(
|
||||
title = category,
|
||||
fontSize = Constants.defaultFontSize,
|
||||
persistenceID = "CityInfo.$category",
|
||||
startsOutOpened = startsOpened,
|
||||
defaultPad = innerPadding,
|
||||
toggleKey = toggleKey,
|
||||
onChange = { onContentResize() }
|
||||
) {
|
||||
it.add(showHideTable).fillX().right()
|
||||
@ -392,7 +396,7 @@ class CityStatsTable(private val cityScreen: CityScreen): Table() {
|
||||
greatPeopleTable.add(ImageGetter.getConstructionPortrait(greatPersonName, 50f)).row()
|
||||
}
|
||||
|
||||
lowerTable.addCategory("Great People", greatPeopleTable)
|
||||
lowerTable.addCategory("Great People", greatPeopleTable, KeyboardBinding.GreatPeopleDetail)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import com.unciv.ui.components.extensions.packIfNeeded
|
||||
import com.unciv.ui.components.extensions.pad
|
||||
import com.unciv.ui.components.extensions.surroundWithCircle
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.images.IconCircleGroup
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
@ -176,14 +177,11 @@ class DetailedStatsPopup(
|
||||
val button = label
|
||||
.surroundWithCircle(25f, color = BaseScreen.skinStrings.skinConfig.baseColor)
|
||||
.surroundWithCircle(27f, false)
|
||||
button.keyShortcuts.run {
|
||||
add(Input.Keys.PLUS)
|
||||
add(Input.Keys.NUMPAD_ADD)
|
||||
}
|
||||
button.onActivation {
|
||||
button.onActivation(binding = KeyboardBinding.ShowStatDetails) {
|
||||
isDetailed = !isDetailed
|
||||
update()
|
||||
}
|
||||
button.keyShortcuts.add(Input.Keys.PLUS) //todo Choose alternative (alt binding, remove, auto-equivalence, multikey bindings)
|
||||
return button
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import com.unciv.ui.components.extensions.darken
|
||||
import com.unciv.ui.components.extensions.surroundWithCircle
|
||||
import com.unciv.ui.components.extensions.toGroup
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
@ -141,6 +142,7 @@ class SpecialistAllocationTable(private val cityScreen: CityScreen) : Table(Base
|
||||
fontSize = Constants.defaultFontSize,
|
||||
persistenceID = "CityStatsTable.Specialists",
|
||||
startsOutOpened = true,
|
||||
toggleKey = KeyboardBinding.SpecialistDetail,
|
||||
onChange = onChange
|
||||
) {
|
||||
it.add(this)
|
||||
|
@ -52,13 +52,12 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
|
||||
if (unitAction.type == UnitActionType.Promote && unitAction.action != null)
|
||||
actionButton.color = Color.GREEN.cpy().lerp(Color.WHITE, 0.5f)
|
||||
|
||||
actionButton.addTooltip(binding)
|
||||
actionButton.pack()
|
||||
|
||||
if (unitAction.action == null) {
|
||||
actionButton.disable()
|
||||
} else {
|
||||
actionButton.onActivation(unitAction.uncivSound) {
|
||||
actionButton.onActivation(unitAction.uncivSound, binding) {
|
||||
unitAction.action.invoke()
|
||||
GUI.setUpdateWorldOnNextRender()
|
||||
// We keep the unit action/selection overlay from the previous unit open even when already selecting another unit
|
||||
@ -70,7 +69,6 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
|
||||
worldScreen.switchToNextUnit()
|
||||
}
|
||||
}
|
||||
actionButton.keyShortcuts.add(binding)
|
||||
}
|
||||
|
||||
return actionButton
|
||||
|
Loading…
Reference in New Issue
Block a user