mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-28 14:24:43 -04:00
You can now input distinct numbers when trading gold (#5072)
* Type amount of gold in trade requests * Remove extraneous spaces in template.properties * Implemented proposed changes * Fixed tests
This commit is contained in:
parent
980f0f4611
commit
a20baca7c2
@ -146,6 +146,8 @@ Very well, we shall look for new lands to settle. =
|
|||||||
We shall do as we please. =
|
We shall do as we please. =
|
||||||
We noticed your new city near our borders, despite your promise. This will have....implications. =
|
We noticed your new city near our borders, despite your promise. This will have....implications. =
|
||||||
|
|
||||||
|
Enter the amount of gold =
|
||||||
|
|
||||||
# City-States
|
# City-States
|
||||||
|
|
||||||
Provides [amountOfCulture] culture at 30 Influence =
|
Provides [amountOfCulture] culture at 30 Influence =
|
||||||
@ -713,6 +715,10 @@ Worked by [cityName] =
|
|||||||
Lock =
|
Lock =
|
||||||
Unlock =
|
Unlock =
|
||||||
Move to city =
|
Move to city =
|
||||||
|
Please enter a new name for your city =
|
||||||
|
|
||||||
|
# Ask for text or numbers popup UI
|
||||||
|
|
||||||
Invalid input! Please enter a different string. =
|
Invalid input! Please enter a different string. =
|
||||||
Please enter some text =
|
Please enter some text =
|
||||||
|
|
||||||
|
@ -60,13 +60,10 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen() {
|
|||||||
label = "Choose name for [${unit.baseUnit.name}]",
|
label = "Choose name for [${unit.baseUnit.name}]",
|
||||||
icon = ImageGetter.getUnitIcon(unit.name).surroundWithCircle(80f),
|
icon = ImageGetter.getUnitIcon(unit.name).surroundWithCircle(80f),
|
||||||
defaultText = unit.name,
|
defaultText = unit.name,
|
||||||
|
validate = { it != unit.name},
|
||||||
actionOnOk = { userInput ->
|
actionOnOk = { userInput ->
|
||||||
if (userInput == unit.name)
|
|
||||||
return@AskTextPopup false
|
|
||||||
|
|
||||||
unit.instanceName = userInput
|
unit.instanceName = userInput
|
||||||
this.game.setScreen(PromotionPickerScreen(unit))
|
this.game.setScreen(PromotionPickerScreen(unit))
|
||||||
return@AskTextPopup true
|
|
||||||
}
|
}
|
||||||
).open()
|
).open()
|
||||||
}
|
}
|
||||||
|
@ -117,16 +117,12 @@ class ReligiousBeliefsPickerScreen (
|
|||||||
label = "Choose a name for your religion",
|
label = "Choose a name for your religion",
|
||||||
icon = ImageGetter.getCircledReligionIcon(religionName!!, 80f),
|
icon = ImageGetter.getCircledReligionIcon(religionName!!, 80f),
|
||||||
defaultText = religionName!!,
|
defaultText = religionName!!,
|
||||||
actionOnOk = { religionName ->
|
validate = { religionName ->
|
||||||
if (religionName == Constants.noReligionName
|
religionName != Constants.noReligionName
|
||||||
|| gameInfo.ruleSet.religions.any { it == religionName }
|
&& gameInfo.ruleSet.religions.none { it == religionName }
|
||||||
|| gameInfo.religions.any { it.value.name == religionName }
|
&& gameInfo.religions.none { it.value.name == religionName }
|
||||||
) {
|
},
|
||||||
return@AskTextPopup false
|
actionOnOk = { changeDisplayedReligionName(it) }
|
||||||
}
|
|
||||||
changeDisplayedReligionName(religionName)
|
|
||||||
return@AskTextPopup true
|
|
||||||
}
|
|
||||||
).open()
|
).open()
|
||||||
}
|
}
|
||||||
changeReligionNameButton.disable()
|
changeReligionNameButton.disable()
|
||||||
|
@ -19,20 +19,33 @@ class OfferColumnsTable(private val tradeLogic: TradeLogic, val screen: Diplomac
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val ourAvailableOffersTable = OffersListScroll("OurAvail") {
|
private val ourAvailableOffersTable = OffersListScroll("OurAvail") {
|
||||||
if (it.type == TradeType.Gold) openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization)
|
when (it.type) {
|
||||||
else addOffer(it, tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers)
|
TradeType.Gold -> openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization.gold)
|
||||||
|
TradeType.Gold_Per_Turn -> openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization.statsForNextTurn.gold.toInt())
|
||||||
|
else -> addOffer(it, tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val ourOffersTable = OffersListScroll("OurTrade") {
|
private val ourOffersTable = OffersListScroll("OurTrade") {
|
||||||
if (it.type == TradeType.Gold) openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization)
|
when (it.type) {
|
||||||
else addOffer(it.copy(amount = -it.amount), tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers)
|
TradeType.Gold -> openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization.gold)
|
||||||
|
TradeType.Gold_Per_Turn -> openGoldSelectionPopup(it, tradeLogic.currentTrade.ourOffers, tradeLogic.ourCivilization.statsForNextTurn.gold.toInt())
|
||||||
|
else -> addOffer(it.copy(amount = -it.amount), tradeLogic.currentTrade.ourOffers, tradeLogic.currentTrade.theirOffers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private val theirOffersTable = OffersListScroll("TheirTrade") {
|
private val theirOffersTable = OffersListScroll("TheirTrade") {
|
||||||
if (it.type == TradeType.Gold) openGoldSelectionPopup(it, tradeLogic.currentTrade.theirOffers, tradeLogic.otherCivilization)
|
when (it.type) {
|
||||||
else addOffer(it.copy(amount = -it.amount), tradeLogic.currentTrade.theirOffers, tradeLogic.currentTrade.ourOffers)
|
TradeType.Gold -> openGoldSelectionPopup(it, tradeLogic.currentTrade.theirOffers, tradeLogic.otherCivilization.gold)
|
||||||
|
TradeType.Gold_Per_Turn -> openGoldSelectionPopup(it, tradeLogic.currentTrade.theirOffers, tradeLogic.otherCivilization.statsForNextTurn.gold.toInt())
|
||||||
|
else -> addOffer(it.copy(amount = -it.amount), tradeLogic.currentTrade.theirOffers, tradeLogic.currentTrade.ourOffers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private val theirAvailableOffersTable = OffersListScroll("TheirAvail") {
|
private val theirAvailableOffersTable = OffersListScroll("TheirAvail") {
|
||||||
if (it.type == TradeType.Gold) openGoldSelectionPopup(it, tradeLogic.currentTrade.theirOffers, tradeLogic.otherCivilization)
|
when (it.type) {
|
||||||
else addOffer(it, tradeLogic.currentTrade.theirOffers, tradeLogic.currentTrade.ourOffers)
|
TradeType.Gold -> openGoldSelectionPopup(it, tradeLogic.currentTrade.theirOffers, tradeLogic.otherCivilization.gold)
|
||||||
|
TradeType.Gold_Per_Turn -> openGoldSelectionPopup(it, tradeLogic.currentTrade.theirOffers, tradeLogic.otherCivilization.statsForNextTurn.gold.toInt())
|
||||||
|
else -> addOffer(it, tradeLogic.currentTrade.theirOffers, tradeLogic.currentTrade.ourOffers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -64,45 +77,27 @@ class OfferColumnsTable(private val tradeLogic: TradeLogic, val screen: Diplomac
|
|||||||
theirAvailableOffersTable.update(theirFilteredOffers, tradeLogic.ourAvailableOffers)
|
theirAvailableOffersTable.update(theirFilteredOffers, tradeLogic.ourAvailableOffers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openGoldSelectionPopup(offer: TradeOffer, ourOffers: TradeOffersList, maxGold: Int) {
|
||||||
class goldSelectionPopup(screen: DiplomacyScreen, offer: TradeOffer, ourOffers: TradeOffersList,
|
val existingGoldOffer = ourOffers.firstOrNull { it.type == offer.type }
|
||||||
offeringCiv: CivilizationInfo, onChange: () -> Unit):Popup(screen){
|
|
||||||
init {
|
|
||||||
val existingGoldOffer = ourOffers.firstOrNull { it.type == TradeType.Gold }
|
|
||||||
if (existingGoldOffer != null)
|
if (existingGoldOffer != null)
|
||||||
offer.amount = existingGoldOffer.amount
|
offer.amount = existingGoldOffer.amount
|
||||||
val amountLabel = offer.amount.toLabel()
|
AskNumberPopup(
|
||||||
val minitable = Table().apply { defaults().pad(5f) }
|
screen,
|
||||||
|
label = "Enter the amount of gold",
|
||||||
fun incrementAmount(delta: Int) {
|
icon = ImageGetter.getStatIcon("Gold").surroundWithCircle(80f),
|
||||||
offer.amount += delta
|
defaultText = offer.amount.toString(),
|
||||||
if (offer.amount < 0) offer.amount = 0
|
amountButtons =
|
||||||
if (offer.amount > offeringCiv.gold) offer.amount = offeringCiv.gold
|
if (offer.type == TradeType.Gold) listOf(50, 500)
|
||||||
amountLabel.setText(offer.amount)
|
else listOf(5, 15),
|
||||||
}
|
bounds = IntRange(0, maxGold),
|
||||||
|
actionOnOk = { userInput ->
|
||||||
minitable.add("-500".toTextButton().onClick { incrementAmount(-500) })
|
offer.amount = userInput
|
||||||
minitable.add("-50".toTextButton().onClick { incrementAmount(-50) })
|
|
||||||
minitable.add(amountLabel)
|
|
||||||
minitable.add("+50".toTextButton().onClick { incrementAmount(50) })
|
|
||||||
minitable.add("+500".toTextButton().onClick { incrementAmount(500) })
|
|
||||||
|
|
||||||
add(minitable).row()
|
|
||||||
|
|
||||||
addCloseButton {
|
|
||||||
if (existingGoldOffer == null)
|
if (existingGoldOffer == null)
|
||||||
ourOffers.add(offer)
|
ourOffers.add(offer)
|
||||||
else existingGoldOffer.amount = offer.amount
|
else existingGoldOffer.amount = offer.amount
|
||||||
if (offer.amount == 0) ourOffers.remove(offer)
|
if (offer.amount == 0) ourOffers.remove(offer)
|
||||||
onChange()
|
onChange()
|
||||||
}
|
}
|
||||||
|
).open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openGoldSelectionPopup(offer: TradeOffer, ourOffers: TradeOffersList, offeringCiv: CivilizationInfo) {
|
|
||||||
if (screen.stage.actors.any { it is goldSelectionPopup }) return
|
|
||||||
val selectionPopup = goldSelectionPopup(screen, offer, ourOffers, offeringCiv, onChange)
|
|
||||||
selectionPopup.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
126
core/src/com/unciv/ui/utils/AskNumberPopup.kt
Normal file
126
core/src/com/unciv/ui/utils/AskNumberPopup.kt
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package com.unciv.ui.utils
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Button
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||||
|
|
||||||
|
/** Simple class for showing a prompt for a positive integer to the user
|
||||||
|
* @param screen The previous screen the user was on
|
||||||
|
* @param label A line of text shown to the user
|
||||||
|
* @param icon Icon at the top, should have size 80f
|
||||||
|
* @param defaultText The text that should be in the prompt at the start
|
||||||
|
* @param amountButtons Buttons that when clicked will add/subtract these amounts to the number
|
||||||
|
* @param bounds The bounds in which the number must lie. Defaults to [Int.MIN_VALUE, Int.MAX_VALUE]
|
||||||
|
* @param errorText Text that will be shown when an error is detected
|
||||||
|
* @param validate Function that should return `true` when a valid input is detected
|
||||||
|
* @param actionOnOk Lambda that will be executed after pressing 'OK'.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class AskNumberPopup(
|
||||||
|
screen: CameraStageBaseScreen,
|
||||||
|
label: String = "Please enter a number",
|
||||||
|
icon: IconCircleGroup = ImageGetter.getImage("OtherIcons/Pencil").apply { this.color = Color.BLACK }.surroundWithCircle(80f),
|
||||||
|
defaultText: String = "",
|
||||||
|
amountButtons: List<Int> = listOf(),
|
||||||
|
bounds: IntRange = IntRange(Int.MIN_VALUE, Int.MAX_VALUE),
|
||||||
|
errorText: String = "Invalid input! Please enter a valid number.",
|
||||||
|
validate: (input: Int) -> Boolean = { true },
|
||||||
|
actionOnOk: (input: Int) -> Unit = { },
|
||||||
|
): Popup(screen) {
|
||||||
|
/** Note for future developers: Why this class only accepts positive digits and not negative.
|
||||||
|
*
|
||||||
|
* The problems is the minus sign. This might not seem like a large obstacle, but problems
|
||||||
|
* arrive quickly. First is that our clean `DigitsOnlyFilter()` must be replaced with a check
|
||||||
|
* that allows for adding a minus sign, but only when it is the first character. So far so good,
|
||||||
|
* until a user starts typing numbers before an already placed - sign --> crash. Fix that
|
||||||
|
* by disallowing any character being typed in front of a - sign. All is fixed right? Wrong!
|
||||||
|
* Because you now also disallowed writing two minus signs at the same time, copying over a
|
||||||
|
* new number after clamping now disallows overwriting the existing minus sign with a new minus
|
||||||
|
* sign, as there is already a minus sign in the number. Well, no problem, you can just remove
|
||||||
|
* the number before overwriting it with the clamped variant. But now you reset your cursor
|
||||||
|
* position every time you type a character. You might start trying to cache the cursor position
|
||||||
|
* as well, but at that point you're basically rewriting the setText() function, and when I
|
||||||
|
* reached this point I decided to stop.
|
||||||
|
*
|
||||||
|
* P.S., if you do decide to go on this quest of adding minus signs, don't forget that
|
||||||
|
* `"-".toInt()` also crashes, so you need to exclude that before checking to clamp.
|
||||||
|
*/
|
||||||
|
|
||||||
|
init {
|
||||||
|
val wrapper = Table()
|
||||||
|
wrapper.add(icon).padRight(10f)
|
||||||
|
wrapper.add(label.toLabel())
|
||||||
|
add(wrapper).colspan(2).row()
|
||||||
|
|
||||||
|
val nameField = TextField(defaultText, skin)
|
||||||
|
nameField.textFieldFilter = TextField.TextFieldFilter { _, char -> char.isDigit() || char == '-' }
|
||||||
|
|
||||||
|
fun isValidInt(input: String): Boolean {
|
||||||
|
return input.toIntOrNull() != null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun clampInBounds(input: String): String {
|
||||||
|
val int = input.toIntOrNull() ?: return input
|
||||||
|
|
||||||
|
if (bounds.first > int) {
|
||||||
|
return bounds.first.toString()
|
||||||
|
}
|
||||||
|
if (bounds.last < int)
|
||||||
|
return bounds.last.toString()
|
||||||
|
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
nameField.onChange {
|
||||||
|
nameField.text = clampInBounds(nameField.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
val centerTable = Table(skin)
|
||||||
|
|
||||||
|
fun addValueButton(value: Int) {
|
||||||
|
centerTable.add(
|
||||||
|
Button(
|
||||||
|
if (value > 0) "+$value".toLabel()
|
||||||
|
else value.toLabel(),
|
||||||
|
skin
|
||||||
|
).apply {
|
||||||
|
onClick {
|
||||||
|
if (isValidInt(nameField.text))
|
||||||
|
nameField.text = clampInBounds((nameField.text.toInt() + value).toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).pad(5f)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (value in amountButtons.reversed()) {
|
||||||
|
addValueButton(-value)
|
||||||
|
}
|
||||||
|
|
||||||
|
centerTable.add(nameField).growX().pad(10f)
|
||||||
|
|
||||||
|
add(centerTable).colspan(2).row()
|
||||||
|
|
||||||
|
for (value in amountButtons) {
|
||||||
|
addValueButton(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
val errorLabel = errorText.toLabel()
|
||||||
|
errorLabel.color = Color.RED
|
||||||
|
|
||||||
|
addOKButton(
|
||||||
|
validate = {
|
||||||
|
val errorFound = !isValidInt(nameField.text) || !validate(nameField.text.toInt())
|
||||||
|
if (errorFound) add(errorLabel).colspan(2).center()
|
||||||
|
!errorFound
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
actionOnOk(nameField.text.toInt())
|
||||||
|
}
|
||||||
|
addCloseButton()
|
||||||
|
equalizeLastTwoButtonWidths()
|
||||||
|
|
||||||
|
keyboardFocus = nameField
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
|||||||
* @param defaultText The text that should be in the prompt at the start
|
* @param defaultText The text that should be in the prompt at the start
|
||||||
* @param errorText Text that will be shown when an error is detected
|
* @param errorText Text that will be shown when an error is detected
|
||||||
* @param maxLength The maximal amount of characters the user may input
|
* @param maxLength The maximal amount of characters the user may input
|
||||||
|
* @param validate Function that should return `true` when a valid input is entered, false otherwise
|
||||||
* @param actionOnOk Lambda that will be executed after pressing 'OK'.
|
* @param actionOnOk Lambda that will be executed after pressing 'OK'.
|
||||||
* Gets the text the user inputted as a parameter. Should return `true` if ready to close,
|
* Gets the text the user inputted as a parameter. Should return `true` if ready to close,
|
||||||
* `false` if `errorText` is to be displayed
|
* `false` if `errorText` is to be displayed
|
||||||
@ -22,7 +23,8 @@ class AskTextPopup(
|
|||||||
defaultText: String = "",
|
defaultText: String = "",
|
||||||
errorText: String = "Invalid input! Please enter a different string.",
|
errorText: String = "Invalid input! Please enter a different string.",
|
||||||
maxLength: Int = 32,
|
maxLength: Int = 32,
|
||||||
actionOnOk: (input: String) -> Boolean = { true },
|
validate: (input: String) -> Boolean = { true },
|
||||||
|
actionOnOk: (input: String) -> Unit = {},
|
||||||
) : Popup(screen) {
|
) : Popup(screen) {
|
||||||
|
|
||||||
val illegalChars = "[]{}\"\\<>"
|
val illegalChars = "[]{}\"\\<>"
|
||||||
@ -42,10 +44,14 @@ class AskTextPopup(
|
|||||||
val errorLabel = errorText.toLabel()
|
val errorLabel = errorText.toLabel()
|
||||||
errorLabel.color = Color.RED
|
errorLabel.color = Color.RED
|
||||||
|
|
||||||
addOKButton(automaticallyCloseOnPress = false) {
|
addOKButton(
|
||||||
if (nameField.text == "" || !actionOnOk(nameField.text))
|
validate = {
|
||||||
add(errorLabel).colspan(2).center()
|
val errorFound = nameField.text == "" || !validate(nameField.text)
|
||||||
else close()
|
if (errorFound) add(errorLabel).colspan(2).center()
|
||||||
|
!errorFound
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
actionOnOk(nameField.text)
|
||||||
}
|
}
|
||||||
addCloseButton()
|
addCloseButton()
|
||||||
equalizeLastTwoButtonWidths()
|
equalizeLastTwoButtonWidths()
|
||||||
|
@ -145,21 +145,23 @@ open class Popup(val screen: CameraStageBaseScreen): Table(CameraStageBaseScreen
|
|||||||
* Adds a [TextButton] that can close the popup, with [RETURN][KeyCharAndCode.RETURN] already mapped.
|
* Adds a [TextButton] that can close the popup, with [RETURN][KeyCharAndCode.RETURN] already mapped.
|
||||||
* @param text The button's caption, defaults to "OK".
|
* @param text The button's caption, defaults to "OK".
|
||||||
* @param additionalKey An additional key that should act like a click.
|
* @param additionalKey An additional key that should act like a click.
|
||||||
* @param automaticallyCloseOnPress Whether the popup should be closed when pressing this button.
|
* @param validate Function that should return true when the popup can be closed and `action` can be run.
|
||||||
|
* When this function returns false, nothing happens.
|
||||||
* @param action A lambda to be executed after closing the popup when the button is clicked.
|
* @param action A lambda to be executed after closing the popup when the button is clicked.
|
||||||
* @return The new [Cell], NOT marked as end of row.
|
* @return The new [Cell], NOT marked as end of row.
|
||||||
*/
|
*/
|
||||||
fun addOKButton(
|
fun addOKButton(
|
||||||
text: String = Constants.OK,
|
text: String = Constants.OK,
|
||||||
additionalKey: KeyCharAndCode? = null,
|
additionalKey: KeyCharAndCode? = null,
|
||||||
automaticallyCloseOnPress: Boolean = true,
|
validate: (() -> Boolean) = { true },
|
||||||
action: (() -> Unit),
|
action: (() -> Unit),
|
||||||
): Cell<TextButton> {
|
): Cell<TextButton> {
|
||||||
val okAction = {
|
val okAction = {
|
||||||
if (automaticallyCloseOnPress)
|
if (validate()) {
|
||||||
close()
|
close()
|
||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
keyPressDispatcher[KeyCharAndCode.RETURN] = okAction
|
keyPressDispatcher[KeyCharAndCode.RETURN] = okAction
|
||||||
return addButtonInRow(text, additionalKey, okAction)
|
return addButtonInRow(text, additionalKey, okAction)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user