mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-20 12:48:56 +07:00
Keyboard mapping by keyCode (#4542)
* Keyboard mapping by keyCode * Keyboard mapping by keyCode - v2
This commit is contained in:
@ -95,7 +95,7 @@ class SaveAndLoadMapScreen(mapToSave: TileMap?, save:Boolean = false, previousSc
|
||||
Gdx.app.clipboard.contents = base64Gzip
|
||||
}
|
||||
copyMapAsTextButton.onClick (copyMapAsTextAction)
|
||||
keyPressDispatcher['\u0003'] = copyMapAsTextAction // Ctrl-C
|
||||
keyPressDispatcher[KeyCharAndCode.ctrl('C')] = copyMapAsTextAction
|
||||
rightSideTable.add(copyMapAsTextButton).row()
|
||||
} else {
|
||||
val loadFromClipboardButton = "Load copied data".toTextButton()
|
||||
@ -111,7 +111,7 @@ class SaveAndLoadMapScreen(mapToSave: TileMap?, save:Boolean = false, previousSc
|
||||
}
|
||||
}
|
||||
loadFromClipboardButton.onClick(loadFromClipboardAction)
|
||||
keyPressDispatcher['\u0016'] = loadFromClipboardAction // Ctrl-V
|
||||
keyPressDispatcher[KeyCharAndCode.ctrl('V')] = loadFromClipboardAction
|
||||
rightSideTable.add(loadFromClipboardButton).row()
|
||||
rightSideTable.add(couldNotLoadMapLabel).row()
|
||||
}
|
||||
@ -123,7 +123,7 @@ class SaveAndLoadMapScreen(mapToSave: TileMap?, save:Boolean = false, previousSc
|
||||
}, this).open()
|
||||
}
|
||||
deleteButton.onClick(deleteAction)
|
||||
keyPressDispatcher['\u007f'] = deleteAction // Input.Keys.DEL but ascii has precedence
|
||||
keyPressDispatcher[KeyCharAndCode.DEL] = deleteAction
|
||||
rightSideTable.add(deleteButton).row()
|
||||
|
||||
topTable.add(rightSideTable)
|
||||
|
@ -8,13 +8,15 @@ import com.badlogic.gdx.scenes.scene2d.InputListener
|
||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||
|
||||
/*
|
||||
* For now, combination keys cannot easily be expressed.
|
||||
* For now, many combination keys cannot easily be expressed.
|
||||
* Pressing Ctrl-Letter will arrive one event for Input.Keys.CONTROL_LEFT and one for the ASCII control code point
|
||||
* so Ctrl-R can be handled using KeyCharAndCode('\u0012')
|
||||
* so Ctrl-R can be handled using KeyCharAndCode('\u0012') or KeyCharAndCode.ctrl('R')
|
||||
* Pressing Alt-Something likewise will fire once for Alt and once for the unmodified keys with no indication Alt is held
|
||||
* (Exception: international keyboard AltGr-combos)
|
||||
* An update supporting easy declarations for any modifier combos would need to use Gdx.input.isKeyPressed()
|
||||
* Gdx seems to omit support for a modifier mask (e.g. Ctrl-Alt-Shift) so we would need to reinvent this
|
||||
*
|
||||
* Note: It is important that KeyCharAndCode is an immutable data class to support usage as HashMap key
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -23,18 +25,14 @@ import com.badlogic.gdx.scenes.scene2d.Stage
|
||||
* Example: KeyCharAndCode('R'), KeyCharAndCode(Input.Keys.F1)
|
||||
*/
|
||||
data class KeyCharAndCode(val char: Char, val code: Int) {
|
||||
// express keys with a Char value
|
||||
constructor(char: Char): this(char.toLowerCase(), 0)
|
||||
// express keys that only have a keyCode like F1
|
||||
/** helper 'cloning constructor' to allow feeding both fields from a factory function */
|
||||
private constructor(from: KeyCharAndCode): this(from.char, from.code)
|
||||
/** Map keys from a Char - will detect by keycode if one can be mapped, by character otherwise */
|
||||
constructor(char: Char): this(mapChar(char))
|
||||
/** express keys that only have a keyCode like F1 */
|
||||
constructor(code: Int): this(Char.MIN_VALUE, code)
|
||||
// helper for use in InputListener keyTyped()
|
||||
constructor(event: InputEvent?, character: Char)
|
||||
: this (
|
||||
character.toLowerCase(),
|
||||
if (character == Char.MIN_VALUE && event!=null) event.keyCode else 0
|
||||
)
|
||||
|
||||
// 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
|
||||
override fun toString(): String {
|
||||
// debug helper
|
||||
@ -46,13 +44,29 @@ data class KeyCharAndCode(val char: Char, val code: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience shortcuts for frequently used constants
|
||||
companion object {
|
||||
// Convenience shortcuts for frequently used constants
|
||||
val BACK = KeyCharAndCode(Input.Keys.BACK)
|
||||
val ESC = KeyCharAndCode('\u001B')
|
||||
val RETURN = KeyCharAndCode('\r')
|
||||
val NEWLINE = KeyCharAndCode('\n')
|
||||
val SPACE = KeyCharAndCode(' ')
|
||||
val ESC = KeyCharAndCode(Input.Keys.ESCAPE)
|
||||
val RETURN = KeyCharAndCode(Input.Keys.ENTER)
|
||||
val NUMPAD_ENTER = KeyCharAndCode(Input.Keys.NUMPAD_ENTER)
|
||||
val SPACE = KeyCharAndCode(Input.Keys.SPACE)
|
||||
val DEL = KeyCharAndCode(Input.Keys.DEL)
|
||||
val FORWARD_DEL = KeyCharAndCode(Input.Keys.FORWARD_DEL) // this is what I see for both 'Del' keys
|
||||
/** 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)
|
||||
|
||||
/** mini-factory for control codes - case insensitive */
|
||||
fun ctrl(letter: Char) = KeyCharAndCode((letter.toInt() and 31).toChar(), 0)
|
||||
|
||||
/** mini-factory for KeyCharAndCode values to be compared by character, not by code */
|
||||
fun ascii(char: Char) = KeyCharAndCode(char.toLowerCase(), 0)
|
||||
|
||||
/** factory maps a Char to a keyCode if possible, returns a Char-based instance otherwise */
|
||||
fun mapChar(char: Char): KeyCharAndCode {
|
||||
val code = Input.Keys.valueOf(char.toUpperCase().toString())
|
||||
return if (code == -1) KeyCharAndCode(char,0) else KeyCharAndCode(Char.MIN_VALUE, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,21 +108,28 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
|
||||
|
||||
// access by KeyCharAndCode
|
||||
operator fun set(key: KeyCharAndCode, action: () -> Unit) {
|
||||
if (key == KeyCharAndCode.UNKNOWN) return
|
||||
super.put(key, action)
|
||||
// On Android the Enter key will fire with Ascii code `Linefeed`, on desktop as `Carriage Return`
|
||||
// Make both Enter keys equivalent
|
||||
if (key == KeyCharAndCode.RETURN)
|
||||
super.put(KeyCharAndCode.NEWLINE, action)
|
||||
super.put(KeyCharAndCode.NUMPAD_ENTER, action)
|
||||
// Likewise always match Back to ESC
|
||||
if (key == KeyCharAndCode.BACK)
|
||||
super.put(KeyCharAndCode.ESC, action)
|
||||
// And make two codes for DEL equivalent
|
||||
if (key == KeyCharAndCode.DEL)
|
||||
super.put(KeyCharAndCode.FORWARD_DEL, action)
|
||||
checkInstall()
|
||||
}
|
||||
override fun remove(key: KeyCharAndCode): (() -> Unit)? {
|
||||
if (key == KeyCharAndCode.UNKNOWN) return null
|
||||
val result = super.remove(key)
|
||||
if (key == KeyCharAndCode.RETURN)
|
||||
super.remove(KeyCharAndCode.NEWLINE)
|
||||
super.remove(KeyCharAndCode.NUMPAD_ENTER)
|
||||
if (key == KeyCharAndCode.BACK)
|
||||
super.remove(KeyCharAndCode.ESC)
|
||||
if (key == KeyCharAndCode.DEL)
|
||||
super.remove(KeyCharAndCode.FORWARD_DEL)
|
||||
checkInstall()
|
||||
return result
|
||||
}
|
||||
@ -145,13 +166,23 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
|
||||
listener =
|
||||
object : InputListener() {
|
||||
override fun keyTyped(event: InputEvent?, character: Char): Boolean {
|
||||
val key = KeyCharAndCode(event, character)
|
||||
// look for both key code and ascii entries - ascii first as the
|
||||
// Char constructor of KeyCharAndCode generates keyCode based instances
|
||||
// preferentially but we would miss Ctrl- combos otherwise
|
||||
val key = when {
|
||||
contains(KeyCharAndCode.ascii(character)) ->
|
||||
KeyCharAndCode.ascii(character)
|
||||
event == null ->
|
||||
KeyCharAndCode.UNKNOWN
|
||||
else ->
|
||||
KeyCharAndCode(event.keyCode)
|
||||
}
|
||||
|
||||
// see if we want to handle this key, and if not, let it propagate
|
||||
if (!contains(key) || (checkIgnoreKeys?.invoke() == true))
|
||||
return super.keyTyped(event, character)
|
||||
|
||||
//try-catch mainly for debugging. Breakpoints in the vicinity can make the event fire twice in rapid succession, second time the context can be invalid
|
||||
// try-catch mainly for debugging. Breakpoints in the vicinity can make the event fire twice in rapid succession, second time the context can be invalid
|
||||
try {
|
||||
this@KeyPressDispatcher[key]?.invoke()
|
||||
} catch (ex: Exception) {}
|
||||
|
@ -251,11 +251,11 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
|
||||
if (!mapHolder.setCenterPosition(capital.location))
|
||||
game.setScreen(CityScreen(capital))
|
||||
}
|
||||
keyPressDispatcher['\u000F'] = { this.openOptionsPopup() } // Ctrl-O: Game Options
|
||||
keyPressDispatcher['\u0013'] = { game.setScreen(SaveGameScreen(gameInfo)) } // Ctrl-S: Save
|
||||
keyPressDispatcher['\u000C'] = { game.setScreen(LoadGameScreen(this)) } // Ctrl-L: Load
|
||||
keyPressDispatcher['+'] = { this.mapHolder.zoomIn() } // '+' Zoom - Input.Keys.NUMPAD_ADD would need dispatcher patch
|
||||
keyPressDispatcher['-'] = { this.mapHolder.zoomOut() } // '-' Zoom
|
||||
keyPressDispatcher[KeyCharAndCode.ctrl('O')] = { this.openOptionsPopup() } // Game Options
|
||||
keyPressDispatcher[KeyCharAndCode.ctrl('S')] = { game.setScreen(SaveGameScreen(gameInfo)) } // Save
|
||||
keyPressDispatcher[KeyCharAndCode.ctrl('L')] = { game.setScreen(LoadGameScreen(this)) } // Load
|
||||
keyPressDispatcher[Input.Keys.NUMPAD_ADD] = { this.mapHolder.zoomIn() } // '+' Zoom
|
||||
keyPressDispatcher[Input.Keys.NUMPAD_SUBTRACT] = { this.mapHolder.zoomOut() } // '-' Zoom
|
||||
keyPressDispatcher.setCheckpoint()
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user