Make image preview scalable

This commit is contained in:
nea
2023-07-23 01:35:16 +02:00
parent cdf3938b77
commit 91febc31ad
9 changed files with 158 additions and 23 deletions

View File

@@ -4,7 +4,7 @@ fabric_loader = "0.14.21"
fabric_api = "0.83.0+1.20"
fabric_kotlin = "1.9.4+kotlin.1.8.21"
yarn = "1.20+build.1"
libgui = "8.0.0+1.20"
libgui = "8.0.1+1.20"
rei = "12.0.622"
devauth = "1.0.0"
modmenu = "7.0.0"

View File

@@ -5,8 +5,11 @@ import io.ktor.client.statement.*
import io.ktor.utils.io.jvm.javaio.*
import java.net.URL
import java.util.*
import moe.nea.jarvis.api.Point
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlin.math.max
import kotlin.math.min
import net.minecraft.client.gui.screen.ChatScreen
import net.minecraft.client.texture.NativeImage
@@ -35,12 +38,13 @@ object ImagePreview : FirmamentFeature {
val allowedHosts by string("allowed-hosts") { "cdn.discordapp.com,media.discordapp.com,media.discordapp.net,i.imgur.com" }
val actualAllowedHosts get() = allowedHosts.split(",").map { it.trim() }
val screenPercentage by integer("percentage", 10, 100) { 50 }
val position by position("position", 16 * 20, 9 * 20) { Point(0.0, 0.0) }
}
fun isHostAllowed(host: String) =
private fun isHostAllowed(host: String) =
TConfig.allowAllHosts || TConfig.actualAllowedHosts.any { it.equals(host, ignoreCase = true) }
fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/"))
private fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/"))
override val config get() = TConfig
val urlRegex = "https://[^. ]+\\.[^ ]+(\\.(png|gif|jpe?g))(\\?[^ ]*)?( |$)".toRegex()
@@ -54,7 +58,7 @@ object ImagePreview : FirmamentFeature {
val imageCache: MutableMap<String, Deferred<Image?>> =
Collections.synchronizedMap(mutableMapOf<String, Deferred<Image?>>())
fun tryCacheUrl(url: String) {
private fun tryCacheUrl(url: String) {
if (!isUrlAllowed(url)) {
return
}
@@ -81,6 +85,7 @@ object ImagePreview : FirmamentFeature {
}
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun onLoad() {
ClientChatLineReceivedEvent.subscribe {
it.replaceWith = it.text.transformEachRecursively { child ->
@@ -122,16 +127,10 @@ object ImagePreview : FirmamentFeature {
val imageFuture = imageCache[url] ?: return@subscribe
if (!imageFuture.isCompleted) return@subscribe
val image = imageFuture.getCompleted() ?: return@subscribe
val screen = MC.screen!!
val scale =
min(
1F,
min(
(TConfig.screenPercentage / 100F * screen.width.toFloat()) / image.width,
screen.height.toFloat() / image.height
)
)
it.drawContext.matrices.push()
val pos = TConfig.position
pos.applyTransformations(it.drawContext.matrices)
val scale = min(1F, min((9 * 20F) / image.height, (16 * 20F) / image.width))
it.drawContext.matrices.scale(scale, scale, 1F)
it.drawContext.drawTexture(
image.texture,

View File

@@ -22,10 +22,14 @@ import io.github.cottonmc.cotton.gui.widget.WGridPanel
import io.github.cottonmc.cotton.gui.widget.WLabel
import io.github.cottonmc.cotton.gui.widget.WWidget
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text
class GuiAppender(val width: Int) {
class GuiAppender(val width: Int, val screenAccessor: () -> Screen) {
private var row = 0
internal val panel = WGridPanel().also { it.setGaps(4, 4) }
internal val reloadables = mutableListOf<(() -> Unit)>()
fun set(x: Int, y: Int, w: Int, h: Int, widget: WWidget) {

View File

@@ -0,0 +1,34 @@
package moe.nea.firmament.gui.config
import io.github.cottonmc.cotton.gui.widget.WButton
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import net.minecraft.text.MutableText
import net.minecraft.text.Text
import moe.nea.firmament.jarvis.JarvisIntegration
import moe.nea.firmament.util.MC
class HudMetaHandler(val config: ManagedConfig, val label: MutableText, val width: Int, val height: Int) :
ManagedConfig.OptionHandler<HudMeta> {
override fun toJson(element: HudMeta): JsonElement? {
return Json.encodeToJsonElement(element.position)
}
override fun fromJson(element: JsonElement): HudMeta {
return HudMeta(Json.decodeFromJsonElement(element), label, width, height)
}
override fun emitGuiElements(opt: ManagedConfig.Option<HudMeta>, guiAppender: GuiAppender) {
guiAppender.appendLabeledRow(opt.labelText, WButton(Text.translatable("firmament.hud.edit", label))
.also {
it.setOnClick {
MC.screen = JarvisIntegration.jarvis.getHudEditor(
guiAppender.screenAccessor.invoke(),
listOf(opt.value)
)
}
})
}
}

View File

@@ -0,0 +1,46 @@
package moe.nea.firmament.gui.config
import moe.nea.jarvis.api.JarvisHud
import moe.nea.jarvis.api.JarvisScalable
import kotlinx.serialization.Serializable
import net.minecraft.text.Text
@Serializable
data class HudPosition(
var x: Double,
var y: Double,
var scale: Float,
)
data class HudMeta(
val position: HudPosition,
private val label: Text,
private val width: Int,
private val height: Int,
) : JarvisScalable, JarvisHud {
override fun getX(): Double = position.x
override fun setX(newX: Double) {
position.x = newX
}
override fun getY(): Double = position.y
override fun setY(newY: Double) {
position.y = newY
}
override fun getLabel(): Text = label
override fun getWidth(): Int = width
override fun getHeight(): Int = height
override fun getScale(): Float = position.scale
override fun setScale(newScale: Float) {
position.scale = newScale
}
}

View File

@@ -21,6 +21,7 @@ package moe.nea.firmament.gui.config
import io.github.cottonmc.cotton.gui.client.CottonClientScreen
import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription
import io.github.cottonmc.cotton.gui.widget.data.Insets
import moe.nea.jarvis.api.Point
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonElement
@@ -150,6 +151,19 @@ abstract class ManagedConfig(val name: String) {
}
protected fun position(
propertyName: String,
width: Int,
height: Int,
default: () -> Point,
): Option<HudMeta> {
val label = Text.translatable("firmament.config.${name}.${propertyName}")
return option(propertyName, {
val p = default()
HudMeta(HudPosition(p.x, p.y, 1F), label, width, height)
}, HudMetaHandler(this, label, width, height))
}
protected fun integer(
propertyName: String,
min: Int,
@@ -175,18 +189,26 @@ abstract class ManagedConfig(val name: String) {
fun getConfigEditor(parent: Screen? = null): CottonClientScreen {
val lwgd = LightweightGuiDescription()
val guiapp = GuiAppender(20)
var screen: Screen? = null
val guiapp = GuiAppender(20, { requireNotNull(screen) { "Screen Accessor called too early" } })
latestGuiAppender = guiapp
guiapp.panel.insets = Insets.ROOT_PANEL
sortedOptions.forEach { it.appendToGui(guiapp) }
guiapp.reloadables.forEach { it() }
lwgd.setRootPanel(guiapp.panel)
return object : CottonClientScreen(lwgd) {
override fun close() {
latestGuiAppender = null
MC.screen = parent
screen =
object : CottonClientScreen(lwgd) {
override fun init() {
latestGuiAppender = guiapp
super.init()
}
override fun close() {
latestGuiAppender = null
MC.screen = parent
}
}
}
return screen
}
fun showConfigEditor(parent: Screen? = null) {

View File

@@ -1,21 +1,42 @@
package moe.nea.firmament.jarvis
import moe.nea.jarvis.api.Jarvis
import moe.nea.jarvis.api.JarvisConfigOption
import moe.nea.jarvis.api.JarvisHud
import moe.nea.jarvis.api.JarvisPlugin
import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text
import moe.nea.firmament.Firmament
import moe.nea.firmament.features.FeatureManager
import moe.nea.firmament.gui.config.HudMeta
import moe.nea.firmament.gui.config.HudMetaHandler
import moe.nea.firmament.repo.RepoManager
class JarvisIntegration : JarvisPlugin {
override fun getModId(): String =
Firmament.MOD_ID
override fun getAllConfigOptions(): List<JarvisConfigOption> {
val configs = listOf(
companion object {
lateinit var jarvis: Jarvis
}
override fun onInitialize(jarvis: Jarvis) {
Companion.jarvis = jarvis
}
val configs
get() = listOf(
RepoManager.Config
) + FeatureManager.allFeatures.mapNotNull { it.config }
override fun getAllHuds(): List<JarvisHud> {
return configs.flatMap { config ->
config.sortedOptions.mapNotNull { if (it.handler is HudMetaHandler) it.value as HudMeta else null }
}
}
override fun getAllConfigOptions(): List<JarvisConfigOption> {
return configs.flatMap { config ->
config.sortedOptions.map {
object : JarvisConfigOption {

View File

@@ -0,0 +1,7 @@
package moe.nea.firmament.util
import kotlin.properties.ReadOnlyProperty
fun <T, V, M> ReadOnlyProperty<T, V>.map(mapper: (V) -> M): ReadOnlyProperty<T, M> {
return ReadOnlyProperty { thisRef, property -> mapper(this@map.getValue(thisRef, property)) }
}

View File

@@ -71,5 +71,7 @@
"firmament.config.image-preview.enabled": "Enable Image Preview",
"firmament.config.image-preview.allow-all-hosts": "Allow all Image Hosts",
"firmament.config.image-preview.allowed-hosts": "Allowed Image Hosts",
"firmament.config.image-preview.percentage": "Image Width (Percentage of screen)"
"firmament.config.image-preview.percentage": "Image Width (Percentage of screen)",
"firmament.config.image-preview.position": "Chat Image Preview",
"firmament.hud.edit": "Edit %s"
}