Ui improvements (#779)

* Notifications scroll: keep scrolling position on game update if it's still the same list

* tech picker shows current/recent technology centered on the screen

* using NotificationAction interface to attach various actions to Notifications

* tech notifications: center on tech that was discovered
This commit is contained in:
sulai 2019-05-20 15:31:04 +02:00 committed by Yair Morgenstern
parent 22c45af5d8
commit 95a3a65c34
9 changed files with 118 additions and 33 deletions

View File

@ -5,6 +5,7 @@ import com.unciv.GameParameters
import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.LocationAction
import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap
@ -97,7 +98,7 @@ class GameInfo {
}
else {
val positions = tiles.map { it.position }
thisPlayer.addNotification("[${positions.size}] enemy units were spotted $inOrNear our territory", positions, Color.RED)
thisPlayer.addNotification("[${positions.size}] enemy units were spotted $inOrNear our territory", Color.RED, LocationAction(positions))
}
}

View File

@ -5,6 +5,7 @@ import com.unciv.Constants
import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CityAction
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.TileInfo
import com.unciv.models.gamebasics.unit.BaseUnit
@ -192,7 +193,7 @@ class Automation {
else theChosenOne = relativeCostEffectiveness.minBy { it.remainingWork }!!.choice // ignore modifiers, go for the cheapest.
currentConstruction = theChosenOne
cityInfo.civInfo.addNotification("Work has started on [$currentConstruction]", cityInfo.location, Color.BROWN)
cityInfo.civInfo.addNotification("Work has started on [$currentConstruction]", Color.BROWN, CityAction(cityInfo.location))
}
}

View File

@ -499,14 +499,14 @@ class CivilizationInfo {
return false
}
fun addNotification(text: String, location: Vector2?,color: Color) {
val locations = if(location!=null) listOf(location) else emptyList()
addNotification(text, locations, color)
fun addNotification(text: String, location: Vector2?, color: Color) {
val locations = if (location != null) listOf(location) else emptyList()
addNotification(text, color, LocationAction(locations))
}
fun addNotification(text: String, locations: List<Vector2>, color: Color) {
if(playerType==PlayerType.AI) return // no point in lengthening the saved game info if no one will read it
notifications.add(Notification(text, locations,color))
fun addNotification(text: String, color: Color, action: NotificationAction?=null) {
if (playerType == PlayerType.AI) return // no point in lengthening the saved game info if no one will read it
notifications.add(Notification(text, color, action))
}
fun addGreatPerson(greatPerson: String, city:CityInfo = cities.random()) {

View File

@ -2,17 +2,57 @@ package com.unciv.logic.civilization
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Vector2
import com.unciv.models.gamebasics.GameBasics
import com.unciv.ui.cityscreen.CityScreen
import com.unciv.ui.pickerscreens.TechPickerScreen
import com.unciv.ui.worldscreen.WorldScreen
class Notification {
var text: String = ""
var locations: ArrayList<Vector2> = ArrayList()
var color: Color = Color.BLACK
/**
* [action] is not realized as lambda, as it would be too easy to introduce references to objects
* there that should not be serialized to the saved game.
*/
open class Notification(
// default parameters necessary for json deserialization
var text: String = "",
var color: Color = Color.BLACK,
var action: NotificationAction? = null
)
internal constructor() // Needed for json deserialization
/** defines what to do if the user clicks on a notification */
interface NotificationAction {
fun execute(worldScreen: WorldScreen)
}
constructor(text: String, locations: List<Vector2> = ArrayList(), color: Color) {
this.text = text
this.locations = ArrayList(locations)
this.color = color
/** cycle through locations */
data class LocationAction(var locations: ArrayList<Vector2> = ArrayList()) : NotificationAction {
constructor(locations: List<Vector2>): this(ArrayList(locations))
override fun execute(worldScreen: WorldScreen) {
if (locations.isNotEmpty()) {
var index = locations.indexOf(worldScreen.tileMapHolder.selectedTile?.position)
index = ++index % locations.size // cycle through locations
worldScreen.tileMapHolder.setCenterPosition(locations[index])
}
}
}
/** show tech screen */
class TechAction(val techName: String = "") : NotificationAction {
override fun execute(worldScreen: WorldScreen) {
val tech = GameBasics.Technologies[techName]
worldScreen.game.screen = TechPickerScreen(worldScreen.currentPlayerCiv, tech)
}
}
/** enter city */
data class CityAction(val city: Vector2 = Vector2.Zero): NotificationAction {
override fun execute(worldScreen: WorldScreen) {
worldScreen.tileMapHolder.tileMap[city].getCity()?.let {
worldScreen.game.screen = CityScreen(it)
}
}
}

View File

@ -42,7 +42,11 @@ class TechManager {
return (GameBasics.Technologies[techName]!!.cost * civInfo.getDifficulty().researchCostModifier).toInt()
}
fun currentTechnology(): String? {
fun currentTechnology(): Technology? = currentTechnologyName()?.let {
GameBasics.Technologies[it]
}
fun currentTechnologyName(): String? {
if (techsToResearch.isEmpty()) return null
else return techsToResearch[0]
}
@ -91,7 +95,7 @@ class TechManager {
}
fun nextTurn(scienceForNewTurn: Int) {
val currentTechnology = currentTechnology()
val currentTechnology = currentTechnologyName()
if (currentTechnology == null) return
techsInProgress[currentTechnology] = researchOfTech(currentTechnology) + scienceForNewTurn
if (techsInProgress[currentTechnology]!! < costOfTech(currentTechnology))
@ -121,7 +125,7 @@ class TechManager {
researchedTechUniques = researchedTechUniques.withItem(unique)
updateTransientBooleans()
civInfo.addNotification("Research of [$techName] has completed!", null, Color.BLUE)
civInfo.addNotification("Research of [$techName] has completed!", Color.BLUE, TechAction(techName))
val currentEra = civInfo.getEra()
if (previousEra < currentEra) {

View File

@ -15,6 +15,7 @@ open class PickerScreen : CameraStageBaseScreen() {
protected var topTable: Table
var bottomTable:Table = Table()
internal var splitPane: SplitPane
protected var scrollPane: ScrollPane
init {
bottomTable.add(closeButton).width(stage.width / 4)
@ -33,7 +34,7 @@ open class PickerScreen : CameraStageBaseScreen() {
bottomTable.align(Align.center)
topTable = Table()
val scrollPane = ScrollPane(topTable)
scrollPane = ScrollPane(topTable)
scrollPane.setSize(stage.width, stage.height * screenSplit)

View File

@ -1,5 +1,6 @@
package com.unciv.ui.pickerscreens
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.unciv.UnCivGame
@ -13,7 +14,7 @@ import java.util.*
import kotlin.collections.HashSet
class TechPickerScreen(internal val civInfo: CivilizationInfo) : PickerScreen() {
class TechPickerScreen(internal val civInfo: CivilizationInfo, centerOnTech: Technology? = null) : PickerScreen() {
private var techNameToButton = HashMap<String, TechButton>()
private var isFreeTechPick: Boolean = false
@ -101,6 +102,21 @@ class TechPickerScreen(internal val civInfo: CivilizationInfo) : PickerScreen()
}
displayTutorials("TechPickerScreen")
// per default show current/recent technology,
// and possibly select it to show description,
// which is very helpful when just discovered and clicking the notification
val tech = centerOnTech ?: civInfo.tech.currentTechnology()
if (tech != null) {
// select only if there it doesn't mess up tempTechsToResearch
if (civInfo.tech.isResearched(tech.name) || civInfo.tech.techsToResearch.size <= 1) {
selectTechnology(tech, true)
}
else {
centerOnTechnology(tech)
}
}
}
private fun setButtonsInfo() {
@ -131,9 +147,19 @@ class TechPickerScreen(internal val civInfo: CivilizationInfo) : PickerScreen()
}
}
private fun selectTechnology(tech: Technology?) {
private fun selectTechnology(tech: Technology?, center: Boolean = false) {
selectedTech = tech
descriptionLabel.setText(tech!!.description)
descriptionLabel.setText(tech?.description)
if(tech==null)
return
// center on technology
if (center) {
centerOnTechnology(tech)
}
if (isFreeTechPick) {
selectTechnologyForFreeTech(tech)
return
@ -153,6 +179,14 @@ class TechPickerScreen(internal val civInfo: CivilizationInfo) : PickerScreen()
setButtonsInfo()
}
private fun centerOnTechnology(tech: Technology) {
Gdx.app.postRunnable {
techNameToButton[tech.name]?.let {
scrollPane.scrollTo(it.x, it.y, it.width, it.height, true, true)
scrollPane.updateVisualScroll()
}
}
}
private fun selectTechnologyForFreeTech(tech: Technology) {

View File

@ -9,6 +9,9 @@ import com.unciv.ui.utils.*
import kotlin.math.min
class NotificationsScroll(internal val worldScreen: WorldScreen) : ScrollPane(null) {
var notificationsHash : Int = 0
private var notificationsTable = Table()
init {
@ -17,10 +20,15 @@ class NotificationsScroll(internal val worldScreen: WorldScreen) : ScrollPane(nu
}
internal fun update(notifications: MutableList<Notification>) {
// no news? - keep our list as it is, especially don't reset scroll position
if(notificationsHash == notifications.hashCode())
return
notificationsHash = notifications.hashCode()
notificationsTable.clearChildren()
for (notification in notifications.toList()) { // tolist to avoid concurrecy problems
val label = notification.text.toLabel().setFontColor(Color.BLACK)
.setFontSize(14)
for (notification in notifications.toList()) { // toList to avoid concurrency problems
val label = notification.text.toLabel().setFontColor(Color.BLACK).setFontSize(14)
val listItem = Table()
listItem.add(ImageGetter.getCircle()
@ -34,11 +42,7 @@ class NotificationsScroll(internal val worldScreen: WorldScreen) : ScrollPane(nu
add(listItem).pad(3f)
touchable = Touchable.enabled
onClick {
if (notification.locations.isNotEmpty()) {
var index = notification.locations.indexOf(worldScreen.tileMapHolder.selectedTile?.position)
index = ++index % notification.locations.size // cycle through locations
worldScreen.tileMapHolder.setCenterPosition(notification.locations[index])
}
notification.action?.execute(worldScreen)
}
}

View File

@ -207,7 +207,7 @@ class WorldScreen : CameraStageBaseScreen() {
techButton.add(buttonPic)
}
else {
val currentTech = civInfo.tech.currentTechnology()!!
val currentTech = civInfo.tech.currentTechnologyName()!!
val innerButton = TechButton(currentTech,civInfo.tech)
innerButton.color = colorFromRGB(7, 46, 43)
techButton.add(innerButton)