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
Before Width: | Height: | Size: 951 B |
Before Width: | Height: | Size: 631 B |
Before Width: | Height: | Size: 176 B |
Before Width: | Height: | Size: 832 B |
Before Width: | Height: | Size: 381 B |
Before Width: | Height: | Size: 385 B |
BIN
android/Images.Skin/Skins/Minimal/checkbox-pressed.9.png
Normal file
After Width: | Height: | Size: 596 B |
BIN
android/Images.Skin/Skins/Minimal/checkbox.9.png
Normal file
After Width: | Height: | Size: 486 B |
BIN
android/Images.Skin/Skins/Minimal/rectangleWithOutline.9.png
Normal file
After Width: | Height: | Size: 91 B |
BIN
android/Images.Skin/Skins/Minimal/roundedEdgeRectangle.9.png
Normal file
After Width: | Height: | Size: 565 B |
BIN
android/Images.Skin/Skins/Minimal/select-box-pressed.9.png
Normal file
After Width: | Height: | Size: 256 B |
BIN
android/Images.Skin/Skins/Minimal/select-box.9.png
Normal file
After Width: | Height: | Size: 260 B |
@ -81,6 +81,7 @@ object Constants {
|
||||
const val dropboxMultiplayerServer = "Dropbox"
|
||||
|
||||
const val defaultTileset = "HexaRealm"
|
||||
const val defaultSkin = "Minimal"
|
||||
|
||||
/**
|
||||
* Use this to determine whether a [MapUnit][com.unciv.logic.map.MapUnit]'s movement is exhausted
|
||||
|
@ -38,6 +38,7 @@ class GameSettings {
|
||||
|
||||
var turnsBetweenAutosaves = 1
|
||||
var tileSet: String = Constants.defaultTileset
|
||||
var skin: String = Constants.defaultSkin
|
||||
var showTutorials: Boolean = true
|
||||
var autoAssignCityProduction: Boolean = true
|
||||
var autoBuildingRoads: Boolean = true
|
||||
|
@ -47,6 +47,7 @@ object ImageGetter {
|
||||
// We then shove all the drawables into a hashmap, because the atlas specifically tells us
|
||||
// that the search on it is inefficient
|
||||
private val textureRegionDrawables = HashMap<String, TextureRegionDrawable>()
|
||||
private val ninePatchDrawables = HashMap<String, NinePatchDrawable>()
|
||||
|
||||
fun resetAtlases() {
|
||||
atlases.values.forEach { it.dispose() }
|
||||
@ -95,8 +96,13 @@ object ImageGetter {
|
||||
atlases[extraAtlas] = tempAtlas // cache the freshly loaded
|
||||
}
|
||||
for (region in tempAtlas.regions) {
|
||||
val drawable = TextureRegionDrawable(region)
|
||||
textureRegionDrawables[region.name] = drawable
|
||||
if (region.name.startsWith("Skins")) {
|
||||
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]!!
|
||||
}
|
||||
|
||||
fun getNinePatch(fileName: String?): NinePatchDrawable {
|
||||
return ninePatchDrawables[fileName] ?: NinePatchDrawable(NinePatch(textureRegionDrawables[whiteDotLocation]!!.region))
|
||||
}
|
||||
|
||||
fun getRoundedEdgeRectangle(tintColor: Color? = null): NinePatchDrawable {
|
||||
val region = getDrawable("Skin/roundedEdgeRectangle").region
|
||||
val drawable = NinePatchDrawable(NinePatch(region, 25, 25, 0, 0))
|
||||
drawable.setPadding(5f, 15f, 5f, 15f)
|
||||
val drawable = getNinePatch("Skins/${UncivGame.Current.settings.skin}/roundedEdgeRectangle")
|
||||
|
||||
if (tintColor == null) return drawable
|
||||
return drawable.tint(tintColor)
|
||||
}
|
||||
|
||||
fun getRectangleWithOutline(): NinePatchDrawable {
|
||||
val region = getDrawable("Skin/rectangleWithOutline").region
|
||||
return NinePatchDrawable(NinePatch(region, 1, 1, 1, 1))
|
||||
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/rectangleWithOutline")
|
||||
}
|
||||
|
||||
fun getSelectBox(): NinePatchDrawable {
|
||||
val region = getDrawable("Skin/select-box").region
|
||||
return NinePatchDrawable(NinePatch(region, 10, 25, 5, 5))
|
||||
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/select-box")
|
||||
}
|
||||
|
||||
fun getSelectBoxPressed(): NinePatchDrawable {
|
||||
val region = getDrawable("Skin/select-box-pressed").region
|
||||
return NinePatchDrawable(NinePatch(region, 10, 25, 5, 5))
|
||||
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/select-box-pressed")
|
||||
}
|
||||
|
||||
fun getCheckBox(): Drawable {
|
||||
return getDrawable("Skin/checkbox")
|
||||
fun getCheckBox(): NinePatchDrawable {
|
||||
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/checkbox")
|
||||
}
|
||||
|
||||
fun getCheckBoxPressed(): Drawable {
|
||||
return getDrawable("Skin/checkbox-pressed")
|
||||
fun getCheckBoxPressed(): NinePatchDrawable {
|
||||
return getNinePatch("Skins/${UncivGame.Current.settings.skin}/checkbox-pressed")
|
||||
}
|
||||
|
||||
fun imageExists(fileName: String) = textureRegionDrawables.containsKey(fileName)
|
||||
@ -459,6 +464,8 @@ object ImageGetter {
|
||||
return specialist
|
||||
}
|
||||
|
||||
fun getAvailableSkins() = ninePatchDrawables.keys.asSequence().map { it.split("/")[1] }.distinct()
|
||||
|
||||
fun getAvailableTilesets() = textureRegionDrawables.keys.asSequence().filter { it.startsWith("TileSets") }
|
||||
.map { it.split("/")[1] }.distinct()
|
||||
}
|
||||
|
@ -16,14 +16,12 @@ import com.unciv.ui.utils.WrappableLabel
|
||||
import com.unciv.ui.utils.extensions.brighten
|
||||
import com.unciv.ui.utils.extensions.onChange
|
||||
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"))
|
||||
|
||||
fun displayTab(
|
||||
optionsPopup: OptionsPopup,
|
||||
onResolutionChange: () -> Unit,
|
||||
onTilesetChange: () -> Unit
|
||||
onChange: () -> Unit,
|
||||
) = Table(BaseScreen.skin).apply {
|
||||
pad(10f)
|
||||
defaults().pad(2.5f)
|
||||
@ -44,9 +42,11 @@ fun displayTab(
|
||||
|
||||
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) {
|
||||
settings.continuousRendering = it
|
||||
@ -147,3 +147,21 @@ private fun addTileSetSelectBox(table: Table, settings: GameSettings, selectBoxM
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class OptionsPopup(
|
||||
)
|
||||
tabs.addPage(
|
||||
"Display",
|
||||
displayTab(this, ::reloadWorldAndOptions, ::reloadWorldAndOptions),
|
||||
displayTab(this, ::reloadWorldAndOptions),
|
||||
ImageGetter.getImage("UnitPromotionIcons/Scouting"), 24f
|
||||
)
|
||||
tabs.addPage(
|
||||
@ -147,6 +147,7 @@ class OptionsPopup(
|
||||
}
|
||||
}
|
||||
withGLContext {
|
||||
BaseScreen.setSkin()
|
||||
UncivGame.Current.screen?.openOptionsPopup(tabs.activePage)
|
||||
}
|
||||
}
|
||||
|