diff --git a/core/src/com/unciv/ui/components/KeyboardPanningListener.kt b/core/src/com/unciv/ui/components/KeyboardPanningListener.kt new file mode 100644 index 0000000000..fdea302947 --- /dev/null +++ b/core/src/com/unciv/ui/components/KeyboardPanningListener.kt @@ -0,0 +1,74 @@ +package com.unciv.ui.components + +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.Input +import com.badlogic.gdx.scenes.scene2d.InputEvent +import com.badlogic.gdx.scenes.scene2d.InputListener +import com.badlogic.gdx.scenes.scene2d.actions.Actions +import com.badlogic.gdx.scenes.scene2d.actions.RepeatAction +import com.badlogic.gdx.scenes.scene2d.ui.TextField + +class KeyboardPanningListener( + private val mapHolder: ZoomableScrollPane, + allowWASD: Boolean, +) : InputListener() { + private val pressedKeys = mutableSetOf() + private var infiniteAction: RepeatAction? = null + private val amountToMove = 6 / mapHolder.scaleX + 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) + else setOf() + ) + + override fun keyDown(event: InputEvent, keycode: Int): Boolean { + if (event.target is TextField) return false + if (keycode !in allowedKeys) return false + // Without the following Ctrl-S would leave WASD map scrolling stuck + // Might be obsolete with keyboard shortcut refactoring + if (Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT)) return false + if (Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT)) return false + pressedKeys.add(keycode) + startLoop() + return true + } + + override fun keyUp(event: InputEvent?, keycode: Int): Boolean { + if (keycode !in allowedKeys) return false + pressedKeys.remove(keycode) + if (pressedKeys.isEmpty()) stopLoop() + return true + } + + private fun startLoop() { + if (infiniteAction != null) return + // create a copy of the action, because removeAction() will destroy this instance + infiniteAction = Actions.forever( + Actions.delay( + 0.01f, + Actions.run { whileKeyPressedLoop() }) + ) + mapHolder.addAction(infiniteAction) + } + + private fun stopLoop() { + if (infiniteAction == null) return + // stop the loop otherwise it keeps going even after removal + infiniteAction?.finish() + // remove and nil the action + mapHolder.removeAction(infiniteAction) + infiniteAction = null + } + + private fun whileKeyPressedLoop() { + for (keycode in pressedKeys) { + when (keycode) { + Input.Keys.W, Input.Keys.UP -> mapHolder.scrollY = mapHolder.restrictY(-amountToMove) + Input.Keys.S, Input.Keys.DOWN -> mapHolder.scrollY = mapHolder.restrictY(amountToMove) + Input.Keys.A, Input.Keys.LEFT -> mapHolder.scrollX = mapHolder.restrictX(amountToMove) + Input.Keys.D, Input.Keys.RIGHT -> mapHolder.scrollX = mapHolder.restrictX(-amountToMove) + } + } + mapHolder.updateVisualScroll() + } +} diff --git a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt index 3e112e21b2..001b745765 100644 --- a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt +++ b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt @@ -22,9 +22,7 @@ import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache import com.unciv.ui.components.AutoScrollPane -import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.components.KeyCharAndCode -import com.unciv.ui.screens.basescreen.RecreateOnResize import com.unciv.ui.components.UncivTooltip.Companion.addTooltip import com.unciv.ui.components.extensions.center import com.unciv.ui.components.extensions.keyShortcuts @@ -33,21 +31,24 @@ import com.unciv.ui.components.extensions.surroundWithCircle import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.tilegroups.TileGroupMap import com.unciv.ui.images.ImageGetter -import com.unciv.ui.screens.mapeditorscreen.MapEditorScreen -import com.unciv.ui.screens.multiplayerscreens.MultiplayerScreen import com.unciv.ui.popups.Popup import com.unciv.ui.popups.ToastPopup import com.unciv.ui.popups.closeAllPopups import com.unciv.ui.popups.hasOpenPopups import com.unciv.ui.popups.popups -import com.unciv.ui.screens.savescreens.LoadGameScreen -import com.unciv.ui.screens.savescreens.QuickSave +import com.unciv.ui.screens.basescreen.BaseScreen +import com.unciv.ui.screens.basescreen.RecreateOnResize import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen +import com.unciv.ui.screens.mainmenuscreen.EasterEggRulesets.modifyForEasterEgg +import com.unciv.ui.screens.mapeditorscreen.EditorMapHolder +import com.unciv.ui.screens.mapeditorscreen.MapEditorScreen +import com.unciv.ui.screens.multiplayerscreens.MultiplayerScreen import com.unciv.ui.screens.newgamescreen.NewGameScreen import com.unciv.ui.screens.pickerscreens.ModManagementScreen -import com.unciv.ui.screens.worldscreen.mainmenu.WorldScreenMenuPopup -import com.unciv.ui.screens.mainmenuscreen.EasterEggRulesets.modifyForEasterEgg +import com.unciv.ui.screens.savescreens.LoadGameScreen +import com.unciv.ui.screens.savescreens.QuickSave import com.unciv.ui.screens.worldscreen.WorldScreen +import com.unciv.ui.screens.worldscreen.mainmenu.WorldScreenMenuPopup import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.launchOnGLThread import kotlin.math.min @@ -132,7 +133,7 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { launchOnGLThread { // for GL context ImageGetter.setNewRuleset(mapRuleset) - val mapHolder = com.unciv.ui.screens.mapeditorscreen.EditorMapHolder( + val mapHolder = EditorMapHolder( this@MainMenuScreen, newMap ) {} diff --git a/core/src/com/unciv/ui/screens/mapeditorscreen/MapEditorScreen.kt b/core/src/com/unciv/ui/screens/mapeditorscreen/MapEditorScreen.kt index ed499493b6..940133914d 100644 --- a/core/src/com/unciv/ui/screens/mapeditorscreen/MapEditorScreen.kt +++ b/core/src/com/unciv/ui/screens/mapeditorscreen/MapEditorScreen.kt @@ -18,6 +18,7 @@ import com.unciv.ui.popups.ConfirmPopup import com.unciv.ui.components.tilegroups.TileGroup import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.components.KeyCharAndCode +import com.unciv.ui.components.KeyboardPanningListener import com.unciv.ui.screens.basescreen.RecreateOnResize import com.unciv.ui.screens.worldscreen.ZoomButtonPair @@ -64,7 +65,7 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize { var tileMatchFuzziness = MapEditorOptionsTab.TileMatchFuzziness.CompleteMatch // UI - var mapHolder: com.unciv.ui.screens.mapeditorscreen.EditorMapHolder + var mapHolder: EditorMapHolder val tabs: MapEditorMainTabs var tileClickHandler: ((tile: Tile)->Unit)? = null private var zoomController: ZoomButtonPair? = null @@ -117,7 +118,7 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize { fun getToolsWidth() = stage.width * 0.4f - private fun newMapHolder(): com.unciv.ui.screens.mapeditorscreen.EditorMapHolder { + private fun newMapHolder(): EditorMapHolder { ImageGetter.setNewRuleset(ruleset) // setNewRuleset is missing some graphics - those "EmojiIcons"&co already rendered as font characters // so to get the "Water" vs "Gold" icons when switching between Deciv and Vanilla to render properly, @@ -130,9 +131,12 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize { tileMap.setStartingLocationsTransients() UncivGame.Current.translations.translationActiveMods = ruleset.mods - val result = com.unciv.ui.screens.mapeditorscreen.EditorMapHolder(this, tileMap) { + val result = EditorMapHolder(this, tileMap) { tileClickHandler?.invoke(it) } + for (oldPanningListener in stage.root.listeners.filterIsInstance()) + stage.removeListener(oldPanningListener) // otherwise they accumulate + stage.addListener(KeyboardPanningListener(result, allowWASD = false)) stage.root.addActorAt(0, result) stage.scrollFocus = result diff --git a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt index eff9f9449d..a048016956 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt @@ -4,13 +4,8 @@ import com.badlogic.gdx.Application import com.badlogic.gdx.Gdx import com.badlogic.gdx.Input import com.badlogic.gdx.math.Vector2 -import com.badlogic.gdx.scenes.scene2d.InputEvent -import com.badlogic.gdx.scenes.scene2d.InputListener -import com.badlogic.gdx.scenes.scene2d.actions.Actions -import com.badlogic.gdx.scenes.scene2d.actions.RepeatAction import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton -import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.UncivGame @@ -27,8 +22,9 @@ import com.unciv.logic.trade.TradeEvaluation import com.unciv.models.TutorialTrigger import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.ui.components.KeyboardBinding import com.unciv.ui.components.KeyCharAndCode +import com.unciv.ui.components.KeyboardBinding +import com.unciv.ui.components.KeyboardPanningListener import com.unciv.ui.components.extensions.centerX import com.unciv.ui.components.extensions.darken import com.unciv.ui.components.extensions.isEnabled @@ -277,67 +273,7 @@ class WorldScreen( } private fun addKeyboardListener() { - stage.addListener( - object : InputListener() { - private val pressedKeys = mutableSetOf() - private var infiniteAction: RepeatAction? = null - private val amountToMove = 6 / mapHolder.scaleX - private val ALLOWED_KEYS = setOf(Input.Keys.W, Input.Keys.S, Input.Keys.A, Input.Keys.D, - Input.Keys.UP, Input.Keys.DOWN, Input.Keys.LEFT, Input.Keys.RIGHT) - - - override fun keyDown(event: InputEvent, keycode: Int): Boolean { - if (event.target !is TextField) { - if (keycode !in ALLOWED_KEYS) return false - // Without the following Ctrl-S would leave WASD map scrolling stuck - // Might be obsolete with keyboard shortcut refactoring - if (Gdx.input.isKeyPressed(Input.Keys.CONTROL_LEFT) || Gdx.input.isKeyPressed( - Input.Keys.CONTROL_RIGHT - ) - ) return false - - pressedKeys.add(keycode) - if (infiniteAction == null) { - // create a copy of the action, because removeAction() will destroy this instance - infiniteAction = Actions.forever( - Actions.delay( - 0.01f, - Actions.run { whileKeyPressedLoop() }) - ) - mapHolder.addAction(infiniteAction) - } - } - return true - } - - fun whileKeyPressedLoop() { - for (keycode in pressedKeys) { - when (keycode) { - Input.Keys.W, Input.Keys.UP -> mapHolder.scrollY = mapHolder.restrictY(-amountToMove) - Input.Keys.S, Input.Keys.DOWN -> mapHolder.scrollY = mapHolder.restrictY(amountToMove) - Input.Keys.A, Input.Keys.LEFT -> mapHolder.scrollX = mapHolder.restrictX(amountToMove) - Input.Keys.D, Input.Keys.RIGHT -> mapHolder.scrollX = mapHolder.restrictX(-amountToMove) - } - } - mapHolder.updateVisualScroll() - } - - override fun keyUp(event: InputEvent?, keycode: Int): Boolean { - if (keycode !in ALLOWED_KEYS) return false - - pressedKeys.remove(keycode) - if (infiniteAction != null && pressedKeys.isEmpty()) { - // stop the loop otherwise it keeps going even after removal - infiniteAction?.finish() - // remove and nil the action - mapHolder.removeAction(infiniteAction) - infiniteAction = null - } - return true - } - } - ) - + stage.addListener(KeyboardPanningListener(mapHolder, allowWASD = true)) } private suspend fun loadLatestMultiplayerState(): Unit = coroutineScope {