mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-22 05:41:11 +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
|
Gdx.app.clipboard.contents = base64Gzip
|
||||||
}
|
}
|
||||||
copyMapAsTextButton.onClick (copyMapAsTextAction)
|
copyMapAsTextButton.onClick (copyMapAsTextAction)
|
||||||
keyPressDispatcher['\u0003'] = copyMapAsTextAction // Ctrl-C
|
keyPressDispatcher[KeyCharAndCode.ctrl('C')] = copyMapAsTextAction
|
||||||
rightSideTable.add(copyMapAsTextButton).row()
|
rightSideTable.add(copyMapAsTextButton).row()
|
||||||
} else {
|
} else {
|
||||||
val loadFromClipboardButton = "Load copied data".toTextButton()
|
val loadFromClipboardButton = "Load copied data".toTextButton()
|
||||||
@ -111,7 +111,7 @@ class SaveAndLoadMapScreen(mapToSave: TileMap?, save:Boolean = false, previousSc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadFromClipboardButton.onClick(loadFromClipboardAction)
|
loadFromClipboardButton.onClick(loadFromClipboardAction)
|
||||||
keyPressDispatcher['\u0016'] = loadFromClipboardAction // Ctrl-V
|
keyPressDispatcher[KeyCharAndCode.ctrl('V')] = loadFromClipboardAction
|
||||||
rightSideTable.add(loadFromClipboardButton).row()
|
rightSideTable.add(loadFromClipboardButton).row()
|
||||||
rightSideTable.add(couldNotLoadMapLabel).row()
|
rightSideTable.add(couldNotLoadMapLabel).row()
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ class SaveAndLoadMapScreen(mapToSave: TileMap?, save:Boolean = false, previousSc
|
|||||||
}, this).open()
|
}, this).open()
|
||||||
}
|
}
|
||||||
deleteButton.onClick(deleteAction)
|
deleteButton.onClick(deleteAction)
|
||||||
keyPressDispatcher['\u007f'] = deleteAction // Input.Keys.DEL but ascii has precedence
|
keyPressDispatcher[KeyCharAndCode.DEL] = deleteAction
|
||||||
rightSideTable.add(deleteButton).row()
|
rightSideTable.add(deleteButton).row()
|
||||||
|
|
||||||
topTable.add(rightSideTable)
|
topTable.add(rightSideTable)
|
||||||
|
@ -8,13 +8,15 @@ import com.badlogic.gdx.scenes.scene2d.InputListener
|
|||||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
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
|
* 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
|
* 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)
|
* (Exception: international keyboard AltGr-combos)
|
||||||
* An update supporting easy declarations for any modifier combos would need to use Gdx.input.isKeyPressed()
|
* 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
|
* 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)
|
* Example: KeyCharAndCode('R'), KeyCharAndCode(Input.Keys.F1)
|
||||||
*/
|
*/
|
||||||
data class KeyCharAndCode(val char: Char, val code: Int) {
|
data class KeyCharAndCode(val char: Char, val code: Int) {
|
||||||
// express keys with a Char value
|
/** helper 'cloning constructor' to allow feeding both fields from a factory function */
|
||||||
constructor(char: Char): this(char.toLowerCase(), 0)
|
private constructor(from: KeyCharAndCode): this(from.char, from.code)
|
||||||
// express keys that only have a keyCode like F1
|
/** 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)
|
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
|
// 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
|
||||||
@ -46,13 +44,29 @@ data class KeyCharAndCode(val char: Char, val code: Int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience shortcuts for frequently used constants
|
|
||||||
companion object {
|
companion object {
|
||||||
|
// Convenience shortcuts for frequently used constants
|
||||||
val BACK = KeyCharAndCode(Input.Keys.BACK)
|
val BACK = KeyCharAndCode(Input.Keys.BACK)
|
||||||
val ESC = KeyCharAndCode('\u001B')
|
val ESC = KeyCharAndCode(Input.Keys.ESCAPE)
|
||||||
val RETURN = KeyCharAndCode('\r')
|
val RETURN = KeyCharAndCode(Input.Keys.ENTER)
|
||||||
val NEWLINE = KeyCharAndCode('\n')
|
val NUMPAD_ENTER = KeyCharAndCode(Input.Keys.NUMPAD_ENTER)
|
||||||
val SPACE = KeyCharAndCode(' ')
|
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
|
// access by KeyCharAndCode
|
||||||
operator fun set(key: KeyCharAndCode, action: () -> Unit) {
|
operator fun set(key: KeyCharAndCode, action: () -> Unit) {
|
||||||
|
if (key == KeyCharAndCode.UNKNOWN) return
|
||||||
super.put(key, action)
|
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)
|
if (key == KeyCharAndCode.RETURN)
|
||||||
super.put(KeyCharAndCode.NEWLINE, action)
|
super.put(KeyCharAndCode.NUMPAD_ENTER, action)
|
||||||
// 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)? {
|
||||||
|
if (key == KeyCharAndCode.UNKNOWN) return null
|
||||||
val result = super.remove(key)
|
val result = super.remove(key)
|
||||||
if (key == KeyCharAndCode.RETURN)
|
if (key == KeyCharAndCode.RETURN)
|
||||||
super.remove(KeyCharAndCode.NEWLINE)
|
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
|
||||||
}
|
}
|
||||||
@ -145,13 +166,23 @@ class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (()
|
|||||||
listener =
|
listener =
|
||||||
object : InputListener() {
|
object : InputListener() {
|
||||||
override fun keyTyped(event: InputEvent?, character: Char): Boolean {
|
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
|
// see if we want to handle this key, and if not, let it propagate
|
||||||
if (!contains(key) || (checkIgnoreKeys?.invoke() == true))
|
if (!contains(key) || (checkIgnoreKeys?.invoke() == true))
|
||||||
return super.keyTyped(event, character)
|
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 {
|
try {
|
||||||
this@KeyPressDispatcher[key]?.invoke()
|
this@KeyPressDispatcher[key]?.invoke()
|
||||||
} catch (ex: Exception) {}
|
} catch (ex: Exception) {}
|
||||||
|
@ -251,11 +251,11 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
|
|||||||
if (!mapHolder.setCenterPosition(capital.location))
|
if (!mapHolder.setCenterPosition(capital.location))
|
||||||
game.setScreen(CityScreen(capital))
|
game.setScreen(CityScreen(capital))
|
||||||
}
|
}
|
||||||
keyPressDispatcher['\u000F'] = { this.openOptionsPopup() } // Ctrl-O: Game Options
|
keyPressDispatcher[KeyCharAndCode.ctrl('O')] = { this.openOptionsPopup() } // Game Options
|
||||||
keyPressDispatcher['\u0013'] = { game.setScreen(SaveGameScreen(gameInfo)) } // Ctrl-S: Save
|
keyPressDispatcher[KeyCharAndCode.ctrl('S')] = { game.setScreen(SaveGameScreen(gameInfo)) } // Save
|
||||||
keyPressDispatcher['\u000C'] = { game.setScreen(LoadGameScreen(this)) } // Ctrl-L: Load
|
keyPressDispatcher[KeyCharAndCode.ctrl('L')] = { game.setScreen(LoadGameScreen(this)) } // Load
|
||||||
keyPressDispatcher['+'] = { this.mapHolder.zoomIn() } // '+' Zoom - Input.Keys.NUMPAD_ADD would need dispatcher patch
|
keyPressDispatcher[Input.Keys.NUMPAD_ADD] = { this.mapHolder.zoomIn() } // '+' Zoom
|
||||||
keyPressDispatcher['-'] = { this.mapHolder.zoomOut() } // '-' Zoom
|
keyPressDispatcher[Input.Keys.NUMPAD_SUBTRACT] = { this.mapHolder.zoomOut() } // '-' Zoom
|
||||||
keyPressDispatcher.setCheckpoint()
|
keyPressDispatcher.setCheckpoint()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user