Merge remote-tracking branch 'origin/master'
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 7.6 KiB |
BIN
android/Images/OtherIcons/SecretOptions.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
android/Images/OtherIcons/Settings.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
android/Images/OtherIcons/Speaker.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
android/assets/ExtraImages/banner.png
Normal file
After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 138 KiB |
Before Width: | Height: | Size: 967 KiB After Width: | Height: | Size: 997 KiB |
@ -415,9 +415,17 @@ Could not save game to custom location! = Speichern in externem Speicherort fehl
|
||||
# Options
|
||||
|
||||
Options = Optionen
|
||||
Display options = Anzeigeeinstellungen
|
||||
Gameplay options = Spielmechanikeinstellungen
|
||||
Other options = Andere Einstellungen
|
||||
About = Über
|
||||
Display = Anzeige
|
||||
Gameplay = Spielmechanik
|
||||
Sound = Sound
|
||||
Multiplayer = Mehrspieler
|
||||
Advanced = Erweitert
|
||||
Locate mod errors = Mod-Probleme
|
||||
Debug = Nur für Eingeweihte
|
||||
|
||||
See online Readme = Readme online öffnen
|
||||
Visit repository = Repository der Entwickler besuchen
|
||||
Turns between autosaves = Runden bis zum nächsten automatischen Speichern
|
||||
Sound effects volume = Lautstärke Soundeffekte
|
||||
Music volume = Lautstärke Musik
|
||||
@ -444,13 +452,15 @@ Show tile yields = Felderträge anzeigen
|
||||
Continuous rendering = Kontinuierliches Rendern
|
||||
When disabled, saves battery life but certain animations will be suspended = Es spart Akku, wenn es deaktiviert ist, aber bestimmte Animationen werden nicht angezeigt.
|
||||
Order trade offers by amount = Handelsangebote nach Menge sortieren
|
||||
Check extension mods based on vanilla = Erweiterungs-Mods mit Vanilla-Regelsatz prüfen
|
||||
Checking mods for errors... = Mods werden geprüft...
|
||||
Show experimental world wrap for maps = 'World Wrap'-Option für neue Karten anbieten
|
||||
HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! = WARNUNG: HOCHGRADIG EXPERIMENTELL - DU WURDEST GEWARNT!
|
||||
HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES! = WARNUNG: HOCHGRADIG EXPERIMENTELL - UPDATES WERDEN SPEICHERSTÄNDE ZERSTÖREN!
|
||||
Enable portrait orientation = Hochkant-Orientierung zulassen
|
||||
Generate translation files = Erstelle Übersetzungsdateien
|
||||
Translation files are generated successfully. = Die Übersetzungsdateien wurden erfolgreich erstellt.
|
||||
Locate mod errors = Mod-Fehler lokalisieren
|
||||
Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. = Bitte beachte, daß die Übersetzungen eine andauernde Leistung einer Gemeinschaft von Freiwilligen sind und damit oft unvollständig. Die angezeigte Prozentzahl bedeutet den Anteil übersetzter Texte im gesamten Spiel. Wenn Du helfen willst, die Übersetzungen zu verbessern - dies ist ein Link zur Anleitung.
|
||||
|
||||
# Notifications
|
||||
|
||||
@ -466,7 +476,7 @@ You have entered a Golden Age! = Ein Goldenes Zeitalter hat begonnen!
|
||||
A [greatPerson] has been born in [cityName]! = [cityName] - Ein [greatPerson] wurde geboren!
|
||||
We have encountered [civName]! = Wir sind auf [civName] getroffen!
|
||||
[cityStateName] has given us [stats] as a token of goodwill for meeting us = [cityStateName] hat uns [stats] als Zeichen des guten Willens für unsere Begegnung übergeben
|
||||
[cityStateName] has given us [stats] as we are the first major civ to meet them = [cityStateName] hat uns [stats] übergeben, da wir die erste bedeutende Zivilisation sind, die er getroffen hat
|
||||
[cityStateName] has given us [stats] as we are the first major civ to meet them = [cityStateName] hat uns [stats] übergeben, da wir die erste bedeutende Zivilisation sind, die sie getroffen haben
|
||||
Cannot provide unit upkeep for [unitName] - unit has been disbanded! = Der Unterhalt für [unitName] konnte nicht bezahlt werden - Einheit wurde aufgelöst!
|
||||
[cityName] has grown! = [cityName] ist gewachsen!
|
||||
[cityName] is starving! = [cityName] verhungert!
|
||||
@ -1128,7 +1138,6 @@ Choose name for [unitName] = Wähle Namen für [unitName]
|
||||
|
||||
# Multiplayer Turn Checker Service
|
||||
|
||||
Multiplayer options = Mehrspieler Einstellungen
|
||||
Enable out-of-game turn notifications = Aktiviere Zug Benachrichtigungen außerhalb des Spiels
|
||||
Time between turn checks out-of-game (in minutes) = Intervall zwischen Zug Prüfungen (in Minuten)
|
||||
Show persistent notification for turn notifier service = Zeige dauerhafte Benachrichtigung für den Zug-Benachrichtungsdienst
|
||||
|
@ -420,9 +420,16 @@ Could not save game to custom location! =
|
||||
# Options
|
||||
|
||||
Options =
|
||||
Display options =
|
||||
Gameplay options =
|
||||
Other options =
|
||||
About =
|
||||
Display =
|
||||
Gameplay =
|
||||
Sound =
|
||||
Advanced =
|
||||
Multiplayer =
|
||||
Locate mod errors =
|
||||
Debug =
|
||||
|
||||
See online Readme =
|
||||
Turns between autosaves =
|
||||
Sound effects volume =
|
||||
Music volume =
|
||||
@ -444,18 +451,19 @@ off =
|
||||
Show pixel units =
|
||||
Show pixel improvements =
|
||||
Enable nuclear weapons =
|
||||
Fontset =
|
||||
Show tile yields =
|
||||
Continuous rendering =
|
||||
When disabled, saves battery life but certain animations will be suspended =
|
||||
Order trade offers by amount =
|
||||
Check extension mods based on vanilla =
|
||||
Checking mods for errors... =
|
||||
Show experimental world wrap for maps =
|
||||
HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! =
|
||||
HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES! =
|
||||
Enable portrait orientation =
|
||||
Generate translation files =
|
||||
Translation files are generated successfully. =
|
||||
Locate mod errors =
|
||||
Please note that translations are a community-based work in progress and are INCOMPLETE! The percentage shown is how much of the language is translated in-game. If you want to help translating the game into your language, click here. =
|
||||
|
||||
# Notifications
|
||||
|
||||
@ -1135,7 +1143,6 @@ Choose name for [unitName] =
|
||||
|
||||
# Multiplayer Turn Checker Service
|
||||
|
||||
Multiplayer options =
|
||||
Enable out-of-game turn notifications =
|
||||
Time between turn checks out-of-game (in minutes) =
|
||||
Show persistent notification for turn notifier service =
|
||||
|
@ -43,7 +43,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
||||
*/
|
||||
var viewEntireMapForDebug = false
|
||||
/** For when you need to test something in an advanced game and don't have time to faff around */
|
||||
val superchargedForDebug = false
|
||||
var superchargedForDebug = false
|
||||
|
||||
/** Simulate until this turn on the first "Next turn" button press.
|
||||
* Does not update World View changes until finished.
|
||||
|
@ -367,8 +367,8 @@ class TileMap {
|
||||
}
|
||||
} else {
|
||||
// Yes the map generator calls this repeatedly, and we don't want to end up with an oversized tileMatrix
|
||||
// rightX is -leftX or -leftX + 1
|
||||
if (tileMatrix.size != 1 - 2 * leftX && tileMatrix.size != 2 - 2 * leftX)
|
||||
// rightX is -leftX or -leftX + 1 or -leftX + 2
|
||||
if (tileMatrix.size !in (1 - 2 * leftX)..(3 - 2 * leftX))
|
||||
throw(IllegalStateException("TileMap.setTransients called on existing tileMatrix of different size"))
|
||||
}
|
||||
|
||||
|
@ -1,43 +1,22 @@
|
||||
package com.unciv.ui
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.MainMenuScreen
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.pickerscreens.PickerScreen
|
||||
import com.unciv.ui.utils.*
|
||||
|
||||
|
||||
class LanguageTable(val language:String, val percentComplete: Int):Table(){
|
||||
private val blue = ImageGetter.getBlue()
|
||||
private val darkBlue = blue.cpy().lerp(Color.BLACK,0.5f)!!
|
||||
|
||||
init{
|
||||
pad(10f)
|
||||
defaults().pad(10f)
|
||||
left()
|
||||
if(ImageGetter.imageExists("FlagIcons/$language"))
|
||||
add(ImageGetter.getImage("FlagIcons/$language")).size(40f)
|
||||
|
||||
val spaceSplitLang = language.replace("_"," ")
|
||||
add("$spaceSplitLang ($percentComplete%)".toLabel())
|
||||
update("")
|
||||
touchable = Touchable.enabled // so click listener is activated when any part is clicked, not only children
|
||||
pack()
|
||||
}
|
||||
|
||||
fun update(chosenLanguage:String){
|
||||
background = ImageGetter.getBackground( if(chosenLanguage==language) blue else darkBlue)
|
||||
}
|
||||
|
||||
}
|
||||
import com.unciv.ui.utils.enable
|
||||
import com.unciv.ui.utils.onClick
|
||||
import com.unciv.ui.utils.LanguageTable
|
||||
import com.unciv.ui.utils.LanguageTable.Companion.addLanguageTables
|
||||
import com.unciv.ui.worldscreen.mainmenu.OptionsPopup
|
||||
|
||||
/** A [PickerScreen] to select a language, used once on the initial run after a fresh install.
|
||||
* After that, [OptionsPopup] provides the functionality.
|
||||
* Reusable code is in [LanguageTable] and [addLanguageTables].
|
||||
*/
|
||||
class LanguagePickerScreen : PickerScreen() {
|
||||
var chosenLanguage = "English"
|
||||
|
||||
private val languageTables = ArrayList<LanguageTable>()
|
||||
private val languageTables: ArrayList<LanguageTable>
|
||||
|
||||
fun update() {
|
||||
languageTables.forEach { it.update(chosenLanguage) }
|
||||
@ -45,25 +24,8 @@ class LanguagePickerScreen : PickerScreen(){
|
||||
|
||||
init {
|
||||
closeButton.isVisible = false
|
||||
/// trimMargin is overhead, but easier to maintain and see when it might get trimmed without wrap:
|
||||
val translationDisclaimer = """
|
||||
|Please note that translations are a community-based work in progress and are INCOMPLETE!
|
||||
|The percentage shown is how much of the language is translated in-game.
|
||||
|If you want to help translating the game into your language,
|
||||
| instructions are in the Github readme! (Menu > Community > Github)
|
||||
""".trimMargin()
|
||||
topTable.add(translationDisclaimer.toLabel()).pad(10f).row()
|
||||
val tableLanguages = Table()
|
||||
tableLanguages.defaults().uniformX()
|
||||
tableLanguages.defaults().pad(10.0f)
|
||||
tableLanguages.defaults().fillX()
|
||||
topTable.add(tableLanguages).row()
|
||||
|
||||
val languageCompletionPercentage = UncivGame.Current.translations
|
||||
.percentCompleteOfLanguages
|
||||
languageTables.addAll(languageCompletionPercentage
|
||||
.map { LanguageTable(it.key,if(it.key=="English") 100 else it.value) }
|
||||
.sortedByDescending { it.percentComplete} )
|
||||
languageTables = topTable.addLanguageTables(stage.width - 60f)
|
||||
|
||||
languageTables.forEach {
|
||||
it.onClick {
|
||||
@ -71,7 +33,6 @@ class LanguagePickerScreen : PickerScreen(){
|
||||
rightSideButton.enable()
|
||||
update()
|
||||
}
|
||||
tableLanguages.add(it).row()
|
||||
}
|
||||
|
||||
rightSideButton.setText("Pick language".tr())
|
||||
|
68
core/src/com/unciv/ui/utils/LanguageTable.kt
Normal file
@ -0,0 +1,68 @@
|
||||
package com.unciv.ui.utils
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.ui.civilopedia.FormattedLine
|
||||
import com.unciv.ui.civilopedia.MarkupRenderer
|
||||
import java.util.ArrayList
|
||||
|
||||
/** Represents a row in the Language picker, used both in OptionsPopup and in LanguagePickerScreen */
|
||||
internal class LanguageTable(val language:String, val percentComplete: Int): Table(){
|
||||
private val blue = ImageGetter.getBlue()
|
||||
private val darkBlue = blue.cpy().lerp(Color.BLACK,0.5f)!!
|
||||
|
||||
init{
|
||||
pad(10f)
|
||||
defaults().pad(10f)
|
||||
left()
|
||||
if(ImageGetter.imageExists("FlagIcons/$language"))
|
||||
add(ImageGetter.getImage("FlagIcons/$language")).size(40f)
|
||||
|
||||
val spaceSplitLang = language.replace("_"," ")
|
||||
add("$spaceSplitLang ($percentComplete%)".toLabel())
|
||||
update("")
|
||||
touchable =
|
||||
Touchable.enabled // so click listener is activated when any part is clicked, not only children
|
||||
pack()
|
||||
}
|
||||
|
||||
fun update(chosenLanguage:String){
|
||||
background = ImageGetter.getBackground(if (chosenLanguage == language) blue else darkBlue)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Extension to add the Language boxes to a Table, used both in OptionsPopup and in LanguagePickerScreen */
|
||||
internal fun Table.addLanguageTables(expectedWidth: Float): ArrayList<LanguageTable> {
|
||||
val languageTables = ArrayList<LanguageTable>()
|
||||
|
||||
val translationDisclaimer = FormattedLine(
|
||||
text = "Please note that translations are a community-based work in progress and are" +
|
||||
" INCOMPLETE! The percentage shown is how much of the language is translated in-game." +
|
||||
" If you want to help translating the game into your language, click here.",
|
||||
link = "https://github.com/yairm210/Unciv/wiki/Translating",
|
||||
size = 15
|
||||
)
|
||||
add(MarkupRenderer.render(listOf(translationDisclaimer),expectedWidth)).pad(5f).row()
|
||||
|
||||
val tableLanguages = Table()
|
||||
tableLanguages.defaults().uniformX()
|
||||
tableLanguages.defaults().pad(10.0f)
|
||||
tableLanguages.defaults().fillX()
|
||||
|
||||
val languageCompletionPercentage = UncivGame.Current.translations
|
||||
.percentCompleteOfLanguages
|
||||
languageTables.addAll(languageCompletionPercentage
|
||||
.map { LanguageTable(it.key, if (it.key == "English") 100 else it.value) }
|
||||
.sortedByDescending { it.percentComplete} )
|
||||
|
||||
languageTables.forEach {
|
||||
tableLanguages.add(it).row()
|
||||
}
|
||||
add(tableLanguages).row()
|
||||
|
||||
return languageTables
|
||||
}
|
||||
}
|
||||
}
|
355
core/src/com/unciv/ui/utils/TabbedPager.kt
Normal file
@ -0,0 +1,355 @@
|
||||
package com.unciv.ui.utils
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.Group
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*
|
||||
import com.unciv.UncivGame
|
||||
import kotlin.math.min
|
||||
|
||||
/*
|
||||
Unimplemented ideas:
|
||||
Allow "fixed header" content that does not participate in scrolling
|
||||
(OptionsPopup mod check tab)
|
||||
`scrollAlign: Align` property controls initial content scroll position (currently it's Align.top)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements a 'Tabs' widget where different pages can be switched by selecting a header button.
|
||||
*
|
||||
* Each page is an Actor, passed to the Widget via [addPage]. Pages can be [removed][removePage],
|
||||
* [replaced][replacePage] or dynamically added after the Widget is already shown.
|
||||
|
||||
* Pages are automatically scrollable, switching pages preserves scroll positions individually.
|
||||
* Pages can be disabled or secret - any 'secret' pages added require a later call to [askForPassword]
|
||||
* to activate them (or discard if the password is wrong).
|
||||
*
|
||||
* The size parameters are lower and upper bounds of the page content area. The widget will always report
|
||||
* these bounds (plus header height) as layout properties min/max-Width/Height, and measure the content
|
||||
* area of added pages and set the reported pref-W/H to their maximum within these bounds. But, if a
|
||||
* maximum is not specified, that coordinate will grow with content unlimited, and layout max-W/H will
|
||||
* always report the same as pref-W/H.
|
||||
*/
|
||||
//region Fields and initialization
|
||||
@Suppress("MemberVisibilityCanBePrivate", "unused") // All member are part of our API
|
||||
class TabbedPager(
|
||||
private val minimumWidth: Float = 0f,
|
||||
private var maximumWidth: Float = Float.MAX_VALUE,
|
||||
private val minimumHeight: Float = 0f,
|
||||
private var maximumHeight: Float = Float.MAX_VALUE,
|
||||
private val headerFontSize: Int = 18,
|
||||
private val headerFontColor: Color = Color.WHITE,
|
||||
private val highlightColor: Color = Color.BLUE,
|
||||
backgroundColor: Color = ImageGetter.getBlue().lerp(Color.BLACK, 0.5f),
|
||||
private val headerPadding: Float = 10f,
|
||||
capacity: Int = 4
|
||||
) : Table() {
|
||||
|
||||
private class PageState(
|
||||
var content: Actor,
|
||||
var disabled: Boolean = false,
|
||||
val onActivation: ((Int, String)->Unit)? = null
|
||||
) {
|
||||
var scrollX = 0f
|
||||
var scrollY = 0f
|
||||
|
||||
var button: Button = Button(CameraStageBaseScreen.skin)
|
||||
var buttonX = 0f
|
||||
var buttonW = 0f
|
||||
}
|
||||
|
||||
private var preferredWidth = minimumWidth
|
||||
private val growMaxWidth = maximumWidth == Float.MAX_VALUE
|
||||
private val limitWidth = maximumWidth
|
||||
private var preferredHeight = minimumHeight
|
||||
private val growMaxHeight = maximumHeight == Float.MAX_VALUE
|
||||
private val limitHeight = maximumHeight
|
||||
|
||||
private val pages = ArrayList<PageState>(capacity)
|
||||
|
||||
/**
|
||||
* Index of currently selected page, or -1 of none. Read-only, use [selectPage] to change.
|
||||
*/
|
||||
var activePage = -1
|
||||
private set
|
||||
|
||||
private val header = Table(CameraStageBaseScreen.skin)
|
||||
private val headerScroll = AutoScrollPane(header)
|
||||
private var headerHeight = 0f
|
||||
|
||||
private val contentScroll = AutoScrollPane(null)
|
||||
|
||||
private val deferredSecretPages = ArrayDeque<PageState>(0)
|
||||
private var askPasswordLock = false
|
||||
|
||||
init {
|
||||
background = ImageGetter.getBackground(backgroundColor)
|
||||
header.defaults().pad(headerPadding, headerPadding * 0.5f)
|
||||
headerScroll.setOverscroll(false,false)
|
||||
headerScroll.setScrollingDisabled(false, true)
|
||||
// Measure header height, most likely its final value
|
||||
removePage(addPage("Dummy"))
|
||||
add(headerScroll).growX().minHeight(headerHeight).row()
|
||||
add(contentScroll).grow().row()
|
||||
}
|
||||
|
||||
//endregion
|
||||
//region Widget interface
|
||||
|
||||
// The following are part of the Widget interface and serve dynamic sizing
|
||||
override fun getPrefWidth() = preferredWidth
|
||||
fun setPrefWidth(width: Float) {
|
||||
if (width !in minimumWidth..maximumWidth) throw IllegalArgumentException()
|
||||
preferredWidth = width
|
||||
invalidateHierarchy()
|
||||
}
|
||||
override fun getPrefHeight() = preferredHeight + headerHeight
|
||||
fun setPrefHeight(height: Float) {
|
||||
if (height - headerHeight !in minimumHeight..maximumHeight) throw IllegalArgumentException()
|
||||
preferredHeight = height - headerHeight
|
||||
invalidateHierarchy()
|
||||
}
|
||||
override fun getMinWidth() = minimumWidth
|
||||
override fun getMaxWidth() = maximumWidth
|
||||
override fun getMinHeight() = headerHeight + minimumHeight
|
||||
override fun getMaxHeight() = headerHeight + maximumHeight
|
||||
|
||||
//endregion
|
||||
//region API
|
||||
|
||||
/** @return Number of pages currently stored */
|
||||
fun pageCount() = pages.size
|
||||
|
||||
/** @return index of a page by its (untranslated) caption, or -1 if no such page exists */
|
||||
fun getPageIndex(caption: String) = pages.indexOfLast { it.button.name == caption }
|
||||
|
||||
/** Change the selected page by using its index.
|
||||
* @param index Page number or -1 to deselect the current page.
|
||||
* @return `true` if the page was successfully changed.
|
||||
*/
|
||||
fun selectPage(index: Int): Boolean {
|
||||
if (index !in -1 until pages.size) return false
|
||||
if (activePage == index) return false
|
||||
if (index >= 0 && pages[index].disabled) return false
|
||||
if (activePage != -1) {
|
||||
pages[activePage].apply {
|
||||
button.color = Color.WHITE
|
||||
scrollX = contentScroll.scrollX
|
||||
scrollY = contentScroll.scrollY
|
||||
contentScroll.removeActor(content)
|
||||
}
|
||||
}
|
||||
activePage = index
|
||||
if (index != -1) {
|
||||
pages[index].apply {
|
||||
button.color = highlightColor
|
||||
contentScroll.actor = content
|
||||
contentScroll.layout()
|
||||
if (scrollX < 0f) // was marked to center on first show
|
||||
scrollX = ((content.width - this@TabbedPager.width) / 2).coerceIn(0f, contentScroll.maxX)
|
||||
contentScroll.scrollX = scrollX
|
||||
contentScroll.scrollY = scrollY
|
||||
contentScroll.updateVisualScroll()
|
||||
headerScroll.let {
|
||||
it.scrollX = (buttonX + (buttonW - it.width) / 2).coerceIn(0f, it.maxX)
|
||||
}
|
||||
onActivation?.invoke(index, button.name)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Change the selected page by using its caption.
|
||||
* @param caption Caption of the page to select. A nonexistent name will deselect the current page.
|
||||
* @return `true` if the page was successfully changed.
|
||||
*/
|
||||
fun selectPage(caption: String) = selectPage(getPageIndex(caption))
|
||||
private fun selectPage(page: PageState) = selectPage(getPageIndex(page))
|
||||
|
||||
/** Change the disabled property of a page by its index.
|
||||
* @return previous value or `false` if index invalid.
|
||||
*/
|
||||
fun setPageDisabled(index: Int, disabled: Boolean): Boolean {
|
||||
if (index !in 0 until pages.size) return false
|
||||
val page = pages[index]
|
||||
val oldValue = page.disabled
|
||||
page.disabled = disabled
|
||||
page.button.isEnabled = !disabled
|
||||
if (disabled && index == activePage) selectPage(-1)
|
||||
return oldValue
|
||||
}
|
||||
|
||||
/** Change the disabled property of a page by its caption.
|
||||
* @return previous value or `false` if caption not found.
|
||||
*/
|
||||
fun setPageDisabled(caption: String, disabled: Boolean) = setPageDisabled(getPageIndex(caption), disabled)
|
||||
|
||||
/** Remove a page by its index.
|
||||
* @return `true` if page successfully removed */
|
||||
fun removePage(index: Int): Boolean {
|
||||
if (index !in 0 until pages.size) return false
|
||||
if (index == activePage) selectPage(-1)
|
||||
val page = pages.removeAt(index)
|
||||
header.getCell(page.button).clearActor()
|
||||
header.cells.removeIndex(index)
|
||||
return true
|
||||
}
|
||||
|
||||
/** Remove a page by its caption.
|
||||
* @return `true` if page successfully removed */
|
||||
fun removePage(caption: String) = removePage(getPageIndex(caption))
|
||||
|
||||
/** Replace a page's content by its index. */
|
||||
fun replacePage(index: Int, content: Actor) {
|
||||
if (index !in 0 until pages.size) return
|
||||
val isActive = index == activePage
|
||||
if (isActive) selectPage(-1)
|
||||
pages[index].content = content
|
||||
if (isActive) selectPage(index)
|
||||
}
|
||||
|
||||
/** Replace a page's content by its caption. */
|
||||
fun replacePage(caption: String, content: Actor) = replacePage(getPageIndex(caption), content)
|
||||
|
||||
/** Add a page!
|
||||
* @param caption Text to be shown on the header button (automatically translated), can later be used to reference the page in other calls.
|
||||
* @param content Actor to show when this page is selected.
|
||||
* @param icon Actor, typically an [Image], to show before the caption.
|
||||
* @param iconSize Size for [icon] - if not zero, the icon is wrapped to allow a [setSize] even on [Image] which ignores size.
|
||||
* @param insertBefore -1 to add at the end or index of existing page to insert this before
|
||||
* @param secret Marks page as 'secret'. A password is asked once per [TabbedPager] and if it does not match the has passed in the constructor the page and all subsequent secret pages are dropped.
|
||||
* @param disabled Initial disabled state. Disabled pages cannot be selected even with [selectPage], their button is dimmed.
|
||||
* @param onActivation _Optional_ callback called when this page is shown (per actual change to this page, not per header click). Lambda arguments are page index and caption.
|
||||
* @return The new page's index or -1 if it could not be immediately added (secret).
|
||||
*/
|
||||
fun addPage(
|
||||
caption: String,
|
||||
content: Actor? = null,
|
||||
icon: Actor? = null,
|
||||
iconSize: Float = 0f,
|
||||
insertBefore: Int = -1,
|
||||
secret: Boolean = false,
|
||||
disabled: Boolean = false,
|
||||
onActivation: ((Int, String)->Unit)? = null
|
||||
): Int {
|
||||
// Build page descriptor and header button
|
||||
val page = PageState(content ?: Group(), disabled, onActivation)
|
||||
page.button.apply {
|
||||
name = caption // enable finding pages by untranslated caption without needing our own field
|
||||
if (icon != null) {
|
||||
if (iconSize != 0f) {
|
||||
val wrapper = Group().apply {
|
||||
isTransform =
|
||||
false // performance helper - nothing here is rotated or scaled
|
||||
setSize(iconSize, iconSize)
|
||||
icon.setSize(iconSize, iconSize)
|
||||
icon.center(this)
|
||||
addActor(icon)
|
||||
}
|
||||
add(wrapper).padRight(headerPadding * 0.5f)
|
||||
} else {
|
||||
add(icon)
|
||||
}
|
||||
}
|
||||
add(caption.toLabel(headerFontColor, headerFontSize))
|
||||
isEnabled = !disabled
|
||||
onClick {
|
||||
selectPage(page)
|
||||
}
|
||||
pack()
|
||||
if (height + 2 * headerPadding > headerHeight) {
|
||||
headerHeight = height + 2 * headerPadding
|
||||
if (activePage >= 0) this@TabbedPager.invalidateHierarchy()
|
||||
}
|
||||
}
|
||||
|
||||
// Support 'secret' pages
|
||||
if (secret) {
|
||||
deferredSecretPages.addLast(page)
|
||||
return -1
|
||||
}
|
||||
|
||||
return addAndShowPage(page, insertBefore)
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate any [secret][addPage] pages by asking for the password.
|
||||
*
|
||||
* If the parent of this Widget is a Popup, then this needs to be called _after_ the parent
|
||||
* is shown to ensure proper popup stacking.
|
||||
*/
|
||||
fun askForPassword(secretHashCode: Int = 0) {
|
||||
class PassPopup(screen: CameraStageBaseScreen, unlockAction: ()->Unit, lockAction: ()->Unit) : Popup(screen) {
|
||||
val passEntry = TextField("", CameraStageBaseScreen.skin)
|
||||
init {
|
||||
passEntry.isPasswordMode = true
|
||||
add(passEntry).row()
|
||||
addOKButton {
|
||||
if (passEntry.text.hashCode() == secretHashCode) unlockAction() else lockAction()
|
||||
}
|
||||
this.keyboardFocus = passEntry
|
||||
}
|
||||
}
|
||||
|
||||
if (!UncivGame.isCurrentInitialized() || askPasswordLock || deferredSecretPages.isEmpty()) return
|
||||
askPasswordLock = true // race condition: Popup closes _first_, then deferredSecretPages is emptied -> parent shows and calls us again
|
||||
|
||||
PassPopup(UncivGame.Current.screen as CameraStageBaseScreen, {
|
||||
addDeferredSecrets()
|
||||
}, {
|
||||
deferredSecretPages.clear()
|
||||
}).open(true)
|
||||
}
|
||||
|
||||
//endregion
|
||||
//region Helper routines
|
||||
|
||||
private fun getPageIndex(page: PageState) = pages.indexOf(page)
|
||||
|
||||
private fun addAndShowPage(page: PageState, insertBefore: Int): Int {
|
||||
// Update pages array and header table
|
||||
val newIndex: Int
|
||||
val buttonCell: Cell<Button>
|
||||
if (insertBefore >= 0 && insertBefore < pages.size) {
|
||||
newIndex = insertBefore
|
||||
pages.add(insertBefore, page)
|
||||
header.addActorAt(insertBefore, page.button)
|
||||
buttonCell = header.getCell(page.button)
|
||||
} else {
|
||||
newIndex = pages.size
|
||||
pages.add(page)
|
||||
buttonCell = header.add(page.button)
|
||||
}
|
||||
page.buttonX = if (newIndex == 0) 0f else pages[newIndex-1].run { buttonX + buttonW }
|
||||
page.buttonW = buttonCell.run { prefWidth + padLeft + padRight }
|
||||
for (i in newIndex + 1 until pages.size)
|
||||
pages[i].buttonX += page.buttonW
|
||||
|
||||
// Content Sizing
|
||||
if (page.content is WidgetGroup) {
|
||||
(page.content as WidgetGroup).packIfNeeded()
|
||||
val contentWidth = min(page.content.width, limitWidth)
|
||||
if (contentWidth > preferredWidth) {
|
||||
preferredWidth = contentWidth
|
||||
if (activePage >= 0) invalidateHierarchy()
|
||||
}
|
||||
val contentHeight = min(page.content.height, limitHeight)
|
||||
if (contentHeight > preferredHeight) {
|
||||
preferredHeight = contentHeight
|
||||
if (activePage >= 0) invalidateHierarchy()
|
||||
}
|
||||
page.scrollX = -1f // mark to center later when all pages are measured
|
||||
}
|
||||
if (growMaxWidth) maximumWidth = minimumWidth
|
||||
if (growMaxHeight) maximumHeight = minimumHeight
|
||||
|
||||
return newIndex
|
||||
}
|
||||
|
||||
private fun addDeferredSecrets() {
|
||||
while (true) {
|
||||
val page = deferredSecretPages.removeFirstOrNull() ?: return
|
||||
addAndShowPage(page, -1)
|
||||
}
|
||||
}
|
||||
}
|
44
core/src/com/unciv/ui/utils/WrappableLabel.kt
Normal file
@ -0,0 +1,44 @@
|
||||
package com.unciv.ui.utils
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||
import com.unciv.models.translations.tr
|
||||
import kotlin.math.min
|
||||
|
||||
/** A [Label] that unlike the original participates correctly in layout
|
||||
* Caveat: You still need to turn wrap on _after_ instantiation, doing it here in init leads to hell.
|
||||
*
|
||||
* @param text Automatically translated text
|
||||
* @param expectedWidth Upper limit for the preferred width the Label will report
|
||||
*/
|
||||
class WrappableLabel(
|
||||
text: String,
|
||||
private val expectedWidth: Float,
|
||||
fontColor: Color = Color.WHITE,
|
||||
fontSize: Int = 18
|
||||
) : Label(text.tr(), CameraStageBaseScreen.skin) {
|
||||
private var _measuredWidth = 0f
|
||||
|
||||
init {
|
||||
if (fontColor != Color.WHITE || fontSize!=18) {
|
||||
val style = LabelStyle(this.style)
|
||||
style.fontColor = fontColor
|
||||
if (fontSize != 18) {
|
||||
style.font = Fonts.font
|
||||
setFontScale(fontSize / Fonts.ORIGINAL_FONT_SIZE)
|
||||
}
|
||||
setStyle(style)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setWrap(wrap: Boolean) {
|
||||
_measuredWidth = super.getPrefWidth()
|
||||
super.setWrap(wrap)
|
||||
}
|
||||
|
||||
private fun getMeasuredWidth(): Float = if (wrap) _measuredWidth else super.getPrefWidth()
|
||||
|
||||
override fun getMinWidth() = 48f // ~ 2 chars
|
||||
override fun getPrefWidth() = min(getMeasuredWidth(), expectedWidth)
|
||||
override fun getMaxWidth() = getMeasuredWidth()
|
||||
}
|
@ -3,93 +3,157 @@ package com.unciv.ui.worldscreen.mainmenu
|
||||
import com.badlogic.gdx.Application
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.Input
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.*
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.Constants
|
||||
import com.unciv.MainMenuScreen
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.models.UncivSound
|
||||
import com.unciv.models.metadata.BaseRuleset
|
||||
import com.unciv.models.ruleset.Ruleset.CheckModLinksStatus
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.tilesets.TileSetCache
|
||||
import com.unciv.models.translations.TranslationFileWriter
|
||||
import com.unciv.models.translations.Translations
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.civilopedia.FormattedLine
|
||||
import com.unciv.ui.civilopedia.MarkupRenderer
|
||||
import com.unciv.ui.civilopedia.SimpleCivilopediaText
|
||||
import com.unciv.ui.utils.*
|
||||
import com.unciv.ui.utils.LanguageTable.Companion.addLanguageTables
|
||||
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
|
||||
import com.unciv.ui.worldscreen.WorldScreen
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.math.min
|
||||
import com.badlogic.gdx.utils.Array as GdxArray
|
||||
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
|
||||
|
||||
class Language(val language:String, val percentComplete:Int){
|
||||
override fun toString(): String {
|
||||
val spaceSplitLang = language.replace("_"," ")
|
||||
return "$spaceSplitLang - $percentComplete%"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Options (Settings) Popup
|
||||
* @param previousScreen Tha caller - note if this is a [WorldScreen] or [MainMenuScreen] they will be rebuilt when major options change.
|
||||
*/
|
||||
//region Fields
|
||||
class OptionsPopup(val previousScreen: CameraStageBaseScreen) : Popup(previousScreen) {
|
||||
private var selectedLanguage: String = "English"
|
||||
private val settings = previousScreen.game.settings
|
||||
private val optionsTable = Table(CameraStageBaseScreen.skin)
|
||||
private val resolutionArray = GdxArray(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000"))
|
||||
private val tabs: TabbedPager
|
||||
private val resolutionArray = com.badlogic.gdx.utils.Array(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000"))
|
||||
private var modCheckFirstRun = true // marker for automatic first run on selecting the page
|
||||
private var modCheckCheckBox: CheckBox? = null
|
||||
private var modCheckResultCell: Cell<Actor>? = null
|
||||
private val selectBoxMinWidth: Float
|
||||
|
||||
//endregion
|
||||
|
||||
init {
|
||||
settings.addCompletedTutorialTask("Open the options table")
|
||||
|
||||
optionsTable.defaults().pad(2.5f)
|
||||
rebuildOptionsTable()
|
||||
innerTable.pad(0f)
|
||||
val tabMaxWidth: Float
|
||||
val tabMinWidth: Float
|
||||
val tabMaxHeight: Float
|
||||
previousScreen.run {
|
||||
selectBoxMinWidth = if (stage.width < 600f) 200f else 240f
|
||||
tabMaxWidth = if (isPortrait()) stage.width - 10f else 0.8f * stage.width
|
||||
tabMinWidth = 0.6f * stage.width
|
||||
tabMaxHeight = (if (isPortrait()) 0.7f else 0.8f) * stage.height
|
||||
}
|
||||
tabs = TabbedPager(tabMinWidth, tabMaxWidth, 0f, tabMaxHeight,
|
||||
headerFontSize = 21, backgroundColor = Color.CLEAR, capacity = 8)
|
||||
add(tabs).pad(0f).grow().row()
|
||||
|
||||
val scrollPane = ScrollPane(optionsTable, skin)
|
||||
scrollPane.setOverscroll(false, false)
|
||||
scrollPane.fadeScrollBars = false
|
||||
scrollPane.setScrollingDisabled(true, false)
|
||||
add(scrollPane).maxHeight(screen.stage.height * 0.6f).row()
|
||||
tabs.addPage("About", getAboutTab(), ImageGetter.getExternalImage("Icon.png"), 24f)
|
||||
tabs.addPage("Display", getDisplayTab(), ImageGetter.getImage("UnitPromotionIcons/Scouting"), 24f)
|
||||
tabs.addPage("Gameplay", getGamePlayTab(), ImageGetter.getImage("OtherIcons/Options"), 24f)
|
||||
tabs.addPage("Language", getLanguageTab(), ImageGetter.getImage("FlagIcons/${settings.language}"), 24f)
|
||||
tabs.addPage("Sound", getSoundTab(), ImageGetter.getImage("OtherIcons/Speaker"), 24f)
|
||||
// at the moment the notification service only exists on Android
|
||||
if (Gdx.app.type == Application.ApplicationType.Android)
|
||||
tabs.addPage("Multiplayer", getMultiplayerTab(), ImageGetter.getImage("OtherIcons/Multiplayer"), 24f)
|
||||
tabs.addPage("Advanced", getAdvancedTab(), ImageGetter.getImage("OtherIcons/Settings"), 24f)
|
||||
if (RulesetCache.size > 1) {
|
||||
tabs.addPage("Locate mod errors", getModCheckTab(), ImageGetter.getImage("OtherIcons/Mods"), 24f) { _, _ ->
|
||||
if (modCheckFirstRun) runModChecker()
|
||||
}
|
||||
}
|
||||
if (Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT) && Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT)) {
|
||||
tabs.addPage("Debug", getDebugTab(), ImageGetter.getImage("OtherIcons/SecretOptions"), 24f, secret = true)
|
||||
}
|
||||
|
||||
addCloseButton {
|
||||
previousScreen.game.limitOrientationsHelper?.allowPortrait(settings.allowAndroidPortrait)
|
||||
if (previousScreen is WorldScreen)
|
||||
previousScreen.enableNextTurnButtonAfterOptions()
|
||||
}
|
||||
}.padBottom(10f)
|
||||
|
||||
pack() // Needed to show the background.
|
||||
center(previousScreen.stage)
|
||||
}
|
||||
|
||||
private fun addHeader(text: String) {
|
||||
optionsTable.add(text.toLabel(fontSize = 24)).colspan(2).padTop(if (optionsTable.cells.isEmpty) 0f else 20f).row()
|
||||
}
|
||||
|
||||
private fun addYesNoRow(text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
|
||||
optionsTable.add(text.toLabel())
|
||||
val button = YesNoButton(initialValue, CameraStageBaseScreen.skin) {
|
||||
action(it)
|
||||
settings.save()
|
||||
if (updateWorld && previousScreen is WorldScreen)
|
||||
previousScreen.shouldUpdate = true
|
||||
}
|
||||
optionsTable.add(button).row()
|
||||
override fun setVisible(visible: Boolean) {
|
||||
super.setVisible(visible)
|
||||
if (!visible) return
|
||||
tabs.askForPassword(secretHashCode = 2747985)
|
||||
if (tabs.activePage < 0) tabs.selectPage(2)
|
||||
}
|
||||
|
||||
/** Reload this Popup after major changes (resolution, tileset, language) */
|
||||
private fun reloadWorldAndOptions() {
|
||||
settings.save()
|
||||
if (previousScreen is WorldScreen) {
|
||||
previousScreen.game.worldScreen = WorldScreen(previousScreen.gameInfo, previousScreen.viewingCiv)
|
||||
previousScreen.game.setWorldScreen()
|
||||
|
||||
} else if (previousScreen is MainMenuScreen) {
|
||||
previousScreen.game.setScreen(MainMenuScreen())
|
||||
}
|
||||
(previousScreen.game.screen as CameraStageBaseScreen).openOptionsPopup()
|
||||
}
|
||||
|
||||
private fun rebuildOptionsTable() {
|
||||
settings.save()
|
||||
optionsTable.clear()
|
||||
//region Page builders
|
||||
|
||||
addHeader("Display options")
|
||||
private fun getAboutTab(): Table {
|
||||
defaults().pad(5f)
|
||||
val version = previousScreen.game.version
|
||||
val versionAnchor = version.replace(".","")
|
||||
val lines = sequence {
|
||||
yield(FormattedLine(extraImage = "banner", imageSize = 240f, centered = true))
|
||||
yield(FormattedLine())
|
||||
yield(FormattedLine("{Version}: $version", link = "https://github.com/yairm210/Unciv/blob/master/changelog.md#$versionAnchor"))
|
||||
yield(FormattedLine("See online Readme", link = "https://github.com/yairm210/Unciv/blob/master/README.md#unciv---foss-civ-v-for-androiddesktop"))
|
||||
yield(FormattedLine("Visit repository", link = "https://github.com/yairm210/Unciv"))
|
||||
}
|
||||
return MarkupRenderer.render(lines.toList()).pad(20f)
|
||||
}
|
||||
|
||||
private fun getLanguageTab() = Table(CameraStageBaseScreen.skin).apply {
|
||||
val languageTables = this.addLanguageTables(tabs.prefWidth * 0.9f - 10f)
|
||||
|
||||
var chosenLanguage = settings.language
|
||||
fun selectLanguage() {
|
||||
settings.language = chosenLanguage
|
||||
previousScreen.game.translations.tryReadTranslationForCurrentLanguage()
|
||||
reloadWorldAndOptions()
|
||||
}
|
||||
fun updateSelection() {
|
||||
languageTables.forEach { it.update(chosenLanguage) }
|
||||
if (chosenLanguage != settings.language)
|
||||
selectLanguage()
|
||||
}
|
||||
updateSelection()
|
||||
|
||||
languageTables.forEach {
|
||||
it.onClick {
|
||||
chosenLanguage = it.language
|
||||
updateSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDisplayTab() = Table(CameraStageBaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(2.5f)
|
||||
|
||||
addYesNoRow("Show worked tiles", settings.showWorkedTiles, true) { settings.showWorkedTiles = it }
|
||||
addYesNoRow("Show resources and improvements", settings.showResourcesAndImprovements, true) { settings.showResourcesAndImprovements = it }
|
||||
@ -100,8 +164,6 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
|
||||
addYesNoRow("Show pixel units", settings.showPixelUnits, true) { settings.showPixelUnits = it }
|
||||
addYesNoRow("Show pixel improvements", settings.showPixelImprovements, true) { settings.showPixelImprovements = it }
|
||||
|
||||
addLanguageSelectBox()
|
||||
|
||||
addResolutionSelectBox()
|
||||
|
||||
addTileSetSelectBox()
|
||||
@ -112,10 +174,15 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
|
||||
}
|
||||
|
||||
val continuousRenderingDescription = "When disabled, saves battery life but certain animations will be suspended"
|
||||
optionsTable.add(continuousRenderingDescription.toLabel(fontSize = 14)).colspan(2).padTop(20f).row()
|
||||
|
||||
addHeader("Gameplay options")
|
||||
val continuousRenderingLabel = WrappableLabel(continuousRenderingDescription,
|
||||
tabs.prefWidth, Color.ORANGE.cpy().lerp(Color.WHITE, 0.7f), 14)
|
||||
continuousRenderingLabel.wrap = true
|
||||
add(continuousRenderingLabel).colspan(2).padTop(10f).row()
|
||||
}
|
||||
|
||||
private fun getGamePlayTab() = Table(CameraStageBaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
addYesNoRow("Check for idle units", settings.checkForDueUnits, true) { settings.checkForDueUnits = it }
|
||||
addYesNoRow("Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it }
|
||||
addYesNoRow("Auto-assign city production", settings.autoAssignCityProduction, true) {
|
||||
@ -130,25 +197,54 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
|
||||
addYesNoRow("Auto-build roads", settings.autoBuildingRoads) { settings.autoBuildingRoads = it }
|
||||
addYesNoRow("Automated workers replace improvements", settings.automatedWorkersReplaceImprovements) { settings.automatedWorkersReplaceImprovements = it }
|
||||
addYesNoRow("Order trade offers by amount", settings.orderTradeOffersByAmount) { settings.orderTradeOffersByAmount = it }
|
||||
}
|
||||
|
||||
private fun getSoundTab() = Table(CameraStageBaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
|
||||
addSoundEffectsVolumeSlider()
|
||||
|
||||
val musicLocation = Gdx.files.local(previousScreen.game.musicLocation)
|
||||
if (musicLocation.exists())
|
||||
addMusicVolumeSlider()
|
||||
else
|
||||
addDownloadMusic(musicLocation)
|
||||
}
|
||||
|
||||
private fun getMultiplayerTab(): Table = Table(CameraStageBaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
|
||||
addYesNoRow("Enable out-of-game turn notifications", settings.multiplayerTurnCheckerEnabled) {
|
||||
settings.multiplayerTurnCheckerEnabled = it
|
||||
settings.save()
|
||||
tabs.replacePage("Multiplayer", getMultiplayerTab())
|
||||
}
|
||||
|
||||
if (settings.multiplayerTurnCheckerEnabled) {
|
||||
addMultiplayerTurnCheckerDelayBox()
|
||||
|
||||
addYesNoRow("Show persistent notification for turn notifier service", settings.multiplayerTurnCheckerPersistentNotificationEnabled)
|
||||
{ settings.multiplayerTurnCheckerPersistentNotificationEnabled = it }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAdvancedTab() = Table(CameraStageBaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
|
||||
addAutosaveTurnsSelectBox()
|
||||
|
||||
// at the moment the notification service only exists on Android
|
||||
addNotificationOptions()
|
||||
|
||||
addHeader("Other options")
|
||||
|
||||
|
||||
addYesNoRow("{Show experimental world wrap for maps}\n{HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED!}".tr(),
|
||||
addYesNoRow("{Show experimental world wrap for maps}\n{HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED!}",
|
||||
settings.showExperimentalWorldWrap) {
|
||||
settings.showExperimentalWorldWrap = it
|
||||
}
|
||||
addYesNoRow("{Enable experimental religion in start games}\n{HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES!}".tr(),
|
||||
addYesNoRow("{Enable experimental religion in start games}\n{HIGHLY EXPERIMENTAL - UPDATES WILL BREAK SAVES!}",
|
||||
settings.showExperimentalReligion) {
|
||||
settings.showExperimentalReligion = it
|
||||
}
|
||||
|
||||
|
||||
if (previousScreen.game.limitOrientationsHelper != null) {
|
||||
addYesNoRow("Enable portrait orientation", settings.allowAndroidPortrait) {
|
||||
settings.allowAndroidPortrait = it
|
||||
@ -157,26 +253,77 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
|
||||
}
|
||||
}
|
||||
|
||||
addSoundEffectsVolumeSlider()
|
||||
addMusicVolumeSlider()
|
||||
|
||||
addTranslationGeneration()
|
||||
addModCheckerPopup()
|
||||
|
||||
addSetUserId()
|
||||
|
||||
optionsTable.add("Version".toLabel()).pad(10f)
|
||||
val versionLabel = previousScreen.game.version.toLabel()
|
||||
if (previousScreen.game.version[0] in '0'..'9')
|
||||
versionLabel.onClick {
|
||||
val url = "https://github.com/yairm210/Unciv/blob/master/changelog.md#" +
|
||||
previousScreen.game.version.replace(".","")
|
||||
Gdx.net.openURI(url)
|
||||
}
|
||||
optionsTable.add(versionLabel).pad(10f).row()
|
||||
}
|
||||
|
||||
private fun addMinimapSizeSlider() {
|
||||
optionsTable.add("Show minimap".tr())
|
||||
private fun getModCheckTab() = Table(CameraStageBaseScreen.skin).apply {
|
||||
defaults().pad(10f).align(Align.top)
|
||||
modCheckCheckBox = "Check extension mods based on vanilla".toCheckBox {
|
||||
runModChecker(it)
|
||||
}
|
||||
add(modCheckCheckBox).row()
|
||||
modCheckResultCell = add("Checking mods for errors...".toLabel())
|
||||
}
|
||||
|
||||
private fun runModChecker(complex: Boolean = false) {
|
||||
modCheckFirstRun = false
|
||||
if (modCheckCheckBox == null) return
|
||||
modCheckCheckBox!!.disable()
|
||||
if (modCheckResultCell == null) return
|
||||
thread(name="ModChecker") {
|
||||
val lines = ArrayList<FormattedLine>()
|
||||
var noProblem = true
|
||||
for (mod in RulesetCache.values.sortedBy { it.name }) {
|
||||
val modLinks = if (complex) RulesetCache.checkCombinedModLinks(linkedSetOf(mod.name))
|
||||
else mod.checkModLinks()
|
||||
val color = when (modLinks.status) {
|
||||
CheckModLinksStatus.OK -> "#0F0"
|
||||
CheckModLinksStatus.Warning -> "#FF0"
|
||||
CheckModLinksStatus.Error -> "#F00"
|
||||
}
|
||||
val label = if (mod.name.isEmpty()) BaseRuleset.Civ_V_Vanilla.fullName else mod.name
|
||||
lines += FormattedLine("$label{}", starred = true, color = color, header = 3)
|
||||
if (modLinks.isNotOK()) {
|
||||
lines += FormattedLine(modLinks.message)
|
||||
noProblem = false
|
||||
}
|
||||
lines += FormattedLine()
|
||||
}
|
||||
if (noProblem) lines += FormattedLine("{No problems found}.")
|
||||
|
||||
Gdx.app.postRunnable {
|
||||
val result = SimpleCivilopediaText(lines).renderCivilopediaText(tabs.prefWidth - 25f)
|
||||
modCheckResultCell?.setActor(result)
|
||||
modCheckCheckBox!!.enable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDebugTab() = Table(CameraStageBaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(5f)
|
||||
|
||||
val game = UncivGame.Current
|
||||
add("Supercharged".toCheckBox(game.superchargedForDebug) {
|
||||
game.superchargedForDebug = it
|
||||
}).row()
|
||||
add("View entire map".toCheckBox(game.viewEntireMapForDebug) {
|
||||
game.viewEntireMapForDebug = it
|
||||
}).row()
|
||||
if (game.isGameInfoInitialized()) {
|
||||
add("God mode (current game)".toCheckBox(game.gameInfo.gameParameters.godMode) {
|
||||
game.gameInfo.gameParameters.godMode = it
|
||||
}).row()
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
//region Row builders
|
||||
|
||||
private fun Table.addMinimapSizeSlider() {
|
||||
add("Show minimap".toLabel()).left().fillX()
|
||||
|
||||
// The meaning of the values needs a formula to be synchronized between here and
|
||||
// [Minimap.init]. It goes off-10%-11%..29%-30%-35%-40%-45%-50% - and the percentages
|
||||
@ -203,10 +350,121 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
|
||||
if (previousScreen is WorldScreen)
|
||||
previousScreen.shouldUpdate = true
|
||||
}
|
||||
optionsTable.add(minimapSlider).pad(10f).row()
|
||||
add(minimapSlider).pad(10f).row()
|
||||
}
|
||||
|
||||
private fun addSetUserId() {
|
||||
private fun Table.addResolutionSelectBox() {
|
||||
add("Resolution".toLabel()).left().fillX()
|
||||
|
||||
val resolutionSelectBox = SelectBox<String>(skin)
|
||||
resolutionSelectBox.items = resolutionArray
|
||||
resolutionSelectBox.selected = settings.resolution
|
||||
add(resolutionSelectBox).minWidth(selectBoxMinWidth).pad(10f).row()
|
||||
|
||||
resolutionSelectBox.onChange {
|
||||
settings.resolution = resolutionSelectBox.selected
|
||||
reloadWorldAndOptions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Table.addTileSetSelectBox() {
|
||||
add("Tileset".toLabel()).left().fillX()
|
||||
|
||||
val tileSetSelectBox = SelectBox<String>(skin)
|
||||
val tileSetArray = GdxArray<String>()
|
||||
val tileSets = ImageGetter.getAvailableTilesets()
|
||||
for (tileset in tileSets) tileSetArray.add(tileset)
|
||||
tileSetSelectBox.items = tileSetArray
|
||||
tileSetSelectBox.selected = settings.tileSet
|
||||
add(tileSetSelectBox).minWidth(selectBoxMinWidth).pad(10f).row()
|
||||
|
||||
tileSetSelectBox.onChange {
|
||||
settings.tileSet = tileSetSelectBox.selected
|
||||
TileSetCache.assembleTileSetConfigs()
|
||||
reloadWorldAndOptions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Table.addSoundEffectsVolumeSlider() {
|
||||
add("Sound effects volume".tr()).left().fillX()
|
||||
|
||||
val soundEffectsVolumeSlider = UncivSlider(0f, 1.0f, 0.1f,
|
||||
initial = settings.soundEffectsVolume
|
||||
) {
|
||||
settings.soundEffectsVolume = it
|
||||
settings.save()
|
||||
}
|
||||
add(soundEffectsVolumeSlider).pad(5f).row()
|
||||
}
|
||||
|
||||
private fun Table.addMusicVolumeSlider() {
|
||||
add("Music volume".tr()).left().fillX()
|
||||
|
||||
val musicVolumeSlider = UncivSlider(0f, 1.0f, 0.1f,
|
||||
initial = settings.musicVolume,
|
||||
sound = UncivSound.Silent
|
||||
) {
|
||||
settings.musicVolume = it
|
||||
settings.save()
|
||||
|
||||
val music = previousScreen.game.music
|
||||
if (music == null) // restart music, if it was off at the app start
|
||||
thread(name = "Music") { previousScreen.game.startMusic() }
|
||||
|
||||
music?.volume = 0.4f * it
|
||||
}
|
||||
musicVolumeSlider.value = settings.musicVolume
|
||||
add(musicVolumeSlider).pad(5f).row()
|
||||
}
|
||||
|
||||
private fun Table.addDownloadMusic(musicLocation: FileHandle) {
|
||||
val downloadMusicButton = "Download music".toTextButton()
|
||||
add(downloadMusicButton).colspan(2).row()
|
||||
val errorTable = Table()
|
||||
add(errorTable).colspan(2).row()
|
||||
|
||||
downloadMusicButton.onClick {
|
||||
downloadMusicButton.disable()
|
||||
errorTable.clear()
|
||||
errorTable.add("Downloading...".toLabel())
|
||||
|
||||
// So the whole game doesn't get stuck while downloading the file
|
||||
thread(name = "Music") {
|
||||
try {
|
||||
val file = DropBox.downloadFile("/Music/thatched-villagers.mp3")
|
||||
musicLocation.write(file, false)
|
||||
Gdx.app.postRunnable {
|
||||
tabs.replacePage("Sound", getSoundTab())
|
||||
previousScreen.game.startMusic()
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Gdx.app.postRunnable {
|
||||
errorTable.clear()
|
||||
errorTable.add("Could not download music!".toLabel(Color.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Table.addMultiplayerTurnCheckerDelayBox() {
|
||||
add("Time between turn checks out-of-game (in minutes)".toLabel()).left().fillX()
|
||||
|
||||
val checkDelaySelectBox = SelectBox<Int>(skin)
|
||||
val possibleDelaysArray = GdxArray<Int>()
|
||||
possibleDelaysArray.addAll(1, 2, 5, 15)
|
||||
checkDelaySelectBox.items = possibleDelaysArray
|
||||
checkDelaySelectBox.selected = settings.multiplayerTurnCheckerDelayInMinutes
|
||||
|
||||
add(checkDelaySelectBox).pad(10f).row()
|
||||
|
||||
checkDelaySelectBox.onChange {
|
||||
settings.multiplayerTurnCheckerDelayInMinutes = checkDelaySelectBox.selected
|
||||
settings.save()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Table.addSetUserId() {
|
||||
val idSetLabel = "".toLabel()
|
||||
val takeUserIdFromClipboardButton = "Take user ID from clipboard".toTextButton()
|
||||
.onClick {
|
||||
@ -225,27 +483,28 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
|
||||
idSetLabel.setFontColor(Color.RED).setText("Invalid ID!".tr())
|
||||
}
|
||||
}
|
||||
optionsTable.add(takeUserIdFromClipboardButton).pad(5f).colspan(2).row()
|
||||
optionsTable.add(idSetLabel).colspan(2).row()
|
||||
add(takeUserIdFromClipboardButton).pad(5f).colspan(2).row()
|
||||
add(idSetLabel).colspan(2).row()
|
||||
}
|
||||
|
||||
private fun addNotificationOptions() {
|
||||
if (Gdx.app.type == Application.ApplicationType.Android) {
|
||||
addHeader("Multiplayer options")
|
||||
private fun Table.addAutosaveTurnsSelectBox() {
|
||||
add("Turns between autosaves".toLabel()).left().fillX()
|
||||
|
||||
addYesNoRow("Enable out-of-game turn notifications", settings.multiplayerTurnCheckerEnabled)
|
||||
{ settings.multiplayerTurnCheckerEnabled = it }
|
||||
val autosaveTurnsSelectBox = SelectBox<Int>(skin)
|
||||
val autosaveTurnsArray = GdxArray<Int>()
|
||||
autosaveTurnsArray.addAll(1, 2, 5, 10)
|
||||
autosaveTurnsSelectBox.items = autosaveTurnsArray
|
||||
autosaveTurnsSelectBox.selected = settings.turnsBetweenAutosaves
|
||||
|
||||
if (settings.multiplayerTurnCheckerEnabled) {
|
||||
addMultiplayerTurnCheckerDelayBox()
|
||||
add(autosaveTurnsSelectBox).pad(10f).row()
|
||||
|
||||
addYesNoRow("Show persistent notification for turn notifier service", settings.multiplayerTurnCheckerPersistentNotificationEnabled)
|
||||
{ settings.multiplayerTurnCheckerPersistentNotificationEnabled = it }
|
||||
}
|
||||
autosaveTurnsSelectBox.onChange {
|
||||
settings.turnsBetweenAutosaves = autosaveTurnsSelectBox.selected
|
||||
settings.save()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTranslationGeneration() {
|
||||
private fun Table.addTranslationGeneration() {
|
||||
if (Gdx.app.type == Application.ApplicationType.Desktop) {
|
||||
val generateTranslationsButton = "Generate translation files".toTextButton()
|
||||
val generateAction = {
|
||||
@ -259,206 +518,38 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
|
||||
generateTranslationsButton.onClick(generateAction)
|
||||
keyPressDispatcher[Input.Keys.F12] = generateAction
|
||||
generateTranslationsButton.addTooltip("F12",18f)
|
||||
optionsTable.add(generateTranslationsButton).colspan(2).row()
|
||||
add(generateTranslationsButton).colspan(2).row()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addModCheckerPopup() {
|
||||
//if (RulesetCache.isEmpty()) return
|
||||
val modCheckerButton = "Locate mod errors".toTextButton()
|
||||
modCheckerButton.onClick {
|
||||
val lines = ArrayList<String>()
|
||||
for (mod in RulesetCache.values) {
|
||||
val modLinks = mod.checkModLinks()
|
||||
if (modLinks.isNotOK()) {
|
||||
lines += ""
|
||||
lines += mod.name
|
||||
lines += ""
|
||||
lines += modLinks.message
|
||||
lines += ""
|
||||
}
|
||||
}
|
||||
if (lines.isEmpty()) lines += "{No problems found}."
|
||||
val popup = Popup(screen)
|
||||
popup.name = "ModCheckerPopup"
|
||||
popup.add(ScrollPane(lines.joinToString("\n").toLabel()).apply { setOverscroll(false, false) })
|
||||
.maxHeight(screen.stage.height / 2).row()
|
||||
popup.addCloseButton()
|
||||
popup.open(true)
|
||||
}
|
||||
optionsTable.add(modCheckerButton).colspan(2).row()
|
||||
}
|
||||
|
||||
private fun addSoundEffectsVolumeSlider() {
|
||||
optionsTable.add("Sound effects volume".tr())
|
||||
|
||||
val soundEffectsVolumeSlider = UncivSlider(0f, 1.0f, 0.1f,
|
||||
initial = settings.soundEffectsVolume
|
||||
) {
|
||||
settings.soundEffectsVolume = it
|
||||
private fun Table.addYesNoRow(text: String, initialValue: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
|
||||
val wrapWidth = tabs.prefWidth - 60f
|
||||
add(WrappableLabel(text, wrapWidth).apply { wrap = true })
|
||||
.left().fillX()
|
||||
.maxWidth(wrapWidth)
|
||||
val button = YesNoButton(initialValue, CameraStageBaseScreen.skin) {
|
||||
action(it)
|
||||
settings.save()
|
||||
if (updateWorld && previousScreen is WorldScreen)
|
||||
previousScreen.shouldUpdate = true
|
||||
}
|
||||
optionsTable.add(soundEffectsVolumeSlider).pad(5f).row()
|
||||
add(button).row()
|
||||
}
|
||||
|
||||
private fun addMusicVolumeSlider() {
|
||||
val musicLocation = Gdx.files.local(previousScreen.game.musicLocation)
|
||||
if (musicLocation.exists()) {
|
||||
optionsTable.add("Music volume".tr())
|
||||
//endregion
|
||||
|
||||
val musicVolumeSlider = UncivSlider(0f, 1.0f, 0.1f,
|
||||
initial = settings.musicVolume,
|
||||
sound = UncivSound.Silent
|
||||
) {
|
||||
settings.musicVolume = it
|
||||
settings.save()
|
||||
|
||||
val music = previousScreen.game.music
|
||||
if (music == null) // restart music, if it was off at the app start
|
||||
thread(name = "Music") { previousScreen.game.startMusic() }
|
||||
|
||||
music?.volume = 0.4f * it
|
||||
}
|
||||
musicVolumeSlider.value = settings.musicVolume
|
||||
optionsTable.add(musicVolumeSlider).pad(5f).row()
|
||||
} else {
|
||||
val downloadMusicButton = "Download music".toTextButton()
|
||||
optionsTable.add(downloadMusicButton).colspan(2).row()
|
||||
val errorTable = Table()
|
||||
optionsTable.add(errorTable).colspan(2).row()
|
||||
|
||||
downloadMusicButton.onClick {
|
||||
downloadMusicButton.disable()
|
||||
errorTable.clear()
|
||||
errorTable.add("Downloading...".toLabel())
|
||||
|
||||
// So the whole game doesn't get stuck while downloading the file
|
||||
thread(name = "Music") {
|
||||
try {
|
||||
val file = DropBox.downloadFile("/Music/thatched-villagers.mp3")
|
||||
musicLocation.write(file, false)
|
||||
Gdx.app.postRunnable {
|
||||
rebuildOptionsTable()
|
||||
previousScreen.game.startMusic()
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Gdx.app.postRunnable {
|
||||
errorTable.clear()
|
||||
errorTable.add("Could not download music!".toLabel(Color.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addResolutionSelectBox() {
|
||||
optionsTable.add("Resolution".toLabel())
|
||||
|
||||
val resolutionSelectBox = SelectBox<String>(skin)
|
||||
resolutionSelectBox.items = resolutionArray
|
||||
resolutionSelectBox.selected = settings.resolution
|
||||
optionsTable.add(resolutionSelectBox).minWidth(240f).pad(10f).row()
|
||||
|
||||
resolutionSelectBox.onChange {
|
||||
settings.resolution = resolutionSelectBox.selected
|
||||
reloadWorldAndOptions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTileSetSelectBox() {
|
||||
optionsTable.add("Tileset".toLabel())
|
||||
|
||||
val tileSetSelectBox = SelectBox<String>(skin)
|
||||
val tileSetArray = GdxArray<String>()
|
||||
val tileSets = ImageGetter.getAvailableTilesets()
|
||||
for (tileset in tileSets) tileSetArray.add(tileset)
|
||||
tileSetSelectBox.items = tileSetArray
|
||||
tileSetSelectBox.selected = settings.tileSet
|
||||
optionsTable.add(tileSetSelectBox).minWidth(240f).pad(10f).row()
|
||||
|
||||
tileSetSelectBox.onChange {
|
||||
settings.tileSet = tileSetSelectBox.selected
|
||||
TileSetCache.assembleTileSetConfigs()
|
||||
reloadWorldAndOptions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAutosaveTurnsSelectBox() {
|
||||
optionsTable.add("Turns between autosaves".toLabel())
|
||||
|
||||
val autosaveTurnsSelectBox = SelectBox<Int>(skin)
|
||||
val autosaveTurnsArray = GdxArray<Int>()
|
||||
autosaveTurnsArray.addAll(1, 2, 5, 10)
|
||||
autosaveTurnsSelectBox.items = autosaveTurnsArray
|
||||
autosaveTurnsSelectBox.selected = settings.turnsBetweenAutosaves
|
||||
|
||||
optionsTable.add(autosaveTurnsSelectBox).pad(10f).row()
|
||||
|
||||
autosaveTurnsSelectBox.onChange {
|
||||
settings.turnsBetweenAutosaves = autosaveTurnsSelectBox.selected
|
||||
settings.save()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMultiplayerTurnCheckerDelayBox() {
|
||||
optionsTable.add("Time between turn checks out-of-game (in minutes)".toLabel())
|
||||
|
||||
val checkDelaySelectBox = SelectBox<Int>(skin)
|
||||
val possibleDelaysArray = GdxArray<Int>()
|
||||
possibleDelaysArray.addAll(1, 2, 5, 15)
|
||||
checkDelaySelectBox.items = possibleDelaysArray
|
||||
checkDelaySelectBox.selected = settings.multiplayerTurnCheckerDelayInMinutes
|
||||
|
||||
optionsTable.add(checkDelaySelectBox).pad(10f).row()
|
||||
|
||||
checkDelaySelectBox.onChange {
|
||||
settings.multiplayerTurnCheckerDelayInMinutes = checkDelaySelectBox.selected
|
||||
settings.save()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addLanguageSelectBox() {
|
||||
val languageSelectBox = SelectBox<Language>(skin)
|
||||
val languageArray = GdxArray<Language>()
|
||||
previousScreen.game.translations.percentCompleteOfLanguages
|
||||
.map { Language(it.key, if (it.key == "English") 100 else it.value) }
|
||||
.sortedByDescending { it.percentComplete }
|
||||
.forEach { languageArray.add(it) }
|
||||
if (languageArray.size == 0) return
|
||||
|
||||
optionsTable.add("Language".toLabel())
|
||||
languageSelectBox.items = languageArray
|
||||
val matchingLanguage = languageArray.firstOrNull { it.language == settings.language }
|
||||
languageSelectBox.selected = matchingLanguage ?: languageArray.first()
|
||||
optionsTable.add(languageSelectBox).minWidth(240f).pad(10f).row()
|
||||
|
||||
languageSelectBox.onChange {
|
||||
// Sometimes the "changed" is triggered even when we didn't choose something
|
||||
selectedLanguage = languageSelectBox.selected.language
|
||||
|
||||
if (selectedLanguage != settings.language)
|
||||
selectLanguage()
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectLanguage() {
|
||||
settings.language = selectedLanguage
|
||||
previousScreen.game.translations.tryReadTranslationForCurrentLanguage()
|
||||
reloadWorldAndOptions()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
This TextButton subclass helps to keep looks and behaviour of our Yes/No
|
||||
in one place, but it also helps keeping context for those action lambdas.
|
||||
|
||||
Usage: YesNoButton(someSetting: Boolean, skin) { someSetting = it; sideEffects() }
|
||||
/**
|
||||
* This TextButton subclass helps to keep looks and behaviour of our Yes/No
|
||||
* in one place, but it also helps keeping context for those action lambdas.
|
||||
*
|
||||
* Usage: YesNoButton(someSetting: Boolean, skin) { someSetting = it; sideEffects() }
|
||||
*/
|
||||
private fun Boolean.toYesNo(): String = (if (this) Constants.yes else Constants.no).tr()
|
||||
private class YesNoButton(initialValue: Boolean, skin: Skin, action: (Boolean) -> Unit)
|
||||
: TextButton (initialValue.toYesNo(), skin ) {
|
||||
private class YesNoButton(
|
||||
initialValue: Boolean,
|
||||
skin: Skin,
|
||||
action: (Boolean) -> Unit
|
||||
) : TextButton (initialValue.toYesNo(), skin ) {
|
||||
|
||||
var value = initialValue
|
||||
private set(value) {
|
||||
@ -473,4 +564,9 @@ private class YesNoButton(initialValue: Boolean, skin: Skin, action: (Boolean) -
|
||||
action.invoke(value)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun Boolean.toYesNo(): String = (if (this) Constants.yes else Constants.no).tr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|