mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-26 13:27:22 -04:00
Tooltips - all key indicators (#4128)
This commit is contained in:
parent
434136e6cc
commit
d6ec203873
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
77
core/src/com/unciv/ui/utils/StaticTooltip.kt
Normal file
77
core/src/com/unciv/ui/utils/StaticTooltip.kt
Normal 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)))
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user