Make image preview scalable
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/main/kotlin/moe/nea/firmament/gui/config/JAnyHud.kt
Normal file
46
src/main/kotlin/moe/nea/firmament/gui/config/JAnyHud.kt
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
7
src/main/kotlin/moe/nea/firmament/util/propertyutil.kt
Normal file
7
src/main/kotlin/moe/nea/firmament/util/propertyutil.kt
Normal 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)) }
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user