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:
SomeTroglodyte 2023-07-30 16:40:05 +02:00 committed by GitHub
parent a5e1f6d800
commit 52e756e9fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 239 additions and 100 deletions

View File

@ -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)

View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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) {

View File

@ -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)

View File

@ -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 {

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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