From d79c68b2737e1f08f35175b1310b390f06f09a8d Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:11:10 +0200 Subject: [PATCH] More keyboard binding work - World, World Menu Popup, WASD (#9598) * Groundwork for tooltips for user-bindable keys * WorldScreen keyboard revisited * WorldScreen menu popup keyboard support * WorldScreen WASD bindable --- .../com/unciv/ui/components/UncivTooltip.kt | 39 ++++++++--- .../components/input/ActivationExtensions.kt | 13 ++++ .../ui/components/input/KeyboardBinding.kt | 67 +++++++++++++------ .../input/KeyboardPanningListener.kt | 22 ++++-- core/src/com/unciv/ui/popups/Popup.kt | 19 ++++-- .../unciv/ui/popups/options/KeyBindingsTab.kt | 6 +- .../unciv/ui/screens/basescreen/BaseScreen.kt | 4 +- .../worldscreen/TechPolicyDiplomacyButtons.kt | 30 ++++++--- .../ui/screens/worldscreen/WorldScreen.kt | 65 +++++++++++------- .../screens/worldscreen/WorldScreenTopBar.kt | 17 +++-- .../mainmenu/WorldScreenMenuPopup.kt | 25 +++---- .../worldscreen/status/NextTurnButton.kt | 7 +- .../unit/actions/UnitActionsTable.kt | 3 +- 13 files changed, 210 insertions(+), 107 deletions(-) diff --git a/core/src/com/unciv/ui/components/UncivTooltip.kt b/core/src/com/unciv/ui/components/UncivTooltip.kt index e435dd42fd..e396e48bb8 100644 --- a/core/src/com/unciv/ui/components/UncivTooltip.kt +++ b/core/src/com/unciv/ui/components/UncivTooltip.kt @@ -18,6 +18,8 @@ import com.unciv.GUI import com.unciv.models.translations.tr import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.input.KeyCharAndCode +import com.unciv.ui.components.input.KeyboardBinding +import com.unciv.ui.components.input.KeyboardBindings import com.unciv.ui.screens.basescreen.BaseScreen /** @@ -203,13 +205,15 @@ class UncivTooltip ( /** * Add a [Label]-based Tooltip with a rounded-corner background to a [Table] or other [Group]. * + * Removes any previous tooltips (so this can be used to clear tips by passing an empty [text]). * Tip is positioned over top right corner, slightly overshooting the receiver widget, longer tip [text]s will extend to the left. + * Note - since this is mainly used for keyboard tips, this is by default automatically suppressed on devices without keyboard. Use the [always] parameter to override. * * @param text Automatically translated tooltip text * @param size _Vertical_ size of the entire Tooltip including background - * @param always override requirement: presence of physical keyboard * @param targetAlign Point on the [target] widget to align the Tooltip to * @param tipAlign Point on the Tooltip to align with the given point on the [target] + * @param hideIcons Do not automatically add ruleset object icons during translation */ fun Actor.addTooltip( text: String, @@ -219,6 +223,11 @@ class UncivTooltip ( tipAlign: Int = Align.top, hideIcons: Boolean = false ) { + for (tip in listeners.filterIsInstance>()) { + tip.hide(true) + removeListener(tip) + } + if (!(always || GUI.keyboardAvailable) || text.isEmpty()) return val label = text.toLabel(BaseScreen.skinStrings.skinConfig.baseColor, 38, hideIcons = hideIcons) @@ -252,28 +261,42 @@ class UncivTooltip ( } /** - * Add a single Char [Label]-based Tooltip with a rounded-corner background to a [Table] or other [Group]. + * Add a single-Char [Label]-based Tooltip with a rounded-corner background to a [Table] or other [Group]. * + * Note this is automatically suppressed on devices without keyboard. * Tip is positioned over top right corner, slightly overshooting the receiver widget. * * @param size _Vertical_ size of the entire Tooltip including background - * @param always override requirement: presence of physical keyboard */ - fun Actor.addTooltip(char: Char, size: Float = 26f, always: Boolean = false) { - addTooltip((if (char in "Ii") 'i' else char.uppercaseChar()).toString(), size, always) + fun Actor.addTooltip(char: Char, size: Float = 26f) { + addTooltip((if (char in "Ii") 'i' else char.uppercaseChar()).toString(), size) } /** * Add a [Label]-based Tooltip for a keyboard binding with a rounded-corner background to a [Table] or other [Group]. * + * Note this is automatically suppressed on devices without keyboard. * Tip is positioned over top right corner, slightly overshooting the receiver widget. * * @param size _Vertical_ size of the entire Tooltip including background - * @param always override requirement: presence of physical keyboard */ - fun Actor.addTooltip(key: KeyCharAndCode, size: Float = 26f, always: Boolean = false) { + fun Actor.addTooltip(key: KeyCharAndCode, size: Float = 26f) { if (key != KeyCharAndCode.UNKNOWN) - addTooltip(key.toString().tr(), size, always) + addTooltip(key.toString().tr(), size) + } + + /** + * Add a [Label]-based Tooltip for a dynamic keyboard binding with a rounded-corner background to a [Table] or other [Group]. + * + * Note this is automatically suppressed on devices without keyboard. + * Tip is positioned over top right corner, slightly overshooting the receiver widget. + * + * @param size _Vertical_ size of the entire Tooltip including background + */ + fun Actor.addTooltip(binding: KeyboardBinding, size: Float = 26f) { + val key = KeyboardBindings[binding] + if (key != KeyCharAndCode.UNKNOWN) + addTooltip(key.toString().tr(), size) } } } diff --git a/core/src/com/unciv/ui/components/input/ActivationExtensions.kt b/core/src/com/unciv/ui/components/input/ActivationExtensions.kt index 444e064356..c623ad9657 100644 --- a/core/src/com/unciv/ui/components/input/ActivationExtensions.kt +++ b/core/src/com/unciv/ui/components/input/ActivationExtensions.kt @@ -5,6 +5,7 @@ import com.badlogic.gdx.scenes.scene2d.Stage import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener import com.badlogic.gdx.scenes.scene2d.utils.Disableable import com.unciv.models.UncivSound +import com.unciv.ui.components.UncivTooltip.Companion.addTooltip /** Used to stop activation events if this returns `true`. */ internal fun Actor.isActive(): Boolean = isVisible && ((this as? Disableable)?.isDisabled != true) @@ -38,6 +39,18 @@ fun Actor.onActivation( return this } +/** Assigns an activation [handler][action] to your Widget, which reacts to clicks and a [key stroke][binding]. + * A tooltip is attached automatically, if there is a keyboard and the [binding] has a mapping. + * A [sound] will be played (concurrently) on activation unless you specify [UncivSound.Silent]. + * @return `this` to allow chaining + */ +fun Actor.onActivation(sound: UncivSound = UncivSound.Click, binding: KeyboardBinding, action: ActivationAction): Actor { + onActivation(ActivationTypes.Tap, sound, action = action) + keyShortcuts.add(binding) + addTooltip(binding) + return this +} + /** Routes clicks and [keyboard shortcuts][keyShortcuts] to your handler [action]. * A [sound] will be played (concurrently) on activation unless you specify [UncivSound.Silent]. * @return `this` to allow chaining diff --git a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt index 801785ffdc..10491f20e4 100644 --- a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt +++ b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt @@ -18,34 +18,60 @@ enum class KeyboardBinding( None(Category.None, KeyCharAndCode.UNKNOWN), // Worldscreen + Menu(Category.WorldScreen, KeyCharAndCode.TAB), NextTurn(Category.WorldScreen), NextTurnAlternate(Category.WorldScreen, KeyCharAndCode.SPACE), - Civilopedia(Category.WorldScreen, Input.Keys.F1), EmpireOverview(Category.WorldScreen), - 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')), + MusicPlayer(Category.WorldScreen, KeyCharAndCode.ctrl('m')), + + /* + * 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. + * The comments show a description like found in the mentioned sources for comparison. + * @see http://gaming.stackexchange.com/questions/8122/ddg#8125 + */ + Civilopedia(Category.WorldScreen, Input.Keys.F1), // Civilopedia + EmpireOverviewTrades(Category.WorldScreen, Input.Keys.F2), // Economic info + EmpireOverviewUnits(Category.WorldScreen, Input.Keys.F3), // Military info + EmpireOverviewPolitics(Category.WorldScreen, Input.Keys.F4), // Diplomacy info + SocialPolicies(Category.WorldScreen, Input.Keys.F5), // Social Policies Screen + TechnologyTree(Category.WorldScreen, Input.Keys.F6), // Tech Screen + EmpireOverviewNotifications(Category.WorldScreen, Input.Keys.F7), // Notification Log + VictoryScreen(Category.WorldScreen, "Victory status", Input.Keys.F8), // Victory Progress + EmpireOverviewStats(Category.WorldScreen, Input.Keys.F9), // Demographics + EmpireOverviewResources(Category.WorldScreen, Input.Keys.F10), // originally Strategic View + QuickSave(Category.WorldScreen, Input.Keys.F11), // Quick Save + QuickLoad(Category.WorldScreen, Input.Keys.F12), // Quick Load + ViewCapitalCity(Category.WorldScreen, Input.Keys.HOME), // Capital City View + Options(Category.WorldScreen, KeyCharAndCode.ctrl('o')), // Game Options + SaveGame(Category.WorldScreen, KeyCharAndCode.ctrl('s')), // Save + LoadGame(Category.WorldScreen, KeyCharAndCode.ctrl('l')), // Load + ToggleResourceDisplay(Category.WorldScreen, KeyCharAndCode.ctrl('r')), // Show Resources Icons + ToggleYieldDisplay(Category.WorldScreen, KeyCharAndCode.ctrl('y')), // Yield Icons, originally just "Y" + // End of Civ5-inspired bindings + QuitGame(Category.WorldScreen, KeyCharAndCode.ctrl('q')), + NewGame(Category.WorldScreen, KeyCharAndCode.ctrl('n')), + Diplomacy(Category.WorldScreen, KeyCharAndCode.UNKNOWN), + Espionage(Category.WorldScreen, KeyCharAndCode.UNKNOWN), + Undo(Category.WorldScreen, KeyCharAndCode.ctrl('z')), 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), + // Map Panning - separate to get own expander. Map editor use will need to check this - it's arrows only + PanUp(Category.MapPanning, Input.Keys.UP), + PanLeft(Category.MapPanning, Input.Keys.LEFT), + PanDown(Category.MapPanning, Input.Keys.DOWN), + PanRight(Category.MapPanning, Input.Keys.RIGHT), + PanUpAlternate(Category.MapPanning, 'W'), + PanLeftAlternate(Category.MapPanning, 'A'), + PanDownAlternate(Category.MapPanning, 'S'), + PanRightAlternate(Category.MapPanning, 'D'), + // 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. @@ -97,7 +123,10 @@ enum class KeyboardBinding( None, WorldScreen { // Conflict checking within group plus keys assigned to UnitActions are a problem - override fun checkConflictsIn() = sequenceOf(this, UnitActions) + override fun checkConflictsIn() = sequenceOf(this, MapPanning, UnitActions) + }, + MapPanning { + override fun checkConflictsIn() = sequenceOf(this, WorldScreen) }, UnitActions { // Conflict checking within group disabled, but any key assigned on WorldScreen is a problem diff --git a/core/src/com/unciv/ui/components/input/KeyboardPanningListener.kt b/core/src/com/unciv/ui/components/input/KeyboardPanningListener.kt index 2917c5106f..c1b64a6ace 100644 --- a/core/src/com/unciv/ui/components/input/KeyboardPanningListener.kt +++ b/core/src/com/unciv/ui/components/input/KeyboardPanningListener.kt @@ -20,9 +20,19 @@ class KeyboardPanningListener( private val pressedKeys = mutableSetOf() private var infiniteAction: RepeatAction? = null + + private val keycodeUp = KeyboardBindings[KeyboardBinding.PanUp].code + private val keycodeLeft = KeyboardBindings[KeyboardBinding.PanLeft].code + private val keycodeDown = KeyboardBindings[KeyboardBinding.PanDown].code + private val keycodeRight = KeyboardBindings[KeyboardBinding.PanRight].code + private val keycodeUpAlt = KeyboardBindings[KeyboardBinding.PanUpAlternate].code + private val keycodeLeftAlt = KeyboardBindings[KeyboardBinding.PanLeftAlternate].code + private val keycodeDownAlt = KeyboardBindings[KeyboardBinding.PanDownAlternate].code + private val keycodeRightAlt = KeyboardBindings[KeyboardBinding.PanRightAlternate].code + private val allowedKeys = - setOf(Input.Keys.UP, Input.Keys.DOWN, Input.Keys.LEFT, Input.Keys.RIGHT) + ( - if (allowWASD) setOf(Input.Keys.W, Input.Keys.S, Input.Keys.A, Input.Keys.D) + setOf(keycodeUp, keycodeLeft, keycodeDown, keycodeRight) + ( + if (allowWASD) setOf(keycodeUpAlt, keycodeLeftAlt, keycodeDownAlt, keycodeRightAlt) else setOf() ) @@ -70,10 +80,10 @@ class KeyboardPanningListener( var deltaY = 0f for (keycode in pressedKeys) { when (keycode) { - Input.Keys.W, Input.Keys.UP -> deltaY -= 1f - Input.Keys.S, Input.Keys.DOWN -> deltaY += 1f - Input.Keys.A, Input.Keys.LEFT -> deltaX += 1f - Input.Keys.D, Input.Keys.RIGHT -> deltaX -= 1f + keycodeUp, keycodeUpAlt -> deltaY -= 1f + keycodeDown, keycodeDownAlt -> deltaY += 1f + keycodeLeft, keycodeLeftAlt -> deltaX += 1f + keycodeRight, keycodeRightAlt -> deltaX -= 1f } } mapHolder.doKeyOrMousePanning(deltaX, deltaY) diff --git a/core/src/com/unciv/ui/popups/Popup.kt b/core/src/com/unciv/ui/popups/Popup.kt index fb9bf3f6cb..7e8a4d934c 100644 --- a/core/src/com/unciv/ui/popups/Popup.kt +++ b/core/src/com/unciv/ui/popups/Popup.kt @@ -20,16 +20,20 @@ import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.logic.event.EventBus import com.unciv.ui.components.AutoScrollPane -import com.unciv.ui.components.input.KeyCharAndCode -import com.unciv.ui.components.input.KeyboardBinding -import com.unciv.ui.components.input.KeyboardBindings import com.unciv.ui.components.extensions.addSeparator import com.unciv.ui.components.extensions.center import com.unciv.ui.components.extensions.darken -import com.unciv.ui.components.input.keyShortcuts -import com.unciv.ui.components.input.onActivation import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toTextButton +import com.unciv.ui.components.input.KeyCharAndCode +import com.unciv.ui.components.input.KeyboardBinding +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.popups.Popup.Scrollability +import com.unciv.ui.popups.Popup.Scrollability.All +import com.unciv.ui.popups.Popup.Scrollability.None +import com.unciv.ui.popups.Popup.Scrollability.WithoutButtons import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.UncivStage @@ -292,6 +296,11 @@ open class Popup( @Suppress("unused") // Keep the offer to pass an Input.keys value fun addButton(text: String, key: Int, style: TextButtonStyle? = null, action: () -> Unit) = addButton(text, KeyCharAndCode(key), style, action).apply { row() } + fun addButton(text: String, binding: KeyboardBinding, style: TextButtonStyle? = null, action: () -> Unit): Cell { + val button = text.toTextButton(style) + button.onActivation(binding = binding) { action() } + return bottomTable.add(button) + } /** * Adds a [TextButton] that closes the popup, with [BACK][KeyCharAndCode.BACK] already mapped. diff --git a/core/src/com/unciv/ui/popups/options/KeyBindingsTab.kt b/core/src/com/unciv/ui/popups/options/KeyBindingsTab.kt index 034b48a3df..c0ec99c8a7 100644 --- a/core/src/com/unciv/ui/popups/options/KeyBindingsTab.kt +++ b/core/src/com/unciv/ui/popups/options/KeyBindingsTab.kt @@ -7,10 +7,10 @@ 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.KeyCharAndCode -import com.unciv.ui.components.input.KeyboardBinding import com.unciv.ui.components.TabbedPager import com.unciv.ui.components.extensions.toLabel +import com.unciv.ui.components.input.KeyCharAndCode +import com.unciv.ui.components.input.KeyboardBinding import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen import com.unciv.ui.screens.civilopediascreen.FormattedLine @@ -109,7 +109,7 @@ class KeyBindingsTab( } } - fun save () { + fun save() { for ((binding, widget) in groupedWidgets.asSequence().flatMap { it.value.entries }) { keyBindings[binding] = widget.current } diff --git a/core/src/com/unciv/ui/screens/basescreen/BaseScreen.kt b/core/src/com/unciv/ui/screens/basescreen/BaseScreen.kt index bed46f2ad8..e622eaaf4e 100644 --- a/core/src/com/unciv/ui/screens/basescreen/BaseScreen.kt +++ b/core/src/com/unciv/ui/screens/basescreen/BaseScreen.kt @@ -20,9 +20,9 @@ import com.unciv.models.TutorialTrigger import com.unciv.models.skins.SkinStrings import com.unciv.ui.components.Fonts import com.unciv.ui.components.extensions.isNarrowerThan4to3 +import com.unciv.ui.components.input.DispatcherVetoer import com.unciv.ui.components.input.KeyShortcutDispatcher import com.unciv.ui.components.input.KeyShortcutDispatcherVeto -import com.unciv.ui.components.input.DispatcherVetoer import com.unciv.ui.components.input.installShortcutDispatcher import com.unciv.ui.components.input.keyShortcuts import com.unciv.ui.crashhandling.CrashScreen @@ -174,7 +174,7 @@ abstract class BaseScreen : Screen { /** @return `true` if the screen is narrower than 4:3 landscape */ fun isNarrowerThan4to3() = stage.isNarrowerThan4to3() - fun openOptionsPopup(startingPage: Int = OptionsPopup.defaultPage, onClose: () -> Unit = {}) { + open fun openOptionsPopup(startingPage: Int = OptionsPopup.defaultPage, onClose: () -> Unit = {}) { OptionsPopup(this, startingPage, onClose).open(force = true) } } diff --git a/core/src/com/unciv/ui/screens/worldscreen/TechPolicyDiplomacyButtons.kt b/core/src/com/unciv/ui/screens/worldscreen/TechPolicyDiplomacyButtons.kt index d2144c8528..5f68501895 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/TechPolicyDiplomacyButtons.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/TechPolicyDiplomacyButtons.kt @@ -9,8 +9,10 @@ import com.unciv.models.UncivSound import com.unciv.models.translations.tr import com.unciv.ui.components.Fonts import com.unciv.ui.components.extensions.colorFromRGB -import com.unciv.ui.components.input.onClick +import com.unciv.ui.components.extensions.disable import com.unciv.ui.components.extensions.toLabel +import com.unciv.ui.components.input.KeyboardBinding +import com.unciv.ui.components.input.onActivation import com.unciv.ui.images.ImageGetter import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.diplomacyscreen.DiplomacyScreen @@ -51,31 +53,28 @@ class TechPolicyDiplomacyButtons(val worldScreen: WorldScreen) : Table(BaseScree pickTechButton.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/PickTechButton", BaseScreen.skinStrings.roundedEdgeRectangleShape, colorFromRGB(7, 46, 43)) pickTechButton.defaults().pad(20f) pickTechButton.add(pickTechLabel) - techButtonHolder.onClick(UncivSound.Paper) { + techButtonHolder.onActivation(UncivSound.Paper, KeyboardBinding.TechnologyTree) { game.pushScreen(TechPickerScreen(viewingCiv)) } undoButton.add(ImageGetter.getImage("OtherIcons/Resume")).size(30f).pad(15f) - undoButton.onClick { - Concurrency.run { - // Most of the time we won't load this, so we only set transients once we see it's relevant - worldScreen.preActionGameInfo.setTransients() - game.loadGame(worldScreen.preActionGameInfo) - } + undoButton.onActivation(binding = KeyboardBinding.Undo) { + handleUndo() } policyScreenButton.add(ImageGetter.getImage("PolicyIcons/Constitution")).size(30f).pad(15f) - policyButtonHolder.onClick { + policyButtonHolder.onActivation(binding = KeyboardBinding.SocialPolicies) { game.pushScreen(PolicyPickerScreen(worldScreen.selectedCiv, worldScreen.canChangeState)) } diplomacyButton.add(ImageGetter.getImage("OtherIcons/DiplomacyW")).size(30f).pad(15f) - diplomacyButtonHolder.onClick { + diplomacyButtonHolder.onActivation(binding = KeyboardBinding.Diplomacy) { game.pushScreen(DiplomacyScreen(viewingCiv)) } + if (game.gameInfo!!.isEspionageEnabled()) { espionageButton.add(ImageGetter.getImage("OtherIcons/Spy_White")).size(30f).pad(15f) - espionageButtonHolder.onClick { + espionageButtonHolder.onActivation(binding = KeyboardBinding.Espionage) { game.pushScreen(EspionageOverviewScreen(viewingCiv)) } } @@ -162,4 +161,13 @@ class TechPolicyDiplomacyButtons(val worldScreen: WorldScreen) : Table(BaseScree espionageButtonHolder.actor = espionageButton } } + + private fun handleUndo() { + undoButton.disable() + Concurrency.run { + // Most of the time we won't load this, so we only set transients once we see it's relevant + worldScreen.preActionGameInfo.setTransients() + game.loadGame(worldScreen.preActionGameInfo) + } + } } diff --git a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt index c0e904d566..d887111533 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt @@ -21,19 +21,20 @@ import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached import com.unciv.logic.multiplayer.storage.MultiplayerAuthException import com.unciv.logic.trade.TradeEvaluation import com.unciv.models.TutorialTrigger +import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.components.input.KeyCharAndCode -import com.unciv.ui.components.input.KeyboardBinding -import com.unciv.ui.components.input.KeyboardPanningListener import com.unciv.ui.components.extensions.centerX import com.unciv.ui.components.extensions.darken import com.unciv.ui.components.extensions.isEnabled -import com.unciv.ui.components.input.onClick import com.unciv.ui.components.extensions.setFontSize import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toTextButton +import com.unciv.ui.components.input.KeyCharAndCode import com.unciv.ui.components.input.KeyShortcutDispatcherVeto +import com.unciv.ui.components.input.KeyboardBinding +import com.unciv.ui.components.input.KeyboardPanningListener +import com.unciv.ui.components.input.onClick import com.unciv.ui.images.ImageGetter import com.unciv.ui.popups.AuthPopup import com.unciv.ui.popups.Popup @@ -43,18 +44,18 @@ import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.cityscreen.CityScreen import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen import com.unciv.ui.screens.mainmenuscreen.MainMenuScreen +import com.unciv.ui.screens.newgamescreen.NewGameScreen import com.unciv.ui.screens.overviewscreen.EmpireOverviewCategories import com.unciv.ui.screens.overviewscreen.EmpireOverviewScreen import com.unciv.ui.screens.pickerscreens.DiplomaticVoteResultScreen import com.unciv.ui.screens.pickerscreens.GreatPersonPickerScreen -import com.unciv.ui.screens.pickerscreens.PolicyPickerScreen -import com.unciv.ui.screens.pickerscreens.TechPickerScreen import com.unciv.ui.screens.savescreens.LoadGameScreen import com.unciv.ui.screens.savescreens.QuickSave import com.unciv.ui.screens.savescreens.SaveGameScreen import com.unciv.ui.screens.victoryscreen.VictoryScreen import com.unciv.ui.screens.worldscreen.bottombar.BattleTable import com.unciv.ui.screens.worldscreen.bottombar.TileInfoTable +import com.unciv.ui.screens.worldscreen.mainmenu.WorldScreenMusicPopup import com.unciv.ui.screens.worldscreen.minimap.MinimapHolder import com.unciv.ui.screens.worldscreen.status.MultiplayerStatusButton import com.unciv.ui.screens.worldscreen.status.NextTurnButton @@ -63,10 +64,10 @@ import com.unciv.ui.screens.worldscreen.status.StatusButtons import com.unciv.ui.screens.worldscreen.unit.UnitTable import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsTable import com.unciv.utils.Concurrency +import com.unciv.utils.debug import com.unciv.utils.launchOnGLThread import com.unciv.utils.launchOnThreadPool import com.unciv.utils.withGLContext -import com.unciv.utils.debug import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope @@ -224,27 +225,27 @@ class WorldScreen( game.pushScreen(EmpireOverviewScreen(selectedCiv, category)) } + fun openNewGameScreen() { + val newGameSetupInfo = GameSetupInfo(gameInfo) + newGameSetupInfo.mapParameters.reseed() + val newGameScreen = NewGameScreen(newGameSetupInfo) + game.pushScreen(newGameScreen) + } + private fun addKeyboardPresses() { // Space and N are assigned in NextTurnButton constructor + // Functions that have a big button are assigned there (WorldScreenTopBar, TechPolicyDiplomacyButtons..) globalShortcuts.add(KeyboardBinding.Civilopedia) { game.pushScreen(CivilopediaScreen(gameInfo.ruleset)) } - globalShortcuts.add(KeyboardBinding.EmpireOverview) { openEmpireOverview() } // Empire overview last used page - /* - * 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 - */ - 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 + globalShortcuts.add(KeyboardBinding.EmpireOverviewTrades) { openEmpireOverview(EmpireOverviewCategories.Trades) } + globalShortcuts.add(KeyboardBinding.EmpireOverviewUnits) { openEmpireOverview(EmpireOverviewCategories.Units) } + globalShortcuts.add(KeyboardBinding.EmpireOverviewPolitics) { openEmpireOverview(EmpireOverviewCategories.Politics) } + globalShortcuts.add(KeyboardBinding.EmpireOverviewNotifications) { openEmpireOverview(EmpireOverviewCategories.Notifications) } + globalShortcuts.add(KeyboardBinding.VictoryScreen) { game.pushScreen(VictoryScreen(this)) } + globalShortcuts.add(KeyboardBinding.EmpireOverviewStats) { openEmpireOverview(EmpireOverviewCategories.Stats) } + globalShortcuts.add(KeyboardBinding.EmpireOverviewResources) { openEmpireOverview(EmpireOverviewCategories.Resources) } + globalShortcuts.add(KeyboardBinding.QuickSave) { QuickSave.save(gameInfo, this) } + globalShortcuts.add(KeyboardBinding.QuickLoad) { QuickSave.load(this) } + globalShortcuts.add(KeyboardBinding.ViewCapitalCity) { val capital = gameInfo.getCurrentPlayerCivilization().getCapital() if (capital != null && !mapHolder.setCenterPosition(capital.location)) game.pushScreen(CityScreen(capital)) @@ -257,6 +258,10 @@ class WorldScreen( 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(KeyboardBinding.NewGame) { openNewGameScreen() } + globalShortcuts.add(KeyboardBinding.MusicPlayer) { + WorldScreenMusicPopup(this).open(force = true) + } 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() } @@ -266,6 +271,16 @@ class WorldScreen( globalShortcuts.add(KeyboardBinding.ToggleMovementDisplay) { minimapWrapper.movementsImageButton.toggle() } } + // Handle disabling and re-enabling WASD listener while Options are open + override fun openOptionsPopup(startingPage: Int, onClose: () -> Unit) { + val oldListener = stage.root.listeners.filterIsInstance().firstOrNull() + if (oldListener != null) stage.removeListener(oldListener) + super.openOptionsPopup(startingPage) { + addKeyboardListener() + onClose() + } + } + private fun toggleUI() { uiEnabled = !uiEnabled topBar.isVisible = uiEnabled diff --git a/core/src/com/unciv/ui/screens/worldscreen/WorldScreenTopBar.kt b/core/src/com/unciv/ui/screens/worldscreen/WorldScreenTopBar.kt index b2ca7d6aaa..bd16b0040e 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/WorldScreenTopBar.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/WorldScreenTopBar.kt @@ -16,18 +16,18 @@ import com.unciv.models.stats.Stats import com.unciv.models.translations.tr import com.unciv.ui.components.Fonts import com.unciv.ui.components.MayaCalendar -import com.unciv.ui.components.UncivTooltip.Companion.addTooltip import com.unciv.ui.components.YearTextUtil import com.unciv.ui.components.extensions.colorFromRGB import com.unciv.ui.components.extensions.darken -import com.unciv.ui.components.input.onClick import com.unciv.ui.components.extensions.setFontColor import com.unciv.ui.components.extensions.setFontSize import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toStringSigned import com.unciv.ui.components.extensions.toTextButton +import com.unciv.ui.components.input.KeyboardBinding +import com.unciv.ui.components.input.onActivation +import com.unciv.ui.components.input.onClick import com.unciv.ui.images.ImageGetter -import com.unciv.ui.popups.popups import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.civilopediascreen.CivilopediaCategories import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen @@ -180,8 +180,9 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() { } val overviewButton = "Overview".toTextButton() - overviewButton.addTooltip('e') - overviewButton.onClick { worldScreen.openEmpireOverview() } + overviewButton.onActivation(binding = KeyboardBinding.EmpireOverview) { + worldScreen.openEmpireOverview() + } unitSupplyCell = add() add(overviewButton).pad(10f) @@ -209,10 +210,8 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() { defaults().pad(10f) menuButton.color = Color.WHITE - menuButton.onClick { - val worldScreenMenuPopup = worldScreen.popups.firstOrNull { it is WorldScreenMenuPopup } - if (worldScreenMenuPopup != null) worldScreenMenuPopup.close() - else WorldScreenMenuPopup(worldScreen).open(force = true) + menuButton.onActivation(binding = KeyboardBinding.Menu) { + WorldScreenMenuPopup(worldScreen).open(force = true) } selectedCivLabel.setFontSize(25) diff --git a/core/src/com/unciv/ui/screens/worldscreen/mainmenu/WorldScreenMenuPopup.kt b/core/src/com/unciv/ui/screens/worldscreen/mainmenu/WorldScreenMenuPopup.kt index 7b6b6ffda0..5eead8c1f1 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/mainmenu/WorldScreenMenuPopup.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/mainmenu/WorldScreenMenuPopup.kt @@ -1,9 +1,8 @@ package com.unciv.ui.screens.worldscreen.mainmenu -import com.unciv.models.metadata.GameSetupInfo +import com.unciv.ui.components.input.KeyboardBinding import com.unciv.ui.popups.Popup import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen -import com.unciv.ui.screens.newgamescreen.NewGameScreen import com.unciv.ui.screens.savescreens.LoadGameScreen import com.unciv.ui.screens.savescreens.SaveGameScreen import com.unciv.ui.screens.victoryscreen.VictoryScreen @@ -16,32 +15,27 @@ class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen, sc addButton("Main menu") { worldScreen.game.goToMainMenu() }.row() - addButton("Civilopedia") { + addButton("Civilopedia", KeyboardBinding.Civilopedia) { close() worldScreen.game.pushScreen(CivilopediaScreen(worldScreen.gameInfo.ruleset)) }.row() - addButton("Save game") { + addButton("Save game", KeyboardBinding.SaveGame) { close() worldScreen.game.pushScreen(SaveGameScreen(worldScreen.gameInfo)) }.row() - addButton("Load game") { + addButton("Load game", KeyboardBinding.LoadGame) { close() worldScreen.game.pushScreen(LoadGameScreen()) }.row() - - addButton("Start new game") { + addButton("Start new game", KeyboardBinding.NewGame) { close() - val newGameSetupInfo = GameSetupInfo(worldScreen.gameInfo) - newGameSetupInfo.mapParameters.reseed() - val newGameScreen = NewGameScreen(newGameSetupInfo) - worldScreen.game.pushScreen(newGameScreen) + worldScreen.openNewGameScreen() }.row() - - addButton("Victory status") { + addButton("Victory status", KeyboardBinding.VictoryScreen) { close() worldScreen.game.pushScreen(VictoryScreen(worldScreen)) }.row() - addButton("Options") { + addButton("Options", KeyboardBinding.Options) { close() worldScreen.openOptionsPopup() }.row() @@ -49,10 +43,11 @@ class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen, sc close() WorldScreenCommunityPopup(worldScreen).open(force = true) }.row() - addButton("Music") { + addButton("Music", KeyboardBinding.MusicPlayer) { close() WorldScreenMusicPopup(worldScreen).open(force = true) }.row() + addCloseButton() pack() } diff --git a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt index 33b7dcf34b..9ddc97343f 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt @@ -5,13 +5,14 @@ import com.unciv.Constants import com.unciv.logic.civilization.managers.ReligionState import com.unciv.models.ruleset.BeliefType import com.unciv.models.translations.tr -import com.unciv.ui.components.input.KeyboardBinding +import com.unciv.ui.components.UncivTooltip.Companion.addTooltip import com.unciv.ui.components.extensions.disable import com.unciv.ui.components.extensions.enable import com.unciv.ui.components.extensions.isEnabled +import com.unciv.ui.components.extensions.setSize +import com.unciv.ui.components.input.KeyboardBinding import com.unciv.ui.components.input.keyShortcuts import com.unciv.ui.components.input.onActivation -import com.unciv.ui.components.extensions.setSize import com.unciv.ui.images.IconTextButton import com.unciv.ui.images.ImageGetter import com.unciv.ui.popups.ConfirmPopup @@ -45,6 +46,8 @@ class NextTurnButton : IconTextButton("", null, 30) { isEnabled = !worldScreen.hasOpenPopups() && worldScreen.isPlayersTurn && !worldScreen.waitingForAutosave && !worldScreen.isNextTurnUpdateRunning() + + if (isEnabled) addTooltip(KeyboardBinding.NextTurn) else addTooltip("") } internal fun updateButton(nextTurnAction: NextTurnAction) { label.setText(nextTurnAction.text.tr()) 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 44a50ce5d0..2147bda19e 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 @@ -12,7 +12,6 @@ import com.unciv.models.UnitActionType import com.unciv.models.UpgradeUnitAction 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.input.onRightClick @@ -53,7 +52,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() { if (unitAction.type == UnitActionType.Promote && unitAction.action != null) actionButton.color = Color.GREEN.cpy().lerp(Color.WHITE, 0.5f) - actionButton.addTooltip(KeyboardBindings[binding]) + actionButton.addTooltip(binding) actionButton.pack() if (unitAction.action == null) {