Tooltips - all key indicators (#4128)

This commit is contained in:
SomeTroglodyte 2021-06-15 18:43:43 +02:00 committed by GitHub
parent 434136e6cc
commit d6ec203873
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 120 additions and 36 deletions

View File

@ -21,6 +21,7 @@ import com.unciv.ui.newgamescreen.NewGameScreen
import com.unciv.ui.pickerscreens.ModManagementScreen
import com.unciv.ui.saves.LoadGameScreen
import com.unciv.ui.utils.*
import com.unciv.ui.utils.StaticTooltip.Companion.addStaticTip
import kotlin.concurrent.thread
class MainMenuScreen: CameraStageBaseScreen() {
@ -38,6 +39,7 @@ class MainMenuScreen: CameraStageBaseScreen() {
text: String,
icon: String,
key: Char? = null,
keyVisualOnly: Boolean = false,
function: () -> Unit
): Table {
val table = Table().pad(15f, 30f, 15f, 30f)
@ -49,7 +51,9 @@ class MainMenuScreen: CameraStageBaseScreen() {
table.onClick(function)
if (key != null) {
keyPressDispatcher[key] = function
if (!keyVisualOnly)
keyPressDispatcher[key] = function
table.addStaticTip(key, 32f)
}
table.pack()
@ -152,7 +156,8 @@ class MainMenuScreen: CameraStageBaseScreen() {
init{
// Using MainMenuScreen.getMenuButton - normally that would place key bindings into the
// screen's key dispatcher, but we need them in this Popup's dispatcher instead.
// So we bind the keys separately.
// Thus the crutch with keyVisualOnly, we assign the key binding here but want
// The button to install the tooltip handler anyway.
defaults().pad(10f)
@ -164,7 +169,7 @@ class MainMenuScreen: CameraStageBaseScreen() {
screen.game.setScreen(newMapScreen)
screen.dispose()
}
val newMapButton = screen.getMenuButton("New map", "OtherIcons/New", function = newMapAction)
val newMapButton = screen.getMenuButton("New map", "OtherIcons/New", 'n', true, newMapAction)
newMapButton.background = tableBackground
add(newMapButton).row()
keyPressDispatcher['n'] = newMapAction
@ -175,7 +180,7 @@ class MainMenuScreen: CameraStageBaseScreen() {
screen.game.setScreen(loadMapScreen)
screen.dispose()
}
val loadMapButton = screen.getMenuButton("Load map", "OtherIcons/Load", function = loadMapAction)
val loadMapButton = screen.getMenuButton("Load map", "OtherIcons/Load", 'l', true, loadMapAction)
loadMapButton.background = tableBackground
add(loadMapButton).row()
keyPressDispatcher['l'] = loadMapAction

View File

@ -1,7 +1,5 @@
package com.unciv.ui.overviewscreen
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group
@ -20,6 +18,8 @@ import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.PromotionPickerScreen
import com.unciv.ui.trade.DiplomacyScreen
import com.unciv.ui.utils.*
import com.unciv.ui.utils.KeyPressDispatcher.Companion.keyboardAvailable
import com.unciv.ui.utils.StaticTooltip.Companion.addStaticTip
import java.text.DecimalFormat
import kotlin.math.abs
import kotlin.math.roundToInt
@ -48,7 +48,6 @@ class EmpireOverviewScreen(private var viewingPlayer:CivilizationInfo, defaultPa
// Buttons now hold their old label plus optionally an indicator for the shortcut key.
// Implement this templated on UnitActionsTable.getUnitActionButton()
val iconAndKey = ButtonDecorations.keyIconMap[name] ?: return // category without decoration entry disappears
val keyboardAvailable = Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)
val setCategoryAction = {
centerTable.clear()
centerTable.add(ScrollPane(table).apply { setOverscroll(false, false) })
@ -65,7 +64,7 @@ class EmpireOverviewScreen(private var viewingPlayer:CivilizationInfo, defaultPa
}
button.add(name.toLabel(Color.WHITE)).pad(5f)
if (!disabled && keyboardAvailable && iconAndKey.key != Char.MIN_VALUE) {
button.add("(${iconAndKey.key})".toLabel(Color.WHITE))
button.addStaticTip(iconAndKey.key)
keyPressDispatcher[iconAndKey.key] = setCategoryAction
}
setCategoryActions[name] = setCategoryAction

View File

@ -1,5 +1,6 @@
package com.unciv.ui.utils
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.scenes.scene2d.EventListener
import com.badlogic.gdx.scenes.scene2d.InputEvent
@ -192,4 +193,8 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
checkInstall()
}
companion object {
/** Tests presence of a physical keyboard - static here as convenience shortcut only */
val keyboardAvailable = Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)
}
}

View File

@ -0,0 +1,77 @@
package com.unciv.ui.utils
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.InputEvent
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.Tooltip
import com.badlogic.gdx.scenes.scene2d.ui.TooltipManager
import com.unciv.UncivGame
import com.unciv.ui.utils.KeyPressDispatcher.Companion.keyboardAvailable
/**
* Modify Gdx [Tooltip] to place the tip over the top right corner of its target
*
* Usage: [table][Table].addStaticTip([key][Char])
*
* Note: This is currently limited to displaying a single character in a circle of hardcoded size,
* displayed half-overlapping, partially out of the parent's bounding box, over the top right part
* of a Table-based Button. Adapting to new usecases shouldn't be too hard, though.
*
* @param contents The actor to display as Tooltip
* @param manager The [TooltipManager] to use - suggested: [tooltipManager]
*/
class StaticTooltip(contents: Actor, manager: TooltipManager) : Tooltip<Actor>(contents,manager) {
init {
// Neither this nor tooltipManager.animations = false actually make the tip appear
// instantly. However, they hide the bug that the very first appearance is misplaced.
setInstant(true)
}
// mark event as handled while Tooltip is shown, ignore otherwise
override fun mouseMoved(event: InputEvent?, x: Float, y: Float): Boolean {
if (container.hasParent()) return false
return super.mouseMoved(event, x, y)
}
// put the tip in a fixed place relative to the target actor
// event.listenerActor is our button, and x/y are relative to its bottom left edge
override fun enter(event: InputEvent, x: Float, y: Float, pointer: Int, fromActor: Actor?) {
super.enter(event, event.listenerActor.width, event.listenerActor.height, pointer, fromActor)
}
companion object {
/** Sizes the character height relative to the surrounding circle size */
const val charHeightToCircleSize = 28f / 32f
/** A factory for the default [TooltipManager] with a few altered properties */
fun tooltipManager(size: Float): TooltipManager =
TooltipManager.getInstance().apply {
initialTime = 0f
offsetX = -0.75f * size // less than the tip actor width so it overshoots a little which looks nice
offsetY = 0f
animations = false
}
/** Extension adds a circled single character as Tooltip over the top right part of a receiver Table */
fun Table.addStaticTip (key: Char, size: Float = 26f) {
if (!keyboardAvailable || key == Char.MIN_VALUE) return
val displayKey = if (key in "iI") 'i' else key.toUpperCase()
// Todo: Inefficient.
// The pixels have likely already been fetched from the font implementation
// and cached in a TextureRegion - but I'm lacking the skills to get them from there.
val keyPixmap = UncivGame.Current.fontImplementation!!.getCharPixmap(displayKey)
val height = size * charHeightToCircleSize
val width = height * keyPixmap.width / keyPixmap.height
val keyImage = Image(Texture(keyPixmap)).apply {
setSize(width, height)
color = ImageGetter.getBlue()
}.surroundWithCircle(size, resizeActor = false, color = Color.LIGHT_GRAY)
addListener(StaticTooltip(keyImage, tooltipManager(size)))
}
}
}

View File

@ -1,7 +1,5 @@
package com.unciv.ui.worldscreen
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group
@ -15,16 +13,21 @@ import com.unciv.ui.overviewscreen.EmpireOverviewScreen
import com.unciv.ui.pickerscreens.PolicyPickerScreen
import com.unciv.ui.pickerscreens.TechPickerScreen
import com.unciv.ui.utils.*
import com.unciv.ui.utils.StaticTooltip.Companion.addStaticTip
import com.unciv.ui.victoryscreen.VictoryScreen
import com.unciv.ui.worldscreen.mainmenu.WorldScreenMenuPopup
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.roundToInt
/**
* Table consisting of the menu button, current civ, some stats and the overview button for the top of [WorldScreen]
*/
class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
var selectedCivLabel = worldScreen.selectedCiv.civName.toLabel()
private var selectedCivIconHolder = Container<Actor>()
private var selectedCivLabel = worldScreen.selectedCiv.civName.toLabel()
private var selectedCivIconHolder = Container<Actor>()
private val turnsLabel = "Turns: 0/400".toLabel()
private val goldLabel = "0".toLabel(colorFromRGB(225, 217, 71))
@ -37,8 +40,8 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
private val happinessImage = Group()
// These are all to improve performance IE reduce update time (was 150 ms on my phone, which is a lot!)
private val malcontentColor = Color.valueOf("ef5350")
private val happinessColor = colorFromRGB(92, 194, 77)
private val malcontentColor = colorFromRGB(239,83,80) // Color.valueOf("ef5350")
private val happinessColor = colorFromRGB(92, 194, 77) // Color.valueOf("8cc24d")
private val malcontentGroup = ImageGetter.getStatIcon("Malcontent")
private val happinessGroup = ImageGetter.getStatIcon("Happiness")
@ -149,9 +152,7 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
private fun getOverviewButton(): Button {
val overviewButton = Button(CameraStageBaseScreen.skin)
overviewButton.add("Overview".toLabel()).pad(10f)
if (Gdx.app.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)) {
overviewButton.add("(E)".toLabel(Color.WHITE))
}
overviewButton.addStaticTip('e')
overviewButton.pack()
overviewButton.onClick { worldScreen.game.setScreen(EmpireOverviewScreen(worldScreen.selectedCiv)) }
overviewButton.centerY(this)
@ -217,10 +218,10 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
cultureLabel.setText(getCultureText(civInfo, nextTurnStats))
faithLabel.setText(civInfo.religionManager.storedFaith.toString() + "(+" + nextTurnStats.faith.roundToInt() + ")")
updateSelectedCivTabel()
updateSelectedCivTable()
}
private fun updateSelectedCivTabel() {
private fun updateSelectedCivTable() {
if (selectedCivLabel.text.toString() == worldScreen.selectedCiv.civName.tr()) return
selectedCivLabel.setText(worldScreen.selectedCiv.civName.tr())
@ -235,18 +236,19 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
if (nextTurnStats.culture == 0f) return cultureString // when you start the game, you're not producing any culture
val turnsToNextPolicy = (civInfo.policies.getCultureNeededForNextPolicy() - civInfo.policies.storedCulture) / nextTurnStats.culture
if (turnsToNextPolicy > 0) cultureString += " (" + ceil(turnsToNextPolicy).toInt() + ")"
else cultureString += " (!)"
cultureString += if (turnsToNextPolicy <= 0f) " (!)"
else " (" + ceil(turnsToNextPolicy).toInt() + ")"
return cultureString
}
private fun getHappinessText(civInfo: CivilizationInfo): String {
var happinessText = civInfo.getHappiness().toString()
if (civInfo.goldenAges.isGoldenAge())
happinessText += " " + "GOLDEN AGE".tr() + "(${civInfo.goldenAges.turnsLeftForCurrentGoldenAge})"
else
happinessText += (" (" + civInfo.goldenAges.storedHappiness + "/"
+ civInfo.goldenAges.happinessRequiredForNextGoldenAge() + ")")
val goldenAges = civInfo.goldenAges
happinessText +=
if (goldenAges.isGoldenAge())
" {GOLDEN AGE}(${goldenAges.turnsLeftForCurrentGoldenAge})".tr()
else
" (${goldenAges.storedHappiness}/${goldenAges.happinessRequiredForNextGoldenAge()})"
return happinessText
}

View File

@ -1,7 +1,5 @@
package com.unciv.ui.worldscreen.unit
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Button
@ -13,10 +11,12 @@ import com.unciv.models.UnitAction
import com.unciv.models.translations.equalsPlaceholderText
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.ui.utils.*
import com.unciv.ui.utils.KeyPressDispatcher.Companion.keyboardAvailable
import com.unciv.ui.utils.StaticTooltip.Companion.addStaticTip
import com.unciv.ui.worldscreen.WorldScreen
import kotlin.concurrent.thread
private data class UnitIconAndKey(val Icon: Actor, var key: Char = 0.toChar())
private data class UnitIconAndKey(val Icon: Actor, var key: Char = Char.MIN_VALUE)
class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
@ -76,17 +76,13 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
val iconAndKey = getIconAndKeyForUnitAction(unitAction.title)
// If peripheral keyboard not detected, hotkeys will not be displayed
val keyboardAvailable = Gdx.input.isPeripheralAvailable(Input.Peripheral.HardwareKeyboard)
if (!keyboardAvailable){iconAndKey.key = 0.toChar()}
if (!keyboardAvailable) { iconAndKey.key = Char.MIN_VALUE }
val actionButton = Button(CameraStageBaseScreen.skin)
actionButton.add(iconAndKey.Icon).size(20f).pad(5f)
val fontColor = if (unitAction.isCurrentAction) Color.YELLOW else Color.WHITE
actionButton.add(unitAction.title.toLabel(fontColor)).pad(5f)
if (iconAndKey.key != 0.toChar()) {
val keyLabel = "(${iconAndKey.key.toUpperCase()})".toLabel(Color.WHITE)
actionButton.add(keyLabel)
}
actionButton.addStaticTip(iconAndKey.key)
actionButton.pack()
val action = {
unitAction.action?.invoke()
@ -95,7 +91,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
if (unitAction.action == null) actionButton.disable()
else {
actionButton.onClick(unitAction.uncivSound, action)
if (iconAndKey.key != 0.toChar())
if (iconAndKey.key != Char.MIN_VALUE)
worldScreen.keyPressDispatcher[iconAndKey.key] = {
thread(name = "Sound") { Sounds.play(unitAction.uncivSound) }
action()