Tabbed options (#5081)

* Tabbed Options Screen

* Tabbed Options Screen - atlas
This commit is contained in:
SomeTroglodyte
2021-09-04 20:30:39 +02:00
committed by GitHub
parent 60500d17a6
commit 6bc58ab5a3
14 changed files with 1655 additions and 1080 deletions

View File

@ -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.

View File

@ -1,69 +1,31 @@
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.*
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
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)
}
}
class LanguagePickerScreen : PickerScreen(){
/** 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(){
fun update() {
languageTables.forEach { it.update(chosenLanguage) }
}
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())
@ -89,4 +50,4 @@ class LanguagePickerScreen : PickerScreen(){
game.setScreen(MainMenuScreen())
dispose()
}
}
}

View 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
}
}
}

View 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)
}
}
}

View 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()
}

View File

@ -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%"
}
}
class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScreen) {
private var selectedLanguage: String = "English"
/**
* 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 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,16 +174,21 @@ 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) {
settings.autoAssignCityProduction = it
if (it && previousScreen is WorldScreen &&
previousScreen.viewingCiv.isCurrentPlayer() && previousScreen.viewingCiv.playerType == PlayerType.Human) {
previousScreen.viewingCiv.isCurrentPlayer() && previousScreen.viewingCiv.playerType == PlayerType.Human) {
previousScreen.gameInfo.currentPlayerCiv.cities.forEach { city ->
city.cityConstructions.chooseNextConstruction()
}
@ -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(),
settings.showExperimentalWorldWrap) {
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(),
settings.showExperimentalReligion) {
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()
addSetUserId()
}
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,49 +350,161 @@ 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() {
val idSetLabel = "".toLabel()
val takeUserIdFromClipboardButton = "Take user ID from clipboard".toTextButton()
.onClick {
try {
val clipboardContents = Gdx.app.clipboard.contents.trim()
UUID.fromString(clipboardContents)
YesNoPopup("Doing this will reset your current user ID to the clipboard contents - are you sure?",
{
settings.userId = clipboardContents
settings.save()
idSetLabel.setFontColor(Color.WHITE).setText("ID successfully set!".tr())
}, previousScreen).open(true)
idSetLabel.isVisible = true
} catch (ex: Exception) {
idSetLabel.isVisible = true
idSetLabel.setFontColor(Color.RED).setText("Invalid ID!".tr())
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))
}
}
optionsTable.add(takeUserIdFromClipboardButton).pad(5f).colspan(2).row()
optionsTable.add(idSetLabel).colspan(2).row()
}
private fun addNotificationOptions() {
if (Gdx.app.type == Application.ApplicationType.Android) {
addHeader("Multiplayer options")
addYesNoRow("Enable out-of-game turn notifications", settings.multiplayerTurnCheckerEnabled)
{ settings.multiplayerTurnCheckerEnabled = it }
if (settings.multiplayerTurnCheckerEnabled) {
addMultiplayerTurnCheckerDelayBox()
addYesNoRow("Show persistent notification for turn notifier service", settings.multiplayerTurnCheckerPersistentNotificationEnabled)
{ settings.multiplayerTurnCheckerPersistentNotificationEnabled = it }
}
}
}
private fun addTranslationGeneration() {
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 {
try {
val clipboardContents = Gdx.app.clipboard.contents.trim()
UUID.fromString(clipboardContents)
YesNoPopup("Doing this will reset your current user ID to the clipboard contents - are you sure?",
{
settings.userId = clipboardContents
settings.save()
idSetLabel.setFontColor(Color.WHITE).setText("ID successfully set!".tr())
}, previousScreen).open(true)
idSetLabel.isVisible = true
} catch (ex: Exception) {
idSetLabel.isVisible = true
idSetLabel.setFontColor(Color.RED).setText("Invalid ID!".tr())
}
}
add(takeUserIdFromClipboardButton).pad(5f).colspan(2).row()
add(idSetLabel).colspan(2).row()
}
private fun Table.addAutosaveTurnsSelectBox() {
add("Turns between autosaves".toLabel()).left().fillX()
val autosaveTurnsSelectBox = SelectBox<Int>(skin)
val autosaveTurnsArray = GdxArray<Int>()
autosaveTurnsArray.addAll(1, 2, 5, 10)
autosaveTurnsSelectBox.items = autosaveTurnsArray
autosaveTurnsSelectBox.selected = settings.turnsBetweenAutosaves
add(autosaveTurnsSelectBox).pad(10f).row()
autosaveTurnsSelectBox.onChange {
settings.turnsBetweenAutosaves = autosaveTurnsSelectBox.selected
settings.save()
}
}
private fun Table.addTranslationGeneration() {
if (Gdx.app.type == Application.ApplicationType.Desktop) {
val generateTranslationsButton = "Generate translation files".toTextButton()
val generateAction = {
@ -259,218 +518,55 @@ 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()
/**
* 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 class YesNoButton(
initialValue: Boolean,
skin: Skin,
action: (Boolean) -> Unit
) : TextButton (initialValue.toYesNo(), skin ) {
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
var value = initialValue
private set(value) {
field = value
setText(value.toYesNo())
}
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))
}
}
}
init {
color = ImageGetter.getBlue()
onClick {
value = !value
action.invoke(value)
}
}
}
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() }
*/
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 ) {
var value = initialValue
private set(value) {
field = value
setText(value.toYesNo())
}
init {
color = ImageGetter.getBlue()
onClick {
value = !value
action.invoke(value)
companion object {
fun Boolean.toYesNo(): String = (if (this) Constants.yes else Constants.no).tr()
}
}
}