Improved city button (#771)

* improved CityButton

* keep city button out of the way if a unit on the city tile is selected

* fixed city button to be clickable always. however this hides units behind it

* added click area for city buttons in an own layer, so the units render in front and the city button click area handles the clicks

* city button: simplified code
This commit is contained in:
sulai 2019-05-18 22:09:02 +02:00 committed by Yair Morgenstern
parent 054b3b000d
commit 73e397c2c2
4 changed files with 75 additions and 55 deletions

View File

@ -2,9 +2,10 @@ package com.unciv.ui.tilegroups
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Interpolation import com.badlogic.gdx.math.Interpolation
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.actions.FloatAction import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.scenes.scene2d.ui.Skin import com.badlogic.gdx.scenes.scene2d.ui.Skin
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Align
@ -24,9 +25,8 @@ class CityButton(val city: CityInfo, internal val tileGroup: WorldTileGroup, ski
touchable= Touchable.disabled touchable= Touchable.disabled
} }
var offset: Float = 0f; var buttonDownClickArea: Actor? = null
var isButtonMoved = false fun isButtonMoved() = buttonDownClickArea != null
var isLabelClicked = false
fun update(isCityViewable:Boolean) { fun update(isCityViewable:Boolean) {
val cityButtonText = city.population.population.toString() + " | " + city.name val cityButtonText = city.population.population.toString() + " | " + city.name
@ -36,61 +36,37 @@ class CityButton(val city: CityInfo, internal val tileGroup: WorldTileGroup, ski
label.setFontColor(city.civInfo.getNation().getSecondaryColor()) label.setFontColor(city.civInfo.getNation().getSecondaryColor())
clear() clear()
val unitTable = tileGroup.worldScreen.bottomBar.unitTable
if (UnCivGame.Current.viewEntireMapForDebug || city.civInfo.isCurrentPlayer()) { if (UnCivGame.Current.viewEntireMapForDebug || city.civInfo.isCurrentPlayer()) {
// So you can click anywhere on the button to go to the city // So you can click anywhere on the button to go to the city
touchable = Touchable.enabled touchable = Touchable.enabled
label.touchable = Touchable.enabled label.touchable = Touchable.enabled
label.onClick {
isLabelClicked = true
// clicking on the label swings that label a little down to allow selection of units there.
// second tap on the label will go to the city screen
if (tileGroup.selectCity(city)) {
val floatAction = object : FloatAction(0f, 1f, 0.4f) {
override fun update(percent: Float) {
offset = -height*percent
update(isCityViewable)
}
override fun end() {
isButtonMoved=true
}
}
floatAction.interpolation = Interpolation.swingOut
tileGroup.addAction(floatAction)
}
else {
UnCivGame.Current.screen = CityScreen(city)
}
}
// clicking anywhere else on the button opens the city screen immediately // clicking anywhere else on the button opens the city screen immediately
onClick { onClickEvent { _, x, y ->
// we need to check if the label was just clicked, as onClick will propagate if (!isButtonMoved()) {
// the click event to its touchable parent. if (hit(x, y, true) == label) {
if(!isLabelClicked) // clicking on the label swings that label a little down to allow selection of units there.
UnCivGame.Current.screen = CityScreen(city) // this also allows to target selected units to move to the city tile from elsewhere.
isLabelClicked=false // second tap on the label will go to the city screen
moveButtonDown()
if (unitTable.selectedUnit == null || unitTable.selectedUnit!!.currentMovement==0f)
tileGroup.selectCity(city)
} else {
UnCivGame.Current.screen = CityScreen(city)
}
}
} }
} }
// when deselected, move city button to its original position // when deselected, move city button to its original position
val unitTable = tileGroup.worldScreen.bottomBar.unitTable if (isButtonMoved()
if (isButtonMoved && unitTable.selectedCity != city
&& unitTable.selectedCity == null
&& unitTable.selectedUnit?.currentTile != city.ccenterTile) { && unitTable.selectedUnit?.currentTile != city.ccenterTile) {
isButtonMoved = false moveButtonUp()
val floatAction = object : FloatAction(0f, 1f, 0.4f) {
override fun update(percent: Float) {
offset = -height*(1-percent)
update(isCityViewable)
}
}
floatAction.interpolation = Interpolation.sine
tileGroup.addAction(floatAction)
} }
if (isCityViewable && city.health < city.getMaxHealth().toFloat()) { if (isCityViewable && city.health < city.getMaxHealth().toFloat()) {
@ -129,12 +105,47 @@ class CityButton(val city: CityInfo, internal val tileGroup: WorldTileGroup, ski
add(iconTable).row() add(iconTable).row()
pack() pack()
setOrigin(Align.center) setOrigin(Align.center)
center(tileGroup) centerX(tileGroup)
y += offset // for animated shifting of City button
touchable = Touchable.enabled touchable = Touchable.enabled
updateClickArea()
} }
private fun moveButtonDown() {
val floatAction = Actions.sequence(
Actions.moveBy(0f, -height, 0.4f, Interpolation.swingOut),
Actions.run {
buttonDownClickArea = Actor().onClick {
UnCivGame.Current.screen = CityScreen(city)
}
tileGroup.cityButtonLayerGroup.addActor(buttonDownClickArea)
updateClickArea()
}
)
tileGroup.addAction(floatAction)
}
private fun moveButtonUp() {
val floatAction = Actions.sequence(
Actions.moveBy(0f, height, 0.4f, Interpolation.sine),
Actions.run {
buttonDownClickArea?.remove()
buttonDownClickArea = null
}
)
tileGroup.addAction(floatAction)
}
private fun updateClickArea() {
buttonDownClickArea?.let { clickArea ->
clickArea.setSize(width, height)
clickArea.setScale(scaleX, scaleY)
clickArea.setOrigin(Align.center)
clickArea.centerX(tileGroup.cityButtonLayerGroup)
clickArea.y = y-height
clickArea.touchable = Touchable.enabled
}
}
private fun getConstructionGroup(cityConstructions: CityConstructions): Group { private fun getConstructionGroup(cityConstructions: CityConstructions): Group {
val group= Group() val group= Group()

View File

@ -55,6 +55,8 @@ open class TileGroup(var tileInfo: TileInfo) : Group() {
protected var civilianUnitImage: UnitGroup? = null protected var civilianUnitImage: UnitGroup? = null
protected var militaryUnitImage: UnitGroup? = null protected var militaryUnitImage: UnitGroup? = null
val cityButtonLayerGroup = Group().apply { isTransform=true; setSize(groupSize,groupSize);touchable=Touchable.childrenOnly }
val circleCrosshairFogLayerGroup = Group().apply { isTransform=false; setSize(groupSize,groupSize) } val circleCrosshairFogLayerGroup = Group().apply { isTransform=false; setSize(groupSize,groupSize) }
private val circleImage = ImageGetter.getCircle() // for blue and red circles on the tile private val circleImage = ImageGetter.getCircle() // for blue and red circles on the tile
private val crosshairImage = ImageGetter.getImage("OtherIcons/Crosshair.png") // for when a unit is targete private val crosshairImage = ImageGetter.getImage("OtherIcons/Crosshair.png") // for when a unit is targete
@ -76,6 +78,7 @@ open class TileGroup(var tileInfo: TileInfo) : Group() {
this.addActor(featureLayerGroup) this.addActor(featureLayerGroup)
this.addActor(miscLayerGroup) this.addActor(miscLayerGroup)
this.addActor(unitLayerGroup) this.addActor(unitLayerGroup)
this.addActor(cityButtonLayerGroup)
this.addActor(circleCrosshairFogLayerGroup) this.addActor(circleCrosshairFogLayerGroup)
updateTileImage(false) updateTileImage(false)

View File

@ -122,20 +122,23 @@ fun Label.setFontSize(size:Int): Label {
} }
/** same as [onClick], but sends the [InputEvent] and coordinates along */
// If there are other buttons that require special clicks then we'll have an onclick that will accept a string parameter, no worries fun Actor.onClickEvent(sound: String = "click", function: (event: InputEvent?, x: Float, y: Float) -> Unit) {
fun Actor.onClick(sound:String,function: () -> Unit){
this.addListener(object : ClickListener() { this.addListener(object : ClickListener() {
override fun clicked(event: InputEvent?, x: Float, y: Float) { override fun clicked(event: InputEvent?, x: Float, y: Float) {
if(sound!="") Sounds.play(sound) if (sound != "") Sounds.play(sound)
function() function(event, x, y)
} }
} ) })
}
// If there are other buttons that require special clicks then we'll have an onclick that will accept a string parameter, no worries
fun Actor.onClick(sound: String = "click", function: () -> Unit) {
onClickEvent(sound) { _, _, _ -> function() }
} }
fun Actor.onClick(function: () -> Unit): Actor { fun Actor.onClick(function: () -> Unit): Actor {
onClick("click",function) onClick("click", function)
return this return this
} }

View File

@ -31,6 +31,7 @@ class TileGroupMap<T: TileGroup>(val tileGroups:Collection<T>, padding:Float): G
val featureLayers = ArrayList<Group>() val featureLayers = ArrayList<Group>()
val miscLayers = ArrayList<Group>() val miscLayers = ArrayList<Group>()
val unitLayers = ArrayList<Group>() val unitLayers = ArrayList<Group>()
val cityButtonLayers = ArrayList<Group>()
val circleCrosshairFogLayers = ArrayList<Group>() val circleCrosshairFogLayers = ArrayList<Group>()
for(group in tileGroups.sortedByDescending { it.tileInfo.position.x + it.tileInfo.position.y }){ for(group in tileGroups.sortedByDescending { it.tileInfo.position.x + it.tileInfo.position.y }){
@ -39,6 +40,7 @@ class TileGroupMap<T: TileGroup>(val tileGroups:Collection<T>, padding:Float): G
featureLayers.add(group.featureLayerGroup.apply { setPosition(group.x,group.y) }) featureLayers.add(group.featureLayerGroup.apply { setPosition(group.x,group.y) })
miscLayers.add(group.miscLayerGroup.apply { setPosition(group.x,group.y) }) miscLayers.add(group.miscLayerGroup.apply { setPosition(group.x,group.y) })
unitLayers.add(group.unitLayerGroup.apply { setPosition(group.x,group.y) }) unitLayers.add(group.unitLayerGroup.apply { setPosition(group.x,group.y) })
cityButtonLayers.add(group.cityButtonLayerGroup.apply { setPosition(group.x,group.y) })
circleCrosshairFogLayers.add(group.circleCrosshairFogLayerGroup.apply { setPosition(group.x,group.y) }) circleCrosshairFogLayers.add(group.circleCrosshairFogLayerGroup.apply { setPosition(group.x,group.y) })
} }
for(group in baseLayers) addActor(group) for(group in baseLayers) addActor(group)
@ -46,6 +48,7 @@ class TileGroupMap<T: TileGroup>(val tileGroups:Collection<T>, padding:Float): G
for(group in miscLayers) addActor(group) for(group in miscLayers) addActor(group)
for(group in circleCrosshairFogLayers) addActor(group) for(group in circleCrosshairFogLayers) addActor(group)
for(group in tileGroups) addActor(group) // The above layers are for the visual layers, this is for the clickability for(group in tileGroups) addActor(group) // The above layers are for the visual layers, this is for the clickability
for(group in cityButtonLayers) addActor(group) // city buttons clickability
for(group in unitLayers) addActor(group) // Aaand units above everything else. for(group in unitLayers) addActor(group) // Aaand units above everything else.