Added more options for UI skin mods (#12501)

* Add fallback skin to skin config

* Add icon and font color to SkinElement

* Use foregroundColor if no icon color is specified

+ Fix fallbackSkinVariants are not used

* Add defaultVariantTint to reduce mod copy pasta

* Add docs for new skin variables
This commit is contained in:
Leonard Günther
2024-11-21 18:28:09 +01:00
committed by GitHub
parent 37169ae719
commit 862762735a
5 changed files with 102 additions and 19 deletions

View File

@ -107,6 +107,7 @@ object Constants {
const val defaultFallbackTileset = "FantasyHex"
const val defaultUnitset = "AbsoluteUnits"
const val defaultSkin = "Minimal"
const val defaultFallbackSkin = "Minimal"
/**
* Use this to determine whether a [MapUnit][com.unciv.logic.map.mapunit.MapUnit]'s movement is exhausted

View File

@ -1,10 +1,13 @@
package com.unciv.models.skins
import com.badlogic.gdx.graphics.Color
import com.unciv.Constants
class SkinConfig(initialCapacity: Int) {
var baseColor: Color = Color(0x004085bf)
var clearColor: Color = Color(0x000033ff)
var defaultVariantTint: Color? = null
var fallbackSkin: String? = Constants.defaultFallbackSkin
var skinVariants: HashMap<String, SkinElement> = HashMap(initialCapacity)
constructor() : this(16) // = HashMap.DEFAULT_INITIAL_CAPACITY which is private
@ -16,17 +19,22 @@ class SkinConfig(initialCapacity: Int) {
val image: String? = null
val tint: Color? = null
val alpha: Float? = null
val foregroundColor: Color? = null
val iconColor: Color? = null
}
fun clone() = SkinConfig(skinVariants.size).also { it.updateConfig(this) }
/** 'Merges' [other] into **`this`**
*
* [baseColor] and [clearColor] are overwritten with clones from [other].
* [baseColor], [clearColor], and [defaultVariantTint] are overwritten with clones from [other].
* [fallbackSkin] is overwritten with [other]'s value.
* [skinVariants] with the same key are copied and overwritten, new [skinVariants] are added. */
fun updateConfig(other: SkinConfig) {
baseColor = other.baseColor.cpy()
clearColor = other.clearColor.cpy()
defaultVariantTint = other.defaultVariantTint?.cpy()
fallbackSkin = other.fallbackSkin
skinVariants.putAll(other.skinVariants)
}
}

View File

@ -8,19 +8,21 @@ import com.unciv.ui.images.ImageGetter
class SkinStrings(skin: String = UncivGame.Current.settings.skin) {
private val skinLocation = "Skins/$skin/"
val skinConfig = SkinCache[skin] ?: SkinConfig()
private val fallbackSkinLocation = if (skinConfig.fallbackSkin != null) "Skins/${skinConfig.fallbackSkin}/" else null
private val fallbackSkinConfig = SkinCache[skinConfig.fallbackSkin]
// Default shapes must always end with "Shape" so the UiElementDocsWriter can identify them
val roundedEdgeRectangleSmallShape = skinLocation + "roundedEdgeRectangle-small"
val roundedTopEdgeRectangleSmallShape = skinLocation + "roundedTopEdgeRectangle-small"
val roundedTopEdgeRectangleSmallBorderShape = skinLocation + "roundedTopEdgeRectangle-small-border"
val roundedEdgeRectangleMidShape = skinLocation + "roundedEdgeRectangle-mid"
val roundedEdgeRectangleMidBorderShape = skinLocation + "roundedEdgeRectangle-mid-border"
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"
val roundedEdgeRectangleSmallShape = "roundedEdgeRectangle-small"
val roundedTopEdgeRectangleSmallShape = "roundedTopEdgeRectangle-small"
val roundedTopEdgeRectangleSmallBorderShape = "roundedTopEdgeRectangle-small-border"
val roundedEdgeRectangleMidShape = "roundedEdgeRectangle-mid"
val roundedEdgeRectangleMidBorderShape = "roundedEdgeRectangle-mid-border"
val roundedEdgeRectangleShape = "roundedEdgeRectangle"
val rectangleWithOutlineShape = "rectangleWithOutline"
val selectBoxShape = "select-box"
val selectBoxPressedShape = "select-box-pressed"
val checkboxShape = "checkbox"
val checkboxPressedShape = "checkbox-pressed"
/**
* Gets either a drawable which was defined inside skinConfig for the given path or the drawable
@ -46,27 +48,64 @@ class SkinStrings(skin: String = UncivGame.Current.settings.skin) {
* separate alpha value, it will be applied to a clone of either color.
*/
fun getUiBackground(path: String, default: String? = null, tintColor: Color? = null): NinePatchDrawable {
val locationForDefault = skinLocation + default
val locationByName = skinLocation + path
val skinVariant = skinConfig.skinVariants[path]
val locationByConfigVariant = if (skinVariant?.image != null) skinLocation + skinVariant.image else null
val tint = (skinVariant?.tint ?: tintColor)?.run {
val tint = (skinVariant?.tint ?: skinConfig.defaultVariantTint ?: tintColor)?.run {
if (skinVariant?.alpha == null) this
else cpy().apply { a = skinVariant.alpha }
}
val location = when {
locationByConfigVariant != null && ImageGetter.ninePatchImageExists(locationByConfigVariant) ->
locationByConfigVariant
ImageGetter.ninePatchImageExists(locationByName) ->
locationByName
default != null && ImageGetter.ninePatchImageExists(locationForDefault) ->
locationForDefault
else ->
default
null
}
if (location != null) {
return ImageGetter.getNinePatch(location, tint)
}
val fallbackLocationForDefault = fallbackSkinLocation + default
val fallbackLocationByName = fallbackSkinLocation + path
val fallbackSkinVariant = fallbackSkinConfig?.skinVariants?.get(path)
val fallbackLocationByConfigVariant = if (fallbackSkinVariant?.image != null)
fallbackSkinLocation + fallbackSkinVariant.image
else
null
val fallbackTint = (fallbackSkinVariant?.tint ?: tintColor)?.run {
if (fallbackSkinVariant?.alpha == null) this
else cpy().apply { a = fallbackSkinVariant.alpha }
}
val fallbackLocation = when {
fallbackLocationByConfigVariant != null && ImageGetter.ninePatchImageExists(fallbackLocationByConfigVariant) ->
fallbackLocationByConfigVariant
ImageGetter.ninePatchImageExists(fallbackLocationByName) ->
fallbackLocationByName
default != null && ImageGetter.ninePatchImageExists(fallbackLocationForDefault) ->
fallbackLocationForDefault
else ->
null
}
return ImageGetter.getNinePatch(fallbackLocation, fallbackTint)
}
fun getUIColor(path: String, default: Color? = null) =
skinConfig.skinVariants[path]?.tint
?: default
?: skinConfig.clearColor
fun getUIFontColor(path: String) = skinConfig.skinVariants[path]?.foregroundColor
fun getUIIconColor(path: String) =
skinConfig.skinVariants[path]?.iconColor ?: skinConfig.skinVariants[path]?.foregroundColor
}

View File

@ -111,6 +111,8 @@ object ImageGetter {
}
for (region in tempAtlas.regions) {
if (region.name.startsWith("Skins")) {
// TODO: give user a mod warning that the image names has to be [name].9.png
// if this throws an exception
val ninePatch = tempAtlas.createPatch(region.name)
ninePatchDrawables[region.name] = NinePatchDrawable(ninePatch)
} else {
@ -213,7 +215,8 @@ object ImageGetter {
fun getExternalImage(fileName: String) =
getExternalImage(Gdx.files.internal("ExtraImages/$fileName"))
fun getImage(fileName: String?): Image = ImageWithCustomSize(getDrawable(fileName))
fun getImage(fileName: String?, tintColor: Color? = null): Image =
ImageWithCustomSize(getDrawable(fileName)).apply { color = tintColor ?: Color.WHITE }
fun getDrawable(fileName: String?): TextureRegionDrawable =
textureRegionDrawables[fileName] ?: textureRegionDrawables[whiteDotLocation]!!

View File

@ -169,9 +169,12 @@ This is an example of such a config file that will be explain below:
```json
{
"baseColor": {"r":1,"g":0,"b":0,"a":1},
"defaultVariantTint": {"r":1,"g":1,"b":1,"a":1},
"skinVariants": {
"MainMenuScreen/MenuButton": {
"image": "MyCoolNewDesign"
"image": "MyCoolNewDesign",
"foregroundColor": {"r": 0, "g": 0, "b": 1, "a": 1},
"iconColor": {"r": 0, "g": 1, "b": 0, "a": 1}
},
"TechPickerScreen/TechButton": {
"image": "MyCoolNewDesign",
@ -198,6 +201,21 @@ A color defined with normalized RGBA values. Default value: `{"r": 0, "g": 0.251
Defines the color unciv uses in most ui elements as default
### fallbackSkin
A string. Default value: "Minimal".
The name of another skin to use as a fallback if an image is not found or not specified in this skin.
Set to null to disable fallback.
### defaultVariantTint
A color defined with normalized RGBA values. Default value: null
The tint all skinVariants should use if not explicitly specified in a skinVariant.
If you mostly use colored images set this to white (`{"r": 1, "g": 1, "b": 1, "a": 1}`) to get
the correct colors.
### skinVariants
A dictionary mapping string to a SkinElement. Default value: empty
@ -218,10 +236,24 @@ A path to an image. The file is expected to be located alongside the 6 basic sha
A color defined with normalized RGBA values. Default value: null
The color this UI element should have.
The tint this UI element should get. Is applied as a tint on top of the image. This means that if the
image is colored and the tint is not white the tint color will merge with the image color and not override it.
#### alpha
A float value. Default value: null
The alpha this UI element should have. Overwrites the alpha value of tint if specified.
### foregroundColor
A color defined with normalized RGBA values. Default value: null
The color this UI element should use for its font and icons. To color icon and font differently use
the `iconColor` in addition to this.
### iconColor
A color defined with normalized RGBA values. Default value: null
The color this UI element should use for its icons. Overrides the `foregroundColor` for icons if specified.