Removed Char-only restriction in keyboard support for unit actions, remap a few keys, key tooltips for non-ascii keys (#4606)

This commit is contained in:
SomeTroglodyte 2021-07-22 23:02:20 +02:00 committed by GitHub
parent f2697ee8a1
commit e514df5b82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 24 deletions

View File

@ -35,9 +35,14 @@ data class KeyCharAndCode(val char: Char, val code: Int) {
// From Kotlin 1.5? on the Ctrl- line will need Char(char.code+64) // From Kotlin 1.5? on the Ctrl- line will need Char(char.code+64)
// see https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/char-int-conversions.md // see https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/char-int-conversions.md
override fun toString(): String { override fun toString(): String {
// debug helper // debug helper, but also used for tooltips
fun fixedKeysToString(code: Int) = when (code) {
Input.Keys.BACKSPACE -> "Backspace" // Gdx displaying this as "Delete" is Bullshit!
Input.Keys.FORWARD_DEL -> "Del" // Likewise
else -> Input.Keys.toString(code)
}
return when { return when {
char == Char.MIN_VALUE -> Input.Keys.toString(code) char == Char.MIN_VALUE -> fixedKeysToString(code)
this == ESC -> "ESC" this == ESC -> "ESC"
char < ' ' -> "Ctrl-" + (char.toInt()+64).toChar() char < ' ' -> "Ctrl-" + (char.toInt()+64).toChar()
else -> "\"$char\"" else -> "\"$char\""
@ -51,8 +56,8 @@ data class KeyCharAndCode(val char: Char, val code: Int) {
val RETURN = KeyCharAndCode(Input.Keys.ENTER) val RETURN = KeyCharAndCode(Input.Keys.ENTER)
val NUMPAD_ENTER = KeyCharAndCode(Input.Keys.NUMPAD_ENTER) val NUMPAD_ENTER = KeyCharAndCode(Input.Keys.NUMPAD_ENTER)
val SPACE = KeyCharAndCode(Input.Keys.SPACE) val SPACE = KeyCharAndCode(Input.Keys.SPACE)
val DEL = KeyCharAndCode(Input.Keys.DEL) val BACKSPACE= KeyCharAndCode(Input.Keys.BACKSPACE)
val FORWARD_DEL = KeyCharAndCode(Input.Keys.FORWARD_DEL) // this is what I see for both 'Del' keys val DEL = KeyCharAndCode(Input.Keys.FORWARD_DEL) // Gdx "DEL" is just plain wrong!
/** Guaranteed to be ignored by [KeyPressDispatcher.set] and never to be generated for an actual event, used as fallback to ensure no action is taken */ /** Guaranteed to be ignored by [KeyPressDispatcher.set] and never to be generated for an actual event, used as fallback to ensure no action is taken */
val UNKNOWN = KeyCharAndCode(Input.Keys.UNKNOWN) val UNKNOWN = KeyCharAndCode(Input.Keys.UNKNOWN)
@ -116,9 +121,6 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
// Likewise always match Back to ESC // Likewise always match Back to ESC
if (key == KeyCharAndCode.BACK) if (key == KeyCharAndCode.BACK)
super.put(KeyCharAndCode.ESC, action) super.put(KeyCharAndCode.ESC, action)
// And make two codes for DEL equivalent
if (key == KeyCharAndCode.DEL)
super.put(KeyCharAndCode.FORWARD_DEL, action)
checkInstall() checkInstall()
} }
override fun remove(key: KeyCharAndCode): (() -> Unit)? { override fun remove(key: KeyCharAndCode): (() -> Unit)? {
@ -128,8 +130,6 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
super.remove(KeyCharAndCode.NUMPAD_ENTER) super.remove(KeyCharAndCode.NUMPAD_ENTER)
if (key == KeyCharAndCode.BACK) if (key == KeyCharAndCode.BACK)
super.remove(KeyCharAndCode.ESC) super.remove(KeyCharAndCode.ESC)
if (key == KeyCharAndCode.DEL)
super.remove(KeyCharAndCode.FORWARD_DEL)
checkInstall() checkInstall()
return result return result
} }

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.*
import com.badlogic.gdx.scenes.scene2d.actions.Actions import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.scenes.scene2d.ui.* import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Align
import com.unciv.models.translations.tr
/** /**
* A **Replacement** for Gdx [Tooltip], placement does not follow the mouse. * A **Replacement** for Gdx [Tooltip], placement does not follow the mouse.
@ -180,7 +181,8 @@ class UncivTooltip <T: Actor>(
addListener(UncivTooltip(this, addListener(UncivTooltip(this,
labelWithBackground, labelWithBackground,
forceContentSize = Vector2(size * widthHeightRatio, size), forceContentSize = Vector2(size * widthHeightRatio, size),
offset = Vector2(size/4, 0f) offset = Vector2(-size/4, size/4),
tipAlign = Align.top
)) ))
} }
@ -196,10 +198,17 @@ class UncivTooltip <T: Actor>(
addTooltip((if (char in "Ii") 'i' else char.toUpperCase()).toString(), size, always) addTooltip((if (char in "Ii") 'i' else char.toUpperCase()).toString(), size, always)
} }
/* unused - template in case we need it - problem: how exactly to handle translation? /**
* Add a [Label]-based Tooltip for a keyboard binding with a rounded-corner background to a [Table] or other [Group].
*
* Tip is positioned over top right corner, slightly overshooting the receiver widget.
*
* @param size _Vertical_ size of the entire Tooltip including background
* @param always override requirement: presence of physical keyboard
*/
fun Group.addTooltip(key: KeyCharAndCode, size: Float = 26f, always: Boolean = false) { fun Group.addTooltip(key: KeyCharAndCode, size: Float = 26f, always: Boolean = false) {
addTooltip(key.toString(), size, always) if (key != KeyCharAndCode.UNKNOWN)
addTooltip(key.toString().tr(), size, always)
} }
*/
} }
} }

View File

@ -1,10 +1,10 @@
package com.unciv.ui.worldscreen.unit package com.unciv.ui.worldscreen.unit
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Button import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
@ -17,7 +17,9 @@ import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
import com.unciv.ui.worldscreen.WorldScreen import com.unciv.ui.worldscreen.WorldScreen
import kotlin.concurrent.thread import kotlin.concurrent.thread
private data class UnitIconAndKey(val Icon: Actor, var key: Char = Char.MIN_VALUE) private data class UnitIconAndKey(val icon: Actor, var key: KeyCharAndCode = KeyCharAndCode.UNKNOWN) {
constructor(icon: Actor, key: Char) : this(icon, KeyCharAndCode(key))
}
class UnitActionsTable(val worldScreen: WorldScreen) : Table() { class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
@ -47,12 +49,12 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
"Fortify until healed" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Shield").apply { color = Color.BLACK }, 'h') "Fortify until healed" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Shield").apply { color = Color.BLACK }, 'h')
// Move unit is not actually used anywhere // Move unit is not actually used anywhere
"Move unit" -> return UnitIconAndKey(ImageGetter.getStatIcon("Movement")) "Move unit" -> return UnitIconAndKey(ImageGetter.getStatIcon("Movement"))
"Stop movement" -> return UnitIconAndKey(ImageGetter.getStatIcon("Movement").apply { color = Color.RED }, '.') "Stop movement" -> return UnitIconAndKey(ImageGetter.getStatIcon("Movement").apply { color = Color.RED }, KeyCharAndCode(Input.Keys.END))
"Swap units" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Swap"), 'y') "Swap units" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Swap"), 'y')
"Promote" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Star").apply { color = Color.GOLD }, 'o') "Promote" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Star").apply { color = Color.GOLD }, 'o')
"Construct improvement" -> return UnitIconAndKey(ImageGetter.getUnitIcon(Constants.worker), 'i') "Construct improvement" -> return UnitIconAndKey(ImageGetter.getUnitIcon(Constants.worker), 'i')
"Automate" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Great Engineer"), 'm') "Automate" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Great Engineer"), 'm')
"Stop automation" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Stop"), 'm') "Stop automation" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Stop"), KeyCharAndCode(Input.Keys.END))
"Found city" -> return UnitIconAndKey(ImageGetter.getUnitIcon(Constants.settler), 'c') "Found city" -> return UnitIconAndKey(ImageGetter.getUnitIcon(Constants.settler), 'c')
"Hurry Research" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Great Scientist"), 'g') "Hurry Research" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Great Scientist"), 'g')
"Start Golden Age" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Great Artist"), 'g') "Start Golden Age" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Great Artist"), 'g')
@ -64,10 +66,10 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
"Explore" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Scout"), 'x') "Explore" -> return UnitIconAndKey(ImageGetter.getUnitIcon("Scout"), 'x')
"Stop exploration" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Stop"), 'x') "Stop exploration" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Stop"), 'x')
"Pillage" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Pillage"), 'p') "Pillage" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Pillage"), 'p')
"Disband unit" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/DisbandUnit")) "Disband unit" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/DisbandUnit"), KeyCharAndCode.DEL)
"Gift unit" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Present")) "Gift unit" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Present"))
"Show more" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/ArrowRight"), 'm') "Show more" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/ArrowRight"), KeyCharAndCode(Input.Keys.PAGE_DOWN))
"Back" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/ArrowLeft")) "Back" -> return UnitIconAndKey(ImageGetter.getImage("OtherIcons/ArrowLeft"), KeyCharAndCode(Input.Keys.PAGE_UP))
else -> { else -> {
// If the unit has been fortifying for some turns // If the unit has been fortifying for some turns
if (unitAction.startsWith("Fortification")) return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Shield")) if (unitAction.startsWith("Fortification")) return UnitIconAndKey(ImageGetter.getImage("OtherIcons/Shield"))
@ -91,10 +93,10 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
val iconAndKey = getIconAndKeyForUnitAction(unitAction.title) val iconAndKey = getIconAndKeyForUnitAction(unitAction.title)
// If peripheral keyboard not detected, hotkeys will not be displayed // If peripheral keyboard not detected, hotkeys will not be displayed
if (!keyboardAvailable) { iconAndKey.key = Char.MIN_VALUE } if (!keyboardAvailable) { iconAndKey.key = KeyCharAndCode.UNKNOWN }
val actionButton = Button(CameraStageBaseScreen.skin) val actionButton = Button(CameraStageBaseScreen.skin)
actionButton.add(iconAndKey.Icon).size(20f).pad(5f) actionButton.add(iconAndKey.icon).size(20f).pad(5f)
val fontColor = if (unitAction.isCurrentAction) Color.YELLOW else Color.WHITE val fontColor = if (unitAction.isCurrentAction) Color.YELLOW else Color.WHITE
actionButton.add(unitAction.title.toLabel(fontColor)).pad(5f) actionButton.add(unitAction.title.toLabel(fontColor)).pad(5f)
actionButton.addTooltip(iconAndKey.key) actionButton.addTooltip(iconAndKey.key)
@ -106,7 +108,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table() {
if (unitAction.action == null) actionButton.disable() if (unitAction.action == null) actionButton.disable()
else { else {
actionButton.onClick(unitAction.uncivSound, action) actionButton.onClick(unitAction.uncivSound, action)
if (iconAndKey.key != Char.MIN_VALUE) if (iconAndKey.key != KeyCharAndCode.UNKNOWN)
worldScreen.keyPressDispatcher[iconAndKey.key] = { worldScreen.keyPressDispatcher[iconAndKey.key] = {
thread(name = "Sound") { Sounds.play(unitAction.uncivSound) } thread(name = "Sound") { Sounds.play(unitAction.uncivSound) }
action() action()