diff --git a/core/src/com/unciv/ui/components/UncivTooltip.kt b/core/src/com/unciv/ui/components/UncivTooltip.kt index e396e48bb8..7b21297852 100644 --- a/core/src/com/unciv/ui/components/UncivTooltip.kt +++ b/core/src/com/unciv/ui/components/UncivTooltip.kt @@ -35,16 +35,19 @@ import com.unciv.ui.screens.basescreen.BaseScreen * @param animate Use show/hide animations * @param forceContentSize Force virtual [content] width/height for alignment calculation * - because Gdx auto layout reports wrong dimensions on scaled actors. + * @param contentRefresher Called just before showing the [content], to give the builder a chance to do last-minute updates. + * Return value is used as new `forceContentSize`. */ // region fields class UncivTooltip ( - val target: Actor, - val content: T, - val targetAlign: Int = Align.topRight, - val tipAlign: Int = Align.topRight, - val offset: Vector2 = Vector2.Zero, - val animate: Boolean = true, + private val target: Actor, + private val content: T, + private val targetAlign: Int = Align.topRight, + private val tipAlign: Int = Align.topRight, + private val offset: Vector2 = Vector2.Zero, + private val animate: Boolean = true, forceContentSize: Vector2? = null, + private val contentRefresher: (() -> Vector2?)? = null ) : InputListener() { private val container: Container = Container(content) @@ -57,8 +60,8 @@ class UncivTooltip ( // touching buttons (exit fires, sometimes very late, with "to" actor being the label of the button) private var touchDownSeen = false - private val contentWidth: Float - private val contentHeight: Float + private var contentWidth: Float + private var contentHeight: Float init { content.touchable = Touchable.disabled @@ -82,6 +85,13 @@ class UncivTooltip ( container.remove() } + if (contentRefresher != null) { + val forceContentSize = contentRefresher.invoke() + container.pack() + contentWidth = forceContentSize?.x ?: content.width + contentHeight = forceContentSize?.y ?: content.height + } + val pos = target.localToStageCoordinates(target.getEdgePoint(targetAlign)).add(offset) container.run { val originX = getOriginX(contentWidth, tipAlign) @@ -200,7 +210,7 @@ class UncivTooltip ( companion object { /** Duration of the fade/zoom-in/out animations */ - const val tipAnimationDuration = 0.2f + private const val tipAnimationDuration = 0.2f /** * Add a [Label]-based Tooltip with a rounded-corner background to a [Table] or other [Group]. @@ -221,7 +231,8 @@ class UncivTooltip ( always: Boolean = false, targetAlign: Int = Align.topRight, tipAlign: Int = Align.top, - hideIcons: Boolean = false + hideIcons: Boolean = false, + dynamicTextProvider: (() -> String)? = null ) { for (tip in listeners.filterIsInstance>()) { tip.hide(true) @@ -241,22 +252,37 @@ class UncivTooltip ( val horizontalPad = if (text.length > 1) 10f else 6f background.setPadding(4f+skewPadDescenders, horizontalPad, 8f-skewPadDescenders, horizontalPad) - val widthHeightRatio: Float - val multiRowSize = size * (1 + text.count { it == '\n' }) val labelWithBackground = Container(label).apply { setBackground(background) - pack() - widthHeightRatio = width / height isTransform = true // otherwise setScale is ignored - setScale(multiRowSize / height) } + fun getMultiRowSize(text: String) = size * (1 + text.count { it == '\n' }) + fun scaleContainerAndGetSize(text: String): Vector2 { + val multiRowSize = getMultiRowSize(text) + val widthHeightRatio = labelWithBackground.run { + pack() + setScale(1f) + val ratio = width / height + setScale(multiRowSize / height) + ratio + } + return Vector2(multiRowSize * widthHeightRatio, multiRowSize) + } + + val contentRefresher: (() -> Vector2)? = if (dynamicTextProvider == null) null else { { + val newText = dynamicTextProvider() + label.setText(newText) + scaleContainerAndGetSize(newText) + } } + addListener(UncivTooltip(this, labelWithBackground, - forceContentSize = Vector2(multiRowSize * widthHeightRatio, multiRowSize), - offset = Vector2(-multiRowSize/4, size/4), + forceContentSize = scaleContainerAndGetSize(text), + offset = Vector2(-getMultiRowSize(text)/4, size/4), targetAlign = targetAlign, - tipAlign = tipAlign + tipAlign = tipAlign, + contentRefresher = contentRefresher )) } @@ -287,6 +313,7 @@ class UncivTooltip ( /** * Add a [Label]-based Tooltip for a dynamic keyboard binding with a rounded-corner background to a [Table] or other [Group]. + * Supports dynamic display of changes to the binding while the tip is attached to an actor, fetched the moment it is shown. * * Note this is automatically suppressed on devices without keyboard. * Tip is positioned over top right corner, slightly overshooting the receiver widget. @@ -294,9 +321,10 @@ class UncivTooltip ( * @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) + fun getText() = KeyboardBindings[binding].toString().tr() + addTooltip(getText(), size) { + getText() + } } } } diff --git a/core/src/com/unciv/ui/components/input/KeyCharAndCode.kt b/core/src/com/unciv/ui/components/input/KeyCharAndCode.kt index dcbd6bc12f..40ea67e6dd 100644 --- a/core/src/com/unciv/ui/components/input/KeyCharAndCode.kt +++ b/core/src/com/unciv/ui/components/input/KeyCharAndCode.kt @@ -34,6 +34,7 @@ data class KeyCharAndCode(val char: Char, val code: Int) { //** debug helper, but also used for tooltips */ override fun toString(): String { return when { + this == UNKNOWN -> "" // Makes tooltip code simpler. Sorry, debuggers. char == Char.MIN_VALUE -> GdxKeyCodeFixes.toString(code) this == ESC -> "ESC" char < ' ' -> "Ctrl-" + (char.toCode() + 64).makeChar() diff --git a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt index 220812d5b0..9472fd00e5 100644 --- a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt +++ b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt @@ -17,6 +17,16 @@ enum class KeyboardBinding( /** Used by [KeyShortcutDispatcher.KeyShortcut] to mark an old-style shortcut with a hardcoded key */ None(Category.None, KeyCharAndCode.UNKNOWN), + // MainMenu + Resume(Category.MainMenu), + Quickstart(Category.MainMenu), + StartNewGame(Category.MainMenu, "Start new game", KeyCharAndCode('N')), // Not to be confused with NewGame (from World menu, Ctrl-N) + MainMenuLoad(Category.MainMenu, "Load game", KeyCharAndCode('L')), + Multiplayer(Category.MainMenu), // Name disambiguation maybe soon, not yet necessary + MapEditor(Category.MainMenu, "Map editor", KeyCharAndCode('E')), + ModManager(Category.MainMenu, "Mods", KeyCharAndCode('D')), + MainMenuOptions(Category.MainMenu, "Options", KeyCharAndCode('O')), // Separate binding from World where it's Ctrl-O default + // Worldscreen Menu(Category.WorldScreen, KeyCharAndCode.TAB), NextTurn(Category.WorldScreen), @@ -122,6 +132,7 @@ enum class KeyboardBinding( enum class Category { None, + MainMenu, WorldScreen { // Conflict checking within group plus keys assigned to UnitActions are a problem override fun checkConflictsIn() = sequenceOf(this, MapPanning, UnitActions) diff --git a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt index 517c5a7c59..e947fdf481 100644 --- a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt +++ b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt @@ -1,6 +1,5 @@ package com.unciv.ui.screens.mainmenuscreen -import com.badlogic.gdx.Input import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.actions.Actions import com.badlogic.gdx.scenes.scene2d.ui.Stack @@ -24,14 +23,15 @@ import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache import com.unciv.models.tilesets.TileSetCache import com.unciv.ui.components.AutoScrollPane -import com.unciv.ui.components.input.KeyCharAndCode import com.unciv.ui.components.UncivTooltip.Companion.addTooltip import com.unciv.ui.components.extensions.center -import com.unciv.ui.components.input.keyShortcuts -import com.unciv.ui.components.input.onActivation import com.unciv.ui.components.extensions.surroundWithCircle import com.unciv.ui.components.extensions.toLabel +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.keyShortcuts +import com.unciv.ui.components.input.onActivation import com.unciv.ui.components.tilegroups.TileGroupMap import com.unciv.ui.images.ImageGetter import com.unciv.ui.popups.Popup @@ -78,14 +78,13 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { /** Create one **Main Menu Button** including onClick/key binding * @param text The text to display on the button * @param icon The path of the icon to display on the button - * @param key Optional key binding (limited to Char subset of [KeyCharAndCode], which is OK for the main menu) + * @param binding keyboard binding * @param function Action to invoke when the button is activated */ private fun getMenuButton( text: String, icon: String, - key: Char? = null, - keyVisualOnly: Boolean = false, + binding: KeyboardBinding, function: () -> Unit ): Table { val table = Table().pad(15f, 30f, 15f, 30f) @@ -98,17 +97,11 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { table.add(text.toLabel(fontSize = 30, alignment = Align.left)).expand().left().minWidth(200f) table.touchable = Touchable.enabled - table.onActivation { + table.onActivation(binding = binding) { stopBackgroundMapGeneration() function() } - if (key != null) { - if (!keyVisualOnly) - table.keyShortcuts.add(key) - table.addTooltip(key, 32f) - } - table.pack() return table } @@ -141,36 +134,36 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { val column2 = if (singleColumn) column1 else Table().apply { defaults().pad(10f).fillX() } if (game.files.autosaveExists()) { - val resumeTable = getMenuButton("Resume","OtherIcons/Resume", 'r') + val resumeTable = getMenuButton("Resume","OtherIcons/Resume", KeyboardBinding.Resume) { resumeGame() } column1.add(resumeTable).row() } - val quickstartTable = getMenuButton("Quickstart", "OtherIcons/Quickstart", 'q') + val quickstartTable = getMenuButton("Quickstart", "OtherIcons/Quickstart", KeyboardBinding.Quickstart) { quickstartNewGame() } column1.add(quickstartTable).row() - val newGameButton = getMenuButton("Start new game", "OtherIcons/New", 'n') + val newGameButton = getMenuButton("Start new game", "OtherIcons/New", KeyboardBinding.StartNewGame) { game.pushScreen(NewGameScreen()) } column1.add(newGameButton).row() - val loadGameTable = getMenuButton("Load game", "OtherIcons/Load", 'l') + val loadGameTable = getMenuButton("Load game", "OtherIcons/Load", KeyboardBinding.MainMenuLoad) { game.pushScreen(LoadGameScreen()) } column1.add(loadGameTable).row() - val multiplayerTable = getMenuButton("Multiplayer", "OtherIcons/Multiplayer", 'm') + val multiplayerTable = getMenuButton("Multiplayer", "OtherIcons/Multiplayer", KeyboardBinding.Multiplayer) { game.pushScreen(MultiplayerScreen()) } column2.add(multiplayerTable).row() - val mapEditorScreenTable = getMenuButton("Map editor", "OtherIcons/MapEditor", 'e') + val mapEditorScreenTable = getMenuButton("Map editor", "OtherIcons/MapEditor", KeyboardBinding.MapEditor) { game.pushScreen(MapEditorScreen()) } column2.add(mapEditorScreenTable).row() - val modsTable = getMenuButton("Mods", "OtherIcons/Mods", 'd') + val modsTable = getMenuButton("Mods", "OtherIcons/Mods", KeyboardBinding.ModManager) { game.pushScreen(ModManagementScreen()) } column2.add(modsTable).row() - val optionsTable = getMenuButton("Options", "OtherIcons/Options", 'o') + val optionsTable = getMenuButton("Options", "OtherIcons/Options", KeyboardBinding.MainMenuOptions) { this.openOptionsPopup() } column2.add(optionsTable).row() @@ -199,9 +192,10 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { .apply { actor.y -= 2.5f } // compensate font baseline (empirical) .surroundWithCircle(64f, resizeActor = false) helpButton.touchable = Touchable.enabled + // Passing the binding directly to onActivation gives you a size 26 tooltip... helpButton.onActivation { openCivilopedia() } - helpButton.keyShortcuts.add(Input.Keys.F1) - helpButton.addTooltip(KeyCharAndCode(Input.Keys.F1), 30f) + helpButton.keyShortcuts.add(KeyboardBinding.Civilopedia) + helpButton.addTooltip(KeyboardBinding.Civilopedia, 30f) helpButton.setPosition(30f, 30f) stage.addActor(helpButton) }