Moddable UI Skins [Basics] (#7714)

* Made UI skins selectable

This allows mods to provide alternative skin pngs without overriding the default skin purely by being downloaded

* Added UI Skin loading from NinePatches

This allows mod creators to define the stretch region and padding directly in the png

* Update baseScreen skin on skin reload

* Merged displayTab onChange functions

As all of them are equal anyway
This commit is contained in:
Leonard Günther
2022-09-04 16:09:14 +02:00
committed by GitHub
parent 30ed0f544d
commit f4dc138186
17 changed files with 49 additions and 21 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 951 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

View File

@ -81,6 +81,7 @@ object Constants {
const val dropboxMultiplayerServer = "Dropbox" const val dropboxMultiplayerServer = "Dropbox"
const val defaultTileset = "HexaRealm" const val defaultTileset = "HexaRealm"
const val defaultSkin = "Minimal"
/** /**
* Use this to determine whether a [MapUnit][com.unciv.logic.map.MapUnit]'s movement is exhausted * Use this to determine whether a [MapUnit][com.unciv.logic.map.MapUnit]'s movement is exhausted

View File

@ -38,6 +38,7 @@ class GameSettings {
var turnsBetweenAutosaves = 1 var turnsBetweenAutosaves = 1
var tileSet: String = Constants.defaultTileset var tileSet: String = Constants.defaultTileset
var skin: String = Constants.defaultSkin
var showTutorials: Boolean = true var showTutorials: Boolean = true
var autoAssignCityProduction: Boolean = true var autoAssignCityProduction: Boolean = true
var autoBuildingRoads: Boolean = true var autoBuildingRoads: Boolean = true

View File

@ -47,6 +47,7 @@ object ImageGetter {
// We then shove all the drawables into a hashmap, because the atlas specifically tells us // We then shove all the drawables into a hashmap, because the atlas specifically tells us
// that the search on it is inefficient // that the search on it is inefficient
private val textureRegionDrawables = HashMap<String, TextureRegionDrawable>() private val textureRegionDrawables = HashMap<String, TextureRegionDrawable>()
private val ninePatchDrawables = HashMap<String, NinePatchDrawable>()
fun resetAtlases() { fun resetAtlases() {
atlases.values.forEach { it.dispose() } atlases.values.forEach { it.dispose() }
@ -95,8 +96,13 @@ object ImageGetter {
atlases[extraAtlas] = tempAtlas // cache the freshly loaded atlases[extraAtlas] = tempAtlas // cache the freshly loaded
} }
for (region in tempAtlas.regions) { for (region in tempAtlas.regions) {
val drawable = TextureRegionDrawable(region) if (region.name.startsWith("Skins")) {
textureRegionDrawables[region.name] = drawable val ninePatch = tempAtlas.createPatch(region.name)
ninePatchDrawables[region.name] = NinePatchDrawable(ninePatch)
} else {
val drawable = TextureRegionDrawable(region)
textureRegionDrawables[region.name] = drawable
}
} }
} }
} }
@ -175,36 +181,35 @@ object ImageGetter {
return textureRegionDrawables[fileName] ?: textureRegionDrawables[whiteDotLocation]!! return textureRegionDrawables[fileName] ?: textureRegionDrawables[whiteDotLocation]!!
} }
fun getNinePatch(fileName: String?): NinePatchDrawable {
return ninePatchDrawables[fileName] ?: NinePatchDrawable(NinePatch(textureRegionDrawables[whiteDotLocation]!!.region))
}
fun getRoundedEdgeRectangle(tintColor: Color? = null): NinePatchDrawable { fun getRoundedEdgeRectangle(tintColor: Color? = null): NinePatchDrawable {
val region = getDrawable("Skin/roundedEdgeRectangle").region val drawable = getNinePatch("Skins/${UncivGame.Current.settings.skin}/roundedEdgeRectangle")
val drawable = NinePatchDrawable(NinePatch(region, 25, 25, 0, 0))
drawable.setPadding(5f, 15f, 5f, 15f)
if (tintColor == null) return drawable if (tintColor == null) return drawable
return drawable.tint(tintColor) return drawable.tint(tintColor)
} }
fun getRectangleWithOutline(): NinePatchDrawable { fun getRectangleWithOutline(): NinePatchDrawable {
val region = getDrawable("Skin/rectangleWithOutline").region return getNinePatch("Skins/${UncivGame.Current.settings.skin}/rectangleWithOutline")
return NinePatchDrawable(NinePatch(region, 1, 1, 1, 1))
} }
fun getSelectBox(): NinePatchDrawable { fun getSelectBox(): NinePatchDrawable {
val region = getDrawable("Skin/select-box").region return getNinePatch("Skins/${UncivGame.Current.settings.skin}/select-box")
return NinePatchDrawable(NinePatch(region, 10, 25, 5, 5))
} }
fun getSelectBoxPressed(): NinePatchDrawable { fun getSelectBoxPressed(): NinePatchDrawable {
val region = getDrawable("Skin/select-box-pressed").region return getNinePatch("Skins/${UncivGame.Current.settings.skin}/select-box-pressed")
return NinePatchDrawable(NinePatch(region, 10, 25, 5, 5))
} }
fun getCheckBox(): Drawable { fun getCheckBox(): NinePatchDrawable {
return getDrawable("Skin/checkbox") return getNinePatch("Skins/${UncivGame.Current.settings.skin}/checkbox")
} }
fun getCheckBoxPressed(): Drawable { fun getCheckBoxPressed(): NinePatchDrawable {
return getDrawable("Skin/checkbox-pressed") return getNinePatch("Skins/${UncivGame.Current.settings.skin}/checkbox-pressed")
} }
fun imageExists(fileName: String) = textureRegionDrawables.containsKey(fileName) fun imageExists(fileName: String) = textureRegionDrawables.containsKey(fileName)
@ -459,6 +464,8 @@ object ImageGetter {
return specialist return specialist
} }
fun getAvailableSkins() = ninePatchDrawables.keys.asSequence().map { it.split("/")[1] }.distinct()
fun getAvailableTilesets() = textureRegionDrawables.keys.asSequence().filter { it.startsWith("TileSets") } fun getAvailableTilesets() = textureRegionDrawables.keys.asSequence().filter { it.startsWith("TileSets") }
.map { it.split("/")[1] }.distinct() .map { it.split("/")[1] }.distinct()
} }

View File

@ -16,14 +16,12 @@ import com.unciv.ui.utils.WrappableLabel
import com.unciv.ui.utils.extensions.brighten import com.unciv.ui.utils.extensions.brighten
import com.unciv.ui.utils.extensions.onChange import com.unciv.ui.utils.extensions.onChange
import com.unciv.ui.utils.extensions.toLabel import com.unciv.ui.utils.extensions.toLabel
import com.unciv.ui.worldscreen.WorldScreen
private val resolutionArray = com.badlogic.gdx.utils.Array(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000")) private val resolutionArray = com.badlogic.gdx.utils.Array(arrayOf("750x500", "900x600", "1050x700", "1200x800", "1500x1000"))
fun displayTab( fun displayTab(
optionsPopup: OptionsPopup, optionsPopup: OptionsPopup,
onResolutionChange: () -> Unit, onChange: () -> Unit,
onTilesetChange: () -> Unit
) = Table(BaseScreen.skin).apply { ) = Table(BaseScreen.skin).apply {
pad(10f) pad(10f)
defaults().pad(2.5f) defaults().pad(2.5f)
@ -44,9 +42,11 @@ fun displayTab(
addUnitIconAlphaSlider(this, settings, optionsPopup.selectBoxMinWidth) addUnitIconAlphaSlider(this, settings, optionsPopup.selectBoxMinWidth)
addResolutionSelectBox(this, settings, optionsPopup.selectBoxMinWidth, onResolutionChange) addResolutionSelectBox(this, settings, optionsPopup.selectBoxMinWidth, onChange)
addTileSetSelectBox(this, settings, optionsPopup.selectBoxMinWidth, onTilesetChange) addTileSetSelectBox(this, settings, optionsPopup.selectBoxMinWidth, onChange)
addSkinSelectBox(this, settings, optionsPopup.selectBoxMinWidth, onChange)
optionsPopup.addCheckbox(this, "Continuous rendering", settings.continuousRendering) { optionsPopup.addCheckbox(this, "Continuous rendering", settings.continuousRendering) {
settings.continuousRendering = it settings.continuousRendering = it
@ -147,3 +147,21 @@ private fun addTileSetSelectBox(table: Table, settings: GameSettings, selectBoxM
onTilesetChange() onTilesetChange()
} }
} }
private fun addSkinSelectBox(table: Table, settings: GameSettings, selectBoxMinWidth: Float, onSkinChange: () -> Unit) {
table.add("UI Skin".toLabel()).left().fillX()
val skinSelectBox = SelectBox<String>(table.skin)
val skinArray = Array<String>()
val skins = ImageGetter.getAvailableSkins()
for (skin in skins) skinArray.add(skin)
skinSelectBox.items = skinArray
skinSelectBox.selected = settings.skin
table.add(skinSelectBox).minWidth(selectBoxMinWidth).pad(10f).row()
skinSelectBox.onChange {
settings.skin = skinSelectBox.selected
// ImageGetter ruleset should be correct no matter what screen we're on
onSkinChange()
}
}

View File

@ -80,7 +80,7 @@ class OptionsPopup(
) )
tabs.addPage( tabs.addPage(
"Display", "Display",
displayTab(this, ::reloadWorldAndOptions, ::reloadWorldAndOptions), displayTab(this, ::reloadWorldAndOptions),
ImageGetter.getImage("UnitPromotionIcons/Scouting"), 24f ImageGetter.getImage("UnitPromotionIcons/Scouting"), 24f
) )
tabs.addPage( tabs.addPage(
@ -147,6 +147,7 @@ class OptionsPopup(
} }
} }
withGLContext { withGLContext {
BaseScreen.setSkin()
UncivGame.Current.screen?.openOptionsPopup(tabs.activePage) UncivGame.Current.screen?.openOptionsPopup(tabs.activePage)
} }
} }