mirror of
https://github.com/yairm210/Unciv.git
synced 2025-01-20 09:17:47 +07:00
Key dispatcher precedence with several Popups open (#4040)
* Key dispatcher precedence with several Popups open * Key dispatcher precedence with several Popups open - remove instrumentation
This commit is contained in:
parent
6614d1050d
commit
e22c53ca96
@ -28,7 +28,7 @@ open class CameraStageBaseScreen : Screen {
|
|||||||
|
|
||||||
protected val tutorialController by lazy { TutorialController(this) }
|
protected val tutorialController by lazy { TutorialController(this) }
|
||||||
|
|
||||||
val keyPressDispatcher = KeyPressDispatcher()
|
val keyPressDispatcher = KeyPressDispatcher(this.javaClass.simpleName)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val resolutions: List<Float> = game.settings.resolution.split("x").map { it.toInt().toFloat() }
|
val resolutions: List<Float> = game.settings.resolution.split("x").map { it.toInt().toFloat() }
|
||||||
@ -37,7 +37,7 @@ open class CameraStageBaseScreen : Screen {
|
|||||||
/** The ExtendViewport sets the _minimum_(!) world size - the actual world size will be larger, fitted to screen/window aspect ratio. */
|
/** The ExtendViewport sets the _minimum_(!) world size - the actual world size will be larger, fitted to screen/window aspect ratio. */
|
||||||
stage = Stage(ExtendViewport(height, height), SpriteBatch())
|
stage = Stage(ExtendViewport(height, height), SpriteBatch())
|
||||||
|
|
||||||
keyPressDispatcher.install(stage, this.javaClass.simpleName) { hasOpenPopups() }
|
keyPressDispatcher.install(stage) { hasOpenPopups() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun show() {}
|
override fun show() {}
|
||||||
|
@ -36,7 +36,6 @@ fun Button.enable() {
|
|||||||
* which is more appropriate to toggle On/Off buttons, while this one is good for 'click-to-do-something' buttons.
|
* which is more appropriate to toggle On/Off buttons, while this one is good for 'click-to-do-something' buttons.
|
||||||
*/
|
*/
|
||||||
var Button.isEnabled: Boolean
|
var Button.isEnabled: Boolean
|
||||||
//Todo: Use in PromotionPickerScreen, TradeTable, WorldScreen.updateNextTurnButton
|
|
||||||
get() = touchable == Touchable.enabled
|
get() = touchable == Touchable.enabled
|
||||||
set(value) = if (value) enable() else disable()
|
set(value) = if (value) enable() else disable()
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import com.badlogic.gdx.scenes.scene2d.EventListener
|
|||||||
import com.badlogic.gdx.scenes.scene2d.InputEvent
|
import com.badlogic.gdx.scenes.scene2d.InputEvent
|
||||||
import com.badlogic.gdx.scenes.scene2d.InputListener
|
import com.badlogic.gdx.scenes.scene2d.InputListener
|
||||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||||
import java.util.HashMap
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For now, combination keys cannot easily be expressed.
|
* For now, combination keys cannot easily be expressed.
|
||||||
@ -40,6 +39,7 @@ data class KeyCharAndCode(val char: Char, val code: Int) {
|
|||||||
// debug helper
|
// debug helper
|
||||||
return when {
|
return when {
|
||||||
char == Char.MIN_VALUE -> Input.Keys.toString(code)
|
char == Char.MIN_VALUE -> Input.Keys.toString(code)
|
||||||
|
char == '\u001B' -> "ESC"
|
||||||
char < ' ' -> "Ctrl-" + (char.toInt()+64).toChar()
|
char < ' ' -> "Ctrl-" + (char.toInt()+64).toChar()
|
||||||
else -> "\"$char\""
|
else -> "\"$char\""
|
||||||
}
|
}
|
||||||
@ -57,14 +57,14 @@ data class KeyCharAndCode(val char: Char, val code: Int) {
|
|||||||
* keyPressDispatcher['+'] = { zoomIn() }
|
* keyPressDispatcher['+'] = { zoomIn() }
|
||||||
* ```
|
* ```
|
||||||
* Optionally use [setCheckpoint] and [revertToCheckPoint] to remember and restore one state.
|
* Optionally use [setCheckpoint] and [revertToCheckPoint] to remember and restore one state.
|
||||||
|
*
|
||||||
|
* @param name Optional name of the container screen or popup for debugging
|
||||||
*/
|
*/
|
||||||
class KeyPressDispatcher: HashMap<KeyCharAndCode, (() -> Unit)>() {
|
class KeyPressDispatcher(val name: String? = null) : HashMap<KeyCharAndCode, (() -> Unit)>() {
|
||||||
private var checkpoint: Set<KeyCharAndCode> = setOf() // set of keys marking a checkpoint
|
private var checkpoint: Set<KeyCharAndCode> = setOf() // set of keys marking a checkpoint
|
||||||
private var listener: EventListener? = null // holds listener code, captures its params in install() function
|
private var listener: EventListener? = null // holds listener code, captures its params in install() function
|
||||||
private var listenerInstalled = false // flag for lazy Stage.addListener()
|
private var listenerInstalled = false // flag for lazy Stage.addListener()
|
||||||
private var installStage: Stage? = null // Keep stage passed by install() for lazy addListener and uninstall
|
private var installStage: Stage? = null // Keep stage passed by install() for lazy addListener and uninstall
|
||||||
var name: String? = null // optional debug label
|
|
||||||
private set
|
|
||||||
|
|
||||||
// access by Char
|
// access by Char
|
||||||
operator fun get(char: Char) = this[KeyCharAndCode(char)]
|
operator fun get(char: Char) = this[KeyCharAndCode(char)]
|
||||||
@ -130,8 +130,7 @@ class KeyPressDispatcher: HashMap<KeyCharAndCode, (() -> Unit)>() {
|
|||||||
* @param stage The [Stage] to add the listener to
|
* @param stage The [Stage] to add the listener to
|
||||||
* @param checkIgnoreKeys An optional lambda - when it returns true all keys are ignored
|
* @param checkIgnoreKeys An optional lambda - when it returns true all keys are ignored
|
||||||
*/
|
*/
|
||||||
fun install(stage: Stage, name: String? = null, checkIgnoreKeys: (() -> Boolean)? = null) {
|
fun install(stage: Stage, checkIgnoreKeys: (() -> Boolean)? = null) {
|
||||||
this.name = name
|
|
||||||
if (installStage != null) uninstall()
|
if (installStage != null) uninstall()
|
||||||
listener =
|
listener =
|
||||||
object : InputListener() {
|
object : InputListener() {
|
||||||
@ -141,7 +140,7 @@ class KeyPressDispatcher: HashMap<KeyCharAndCode, (() -> Unit)>() {
|
|||||||
// 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()
|
||||||
@ -169,11 +168,9 @@ class KeyPressDispatcher: HashMap<KeyCharAndCode, (() -> Unit)>() {
|
|||||||
private fun checkInstall(forceRemove: Boolean = false) {
|
private fun checkInstall(forceRemove: Boolean = false) {
|
||||||
if (listener == null || installStage == null) return
|
if (listener == null || installStage == null) return
|
||||||
if (listenerInstalled && (isEmpty() || isPaused || forceRemove)) {
|
if (listenerInstalled && (isEmpty() || isPaused || forceRemove)) {
|
||||||
println(toString() + ": Removing listener" + (if(forceRemove) " for uninstall" else ""))
|
|
||||||
listenerInstalled = false
|
listenerInstalled = false
|
||||||
installStage!!.removeListener(listener)
|
installStage!!.removeListener(listener)
|
||||||
} else if (!listenerInstalled && !(isEmpty() || isPaused)) {
|
} else if (!listenerInstalled && !(isEmpty() || isPaused)) {
|
||||||
println(toString() + ": Adding listener")
|
|
||||||
installStage!!.addListener(listener)
|
installStage!!.addListener(listener)
|
||||||
listenerInstalled = true
|
listenerInstalled = true
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ open class Popup(val screen: CameraStageBaseScreen): Table(CameraStageBaseScreen
|
|||||||
* while the popup is active through the [hasOpenPopups][CameraStageBaseScreen.hasOpenPopups] mechanism.
|
* while the popup is active through the [hasOpenPopups][CameraStageBaseScreen.hasOpenPopups] mechanism.
|
||||||
* @see [KeyPressDispatcher.install]
|
* @see [KeyPressDispatcher.install]
|
||||||
*/
|
*/
|
||||||
val keyPressDispatcher = KeyPressDispatcher()
|
val keyPressDispatcher = KeyPressDispatcher(this.javaClass.simpleName)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Set actor name for debugging
|
// Set actor name for debugging
|
||||||
@ -44,20 +44,21 @@ open class Popup(val screen: CameraStageBaseScreen): Table(CameraStageBaseScreen
|
|||||||
* closed. Use [force] = true if you want to open this popup above the other one anyway.
|
* closed. Use [force] = true if you want to open this popup above the other one anyway.
|
||||||
*/
|
*/
|
||||||
fun open(force: Boolean = false) {
|
fun open(force: Boolean = false) {
|
||||||
if (force || !screen.hasOpenPopups()) {
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.stage.addActor(this)
|
screen.stage.addActor(this)
|
||||||
innerTable.pack()
|
innerTable.pack()
|
||||||
pack()
|
pack()
|
||||||
center(screen.stage)
|
center(screen.stage)
|
||||||
|
if (force || !screen.hasOpenPopups()) {
|
||||||
|
show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Subroutine for [open] handles only visibility and [keyPressDispatcher] */
|
/** Subroutine for [open] handles only visibility and [keyPressDispatcher] */
|
||||||
private fun show() {
|
private fun show() {
|
||||||
this.isVisible = true
|
this.isVisible = true
|
||||||
keyPressDispatcher.install(screen.stage, name)
|
val currentCount = screen.countOpenPopups()
|
||||||
|
// the lambda is for stacked key dispatcher precedence:
|
||||||
|
keyPressDispatcher.install(screen.stage) { screen.countOpenPopups() > currentCount }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,6 +161,13 @@ open class Popup(val screen: CameraStageBaseScreen): Table(CameraStageBaseScreen
|
|||||||
*/
|
*/
|
||||||
fun CameraStageBaseScreen.hasOpenPopups(): Boolean = stage.actors.any { it is Popup && it.isVisible }
|
fun CameraStageBaseScreen.hasOpenPopups(): Boolean = stage.actors.any { it is Popup && it.isVisible }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts number of visible[Popup]s.
|
||||||
|
*
|
||||||
|
* Used for key dispatcher precedence.
|
||||||
|
*/
|
||||||
|
fun CameraStageBaseScreen.countOpenPopups() = stage.actors.count { it is Popup && it.isVisible }
|
||||||
|
|
||||||
/** Closes all [Popup]s. */
|
/** Closes all [Popup]s. */
|
||||||
fun CameraStageBaseScreen.closeAllPopups() = popups.forEach { it.close() }
|
fun CameraStageBaseScreen.closeAllPopups() = popups.forEach { it.close() }
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user