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_api = "0.83.0+1.20"
fabric_kotlin = "1.9.4+kotlin.1.8.21" fabric_kotlin = "1.9.4+kotlin.1.8.21"
yarn = "1.20+build.1" yarn = "1.20+build.1"
libgui = "8.0.0+1.20" libgui = "8.0.1+1.20"
rei = "12.0.622" rei = "12.0.622"
devauth = "1.0.0" devauth = "1.0.0"
modmenu = "7.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 io.ktor.utils.io.jvm.javaio.*
import java.net.URL import java.net.URL
import java.util.* import java.util.*
import moe.nea.jarvis.api.Point
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import net.minecraft.client.gui.screen.ChatScreen import net.minecraft.client.gui.screen.ChatScreen
import net.minecraft.client.texture.NativeImage 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 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 actualAllowedHosts get() = allowedHosts.split(",").map { it.trim() }
val screenPercentage by integer("percentage", 10, 100) { 50 } 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) } 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 override val config get() = TConfig
val urlRegex = "https://[^. ]+\\.[^ ]+(\\.(png|gif|jpe?g))(\\?[^ ]*)?( |$)".toRegex() val urlRegex = "https://[^. ]+\\.[^ ]+(\\.(png|gif|jpe?g))(\\?[^ ]*)?( |$)".toRegex()
@@ -54,7 +58,7 @@ object ImagePreview : FirmamentFeature {
val imageCache: MutableMap<String, Deferred<Image?>> = val imageCache: MutableMap<String, Deferred<Image?>> =
Collections.synchronizedMap(mutableMapOf<String, Deferred<Image?>>()) Collections.synchronizedMap(mutableMapOf<String, Deferred<Image?>>())
fun tryCacheUrl(url: String) { private fun tryCacheUrl(url: String) {
if (!isUrlAllowed(url)) { if (!isUrlAllowed(url)) {
return return
} }
@@ -81,6 +85,7 @@ object ImagePreview : FirmamentFeature {
} }
} }
@OptIn(ExperimentalCoroutinesApi::class)
override fun onLoad() { override fun onLoad() {
ClientChatLineReceivedEvent.subscribe { ClientChatLineReceivedEvent.subscribe {
it.replaceWith = it.text.transformEachRecursively { child -> it.replaceWith = it.text.transformEachRecursively { child ->
@@ -122,16 +127,10 @@ object ImagePreview : FirmamentFeature {
val imageFuture = imageCache[url] ?: return@subscribe val imageFuture = imageCache[url] ?: return@subscribe
if (!imageFuture.isCompleted) return@subscribe if (!imageFuture.isCompleted) return@subscribe
val image = imageFuture.getCompleted() ?: 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() 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.matrices.scale(scale, scale, 1F)
it.drawContext.drawTexture( it.drawContext.drawTexture(
image.texture, 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.WLabel
import io.github.cottonmc.cotton.gui.widget.WWidget import io.github.cottonmc.cotton.gui.widget.WWidget
import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment import io.github.cottonmc.cotton.gui.widget.data.VerticalAlignment
import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text import net.minecraft.text.Text
class GuiAppender(val width: Int) { class GuiAppender(val width: Int, val screenAccessor: () -> Screen) {
private var row = 0 private var row = 0
internal val panel = WGridPanel().also { it.setGaps(4, 4) } internal val panel = WGridPanel().also { it.setGaps(4, 4) }
internal val reloadables = mutableListOf<(() -> Unit)>() internal val reloadables = mutableListOf<(() -> Unit)>()
fun set(x: Int, y: Int, w: Int, h: Int, widget: WWidget) { 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.CottonClientScreen
import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription import io.github.cottonmc.cotton.gui.client.LightweightGuiDescription
import io.github.cottonmc.cotton.gui.widget.data.Insets import io.github.cottonmc.cotton.gui.widget.data.Insets
import moe.nea.jarvis.api.Point
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonElement 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( protected fun integer(
propertyName: String, propertyName: String,
min: Int, min: Int,
@@ -175,18 +189,26 @@ abstract class ManagedConfig(val name: String) {
fun getConfigEditor(parent: Screen? = null): CottonClientScreen { fun getConfigEditor(parent: Screen? = null): CottonClientScreen {
val lwgd = LightweightGuiDescription() val lwgd = LightweightGuiDescription()
val guiapp = GuiAppender(20) var screen: Screen? = null
val guiapp = GuiAppender(20, { requireNotNull(screen) { "Screen Accessor called too early" } })
latestGuiAppender = guiapp latestGuiAppender = guiapp
guiapp.panel.insets = Insets.ROOT_PANEL guiapp.panel.insets = Insets.ROOT_PANEL
sortedOptions.forEach { it.appendToGui(guiapp) } sortedOptions.forEach { it.appendToGui(guiapp) }
guiapp.reloadables.forEach { it() } guiapp.reloadables.forEach { it() }
lwgd.setRootPanel(guiapp.panel) lwgd.setRootPanel(guiapp.panel)
return object : CottonClientScreen(lwgd) { screen =
override fun close() { object : CottonClientScreen(lwgd) {
latestGuiAppender = null override fun init() {
MC.screen = parent latestGuiAppender = guiapp
super.init()
}
override fun close() {
latestGuiAppender = null
MC.screen = parent
}
} }
} return screen
} }
fun showConfigEditor(parent: Screen? = null) { fun showConfigEditor(parent: Screen? = null) {

View File

@@ -1,21 +1,42 @@
package moe.nea.firmament.jarvis package moe.nea.firmament.jarvis
import moe.nea.jarvis.api.Jarvis
import moe.nea.jarvis.api.JarvisConfigOption import moe.nea.jarvis.api.JarvisConfigOption
import moe.nea.jarvis.api.JarvisHud
import moe.nea.jarvis.api.JarvisPlugin import moe.nea.jarvis.api.JarvisPlugin
import net.minecraft.client.gui.screen.Screen import net.minecraft.client.gui.screen.Screen
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.Firmament import moe.nea.firmament.Firmament
import moe.nea.firmament.features.FeatureManager 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 import moe.nea.firmament.repo.RepoManager
class JarvisIntegration : JarvisPlugin { class JarvisIntegration : JarvisPlugin {
override fun getModId(): String = override fun getModId(): String =
Firmament.MOD_ID Firmament.MOD_ID
override fun getAllConfigOptions(): List<JarvisConfigOption> { companion object {
val configs = listOf( lateinit var jarvis: Jarvis
}
override fun onInitialize(jarvis: Jarvis) {
Companion.jarvis = jarvis
}
val configs
get() = listOf(
RepoManager.Config RepoManager.Config
) + FeatureManager.allFeatures.mapNotNull { it.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 -> return configs.flatMap { config ->
config.sortedOptions.map { config.sortedOptions.map {
object : JarvisConfigOption { 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.enabled": "Enable Image Preview",
"firmament.config.image-preview.allow-all-hosts": "Allow all Image Hosts", "firmament.config.image-preview.allow-all-hosts": "Allow all Image Hosts",
"firmament.config.image-preview.allowed-hosts": "Allowed 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"
} }