Allow closing a Popup by clicking outside its area (#9306)

* Allow closing a Popup by clicking outside its area

* Allow closing a Popup by clicking outside its area - upd1
This commit is contained in:
SomeTroglodyte 2023-05-04 08:25:14 +02:00 committed by GitHub
parent 817764ec38
commit 43b044740c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 18 deletions

View File

@ -621,6 +621,8 @@ open class TabbedPager(
addOKButton {
if (passEntry.text.hashCode() == secretHashCode) unlockAction() else lockAction()
}
clickBehindToClose = true
onCloseCallback = lockAction
this.keyboardFocus = passEntry
}
}

View File

@ -3,6 +3,7 @@ package com.unciv.ui.popups
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Rectangle
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.InputEvent
import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Button
@ -12,6 +13,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle
import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.logic.event.EventBus
@ -42,12 +44,23 @@ open class Popup(
// This exists to differentiate the actual popup (the inner table)
// from the 'screen blocking' part of the popup (which covers the entire screen)
val innerTable = Table(BaseScreen.skin)
protected val innerTable = Table(BaseScreen.skin)
/** Callbacks that will be called whenever this Popup is shown */
val showListeners = mutableListOf<() -> Unit>()
/** Callbacks that will be called whenever this Popup is closed, no matter how (e.g. no distinction OK/Cancel) */
val closeListeners = mutableListOf<() -> Unit>()
val events = EventBus.EventReceiver()
protected val events = EventBus.EventReceiver()
/** Enables/disables closing by clicking/trapping outside [innerTable].
*
* Automatically set when [addCloseButton] is called but may be changed back or enabled without such a button.
*/
protected var clickBehindToClose = false
/** Unlike [closeListeners] this is only fired on "click-behind" closing */
protected var onCloseCallback: (() -> Unit)? = null
init {
// Set actor name for debugging
@ -55,7 +68,7 @@ open class Popup(
background = BaseScreen.skinStrings.getUiBackground(
"General/Popup/Background",
tintColor = Color.GRAY.cpy().apply { a=.5f })
tintColor = Color.GRAY.cpy().apply { a = 0.5f })
innerTable.background = BaseScreen.skinStrings.getUiBackground(
"General/Popup/InnerTable",
tintColor = BaseScreen.skinStrings.skinConfig.baseColor.darken(0.5f)
@ -66,9 +79,11 @@ open class Popup(
super.add(if (scrollable) AutoScrollPane(innerTable, BaseScreen.skin) else innerTable)
this.isVisible = false
touchable = Touchable.enabled // don't allow clicking behind
this.setFillParent(true)
isVisible = false
touchable = Touchable.enabled
// clicking behind gets special treatment
super.addListener(getBehindClickListener())
super.setFillParent(true)
}
/**
@ -114,11 +129,28 @@ open class Popup(
if (nextPopup != null) (nextPopup as Popup).show()
}
/** Allow closing a popup by clicking 'outside', Android-style, but only if a Close button exists */
private fun getBehindClickListener() = object : ClickListener() {
override fun clicked(event: InputEvent?, x: Float, y: Float) {
if (!clickBehindToClose) return
// Since Gdx doesn't limit events to the actually `hit` actors...
if (event?.target != this@Popup) return
close()
onCloseCallback?.invoke()
}
}
/* All additions to the popup are to the inner table - we shouldn't care that there's an inner table at all */
final override fun <T : Actor?> add(actor: T): Cell<T> = innerTable.add(actor)
override fun row(): Cell<Actor> = innerTable.row()
override fun defaults(): Cell<Actor> = innerTable.defaults()
fun addSeparator() = innerTable.addSeparator()
override fun clear() {
innerTable.clear()
clickBehindToClose = false
onCloseCallback = null
}
/**
* Adds a [caption][text] label: A label with word wrap enabled over half the stage width.
@ -126,7 +158,7 @@ open class Popup(
* @param text The caption text.
* @param size The font size for the label.
*/
fun addGoodSizedLabel(text: String, size:Int=Constants.defaultFontSize): Cell<Label> {
fun addGoodSizedLabel(text: String, size: Int = Constants.defaultFontSize): Cell<Label> {
val label = text.toLabel(fontSize = size)
label.wrap = true
label.setAlignment(Align.center)
@ -140,7 +172,12 @@ open class Popup(
* @param action A lambda to be executed when the button is clicked.
* @return The new [Cell]
*/
fun addButton(text: String, key: KeyCharAndCode? = null, style: TextButtonStyle? = null, action: () -> Unit): Cell<TextButton> {
fun addButton(
text: String,
key: KeyCharAndCode? = null,
style: TextButtonStyle? = null,
action: () -> Unit
): Cell<TextButton> {
val button = text.toTextButton(style)
button.onActivation { action() }
button.keyShortcuts.add(key)
@ -148,6 +185,7 @@ open class Popup(
}
fun addButton(text: String, key: Char, style: TextButtonStyle? = null, action: () -> Unit)
= addButton(text, KeyCharAndCode(key), style, action).apply { row() }
@Suppress("unused") // Keep the offer to pass an Input.keys value
fun addButton(text: String, key: Int, style: TextButtonStyle? = null, action: () -> Unit)
= addButton(text, KeyCharAndCode(key), style, action).apply { row() }
@ -164,7 +202,12 @@ open class Popup(
style: TextButtonStyle? = null,
action: (()->Unit)? = null
): Cell<TextButton> {
val cell = addButton(text, additionalKey, style) { close(); if(action!=null) action() }
clickBehindToClose = true
onCloseCallback = action
val cell = addButton(text, additionalKey, style) {
close()
action?.invoke()
}
cell.actor.keyShortcuts.add(KeyCharAndCode.BACK)
return cell
}
@ -224,7 +267,7 @@ open class Popup(
* and a close button if requested
*/
fun reuseWith(newText: String, withCloseButton: Boolean = false) {
innerTable.clear()
clear()
addGoodSizedLabel(newText)
if (withCloseButton) {
row()
@ -264,12 +307,5 @@ val BaseScreen.activePopup: Popup?
fun BaseScreen.hasOpenPopups(): Boolean = stage.hasOpenPopups()
private fun Stage.hasOpenPopups(): Boolean = actors.any { it is Popup && it.isVisible }
/**
* Counts number of visible[Popup]s.
*
* Used for key dispatcher precedence.
*/
private fun Stage.countOpenPopups() = actors.count { it is Popup && it.isVisible }
/** Closes all [Popup]s. */
fun BaseScreen.closeAllPopups() = popups.forEach { it.close() }

View File

@ -547,6 +547,7 @@ private class RandomNationPickerPopup(
closeButton.keyShortcuts.add(KeyCharAndCode.BACK)
closeButton.setPosition(buttonsOffsetFromEdge, buttonsOffsetFromEdge, Align.bottomLeft)
innerTable.addActor(closeButton)
clickBehindToClose = true
val okButton = "OtherIcons/Checkmark".toImageButton(Color.LIME)
okButton.onClick { returnSelected() }

View File

@ -460,6 +460,7 @@ private class NationPickerPopup(
closeButton.keyShortcuts.add(KeyCharAndCode.BACK)
closeButton.setPosition(buttonsOffsetFromEdge, buttonsOffsetFromEdge, Align.bottomLeft)
innerTable.addActor(closeButton)
clickBehindToClose = true
val okButton = "OtherIcons/Checkmark".toImageButton(Color.LIME)
okButton.onClick { returnSelected() }

View File

@ -302,7 +302,7 @@ class WorldScreen(
} catch (ex: Throwable) {
launchOnGLThread {
val (message) = LoadGameScreen.getLoadExceptionMessage(ex, "Couldn't download the latest game state!")
loadingGamePopup.innerTable.clear()
loadingGamePopup.clear()
loadingGamePopup.addGoodSizedLabel(message).colspan(2).row()
loadingGamePopup.addButton("Retry") {
launchOnThreadPool("Load latest multiplayer state after error") {