Civilopedia key bindings (#10723)

* Move CivilopediaImageGetters object to own file

* Keyboard bindings for Civilopedia

* Better default key choices

* Change default Key Binding Tab visibility to ON
This commit is contained in:
SomeTroglodyte
2023-12-13 21:36:29 +01:00
committed by GitHub
parent aba77b8350
commit a24f9d7716
6 changed files with 156 additions and 163 deletions

View File

@ -435,7 +435,6 @@
{},
{"text":"For discussion about missing entries, see the linked github issue.","link":"https://github.com/yairm210/Unciv/issues/8862"}
]
// "uniques": ["Will not be displayed in Civilopedia"] // would prevent use for help link
},
{
"name": "Civilopedia",

View File

@ -165,10 +165,29 @@ enum class KeyboardBinding(
AddConstructionAllTop(Category.CityScreenConstructionMenu, "Add or move to the top in all cities", KeyCharAndCode.ctrl('t')),
RemoveConstructionAll(Category.CityScreenConstructionMenu, "Remove from the queue in all cities", KeyCharAndCode.ctrl('r')),
// Civilopedia
PediaBuildings(Category.Civilopedia, "Buildings", 'b'),
PediaWonders(Category.Civilopedia, "Wonders", 'w'),
PediaResources(Category.Civilopedia, "Resources", 'r'),
PediaTerrains(Category.Civilopedia, "Terrains", 't'),
PediaImprovements(Category.Civilopedia, "Tile Improvements", 'i'),
PediaUnits(Category.Civilopedia, "Units", 'u'),
PediaUnitTypes(Category.Civilopedia, "Unit types", 'y'),
PediaNations(Category.Civilopedia, "Nations", 'n'),
PediaTechnologies(Category.Civilopedia, "Technologies", KeyCharAndCode.ctrl('t')),
PediaPromotions(Category.Civilopedia, "Promotions", 'p'),
PediaPolicies(Category.Civilopedia, "Policies", 'o'),
PediaBeliefs(Category.Civilopedia, "Religions and Beliefs", 'f'),
PediaTutorials(Category.Civilopedia, "Tutorials", Input.Keys.F1),
PediaDifficulties(Category.Civilopedia, "Difficulty levels", 'd'),
PediaEras(Category.Civilopedia, "Eras", 'e'),
PediaSpeeds(Category.Civilopedia, "Speeds", 's'),
PediaSearch(Category.Civilopedia, "Open the Search Dialog", KeyCharAndCode.ctrl('f')),
// Popups
Confirm(Category.Popups, "Confirm Dialog", 'y'),
Cancel(Category.Popups, "Cancel Dialog", 'n'),
UpgradeAll(Category.Popups, KeyCharAndCode.ctrl('a')),
UpgradeAll(Category.Popups, KeyCharAndCode.ctrl('a')), // rethink? No UnitUpgradeMenu category, but CityScreenConstructionMenu gets one?
;
//endregion
@ -188,6 +207,7 @@ enum class KeyboardBinding(
},
CityScreen,
CityScreenConstructionMenu, // Maybe someday a category hierarchy?
Civilopedia,
Popups
;
val label = unCamelCase(name)

View File

@ -43,7 +43,7 @@ class OptionsPopup(
private var keyBindingsTab: KeyBindingsTab? = null
/** Enable the still experimental Keyboard Bindings page in OptionsPopup */
var enableKeyBindingsTab: Boolean = false
var enableKeyBindingsTab: Boolean = true
//endregion
@ -209,7 +209,8 @@ class OptionsPopup(
internal fun showOrHideKeyBindings() {
// At the moment, the Key bindings Tab exists only on-demand. To refactor it back to permanent,
// move the `keyBindingsTab =` line and addPage call to before the Advanced Tab creation,
// then delete this function, delete the enableKeyBindingsTab flag and clean up what is flagged by the compiler as missing or unused.
// then delete this function, delete the enableKeyBindingsTab flag and clean up what is flagged
// by the compiler as missing or unused - like the `add("Show keyboard bindings".toCheckBox` option on DebugTab.
val existingIndex = tabs.getPageIndex(keysTabCaption)
if (enableKeyBindingsTab && existingIndex < 0) {
if (keyBindingsTab == null)

View File

@ -1,111 +1,12 @@
package com.unciv.ui.screens.civilopediascreen
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Container
import com.unciv.Constants
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.unit.UnitMovementType
import com.unciv.ui.components.extensions.setSize
import com.unciv.ui.components.extensions.surroundWithCircle
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.tilegroups.TileGroup
import com.unciv.ui.components.tilegroups.TileSetStrings
import com.unciv.ui.images.IconCircleGroup
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.components.input.KeyboardBinding
import com.unciv.ui.screens.basescreen.TutorialController
import com.unciv.models.ruleset.Belief as BaseBelief
import com.unciv.models.ruleset.unit.UnitType as BaseUnitType
/** Encapsulates the knowledge on how to get an icon for each of the Civilopedia categories */
object CivilopediaImageGetters {
private const val policyIconFolder = "PolicyIcons"
private const val policyBranchIconFolder = "PolicyBranchIcons"
private const val policyInnerSize = 0.25f
// Todo: potential synergy with map editor
fun terrainImage(terrain: Terrain, ruleset: Ruleset, imageSize: Float): Actor {
val tile = Tile()
tile.ruleset = ruleset
when (terrain.type) {
TerrainType.NaturalWonder -> {
tile.naturalWonder = terrain.name
tile.baseTerrain = terrain.turnsInto ?: terrain.occursOn.firstOrNull() ?: Constants.grassland
}
TerrainType.TerrainFeature -> {
tile.baseTerrain =
if (terrain.occursOn.isEmpty() || terrain.occursOn.contains(Constants.grassland))
Constants.grassland
else
terrain.occursOn.lastOrNull()!!
tile.setTerrainTransients()
tile.addTerrainFeature(terrain.name)
}
else ->
tile.baseTerrain = terrain.name
}
tile.setTerrainTransients()
val group = TileGroup(tile, TileSetStrings(), imageSize * 36f/54f) // TileGroup normally spills out of its bounding box
group.isForceVisible = true
group.isForMapEditorIcon = true
group.update()
return Container(group)
}
val construction = { name: String, size: Float ->
ImageGetter.getConstructionPortrait(name, size)
}
val improvement = { name: String, size: Float ->
ImageGetter.getImprovementPortrait(name, size)
}
val nation = { name: String, size: Float ->
val nation = ImageGetter.ruleset.nations[name]
if (nation == null) null
else ImageGetter.getNationPortrait(nation, size)
}
val policy = fun(name: String, size: Float): IconCircleGroup? {
// result is nullable: policy branch complete have no icons but are linked -> nonexistence must be passed down
fun tryImage(path: String, color: Color): IconCircleGroup? {
if (ImageGetter.imageExists(path)) return ImageGetter.getImage(path).apply {
setSize(size * policyInnerSize,size * policyInnerSize)
this.color = color
}.surroundWithCircle(size)
return null
}
return tryImage("$policyBranchIconFolder/$name", Color.BLACK)
?: tryImage("$policyIconFolder/$name", Color.BROWN)
}
val resource = { name: String, size: Float ->
ImageGetter.getResourcePortrait(name, size)
}
val technology = { name: String, size: Float ->
ImageGetter.getTechIconPortrait(name, size)
}
val promotion = { name: String, size: Float ->
ImageGetter.getPromotionPortrait(name, size)
}
val terrain = { name: String, size: Float ->
val terrain = ImageGetter.ruleset.terrains[name]
if (terrain == null) null
else terrainImage(terrain, ImageGetter.ruleset, size)
}
val belief = { name: String, size: Float ->
ImageGetter.getReligionPortrait(name, size)
}
val unitType = { name: String, size: Float ->
val path = UnitMovementType.values().firstOrNull { "Domain: [${it.name}]" == name }
?.let {"UnitTypeIcons/Domain${it.name}" }
?: "UnitTypeIcons/$name"
if (ImageGetter.imageExists(path)) ImageGetter.getImage(path).apply { setSize(size) }
else null
}
}
/** Enum used as keys for Civilopedia "pages" (categories).
*
* Note names are singular on purpose - a "link" allows both key and label
@ -117,121 +18,110 @@ enum class CivilopediaCategories (
val label: String,
val hide: Boolean, // Omitted on CivilopediaScreen
val getImage: ((name: String, size: Float) -> Actor?)?,
val key: KeyCharAndCode = KeyCharAndCode.UNKNOWN,
val binding: KeyboardBinding,
val headerIcon: String,
val getCategoryIterator: (ruleset: Ruleset, tutorialController: TutorialController) -> Collection<ICivilopediaText>
) {
Building ("Buildings", false,
CivilopediaImageGetters.construction,
KeyCharAndCode('B'),
KeyboardBinding.PediaBuildings,
"OtherIcons/Cities",
{ ruleset, _ -> ruleset.buildings.values.filter { !it.isAnyWonder() } }
),
Wonder ("Wonders", false,
CivilopediaImageGetters.construction,
KeyCharAndCode('W'),
KeyboardBinding.PediaWonders,
"OtherIcons/Wonders",
{ ruleset, _ -> ruleset.buildings.values.filter { it.isAnyWonder() } }
),
Resource ("Resources", false,
CivilopediaImageGetters.resource,
KeyCharAndCode('R'),
KeyboardBinding.PediaResources,
"OtherIcons/Resources",
{ ruleset, _ -> ruleset.tileResources.values }
),
Terrain ("Terrains", false,
CivilopediaImageGetters.terrain,
KeyCharAndCode('T'),
KeyboardBinding.PediaTerrains,
"OtherIcons/Terrains",
{ ruleset, _ -> ruleset.terrains.values }
),
Improvement ("Tile Improvements", false,
CivilopediaImageGetters.improvement,
KeyCharAndCode('T'),
KeyboardBinding.PediaImprovements,
"OtherIcons/Improvements",
{ ruleset, _ -> ruleset.tileImprovements.values }
),
Unit ("Units", false,
CivilopediaImageGetters.construction,
KeyCharAndCode('U'),
KeyboardBinding.PediaUnits,
"OtherIcons/Shield",
{ ruleset, _ -> ruleset.units.values }
),
UnitType ("Unit types", false,
CivilopediaImageGetters.unitType,
KeyCharAndCode('U'),
KeyboardBinding.PediaUnitTypes,
"UnitTypeIcons/UnitTypes",
{ ruleset, _ -> BaseUnitType.getCivilopediaIterator(ruleset) }
),
Nation ("Nations", false,
CivilopediaImageGetters.nation,
KeyCharAndCode('N'),
KeyboardBinding.PediaNations,
"OtherIcons/Nations",
{ ruleset, _ -> ruleset.nations.values.filter { !it.isSpectator } }
),
Technology ("Technologies", false,
CivilopediaImageGetters.technology,
KeyCharAndCode('T'),
KeyboardBinding.PediaTechnologies,
"TechIcons/Philosophy",
{ ruleset, _ -> ruleset.technologies.values }
),
Promotion ("Promotions", false,
CivilopediaImageGetters.promotion,
KeyCharAndCode('P'),
KeyboardBinding.PediaPromotions,
"UnitPromotionIcons/Mobility",
{ ruleset, _ -> ruleset.unitPromotions.values }
),
Policy ("Policies", false,
CivilopediaImageGetters.policy,
KeyCharAndCode('P'),
KeyboardBinding.PediaPolicies,
"PolicyIcons/Constitution",
{ ruleset, _ -> ruleset.policies.values }
),
Belief("Religions and Beliefs", false,
Belief("Religions and Beliefs",
hide = false, // Skipping this page when showReligionInCivilopedia returns false is hardcoded
CivilopediaImageGetters.belief,
KeyCharAndCode('R'),
KeyboardBinding.PediaBeliefs,
"ReligionIcons/Religion",
{ ruleset, _ -> (ruleset.beliefs.values.asSequence() +
BaseBelief.getCivilopediaReligionEntry(ruleset)).toList() }
),
Tutorial ("Tutorials", false,
getImage = null,
KeyCharAndCode(Input.Keys.F1),
KeyboardBinding.PediaTutorials,
"OtherIcons/ExclamationMark",
{ _, tutorialController -> tutorialController.getCivilopediaTutorials() }
),
Difficulty ("Difficulty levels", false,
getImage = null,
KeyCharAndCode('D'),
KeyboardBinding.PediaDifficulties,
"OtherIcons/Quickstart",
{ ruleset, _ -> ruleset.difficulties.values }
),
Era ("Eras", false,
getImage = null,
KeyCharAndCode('D'),
KeyboardBinding.PediaEras,
"OtherIcons/Tyrannosaurus",
{ ruleset, _ -> ruleset.eras.values }
),
Speed ("Speeds", false,
getImage = null,
KeyCharAndCode('S'),
KeyboardBinding.PediaSpeeds,
"OtherIcons/Timer",
{ ruleset, _ -> ruleset.speeds.values }
);
private fun getByOffset(offset: Int) = values()[(ordinal + count + offset) % count]
fun nextForKey(key: KeyCharAndCode): CivilopediaCategories {
for (i in 1..count) {
val next = getByOffset(i)
if (next.key == key) return next
}
return this
}
companion object {
private val count = values().size
fun fromLink(name: String): CivilopediaCategories? =
values().firstOrNull { it.name == name }
?: values().firstOrNull { it.label == name }

View File

@ -0,0 +1,101 @@
package com.unciv.ui.screens.civilopediascreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Container
import com.unciv.Constants
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.unit.UnitMovementType
import com.unciv.ui.components.extensions.setSize
import com.unciv.ui.components.extensions.surroundWithCircle
import com.unciv.ui.components.tilegroups.TileGroup
import com.unciv.ui.components.tilegroups.TileSetStrings
import com.unciv.ui.images.IconCircleGroup
import com.unciv.ui.images.ImageGetter
/** Encapsulates the knowledge on how to get an icon for each of the Civilopedia categories */
internal object CivilopediaImageGetters {
private const val policyIconFolder = "PolicyIcons"
private const val policyBranchIconFolder = "PolicyBranchIcons"
private const val policyInnerSize = 0.25f
// Todo: potential synergy with map editor
private fun terrainImage(terrain: Terrain, ruleset: Ruleset, imageSize: Float): Actor {
val tile = Tile()
tile.ruleset = ruleset
when (terrain.type) {
TerrainType.NaturalWonder -> {
tile.naturalWonder = terrain.name
tile.baseTerrain = terrain.turnsInto ?: terrain.occursOn.firstOrNull() ?: Constants.grassland
}
TerrainType.TerrainFeature -> {
tile.baseTerrain =
if (terrain.occursOn.isEmpty() || terrain.occursOn.contains(Constants.grassland))
Constants.grassland
else
terrain.occursOn.lastOrNull()!!
tile.setTerrainTransients()
tile.addTerrainFeature(terrain.name)
}
else ->
tile.baseTerrain = terrain.name
}
tile.setTerrainTransients()
val group = TileGroup(tile, TileSetStrings(), imageSize * 36f / 54f) // TileGroup normally spills out of its bounding box
group.isForceVisible = true
group.isForMapEditorIcon = true
group.update()
return Container(group)
}
val construction = { name: String, size: Float ->
ImageGetter.getConstructionPortrait(name, size)
}
val improvement = { name: String, size: Float ->
ImageGetter.getImprovementPortrait(name, size)
}
val nation = { name: String, size: Float ->
val nation = ImageGetter.ruleset.nations[name]
if (nation == null) null
else ImageGetter.getNationPortrait(nation, size)
}
val policy = fun(name: String, size: Float): IconCircleGroup? {
// result is nullable: policy branch complete have no icons but are linked -> nonexistence must be passed down
fun tryImage(path: String, color: Color): IconCircleGroup? {
if (ImageGetter.imageExists(path)) return ImageGetter.getImage(path).apply {
setSize(size * policyInnerSize,size * policyInnerSize)
this.color = color
}.surroundWithCircle(size)
return null
}
return tryImage("$policyBranchIconFolder/$name", Color.BLACK)
?: tryImage("$policyIconFolder/$name", Color.BROWN)
}
val resource = { name: String, size: Float ->
ImageGetter.getResourcePortrait(name, size)
}
val technology = { name: String, size: Float ->
ImageGetter.getTechIconPortrait(name, size)
}
val promotion = { name: String, size: Float ->
ImageGetter.getPromotionPortrait(name, size)
}
val terrain = { name: String, size: Float ->
val terrain = ImageGetter.ruleset.terrains[name]
if (terrain == null) null
else terrainImage(terrain, ImageGetter.ruleset, size)
}
val belief = { name: String, size: Float ->
ImageGetter.getReligionPortrait(name, size)
}
val unitType = { name: String, size: Float ->
val path = UnitMovementType.values().firstOrNull { "Domain: [${it.name}]" == name }
?.let {"UnitTypeIcons/Domain${it.name}" }
?: "UnitTypeIcons/$name"
if (ImageGetter.imageExists(path)) ImageGetter.getImage(path).apply { setSize(size) }
else null
}
}

View File

@ -14,7 +14,6 @@ import com.unciv.models.ruleset.unique.IHasUniques
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.INamed
import com.unciv.models.translations.tr
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
import com.unciv.ui.components.extensions.colorFromRGB
import com.unciv.ui.components.extensions.toImageButton
import com.unciv.ui.components.extensions.toLabel
@ -236,8 +235,7 @@ class CivilopediaScreen(
if (entries.isEmpty()) continue
val icon = if (categoryKey.headerIcon.isNotEmpty()) ImageGetter.getImage(categoryKey.headerIcon) else null
val button = IconTextButton(categoryKey.label, icon)
button.addTooltip(categoryKey.key)
button.onClick { selectCategory(categoryKey) }
button.onActivation(binding = categoryKey.binding) { selectCategory(categoryKey) }
val cell = buttonTable.add(button)
categoryToButtons[categoryKey] = CategoryButtonInfo(button, currentX, cell.prefWidth)
currentX += cell.prefWidth + 20f
@ -245,11 +243,10 @@ class CivilopediaScreen(
buttonTable.pack()
buttonTableScroll = ScrollPane(buttonTable)
buttonTableScroll.setScrollingDisabled(false, true)
buttonTableScroll.setScrollingDisabled(x = false, y = true)
val searchButton = "OtherIcons/Search".toImageButton(imageSize - 16f, imageSize, skinStrings.skinConfig.baseColor, Color.GOLD)
searchButton.onActivation { searchPopup.open(true) }
searchButton.keyShortcuts.add(KeyboardBinding.Civilopedia) // "hit twice to search"
searchButton.onActivation(binding = KeyboardBinding.PediaSearch) { searchPopup.open(true) }
val closeButton = "OtherIcons/Close".toImageButton(imageSize - 20f, imageSize, skinStrings.skinConfig.baseColor, Color.RED)
closeButton.onActivation { game.popScreen() }
@ -292,26 +289,8 @@ class CivilopediaScreen(
else
selectEntry(link, noScrollAnimation = true)
for (categoryKey in categoryToEntries.keys) {
globalShortcuts.add(categoryKey.key) { navigateCategories(categoryKey.key) }
}
globalShortcuts.add(Input.Keys.LEFT) {
val categoryKey = categoryToEntries.keys
val currentIndex = categoryKey.indexOf(currentCategory)
val targetCategory = categoryKey.elementAt(
(currentIndex + categoryKey.size - 1) % categoryKey.size
)
selectCategory(targetCategory)
}
globalShortcuts.add(Input.Keys.RIGHT) {
val categoryKey = categoryToEntries.keys
val currentIndex = categoryKey.indexOf(currentCategory)
val targetCategory = categoryKey.elementAt(
(currentIndex + categoryKey.size + 1) % categoryKey.size
)
selectCategory(targetCategory)
}
globalShortcuts.add(Input.Keys.LEFT) { navigateCategories(-1) }
globalShortcuts.add(Input.Keys.RIGHT) { navigateCategories(1) }
globalShortcuts.add(Input.Keys.UP) { navigateEntries(-1) }
globalShortcuts.add(Input.Keys.DOWN) { navigateEntries(1) }
globalShortcuts.add(Input.Keys.PAGE_UP) { navigateEntries(-10) }
@ -320,8 +299,11 @@ class CivilopediaScreen(
globalShortcuts.add(Input.Keys.END) { navigateEntries(Int.MAX_VALUE) }
}
private fun navigateCategories(key: KeyCharAndCode) {
selectCategory(currentCategory.nextForKey(key))
private fun navigateCategories(direction: Int) {
val categoryKeys = categoryToEntries.keys
val currentIndex = categoryKeys.indexOf(currentCategory)
val newIndex = (currentIndex + categoryKeys.size + direction) % categoryKeys.size
selectCategory(categoryKeys.elementAt(newIndex))
}
private fun navigateEntries(direction: Int) {