diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index a7e48455b7..f05aa0d27f 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -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 diff --git a/core/src/com/unciv/models/skins/SkinConfig.kt b/core/src/com/unciv/models/skins/SkinConfig.kt index 9411d106eb..9fca0333ae 100644 --- a/core/src/com/unciv/models/skins/SkinConfig.kt +++ b/core/src/com/unciv/models/skins/SkinConfig.kt @@ -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 = 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) } } diff --git a/core/src/com/unciv/models/skins/SkinStrings.kt b/core/src/com/unciv/models/skins/SkinStrings.kt index 7cd4dd383c..6640320e66 100644 --- a/core/src/com/unciv/models/skins/SkinStrings.kt +++ b/core/src/com/unciv/models/skins/SkinStrings.kt @@ -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,22 +48,53 @@ 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 } - return ImageGetter.getNinePatch(location, tint) + + 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) = @@ -69,4 +102,10 @@ class SkinStrings(skin: String = UncivGame.Current.settings.skin) { ?: default ?: skinConfig.clearColor + + fun getUIFontColor(path: String) = skinConfig.skinVariants[path]?.foregroundColor + + fun getUIIconColor(path: String) = + skinConfig.skinVariants[path]?.iconColor ?: skinConfig.skinVariants[path]?.foregroundColor + } diff --git a/core/src/com/unciv/ui/images/ImageGetter.kt b/core/src/com/unciv/ui/images/ImageGetter.kt index f5f877fe13..de98fdbcc6 100644 --- a/core/src/com/unciv/ui/images/ImageGetter.kt +++ b/core/src/com/unciv/ui/images/ImageGetter.kt @@ -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]!! diff --git a/docs/Modders/Creating-a-UI-skin.md b/docs/Modders/Creating-a-UI-skin.md index 549dcad5e5..377f0ebd15 100644 --- a/docs/Modders/Creating-a-UI-skin.md +++ b/docs/Modders/Creating-a-UI-skin.md @@ -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. \ No newline at end of file +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. \ No newline at end of file