Key bindings - unit actions, world screen and grouped UI (#8986)

* Keyboard Bindings: Simpler Widget

* Keyboard Bindings: Reset binding

* Keyboard Bindings: Simpler Widget - revert opening feature

* Keyboard Bindings Step 6: Unit Actions

* Keyboard Bindings Step 7: Grouped UI

* Keyboard Bindings Step 7: Grouped UI-TFW

* Keyboard Bindings Step 6: Unit Actions - patch

* Keyboard Bindings Step 9: World Screen

* Keyboard Bindings - German

* Keyboard Bindings: Fix merge errors

* Keyboard Bindings: Tiny forgotten things

* Merge fixes
This commit is contained in:
SomeTroglodyte 2023-06-12 09:13:57 +02:00 committed by GitHub
parent e109848e28
commit 404a148cfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 246 additions and 110 deletions

View File

@ -816,6 +816,7 @@ Enable display cutout (requires restart) = Aktiviere Bildschirmausschnitt (Neust
## Keys tab ## Keys tab
Keys = Tastenzuordnung Keys = Tastenzuordnung
Please see the Tutorial. = Bitte schau dir das Tutorial an. 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 tab
Locate mod errors = Mod-Probleme 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 ####################### #################### Lines from key bindings #######################
Unit Actions = Einheiten-Aktionen
Popups = Dialoge
Next Turn = Nächste Runde Next Turn = Nächste Runde
Next Turn Alternate = Nächste Runde Alternativ Next Turn Alternate = Nächste Runde Alternativ
Empire Overview = Reichsübersicht 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 Confirm Dialog = Dialog bestätigen
Cancel Dialog = Dialog ablehnen 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. 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. 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. 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: 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. 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 Verknüpfung auf die Standardeinstellungen zurück. 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. = Verknüpfungen, die den Standardtasten zugeordnet sind, werden grau angezeigt, die von dir neu zugewiesenen in weiß. 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. 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 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. 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.) 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. 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.

View File

@ -1,12 +1,11 @@
package com.unciv.models package com.unciv.models
import com.badlogic.gdx.Input
import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Actor
import com.unciv.Constants import com.unciv.Constants
import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.ui.components.Fonts 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 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 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 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 * @param uncivSound _default_ sound, can be overridden in UnitAction instantiation
*/ */
@ -91,95 +91,94 @@ class UpgradeUnitAction(
enum class UnitActionType( enum class UnitActionType(
val value: String, val value: String,
val imageGetter: (()-> Actor)?, val imageGetter: (()-> Actor)?,
val key: KeyCharAndCode, binding: KeyboardBinding? = null,
val isSkippingToNextUnit: Boolean = true, val isSkippingToNextUnit: Boolean = true,
val uncivSound: UncivSound = UncivSound.Click val uncivSound: UncivSound = UncivSound.Click
) { ) {
SwapUnits("Swap units", SwapUnits("Swap units",
{ ImageGetter.getUnitActionPortrait("Swap") }, 'y', false), { ImageGetter.getUnitActionPortrait("Swap") }, false),
Automate("Automate", Automate("Automate",
{ ImageGetter.getUnitActionPortrait("Automate") }, 'm'), { ImageGetter.getUnitActionPortrait("Automate") }),
StopAutomation("Stop automation", StopAutomation("Stop automation",
{ ImageGetter.getUnitActionPortrait("Stop") }, 'm', false), { ImageGetter.getUnitActionPortrait("Stop") }, false),
StopMovement("Stop movement", StopMovement("Stop movement",
{ ImageGetter.getUnitActionPortrait("StopMove") }, '.', false), { ImageGetter.getUnitActionPortrait("StopMove") }, false),
Sleep("Sleep", Sleep("Sleep",
{ ImageGetter.getUnitActionPortrait("Sleep") }, 'f'), { ImageGetter.getUnitActionPortrait("Sleep") }),
SleepUntilHealed("Sleep until healed", SleepUntilHealed("Sleep until healed",
{ ImageGetter.getUnitActionPortrait("Sleep") }, 'h'), { ImageGetter.getUnitActionPortrait("Sleep") }),
Fortify("Fortify", Fortify("Fortify",
{ ImageGetter.getUnitActionPortrait("Fortify") }, 'f', UncivSound.Fortify), { ImageGetter.getUnitActionPortrait("Fortify") }, UncivSound.Fortify),
FortifyUntilHealed("Fortify until healed", FortifyUntilHealed("Fortify until healed",
{ ImageGetter.getUnitActionPortrait("FortifyUntilHealed") }, 'h', UncivSound.Fortify), { ImageGetter.getUnitActionPortrait("FortifyUntilHealed") }, UncivSound.Fortify),
Explore("Explore", Explore("Explore",
{ ImageGetter.getUnitActionPortrait("Explore") }, 'x'), { ImageGetter.getUnitActionPortrait("Explore") }),
StopExploration("Stop exploration", StopExploration("Stop exploration",
{ ImageGetter.getUnitActionPortrait("Stop") }, 'x', false), { ImageGetter.getUnitActionPortrait("Stop") }, false),
Promote("Promote", Promote("Promote",
{ ImageGetter.getUnitActionPortrait("Promote") }, 'o', false, UncivSound.Promote), { ImageGetter.getUnitActionPortrait("Promote") }, false, UncivSound.Promote),
Upgrade("Upgrade", Upgrade("Upgrade",
{ ImageGetter.getUnitActionPortrait("Upgrade") }, 'u', UncivSound.Upgrade), { ImageGetter.getUnitActionPortrait("Upgrade") }, UncivSound.Upgrade),
Transform("Transform", Transform("Transform",
{ ImageGetter.getUnitActionPortrait("Transform") }, 'k', UncivSound.Upgrade), { ImageGetter.getUnitActionPortrait("Transform") }, UncivSound.Upgrade),
Pillage("Pillage", Pillage("Pillage",
{ ImageGetter.getUnitActionPortrait("Pillage") }, 'p', false), { ImageGetter.getUnitActionPortrait("Pillage") }, false),
Paradrop("Paradrop", Paradrop("Paradrop",
{ ImageGetter.getUnitActionPortrait("Paradrop") }, 'p', false), { ImageGetter.getUnitActionPortrait("Paradrop") }, false),
AirSweep("Air Sweep", AirSweep("Air Sweep",
{ ImageGetter.getUnitActionPortrait("AirSweep") }, 'a', false), { ImageGetter.getUnitActionPortrait("AirSweep") }, false),
SetUp("Set up", SetUp("Set up",
{ ImageGetter.getUnitActionPortrait("SetUp") }, 't', false, UncivSound.Setup), { ImageGetter.getUnitActionPortrait("SetUp") }, false, UncivSound.Setup),
FoundCity("Found city", FoundCity("Found city",
{ ImageGetter.getUnitActionPortrait("FoundCity") }, 'c', UncivSound.Silent), { ImageGetter.getUnitActionPortrait("FoundCity") }, UncivSound.Silent),
ConstructImprovement("Construct improvement", ConstructImprovement("Construct improvement",
{ ImageGetter.getUnitActionPortrait("ConstructImprovement") }, 'i', false), { ImageGetter.getUnitActionPortrait("ConstructImprovement") }, false),
Repair(Constants.repair, Repair(Constants.repair,
{ ImageGetter.getUnitActionPortrait("Repair") }, 'r', UncivSound.Construction), { ImageGetter.getUnitActionPortrait("Repair") }, UncivSound.Construction),
Create("Create", Create("Create",
null, 'i', false, UncivSound.Chimes), null, false, UncivSound.Chimes),
HurryResearch("{Hurry Research} (${Fonts.death})", HurryResearch("{Hurry Research} (${Fonts.death})",
{ ImageGetter.getUnitActionPortrait("HurryResearch") }, 'g', UncivSound.Chimes), { ImageGetter.getUnitActionPortrait("HurryResearch") }, UncivSound.Chimes),
StartGoldenAge("Start Golden Age", StartGoldenAge("Start Golden Age",
{ ImageGetter.getUnitActionPortrait("StartGoldenAge") }, 'g', UncivSound.Chimes), { ImageGetter.getUnitActionPortrait("StartGoldenAge") }, UncivSound.Chimes),
HurryWonder("{Hurry Wonder} (${Fonts.death})", HurryWonder("{Hurry Wonder} (${Fonts.death})",
{ ImageGetter.getUnitActionPortrait("HurryConstruction") }, 'g', UncivSound.Chimes), { ImageGetter.getUnitActionPortrait("HurryConstruction") }, UncivSound.Chimes),
HurryBuilding("{Hurry Construction} (${Fonts.death})", HurryBuilding("{Hurry Construction} (${Fonts.death})",
{ ImageGetter.getUnitActionPortrait("HurryConstruction") }, 'g', UncivSound.Chimes), { ImageGetter.getUnitActionPortrait("HurryConstruction") }, UncivSound.Chimes),
ConductTradeMission("{Conduct Trade Mission} (${Fonts.death})", ConductTradeMission("{Conduct Trade Mission} (${Fonts.death})",
{ ImageGetter.getUnitActionPortrait("ConductTradeMission") }, 'g', UncivSound.Chimes), { ImageGetter.getUnitActionPortrait("ConductTradeMission") }, UncivSound.Chimes),
FoundReligion("Found a Religion", FoundReligion("Found a Religion",
{ ImageGetter.getUnitActionPortrait("FoundReligion") }, 'g', UncivSound.Choir), { ImageGetter.getUnitActionPortrait("FoundReligion") }, UncivSound.Choir),
TriggerUnique("Trigger unique", TriggerUnique("Trigger unique",
{ ImageGetter.getUnitActionPortrait("Star") }, 'g', false, UncivSound.Chimes), { ImageGetter.getUnitActionPortrait("Star") }, false, UncivSound.Chimes),
SpreadReligion("Spread Religion", SpreadReligion("Spread Religion",
null, 'g', UncivSound.Choir), null, UncivSound.Choir),
RemoveHeresy("Remove Heresy", RemoveHeresy("Remove Heresy",
{ ImageGetter.getUnitActionPortrait("RemoveHeresy") }, 'h', UncivSound.Fire), { ImageGetter.getUnitActionPortrait("RemoveHeresy") }, UncivSound.Fire),
EnhanceReligion("Enhance a Religion", EnhanceReligion("Enhance a Religion",
{ ImageGetter.getUnitActionPortrait("EnhanceReligion") }, 'g', UncivSound.Choir), { ImageGetter.getUnitActionPortrait("EnhanceReligion") }, UncivSound.Choir),
DisbandUnit("Disband unit", DisbandUnit("Disband unit",
{ ImageGetter.getUnitActionPortrait("DisbandUnit") }, KeyCharAndCode.DEL, false), { ImageGetter.getUnitActionPortrait("DisbandUnit") }, false),
GiftUnit("Gift unit", GiftUnit("Gift unit",
{ ImageGetter.getUnitActionPortrait("Present") }, UncivSound.Silent), { ImageGetter.getUnitActionPortrait("Present") }, UncivSound.Silent),
Wait("Wait", Wait("Wait",
{ ImageGetter.getUnitActionPortrait("Wait") }, 'z', UncivSound.Silent), { ImageGetter.getUnitActionPortrait("Wait") }, UncivSound.Silent),
ShowAdditionalActions("Show more", ShowAdditionalActions("Show more",
{ ImageGetter.getUnitActionPortrait("ShowMore") }, KeyCharAndCode(Input.Keys.PAGE_DOWN), false), { ImageGetter.getUnitActionPortrait("ShowMore") }, false),
HideAdditionalActions("Back", HideAdditionalActions("Back",
{ ImageGetter.getUnitActionPortrait("HideMore") }, KeyCharAndCode(Input.Keys.PAGE_UP), false), { ImageGetter.getUnitActionPortrait("HideMore") }, false),
AddInCapital( "Add in capital", AddInCapital( "Add in capital",
{ ImageGetter.getUnitActionPortrait("AddInCapital")}, 'g', UncivSound.Chimes), { ImageGetter.getUnitActionPortrait("AddInCapital")}, UncivSound.Chimes),
; ;
// Allow shorter initializations // 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) constructor(value: String, imageGetter: (() -> Actor)?, uncivSound: UncivSound = UncivSound.Click)
: this(value, imageGetter, KeyCharAndCode.UNKNOWN, true,uncivSound) : this(value, imageGetter, null, true, uncivSound)
constructor(value: String, imageGetter: (() -> Actor)?, key: Char, isSkippingToNextUnit: Boolean = true, uncivSound: UncivSound = UncivSound.Click)
: this(value, imageGetter, KeyCharAndCode(key), isSkippingToNextUnit, uncivSound)
constructor(value: String, imageGetter: (() -> Actor)?, isSkippingToNextUnit: Boolean = true, uncivSound: UncivSound = UncivSound.Click) 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
} }

View File

@ -135,6 +135,9 @@ object TranslationFileWriter {
linesToTranslate += "${diplomaticModifier.text} = " linesToTranslate += "${diplomaticModifier.text} = "
linesToTranslate += "\n\n#################### Lines from key bindings #######################\n" linesToTranslate += "\n\n#################### Lines from key bindings #######################\n"
for (category in KeyboardBinding.Category.values()) {
linesToTranslate += "${category.label} = "
}
for (binding in KeyboardBinding.values()) { for (binding in KeyboardBinding.values()) {
linesToTranslate += "${binding.label} = " linesToTranslate += "${binding.label} = "
} }

View File

@ -330,23 +330,33 @@ fun Group.addToCenter(actor: Actor) {
* | FORWARD_DEL | 112 | Del | Forward Delete | -1 | 112 | * | 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]. * 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") @Suppress("GDX_KEYS_BUG", "MemberVisibilityCanBePrivate")
object GdxKeyCodeFixes { object GdxKeyCodeFixes {
const val DEL = Input.Keys.FORWARD_DEL const val DEL = Input.Keys.FORWARD_DEL
const val BACKSPACE = Input.Keys.BACKSPACE const val BACKSPACE = Input.Keys.BACKSPACE
const val UNKNOWN = Input.Keys.UNKNOWN
fun toString(keyCode: Int): String = when(keyCode) { fun toString(keyCode: Int): String = when(keyCode) {
UNKNOWN -> ""
DEL -> "Del" DEL -> "Del"
BACKSPACE -> "Backspace" BACKSPACE -> "Backspace"
else -> Input.Keys.toString(keyCode) else -> Input.Keys.toString(keyCode)
?: ""
} }
fun valueOf(name: String): Int = when (name) { fun valueOf(name: String): Int = when (name) {
"" -> UNKNOWN
"Del" -> DEL "Del" -> DEL
"Backspace" -> BACKSPACE "Backspace" -> BACKSPACE
else -> Input.Keys.valueOf(name) else -> {
val code = Input.Keys.valueOf(name)
if (code == -1) UNKNOWN else code
}
} }
} }

View File

@ -1,6 +1,7 @@
package com.unciv.ui.components.input package com.unciv.ui.components.input
import com.badlogic.gdx.Input import com.badlogic.gdx.Input
import com.unciv.Constants
private val unCamelCaseRegex = Regex("([A-Z])([A-Z])([a-z])|([a-z])([A-Z])") private val unCamelCaseRegex = Regex("([A-Z])([A-Z])([a-z])|([a-z])([A-Z])")
@ -11,21 +12,87 @@ enum class KeyboardBinding(
label: String? = null, label: String? = null,
key: KeyCharAndCode? = 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), None(Category.None, KeyCharAndCode.UNKNOWN),
// Worldscreen // Worldscreen
NextTurn(Category.WorldScreen), NextTurn(Category.WorldScreen),
NextTurnAlternate(Category.WorldScreen, KeyCharAndCode.SPACE), NextTurnAlternate(Category.WorldScreen, KeyCharAndCode.SPACE),
Civilopedia(Category.WorldScreen, Input.Keys.F1), Civilopedia(Category.WorldScreen, Input.Keys.F1),
EmpireOverview(Category.WorldScreen), 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 // Popups
Confirm(Category.Popups, "Confirm Dialog", 'y'), Confirm(Category.Popups, "Confirm Dialog", 'y'),
Cancel(Category.Popups, "Cancel Dialog", 'n'), Cancel(Category.Popups, "Cancel Dialog", 'n'),
; ;
enum class Category { enum class Category {
None, WorldScreen, Popups None, WorldScreen, UnitActions, Popups;
val label = unCamelCase(name)
} }
val label: String 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: Char) : this(category, label, KeyCharAndCode(key))
constructor(category: Category, label: String, key: Int) : 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: KeyCharAndCode) : this(category, null, key)
constructor(category: Category, key: Char) : this(category, null, KeyCharAndCode(key)) constructor(category: Category, key: Char) : this(category, KeyCharAndCode(key))
constructor(category: Category, key: Int) : this(category, null, KeyCharAndCode(key)) constructor(category: Category, key: Int) : this(category, KeyCharAndCode(key))
/** Debug helper */ /** Debug helper */
override fun toString() = "$category.$name($defaultKey)" override fun toString() = "$category.$name($defaultKey)"

View File

@ -2,7 +2,10 @@ package com.unciv.ui.popups.options
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.GUI import com.unciv.GUI
import com.unciv.UncivGame
import com.unciv.models.ruleset.RulesetCache 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.KeyCapturingButton
import com.unciv.ui.components.input.KeyboardBinding import com.unciv.ui.components.input.KeyboardBinding
import com.unciv.ui.components.TabbedPager import com.unciv.ui.components.TabbedPager
@ -15,10 +18,10 @@ import com.unciv.ui.screens.civilopediascreen.MarkupRenderer
class KeyBindingsTab( class KeyBindingsTab(
optionsPopup: OptionsPopup, optionsPopup: OptionsPopup,
labelWidth: Float private val labelWidth: Float
) : Table(BaseScreen.skin), TabbedPager.IPageExtensions { ) : Table(BaseScreen.skin), TabbedPager.IPageExtensions {
private val keyBindings = optionsPopup.settings.keyBindings private val keyBindings = optionsPopup.settings.keyBindings
private val keyFields = HashMap<KeyboardBinding, KeyCapturingButton>(KeyboardBinding.values().size) private val groupedWidgets: LinkedHashMap<KeyboardBinding.Category, LinkedHashMap<KeyboardBinding, KeyCapturingButton>>
private val disclaimer = MarkupRenderer.render(listOf( private val disclaimer = MarkupRenderer.render(listOf(
FormattedLine("This is a work in progress.", color = "#b22222", centered = true), // FIREBRICK FormattedLine("This is a work in progress.", color = "#b22222", centered = true), // FIREBRICK
FormattedLine(), FormattedLine(),
@ -32,31 +35,55 @@ class KeyBindingsTab(
} }
init { init {
top()
pad(10f) pad(10f)
defaults().pad(5f) defaults().pad(5f)
for (binding in KeyboardBinding.values()) { val collator = UncivGame.Current.settings.getCollatorFromLocale()
if (binding.hidden) continue groupedWidgets = KeyboardBinding.values().asSequence()
keyFields[binding] = KeyCapturingButton(binding.defaultKey) .filterNot { it.hidden }
.groupBy { it.category } // Materializes a Map<Category,List<KeyboardBinding>>
.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() { private fun update() {
clear() clear()
add(disclaimer).colspan(2).center().row() add(disclaimer).center().row()
for (binding in KeyboardBinding.values()) { for ((category, bindings) in groupedWidgets)
if (binding.hidden) continue add(getCategoryWidget(category, bindings)).row()
add(binding.label.toLabel()) }
add(keyFields[binding]).row()
keyFields[binding]!!.current = keyBindings[binding] private fun getCategoryWidget(
category: KeyboardBinding.Category,
bindings: LinkedHashMap<KeyboardBinding, KeyCapturingButton>
) = 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 () { fun save () {
for (binding in KeyboardBinding.values()) { for ((binding, widget) in groupedWidgets.asSequence().flatMap { it.value.entries }) {
if (binding.hidden) continue keyBindings[binding] = widget.current
keyBindings[binding] = keyFields[binding]!!.current
} }
} }

View File

@ -230,36 +230,42 @@ class WorldScreen(
/* /*
* These try to be faithful to default Civ5 key bindings as found in several places online * 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 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(KeyboardBinding.EmpireOverviewTrades) { openEmpireOverview(EmpireOverviewCategories.Trades) } // Economic info
globalShortcuts.add(Input.Keys.F3) { openEmpireOverview(EmpireOverviewCategories.Units) } // Military info globalShortcuts.add(KeyboardBinding.EmpireOverviewUnits) { openEmpireOverview(EmpireOverviewCategories.Units) } // Military info
globalShortcuts.add(Input.Keys.F4) { openEmpireOverview(EmpireOverviewCategories.Politics) } // Diplomacy info globalShortcuts.add(KeyboardBinding.EmpireOverviewPolitics) { openEmpireOverview(EmpireOverviewCategories.Politics) } // Diplomacy info
globalShortcuts.add(Input.Keys.F5) { game.pushScreen(PolicyPickerScreen(selectedCiv, canChangeState)) } // Social Policies Screen globalShortcuts.add(KeyboardBinding.SocialPolicies) { game.pushScreen(PolicyPickerScreen(selectedCiv, canChangeState)) } // Social Policies Screen
globalShortcuts.add(Input.Keys.F6) { game.pushScreen(TechPickerScreen(viewingCiv)) } // Tech Screen globalShortcuts.add(KeyboardBinding.TechnologyTree) { game.pushScreen(TechPickerScreen(viewingCiv)) } // Tech Screen
globalShortcuts.add(Input.Keys.F7) { openEmpireOverview(EmpireOverviewCategories.Notifications) } // Notification Log globalShortcuts.add(KeyboardBinding.EmpireOverviewNotifications) { openEmpireOverview(EmpireOverviewCategories.Notifications) } // Notification Log
globalShortcuts.add(Input.Keys.F8) { game.pushScreen(VictoryScreen(this)) } // Victory Progress globalShortcuts.add(KeyboardBinding.VictoryScreen) { game.pushScreen(VictoryScreen(this)) } // Victory Progress
globalShortcuts.add(Input.Keys.F9) { openEmpireOverview(EmpireOverviewCategories.Stats) } // Demographics globalShortcuts.add(KeyboardBinding.EmpireOverviewStats) { openEmpireOverview(EmpireOverviewCategories.Stats) } // Demographics
globalShortcuts.add(Input.Keys.F10) { openEmpireOverview(EmpireOverviewCategories.Resources) } // originally Strategic View globalShortcuts.add(KeyboardBinding.EmpireOverviewResources) { openEmpireOverview(EmpireOverviewCategories.Resources) } // originally Strategic View
globalShortcuts.add(Input.Keys.F11) { QuickSave.save(gameInfo, this) } // Quick Save globalShortcuts.add(KeyboardBinding.QuickSave) { QuickSave.save(gameInfo, this) } // Quick Save
globalShortcuts.add(Input.Keys.F12) { QuickSave.load(this) } // Quick Load globalShortcuts.add(KeyboardBinding.QuickLoad) { QuickSave.load(this) } // Quick Load
globalShortcuts.add(Input.Keys.HOME) { // Capital City View globalShortcuts.add(KeyboardBinding.ViewCapitalCity) { // Capital City View
val capital = gameInfo.getCurrentPlayerCivilization().getCapital() val capital = gameInfo.getCurrentPlayerCivilization().getCapital()
if (capital != null && !mapHolder.setCenterPosition(capital.location)) if (capital != null && !mapHolder.setCenterPosition(capital.location))
game.pushScreen(CityScreen(capital)) game.pushScreen(CityScreen(capital))
} }
globalShortcuts.add(KeyCharAndCode.ctrl('O')) { // Game Options globalShortcuts.add(KeyboardBinding.Options) { // Game Options
this.openOptionsPopup(onClose = { this.openOptionsPopup(onClose = {
nextTurnButton.update(this) nextTurnButton.update(this)
}) })
} }
globalShortcuts.add(KeyCharAndCode.ctrl('S')) { game.pushScreen(SaveGameScreen(gameInfo)) } // Save globalShortcuts.add(KeyboardBinding.SaveGame) { game.pushScreen(SaveGameScreen(gameInfo)) } // Save
globalShortcuts.add(KeyCharAndCode.ctrl('L')) { game.pushScreen(LoadGameScreen()) } // Load globalShortcuts.add(KeyboardBinding.LoadGame) { game.pushScreen(LoadGameScreen()) } // Load
globalShortcuts.add(KeyCharAndCode.ctrl('Q')) { game.popScreen() } // WorldScreen is the last screen, so this quits 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_ADD) { this.mapHolder.zoomIn() } // '+' Zoom
globalShortcuts.add(Input.Keys.NUMPAD_SUBTRACT) { this.mapHolder.zoomOut() } // '-' 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')){ private fun toggleUI() {
uiEnabled = !uiEnabled uiEnabled = !uiEnabled
topBar.isVisible = uiEnabled topBar.isVisible = uiEnabled
statusButtons.isVisible = uiEnabled statusButtons.isVisible = uiEnabled
@ -273,7 +279,6 @@ class WorldScreen(
if (uiEnabled) battleTable.update() else battleTable.isVisible = false if (uiEnabled) battleTable.update() else battleTable.isVisible = false
fogOfWarButton.isVisible = uiEnabled && viewingCiv.isSpectator() fogOfWarButton.isVisible = uiEnabled && viewingCiv.isSpectator()
} }
}
private fun addKeyboardListener() { private fun addKeyboardListener() {
stage.addListener(KeyboardPanningListener(mapHolder, allowWASD = true)) stage.addListener(KeyboardPanningListener(mapHolder, allowWASD = true))

View File

@ -24,7 +24,7 @@ class MinimapHolder(val mapHolder: WorldMapHolder) : Table() {
backgroundColor = Color.GREEN backgroundColor = Color.GREEN
) )
/** Button, next to the minimap, to toggle the tile yield map overlay. */ /** Button, next to the minimap, to toggle the tile yield map overlay. */
private val yieldImageButton = MapOverlayToggleButton( val yieldImageButton = MapOverlayToggleButton(
ImageGetter.getImage("StatIcons/Food"), 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(). // 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 }, getter = { UncivGame.Current.settings.showTileYields },

View File

@ -11,10 +11,10 @@ import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.models.UnitAction import com.unciv.models.UnitAction
import com.unciv.models.UnitActionType import com.unciv.models.UnitActionType
import com.unciv.models.UpgradeUnitAction import com.unciv.models.UpgradeUnitAction
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.UncivTooltip import com.unciv.ui.components.UncivTooltip
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
import com.unciv.ui.components.extensions.disable 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.keyShortcuts
import com.unciv.ui.components.input.onActivation import com.unciv.ui.components.input.onActivation
import com.unciv.ui.components.extensions.packIfNeeded import com.unciv.ui.components.extensions.packIfNeeded
@ -31,7 +31,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
for (unitAction in UnitActions.getUnitActions(unit)) { for (unitAction in UnitActions.getUnitActions(unit)) {
val button = getUnitActionButton(unit, unitAction) val button = getUnitActionButton(unit, unitAction)
if (unitAction is UpgradeUnitAction && GUI.keyboardAvailable) { 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) val tipActor = BaseUnitDescriptions.getUpgradeTooltipActor(tipTitle, unit.baseUnit, unitAction.unitToUpgradeTo)
button.addListener(UncivTooltip(button, tipActor button.addListener(UncivTooltip(button, tipActor
, offset = Vector2(0f, tipActor.packIfNeeded().height * 0.333f) // scaling fails to express size in parent coordinates , 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 { private fun getUnitActionButton(unit: MapUnit, unitAction: UnitAction): Button {
val icon = unitAction.getIcon() val icon = unitAction.getIcon()
// If peripheral keyboard not detected, hotkeys will not be displayed // 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 fontColor = if (unitAction.isCurrentAction) Color.YELLOW else Color.WHITE
val actionButton = IconTextButton(unitAction.title, icon, fontColor = fontColor) 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) actionButton.color = Color.GREEN.cpy().lerp(Color.WHITE, 0.5f)
if (unitAction !is UpgradeUnitAction) // Does its own toolTip if (unitAction !is UpgradeUnitAction) // Does its own toolTip
actionButton.addTooltip(key) actionButton.addTooltip(KeyboardBindings[binding])
actionButton.pack() actionButton.pack()
if (unitAction.action == null) { if (unitAction.action == null) {
actionButton.disable() actionButton.disable()
@ -72,7 +72,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
worldScreen.switchToNextUnit() worldScreen.switchToNextUnit()
} }
} }
actionButton.keyShortcuts.add(key) actionButton.keyShortcuts.add(binding)
} }
return actionButton return actionButton