mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-23 13:10:54 +07:00
Keyboard bindings for Main Menu Screen (#9680)
* Main Menu keyboard bindings * Make keyboard binding tooltips dynamic so user changes need no UI rebuild
This commit is contained in:
parent
c26837fdd7
commit
6726d2ce03
@ -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 <T: Actor>(
|
||||
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<T> = Container(content)
|
||||
@ -57,8 +60,8 @@ class UncivTooltip <T: Actor>(
|
||||
// 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 <T: Actor>(
|
||||
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 <T: Actor>(
|
||||
|
||||
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 <T: Actor>(
|
||||
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<UncivTooltip<*>>()) {
|
||||
tip.hide(true)
|
||||
@ -241,22 +252,37 @@ class UncivTooltip <T: Actor>(
|
||||
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 <T: Actor>(
|
||||
|
||||
/**
|
||||
* 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 <T: Actor>(
|
||||
* @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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user