Local and/or jpg mod previews (#9394)

* Allow mod preview image to be a jpg

* Allow preview images from WIP mods lacking github url

* Wiki preview.jpg

* Allow mod preview image to be a jpg - review
This commit is contained in:
SomeTroglodyte 2023-05-16 12:00:44 +02:00 committed by GitHub
parent 7f4b7bbd21
commit 1de866c7ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 28 deletions

View File

@ -253,9 +253,11 @@ object Github {
} }
fun tryGetPreviewImage(modUrl:String, defaultBranch: String): Pixmap?{ fun tryGetPreviewImage(modUrl:String, defaultBranch: String): Pixmap?{
val fileLocation = "$modUrl/$defaultBranch/preview.png" val fileLocation = "$modUrl/$defaultBranch/preview"
.replace("github.com", "raw.githubusercontent.com") .replace("github.com", "raw.githubusercontent.com")
val file = download(fileLocation) ?: return null val file = download("$fileLocation.jpg")
?: download("$fileLocation.png")
?: return null
val byteArray = file.readBytes() val byteArray = file.readBytes()
val buffer = ByteBuffer.allocateDirect(byteArray.size).put(byteArray).position(0) val buffer = ByteBuffer.allocateDirect(byteArray.size).put(byteArray).position(0)
return Pixmap(buffer) return Pixmap(buffer)

View File

@ -2,6 +2,7 @@ package com.unciv.ui.screens.pickerscreens
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.Touchable
@ -52,6 +53,7 @@ import com.unciv.utils.launchOnGLThread
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer
import kotlin.math.max import kotlin.math.max
/** /**
@ -64,6 +66,18 @@ class ModManagementScreen(
previousInstalledMods: HashMap<String, ModUIData>? = null, previousInstalledMods: HashMap<String, ModUIData>? = null,
previousOnlineMods: HashMap<String, ModUIData>? = null previousOnlineMods: HashMap<String, ModUIData>? = null
): PickerScreen(disableScroll = true), RecreateOnResize { ): PickerScreen(disableScroll = true), RecreateOnResize {
companion object {
// Tweakable constants
/** For preview.png */
const val maxAllowedPreviewImageSize = 200f
/** Github queries use this limit */
const val amountPerPage = 100
val modsToHideAsUrl by lazy {
val blockedModsFile = Gdx.files.internal("jsons/ManuallyBlockedMods.json")
json().fromJsonFile(Array<String>::class.java, blockedModsFile)
}
}
private val modTable = Table().apply { defaults().pad(10f) } private val modTable = Table().apply { defaults().pad(10f) }
private val scrollInstalledMods = AutoScrollPane(modTable) private val scrollInstalledMods = AutoScrollPane(modTable)
@ -72,8 +86,6 @@ class ModManagementScreen(
private val modActionTable = Table().apply { defaults().pad(10f) } private val modActionTable = Table().apply { defaults().pad(10f) }
private val optionsManager = ModManagementOptions(this) private val optionsManager = ModManagementOptions(this)
val amountPerPage = 100
private var lastSelectedButton: Button? = null private var lastSelectedButton: Button? = null
private var lastSyncMarkedButton: Button? = null private var lastSyncMarkedButton: Button? = null
private var selectedMod: Github.Repo? = null private var selectedMod: Github.Repo? = null
@ -95,6 +107,7 @@ class ModManagementScreen(
// cleanup - background processing needs to be stopped on exit and memory freed // cleanup - background processing needs to be stopped on exit and memory freed
private var runningSearchJob: Job? = null private var runningSearchJob: Job? = null
private var stopBackgroundTasks = false private var stopBackgroundTasks = false
override fun dispose() { override fun dispose() {
// make sure the worker threads will not continue trying their time-intensive job // make sure the worker threads will not continue trying their time-intensive job
runningSearchJob?.cancel() runningSearchJob?.cancel()
@ -349,14 +362,18 @@ class ModManagementScreen(
* @param repo: the repository instance as received from the GitHub api * @param repo: the repository instance as received from the GitHub api
*/ */
private fun addModInfoToActionTable(repo: Github.Repo) { private fun addModInfoToActionTable(repo: Github.Repo) {
addModInfoToActionTable(repo.html_url, repo.default_branch, repo.pushed_at, repo.owner.login, repo.size) addModInfoToActionTable(
repo.name, repo.html_url, repo.default_branch,
repo.pushed_at, repo.owner.login, repo.size
)
} }
/** Recreate the information part of the right-hand column /** Recreate the information part of the right-hand column
* @param modName: The mod name (name from the RuleSet) * @param modName: The mod name (name from the RuleSet)
* @param modOptions: The ModOptions as enriched by us with GitHub metadata when originally downloaded * @param modOptions: The ModOptions as enriched by us with GitHub metadata when originally downloaded
*/ */
private fun addModInfoToActionTable(modOptions: ModOptions) { private fun addModInfoToActionTable(modName: String, modOptions: ModOptions) {
addModInfoToActionTable( addModInfoToActionTable(
modName,
modOptions.modUrl, modOptions.modUrl,
modOptions.defaultBranch, modOptions.defaultBranch,
modOptions.lastUpdated, modOptions.lastUpdated,
@ -368,6 +385,7 @@ class ModManagementScreen(
private val repoUrlToPreviewImage = HashMap<String, Texture?>() private val repoUrlToPreviewImage = HashMap<String, Texture?>()
private fun addModInfoToActionTable( private fun addModInfoToActionTable(
modName: String,
repoUrl: String, repoUrl: String,
default_branch: String, default_branch: String,
updatedAt: String, updatedAt: String,
@ -380,6 +398,9 @@ class ModManagementScreen(
val imageHolder = Table() val imageHolder = Table()
if (repoUrl.isEmpty())
addLocalPreviewImage(imageHolder, modName)
else
addPreviewImage(imageHolder, repoUrl, default_branch) addPreviewImage(imageHolder, repoUrl, default_branch)
modActionTable.add(imageHolder).row() modActionTable.add(imageHolder).row()
@ -387,7 +408,7 @@ class ModManagementScreen(
if (author.isNotEmpty()) if (author.isNotEmpty())
modActionTable.add("Author: [$author]".toLabel()).row() modActionTable.add("Author: [$author]".toLabel()).row()
if (modSize > 0){ if (modSize > 0) {
if (modSize < 2048) if (modSize < 2048)
modActionTable.add("Size: [$modSize] kB".toLabel()).padBottom(15f).row() modActionTable.add("Size: [$modSize] kB".toLabel()).padBottom(15f).row()
else else
@ -395,7 +416,7 @@ class ModManagementScreen(
} }
// offer link to open the repo itself in a browser // offer link to open the repo itself in a browser
if (repoUrl != "") { if (repoUrl.isNotEmpty()) {
modActionTable.add("Open Github page".toTextButton().onClick { modActionTable.add("Open Github page".toTextButton().onClick {
Gdx.net.openURI(repoUrl) Gdx.net.openURI(repoUrl)
}).row() }).row()
@ -409,6 +430,15 @@ class ModManagementScreen(
} }
} }
private fun setTextureAsPreview(imageHolder: Table, texture: Texture) {
val cell = imageHolder.add(Image(texture))
val largestImageSize = max(texture.width, texture.height)
if (largestImageSize > maxAllowedPreviewImageSize) {
val resizeRatio = maxAllowedPreviewImageSize / largestImageSize
cell.size(texture.width * resizeRatio, texture.height * resizeRatio)
}
}
private fun addPreviewImage( private fun addPreviewImage(
imageHolder: Table, imageHolder: Table,
repoUrl: String, repoUrl: String,
@ -416,19 +446,10 @@ class ModManagementScreen(
) { ) {
if (!repoUrl.startsWith("http")) return // invalid url if (!repoUrl.startsWith("http")) return // invalid url
fun setTextureAsPreview(texture: Texture) {
val maxAllowedImageSize = 200f
val cell = imageHolder.add(Image(texture))
val largestImageSize = max(texture.width, texture.height)
if (largestImageSize > maxAllowedImageSize) {
val resizeRatio = maxAllowedImageSize / largestImageSize
cell.size(texture.width * resizeRatio, texture.height * resizeRatio)
}
}
if (repoUrlToPreviewImage.containsKey(repoUrl)) { if (repoUrlToPreviewImage.containsKey(repoUrl)) {
val texture = repoUrlToPreviewImage[repoUrl] val texture = repoUrlToPreviewImage[repoUrl]
if (texture != null) setTextureAsPreview(texture) if (texture != null) setTextureAsPreview(imageHolder, texture)
return return
} }
@ -443,11 +464,20 @@ class ModManagementScreen(
val texture = Texture(imagePixmap) val texture = Texture(imagePixmap)
imagePixmap.dispose() imagePixmap.dispose()
repoUrlToPreviewImage[repoUrl] = texture repoUrlToPreviewImage[repoUrl] = texture
setTextureAsPreview(texture) setTextureAsPreview(imageHolder, texture)
} }
} }
} }
private fun addLocalPreviewImage(imageHolder: Table, modName: String) {
// No concurrency, order of magnitude 20ms
val modFolder = Gdx.files.local("mods/$modName")
val previewFile = modFolder.child("preview.jpg").takeIf { it.exists() }
?: modFolder.child("preview.png").takeIf { it.exists() }
?: return
setTextureAsPreview(imageHolder, Texture(previewFile))
}
/** Create the special "Download from URL" button */ /** Create the special "Download from URL" button */
private fun getDownloadFromUrlButton(): TextButton { private fun getDownloadFromUrlButton(): TextButton {
val downloadButton = "Download mod from URL".toTextButton() val downloadButton = "Download mod from URL".toTextButton()
@ -586,7 +616,7 @@ class ModManagementScreen(
selectedMod = null selectedMod = null
modActionTable.clear() modActionTable.clear()
// show mod information first // show mod information first
addModInfoToActionTable(mod.modOptions) addModInfoToActionTable(mod.name, mod.modOptions)
// offer 'permanent visual mod' toggle // offer 'permanent visual mod' toggle
val visualMods = game.settings.visualMods val visualMods = game.settings.visualMods
@ -727,10 +757,4 @@ class ModManagementScreen(
override fun recreate(): BaseScreen = ModManagementScreen(installedModInfo, onlineModInfo) override fun recreate(): BaseScreen = ModManagementScreen(installedModInfo, onlineModInfo)
companion object {
val modsToHideAsUrl by lazy {
val blockedModsFile = Gdx.files.internal("jsons/ManuallyBlockedMods.json")
json().fromJsonFile(Array<String>::class.java, blockedModsFile)
}
}
} }

View File

@ -115,7 +115,7 @@ When loading a mod, it needs to be in its own folder in `/mods` - this is how yo
## Other ## Other
You can add an image that will be displayed to users in the mod management by adding a "preview.png" file. You can add an image that will be displayed to users in the mod management by adding a "preview.jpg" _or_ "preview.png" file.
Existing mods can be found [here](https://github.com/topics/unciv-mod)! Existing mods can be found [here](https://github.com/topics/unciv-mod)!