diff --git a/android/assets/jsons/translations/Brazilian_Portuguese.properties b/android/assets/jsons/translations/Brazilian_Portuguese.properties index 8f772b28c3..f70ad48c7e 100644 --- a/android/assets/jsons/translations/Brazilian_Portuguese.properties +++ b/android/assets/jsons/translations/Brazilian_Portuguese.properties @@ -268,8 +268,7 @@ Radius = # Requires translation! Enable Religion = -Show advanced settings = Mostrar configurações avançadas -Hide advanced settings = Esconder configurações avançadas +Advanced Settings = Configurações Avançadas # Requires translation! RNG Seed = Map Height = Altura do Mapa diff --git a/android/assets/jsons/translations/French.properties b/android/assets/jsons/translations/French.properties index b6d95768af..e20d15b3b3 100644 --- a/android/assets/jsons/translations/French.properties +++ b/android/assets/jsons/translations/French.properties @@ -255,8 +255,7 @@ Radius = # Requires translation! Enable Religion = -Show advanced settings = Afficher les paramètres avancés -Hide advanced settings = Cacher les paramètres avancés +Advanced Settings = Paramètres Avancés RNG Seed = Graine du générateur aléatoire Map Height = Altitude moyenne Temperature extremeness = Amplitude thermique diff --git a/android/assets/jsons/translations/German.properties b/android/assets/jsons/translations/German.properties index c97f13c92b..7b19278c77 100644 --- a/android/assets/jsons/translations/German.properties +++ b/android/assets/jsons/translations/German.properties @@ -130,7 +130,7 @@ You refused to stop settling cities near us = Ihr habt euch geweigert, auf Stadt Your arrogant demands are in bad taste = Eure arroganten Forderungen sind geschmacklos. Your use of nuclear weapons is disgusting! = Euer Einsatz von Atomwaffen ist ekelhaft! You have stolen our lands! = Ihr habt unser Land geraubt! -You gave us units! = Ihr habt uns Einheiten geliefert! +You gave us units! = Ihr habt uns Einheiten geschenkt! Demands = Forderungen Please don't settle new cities near us. = Bitte gründet keine neuen Städte in unserer Nähe. @@ -238,23 +238,18 @@ Cultural = Kulturell Map Shape = Kartenform Hexagonal = Sechseckig Rectangular = Rechteckig - # Requires translation! -Height = - # Requires translation! -Width = - # Requires translation! -Radius = - # Requires translation! -Enable Religion = +Height = Höhe +Width = Breite +Radius = Radius +Enable Religion = Religion aktivieren -Show advanced settings = Zeige erweiterte Einstellungen -Hide advanced settings = Verstecke erweiterte Einstellungen +Advanced Settings = Fortgeschrittene Einstellungen RNG Seed = Seed Map Height = Erhebungen Temperature extremeness = Temperaturextreme Resource richness = Ressourcenreichtum Vegetation richness = Vegetationsreichtum -Rare features richness = Reichtum an seltenen Merkmalen +Rare features richness = Außergewöhnliches Gelände Max Coast extension = Maximale Küstenausdehnung Biome areas extension = Biombereichausdehnung Water level = Wasser-Niveau @@ -564,8 +559,7 @@ Pillage = Plündern Are you sure you want to pillage this [improvement]? = Bist du sicher, dass du die Feldverbesserung [improvement] plündern willst? Create [improvement] = Erzeuge [improvement] Start Golden Age = Goldenes Zeitalter starten - # Requires translation! -Show more = +Show more = Weitere Befehle Yes = Ja No = Nein Acquire = Übernehmen @@ -577,8 +571,7 @@ Gold = Gold Happiness = Zufriedenheit Culture = Kultur Science = Wissenschaft - # Requires translation! -Faith = +Faith = Glaube Crop Yield = Ernteertrag Territory = Territorium @@ -746,8 +739,7 @@ Luxury resource = Luxus-ressource Strategic resource = Strategische Ressource Fresh water = Frischwasser non-fresh water = nicht frisches Wasser - # Requires translation! -Natural Wonder = +Natural Wonder = Naturwunder # improvementFilters @@ -1299,8 +1291,7 @@ Colosseum = Kolosseum 'Regard your soldiers as your children, and they will follow you into the deepest valleys; look on them as your own beloved sons, and they will stand by you even unto death.' - Sun Tzu = 'Behandle Deine Soldaten als seien sie Deine Kinder und sie werden Dir in die tiefsten Täler folgen; betrachte sie als Deine geliebten Söhne und sie werden an Deiner Seite stehen, sogar bis zum Tode.' - Sun Tzu Terracotta Army = Terrakotta-Armee - # Requires translation! -Temple = +Temple = Tempel National College = Nationale Hochschule diff --git a/android/assets/jsons/translations/Italian.properties b/android/assets/jsons/translations/Italian.properties index 23729c212d..01a9cc1b0b 100644 --- a/android/assets/jsons/translations/Italian.properties +++ b/android/assets/jsons/translations/Italian.properties @@ -247,8 +247,7 @@ Radius = # Requires translation! Enable Religion = -Show advanced settings = Avanzate -Hide advanced settings = Nascondi avanzate +Advanced settings = Avanzate RNG Seed = Seme RNG Map Height = Altezza mappa Temperature extremeness = Estremità temperatura diff --git a/android/assets/jsons/translations/Spanish.properties b/android/assets/jsons/translations/Spanish.properties index 6e7f76e274..b005d9d3b1 100644 --- a/android/assets/jsons/translations/Spanish.properties +++ b/android/assets/jsons/translations/Spanish.properties @@ -247,8 +247,7 @@ Radius = # Requires translation! Enable Religion = -Show advanced settings = Mostrar Opciones Avanzadas -Hide advanced settings = Ocultar Opciones Avanzadas +Advanced Settings = Opciones Avanzadas RNG Seed = Semilla RNG Map Height = Elevación del Mapa Temperature extremeness = Temperatura Extrema diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 93ed1ca67e..1e258edd56 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -244,8 +244,7 @@ Width = Radius = Enable Religion = -Show advanced settings = -Hide advanced settings = +Advanced Settings = RNG Seed = Map Height = Temperature extremeness = diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index b9a97bb8af..b17a18ff6b 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -192,8 +192,8 @@ class UncivGame(parameters: UncivGameParameters) : Game() { autoSaveThread.join() } else GameSaver.autoSaveSingleThreaded(gameInfo) // NO new thread - settings.save() } + settings.save() threadList.filter { it !== Thread.currentThread() && it.name != "DestroyJavaVM"}.forEach { println (" Thread ${it.name} still running in UncivGame.dispose().") diff --git a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt index 3d13cc448d..f969b1017b 100644 --- a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt @@ -1,22 +1,18 @@ package com.unciv.ui.newgamescreen -import com.badlogic.gdx.scenes.scene2d.ui.CheckBox -import com.badlogic.gdx.scenes.scene2d.ui.SelectBox import com.badlogic.gdx.scenes.scene2d.ui.Table -import com.badlogic.gdx.utils.Array import com.unciv.UncivGame import com.unciv.models.metadata.BaseRuleset import com.unciv.models.metadata.GameSpeed import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.VictoryType -import com.unciv.models.translations.tr -import com.unciv.ui.utils.CameraStageBaseScreen -import com.unciv.ui.utils.ImageGetter -import com.unciv.ui.utils.onChange -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.* -class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPickerTable:(desiredCiv:String)->Unit) - : Table(CameraStageBaseScreen.skin) { +class GameOptionsTable( + val previousScreen: IPreviousScreen, + val withoutMods: Boolean = false, + val updatePlayerPickerTable:(desiredCiv:String)->Unit +) : Table(CameraStageBaseScreen.skin) { var gameParameters = previousScreen.gameSetupInfo.gameParameters val ruleset = previousScreen.ruleset var locked = false @@ -34,36 +30,38 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick top() defaults().pad(5f) - add("Game Options".toLabel(fontSize = 24)).padTop(0f).padBottom(20f).colspan(2).row() add(Table().apply { defaults().pad(5f) - //addBaseRulesetSelectBox() + addBaseRulesetSelectBox() addDifficultySelectBox() addGameSpeedSelectBox() addEraSelectBox() - }).colspan(2).row() - addCityStatesSelectBox() + // align left and right edges with other SelectBoxes but allow independent dropdown width + add(Table().apply { + addCityStatesSlider() + }).colspan(2).fillX().row() + }).row() addVictoryTypeCheckboxes() - val checkboxTable = Table().apply { defaults().pad(5f) } + val checkboxTable = Table().apply { defaults().left().pad(2.5f) } checkboxTable.addBarbariansCheckbox() checkboxTable.addOneCityChallengeCheckbox() checkboxTable.addNuclearWeaponsCheckbox() checkboxTable.addIsOnlineMultiplayerCheckbox() if (UncivGame.Current.settings.showExperimentalReligion) checkboxTable.addReligionCheckbox() - checkboxTable.addModCheckboxes() - add(checkboxTable).colspan(2).row() + add(checkboxTable).center().row() + + if (!withoutMods) + add(getModCheckboxes()).row() pack() } private fun Table.addCheckbox(text: String, initialState: Boolean, lockable: Boolean = true, onChange: (newValue: Boolean) -> Unit) { - val checkbox = CheckBox(text.tr(), CameraStageBaseScreen.skin) - checkbox.isChecked = initialState + val checkbox = text.toCheckBox(initialState) { onChange(it) } checkbox.isDisabled = lockable && locked - checkbox.onChange { onChange(checkbox.isChecked) } - add(checkbox).colspan(2).left().row() + add(checkbox).colspan(2).row() } private fun Table.addBarbariansCheckbox() = @@ -90,25 +88,20 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick addCheckbox("Enable Religion", gameParameters.religionEnabled) { gameParameters.religionEnabled = it } - private fun addCityStatesSelectBox() { - add("{Number of City-States}:".toLabel()) - val cityStatesSelectBox = SelectBox(CameraStageBaseScreen.skin) - + private fun Table.addCityStatesSlider() { val numberOfCityStates = ruleset.nations.filter { it.value.isCityState() }.size + if (numberOfCityStates == 0) return - val cityStatesArray = Array(numberOfCityStates + 1) - (0..numberOfCityStates).forEach { cityStatesArray.add(it) } - - cityStatesSelectBox.items = cityStatesArray - cityStatesSelectBox.selected = gameParameters.numberOfCityStates - add(cityStatesSelectBox).width(50f).row() - cityStatesSelectBox.isDisabled = locked - cityStatesSelectBox.onChange { - gameParameters.numberOfCityStates = cityStatesSelectBox.selected + add("{Number of City-States}:".toLabel()).left().expandX() + val slider = UncivSlider(0f,numberOfCityStates.toFloat(),1f) { + gameParameters.numberOfCityStates = it.toInt() } + slider.value = gameParameters.numberOfCityStates.toFloat() + slider.isDisabled = locked + add(slider).row() } - fun Table.addSelectBox(text: String, values: Collection, initialState: String, onChange: (newValue: String) -> Unit) { + private fun Table.addSelectBox(text: String, values: Collection, initialState: String, onChange: (newValue: String) -> Unit) { add(text.toLabel()).left() val selectBox = TranslatedSelectBox(values, initialState, CameraStageBaseScreen.skin) selectBox.isDisabled = locked @@ -123,6 +116,7 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick } private fun Table.addBaseRulesetSelectBox() { + if (BaseRuleset.values().size < 2) return addSelectBox("{Base Ruleset}:", BaseRuleset.values().map { it.fullName }, gameParameters.baseRuleset.fullName) { gameParameters.baseRuleset = BaseRuleset.values().first { br -> br.fullName == it } @@ -152,18 +146,16 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick val victoryConditionsTable = Table().apply { defaults().pad(5f) } for (victoryType in VictoryType.values()) { if (victoryType == VictoryType.Neutral) continue - val victoryCheckbox = CheckBox(victoryType.name.tr(), CameraStageBaseScreen.skin) - victoryCheckbox.name = victoryType.name - victoryCheckbox.isChecked = gameParameters.victoryTypes.contains(victoryType) - victoryCheckbox.isDisabled = locked - victoryCheckbox.onChange { + val victoryCheckbox = victoryType.name.toCheckBox(gameParameters.victoryTypes.contains(victoryType)) { // If the checkbox is checked, adds the victoryTypes else remove it - if (victoryCheckbox.isChecked) { + if (it) { gameParameters.victoryTypes.add(victoryType) } else { gameParameters.victoryTypes.remove(victoryType) } } + victoryCheckbox.name = victoryType.name + victoryCheckbox.isDisabled = locked victoryConditionsTable.add(victoryCheckbox).left() if (++i % 2 == 0) victoryConditionsTable.row() } @@ -180,8 +172,8 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick ImageGetter.setNewRuleset(ruleset) } - fun Table.addModCheckboxes() { - val table = ModCheckboxTable(gameParameters.mods, previousScreen as CameraStageBaseScreen) { + fun getModCheckboxes(isPortrait: Boolean = false): Table { + return ModCheckboxTable(gameParameters.mods, previousScreen as CameraStageBaseScreen, isPortrait) { UncivGame.Current.translations.translationActiveMods = gameParameters.mods reloadRuleset() update() @@ -196,7 +188,6 @@ class GameOptionsTable(val previousScreen: IPreviousScreen, val updatePlayerPick updatePlayerPickerTable(desiredCiv) } - add(table).row() } } diff --git a/core/src/com/unciv/ui/newgamescreen/IPreviousScreen.kt b/core/src/com/unciv/ui/newgamescreen/IPreviousScreen.kt index 138a6861c7..1d0d9d090c 100644 --- a/core/src/com/unciv/ui/newgamescreen/IPreviousScreen.kt +++ b/core/src/com/unciv/ui/newgamescreen/IPreviousScreen.kt @@ -12,9 +12,6 @@ interface IPreviousScreen { var stage: Stage val ruleset: Ruleset - /** - * Method added for compatibility with [PlayerPickerTable] which addresses - * [setRightSideButtonEnabled] method of previous screen - */ - fun setRightSideButtonEnabled(boolean: Boolean) + // Having `fun setRightSideButtonEnabled(boolean: Boolean)` part of this interface gives a warning: + // "Names of the parameter #1 conflict in the following members of supertypes: 'public abstract fun setRightSideButtonEnabled(boolean: Boolean): Unit defined in com.unciv.ui.newgamescreen.IPreviousScreen, public final fun setRightSideButtonEnabled(bool: Boolean): Unit defined in com.unciv.ui.pickerscreens.PickerScreen'. This may cause problems when calling this function with named arguments." } \ No newline at end of file diff --git a/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt index 8497adef6f..b933186641 100644 --- a/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/MapOptionsTable.kt @@ -13,13 +13,14 @@ import com.unciv.ui.utils.Popup import com.unciv.ui.utils.onChange import com.unciv.ui.utils.toLabel -class MapOptionsTable(val newGameScreen: NewGameScreen): Table() { +class MapOptionsTable(private val newGameScreen: NewGameScreen): Table() { - val mapParameters = newGameScreen.gameSetupInfo.mapParameters + private val mapParameters = newGameScreen.gameSetupInfo.mapParameters private var mapTypeSpecificTable = Table() val generatedMapOptionsTable = MapParametersTable(mapParameters) private val savedMapOptionsTable = Table() lateinit var mapTypeSelectBox: TranslatedSelectBox + private val mapFileSelectBox = createMapFileSelectBox() private val mapFilesSequence = sequence { yieldAll(MapSaver.getMaps().asSequence().map { FileHandleWrapper(it) }) @@ -30,16 +31,13 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() { } } - val mapFileSelectBox = createMapFileSelectBox() - init { - defaults().pad(5f) - add("Map Options".toLabel(fontSize = 24)).top().padBottom(20f).colspan(2).row() + //defaults().pad(5f) - each nested table having the same can give 'stairs' effects, + // better control directly. Besides, the first Labels/Buttons should have 10f to look nice addMapTypeSelection() } private fun addMapTypeSelection() { - add("{Map Type}:".toLabel()) val mapTypes = arrayListOf("Generated") if (mapFilesSequence.any()) mapTypes.add(MapType.custom) mapTypeSelectBox = TranslatedSelectBox(mapTypes, "Generated", CameraStageBaseScreen.skin) @@ -47,8 +45,10 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() { savedMapOptionsTable.defaults().pad(5f) savedMapOptionsTable.add("{Map file}:".toLabel()).left() // because SOME people gotta give the hugest names to their maps - savedMapOptionsTable.add(mapFileSelectBox).maxWidth(newGameScreen.stage.width / 2) - .right().row() + val columnWidth = newGameScreen.stage.width / (if (newGameScreen.isNarrowerThan4to3()) 1 else 3) + savedMapOptionsTable.add(mapFileSelectBox) + .maxWidth((columnWidth - 120f).coerceAtLeast(120f)) + .right().row() fun updateOnMapTypeChange() { @@ -74,8 +74,11 @@ class MapOptionsTable(val newGameScreen: NewGameScreen): Table() { mapTypeSelectBox.onChange { updateOnMapTypeChange() } - add(mapTypeSelectBox).row() - add(mapTypeSpecificTable).colspan(2).row() + val mapTypeSelectWrapper = Table() // wrap to center-align Label and SelectBox easier + mapTypeSelectWrapper.add("{Map Type}:".toLabel()).left().expandX() + mapTypeSelectWrapper.add(mapTypeSelectBox).right() + add(mapTypeSelectWrapper).pad(10f).fillX().row() + add(mapTypeSpecificTable).row() } private fun createMapFileSelectBox(): SelectBox { diff --git a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt index fec0e10779..e381c1b03b 100644 --- a/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/MapParametersTable.kt @@ -8,7 +8,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextField.TextFieldFilter.DigitsOnlyFi import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.map.* -import com.unciv.models.translations.tr import com.unciv.ui.utils.* /** Table for editing [mapParameters] @@ -17,31 +16,38 @@ import com.unciv.ui.utils.* * * @param isEmptyMapAllowed whether the [MapType.empty] option should be present. Is used by the Map Editor, but should **never** be used with the New Game * */ -class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed: Boolean = false): - Table() { +class MapParametersTable( + private val mapParameters: MapParameters, + private val isEmptyMapAllowed: Boolean = false +) : Table() { + // These are accessed fom outside the class to read _and_ write values, + // namely from MapOptionsTable, NewMapScreen and NewGameScreen lateinit var mapTypeSelectBox: TranslatedSelectBox - lateinit var worldSizeSelectBox: TranslatedSelectBox - private var customWorldSizeTable = Table () - private var hexagonalSizeTable = Table() - private var rectangularSizeTable = Table() - lateinit var noRuinsCheckbox: CheckBox - lateinit var noNaturalWondersCheckbox: CheckBox - lateinit var worldWrapCheckbox: CheckBox lateinit var customMapSizeRadius: TextField lateinit var customMapWidth: TextField lateinit var customMapHeight: TextField + private lateinit var worldSizeSelectBox: TranslatedSelectBox + private var customWorldSizeTable = Table () + private var hexagonalSizeTable = Table() + private var rectangularSizeTable = Table() + private lateinit var noRuinsCheckbox: CheckBox + private lateinit var noNaturalWondersCheckbox: CheckBox + private lateinit var worldWrapCheckbox: CheckBox + + // Keep references (in the key) and settings value getters (in the value) of the 'advanced' sliders + // in a HashMap for reuse later - in the reset to defaults button. Better here as field than as closure. + // A HashMap indexed on a Widget is problematic, as it does not define its own hashCode and equals + // overrides nor is a Widget a data class. Seems to work anyway. + private val advancedSliders = HashMapFloat>() init { skin = CameraStageBaseScreen.skin - defaults().pad(5f) + defaults().pad(5f, 10f) addMapShapeSelectBox() addMapTypeSelectBox() addWorldSizeTable() - addNoRuinsCheckbox() - addNoNaturalWondersCheckbox() - if (UncivGame.Current.settings.showExperimentalWorldWrap) - addWorldWrapCheckbox() + addWrappedCheckBoxes() addAdvancedSettings() } @@ -62,7 +68,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed } private fun addMapTypeSelectBox() { - + // MapType is not an enum so we can't simply enumerate. //todo: make it so! val mapTypes = listOfNotNull( MapType.default, MapType.pangaea, @@ -154,61 +160,49 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed mapParameters.mapSize = MapSizeNew(worldSizeSelectBox.selected.value) } - private fun addNoRuinsCheckbox() { - noRuinsCheckbox = CheckBox("No Ancient Ruins".tr(), skin) - noRuinsCheckbox.isChecked = mapParameters.noRuins - noRuinsCheckbox.onChange { mapParameters.noRuins = noRuinsCheckbox.isChecked } - add(noRuinsCheckbox).colspan(2).left().row() + private fun Table.addNoRuinsCheckbox() { + noRuinsCheckbox = "No Ancient Ruins".toCheckBox(mapParameters.noRuins) { + mapParameters.noRuins = it + } + add(noRuinsCheckbox).row() } - private fun addNoNaturalWondersCheckbox() { - noNaturalWondersCheckbox = CheckBox("No Natural Wonders".tr(), skin) - noNaturalWondersCheckbox.isChecked = mapParameters.noNaturalWonders - noNaturalWondersCheckbox.onChange { - mapParameters.noNaturalWonders = noNaturalWondersCheckbox.isChecked + private fun Table.addNoNaturalWondersCheckbox() { + noNaturalWondersCheckbox = "No Natural Wonders".toCheckBox(mapParameters.noNaturalWonders) { + mapParameters.noNaturalWonders = it } - add(noNaturalWondersCheckbox).colspan(2).left().row() + add(noNaturalWondersCheckbox).row() } - private fun addWorldWrapCheckbox() { - worldWrapCheckbox = CheckBox("World Wrap".tr(), skin) - worldWrapCheckbox.isChecked = mapParameters.worldWrap - worldWrapCheckbox.onChange { - mapParameters.worldWrap = worldWrapCheckbox.isChecked + private fun Table.addWorldWrapCheckbox() { + worldWrapCheckbox = "World Wrap".toCheckBox(mapParameters.worldWrap) { + mapParameters.worldWrap = it } - add(worldWrapCheckbox).colspan(2).left().row() - add("World wrap maps are very memory intensive - creating large world wrap maps on Android can lead to crashes!" + add(worldWrapCheckbox).row() + } + + private fun addWrappedCheckBoxes() { + val showWorldWrap = UncivGame.Current.settings.showExperimentalWorldWrap + add(Table(skin).apply { + defaults().left().pad(2.5f) + addNoRuinsCheckbox() + addNoNaturalWondersCheckbox() + if (showWorldWrap) addWorldWrapCheckbox() + }).colspan(2).center().row() + if (showWorldWrap) + add("World wrap maps are very memory intensive - creating large world wrap maps on Android can lead to crashes!" .toLabel(fontSize = 14).apply { wrap=true }).colspan(2).fillX().row() } private fun addAdvancedSettings() { - - val advancedSettingsTable = getAdvancedSettingsTable() - - val button = "Show advanced settings".toTextButton() - - add(button).colspan(2).row() - val advancedSettingsCell = add(Table()).colspan(2) - row() - - button.onClick { - advancedSettingsTable.isVisible = !advancedSettingsTable.isVisible - - if (advancedSettingsTable.isVisible) { - button.setText("Hide advanced settings".tr()) - advancedSettingsCell.setActor(advancedSettingsTable) - } else { - button.setText("Show advanced settings".tr()) - advancedSettingsCell.setActor(Table()) - } + val expander = ExpanderTab("Advanced Settings", startsOutOpened = false) { + addAdvancedControls(it) } - + add(expander).pad(0f).padTop(10f).colspan(2).growX().row() } - private fun getAdvancedSettingsTable(): Table { - - val advancedSettingsTable = Table() - .apply {isVisible = false; defaults().pad(5f)} + private fun addAdvancedControls(table: Table) { + table.defaults().pad(5f) val seedTextField = TextField(mapParameters.seed.toString(), skin) seedTextField.textFieldFilter = DigitsOnlyFilter() @@ -222,17 +216,15 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed } } - advancedSettingsTable.add("RNG Seed".toLabel()).left() - advancedSettingsTable.add(seedTextField).fillX().row() - - val sliders = HashMapFloat>() + table.add("RNG Seed".toLabel()).left() + table.add(seedTextField).fillX().padBottom(10f).row() fun addSlider(text: String, getValue:()->Float, min:Float, max:Float, onChange: (value:Float)->Unit): UncivSlider { val slider = UncivSlider(min, max, (max - min) / 20, onChange = onChange) slider.value = getValue() - advancedSettingsTable.add(text.toLabel()).left() - advancedSettingsTable.add(slider).fillX().row() - sliders[slider] = getValue + table.add(text.toLabel()).left() + table.add(slider).fillX().row() + advancedSliders[slider] = getValue return slider } @@ -264,10 +256,9 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed resetToDefaultButton.onClick { mapParameters.resetAdvancedSettings() seedTextField.text = mapParameters.seed.toString() - for (entry in sliders) + for (entry in advancedSliders) entry.key.value = entry.value() } - advancedSettingsTable.add(resetToDefaultButton).colspan(2).row() - return advancedSettingsTable + table.add(resetToDefaultButton).colspan(2).padTop(10f).row() } } diff --git a/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt b/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt index ba503190ca..8c9dc337f2 100644 --- a/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/ModCheckboxTable.kt @@ -1,89 +1,99 @@ package com.unciv.ui.newgamescreen +import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.CheckBox import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset.CheckModLinksResult import com.unciv.models.ruleset.RulesetCache -import com.unciv.models.translations.tr -import com.unciv.ui.utils.CameraStageBaseScreen -import com.unciv.ui.utils.ToastPopup -import com.unciv.ui.utils.onChange -import com.unciv.ui.utils.toLabel +import com.unciv.ui.utils.* + +class ModCheckboxTable( + private val mods:LinkedHashSet, + private val screen: CameraStageBaseScreen, + isPortrait: Boolean = false, + onUpdate: (String) -> Unit +): Table(){ + private val modRulesets = RulesetCache.values.filter { it.name != "" } -class ModCheckboxTable(val mods:LinkedHashSet, val screen: CameraStageBaseScreen, onUpdate: (String) -> Unit): Table(){ init { - val modRulesets = RulesetCache.values.filter { it.name != "" } - val baseRulesetCheckboxes = ArrayList() - val extentionRulesetModButtons = ArrayList() + val extensionRulesetModButtons = ArrayList() - for (mod in modRulesets) { - val checkBox = CheckBox(mod.name.tr(), CameraStageBaseScreen.skin) - if (mod.name in mods) checkBox.isChecked = true + for (mod in modRulesets.sortedBy { it.name }) { + val checkBox = mod.name.toCheckBox(mod.name in mods) checkBox.onChange { - if (checkBox.isChecked) { - val modLinkErrors = mod.checkModLinks() - if (modLinkErrors.isNotOK()) { - ToastPopup("The mod you selected is incorrectly defined!\n\n$modLinkErrors", screen) - if (modLinkErrors.isError()) { - checkBox.isChecked = false - return@onChange - } - } - - val previousMods = mods.toList() - - if (mod.modOptions.isBaseRuleset) - for (oldBaseRuleset in previousMods) // so we don't get concurrent modification exceptions - if (modRulesets.firstOrNull { it.name == oldBaseRuleset }?.modOptions?.isBaseRuleset == true) - mods.remove(oldBaseRuleset) - mods.add(mod.name) - - var complexModLinkCheck = CheckModLinksResult() - try { - val newRuleset = RulesetCache.getComplexRuleset(mods) - newRuleset.modOptions.isBaseRuleset = true // This is so the checkModLinks finds all connections - complexModLinkCheck = newRuleset.checkModLinks() - } catch (ex: Exception) { - // This happens if a building is dependent on a tech not in the base ruleset - // because newRuleset.updateBuildingCosts() in getComplexRuleset() throws an error - complexModLinkCheck = CheckModLinksResult(Ruleset.CheckModLinksStatus.Error, ex.localizedMessage) - } - - if (complexModLinkCheck.isError()) { - ToastPopup("{The mod you selected is incompatible with the defined ruleset!}\n\n{$complexModLinkCheck}", screen) - checkBox.isChecked = false - mods.clear() - mods.addAll(previousMods) - return@onChange - } - - } else { - mods.remove(mod.name) + if (checkBoxChanged(checkBox, mod)) { + //todo: persist ExpanderTab states here + onUpdate(mod.name) } - - onUpdate(mod.name) - } if (mod.modOptions.isBaseRuleset) baseRulesetCheckboxes.add(checkBox) - else extentionRulesetModButtons.add(checkBox) + else extensionRulesetModButtons.add(checkBox) } + val padTop = if (isPortrait) 0f else 16f + if (baseRulesetCheckboxes.any()) { - add("Base ruleset mods:".toLabel(fontSize = 24)).padTop(16f).colspan(2).row() - val modCheckboxTable = Table().apply { defaults().pad(5f) } - for (checkbox in baseRulesetCheckboxes) modCheckboxTable.add(checkbox).row() - add(modCheckboxTable).colspan(2).row() + add(ExpanderTab("Base ruleset mods:") { + it.defaults().pad(5f,0f) + for (checkbox in baseRulesetCheckboxes) it.add(checkbox).row() + }).padTop(padTop).growX().row() } + if (isPortrait && baseRulesetCheckboxes.any() && extensionRulesetModButtons.any()) + addSeparator(Color.DARK_GRAY, height = 1f) - if (extentionRulesetModButtons.any()) { - add("Extension mods:".toLabel(fontSize = 24)).padTop(16f).colspan(2).row() - val modCheckboxTable = Table().apply { defaults().pad(5f) } - for (checkbox in extentionRulesetModButtons) modCheckboxTable.add(checkbox).row() - add(modCheckboxTable).colspan(2).row() + if (extensionRulesetModButtons.any()) { + add(ExpanderTab("Extension mods:") { + it.defaults().pad(5f,0f) + for (checkbox in extensionRulesetModButtons) it.add(checkbox).row() + }).padTop(padTop).growX().row() + } + } + + private fun checkBoxChanged(checkBox: CheckBox, mod: Ruleset): Boolean { + if (checkBox.isChecked) { + val modLinkErrors = mod.checkModLinks() + if (modLinkErrors.isNotOK()) { + ToastPopup("The mod you selected is incorrectly defined!\n\n$modLinkErrors", screen) + if (modLinkErrors.isError()) { + checkBox.isChecked = false + return false + } + } + + val previousMods = mods.toList() + + if (mod.modOptions.isBaseRuleset) + for (oldBaseRuleset in previousMods) // so we don't get concurrent modification exceptions + if (modRulesets.firstOrNull { it.name == oldBaseRuleset }?.modOptions?.isBaseRuleset == true) + mods.remove(oldBaseRuleset) + mods.add(mod.name) + + var complexModLinkCheck: CheckModLinksResult + try { + val newRuleset = RulesetCache.getComplexRuleset(mods) + newRuleset.modOptions.isBaseRuleset = true // This is so the checkModLinks finds all connections + complexModLinkCheck = newRuleset.checkModLinks() + } catch (ex: Exception) { + // This happens if a building is dependent on a tech not in the base ruleset + // because newRuleset.updateBuildingCosts() in getComplexRuleset() throws an error + complexModLinkCheck = CheckModLinksResult(Ruleset.CheckModLinksStatus.Error, ex.localizedMessage) + } + + if (complexModLinkCheck.isError()) { + ToastPopup("{The mod you selected is incompatible with the defined ruleset!}\n\n{$complexModLinkCheck}", screen) + checkBox.isChecked = false + mods.clear() + mods.addAll(previousMods) + return false + } + + } else { + mods.remove(mod.name) } + return true } } \ No newline at end of file diff --git a/core/src/com/unciv/ui/newgamescreen/NationTable.kt b/core/src/com/unciv/ui/newgamescreen/NationTable.kt index cb66cfc3ef..3edb818fd8 100644 --- a/core/src/com/unciv/ui/newgamescreen/NationTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/NationTable.kt @@ -41,7 +41,7 @@ class NationTable(val nation: Nation, width: Float, minHeight: Float, ruleset: R val titleText = if (ruleset == null || nation.name== Constants.random || nation.name==Constants.spectator) nation.name else nation.getLeaderDisplayName() val leaderDisplayLabel = titleText.toLabel(nation.getInnerColor(), 24) - val leaderDisplayNameMaxWidth = internalWidth - 80 // for the nation indicator + val leaderDisplayNameMaxWidth = internalWidth - 90f // 70 for the nation indicator + 20 extra if (leaderDisplayLabel.width > leaderDisplayNameMaxWidth) { // for instance Polish has really long [x] of [y] translations leaderDisplayLabel.wrap = true titleTable.add(leaderDisplayLabel).width(leaderDisplayNameMaxWidth) diff --git a/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt b/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt index c98ab45ac1..23384ee962 100644 --- a/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt +++ b/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt @@ -2,6 +2,7 @@ package com.unciv.ui.newgamescreen import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle +import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.SelectBox import com.badlogic.gdx.scenes.scene2d.ui.Skin import com.badlogic.gdx.utils.Array @@ -37,29 +38,27 @@ class GameSetupInfo(var gameId:String, var gameParameters: GameParameters, var m } } -class NewGameScreen(private val previousScreen: CameraStageBaseScreen, _gameSetupInfo: GameSetupInfo?=null): IPreviousScreen, PickerScreen(disableScroll = true) { +class NewGameScreen( + private val previousScreen: CameraStageBaseScreen, + _gameSetupInfo: GameSetupInfo? = null +): IPreviousScreen, PickerScreen() { + override val gameSetupInfo = _gameSetupInfo ?: GameSetupInfo() override var ruleset = RulesetCache.getComplexRuleset(gameSetupInfo.gameParameters.mods) // needs to be set because the GameOptionsTable etc. depend on this - var newGameOptionsTable = GameOptionsTable(this) { desiredCiv: String -> playerPickerTable.update(desiredCiv) } + private val newGameOptionsTable = GameOptionsTable(this, isNarrowerThan4to3()) { desiredCiv: String -> playerPickerTable.update(desiredCiv) } // Has to be defined before the mapOptionsTable, since the mapOptionsTable refers to it on init - private var playerPickerTable = PlayerPickerTable(this, gameSetupInfo.gameParameters) - private var mapOptionsTable = MapOptionsTable(this) - + private val playerPickerTable = PlayerPickerTable( + this, gameSetupInfo.gameParameters, + if (isNarrowerThan4to3()) stage.width - 20f else 0f + ) + private val mapOptionsTable = MapOptionsTable(this) init { setDefaultCloseAction(previousScreen) - topTable.add(ScrollPane(newGameOptionsTable).apply { setOverscroll(false, false) }) - .width(stage.width / 3).padTop(20f).top() - topTable.addSeparatorVertical() - topTable.add(ScrollPane(mapOptionsTable).apply { setOverscroll(false, false) }) - .width(stage.width / 3).padTop(20f).top() - topTable.addSeparatorVertical() - topTable.add(ScrollPane(playerPickerTable) - .apply { setOverscroll(false, false) } - .apply { setScrollingDisabled(true, false) }) - .width(stage.width / 3).padTop(20f).top() + if (isNarrowerThan4to3()) initPortrait() + else initLandscape() updateRuleset() @@ -90,7 +89,6 @@ class NewGameScreen(private val previousScreen: CameraStageBaseScreen, _gameSetu Gdx.input.inputProcessor = null // remove input processing - nothing will be clicked! - if (mapOptionsTable.mapTypeSelectBox.selected.value == MapType.custom){ val map = MapSaver.loadMap(gameSetupInfo.mapFile!!) val rulesetIncompatibilities = HashSet() @@ -136,16 +134,61 @@ class NewGameScreen(private val previousScreen: CameraStageBaseScreen, _gameSetu } } + private fun initLandscape() { + scrollPane.setScrollingDisabled(true,true) + + topTable.add("Game Options".toLabel(fontSize = 24)).pad(20f, 0f) + topTable.addSeparatorVertical(Color.BLACK, 1f) + topTable.add("Map Options".toLabel(fontSize = 24)).pad(20f,0f) + topTable.addSeparatorVertical(Color.BLACK, 1f) + topTable.add("Civilizations".toLabel(fontSize = 24)).pad(20f,0f) + topTable.addSeparator(Color.CLEAR, height = 1f) + + topTable.add(ScrollPane(newGameOptionsTable) + .apply { setOverscroll(false, false) }) + .width(stage.width / 3).top() + topTable.addSeparatorVertical(Color.CLEAR, 1f) + topTable.add(ScrollPane(mapOptionsTable) + .apply { setOverscroll(false, false) }) + .width(stage.width / 3).top() + topTable.addSeparatorVertical(Color.CLEAR, 1f) + topTable.add(playerPickerTable) // No ScrollPane, PlayerPickerTable has its own + .width(stage.width / 3).top() + } + + private fun initPortrait() { + scrollPane.setScrollingDisabled(false,false) + + topTable.add(ExpanderTab("Game Options") { + it.add(newGameOptionsTable).row() + }).expandX().fillX().row() + topTable.addSeparator(Color.DARK_GRAY, height = 1f) + + topTable.add(newGameOptionsTable.getModCheckboxes(isPortrait = true)).expandX().fillX().row() + topTable.addSeparator(Color.DARK_GRAY, height = 1f) + + topTable.add(ExpanderTab("Map Options") { + it.add(mapOptionsTable).row() + }).expandX().fillX().row() + topTable.addSeparator(Color.DARK_GRAY, height = 1f) + + (playerPickerTable.playerListTable.parent as ScrollPane).setScrollingDisabled(true,true) + topTable.add(ExpanderTab("Civilizations") { + it.add(playerPickerTable).row() + }).expandX().fillX().row() + } + private fun newGameThread() { try { newGame = GameStarter.startNewGame(gameSetupInfo) } catch (exception: Exception) { Gdx.app.postRunnable { - val cantMakeThatMapPopup = Popup(this) - cantMakeThatMapPopup.addGoodSizedLabel("It looks like we can't make a map with the parameters you requested!".tr()).row() - cantMakeThatMapPopup.addGoodSizedLabel("Maybe you put too many players into too small a map?".tr()).row() - cantMakeThatMapPopup.addCloseButton() - cantMakeThatMapPopup.open() + Popup(this).apply { + addGoodSizedLabel("It looks like we can't make a map with the parameters you requested!".tr()).row() + addGoodSizedLabel("Maybe you put too many players into too small a map?".tr()).row() + addCloseButton() + open() + } Gdx.input.inputProcessor = stage rightSideButton.enable() rightSideButton.setText("Start game!".tr()) @@ -168,10 +211,11 @@ class NewGameScreen(private val previousScreen: CameraStageBaseScreen, _gameSetu GameSaver.saveGame(newGame!!, newGame!!.gameId, true) } catch (ex: Exception) { Gdx.app.postRunnable { - val cantUploadNewGamePopup = Popup(this) - cantUploadNewGamePopup.addGoodSizedLabel("Could not upload game!") - cantUploadNewGamePopup.addCloseButton() - cantUploadNewGamePopup.open() + Popup(this).apply { + addGoodSizedLabel("Could not upload game!") + addCloseButton() + open() + } } newGame = null } diff --git a/core/src/com/unciv/ui/newgamescreen/PlayerPickerTable.kt b/core/src/com/unciv/ui/newgamescreen/PlayerPickerTable.kt index 04ef6d7278..b40a6604d1 100644 --- a/core/src/com/unciv/ui/newgamescreen/PlayerPickerTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/PlayerPickerTable.kt @@ -18,24 +18,30 @@ import com.unciv.models.ruleset.Nation import com.unciv.models.ruleset.Ruleset import com.unciv.models.translations.tr import com.unciv.ui.mapeditor.GameParametersScreen +import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.utils.* +import java.text.Collator import java.util.* /** * This [Table] is used to pick or edit players information for new game creation. - * Could be inserted to [NewGameScreen], [GameParametersScreen] or any other [Screen] + * Could be inserted to [NewGameScreen], [GameParametersScreen] or any other [Screen][CameraStageBaseScreen] * which provides [GameSetupInfo] and [Ruleset]. * Upon player changes updates property [gameParameters]. Also updates available nations when mod changes. * In case it is used in map editor, as a part of [GameParametersScreen], additionally tries to * update units/starting location on the [previousScreen] when player deleted or * switched nation. - * @param [previousScreen] [Screen] where player table is inserted, should provide [GameSetupInfo] as property, - * updated when player added/deleted/changed - * @param [gameParameters] contains info about number of players. + * @param previousScreen A [Screen][CameraStageBaseScreen] where the player table is inserted, should provide [GameSetupInfo] as property, updated when a player is added/deleted/changed + * @param gameParameters contains info about number of players. + * @param blockWidth sets a width for the Civ "blocks". If too small a third of the stage is used. */ -class PlayerPickerTable(val previousScreen: IPreviousScreen, var gameParameters: GameParameters): Table() { +class PlayerPickerTable( + val previousScreen: IPreviousScreen, + var gameParameters: GameParameters, + blockWidth: Float = 0f +): Table() { val playerListTable = Table() - val civBlocksWidth = previousScreen.stage.width / 3 + val civBlocksWidth = if(blockWidth <= 10f) previousScreen.stage.width / 3 - 5f else blockWidth /** Locks player table for editing, currently unused, was previously used for scenarios and could be useful in the future.*/ var locked = false @@ -48,7 +54,6 @@ class PlayerPickerTable(val previousScreen: IPreviousScreen, var gameParameters: player.playerId = "" // This is to stop people from getting other users' IDs and cheating with them in multiplayer games top() - add("Civilizations".toLabel(fontSize = 24)).padBottom(20f).row() add(ScrollPane(playerListTable).apply { setOverscroll(false, false) }).width(civBlocksWidth) update() } @@ -78,7 +83,7 @@ class PlayerPickerTable(val previousScreen: IPreviousScreen, var gameParameters: var player = Player() // no random mode - add first not spectator civ if still available if (noRandom) { - val availableCiv = getAvailablePlayerCivs().firstOrNull { !it.isSpectator() } + val availableCiv = getAvailablePlayerCivs().firstOrNull() if (availableCiv != null) player = Player(availableCiv.name) // Spectators only Humans else player = Player(Constants.spectator).apply { playerType = PlayerType.Human } @@ -88,8 +93,9 @@ class PlayerPickerTable(val previousScreen: IPreviousScreen, var gameParameters: } playerListTable.add(addPlayerButton).pad(10f) } - // can enable start game when more than 1 active player - previousScreen.setRightSideButtonEnabled(gameParameters.players.count { it.chosenCiv != Constants.spectator } > 1) + // enable start game when more than 1 active player + val moreThanOnePlayer = 1 < gameParameters.players.count { it.chosenCiv != Constants.spectator } + (previousScreen as? PickerScreen)?.setRightSideButtonEnabled(moreThanOnePlayer) } /** @@ -128,8 +134,8 @@ class PlayerPickerTable(val previousScreen: IPreviousScreen, var gameParameters: val nationTable = getNationTable(player) playerTable.add(nationTable).left() - val playerTypeTextbutton = player.playerType.name.toTextButton() - playerTypeTextbutton.onClick { + val playerTypeTextButton = player.playerType.name.toTextButton() + playerTypeTextButton.onClick { if (player.playerType == PlayerType.AI) player.playerType = PlayerType.Human // we cannot change Spectator player to AI type, robots not allowed to spectate :( @@ -137,7 +143,7 @@ class PlayerPickerTable(val previousScreen: IPreviousScreen, var gameParameters: player.playerType = PlayerType.AI update() } - playerTable.add(playerTypeTextbutton).width(100f).pad(5f).right() + playerTable.add(playerTypeTextButton).width(100f).pad(5f).right() if (!locked) { playerTable.add("-".toLabel(Color.BLACK, 30).apply { this.setAlignment(Align.center) } .surroundWithCircle(40f) @@ -149,34 +155,34 @@ class PlayerPickerTable(val previousScreen: IPreviousScreen, var gameParameters: } if (gameParameters.isOnlineMultiplayer && player.playerType == PlayerType.Human) { - val playerIdTextfield = TextField(player.playerId, CameraStageBaseScreen.skin) - playerIdTextfield.messageText = "Please input Player ID!".tr() - playerTable.add(playerIdTextfield).colspan(2).fillX().pad(5f) + val playerIdTextField = TextField(player.playerId, CameraStageBaseScreen.skin) + playerIdTextField.messageText = "Please input Player ID!".tr() + playerTable.add(playerIdTextField).colspan(2).fillX().pad(5f) val errorLabel = "✘".toLabel(Color.RED) playerTable.add(errorLabel).pad(5f).row() fun onPlayerIdTextUpdated() { try { - UUID.fromString(IdChecker.checkAndReturnPlayerUuid(playerIdTextfield.text)) - player.playerId = playerIdTextfield.text.trim() + UUID.fromString(IdChecker.checkAndReturnPlayerUuid(playerIdTextField.text)) + player.playerId = playerIdTextField.text.trim() errorLabel.apply { setText("✔");setFontColor(Color.GREEN) } } catch (ex: Exception) { errorLabel.apply { setText("✘");setFontColor(Color.RED) } } } - playerIdTextfield.addListener { onPlayerIdTextUpdated(); true } + playerIdTextField.addListener { onPlayerIdTextUpdated(); true } val currentUserId = UncivGame.Current.settings.userId val setCurrentUserButton = "Set current user".toTextButton() setCurrentUserButton.onClick { - playerIdTextfield.text = currentUserId + playerIdTextField.text = currentUserId onPlayerIdTextUpdated() } playerTable.add(setCurrentUserButton).colspan(3).fillX().pad(5f).row() val copyFromClipboardButton = "Player ID from clipboard".toTextButton() copyFromClipboardButton.onClick { - playerIdTextfield.text = Gdx.app.clipboard.contents + playerIdTextField.text = Gdx.app.clipboard.contents onPlayerIdTextUpdated() } playerTable.add(copyFromClipboardButton).colspan(3).fillX().pad(5f) @@ -214,74 +220,116 @@ class PlayerPickerTable(val previousScreen: IPreviousScreen, var gameParameters: * @param player current player */ private fun popupNationPicker(player: Player) { - val nationsPopup = Popup(previousScreen as CameraStageBaseScreen) - val nationListTable = Table() - - val ruleset = previousScreen.ruleset - val height = previousScreen.stage.height * 0.8f - nationsPopup.add(ScrollPane(nationListTable).apply { setOverscroll(false, false) }) - .size(civBlocksWidth + 10, height) // +10, because the nation table has a 5f pad, for a total of +10f - val nationDetailsTable = Table() - nationsPopup.add(ScrollPane(nationDetailsTable).apply { setOverscroll(false, false) }) - .size(civBlocksWidth + 10, height) // Same here, see above - - val randomNation = Nation().apply { name = "Random"; innerColor = listOf(255, 255, 255); outerColor = listOf(0, 0, 0); setTransients() } - val nations = ArrayList() - if (!noRandom) nations += randomNation - nations += getAvailablePlayerCivs() - for (nation in nations) { - if (player.chosenCiv == nation.name) - continue - // only humans can spectate, sorry robots - if (player.playerType == PlayerType.AI && nation.isSpectator()) - continue - val nationTable = NationTable(nation, civBlocksWidth, 0f) // no need for min height - nationListTable.add(nationTable).row()//.width(civBlocksWidth).row() - nationTable.onClick { - nationDetailsTable.clear() - - val nationUniqueLabel = nation.getUniqueString(ruleset).toLabel(nation.getInnerColor()) - nationUniqueLabel.wrap = true - nationDetailsTable.add(NationTable(nation, civBlocksWidth, height, ruleset)) - nationDetailsTable.onClick { - if (previousScreen is GameParametersScreen) - previousScreen.mapEditorScreen.tileMap.switchPlayersNation(player, nation) - player.chosenCiv = nation.name - nationsPopup.close() - update() - } - } - } - - nationsPopup.pack() - - val closeImage = ImageGetter.getImage("OtherIcons/Close") - closeImage.setSize(30f, 30f) - val closeImageHolder = Group() // This is to add it some more clickable space, to make it easier to click on the phone - closeImageHolder.setSize(50f, 50f) - closeImage.center(closeImageHolder) - closeImageHolder.addActor(closeImage) - closeImageHolder.onClick { nationsPopup.close() } - closeImageHolder.setPosition(0f, nationsPopup.height, Align.topLeft) - nationsPopup.addActor(closeImageHolder) - - nationsPopup.open() + NationPickerPopup(this, player).open() update() } /** - * Returns list of available civilization for all players, according - * to current ruleset, with exeption of city states nations and barbarians - * @return [ArrayList] of available [Nation]s + * Returns a list of available civilization for all players, according + * to current ruleset, with exception of city states nations, spectator and barbarians. + * + * Skips nations already chosen by a player, unless parameter [dontSkipNation] says to keep a + * specific one. That is used so the picker can be used to inspect and confirm the current selection. + * + * @return [Sequence] of available [Nation]s */ - private fun getAvailablePlayerCivs(): ArrayList { - val nations = ArrayList() - for (nation in previousScreen.ruleset.nations.values - .filter { it.isMajorCiv() || it.isSpectator() }) { - if (gameParameters.players.any { it.chosenCiv == nation.name }) - continue - nations.add(nation) + internal fun getAvailablePlayerCivs(dontSkipNation: String? = null) = + previousScreen.ruleset.nations.values.asSequence() + .filter { it.isMajorCiv() } + .filter { it.name == dontSkipNation || gameParameters.players.none { player -> player.chosenCiv == it.name } } + +} + +private class NationPickerPopup( + private val playerPicker: PlayerPickerTable, + private val player: Player +) : Popup(playerPicker.previousScreen as CameraStageBaseScreen) { + private val previousScreen = playerPicker.previousScreen + private val ruleset = previousScreen.ruleset + // This Popup's body has two halves of same size, either side by side or arranged vertically + // depending on screen proportions - determine height for one of those + private val partHeight = screen.stage.height * (if (screen.isNarrowerThan4to3()) 0.45f else 0.8f) + private val civBlocksWidth = playerPicker.civBlocksWidth + private val nationListTable = Table() + private val nationListScroll = ScrollPane(nationListTable) + private val nationDetailsTable = Table() + + init { + nationListScroll.setOverscroll(false, false) + add(nationListScroll).size( civBlocksWidth + 10f, partHeight ) + // +10, because the nation table has a 5f pad, for a total of +10f + if (screen.isNarrowerThan4to3()) row() + add(ScrollPane(nationDetailsTable).apply { setOverscroll(false, false) }) + .size(civBlocksWidth + 10f, partHeight) // Same here, see above + + val randomNation = Nation().apply { + name = "Random" + innerColor = listOf(255, 255, 255) + outerColor = listOf(0, 0, 0) + setTransients() } - return nations + val nations = ArrayList() + if (!playerPicker.noRandom) nations += randomNation + val spectator = previousScreen.ruleset.nations[Constants.spectator] + if (spectator != null) nations += spectator + + nations += playerPicker.getAvailablePlayerCivs(player.chosenCiv) + .sortedWith(compareBy(Collator.getInstance(), { it.name.tr() })) + + var nationListScrollY = 0f + var currentY = 0f + for (nation in nations) { + // only humans can spectate, sorry robots + if (player.playerType == PlayerType.AI && nation.isSpectator()) + continue + if (player.chosenCiv == nation.name) + nationListScrollY = currentY + val nationTable = NationTable(nation, civBlocksWidth, 0f) // no need for min height + val cell = nationListTable.add(nationTable) + currentY += cell.padBottom + cell.prefHeight + cell.padTop + cell.row() + nationTable.onClick { + setNationDetails(nation) + } + if (player.chosenCiv == nation.name) + setNationDetails(nation) + } + + nationListScroll.layout() + pack() + if (nationListScrollY > 0f) { + // center the selected nation vertically, getRowHeight safe because nationListScrollY > 0f ensures at least 1 row + nationListScrollY -= (nationListScroll.height - nationListTable.getRowHeight(0)) / 2 + nationListScroll.scrollY = nationListScrollY.coerceIn(0f, nationListScroll.maxY) + } + + val closeImage = ImageGetter.getImage("OtherIcons/Close") + closeImage.setSize(30f, 30f) + val closeImageHolder = + Group() // This is to add it some more clickable space, to make it easier to click on the phone + closeImageHolder.setSize(50f, 50f) + closeImage.center(closeImageHolder) + closeImageHolder.addActor(closeImage) + closeImageHolder.onClick { close() } + closeImageHolder.setPosition(0f, height, Align.topLeft) + addActor(closeImageHolder) } -} \ No newline at end of file + + private fun setNationDetails(nation: Nation) { + nationDetailsTable.clear() + +// val nationUniqueLabel = nation.getUniqueString(ruleset).toLabel(nation.getInnerColor()) +// nationUniqueLabel.wrap = true + nationDetailsTable.add(NationTable(nation, civBlocksWidth, partHeight, ruleset)) + nationDetailsTable.onClick { + if (previousScreen is GameParametersScreen) + previousScreen.mapEditorScreen.tileMap.switchPlayersNation( + player, + nation + ) + player.chosenCiv = nation.name + close() + playerPicker.update() + } + } +} diff --git a/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt index 57bf560c61..14ecd05732 100644 --- a/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt @@ -6,20 +6,21 @@ import com.unciv.UncivGame import com.unciv.ui.utils.* import com.unciv.ui.utils.AutoScrollPane as ScrollPane -open class PickerScreen(val disableScroll: Boolean = false) : CameraStageBaseScreen() { +open class PickerScreen(disableScroll: Boolean = false) : CameraStageBaseScreen() { internal var closeButton: TextButton = Constants.close.toTextButton() protected var descriptionLabel: Label - protected var rightSideGroup = VerticalGroup() + private var rightSideGroup = VerticalGroup() protected var rightSideButton: TextButton - protected var screenSplit = 0.85f + private val screenSplit = 0.85f + private val maxBottomTableHeight = 150f // about 7 lines of normal text /** * The table displaying the choices from which to pick (usually). * Also the element which most of the screen realestate is devoted to displaying. */ protected var topTable: Table - var bottomTable:Table = Table() + protected var bottomTable:Table = Table() internal var splitPane: SplitPane protected var scrollPane: ScrollPane @@ -36,16 +37,16 @@ open class PickerScreen(val disableScroll: Boolean = false) : CameraStageBaseScr rightSideGroup.addActor(rightSideButton) bottomTable.add(rightSideGroup).pad(10f).right() - bottomTable.height = stage.height * (1 - screenSplit) + bottomTable.height = (stage.height * (1 - screenSplit)).coerceAtMost(maxBottomTableHeight) topTable = Table() scrollPane = ScrollPane(topTable) scrollPane.setScrollingDisabled(disableScroll, disableScroll) - scrollPane.setSize(stage.width, stage.height * screenSplit) + scrollPane.setSize(stage.width, stage.height - bottomTable.height) splitPane = SplitPane(scrollPane, bottomTable, true, skin) - splitPane.splitAmount = screenSplit + splitPane.splitAmount = scrollPane.height / stage.height splitPane.setFillParent(true) stage.addActor(splitPane) } diff --git a/core/src/com/unciv/ui/utils/CameraStageBaseScreen.kt b/core/src/com/unciv/ui/utils/CameraStageBaseScreen.kt index 74581edb61..89b02f2fd0 100644 --- a/core/src/com/unciv/ui/utils/CameraStageBaseScreen.kt +++ b/core/src/com/unciv/ui/utils/CameraStageBaseScreen.kt @@ -101,9 +101,13 @@ open class CameraStageBaseScreen : Screen { keyPressDispatcher[KeyCharAndCode.BACK] = action } + /** @return `true` if the screen is higher than it is wide */ fun isPortrait() = stage.viewport.screenHeight > stage.viewport.screenWidth + /** @return `true` if the screen is higher than it is wide _and_ resolution is at most 1050x700 */ fun isCrampedPortrait() = isPortrait() && game.settings.resolution.split("x").map { it.toInt() }.last() <= 700 + /** @return `true` if the screen is narrower than 4:3 landscape */ + fun isNarrowerThan4to3() = stage.viewport.screenHeight * 4 > stage.viewport.screenWidth * 3 fun openOptionsPopup() { val limitOrientationsHelper = game.limitOrientationsHelper diff --git a/core/src/com/unciv/ui/utils/UncivSlider.kt b/core/src/com/unciv/ui/utils/UncivSlider.kt index 72c5966c21..50d1887e48 100644 --- a/core/src/com/unciv/ui/utils/UncivSlider.kt +++ b/core/src/com/unciv/ui/utils/UncivSlider.kt @@ -71,6 +71,9 @@ class UncivSlider ( } val isDragging: Boolean get() = slider.isDragging + var isDisabled: Boolean + get() = slider.isDisabled + set(value) { slider.isDisabled = value } // Value tip format var tipFormat = "%.1f"