mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 15:27:50 +07:00
(UI) Politics overview diagram: Add legend popup (#10451)
* Refactor Battle damage shadowed Label into reusable WidgetGroup * Make Politics Overview "Grid header" fixed and some linting * Add a Legend popup explaining line colors in the Politics Overview Diagram: Click inside * Missing translation template * Final decision on background didn't make it in properly * Remove unused png - those are now in the Construction atlas * Smaller background image * Color and shadow choices * Redo atlas to include both 10505 and this
This commit is contained in:
BIN
android/Images/OtherIcons/Politics-diagram-bg.png
Normal file
BIN
android/Images/OtherIcons/Politics-diagram-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
7
android/Images/OtherIcons/Politics-diagram-bg.readme.txt
Normal file
7
android/Images/OtherIcons/Politics-diagram-bg.readme.txt
Normal file
@ -0,0 +1,7 @@
|
||||
Used in GlobalPoliticsOverviewTable.DiagramLegendPopup as "easter egg" background.
|
||||
|
||||
Uses 256x256 pixels in its atlas - at the time of adding it, there was ample leftover room,
|
||||
but once the main atlas fill up, please move to another or even its own little atlas,
|
||||
and maybe drop this file.
|
||||
|
||||
Will be seldom used!
|
Binary file not shown.
Before Width: | Height: | Size: 71 KiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 532 KiB After Width: | Height: | Size: 534 KiB |
@ -1424,6 +1424,7 @@ Number of your cities\ndemanding this resource for\n'We Love The King Day' =
|
||||
Politics =
|
||||
Show global politics =
|
||||
Show diagram =
|
||||
Diagram line colors =
|
||||
At war with [enemy] =
|
||||
Defensive pact with [civName] =
|
||||
Friends with [civName] =
|
||||
|
@ -19,7 +19,8 @@ import kotlin.math.max
|
||||
import kotlin.math.sign
|
||||
|
||||
enum class RelationshipLevel(val color: Color) {
|
||||
// War is tested separately for the Diplomacy Screen. Colored RED.
|
||||
// DiplomaticStatus.War is tested separately for the Diplomacy Screen. Colored RED.
|
||||
// DiplomaticStatus.DefensivePact - similar. Colored CYAN.
|
||||
Unforgivable(Color.FIREBRICK),
|
||||
Enemy(Color.YELLOW),
|
||||
Afraid(Color(0x5300ffff)), // HSV(260,100,100)
|
||||
|
59
core/src/com/unciv/ui/components/widgets/ShadowedLabel.kt
Normal file
59
core/src/com/unciv/ui/components/widgets/ShadowedLabel.kt
Normal file
@ -0,0 +1,59 @@
|
||||
package com.unciv.ui.components.widgets
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Stack
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.Constants
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
|
||||
/**
|
||||
* A widget containing two [Label]s superimposed with an offset to create a shadow effect.
|
||||
*
|
||||
* Reported [prefWidth], [prefHeight], [minWidth] and [minHeight] are always those of the Label plus `shadowOffset`
|
||||
*
|
||||
* @param text The label text (sic), autotranslated
|
||||
* @param fontSize as the name says
|
||||
* @param labelColor as the name says
|
||||
* @param shadowColor as the name says
|
||||
* @param hideIcons passed to [translation function][String.tr]
|
||||
* @param shadowOffset displacement distance of the shadow to right and to bottom
|
||||
*/
|
||||
|
||||
class ShadowedLabel(
|
||||
text: String,
|
||||
fontSize: Int = Constants.defaultFontSize,
|
||||
labelColor: Color = Color.WHITE,
|
||||
shadowColor: Color = Color.BLACK,
|
||||
hideIcons: Boolean = true,
|
||||
shadowOffset: Float = 1f
|
||||
) : Stack() {
|
||||
private val widthWithShadow: Float
|
||||
private val heightWithShadow: Float
|
||||
|
||||
init {
|
||||
touchable = Touchable.disabled
|
||||
val shadow = text.toLabel(shadowColor, fontSize, Align.bottomRight, hideIcons)
|
||||
shadow.touchable = Touchable.disabled
|
||||
addActor(shadow)
|
||||
val label = text.toLabel(labelColor, fontSize, Align.topLeft, hideIcons)
|
||||
label.touchable = Touchable.disabled
|
||||
addActor(label)
|
||||
|
||||
shadow.zIndex = 0
|
||||
// Displace the shadow under the label by shadowOffset.
|
||||
// Extending our size is enough due to their different Align values.
|
||||
widthWithShadow = label.prefWidth + shadowOffset
|
||||
heightWithShadow = label.prefHeight + shadowOffset
|
||||
setSize(width + shadowOffset, height + shadowOffset)
|
||||
}
|
||||
|
||||
override fun getPrefWidth() = widthWithShadow
|
||||
override fun getPrefHeight() = heightWithShadow
|
||||
// A Label has min=pref, but Stack overrides so we must override too
|
||||
// or Stack will return the smaller values directly from Label
|
||||
override fun getMinWidth() = widthWithShadow
|
||||
override fun getMinHeight() = heightWithShadow
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package com.unciv.ui.screens.overviewscreen
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.Group
|
||||
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
||||
@ -15,24 +17,29 @@ import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
||||
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
|
||||
import com.unciv.logic.map.HexMath
|
||||
import com.unciv.models.ruleset.Policy.PolicyBranchType
|
||||
import com.unciv.models.ruleset.nation.getContrastRatio
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
|
||||
import com.unciv.ui.components.extensions.addBorder
|
||||
import com.unciv.ui.components.extensions.addSeparator
|
||||
import com.unciv.ui.components.extensions.addSeparatorVertical
|
||||
import com.unciv.ui.components.extensions.center
|
||||
import com.unciv.ui.components.extensions.equalizeColumns
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.components.fonts.Fonts
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||
import com.unciv.ui.components.widgets.ColorMarkupLabel
|
||||
import com.unciv.ui.components.widgets.ShadowedLabel
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popups.AnimatedMenuPopup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.diplomacyscreen.DiplomacyScreen
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class GlobalPoliticsOverviewTable (
|
||||
class GlobalPoliticsOverviewTable(
|
||||
viewingPlayer: Civilization,
|
||||
overviewScreen: EmpireOverviewScreen,
|
||||
persistedData: EmpireOverviewTabPersistableData? = null
|
||||
@ -66,17 +73,31 @@ class GlobalPoliticsOverviewTable (
|
||||
|
||||
|
||||
init {
|
||||
top()
|
||||
if (persistableData.showDiagram) updateDiagram()
|
||||
else updatePoliticsTable()
|
||||
}
|
||||
|
||||
override fun getFixedContent() = fixedContent
|
||||
|
||||
//region Politics Table View
|
||||
|
||||
private fun updatePoliticsTable() {
|
||||
persistableData.showDiagram = false
|
||||
clear()
|
||||
fixedContent.clear()
|
||||
createGlobalPoliticsHeader()
|
||||
createGlobalPoliticsTable()
|
||||
equalizeColumns(fixedContent, this)
|
||||
}
|
||||
|
||||
/** Clears fixedContent and adds the header cells.
|
||||
* Needs to stay matched to [createGlobalPoliticsTable].
|
||||
*
|
||||
* 9 Columns: 5 info, 4 separators. First gets an empty header for contend below = civ image
|
||||
*/
|
||||
private fun createGlobalPoliticsHeader() = fixedContent.run {
|
||||
val diagramButton = "Show diagram".toTextButton().onClick(::updateDiagram)
|
||||
|
||||
clear()
|
||||
add()
|
||||
addSeparatorVertical(Color.GRAY)
|
||||
add("Civilization Info".toLabel())
|
||||
@ -86,16 +107,22 @@ class GlobalPoliticsOverviewTable (
|
||||
add("Wonders".toLabel())
|
||||
addSeparatorVertical(Color.GRAY)
|
||||
add(Table().apply {
|
||||
add("Relations".toLabel()).row()
|
||||
add("Relations".toLabel()).padTop(10f).row()
|
||||
add(diagramButton).pad(10f)
|
||||
})
|
||||
|
||||
createGlobalPoliticsTable()
|
||||
addSeparator(Color.GRAY)
|
||||
}
|
||||
|
||||
/** Clears [EmpireOverviewTab]'s main Table and adds data columns/rows.
|
||||
* Needs to stay matched to [createGlobalPoliticsHeader].
|
||||
*/
|
||||
private fun createGlobalPoliticsTable() {
|
||||
clear()
|
||||
|
||||
for (civ in sequenceOf(viewingPlayer) + viewingPlayer.diplomacyFunctions.getKnownCivsSorted(includeCityStates = false)) {
|
||||
addSeparator(Color.GRAY)
|
||||
// We already have a separator under the fixed header, we only need them here between rows.
|
||||
// This is also the replacement for calling row() explicitly
|
||||
if (cells.size > 0) addSeparator(Color.GRAY)
|
||||
|
||||
// civ image
|
||||
add(ImageGetter.getNationPortrait(civ.nation, 100f)).pad(20f)
|
||||
@ -225,7 +252,8 @@ class GlobalPoliticsOverviewTable (
|
||||
return politicsTable
|
||||
}
|
||||
|
||||
override fun getFixedContent() = fixedContent
|
||||
//endregion
|
||||
//region Diagram View ("Ball of Yarn")
|
||||
|
||||
// Refresh content and determine landscape/portrait layout
|
||||
private fun updateDiagram() {
|
||||
@ -406,6 +434,16 @@ class GlobalPoliticsOverviewTable (
|
||||
|
||||
init {
|
||||
setSize(freeSize, freeSize)
|
||||
|
||||
// An Image Actor does not respect alpha for its hit area, it's always square, but we want a clickable _circle_
|
||||
// Radius to show legend should be no larger than freeSize / 2.25f - 15f (see below), let's make it a little smaller
|
||||
val clickableArea = ClickableCircle(freeSize / 1.25f - 25f)
|
||||
clickableArea.onActivation {
|
||||
DiagramLegendPopup(stage, this)
|
||||
}
|
||||
clickableArea.center(this)
|
||||
addActor(clickableArea)
|
||||
|
||||
val civGroups = HashMap<String, Actor>()
|
||||
val civLines = HashMap<String, MutableSet<Actor>>()
|
||||
val civCount = undefeatedCivs.count()
|
||||
@ -426,7 +464,7 @@ class GlobalPoliticsOverviewTable (
|
||||
addActor(civGroup)
|
||||
}
|
||||
|
||||
for (civ in undefeatedCivs)
|
||||
for (civ in undefeatedCivs) {
|
||||
for (diplomacy in civ.diplomacy.values) {
|
||||
if (diplomacy.otherCiv() !in undefeatedCivs) continue
|
||||
val civGroup = civGroups[civ.civName]!!
|
||||
@ -437,7 +475,8 @@ class GlobalPoliticsOverviewTable (
|
||||
startY = civGroup.y + civGroup.height / 2,
|
||||
endX = otherCivGroup.x + otherCivGroup.width / 2,
|
||||
endY = otherCivGroup.y + otherCivGroup.height / 2,
|
||||
width = 2f)
|
||||
width = 2f
|
||||
)
|
||||
|
||||
statusLine.color = if (diplomacy.diplomaticStatus == DiplomaticStatus.War) Color.RED
|
||||
else if (diplomacy.diplomaticStatus == DiplomaticStatus.DefensivePact
|
||||
@ -451,6 +490,64 @@ class GlobalPoliticsOverviewTable (
|
||||
|
||||
addActorAt(0, statusLine)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private class DiagramLegendPopup(stage: Stage, diagram: Actor) : AnimatedMenuPopup(stage, diagram.getCenterInStageCoordinates()) {
|
||||
init {
|
||||
touchable = Touchable.enabled
|
||||
onActivation { close() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun Actor.getCenterInStageCoordinates(): Vector2 = localToStageCoordinates(Vector2(width / 2, height / 2))
|
||||
|
||||
const val lineWidth = 3f // a little thicker than the actual diagram
|
||||
const val lowContrastWidth = 4f
|
||||
const val lineLength = 120f
|
||||
}
|
||||
|
||||
override fun createContentTable(): Table {
|
||||
val legend = Table()
|
||||
legend.background = ImageGetter.getDrawable("OtherIcons/Politics-diagram-bg")
|
||||
legend.add(ShadowedLabel("Diagram line colors", Constants.headingFontSize)).colspan(2).row()
|
||||
//todo Rethink hardcoding together with the statusLine.color one in DiplomacyGroup
|
||||
legend.addLegendRow("War", Color.RED)
|
||||
for (level in RelationshipLevel.values()) {
|
||||
val lineColor = if (level == RelationshipLevel.Ally) Color.CYAN else level.color
|
||||
legend.addLegendRow(level.name, lineColor)
|
||||
}
|
||||
legend.addLegendRow(Constants.defensivePact, Color.CYAN)
|
||||
return super.createContentTable()!!.apply {
|
||||
add(legend).grow()
|
||||
}
|
||||
}
|
||||
|
||||
fun Table.addLegendRow(text: String, color: Color) {
|
||||
// empiric hack to equalize the "visual impact" a little. Afraid is worst at contrast 1.4, Enemy has 9.8
|
||||
val contrast = getContrastRatio(Color.DARK_GRAY, color).toFloat()
|
||||
val width = lineWidth + (lowContrastWidth - lineWidth) / contrast.coerceAtLeast(1f)
|
||||
val line = ImageGetter.getLine(0f, width / 2, lineLength, width / 2, width)
|
||||
line.color = color
|
||||
add(line).size(lineLength, width).padTop(5f)
|
||||
add(ShadowedLabel(text)).padLeft(5f).padTop(10f).row()
|
||||
}
|
||||
}
|
||||
|
||||
/** An Image Actor does not respect alpha for its hit area, it's always square, but we want a clickable _circle_ */
|
||||
private class ClickableCircle(size: Float) : Group() {
|
||||
val center = Vector2(size / 2, size / 2)
|
||||
val maxDst2 = size * size / 4 // squared radius
|
||||
init {
|
||||
touchable = Touchable.enabled
|
||||
setSize(size, size)
|
||||
}
|
||||
override fun hit(x: Float, y: Float, touchable: Boolean): Actor? {
|
||||
return if (center.dst2(x, y) < maxDst2) this else null
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
||||
|
@ -5,14 +5,12 @@ import com.badlogic.gdx.math.Interpolation
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.Group
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.Actions
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.FloatAction
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.RelativeTemporalAction
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.SequenceAction
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.TemporalAction
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Image
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Stack
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup
|
||||
import com.badlogic.gdx.utils.Align
|
||||
@ -20,8 +18,8 @@ import com.unciv.UncivGame
|
||||
import com.unciv.logic.battle.ICombatant
|
||||
import com.unciv.logic.battle.MapUnitCombatant
|
||||
import com.unciv.logic.map.HexMath
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.tilegroups.TileSetStrings
|
||||
import com.unciv.ui.components.widgets.ShadowedLabel
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||
|
||||
@ -210,17 +208,7 @@ object BattleTableHelpers {
|
||||
private fun createDamageLabel(damage: Int, target: Actor) {
|
||||
if (damage == 0) return
|
||||
|
||||
val label = (-damage).toString().toLabel(Color.RED, damageLabelFontSize, Align.topLeft, true)
|
||||
label.touchable = Touchable.disabled
|
||||
val shadow = (-damage).toString().toLabel(Color.BLACK, damageLabelFontSize, Align.bottomRight, true)
|
||||
shadow.touchable = Touchable.disabled
|
||||
|
||||
val container = Stack(shadow, label)
|
||||
container.touchable = Touchable.disabled
|
||||
|
||||
container.pack()
|
||||
// The +1f is what displaces the shadow under the red label
|
||||
container.setSize(container.width + 1f, container.height + 1f)
|
||||
val container = ShadowedLabel((-damage).toString(), damageLabelFontSize, Color.RED)
|
||||
val targetRight = target.run { localToStageCoordinates(Vector2(width, height * 0.5f)) }
|
||||
container.setPosition(targetRight.x, targetRight.y, Align.center)
|
||||
target.stage.addActor(container)
|
||||
|
@ -734,6 +734,7 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
|
||||
- [charts](https://thenounproject.com/icon/charts-2312023/) by Srinivas Agra (gimped to appear bolder) for the Charts page
|
||||
- [framed image](https://thenounproject.com/icon/framed-image-2332187/) by Jose Dean for Victory Illustrations page
|
||||
- [down](https://thenounproject.com/icon/down-39378/) by Cengiz SARI for Show unit destination
|
||||
- [Cat](https://thenounproject.com/icon/cat-158942/) by Josi for Politics overview diagram legend
|
||||
|
||||
|
||||
### Main menu
|
||||
|
Reference in New Issue
Block a user