From 9d326f9c6bcbb62b120a33930a23f223667cd040 Mon Sep 17 00:00:00 2001 From: Thunderblade73 <85900443+Thunderblade73@users.noreply.github.com> Date: Sat, 7 Sep 2024 16:14:23 +0200 Subject: Backend: Renderable searchbox (#2475) --- .../at/hannibal2/skyhanni/data/model/TextInput.kt | 41 +++++++- .../mixins/transformers/gui/MixinGuiScreen.java | 6 ++ .../skyhanni/utils/renderables/Renderable.kt | 106 +++++++++++++++++++-- .../skyhanni/utils/renderables/RenderableUtils.kt | 12 +++ 4 files changed, 156 insertions(+), 9 deletions(-) diff --git a/src/main/java/at/hannibal2/skyhanni/data/model/TextInput.kt b/src/main/java/at/hannibal2/skyhanni/data/model/TextInput.kt index d7e6ff8e1..6ae2342b6 100644 --- a/src/main/java/at/hannibal2/skyhanni/data/model/TextInput.kt +++ b/src/main/java/at/hannibal2/skyhanni/data/model/TextInput.kt @@ -8,6 +8,7 @@ import at.hannibal2.skyhanni.utils.StringUtils.insert import kotlinx.coroutines.runBlocking import net.minecraft.client.settings.KeyBinding import org.lwjgl.input.Keyboard +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable class TextInput { @@ -22,16 +23,35 @@ class TextInput { } }.replace("(? Unit>() + + fun registerToEvent(key: Int, event: (TextInput) -> Unit) { + updateEvents[key] = event + } + + fun removeFromEvent(key: Int) { + updateEvents.remove(key) + } + companion object { private var activeInstance: TextInput? = null @@ -51,6 +71,13 @@ class TextInput { } } + fun onGuiInput(ci: CallbackInfo) { + if (activeInstance != null) { + ci.cancel() + return + } + } + private var timeSinceKeyEvent = 0L private var carriage @@ -65,6 +92,13 @@ class TextInput { activeInstance?.textBox = value } + private fun updated() { + with(activeInstance) { + if (this == null) return + this.updateEvents.forEach { (_, it) -> it(this) } + } + } + private fun handleTextInput() { if (KeyboardManager.isCopyingKeysDown()) { OSUtils.copyToClipboard(textBox) @@ -73,6 +107,7 @@ class TextInput { if (KeyboardManager.isPastingKeysDown()) { runBlocking { textBox = OSUtils.readFromClipboard() ?: return@runBlocking + updated() } return } @@ -96,6 +131,7 @@ class TextInput { } else { textBox.dropLast(1) } + updated() return } @@ -122,6 +158,7 @@ class TextInput { textBox + char } } + updated() } private fun moveCarriageRight(carriage: Int) = carriage + 1 diff --git a/src/main/java/at/hannibal2/skyhanni/mixins/transformers/gui/MixinGuiScreen.java b/src/main/java/at/hannibal2/skyhanni/mixins/transformers/gui/MixinGuiScreen.java index b14d225cf..b3338acf5 100644 --- a/src/main/java/at/hannibal2/skyhanni/mixins/transformers/gui/MixinGuiScreen.java +++ b/src/main/java/at/hannibal2/skyhanni/mixins/transformers/gui/MixinGuiScreen.java @@ -1,6 +1,7 @@ package at.hannibal2.skyhanni.mixins.transformers.gui; import at.hannibal2.skyhanni.data.ToolTipData; +import at.hannibal2.skyhanni.data.model.TextInput; import at.hannibal2.skyhanni.mixins.hooks.GuiScreenHookKt; import net.minecraft.client.gui.GuiScreen; import net.minecraft.item.ItemStack; @@ -27,4 +28,9 @@ public class MixinGuiScreen { ci.cancel(); } } + + @Inject(method = "handleKeyboardInput", at = @At("HEAD"), cancellable = true) + public void handleKeyboardInput(CallbackInfo ci) { + TextInput.Companion.onGuiInput(ci); + } } diff --git a/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt b/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt index 29105459c..a49f58b00 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/renderables/Renderable.kt @@ -5,6 +5,7 @@ import at.hannibal2.skyhanni.config.features.skillprogress.SkillProgressBarConfi import at.hannibal2.skyhanni.data.GuiData import at.hannibal2.skyhanni.data.HighlightOnHoverSlot import at.hannibal2.skyhanni.data.ToolTipData +import at.hannibal2.skyhanni.data.model.TextInput import at.hannibal2.skyhanni.features.chroma.ChromaShaderManager import at.hannibal2.skyhanni.features.chroma.ChromaType import at.hannibal2.skyhanni.features.misc.DarkenShader @@ -52,7 +53,11 @@ interface Renderable { val horizontalAlign: HorizontalAlignment val verticalAlign: VerticalAlignment fun isHovered(posX: Int, posY: Int) = currentRenderPassMousePosition?.let { (x, y) -> - x in (posX..posX + width) && y in (posY..posY + height) // TODO: adjust for variable height? + x in (posX..posX + width) && y in (posY..posY + height) + } ?: false + + fun isBoxHovered(posX: Int, width: Int, posY: Int, height: Int) = currentRenderPassMousePosition?.let { (x, y) -> + x in (posX..posX + width) && y in (posY..posY + height) } ?: false /** @@ -435,12 +440,7 @@ interface Renderable { val inverseScale = 1 / scale override fun render(posX: Int, posY: Int) { - val fontRenderer = Minecraft.getMinecraft().fontRendererObj - GlStateManager.translate(1.0, 1.0, 0.0) - GlStateManager.scale(scale, scale, 1.0) - fontRenderer.drawStringWithShadow(text, 0f, 0f, color.rgb) - GlStateManager.scale(inverseScale, inverseScale, 1.0) - GlStateManager.translate(-1.0, -1.0, 0.0) + RenderableUtils.renderString(text, scale, color, inverseScale) } } @@ -534,6 +534,98 @@ interface Renderable { } } + /** + * @param searchPrefix text that is static in front of the textbox + * @param onUpdateSize function that is called if the size changes (since the search text can get bigger than [content]) + * @param textInput The text input, can be external or internal + * @param shouldRenderTopElseBottom true == Renders on top, false == Renders at the Bottom + * @param hideIfNoText hides text box if no input is given + * @param ySpacing space between the search and [content] + * @param onHover is triggered if [content] or the text box is hovered + * @param bypassChecks bypass the [shouldAllowLink] logic + * @param condition condition to being able to input / [onHover] to trigger + * @param scale text scale of the textbox + * @param color color of the textbox + * @param key event key for the [textInput] to register the event, needs clearing if [textInput] is external, default = 0 + */ + fun searchBox( + content: Renderable, + searchPrefix: String, + onUpdateSize: (Renderable) -> Unit, + textInput: TextInput = TextInput(), + shouldRenderTopElseBottom: Boolean = true, + hideIfNoText: Boolean = true, + ySpacing: Int = 0, + onHover: (TextInput) -> Unit = {}, + bypassChecks: Boolean = false, + condition: () -> Boolean = { true }, + scale: Double = 1.0, + color: Color = Color.WHITE, + key: Int = 0, + ) = object : Renderable { + + val textBoxHeight = (9 * scale).toInt() + 1 + + override var width: Int = content.width + override val height: Int = content.height + ySpacing + textBoxHeight + override val horizontalAlign = content.horizontalAlign + override val verticalAlign = content.verticalAlign + + val searchWidth get() = (Minecraft.getMinecraft().fontRendererObj.getStringWidth(searchPrefix + textInput.editTextWithAlwaysCarriage()) * scale).toInt() + 1 + + init { + textInput.registerToEvent(key) { + val searchWidth = searchWidth + if (searchWidth > width) { + width = searchWidth + onUpdateSize(this) + } else { + if (width > content.width) { + width = maxOf(content.width, searchWidth) + onUpdateSize(this) + } + } + } + } + + override fun render(posX: Int, posY: Int) { + if (shouldRenderTopElseBottom) { + if (!(hideIfNoText && textInput.textBox.isEmpty())) { + RenderableUtils.renderString(searchPrefix + textInput.editText(), scale, color) + } + GlStateManager.translate(0f, (ySpacing + textBoxHeight).toFloat(), 0f) + } + if (isHovered(posX, posY) && condition() && shouldAllowLink(true, bypassChecks)) { + onHover(textInput) + textInput.makeActive() + textInput.handle() + val yOff: Int + if (shouldRenderTopElseBottom) { + yOff = 0 + } else { + yOff = content.height + ySpacing + } + if (isBoxHovered(posX, width, posY + yOff, textBoxHeight) && (-99).isKeyClicked()) { + textInput.clear() + } + } else { + textInput.disable() + } + if (!shouldRenderTopElseBottom) { + content.render(posX, posY) + GlStateManager.translate(0f, (ySpacing).toFloat(), 0f) + if (!(hideIfNoText && textInput.textBox.isEmpty())) { + RenderableUtils.renderString(searchPrefix + textInput.editText(), scale, color) + } + GlStateManager.translate(0f, -(ySpacing).toFloat(), 0f) + } else { + content.render(posX, posY + textBoxHeight + ySpacing) + GlStateManager.translate(0f, -(ySpacing + textBoxHeight).toFloat(), 0f) + } + } + + } + fun progressBar( percent: Double, startColor: Color = Color(255, 0, 0), diff --git a/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderableUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderableUtils.kt index a78044d72..4a8f28c8e 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderableUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/renderables/RenderableUtils.kt @@ -2,7 +2,9 @@ package at.hannibal2.skyhanni.utils.renderables import at.hannibal2.skyhanni.utils.RenderUtils.HorizontalAlignment import at.hannibal2.skyhanni.utils.RenderUtils.VerticalAlignment +import net.minecraft.client.Minecraft import net.minecraft.client.renderer.GlStateManager +import java.awt.Color internal object RenderableUtils { @@ -72,6 +74,16 @@ internal object RenderableUtils { GlStateManager.translate(0f, -yOffset.toFloat(), 0f) return yOffset } + + @Suppress("NOTHING_TO_INLINE") + inline fun renderString(text: String, scale: Double = 1.0, color: Color = Color.WHITE, inverseScale: Double = 1 / scale) { + val fontRenderer = Minecraft.getMinecraft().fontRendererObj + GlStateManager.translate(1.0, 1.0, 0.0) + GlStateManager.scale(scale, scale, 1.0) + fontRenderer.drawStringWithShadow(text, 0f, 0f, color.rgb) + GlStateManager.scale(inverseScale, inverseScale, 1.0) + GlStateManager.translate(-1.0, -1.0, 0.0) + } } internal abstract class RenderableWrapper internal constructor(protected val content: Renderable) : Renderable { -- cgit