diff --git a/android/assets/jsons/translations/German.properties b/android/assets/jsons/translations/German.properties index 5a33c2bd64..b83fc3b4ff 100644 --- a/android/assets/jsons/translations/German.properties +++ b/android/assets/jsons/translations/German.properties @@ -816,6 +816,7 @@ Enable display cutout (requires restart) = Aktiviere Bildschirmausschnitt (Neust ## Keys tab Keys = Tastenzuordnung Please see the Tutorial. = Bitte schau dir das Tutorial an. +Hit the desired key now = Drücke die gewünschte Taste ## Locate mod errors tab Locate mod errors = Mod-Probleme @@ -2411,9 +2412,34 @@ You returned captured units to us = Ihr habt uns gefangene Einheiten zurückgege #################### Lines from key bindings ####################### +Unit Actions = Einheiten-Aktionen +Popups = Dialoge Next Turn = Nächste Runde Next Turn Alternate = Nächste Runde Alternativ Empire Overview = Reichsübersicht +Empire Overview Trades = Handels-Übersicht +Empire Overview Units = Einheiten-Übersicht +Empire Overview Politics = Politiken-Übersicht +Social Policies = Sozialpolitiken +Technology Tree = Technologie-Baum +Empire Overview Notifications = Benachrichtigungen Historie +Empire Overview Stats = Statistiken-Übersicht +Empire Overview Resources = Ressourcen-Übersicht +Quick Save = Schnellspeichern +Quick Load = Schnellladen +View Capital City = Hauptstadt zeigen +Save Game = Spiel speichern +Load Game = Spiel laden +Quit Game = Spiel beenden +Toggle UI = Oberfläche verstecken an/aus +Toggle Resource Display = Ressourcen-Anzeige an/aus +Toggle Yield Display = Ertrags-Anzeige an/aus +Toggle Worked Tiles Display = Bewirtschaftet-Anzeige an/aus +Toggle Movement Display = Bewegungspfeile an/aus +Zoom In = Reinzoomen +Zoom Out = Rauszoomen +Transform = Transformieren +Repair = Reparieren Confirm Dialog = Dialog bestätigen Cancel Dialog = Dialog ablehnen @@ -6399,11 +6425,11 @@ This is a work in progress. = Dies ist noch in Arbeit. For technical reasons, only direct keys or Ctrl-Letter combinations can be used. = Aus technischen Gründen können nur Direkttasten oder Strg-Buchstaben-Kombinationen verwendet werden. The Escape key is intentionally excluded from being reassigned. = Die Escape-Taste wird absichtlich von einer Neuzuweisung ausgeschlossen. Currently, there are no checks to prevent conflicting assignments. = Derzeit gibt es keine Prüfungen, um widersprüchliche Zuweisungen zu verhindern. -Using the Keys page = Verwendung der Tasten-Seite +Using the Keys page = Verwendung der Seite für Tastatur-Zuweisungen Each binding has a button with an image looking like this: = Jede Verknüpfung hat eine Schaltfläche mit einem Bild, das wie folgt aussieht: -While hovering the mouse over the key button, you can press a desired key directly to assign it. = Wenn du mit der Maus über die Taste fährst, kannst du die gewünschte Taste direkt drücken, um sie zu belegen. -Double-click the image to reset the binding to default. = Mit einem Doppelklick auf das Bild setzt du die Verknüpfung auf die Standardeinstellungen zurück. -Bindings mapped to their default keys are displayed in gray, those reassigned by you in white. = Verknüpfungen, die den Standardtasten zugeordnet sind, werden grau angezeigt, die von dir neu zugewiesenen in weiß. +While hovering the mouse over the key button, you can press a desired key directly to assign it. = Wenn du mit der Maus über die Schaltfläche fährst, kannst du die gewünschte Taste direkt drücken, um sie zuzuweisen. +Double-click the image to reset the binding to default. = Mit einem Doppelklick auf das Bild setzt du die Zuweisung auf die Standardeinstellungen zurück. +Bindings mapped to their default keys are displayed in gray, those reassigned by you in white. = Zuweisungen, die den Standardtasten zugeordnet sind, werden grau angezeigt, die von dir neu zugewiesenen in weiß. For discussion about missing entries, see the linked github issue. = Für Diskussionen über fehlende Einträge, siehe das verlinkte GitHub-Issue. Welcome to the Civilopedia! = Willkommen in der Zivilopädie @@ -6419,4 +6445,3 @@ However, it will reflect the mods you are playing! The combination of base rules If you opened the Civilopedia from the main menu, the "Ruleset" will be that of the last game you started. = Wenn du die Zivilopädie vom Hauptmenü öffnest, wird das "Regelwerk" des letzten gestarteten Spiels ausschlaggebend sein. Letters can select categories, and when there are multiple categories matching the same letter, you can press that repeatedly to cycle between these. = Buchstaben können Kategorien auswählen. Wenn mehrere Kategorien mit dem gleichen Buchstaben anfangen, kannst du den Buchstaben wiederholt drücken, um durch diese durchzuwechseln. (Aktuell werden die Buchstaben aus der englischen Version verwendet.) The arrow keys allow navigation as well - left/right for categories, up/down for entries. = Die Pfeiltasten können auch zur Navigation verwendet werden. - Links/rechts für Kategorien, hoch/runter für Einträge. - diff --git a/core/src/com/unciv/models/UnitAction.kt b/core/src/com/unciv/models/UnitAction.kt index ff22e11ce6..3bb8f44386 100644 --- a/core/src/com/unciv/models/UnitAction.kt +++ b/core/src/com/unciv/models/UnitAction.kt @@ -1,12 +1,11 @@ package com.unciv.models -import com.badlogic.gdx.Input import com.badlogic.gdx.scenes.scene2d.Actor import com.unciv.Constants import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.translations.getPlaceholderParameters import com.unciv.ui.components.Fonts -import com.unciv.ui.components.input.KeyCharAndCode +import com.unciv.ui.components.input.KeyboardBinding import com.unciv.ui.images.ImageGetter @@ -81,7 +80,8 @@ class UpgradeUnitAction( * * @param value _default_ label to display, can be overridden in UnitAction instantiation * @param imageGetter optional lambda to get an Icon - `null` if icon is dependent on outside factors and needs special handling - * @param key keyboard binding - can be a [KeyCharAndCode], a [Char], or omitted. + * @param binding keyboard binding - omitting it will look up the KeyboardBinding of the same name (recommended) + * @param isSkippingToNextUnit if "Auto Unit Cycle" setting and this bit are on, this action will skip to the next unit * @param uncivSound _default_ sound, can be overridden in UnitAction instantiation */ @@ -91,95 +91,94 @@ class UpgradeUnitAction( enum class UnitActionType( val value: String, val imageGetter: (()-> Actor)?, - val key: KeyCharAndCode, + binding: KeyboardBinding? = null, val isSkippingToNextUnit: Boolean = true, val uncivSound: UncivSound = UncivSound.Click ) { SwapUnits("Swap units", - { ImageGetter.getUnitActionPortrait("Swap") }, 'y', false), + { ImageGetter.getUnitActionPortrait("Swap") }, false), Automate("Automate", - { ImageGetter.getUnitActionPortrait("Automate") }, 'm'), + { ImageGetter.getUnitActionPortrait("Automate") }), StopAutomation("Stop automation", - { ImageGetter.getUnitActionPortrait("Stop") }, 'm', false), + { ImageGetter.getUnitActionPortrait("Stop") }, false), StopMovement("Stop movement", - { ImageGetter.getUnitActionPortrait("StopMove") }, '.', false), + { ImageGetter.getUnitActionPortrait("StopMove") }, false), Sleep("Sleep", - { ImageGetter.getUnitActionPortrait("Sleep") }, 'f'), + { ImageGetter.getUnitActionPortrait("Sleep") }), SleepUntilHealed("Sleep until healed", - { ImageGetter.getUnitActionPortrait("Sleep") }, 'h'), + { ImageGetter.getUnitActionPortrait("Sleep") }), Fortify("Fortify", - { ImageGetter.getUnitActionPortrait("Fortify") }, 'f', UncivSound.Fortify), + { ImageGetter.getUnitActionPortrait("Fortify") }, UncivSound.Fortify), FortifyUntilHealed("Fortify until healed", - { ImageGetter.getUnitActionPortrait("FortifyUntilHealed") }, 'h', UncivSound.Fortify), + { ImageGetter.getUnitActionPortrait("FortifyUntilHealed") }, UncivSound.Fortify), Explore("Explore", - { ImageGetter.getUnitActionPortrait("Explore") }, 'x'), + { ImageGetter.getUnitActionPortrait("Explore") }), StopExploration("Stop exploration", - { ImageGetter.getUnitActionPortrait("Stop") }, 'x', false), + { ImageGetter.getUnitActionPortrait("Stop") }, false), Promote("Promote", - { ImageGetter.getUnitActionPortrait("Promote") }, 'o', false, UncivSound.Promote), + { ImageGetter.getUnitActionPortrait("Promote") }, false, UncivSound.Promote), Upgrade("Upgrade", - { ImageGetter.getUnitActionPortrait("Upgrade") }, 'u', UncivSound.Upgrade), + { ImageGetter.getUnitActionPortrait("Upgrade") }, UncivSound.Upgrade), Transform("Transform", - { ImageGetter.getUnitActionPortrait("Transform") }, 'k', UncivSound.Upgrade), + { ImageGetter.getUnitActionPortrait("Transform") }, UncivSound.Upgrade), Pillage("Pillage", - { ImageGetter.getUnitActionPortrait("Pillage") }, 'p', false), + { ImageGetter.getUnitActionPortrait("Pillage") }, false), Paradrop("Paradrop", - { ImageGetter.getUnitActionPortrait("Paradrop") }, 'p', false), + { ImageGetter.getUnitActionPortrait("Paradrop") }, false), AirSweep("Air Sweep", - { ImageGetter.getUnitActionPortrait("AirSweep") }, 'a', false), + { ImageGetter.getUnitActionPortrait("AirSweep") }, false), SetUp("Set up", - { ImageGetter.getUnitActionPortrait("SetUp") }, 't', false, UncivSound.Setup), + { ImageGetter.getUnitActionPortrait("SetUp") }, false, UncivSound.Setup), FoundCity("Found city", - { ImageGetter.getUnitActionPortrait("FoundCity") }, 'c', UncivSound.Silent), + { ImageGetter.getUnitActionPortrait("FoundCity") }, UncivSound.Silent), ConstructImprovement("Construct improvement", - { ImageGetter.getUnitActionPortrait("ConstructImprovement") }, 'i', false), + { ImageGetter.getUnitActionPortrait("ConstructImprovement") }, false), Repair(Constants.repair, - { ImageGetter.getUnitActionPortrait("Repair") }, 'r', UncivSound.Construction), + { ImageGetter.getUnitActionPortrait("Repair") }, UncivSound.Construction), Create("Create", - null, 'i', false, UncivSound.Chimes), + null, false, UncivSound.Chimes), HurryResearch("{Hurry Research} (${Fonts.death})", - { ImageGetter.getUnitActionPortrait("HurryResearch") }, 'g', UncivSound.Chimes), + { ImageGetter.getUnitActionPortrait("HurryResearch") }, UncivSound.Chimes), StartGoldenAge("Start Golden Age", - { ImageGetter.getUnitActionPortrait("StartGoldenAge") }, 'g', UncivSound.Chimes), + { ImageGetter.getUnitActionPortrait("StartGoldenAge") }, UncivSound.Chimes), HurryWonder("{Hurry Wonder} (${Fonts.death})", - { ImageGetter.getUnitActionPortrait("HurryConstruction") }, 'g', UncivSound.Chimes), + { ImageGetter.getUnitActionPortrait("HurryConstruction") }, UncivSound.Chimes), HurryBuilding("{Hurry Construction} (${Fonts.death})", - { ImageGetter.getUnitActionPortrait("HurryConstruction") }, 'g', UncivSound.Chimes), + { ImageGetter.getUnitActionPortrait("HurryConstruction") }, UncivSound.Chimes), ConductTradeMission("{Conduct Trade Mission} (${Fonts.death})", - { ImageGetter.getUnitActionPortrait("ConductTradeMission") }, 'g', UncivSound.Chimes), + { ImageGetter.getUnitActionPortrait("ConductTradeMission") }, UncivSound.Chimes), FoundReligion("Found a Religion", - { ImageGetter.getUnitActionPortrait("FoundReligion") }, 'g', UncivSound.Choir), + { ImageGetter.getUnitActionPortrait("FoundReligion") }, UncivSound.Choir), TriggerUnique("Trigger unique", - { ImageGetter.getUnitActionPortrait("Star") }, 'g', false, UncivSound.Chimes), + { ImageGetter.getUnitActionPortrait("Star") }, false, UncivSound.Chimes), SpreadReligion("Spread Religion", - null, 'g', UncivSound.Choir), + null, UncivSound.Choir), RemoveHeresy("Remove Heresy", - { ImageGetter.getUnitActionPortrait("RemoveHeresy") }, 'h', UncivSound.Fire), + { ImageGetter.getUnitActionPortrait("RemoveHeresy") }, UncivSound.Fire), EnhanceReligion("Enhance a Religion", - { ImageGetter.getUnitActionPortrait("EnhanceReligion") }, 'g', UncivSound.Choir), + { ImageGetter.getUnitActionPortrait("EnhanceReligion") }, UncivSound.Choir), DisbandUnit("Disband unit", - { ImageGetter.getUnitActionPortrait("DisbandUnit") }, KeyCharAndCode.DEL, false), + { ImageGetter.getUnitActionPortrait("DisbandUnit") }, false), GiftUnit("Gift unit", { ImageGetter.getUnitActionPortrait("Present") }, UncivSound.Silent), Wait("Wait", - { ImageGetter.getUnitActionPortrait("Wait") }, 'z', UncivSound.Silent), + { ImageGetter.getUnitActionPortrait("Wait") }, UncivSound.Silent), ShowAdditionalActions("Show more", - { ImageGetter.getUnitActionPortrait("ShowMore") }, KeyCharAndCode(Input.Keys.PAGE_DOWN), false), + { ImageGetter.getUnitActionPortrait("ShowMore") }, false), HideAdditionalActions("Back", - { ImageGetter.getUnitActionPortrait("HideMore") }, KeyCharAndCode(Input.Keys.PAGE_UP), false), + { ImageGetter.getUnitActionPortrait("HideMore") }, false), AddInCapital( "Add in capital", - { ImageGetter.getUnitActionPortrait("AddInCapital")}, 'g', UncivSound.Chimes), + { ImageGetter.getUnitActionPortrait("AddInCapital")}, UncivSound.Chimes), ; // Allow shorter initializations - constructor(value: String, imageGetter: (() -> Actor)?, key: Char, uncivSound: UncivSound = UncivSound.Click) - : this(value, imageGetter, KeyCharAndCode(key), true, uncivSound) constructor(value: String, imageGetter: (() -> Actor)?, uncivSound: UncivSound = UncivSound.Click) - : this(value, imageGetter, KeyCharAndCode.UNKNOWN, true,uncivSound) - constructor(value: String, imageGetter: (() -> Actor)?, key: Char, isSkippingToNextUnit: Boolean = true, uncivSound: UncivSound = UncivSound.Click) - : this(value, imageGetter, KeyCharAndCode(key), isSkippingToNextUnit, uncivSound) + : this(value, imageGetter, null, true, uncivSound) constructor(value: String, imageGetter: (() -> Actor)?, isSkippingToNextUnit: Boolean = true, uncivSound: UncivSound = UncivSound.Click) - : this(value, imageGetter, KeyCharAndCode.UNKNOWN, isSkippingToNextUnit, uncivSound) - + : this(value, imageGetter, null, isSkippingToNextUnit, uncivSound) + val binding: KeyboardBinding = + binding ?: + KeyboardBinding.values().firstOrNull { it.name == name } ?: + KeyboardBinding.None } diff --git a/core/src/com/unciv/models/translations/TranslationFileWriter.kt b/core/src/com/unciv/models/translations/TranslationFileWriter.kt index f30be3bb31..9c4365401b 100644 --- a/core/src/com/unciv/models/translations/TranslationFileWriter.kt +++ b/core/src/com/unciv/models/translations/TranslationFileWriter.kt @@ -135,6 +135,9 @@ object TranslationFileWriter { linesToTranslate += "${diplomaticModifier.text} = " linesToTranslate += "\n\n#################### Lines from key bindings #######################\n" + for (category in KeyboardBinding.Category.values()) { + linesToTranslate += "${category.label} = " + } for (binding in KeyboardBinding.values()) { linesToTranslate += "${binding.label} = " } diff --git a/core/src/com/unciv/ui/components/extensions/Scene2dExtensions.kt b/core/src/com/unciv/ui/components/extensions/Scene2dExtensions.kt index c23b982f76..a952697268 100644 --- a/core/src/com/unciv/ui/components/extensions/Scene2dExtensions.kt +++ b/core/src/com/unciv/ui/components/extensions/Scene2dExtensions.kt @@ -330,23 +330,33 @@ fun Group.addToCenter(actor: Actor) { * | FORWARD_DEL | 112 | Del | Forward Delete | -1 | 112 | * * This acts as proxy, you replace [Input.Keys] by [GdxKeyCodeFixes] and get sensible [DEL], [toString] and [valueOf]. + * Differences in behaviour: toString will return an empty string for un-mapped keycodes and UNKNOWN + * instead of `null` or "Unknown" respectively, + * valueOf will return UNKNOWN for un-mapped names or "" instead of -1. */ @Suppress("GDX_KEYS_BUG", "MemberVisibilityCanBePrivate") object GdxKeyCodeFixes { const val DEL = Input.Keys.FORWARD_DEL const val BACKSPACE = Input.Keys.BACKSPACE + const val UNKNOWN = Input.Keys.UNKNOWN fun toString(keyCode: Int): String = when(keyCode) { + UNKNOWN -> "" DEL -> "Del" BACKSPACE -> "Backspace" else -> Input.Keys.toString(keyCode) + ?: "" } fun valueOf(name: String): Int = when (name) { + "" -> UNKNOWN "Del" -> DEL "Backspace" -> BACKSPACE - else -> Input.Keys.valueOf(name) + else -> { + val code = Input.Keys.valueOf(name) + if (code == -1) UNKNOWN else code + } } } diff --git a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt index f75d703171..d105913cb7 100644 --- a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt +++ b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt @@ -1,6 +1,7 @@ package com.unciv.ui.components.input import com.badlogic.gdx.Input +import com.unciv.Constants private val unCamelCaseRegex = Regex("([A-Z])([A-Z])([a-z])|([a-z])([A-Z])") @@ -11,21 +12,87 @@ enum class KeyboardBinding( label: String? = null, key: KeyCharAndCode? = null ) { - // Used by [KeyShortcutDispatcher.KeyShortcut] to mark an old-style shortcut with a hardcoded key + /** Used by [KeyShortcutDispatcher.KeyShortcut] to mark an old-style shortcut with a hardcoded key */ None(Category.None, KeyCharAndCode.UNKNOWN), + // Worldscreen NextTurn(Category.WorldScreen), NextTurnAlternate(Category.WorldScreen, KeyCharAndCode.SPACE), Civilopedia(Category.WorldScreen, Input.Keys.F1), EmpireOverview(Category.WorldScreen), - Wait(Category.None, 'z'), // Used but excluded from UI because UnitActionType.Wait needs to be done too + EmpireOverviewTrades(Category.WorldScreen, Input.Keys.F2), + EmpireOverviewUnits(Category.WorldScreen, Input.Keys.F3), + EmpireOverviewPolitics(Category.WorldScreen, Input.Keys.F4), + SocialPolicies(Category.WorldScreen, Input.Keys.F5), + TechnologyTree(Category.WorldScreen, Input.Keys.F6), + EmpireOverviewNotifications(Category.WorldScreen, Input.Keys.F7), + VictoryScreen(Category.WorldScreen, "Victory status", Input.Keys.F8), + EmpireOverviewStats(Category.WorldScreen, Input.Keys.F9), + EmpireOverviewResources(Category.WorldScreen, Input.Keys.F10), + QuickSave(Category.WorldScreen, Input.Keys.F11), + QuickLoad(Category.WorldScreen, Input.Keys.F12), + ViewCapitalCity(Category.WorldScreen, Input.Keys.HOME), + Options(Category.WorldScreen, KeyCharAndCode.ctrl('o')), + SaveGame(Category.WorldScreen, KeyCharAndCode.ctrl('s')), + LoadGame(Category.WorldScreen, KeyCharAndCode.ctrl('l')), + QuitGame(Category.WorldScreen, KeyCharAndCode.ctrl('q')), + ToggleUI(Category.WorldScreen, "Toggle UI", KeyCharAndCode.ctrl('u')), + ToggleResourceDisplay(Category.WorldScreen, KeyCharAndCode.ctrl('r')), + ToggleYieldDisplay(Category.WorldScreen, KeyCharAndCode.ctrl('y')), + ToggleWorkedTilesDisplay(Category.WorldScreen, KeyCharAndCode.UNKNOWN), + ToggleMovementDisplay(Category.WorldScreen, KeyCharAndCode.UNKNOWN), + ZoomIn(Category.WorldScreen, Input.Keys.NUMPAD_ADD), + ZoomOut(Category.WorldScreen, Input.Keys.NUMPAD_SUBTRACT), + + // Unit actions - name MUST correspond to UnitActionType.name because the shorthand constructor + // there looks up bindings here by name - which also means we must not use UnitActionType + // here as it will not be guaranteed to already be fully initialized. + SwapUnits(Category.UnitActions,"Swap units", 'y'), + Automate(Category.UnitActions, 'm'), + StopAutomation(Category.UnitActions,"Stop automation", 'm'), + StopMovement(Category.UnitActions,"Stop movement", '.'), + Sleep(Category.UnitActions, 'f'), + SleepUntilHealed(Category.UnitActions,"Sleep until healed", 'h'), + Fortify(Category.UnitActions, 'f'), + FortifyUntilHealed(Category.UnitActions,"Fortify until healed", 'h'), + Explore(Category.UnitActions, 'x'), + StopExploration(Category.UnitActions,"Stop exploration", 'x'), + Promote(Category.UnitActions, 'o'), + Upgrade(Category.UnitActions, 'u'), + Transform(Category.UnitActions, 'k'), + Pillage(Category.UnitActions, 'p'), + Paradrop(Category.UnitActions, 'p'), + AirSweep(Category.UnitActions, 'a'), + SetUp(Category.UnitActions,"Set up", 't'), + FoundCity(Category.UnitActions,"Found city", 'c'), + ConstructImprovement(Category.UnitActions,"Construct improvement", 'i'), + Repair(Category.UnitActions, Constants.repair, 'r'), + Create(Category.UnitActions, 'i'), + HurryResearch(Category.UnitActions, 'g'), + StartGoldenAge(Category.UnitActions, 'g'), + HurryWonder(Category.UnitActions, 'g'), + HurryBuilding(Category.UnitActions,"Hurry Construction", 'g'), + ConductTradeMission(Category.UnitActions, 'g'), + FoundReligion(Category.UnitActions,"Found a Religion", 'g'), + TriggerUnique(Category.UnitActions,"Trigger unique", 'g'), + SpreadReligion(Category.UnitActions, 'g'), + RemoveHeresy(Category.UnitActions, 'h'), + EnhanceReligion(Category.UnitActions,"Enhance a Religion", 'g'), + DisbandUnit(Category.UnitActions,"Disband unit", KeyCharAndCode.DEL), + GiftUnit(Category.UnitActions,"Gift unit", KeyCharAndCode.UNKNOWN), + Wait(Category.UnitActions, 'z'), + ShowAdditionalActions(Category.UnitActions,"Show more", Input.Keys.PAGE_DOWN), + HideAdditionalActions(Category.UnitActions,"Back", Input.Keys.PAGE_UP), + AddInCapital(Category.UnitActions, "Add in capital", 'g'), + // Popups Confirm(Category.Popups, "Confirm Dialog", 'y'), Cancel(Category.Popups, "Cancel Dialog", 'n'), ; enum class Category { - None, WorldScreen, Popups + None, WorldScreen, UnitActions, Popups; + val label = unCamelCase(name) } val label: String @@ -41,8 +108,8 @@ enum class KeyboardBinding( constructor(category: Category, label: String, key: Char) : this(category, label, KeyCharAndCode(key)) constructor(category: Category, label: String, key: Int) : this(category, label, KeyCharAndCode(key)) constructor(category: Category, key: KeyCharAndCode) : this(category, null, key) - constructor(category: Category, key: Char) : this(category, null, KeyCharAndCode(key)) - constructor(category: Category, key: Int) : this(category, null, KeyCharAndCode(key)) + constructor(category: Category, key: Char) : this(category, KeyCharAndCode(key)) + constructor(category: Category, key: Int) : this(category, KeyCharAndCode(key)) /** Debug helper */ override fun toString() = "$category.$name($defaultKey)" diff --git a/core/src/com/unciv/ui/popups/options/KeyBindingsTab.kt b/core/src/com/unciv/ui/popups/options/KeyBindingsTab.kt index c2944ce68b..e9a6f42eb1 100644 --- a/core/src/com/unciv/ui/popups/options/KeyBindingsTab.kt +++ b/core/src/com/unciv/ui/popups/options/KeyBindingsTab.kt @@ -2,7 +2,10 @@ package com.unciv.ui.popups.options import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.GUI +import com.unciv.UncivGame import com.unciv.models.ruleset.RulesetCache +import com.unciv.models.translations.tr +import com.unciv.ui.components.ExpanderTab import com.unciv.ui.components.KeyCapturingButton import com.unciv.ui.components.input.KeyboardBinding import com.unciv.ui.components.TabbedPager @@ -15,10 +18,10 @@ import com.unciv.ui.screens.civilopediascreen.MarkupRenderer class KeyBindingsTab( optionsPopup: OptionsPopup, - labelWidth: Float + private val labelWidth: Float ) : Table(BaseScreen.skin), TabbedPager.IPageExtensions { private val keyBindings = optionsPopup.settings.keyBindings - private val keyFields = HashMap(KeyboardBinding.values().size) + private val groupedWidgets: LinkedHashMap> private val disclaimer = MarkupRenderer.render(listOf( FormattedLine("This is a work in progress.", color = "#b22222", centered = true), // FIREBRICK FormattedLine(), @@ -32,31 +35,55 @@ class KeyBindingsTab( } init { + top() pad(10f) defaults().pad(5f) - for (binding in KeyboardBinding.values()) { - if (binding.hidden) continue - keyFields[binding] = KeyCapturingButton(binding.defaultKey) - } + val collator = UncivGame.Current.settings.getCollatorFromLocale() + groupedWidgets = KeyboardBinding.values().asSequence() + .filterNot { it.hidden } + .groupBy { it.category } // Materializes a Map> + .asSequence() + .map { (category, bindings) -> + category to bindings.asSequence() + .sortedWith(compareBy(collator) { it.label.tr() }) + .map { it to KeyCapturingButton(it.defaultKey) } // associate would materialize a map + .toMap(LinkedHashMap()) + } + .sortedBy { it.first.name.tr() } + .toMap(LinkedHashMap()) } private fun update() { clear() - add(disclaimer).colspan(2).center().row() + add(disclaimer).center().row() - for (binding in KeyboardBinding.values()) { - if (binding.hidden) continue - add(binding.label.toLabel()) - add(keyFields[binding]).row() - keyFields[binding]!!.current = keyBindings[binding] + for ((category, bindings) in groupedWidgets) + add(getCategoryWidget(category, bindings)).row() + } + + private fun getCategoryWidget( + category: KeyboardBinding.Category, + bindings: LinkedHashMap + ) = ExpanderTab( + category.label, + startsOutOpened = false, + defaultPad = 0f, + headerPad = 5f, + // expanderWidth = labelWidth, + persistenceID = "KeyBindings." + category.name + ) { + it.defaults().padTop(5f) + for ((binding, widget) in bindings) { + it.add(binding.label.toLabel()).padRight(10f).minWidth(labelWidth / 2) + it.add(widget).row() + widget.current = keyBindings[binding] } } fun save () { - for (binding in KeyboardBinding.values()) { - if (binding.hidden) continue - keyBindings[binding] = keyFields[binding]!!.current + for ((binding, widget) in groupedWidgets.asSequence().flatMap { it.value.entries }) { + keyBindings[binding] = widget.current } } diff --git a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt index 5f7e42ce0a..3418b3fea5 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt @@ -230,49 +230,54 @@ class WorldScreen( /* * These try to be faithful to default Civ5 key bindings as found in several places online * Some are a little arbitrary, e.g. Economic info, Military info - * Some are very much so as Unciv *is* Strategic View and the Notification log is always visible + * Some are very much so as Unciv *is* Strategic View */ - globalShortcuts.add(Input.Keys.F2) { openEmpireOverview(EmpireOverviewCategories.Trades) } // Economic info - globalShortcuts.add(Input.Keys.F3) { openEmpireOverview(EmpireOverviewCategories.Units) } // Military info - globalShortcuts.add(Input.Keys.F4) { openEmpireOverview(EmpireOverviewCategories.Politics) } // Diplomacy info - globalShortcuts.add(Input.Keys.F5) { game.pushScreen(PolicyPickerScreen(selectedCiv, canChangeState)) } // Social Policies Screen - globalShortcuts.add(Input.Keys.F6) { game.pushScreen(TechPickerScreen(viewingCiv)) } // Tech Screen - globalShortcuts.add(Input.Keys.F7) { openEmpireOverview(EmpireOverviewCategories.Notifications) } // Notification Log - globalShortcuts.add(Input.Keys.F8) { game.pushScreen(VictoryScreen(this)) } // Victory Progress - globalShortcuts.add(Input.Keys.F9) { openEmpireOverview(EmpireOverviewCategories.Stats) } // Demographics - globalShortcuts.add(Input.Keys.F10) { openEmpireOverview(EmpireOverviewCategories.Resources) } // originally Strategic View - globalShortcuts.add(Input.Keys.F11) { QuickSave.save(gameInfo, this) } // Quick Save - globalShortcuts.add(Input.Keys.F12) { QuickSave.load(this) } // Quick Load - globalShortcuts.add(Input.Keys.HOME) { // Capital City View + globalShortcuts.add(KeyboardBinding.EmpireOverviewTrades) { openEmpireOverview(EmpireOverviewCategories.Trades) } // Economic info + globalShortcuts.add(KeyboardBinding.EmpireOverviewUnits) { openEmpireOverview(EmpireOverviewCategories.Units) } // Military info + globalShortcuts.add(KeyboardBinding.EmpireOverviewPolitics) { openEmpireOverview(EmpireOverviewCategories.Politics) } // Diplomacy info + globalShortcuts.add(KeyboardBinding.SocialPolicies) { game.pushScreen(PolicyPickerScreen(selectedCiv, canChangeState)) } // Social Policies Screen + globalShortcuts.add(KeyboardBinding.TechnologyTree) { game.pushScreen(TechPickerScreen(viewingCiv)) } // Tech Screen + globalShortcuts.add(KeyboardBinding.EmpireOverviewNotifications) { openEmpireOverview(EmpireOverviewCategories.Notifications) } // Notification Log + globalShortcuts.add(KeyboardBinding.VictoryScreen) { game.pushScreen(VictoryScreen(this)) } // Victory Progress + globalShortcuts.add(KeyboardBinding.EmpireOverviewStats) { openEmpireOverview(EmpireOverviewCategories.Stats) } // Demographics + globalShortcuts.add(KeyboardBinding.EmpireOverviewResources) { openEmpireOverview(EmpireOverviewCategories.Resources) } // originally Strategic View + globalShortcuts.add(KeyboardBinding.QuickSave) { QuickSave.save(gameInfo, this) } // Quick Save + globalShortcuts.add(KeyboardBinding.QuickLoad) { QuickSave.load(this) } // Quick Load + globalShortcuts.add(KeyboardBinding.ViewCapitalCity) { // Capital City View val capital = gameInfo.getCurrentPlayerCivilization().getCapital() if (capital != null && !mapHolder.setCenterPosition(capital.location)) game.pushScreen(CityScreen(capital)) } - globalShortcuts.add(KeyCharAndCode.ctrl('O')) { // Game Options + globalShortcuts.add(KeyboardBinding.Options) { // Game Options this.openOptionsPopup(onClose = { nextTurnButton.update(this) }) } - globalShortcuts.add(KeyCharAndCode.ctrl('S')) { game.pushScreen(SaveGameScreen(gameInfo)) } // Save - globalShortcuts.add(KeyCharAndCode.ctrl('L')) { game.pushScreen(LoadGameScreen()) } // Load - globalShortcuts.add(KeyCharAndCode.ctrl('Q')) { game.popScreen() } // WorldScreen is the last screen, so this quits + globalShortcuts.add(KeyboardBinding.SaveGame) { game.pushScreen(SaveGameScreen(gameInfo)) } // Save + globalShortcuts.add(KeyboardBinding.LoadGame) { game.pushScreen(LoadGameScreen()) } // Load + globalShortcuts.add(KeyboardBinding.QuitGame) { game.popScreen() } // WorldScreen is the last screen, so this quits globalShortcuts.add(Input.Keys.NUMPAD_ADD) { this.mapHolder.zoomIn() } // '+' Zoom globalShortcuts.add(Input.Keys.NUMPAD_SUBTRACT) { this.mapHolder.zoomOut() } // '-' Zoom + globalShortcuts.add(KeyboardBinding.ToggleUI) { toggleUI() } + globalShortcuts.add(KeyboardBinding.ToggleResourceDisplay) { minimapWrapper.resourceImageButton.toggle() } + globalShortcuts.add(KeyboardBinding.ToggleYieldDisplay) { minimapWrapper.yieldImageButton.toggle() } + globalShortcuts.add(KeyboardBinding.ToggleWorkedTilesDisplay) { minimapWrapper.populationImageButton.toggle() } + globalShortcuts.add(KeyboardBinding.ToggleMovementDisplay) { minimapWrapper.movementsImageButton.toggle() } + } - globalShortcuts.add(KeyCharAndCode.ctrl('U')){ - uiEnabled = !uiEnabled - topBar.isVisible = uiEnabled - statusButtons.isVisible = uiEnabled - techPolicyAndDiplomacy.isVisible = uiEnabled - tutorialTaskTable.isVisible = uiEnabled - bottomTileInfoTable.isVisible = uiEnabled - unitActionsTable.isVisible = uiEnabled - notificationsScroll.isVisible = uiEnabled - minimapWrapper.isVisible = uiEnabled - bottomUnitTable.isVisible = uiEnabled - if (uiEnabled) battleTable.update() else battleTable.isVisible = false - fogOfWarButton.isVisible = uiEnabled && viewingCiv.isSpectator() - } + private fun toggleUI() { + uiEnabled = !uiEnabled + topBar.isVisible = uiEnabled + statusButtons.isVisible = uiEnabled + techPolicyAndDiplomacy.isVisible = uiEnabled + tutorialTaskTable.isVisible = uiEnabled + bottomTileInfoTable.isVisible = uiEnabled + unitActionsTable.isVisible = uiEnabled + notificationsScroll.isVisible = uiEnabled + minimapWrapper.isVisible = uiEnabled + bottomUnitTable.isVisible = uiEnabled + if (uiEnabled) battleTable.update() else battleTable.isVisible = false + fogOfWarButton.isVisible = uiEnabled && viewingCiv.isSpectator() } private fun addKeyboardListener() { diff --git a/core/src/com/unciv/ui/screens/worldscreen/minimap/MinimapHolder.kt b/core/src/com/unciv/ui/screens/worldscreen/minimap/MinimapHolder.kt index 0cb5697a82..ff345395c9 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/minimap/MinimapHolder.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/minimap/MinimapHolder.kt @@ -24,7 +24,7 @@ class MinimapHolder(val mapHolder: WorldMapHolder) : Table() { backgroundColor = Color.GREEN ) /** Button, next to the minimap, to toggle the tile yield map overlay. */ - private val yieldImageButton = MapOverlayToggleButton( + val yieldImageButton = MapOverlayToggleButton( ImageGetter.getImage("StatIcons/Food"), // This is a use in the UI that has little to do with the stat… These buttons have more in common with each other than they do with other uses of getStatIcon(). getter = { UncivGame.Current.settings.showTileYields }, diff --git a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsTable.kt b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsTable.kt index c614b38a3a..02b7f3425a 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsTable.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsTable.kt @@ -11,10 +11,10 @@ import com.unciv.logic.map.mapunit.MapUnit import com.unciv.models.UnitAction import com.unciv.models.UnitActionType import com.unciv.models.UpgradeUnitAction -import com.unciv.ui.components.input.KeyCharAndCode import com.unciv.ui.components.UncivTooltip import com.unciv.ui.components.UncivTooltip.Companion.addTooltip import com.unciv.ui.components.extensions.disable +import com.unciv.ui.components.input.KeyboardBindings import com.unciv.ui.components.input.keyShortcuts import com.unciv.ui.components.input.onActivation import com.unciv.ui.components.extensions.packIfNeeded @@ -31,7 +31,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() { for (unitAction in UnitActions.getUnitActions(unit)) { val button = getUnitActionButton(unit, unitAction) if (unitAction is UpgradeUnitAction && GUI.keyboardAvailable) { - val tipTitle = "«RED»${unitAction.type.key}«»: {Upgrade}" + val tipTitle = "«RED»${KeyboardBindings[unitAction.type.binding]}«»: {Upgrade}" val tipActor = BaseUnitDescriptions.getUpgradeTooltipActor(tipTitle, unit.baseUnit, unitAction.unitToUpgradeTo) button.addListener(UncivTooltip(button, tipActor , offset = Vector2(0f, tipActor.packIfNeeded().height * 0.333f) // scaling fails to express size in parent coordinates @@ -46,7 +46,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() { private fun getUnitActionButton(unit: MapUnit, unitAction: UnitAction): Button { val icon = unitAction.getIcon() // If peripheral keyboard not detected, hotkeys will not be displayed - val key = if (GUI.keyboardAvailable) unitAction.type.key else KeyCharAndCode.UNKNOWN + val binding = unitAction.type.binding val fontColor = if (unitAction.isCurrentAction) Color.YELLOW else Color.WHITE val actionButton = IconTextButton(unitAction.title, icon, fontColor = fontColor) @@ -55,7 +55,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() { actionButton.color = Color.GREEN.cpy().lerp(Color.WHITE, 0.5f) if (unitAction !is UpgradeUnitAction) // Does its own toolTip - actionButton.addTooltip(key) + actionButton.addTooltip(KeyboardBindings[binding]) actionButton.pack() if (unitAction.action == null) { actionButton.disable() @@ -72,7 +72,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() { worldScreen.switchToNextUnit() } } - actionButton.keyShortcuts.add(key) + actionButton.keyShortcuts.add(binding) } return actionButton