Changes to moddable UI (#8055)

* Replaced all occurrences of deprecated NinePatch

function calls

This makes a lot of UI elements already moddable but documentation is still missing

* Added "How to create a UI skin for Unciv" to wiki

* Added image

* Fixed two typos and updated directory image

Old image was missing a folder

* Added clear color to skin config

to support picker screen backgrounds

* Removed deprecated functions in ImageGetter

* Fixed lowercase ui element names

they should always be UpperCamelCase to be consistent

* Added UiElementDocsWriter

to modify the docs for UI elements automatically

* Added default shape to UiElementsDocsWriter.kt

* Revert unintended merge errors

* Rerun file auto generation
This commit is contained in:
Leonard Günther 2022-11-27 18:14:56 +01:00 committed by GitHub
parent 16a7eed4ff
commit 99b6a2254d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 160 additions and 105 deletions

View File

@ -75,7 +75,7 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize {
val table = Table().pad(15f, 30f, 15f, 30f)
table.background = skinStrings.getUiBackground(
"MainMenuScreen/MenuButton",
skinStrings.roundedEdgeRectangle,
skinStrings.roundedEdgeRectangleShape,
skinStrings.skinConfig.baseColor
)
table.add(ImageGetter.getImage(icon)).size(50f).padRight(30f)

View File

@ -18,17 +18,20 @@ class SkinElement {
class SkinConfig {
var baseColor: Color = Color(0x004085bf)
var clearColor: Color = Color(0x000033ff)
var skinVariants: HashMap<String, SkinElement> = HashMap()
fun clone(): SkinConfig {
val toReturn = SkinConfig()
toReturn.baseColor = baseColor.cpy()
toReturn.clearColor = clearColor.cpy()
toReturn.skinVariants.putAll(skinVariants.map { Pair(it.key, it.value.clone()) })
return toReturn
}
fun updateConfig(other: SkinConfig) {
baseColor = other.baseColor.cpy()
clearColor = other.clearColor.cpy()
for ((variantName, element) in other.skinVariants){
skinVariants[variantName] = element.clone()
}

View File

@ -9,12 +9,13 @@ class SkinStrings(skin: String = UncivGame.Current.settings.skin) {
private val skinLocation = "Skins/$skin/"
val skinConfig = SkinCache[skin] ?: SkinConfig()
val roundedEdgeRectangle = skinLocation + "roundedEdgeRectangle"
val rectangleWithOutline = skinLocation + "rectangleWithOutline"
val selectBox = skinLocation + "select-box"
val selectBoxPressed = skinLocation + "select-box-pressed"
val checkbox = skinLocation + "checkbox"
val checkboxPressed = skinLocation + "checkbox-pressed"
// Default shapes must always end with "Shape" so the UiElementDocsWriter can identify them
val roundedEdgeRectangleShape = skinLocation + "roundedEdgeRectangle"
val rectangleWithOutlineShape = skinLocation + "rectangleWithOutline"
val selectBoxShape = skinLocation + "select-box"
val selectBoxPressedShape = skinLocation + "select-box-pressed"
val checkboxShape = skinLocation + "checkbox"
val checkboxPressedShape = skinLocation + "checkbox-pressed"
/**
* Gets either a drawable which was defined inside skinConfig for the given path or the drawable
@ -26,6 +27,10 @@ class SkinStrings(skin: String = UncivGame.Current.settings.skin) {
* If the UI element is used in multiple Screens start the path with General
* e.g. General/Tooltip
*
* If the UI element has multiple states with different tints use a distinct
* name for every state e.g.
* - CityScreen/CityConstructionTable/QueueEntry
* - CityScreen/CityConstructionTable/QueueEntrySelected
*
* @param default The path to the background which should be used if path is not available.
* Should be one of the predefined ones inside SkinStrings or null to get a

View File

@ -292,7 +292,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
if (constructionQueueIndex == selectedQueueEntry)
table.background = BaseScreen.skinStrings.getUiBackground(
"CityScreen/CityConstructionTable/QueueEntry",
"CityScreen/CityConstructionTable/QueueEntrySelected",
tintColor = Color.GREEN.darken(0.5f)
)
@ -356,7 +356,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
if (!isSelectedQueueEntry() && cityScreen.selectedConstruction == construction) {
pickConstructionButton.background = BaseScreen.skinStrings.getUiBackground(
"CityScreen/CityConstructionTable/PickConstructionButton",
"CityScreen/CityConstructionTable/PickConstructionButtonSelected",
tintColor = Color.GREEN.darken(0.5f)
)
}

View File

@ -17,7 +17,7 @@ class CityScreenCityPickerTable(private val cityScreen: CityScreen) : Table() {
fun update() {
val city = cityScreen.city
val civInfo = city.civInfo
background = BaseScreen.skinStrings.getUiBackground("CityScreen/CityPickerTable", BaseScreen.skinStrings.roundedEdgeRectangle, civInfo.nation.getOuterColor())
background = BaseScreen.skinStrings.getUiBackground("CityScreen/CityPickerTable", BaseScreen.skinStrings.roundedEdgeRectangleShape, civInfo.nation.getOuterColor())
clear()
if (civInfo.cities.size > 1) {

View File

@ -191,39 +191,6 @@ object ImageGetter {
return drawable.tint(tintColor)
}
@Deprecated("Use SkinStrings.getUiBackground instead to make UI element moddable", ReplaceWith("BaseScreen.skinStrings.getUiBackground(path, BaseScreen.skinStrings.roundedEdgeRectangle, tintColor)", "com.unciv.ui.utils.BaseScreen"))
fun getRoundedEdgeRectangle(tintColor: Color? = null): NinePatchDrawable {
val drawable = getNinePatch("Skins/${UncivGame.Current.settings.skin}/roundedEdgeRectangle")
if (tintColor == null) return drawable
return drawable.tint(tintColor)
}
@Deprecated("Use SkinStrings.getUiBackground instead to make UI element moddable", ReplaceWith("BaseScreen.skinStrings.getUiBackground(path, BaseScreen.skinStrings.rectangleWithOutline)", "com.unciv.ui.utils.BaseScreen"))
fun getRectangleWithOutline(): NinePatchDrawable {
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/rectangleWithOutline")
}
@Deprecated("Use SkinStrings.getUiBackground instead to make UI element moddable", ReplaceWith("BaseScreen.skinStrings.getUiBackground(path, BaseScreen.skinStrings.selectBox)", "com.unciv.ui.utils.BaseScreen"))
fun getSelectBox(): NinePatchDrawable {
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/select-box")
}
@Deprecated("Use SkinStrings.getUiBackground instead to make UI element moddable", ReplaceWith("BaseScreen.skinStrings.getUiBackground(path, BaseScreen.skinStrings.selectBoxPressed)", "com.unciv.ui.utils.BaseScreen"))
fun getSelectBoxPressed(): NinePatchDrawable {
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/select-box-pressed")
}
@Deprecated("Use SkinStrings.getUiBackground instead to make UI element moddable", ReplaceWith("BaseScreen.skinStrings.getUiBackground(path, BaseScreen.skinStrings.checkbox)", "com.unciv.ui.utils.BaseScreen"))
fun getCheckBox(): NinePatchDrawable {
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/checkbox")
}
@Deprecated("Use SkinStrings.getUiBackground instead to make UI element moddable", ReplaceWith("BaseScreen.skinStrings.getUiBackground(path, BaseScreen.skinStrings.checkboxPressed)", "com.unciv.ui.utils.BaseScreen"))
fun getCheckBoxPressed(): NinePatchDrawable {
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/checkbox-pressed")
}
fun imageExists(fileName: String) = textureRegionDrawables.containsKey(fileName)
fun techIconExists(techName: String) = imageExists("TechIcons/$techName")
fun unitIconExists(unitName: String) = imageExists("UnitIcons/$unitName")
@ -342,14 +309,6 @@ object ImageGetter {
fun getCircle() = getImage("OtherIcons/Circle")
fun getTriangle() = getImage("OtherIcons/Triangle")
@Deprecated("Use SkinStrings.getUiBackground instead to make UI element moddable", ReplaceWith("BaseScreen.skinStrings.getUiBackground(path, tintColor=color)", "com.unciv.ui.utils.BaseScreen"))
fun getBackground(color: Color): Drawable {
val drawable = getDrawable("")
drawable.minHeight = 0f
drawable.minWidth = 0f
return drawable.tint(color)
}
fun getRedCross(size: Float, alpha: Float): Actor {
val redCross = getImage("OtherIcons/Close")
redCross.setSize(size, size)

View File

@ -59,6 +59,9 @@ class MultiplayerScreen(previousScreen: BaseScreen) : PickerScreen() {
}
game.onlineMultiplayer.requestUpdate()
pickerPane.bottomTable.background = skinStrings.getUiBackground("MultiplayerScreen/BottomTable", tintColor = skinStrings.skinConfig.clearColor)
pickerPane.topTable.background = skinStrings.getUiBackground("MultiplayerScreen/TopTable", tintColor = skinStrings.skinConfig.clearColor)
}
private fun setupRightSideButton() {

View File

@ -29,6 +29,7 @@ class GameOptionsTable(
init {
getGameOptionsTable()
background = BaseScreen.skinStrings.getUiBackground("NewGameScreen/GameOptionsTable", tintColor = BaseScreen.skinStrings.skinConfig.clearColor)
}
fun update() {

View File

@ -36,6 +36,7 @@ class MapOptionsTable(private val newGameScreen: NewGameScreen): Table() {
//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()
background = BaseScreen.skinStrings.getUiBackground("NewGameScreen/MapOptionsTable", tintColor = BaseScreen.skinStrings.skinConfig.clearColor)
}
private fun addMapTypeSelection() {

View File

@ -53,7 +53,7 @@ class NationTable(val nation: Nation, width: Float, minHeight: Float, ruleset: R
if (ruleset != null) {
titleTable.padBottom(borderWidth) // visual centering including upper border
innerTable.background = BaseScreen.skinStrings.getUiBackground(
"NewGameScreen/NationTable/InnerTable",
"NewGameScreen/NationTable/RightInnerTable",
tintColor = textBackgroundColor
)
val lines = nation.getCivilopediaTextLines(ruleset)
@ -67,10 +67,6 @@ class NationTable(val nation: Nation, width: Float, minHeight: Float, ruleset: R
borderTable.add(innerTable).pad(borderWidth).grow()
add(borderTable).pad(borderWidth).width(width).minHeight(minHeight - totalPadding)
} else {
innerTable.background = BaseScreen.skinStrings.getUiBackground(
"NewGameScreen/NationTable/InnerTable",
tintColor = outerColor
)
add(innerTable).width(width).minHeight(minHeight - totalPadding)
}

View File

@ -76,6 +76,9 @@ class NewGameScreen(
if (isNarrowerThan4to3()) initPortrait()
else initLandscape()
pickerPane.bottomTable.background = skinStrings.getUiBackground("NewGameScreen/BottomTable", tintColor = skinStrings.skinConfig.clearColor)
pickerPane.topTable.background = skinStrings.getUiBackground("NewGameScreen/TopTable", tintColor = skinStrings.skinConfig.clearColor)
if (UncivGame.Current.settings.lastGameSetup != null) {
rightSideGroup.addActorAt(0, VerticalGroup().padBottom(5f))
val resetToDefaultsButton = "Reset to defaults".toTextButton()

View File

@ -61,6 +61,7 @@ class PlayerPickerTable(
top()
add(ScrollPane(playerListTable).apply { setOverscroll(false, false) }).width(civBlocksWidth)
update()
background = BaseScreen.skinStrings.getUiBackground("NewGameScreen/PlayerPickerTable", tintColor = BaseScreen.skinStrings.skinConfig.clearColor)
}
/**

View File

@ -79,7 +79,7 @@ class NotificationsOverviewTable(
val label = WrappableLabel(notification.text, labelWidth, Color.BLACK, 20)
notificationTable.add(label)
notificationTable.background = BaseScreen.skinStrings.getUiBackground("OverviewScreen/NotificationOverviewTable/Notification", BaseScreen.skinStrings.roundedEdgeRectangle)
notificationTable.background = BaseScreen.skinStrings.getUiBackground("OverviewScreen/NotificationOverviewTable/Notification", BaseScreen.skinStrings.roundedEdgeRectangleShape)
notificationTable.touchable = Touchable.enabled
notificationTable.onClick {
UncivGame.Current.resetToWorldScreen()

View File

@ -134,6 +134,9 @@ class ModManagementScreen(
reloadOnlineMods()
else
refreshOnlineModTable()
pickerPane.bottomTable.background = skinStrings.getUiBackground("ModManagementScreen/BottomTable", tintColor = skinStrings.skinConfig.clearColor)
pickerPane.topTable.background = skinStrings.getUiBackground("ModManagementScreen/TopTable", tintColor = skinStrings.skinConfig.clearColor)
}
private fun initPortrait() {

View File

@ -24,7 +24,7 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
init {
touchable = Touchable.enabled
background = BaseScreen.skinStrings.getUiBackground("TechPickerScreen/TechButton", BaseScreen.skinStrings.roundedEdgeRectangle)
background = BaseScreen.skinStrings.getUiBackground("TechPickerScreen/TechButton", BaseScreen.skinStrings.roundedEdgeRectangleShape)
pad(10f)
if (ImageGetter.techIconExists(techName))

View File

@ -73,6 +73,8 @@ class TechPickerScreen(
createTechTable()
setButtonsInfo()
topTable.add(techTable)
techTable.background = skinStrings.getUiBackground("TechPickerScreen/Background", tintColor = skinStrings.skinConfig.clearColor)
pickerPane.bottomTable.background = skinStrings.getUiBackground("TechPickerScreen/BottomTable", tintColor = skinStrings.skinConfig.clearColor)
rightSideButton.setText("Pick a tech".tr())
rightSideButton.onClick(UncivSound.Paper) {

View File

@ -83,6 +83,8 @@ class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() {
rightSideButton.onActivation { onLoadGame() }
rightSideButton.keyShortcuts.add(KeyCharAndCode.RETURN)
rightSideButton.isVisible = false
pickerPane.bottomTable.background = skinStrings.getUiBackground("LoadGameScreen/BottomTable", tintColor = skinStrings.skinConfig.clearColor)
pickerPane.topTable.background = skinStrings.getUiBackground("LoadGameScreen/TopTable", tintColor = skinStrings.skinConfig.clearColor)
}
override fun resetWindowState() {

View File

@ -140,7 +140,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab
if (!showAdditionalInfoTags || tileGroup.tileInfo.airUnits.isEmpty()) return
val secondaryColor = city.civInfo.nation.getInnerColor()
val airUnitTable = Table()
airUnitTable.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/CityButton/AirUnitTable", BaseScreen.skinStrings.roundedEdgeRectangle, city.civInfo.nation.getOuterColor()).apply { setMinSize(0f,0f) }
airUnitTable.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/CityButton/AirUnitTable", BaseScreen.skinStrings.roundedEdgeRectangleShape, city.civInfo.nation.getOuterColor()).apply { setMinSize(0f,0f) }
val aircraftImage = ImageGetter.getImage("OtherIcons/Aircraft")
aircraftImage.color = secondaryColor
airUnitTable.add(aircraftImage).size(15f)
@ -194,7 +194,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab
}
val iconTable = IconTable().apply { isTransform = false }
iconTable.touchable = Touchable.enabled
iconTable.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/CityButton/IconTable", BaseScreen.skinStrings.roundedEdgeRectangle, city.civInfo.nation.getOuterColor())
iconTable.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/CityButton/IconTable", BaseScreen.skinStrings.roundedEdgeRectangleShape, city.civInfo.nation.getOuterColor())
if (city.isInResistance()) {
val resistanceImage = ImageGetter.getImage("StatIcons/Resistance")

View File

@ -110,6 +110,10 @@ abstract class BaseScreen : Screen {
companion object {
var enableSceneDebug = false
/** Colour to use for empty sections of the screen.
* Gets overwritten by SkinConfig.clearColor after starting Unciv */
var clearColor = Color(0f, 0f, 0.2f, 1f)
lateinit var skin: Skin
lateinit var skinStrings: SkinStrings
fun setSkin() {
@ -117,15 +121,15 @@ abstract class BaseScreen : Screen {
skinStrings = SkinStrings()
skin = Skin().apply {
add("Nativefont", Fonts.font, BitmapFont::class.java)
add("RoundedEdgeRectangle", skinStrings.getUiBackground("", skinStrings.roundedEdgeRectangle), Drawable::class.java)
add("RoundedEdgeRectangle", skinStrings.getUiBackground("", skinStrings.roundedEdgeRectangleShape), Drawable::class.java)
add("Rectangle", ImageGetter.getDrawable(""), Drawable::class.java)
add("Circle", ImageGetter.getDrawable("OtherIcons/Circle").apply { setMinSize(20f, 20f) }, Drawable::class.java)
add("Scrollbar", ImageGetter.getDrawable("").apply { setMinSize(10f, 10f) }, Drawable::class.java)
add("RectangleWithOutline",skinStrings.getUiBackground("", skinStrings.rectangleWithOutline), Drawable::class.java)
add("Select-box", skinStrings.getUiBackground("", skinStrings.selectBox), Drawable::class.java)
add("Select-box-pressed", skinStrings.getUiBackground("", skinStrings.selectBoxPressed), Drawable::class.java)
add("Checkbox", skinStrings.getUiBackground("", skinStrings.checkbox), Drawable::class.java)
add("Checkbox-pressed", skinStrings.getUiBackground("", skinStrings.checkboxPressed), Drawable::class.java)
add("RectangleWithOutline",skinStrings.getUiBackground("", skinStrings.rectangleWithOutlineShape), Drawable::class.java)
add("Select-box", skinStrings.getUiBackground("", skinStrings.selectBoxShape), Drawable::class.java)
add("Select-box-pressed", skinStrings.getUiBackground("", skinStrings.selectBoxPressedShape), Drawable::class.java)
add("Checkbox", skinStrings.getUiBackground("", skinStrings.checkboxShape), Drawable::class.java)
add("Checkbox-pressed", skinStrings.getUiBackground("", skinStrings.checkboxPressedShape), Drawable::class.java)
load(Gdx.files.internal("Skin.json"))
}
skin.get(TextButton.TextButtonStyle::class.java).font = Fonts.font
@ -142,9 +146,8 @@ abstract class BaseScreen : Screen {
font = Fonts.font
listStyle.font = Fonts.font
}
clearColor = skinStrings.skinConfig.clearColor
}
/** Colour to use for empty sections of the screen. */
val clearColor = Color(0f, 0f, 0.2f, 1f)
}
/** @return `true` if the screen is higher than it is wide */

View File

@ -15,7 +15,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.Tooltip
import com.badlogic.gdx.utils.Align
import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.utils.extensions.toLabel
/**
@ -186,7 +185,7 @@ class UncivTooltip <T: Actor>(
val label = text.toLabel(BaseScreen.skinStrings.skinConfig.baseColor, 38)
label.setAlignment(Align.center)
val background = BaseScreen.skinStrings.getUiBackground("General/Tooltip", BaseScreen.skinStrings.roundedEdgeRectangle, Color.LIGHT_GRAY)
val background = BaseScreen.skinStrings.getUiBackground("General/Tooltip", BaseScreen.skinStrings.roundedEdgeRectangleShape, Color.LIGHT_GRAY)
// This controls text positioning relative to the background.
// The minute fiddling makes both single caps and longer text look centered.
@Suppress("SpellCheckingInspection")

View File

@ -283,7 +283,7 @@ class VictoryScreen(val worldScreen: WorldScreen) : PickerScreen() {
labelText = Constants.unknownNationName
}
civGroup.background = skinStrings.getUiBackground("VictoryScreen/CivGroup", skinStrings.roundedEdgeRectangle, backgroundColor)
civGroup.background = skinStrings.getUiBackground("VictoryScreen/CivGroup", skinStrings.roundedEdgeRectangleShape, backgroundColor)
val label = labelText.toLabel(labelColor)
label.setAlignment(Align.center)

View File

@ -71,7 +71,7 @@ class NotificationsScroll(
for (notification in notifications.asReversed().toList()) { // toList to avoid concurrency problems
val listItem = Table()
listItem.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/Notification", BaseScreen.skinStrings.roundedEdgeRectangle)
listItem.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/Notification", BaseScreen.skinStrings.roundedEdgeRectangleShape)
val labelWidth = maxEntryWidth - iconSize * notification.icons.size - 10f
val label = WrappableLabel(notification.text, labelWidth, Color.BLACK, 30)

View File

@ -15,7 +15,7 @@ class PlayerReadyScreen(worldScreen: WorldScreen) : BaseScreen() {
table.touchable = Touchable.enabled
val curCiv = worldScreen.viewingCiv
table.background = skinStrings.getUiBackground(
"PlayerReadyScreen",
"PlayerReadyScreen/Background",
tintColor = curCiv.nation.getOuterColor()
)

View File

@ -44,7 +44,7 @@ class TechPolicyDiplomacyButtons(val worldScreen: WorldScreen) : Table(BaseScree
add(espionageButtonHolder).padTop(10f)
add().growX() // Allows Policy and Diplo buttons to keep to the left
pickTechButton.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/PickTechButton", BaseScreen.skinStrings.roundedEdgeRectangle, colorFromRGB(7, 46, 43))
pickTechButton.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/PickTechButton", BaseScreen.skinStrings.roundedEdgeRectangleShape, colorFromRGB(7, 46, 43))
pickTechButton.defaults().pad(20f)
pickTechButton.add(pickTechLabel)
techButtonHolder.onClick(UncivSound.Paper) {

View File

@ -80,10 +80,10 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
resourceTable.background = BaseScreen.skinStrings.getUiBackground("WorldScreen/TopBar/ResourceTable", tintColor = backColor)
add(statsTable).colspan(3).growX().row()
add(resourceTable).colspan(3).growX().row()
val leftFillerBG = BaseScreen.skinStrings.getUiBackground("WorldScreen/TopBar/LeftAttachment", BaseScreen.skinStrings.roundedEdgeRectangle, backColor)
val leftFillerBG = BaseScreen.skinStrings.getUiBackground("WorldScreen/TopBar/LeftAttachment", BaseScreen.skinStrings.roundedEdgeRectangleShape, backColor)
leftFillerCell = add(BackgroundActor(leftFillerBG, Align.topLeft))
add().growX()
val rightFillerBG = BaseScreen.skinStrings.getUiBackground("WorldScreen/TopBar/RightAttachment", BaseScreen.skinStrings.roundedEdgeRectangle, backColor)
val rightFillerBG = BaseScreen.skinStrings.getUiBackground("WorldScreen/TopBar/RightAttachment", BaseScreen.skinStrings.roundedEdgeRectangleShape, backColor)
rightFillerCell = add(BackgroundActor(rightFillerBG, Align.topRight))
pack()
}

View File

@ -65,6 +65,7 @@ internal object DesktopLauncher {
if (!isRunFromJAR) {
UniqueDocsWriter().write()
UiElementDocsWriter().write()
}
val platformSpecificHelper = PlatformSpecificHelpersDesktop(config)

View File

@ -0,0 +1,40 @@
package com.unciv.app.desktop
import java.io.File
class UiElementDocsWriter {
fun write() {
val lines = File("../../docs/Modders/Creating-a-UI-skin.md").readLines()
val startIndex = lines.indexOf("<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION -->")
val endIndex = lines.indexOf("<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION_END -->")
val table = mutableListOf(
"<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION -->",
"| Directory | Name | Default shape | Image |",
"|---|:---:|:---:|---|"
)
val elements = mutableListOf<String>()
File("../../core/src/com/unciv/").walk().forEach { file ->
if (file.path.endsWith(".kt")) {
val results = Regex("getUiBackground\\((\\X*?)\"(?<path>.*)\"[ ,\n\r]*((BaseScreen\\.)?skinStrings\\.(?<defaultShape>.*)Shape)?\\X*?\\)")
.findAll(file.readText())
for (result in results) {
val path = result.groups["path"]?.value
val name = path?.takeLastWhile { it != '/' } ?: ""
val defaultShape = result.groups["defaultShape"]?.value
if (name.isNotBlank())
elements.add("| ${path!!.dropLast(name.length)} | $name | $defaultShape | |")
}
}
}
table.addAll(elements.sorted())
table.add("<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION_END -->")
val newLines = lines.subList(0, startIndex) + table + lines.subList(endIndex + 1, lines.size)
File("../../docs/Modders/Creating-a-UI-skin.md").writeText(newLines.joinToString("\n"))
}
}

View File

@ -8,7 +8,7 @@ Just like [tilesets](Creating-a-custom-tileset.md), UI skins can be used to alte
We use so called 9.png (or Ninepatch) files for every skin image because UI elements need a way to be resized based on game window size and resolution. Ninepatch files can be created manually by adding black pixels around your custom images in a specific manner or by using [Android Studio's Draw 9-patch tool](https://developer.android.com/studio/write/draw9patch) or [this tool by romannurik](https://romannurik.github.io/AndroidAssetStudio/nine-patches.html) for example. You may also check if your favorite image creation tool supports nine patches itself to generate them more easily.
Every skin image needs to be **gray scale** since colors are applied later in game. The color for the image can be modified using the [skinConfig](Creating-a-UI-skin.md#tint). Please note that tileable ninepatches and ninepatches with multiple stretch areas are not supported because of technical restrictions by libgdx.
A skin image can either be gray scale and later be colored in game by modifying the `tint` in the [skinConfig](Creating-a-UI-skin.md#tint) or be colored directly in the image. When coloring the image directly it is important to set the tint of the UI element to white. Please note that tileable ninepatches and ninepatches with multiple stretch areas are not supported because of technical restrictions by libgdx.
There are 6 basic shapes which can be placed inside the `Images/Skins/MyCoolSkinExample` folder:
- checkbox
@ -20,29 +20,39 @@ There are 6 basic shapes which can be placed inside the `Images/Skins/MyCoolSkin
These shapes are used all over Unciv and can be replaced to make a lot of UI elements change appearance at once. To change just one specific element use the [table](Creating-a-UI-skin.md#Available-UI-elements) below to create an image at the specified directory using the specified name inside `Images/Skins/MyCoolSkinExample`. See the image below for an example file structure. ![skinExample](https://user-images.githubusercontent.com/24532072/198904598-0d298035-5b02-431b-bfb4-7da4b9c821c9.png)
## Limitations
- UI elements which change color because they have multiple states can not be given multiple colors based on their state using tint
- When coloring the image directly, setting the tint of the UI element to white overwrites these states
- Tileable ninepatches and ninepatches with multiple stretch areas are not supported because of technical restrictions by libgdx
## Available UI elements
<!--- We should add an image to every identifier so its easier for modders to know which UI elements are which -->
<!--- The following table is auto generated and should not be modified manually. If you want to change it see UiElementDocsWriter.kt -->
<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION -->
| Directory | Name | Default shape | Image |
|---|:---:|:---:|---|
| CivilopediaScreen/ | EntryButton | null | |
| CityScreen/ | CityInfoTable | null | |
| CityScreen/ | CityPickerTable | roundedEdgeRectangle | |
| CityScreen/CitizenManagementTable/ | AvoidCell | null | |
| CityScreen/CitizenManagementTable/ | FocusCell | null | |
| CityScreen/CitizenManagementTable/ | ResetCell | null | |
| CityScreen/CityConstructionTable/ | AvailableConstructionsTable | null | |
| CityScreen/CityConstructionTable/ | ConstructionsQueueTable | null | |
| CityScreen/CityConstructionTable/ | Header | null | |
| CityScreen/CityConstructionTable/ | PickConstructionButton | null | |
| CityScreen/CityConstructionTable/ | PickConstructionButtonSelected | null | |
| CityScreen/CityConstructionTable/ | QueueEntry | null | |
| CityScreen/ | CityInfoTable | null | |
| CityScreen/CitizenManagementTable/ | AvoidCell | null | |
| CityScreen/CitizenManagementTable/ | FocusCell | null | |
| CityScreen/CitizenManagementTable/ | ResetCell | null | |
| CityScreen/ | CityPickerTable | roundedEdgeRectangle | |
| CityScreen/CityConstructionTable/ | QueueEntrySelected | null | |
| CityScreen/CityScreenTileTable/ | Background | null | |
| CityScreen/CityScreenTileTable/ | InnerTable | null | |
| CityScreen/CityStatsTable/ | Background | null | |
| CityScreen/CityStatsTable/ | InnerTable | null | |
| CityScreen/ConstructionInfoTable/ | Background | null | |
| CityScreen/ConstructionInfoTable/ | SelectedConstructionTable | null | |
| CivilopediaScreen/ | EntryButton | null | |
| General/ | Border | null | |
| General/ | ExpanderTab | null | |
| General/ | HealthBar | null | |
@ -51,12 +61,24 @@ These shapes are used all over Unciv and can be replaced to make a lot of UI ele
| General/Popup/ | Background | null | |
| General/Popup/ | InnerTable | null | |
| LanguagePickerScreen/ | LanguageTable | null | |
| LoadGameScreen/ | BottomTable | null | |
| LoadGameScreen/ | TopTable | null | |
| MainMenuScreen/ | Background | null | |
| MainMenuScreen/ | MenuButton | roundedEdgeRectangle | |
| MapEditor/MapEditorToolsDrawer/ | Handle | null | |
| ModManagementOptions/ | ExpanderTab | null | |
| ModManagementScreen/ | BottomTable | null | |
| ModManagementScreen/ | TopTable | null | |
| MultiplayerScreen/ | BottomTable | null | |
| MultiplayerScreen/ | TopTable | null | |
| NewGameScreen/ | BottomTable | null | |
| NewGameScreen/ | GameOptionsTable | null | |
| NewGameScreen/ | MapOptionsTable | null | |
| NewGameScreen/ | PlayerPickerTable | null | |
| NewGameScreen/ | TopTable | null | |
| NewGameScreen/NationTable/ | Background | null | |
| NewGameScreen/NationTable/ | BorderTable | null | |
| NewGameScreen/NationTable/ | InnerTable | null | |
| NewGameScreen/NationTable/ | RightInnerTable | null | |
| NewGameScreen/NationTable/ | Title | null | |
| NewGameScreen/PlayerPickerTable/ | PlayerTable | null | |
| OverviewScreen/DiplomacyOverviewTab/ | CivTable | null | |
@ -64,24 +86,28 @@ These shapes are used all over Unciv and can be replaced to make a lot of UI ele
| OverviewScreen/ReligionOverviewTab/ | BeliefDescription | null | |
| OverviewScreen/TradesOverviewTab/ | OffersTable | null | |
| OverviewScreen/UnitOverviewTab/ | UnitSupplyTable | null | |
| PlayerReadyScreen/ | Background | null | |
| TechPickerScreen/ | Background | null | |
| TechPickerScreen/ | BottomTable | null | |
| TechPickerScreen/ | TechButton | roundedEdgeRectangle | |
| VictoryScreen/ | CivGroup | roundedEdgeRectangle | |
| WorldScreen/ | AirUnitTable | null | |
| WorldScreen/ | BattleTable | null | |
| WorldScreen/ | Notification | roundedEdgeRectangle | |
| WorldScreen/ | PickTechButton | roundedEdgeRectangle | |
| WorldScreen/ | TileInfoTable | null | |
| WorldScreen/ | TutorialTaskTable | null | |
| WorldScreen/ | UnitTable | null | |
| WorldScreen/CityButton/ | AirUnitTable | roundedEdgeRectangle | |
| WorldScreen/CityButton/ | IconTable | roundedEdgeRectangle | |
| WorldScreen/CityButton/ | InfluenceBar | null | |
| WorldScreen/ | Notification | null | |
| WorldScreen/ | PickTechButton | roundedEdgeRectangle | |
| WorldScreen/ | TutorialTaskTable | null | |
| WorldScreen/TopBar/ | ResourceTable | null | |
| WorldScreen/TopBar/ | StatsTable | null | |
| WorldScreen/TopBar/ | LeftAttachment | roundedEdgeRectangle | |
| WorldScreen/TopBar/ | RightAttachment | roundedEdgeRectangle | |
| WorldScreen/ | BattleTable | null | |
| WorldScreen/ | TileInfoTable | null | |
| WorldScreen/Minimap/ | Background | null | |
| WorldScreen/Minimap/ | Border | null | |
| WorldScreen/ | UnitTable | null | |
| WorldScreen/TopBar/ | LeftAttachment | roundedEdgeRectangle | |
| WorldScreen/TopBar/ | ResourceTable | null | |
| WorldScreen/TopBar/ | RightAttachment | roundedEdgeRectangle | |
| WorldScreen/TopBar/ | StatsTable | null | |
<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION_END -->
## SkinConfig
@ -92,32 +118,34 @@ To create a config for your skin you just need to create a new .json file under
This is an example of such a config file that will be explain below:
```json
{
"baseColor": {"r":1,"g":0,"b":0,"a":1},
"skinVariants": {
"MainMenuScreen/MenuButton": {
"image": "MyCoolNewDesign",
"image": "MyCoolNewDesign"
},
"TechPickerScreen/TechButton": {
"image": "MyCoolNewDesign",
"alpha": 0.7
"image": "MyCoolNewDesign",
"alpha": 0.7
},
"WorldScreen/TopBar/ResourceTable": {
"alpha": 0.8
"alpha": 0.8
},
"WorldScreen/UnitTable": {
"tint": {"r": 1, "g": 0, "b": 0},
"image": "WorldScreen/TopBar/ResourceTable",
"alpha": 0.4
"tint": {"r": 1, "g": 0, "b": 0},
"image": "WorldScreen/TopBar/ResourceTable",
"alpha": 0.4
},
"WorldScreen/Minimap/Background": {
"tint": {"r": 0.2, "g": 0.4, "b": 0.45, "a": 1}
},
"tint": {"r": 0.2, "g": 0.4, "b": 0.45, "a": 1}
}
}
}
```
### baseColor
A color defined with normalized RGBA values. Default value: "{"r": 0, "g": 0.251, "b": 0.522, "a": 0.749}"
A color defined with normalized RGBA values. Default value: `{"r": 0, "g": 0.251, "b": 0.522, "a": 0.749}`
Defines the color unciv uses in most ui elements as default
@ -147,4 +175,4 @@ The color this UI element should have.
A float value. Default value: null
The alpha this UI element should have. Overwrites the alpha value of tint if specified.
The alpha this UI element should have. Overwrites the alpha value of tint if specified.

View File

@ -1697,6 +1697,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Conditional
??? example "&lt;if starting in the [era]&gt;"
Example: "&lt;if starting in the [Ancient era]&gt;"
Applicable to: Conditional
??? example "&lt;if no other Civilization has researched this&gt;"
Applicable to: Conditional