diff --git a/build.gradle b/build.gradle index 9be33d7..aee53fe 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,19 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1' implementation "meteordevelopment:orbit:0.2.3" include "meteordevelopment:orbit:0.2.3" + + implementation "io.github.humbleui:skija-shared:0.143.11" + include "io.github.humbleui:skija-shared:0.143.11" + implementation "io.github.humbleui:skija-windows-x64:0.143.11" + include "io.github.humbleui:skija-windows-x64:0.143.11" + implementation "io.github.humbleui:skija-linux-x64:0.143.11" + include "io.github.humbleui:skija-linux-x64:0.143.11" + implementation "io.github.humbleui:skija-linux-arm64:0.143.11" + include "io.github.humbleui:skija-linux-arm64:0.143.11" + implementation "io.github.humbleui:skija-macos-x64:0.143.11" + include "io.github.humbleui:skija-macos-x64:0.143.11" + implementation "io.github.humbleui:skija-macos-arm64:0.143.11" + include "io.github.humbleui:skija-macos-arm64:0.143.11" } processResources { diff --git a/gradle.properties b/gradle.properties index 8642bee..6bfd6a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ loom_version=1.13-SNAPSHOT fabric_kotlin_version=1.13.7+kotlin.2.2.21 # Mod Properties -mod_version=1.2.1 +mod_version=1.2.2 maven_group=dev.oblongboot.sxp archives_base_name=slayerxpoverlay diff --git a/src/main/java/dev/oblongboot/sxp/mixin/HotbarMixin.java b/src/main/java/dev/oblongboot/sxp/mixin/HotbarMixin.java new file mode 100644 index 0000000..5dc77ea --- /dev/null +++ b/src/main/java/dev/oblongboot/sxp/mixin/HotbarMixin.java @@ -0,0 +1,28 @@ +package dev.oblongboot.sxp.mixin; + +import dev.oblongboot.sxp.ui.SettingsScreen; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiGraphics; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Gui.class) +public class HotbarMixin { + @Inject(method = "renderItemHotbar", at = @At("HEAD"), cancellable = true) + private void injectRenderHotbar(GuiGraphics guiGraphics, DeltaTracker deltaTracker, CallbackInfo ci) { + if (Minecraft.getInstance().screen instanceof SettingsScreen) { + ci.cancel(); + } + } + + @Inject(method = "renderCrosshair", at = @At("HEAD"), cancellable = true) + private void injectRenderCrosshair(GuiGraphics g, DeltaTracker d, CallbackInfo ci) { + if (Minecraft.getInstance().screen instanceof SettingsScreen) { + ci.cancel(); + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/oblongboot/sxp/mixin/MinecraftMixin.java b/src/main/java/dev/oblongboot/sxp/mixin/MinecraftMixin.java new file mode 100644 index 0000000..a88b989 --- /dev/null +++ b/src/main/java/dev/oblongboot/sxp/mixin/MinecraftMixin.java @@ -0,0 +1,28 @@ +package dev.oblongboot.sxp.mixin; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.main.GameConfig; +import dev.oblongboot.sxp.utils.skia.SkiaContext; +import org.lwjgl.glfw.GLFW; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + + @Inject(method = "", at = @At("TAIL")) + private void registerSkia(GameConfig gameConfig, CallbackInfo ci) { + int[] width = new int[1]; + int[] height = new int[1]; + + long windowHandle = Minecraft.getInstance().getWindow().handle(); + GLFW.glfwGetFramebufferSize(windowHandle, width, height); + + int finalWidth = Math.max(width[0], 1); + int finalHeight = Math.max(height[0], 1); + + SkiaContext.INSTANCE.initSkia(finalWidth, finalHeight); + } +} diff --git a/src/main/java/dev/oblongboot/sxp/mixin/WindowMixin.java b/src/main/java/dev/oblongboot/sxp/mixin/WindowMixin.java new file mode 100644 index 0000000..e664594 --- /dev/null +++ b/src/main/java/dev/oblongboot/sxp/mixin/WindowMixin.java @@ -0,0 +1,27 @@ +package dev.oblongboot.sxp.mixin; + +import com.mojang.blaze3d.platform.Window; +import dev.oblongboot.sxp.utils.skia.SkiaContext; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import com.mojang.blaze3d.TracyFrameCapture; + +@Mixin(Window.class) +public class WindowMixin { + + @Inject(method = "onFramebufferResize", at = @At("RETURN")) + private void onFramebufferResize(long window, int width, int height, CallbackInfo ci) { + int finalWidth = Math.max(width, 1); + int finalHeight = Math.max(height, 1); + System.out.println("Window resized to " + finalWidth + "x" + finalHeight); + + SkiaContext.INSTANCE.initSkia(finalWidth, finalHeight); + } + + @Inject(method = "updateDisplay", at = @At("HEAD")) + private void onUpdateDisplay(TracyFrameCapture capturer, CallbackInfo ci) { + SkiaContext.INSTANCE.draw(); + } +} diff --git a/src/main/kotlin/dev/oblongboot/sxp/Slayerxpoverlay.kt b/src/main/kotlin/dev/oblongboot/sxp/Slayerxpoverlay.kt index 8f3e423..545e3a1 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/Slayerxpoverlay.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/Slayerxpoverlay.kt @@ -68,12 +68,12 @@ object Slayerxpoverlay : ModInitializer { try { if (!shouldCheck) return@launch shouldCheck = false - val updateAvailable = dev.oblongboot.sxp.utils.UpdateChecker.isUpdateAvailable("1.2.1") + val updateAvailable = dev.oblongboot.sxp.utils.UpdateChecker.isUpdateAvailable("1.2.2") if (updateAvailable) { Minecraft.getInstance().execute { modMessage( "A new version of SlayerXPOverlayFabric is available! " + - "You are running version v1.2.1. " + + "You are running version v1.2.2. " + "Please check the GitHub page for the latest version." ) } diff --git a/src/main/kotlin/dev/oblongboot/sxp/core/ButtonSetting.kt b/src/main/kotlin/dev/oblongboot/sxp/core/ButtonSetting.kt index 93eca07..60a6791 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/core/ButtonSetting.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/core/ButtonSetting.kt @@ -9,19 +9,36 @@ class ButtonSetting( description: String = "", private val onClickAction: (() -> Unit)? = null ) : Setting(name, description, false) { + override var x = 0 + override var y = 0 override val width = 240 override val height = 30 - override fun render(ctx: GuiGraphics) { - val isHovered = isWithinBounds2(Render2D.Mouse.x.toInt(), Render2D.Mouse.y.toInt()) - val baseColor = Color(50, 90, 150, 180) - val hoverColor = if (isHovered) baseColor.brighter() else baseColor - Render2D.drawWhateverTheFuckThisIs(ctx, x, y, width, height, 6, hoverColor) - Render2D.drawOutline(ctx, x, y, width, height, Color(0, 180, 255)) - val textY = y + (height - Render2D.textRenderer.lineHeight) / 2 - val textX = x + (width - Render2D.textRenderer.width(name)) / 2 + override fun render(mouseX: Int, mouseY: Int) { + val skija = dev.oblongboot.sxp.utils.skia.SkijaRenderer + val isHovered = isWithinBounds2(mouseX, mouseY) + val baseColor = if (isHovered) skija.argb(160, 40, 80, 140) else skija.argb(100, 20, 40, 70) + + skija.drawRoundedRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, baseColor) + + if (isHovered) { + skija.drawRoundedGlow(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, skija.argb(60, 0, 120, 255), 10f, 1f) + } + + val borderColor = if (isHovered) skija.argb(200, 0, 150, 255) else skija.argb(100, 0, 100, 200) + skija.drawRoundedRectBorderGradient( + x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, 1f, + borderColor, borderColor, + dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT + ) + + val font = dev.oblongboot.sxp.ui.SettingsScreen.elementFont + val textWidth = skija.getTextWidth(name, font) + val textY = y + height / 3.8f// - 5f + val textX = x + (width - textWidth) / 2f - Render2D.drawString(ctx, name, textX, textY, 1f, true) + val textColor = if (isHovered) skija.argb(255, 255, 255, 255) else skija.argb(255, 220, 230, 255) + skija.drawText(name, textX, textY, textColor, font) } override fun onClick(mouseX: Int, mouseY: Int): Boolean { @@ -44,7 +61,7 @@ class ButtonSetting( value = default } - override fun onValueChanged(oldValue: Boolean, newValue: Boolean) {} // why am i even overrideing this + override fun onValueChanged(oldValue: Boolean, newValue: Boolean) {} private fun isWithinBounds2(mouseX: Int, mouseY: Int): Boolean { return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height diff --git a/src/main/kotlin/dev/oblongboot/sxp/core/CheckboxSetting.kt b/src/main/kotlin/dev/oblongboot/sxp/core/CheckboxSetting.kt index 54b704a..21007e1 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/core/CheckboxSetting.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/core/CheckboxSetting.kt @@ -11,6 +11,8 @@ class CheckboxSetting( defaultSelected: Set = emptySet(), description: String = "" ) : Setting>(name, description, defaultSelected.toMutableSet()) { + override var x = 0 + override var y = 0 override val width = 240 override val height = 30 private val optionHeight = 22 @@ -20,80 +22,92 @@ class CheckboxSetting( private val animSpeed = 0.25f private var expanded = false - override fun render(ctx: GuiGraphics) { - val baseColor = Color(50, 60, 90, 180) - val isHovered = isWithinBounds(Render2D.Mouse.x.toInt(), Render2D.Mouse.y.toInt()) - val hoverColor = if (isHovered) baseColor.brighter() else baseColor + override fun render(mouseX: Int, mouseY: Int) { + val skija = dev.oblongboot.sxp.utils.skia.SkijaRenderer + val isHovered = isWithinBounds(mouseX, mouseY) + val baseColor = if (isHovered) skija.argb(160, 40, 80, 140) else skija.argb(100, 20, 40, 70) val visibleOptionCount = options.size.coerceAtMost(maxVisibleOptions) val targetHeight = if (expanded) visibleOptionCount * optionHeight else 0 animHeight += (targetHeight - animHeight) * animSpeed - Render2D.drawWhateverTheFuckThisIs(ctx, x, y, width, height, 6, hoverColor) - Render2D.drawOutline(ctx, x, y, width, height, Color(0, 180, 255)) - val textY = y + (height - Render2D.textRenderer.lineHeight) / 2 - Render2D.drawString(ctx, name, x + 10, textY, 1f, true) + + skija.drawRoundedRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, baseColor) + + if (isHovered) { + skija.drawRoundedGlow(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, skija.argb(60, 0, 120, 255), 10f, 1f) + } + + val borderColor = if (expanded || isHovered) skija.argb(255, 0, 150, 255) else skija.argb(150, 0, 100, 200) + skija.drawRoundedRectBorderGradient( + x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, 1f, + borderColor, borderColor, + dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT + ) + + val font = dev.oblongboot.sxp.ui.SettingsScreen.elementFont + val textY = y + height / 3.8f// - 5f + skija.drawText(name, x + 10f, textY, skija.argb(255, 255, 255, 255), font) val selectedSummary = if (value.isNotEmpty()) "${value.size} selected" else "NONE" - Render2D.drawString(ctx, selectedSummary, x + width - 100, textY, 1f, true) + val tw = skija.getTextWidth(selectedSummary, font) + skija.drawText(selectedSummary, x + width - 35f - tw, textY, skija.argb(255, 180, 200, 230), font) val arrowSymbol = if (expanded) "▲" else "▼" - Render2D.drawString(ctx, arrowSymbol, x + width - 15, textY, 1f, true) + val aw = skija.getTextWidth(arrowSymbol, font) + skija.drawText(arrowSymbol, x + width - 15f - aw/2f, textY, skija.argb(255, 255, 255, 255), font) if (animHeight > 0.5f) { val actualVisibleOptions = if (expanded) visibleOptionCount else 0 val maxScroll = (options.size - maxVisibleOptions).coerceAtLeast(0) scrollOffset = scrollOffset.coerceIn(0, maxScroll) + + val totalAnimHeight = animHeight + if (totalAnimHeight > 2f) { + skija.drawRoundedRect(x.toFloat(), (y + height).toFloat(), width.toFloat(), totalAnimHeight, 4f, skija.argb(220, 15, 25, 40)) + skija.drawRoundedRectBorderGradient(x.toFloat(), (y + height).toFloat(), width.toFloat(), totalAnimHeight, 4f, 1f, skija.argb(100, 0, 100, 200), skija.argb(100, 0, 100, 200), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT) + } for (i in 0 until actualVisibleOptions) { val optionIndex = i + scrollOffset if (optionIndex >= options.size) break val optionY = y + height + (i * optionHeight) - val optionHovered = isWithinBounds( - Render2D.Mouse.x.toInt(), - Render2D.Mouse.y.toInt(), - x, - optionY, - width, - optionHeight - ) + val optionHovered = isWithinBounds(mouseX, mouseY, x, optionY, width, optionHeight) val isSelected = value.contains(optionIndex) - val optionColor = when { - isSelected -> Color(0, 120, 80, 220) - optionHovered -> Color(70, 80, 100, 200) - else -> Color(50, 60, 80, 180) + if (optionHovered) { + skija.drawRoundedRect(x.toFloat(), optionY.toFloat(), width.toFloat(), optionHeight.toFloat(), 4f, skija.argb(80, 40, 80, 140)) } - Render2D.drawWhateverTheFuckThisIs(ctx, x, optionY, width, optionHeight, 3, optionColor) - Render2D.drawOutline(ctx, x, optionY, width, optionHeight, Color(0, 130, 200)) - - - val boxX = x + 8 - val boxSize = 14 - val boxColor = if (isSelected) Color(0, 180, 80, 220) else Color(60, 60, 70, 180) - Render2D.drawWhateverTheFuckThisIs(ctx, boxX, optionY + 4, boxSize, boxSize, 3, boxColor) - Render2D.drawOutline(ctx, boxX, optionY + 4, boxSize, boxSize, Color.WHITE) - if (isSelected) Render2D.drawString(ctx, "✔", boxX + 3, optionY + 4, 1f, true) - + val boxX = x + 8f + val boxSize = 14f + val boxY = optionY + (optionHeight - boxSize) / 2f + + if (isSelected) { + skija.drawRoundedRectGradient(boxX, boxY, boxSize, boxSize, 2f, skija.argb(200, 0, 150, 255), skija.argb(200, 0, 100, 200)) + } else { + skija.drawRoundedRect(boxX, boxY, boxSize, boxSize, 2f, skija.argb(100, 30, 40, 60)) + skija.drawRoundedRectBorderGradient(boxX, boxY, boxSize, boxSize, 2f, 1f, skija.argb(150, 80, 100, 120), skija.argb(150, 60, 80, 100), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT) + } - val textOffsetX = boxX + boxSize + 6 - val textOffsetY = optionY + (optionHeight - Render2D.textRenderer.lineHeight) / 2 - Render2D.drawString(ctx, options[optionIndex], textOffsetX, textOffsetY, 1f, true) + val textOffsetX = boxX + boxSize + 6f + val textOffsetY = optionY + optionHeight / 2f - 1f + val textColor = if (isSelected) skija.argb(255, 255, 255, 255) else skija.argb(220, 200, 200, 200) + skija.drawText(options[optionIndex], textOffsetX, textOffsetY, textColor, font) } - if (options.size > maxVisibleOptions) { val indicatorY = y + height + (actualVisibleOptions * optionHeight) + val smallFont = dev.oblongboot.sxp.ui.SettingsScreen.smallFont if (scrollOffset > 0) { - Render2D.drawString(ctx, "↑", x + width - 20, y + height + 5, 1f, true) + skija.drawText("↑", x + width - 20f, y + height + 10f, skija.argb(200, 255, 255, 255), font) } if (scrollOffset < maxScroll) { - Render2D.drawString(ctx, "↓", x + width - 20, indicatorY - 15, 1f, true) + skija.drawText("↓", x + width - 20f, indicatorY - 5f, skija.argb(200, 255, 255, 255), font) } val positionText = "${scrollOffset + 1}-${(scrollOffset + actualVisibleOptions).coerceAtMost(options.size)} of ${options.size}" - Render2D.drawString(ctx, positionText, x + 5, indicatorY + 2, 0.8f, true) + skija.drawText(positionText, x + 5f, indicatorY + 2f, skija.argb(150, 200, 200, 200), smallFont) } } } diff --git a/src/main/kotlin/dev/oblongboot/sxp/core/ColorboxSetting.kt b/src/main/kotlin/dev/oblongboot/sxp/core/ColorboxSetting.kt index 18e374b..14548a9 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/core/ColorboxSetting.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/core/ColorboxSetting.kt @@ -39,16 +39,17 @@ class ColorboxSetting( updateHSVFromColor(value) } - override fun render(ctx: GuiGraphics) { - val baseColor = Color(50, 60, 90, 180) - val isHovered = isWithinBounds(Render2D.Mouse.x.toInt(), Render2D.Mouse.y.toInt()) - val hoverColor = if (isHovered) baseColor.brighter() else baseColor + override fun render(mouseX: Int, mouseY: Int) { + val skija = dev.oblongboot.sxp.utils.skia.SkijaRenderer + val baseColor = skija.argb(100, 20, 40, 70) + val isHovered = isWithinBounds(mouseX, mouseY) + val hoverColor = if (isHovered) skija.argb(160, 40, 80, 140) else baseColor val targetHeight = if (expanded) pickerHeight + 60 else 0 animHeight += (targetHeight - animHeight) * animSpeed - val mouseX = Render2D.Mouse.x.toInt() - val mouseY = Render2D.Mouse.y.toInt() + val mouseXFloat = mouseX.toFloat() + val mouseYFloat = mouseY.toFloat() val mouseDown = Render2D.Mouse.isDown(0) val pickerX = x + 10 @@ -69,87 +70,107 @@ class ColorboxSetting( draggingAlpha = false } aaa = mouseDown + + skija.drawRoundedRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, hoverColor) - Render2D.drawWhateverTheFuckThisIs(ctx, x, y, width, height, 6, hoverColor) - Render2D.drawOutline(ctx, x, y, width, height, Color(0, 180, 255)) - val textY = y + (height - Render2D.textRenderer.lineHeight) / 2 - Render2D.drawString(ctx, name, x + 10, textY, 1f, true) - - val previewX = x + width - previewSize - 10 - val previewY = y + (height - previewSize) / 2 - Render2D.drawWhateverTheFuckThisIs(ctx, previewX, previewY, previewSize, previewSize, 3, value) - Render2D.drawOutline(ctx, previewX, previewY, previewSize, previewSize, Color.WHITE) + if (isHovered) { + skija.drawRoundedGlow(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, skija.argb(60, 0, 120, 255), 10f, 1f) + } + + val borderColor = if (expanded || isHovered) skija.argb(255, 0, 150, 255) else skija.argb(150, 0, 100, 200) + skija.drawRoundedRectBorderGradient( + x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, 1f, + borderColor, borderColor, + dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT + ) + + val font = dev.oblongboot.sxp.ui.SettingsScreen.elementFont + val textY = y + height / 3.8f// - 5f + skija.drawText(name, x + 10f, textY, skija.argb(255, 255, 255, 255), font) + + val previewX = x + width - previewSize - 10f + val previewY = y + (height - previewSize) / 2f + val previewArgb = skija.argb(value.alpha, value.red, value.green, value.blue) + skija.drawRoundedRect(previewX, previewY, previewSize.toFloat(), previewSize.toFloat(), 3f, previewArgb) + skija.drawRoundedRectBorderGradient(previewX, previewY, previewSize.toFloat(), previewSize.toFloat(), 3f, 1f, skija.argb(150, 255, 255, 255), skija.argb(50, 255, 255, 255), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT) if (animHeight > 0.5f) { - Render2D.drawWhateverTheFuckThisIs(ctx, x, pickerY - 3, width, (animHeight + 6).toInt(), 6, Color(30, 40, 60, 220)) - drawSVPicker(ctx, pickerX, pickerY, svWidth, pickerHeight) - drawHueBar(ctx, hueX, pickerY, hueBarWidth, hueBarHeight) - drawAlphaSlider(ctx, pickerX, alphaY, pickerWidth, 15) - val finalPreviewX = pickerX + pickerWidth - 60 - val finalPreviewY = alphaY + 20 - Render2D.drawWhateverTheFuckThisIs(ctx, finalPreviewX, finalPreviewY, 50, 25, 3, value) - Render2D.drawOutline(ctx, finalPreviewX, finalPreviewY, 50, 25, Color.WHITE) + skija.drawRoundedRect(x.toFloat(), pickerY - 3f, width.toFloat(), animHeight + 6f, 4f, skija.argb(220, 15, 25, 40)) + skija.drawRoundedRectBorderGradient(x.toFloat(), pickerY - 3f, width.toFloat(), animHeight + 6f, 4f, 1f, skija.argb(100, 0, 100, 200), skija.argb(100, 0, 100, 200), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT) + + drawSVPicker(pickerX, pickerY, svWidth, pickerHeight) + drawHueBar(hueX, pickerY, hueBarWidth, hueBarHeight) + drawAlphaSlider(pickerX, alphaY, pickerWidth, 15) + + val finalPreviewX = pickerX + pickerWidth - 60f + val finalPreviewY = alphaY + 20f + skija.drawRoundedRect(finalPreviewX, finalPreviewY, 50f, 25f, 3f, previewArgb) + skija.drawRoundedRectBorderGradient(finalPreviewX, finalPreviewY, 50f, 25f, 3f, 1f, skija.argb(150, 255, 255, 255), skija.argb(50, 255, 255, 255), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT) + val hexColor = String.format("#%02X%02X%02X%02X", value.red, value.green, value.blue, value.alpha) - Render2D.drawString(ctx, hexColor, pickerX, finalPreviewY + 7, 0.9f, true) + val smallFont = dev.oblongboot.sxp.ui.SettingsScreen.smallFont + skija.drawText(hexColor, pickerX.toFloat(), finalPreviewY + 15f, skija.argb(255, 180, 200, 230), smallFont) } } - private fun drawSVPicker(ctx: GuiGraphics, px: Int, py: Int, w: Int, h: Int) { - val stripWidth = 4 - val stripHeight = 8 + private fun drawSVPicker(px: Int, py: Int, w: Int, h: Int) { + val skija = dev.oblongboot.sxp.utils.skia.SkijaRenderer + val stripWidth = 4f + val stripHeight = 8f - for (i in 0 until w step stripWidth) { - for (j in 0 until h step stripHeight) { + for (i in 0 until w step stripWidth.toInt()) { + for (j in 0 until h step stripHeight.toInt()) { val s = i.toFloat() / w val v = 1f - (j.toFloat() / h) val color = Color.getHSBColor(hue, s, v) - Render2D.drawWhateverTheFuckThisIs(ctx, px + i, py + j, stripWidth, stripHeight, 0, color) + skija.drawRoundedRect(px + i.toFloat(), py + j.toFloat(), stripWidth, stripHeight, 0f, skija.argb(255, color.red, color.green, color.blue)) } } - val cursorX = px + (saturation * w).toInt() - val cursorY = py + ((1f - brightness) * h).toInt() - Render2D.drawOutline(ctx, cursorX - 4, cursorY - 4, 8, 8, Color.WHITE) - Render2D.drawOutline(ctx, cursorX - 3, cursorY - 3, 6, 6, Color.BLACK) + val cursorX = px + (saturation * w).toFloat() + val cursorY = py + ((1f - brightness) * h).toFloat() + skija.drawRoundedRectBorderGradient(cursorX - 4f, cursorY - 4f, 8f, 8f, 4f, 1f, skija.argb(255, 255, 255, 255), skija.argb(255, 255, 255, 255), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.LEFT_TO_RIGHT) + skija.drawRoundedRectBorderGradient(cursorX - 3f, cursorY - 3f, 6f, 6f, 3f, 1f, skija.argb(255, 0, 0, 0), skija.argb(255, 0, 0, 0), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.LEFT_TO_RIGHT) } - private fun drawHueBar(ctx: GuiGraphics, hx: Int, hy: Int, w: Int, h: Int) { - val stripHeight = 4 - for (i in 0 until h step stripHeight) { + private fun drawHueBar(hx: Int, hy: Int, w: Int, h: Int) { + val skija = dev.oblongboot.sxp.utils.skia.SkijaRenderer + val stripHeight = 4f + for (i in 0 until h step stripHeight.toInt()) { val hueVal = i.toFloat() / h val color = Color.getHSBColor(hueVal, 1f, 1f) - Render2D.drawWhateverTheFuckThisIs(ctx, hx, hy + i, w, stripHeight, 0, color) + skija.drawRoundedRect(hx.toFloat(), hy + i.toFloat(), w.toFloat(), stripHeight, 0f, skija.argb(255, color.red, color.green, color.blue)) } - Render2D.drawOutline(ctx, hx, hy, w, h, Color.WHITE) + skija.drawRoundedRectBorderGradient(hx.toFloat(), hy.toFloat(), w.toFloat(), h.toFloat(), 0f, 1f, skija.argb(255, 255, 255, 255), skija.argb(255, 255, 255, 255), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.LEFT_TO_RIGHT) - val cursorY = hy + (hue * h).toInt() - Render2D.drawWhateverTheFuckThisIs(ctx, hx - 2, cursorY - 2, w + 4, 4, 0, Color.WHITE) + val cursorY = hy + (hue * h).toFloat() + skija.drawRoundedRect(hx - 2f, cursorY - 2f, w + 4f, 4f, 2f, skija.argb(255, 255, 255, 255)) } - private fun drawAlphaSlider(ctx: GuiGraphics, ax: Int, ay: Int, w: Int, h: Int) { - val checkSize = 8 - for (i in 0 until w step checkSize) { - for (j in 0 until h step checkSize) { - val isLight = ((i / checkSize) + (j / checkSize)) % 2 == 0 - val color = if (isLight) Color(200, 200, 200) else Color(100, 100, 100) - Render2D.drawWhateverTheFuckThisIs(ctx, ax + i, ay + j, checkSize, checkSize, 0, color) + private fun drawAlphaSlider(ax: Int, ay: Int, w: Int, h: Int) { + val skija = dev.oblongboot.sxp.utils.skia.SkijaRenderer + val checkSize = 8f + for (i in 0 until w step checkSize.toInt()) { + for (j in 0 until h step checkSize.toInt()) { + val isLight = ((i / checkSize.toInt()) + (j / checkSize.toInt())) % 2 == 0 + val color = if (isLight) skija.argb(255, 200, 200, 200) else skija.argb(255, 100, 100, 100) + skija.drawRoundedRect(ax + i.toFloat(), ay + j.toFloat(), checkSize, checkSize, 0f, color) } } val baseColor = Color(value.red, value.green, value.blue) - val stripWidth = 4 - for (i in 0 until w step stripWidth) { + val stripWidth = 4f + for (i in 0 until w step stripWidth.toInt()) { val a = (i.toFloat() / w * 255).toInt() - val color = Color(baseColor.red, baseColor.green, baseColor.blue, a) - Render2D.drawWhateverTheFuckThisIs(ctx, ax + i, ay, stripWidth, h, 0, color) + skija.drawRoundedRect(ax + i.toFloat(), ay.toFloat(), stripWidth, h.toFloat(), 0f, skija.argb(a, baseColor.red, baseColor.green, baseColor.blue)) } - Render2D.drawOutline(ctx, ax, ay, w, h, Color.WHITE) + skija.drawRoundedRectBorderGradient(ax.toFloat(), ay.toFloat(), w.toFloat(), h.toFloat(), 0f, 1f, skija.argb(255, 255, 255, 255), skija.argb(255, 255, 255, 255), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.LEFT_TO_RIGHT) - val cursorX = ax + (alpha.toFloat() / 255f * w).toInt() - Render2D.drawWhateverTheFuckThisIs(ctx, cursorX - 2, ay - 2, 4, h + 4, 0, Color.WHITE) + val cursorX = ax + (alpha.toFloat() / 255f * w).toFloat() + skija.drawRoundedRect(cursorX - 2f, ay - 2f, 4f, h + 4f, 2f, skija.argb(255, 255, 255, 255)) } override fun onClick(mouseX: Int, mouseY: Int): Boolean { diff --git a/src/main/kotlin/dev/oblongboot/sxp/core/DropdownSetting.kt b/src/main/kotlin/dev/oblongboot/sxp/core/DropdownSetting.kt index f87b547..521bf5d 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/core/DropdownSetting.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/core/DropdownSetting.kt @@ -20,77 +20,83 @@ class DropdownSetting( private var animHeight = 0f private val animSpeed = 0.25f - override fun render(ctx: GuiGraphics) { + override fun render(mouseX: Int, mouseY: Int) { val currentText = options.getOrNull(value) ?: "N/A" - val baseColor = Color(50, 60, 90, 180) - val isHovered = isWithinBounds(Render2D.Mouse.x.toInt(), Render2D.Mouse.y.toInt()) - val hoverColor = if (isHovered) baseColor.brighter() else baseColor + val skija = dev.oblongboot.sxp.utils.skia.SkijaRenderer + val isHovered = isWithinBounds(mouseX, mouseY) + val baseColor = if (isHovered) skija.argb(160, 40, 80, 140) else skija.argb(100, 20, 40, 70) val maxVisibleOptions = 3 val visibleOptionCount = options.size.coerceAtMost(maxVisibleOptions) val targetHeight = if (expanded) visibleOptionCount * optionHeight else 0 animHeight += (targetHeight - animHeight) * animSpeed - Render2D.drawWhateverTheFuckThisIs(ctx, x, y, width, height, 6, hoverColor) - Render2D.drawOutline(ctx, x, y, width, height, Color(0, 180, 255)) + skija.drawRoundedRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, baseColor) - val textY = y + (height - Render2D.textRenderer.lineHeight) / 2 - Render2D.drawString(ctx, name, x + 10, textY, 1f, true) - - val displayText = if (value >= 0 && value < options.size) { - options[value] - } else { - "DROPDOWN" + if (isHovered) { + skija.drawRoundedGlow(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, skija.argb(60, 0, 120, 255), 10f, 1f) } - Render2D.drawString(ctx, displayText, x + width - 80, textY, 1f, true) - val arrowX = x + width - 15 + + val borderWidth = if (expanded) 2f else 1f + val borderColor = if (expanded || isHovered) skija.argb(255, 0, 150, 255) else skija.argb(150, 0, 100, 200) + skija.drawRoundedRectBorderGradient( + x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, borderWidth, + borderColor, borderColor, + dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT + ) + + val font = dev.oblongboot.sxp.ui.SettingsScreen.elementFont + val textY = y + height / 3.8f// - 5f + skija.drawText(name, x + 10f, textY, skija.argb(255, 255, 255, 255), font) + + val displayText = if (value >= 0 && value < options.size) options[value] else "DROPDOWN" + val tw = skija.getTextWidth(displayText, font) + skija.drawText(displayText, x + width - 35f - tw, textY, skija.argb(255, 180, 200, 230), font) + val arrowSymbol = if (expanded) "▲" else "▼" - Render2D.drawString(ctx, arrowSymbol, arrowX, textY, 1f, true) + val aw = skija.getTextWidth(arrowSymbol, font) + skija.drawText(arrowSymbol, x + width - 15f - aw/2f, textY, skija.argb(255, 255, 255, 255), font) if (animHeight > 0.5f) { val actualVisibleOptions = if (expanded) maxVisibleOptions else 0 - val maxScroll = (options.size - maxVisibleOptions).coerceAtLeast(0) scrollOffset = scrollOffset.coerceIn(0, maxScroll) + val totalAnimHeight = animHeight + if (totalAnimHeight > 2f) { + skija.drawRoundedRect(x.toFloat(), (y + height).toFloat(), width.toFloat(), totalAnimHeight, 4f, skija.argb(220, 15, 25, 40)) + skija.drawRoundedRectBorderGradient(x.toFloat(), (y + height).toFloat(), width.toFloat(), totalAnimHeight, 4f, 1f, skija.argb(100, 0, 100, 200), skija.argb(100, 0, 100, 200), dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT) + } + for (i in 0 until actualVisibleOptions) { val optionIndex = i + scrollOffset if (optionIndex >= options.size) break val optionY = y + height + (i * optionHeight) - val optionHovered = isWithinBounds( - Render2D.Mouse.x.toInt(), - Render2D.Mouse.y.toInt(), - x, - optionY, - width, - optionHeight - ) + val optionHovered = isWithinBounds(mouseX, mouseY, x, optionY, width, optionHeight) - val optionColor = when { - optionIndex == value -> Color(0, 120, 200, 220) - optionHovered -> Color(60, 75, 110, 200) - else -> Color(40, 50, 75, 180) + if (optionIndex == value) { + skija.drawRoundedRect(x.toFloat(), optionY.toFloat(), width.toFloat(), optionHeight.toFloat(), 4f, skija.argb(150, 0, 120, 200)) + } else if (optionHovered) { + skija.drawRoundedRect(x.toFloat(), optionY.toFloat(), width.toFloat(), optionHeight.toFloat(), 4f, skija.argb(80, 40, 80, 140)) } - - Render2D.drawWhateverTheFuckThisIs(ctx, x, optionY, width, optionHeight, 3, optionColor) - Render2D.drawOutline(ctx, x, optionY, width, optionHeight, Color(0, 130, 200)) - val optionTextY = optionY + (optionHeight - Render2D.textRenderer.lineHeight) / 2 - Render2D.drawString(ctx, options[optionIndex], x + 10, optionTextY, 1f, true) + val optionTextY = optionY + optionHeight / 2f + 1f + val textColor = if (optionIndex == value) skija.argb(255, 255, 255, 255) else skija.argb(220, 200, 200, 200) + skija.drawText(options[optionIndex], x + 10f, optionTextY, textColor, font) } - if (options.size > maxVisibleOptions) { val indicatorY = y + height + (actualVisibleOptions * optionHeight) + val smallFont = dev.oblongboot.sxp.ui.SettingsScreen.smallFont if (scrollOffset > 0) { - Render2D.drawString(ctx, "↑", x + width - 20, y + height + 5, 1f, true) + skija.drawText("↑", x + width - 20f, y + height + 10f, skija.argb(200, 255, 255, 255), font) } if (scrollOffset < maxScroll) { - Render2D.drawString(ctx, "↓", x + width - 20, indicatorY - 15, 1f, true) + skija.drawText("↓", x + width - 20f, indicatorY - 5f, skija.argb(200, 255, 255, 255), font) } val positionText = "${scrollOffset + 1}-${(scrollOffset + actualVisibleOptions).coerceAtMost(options.size)} of ${options.size}" - Render2D.drawString(ctx, positionText, x + 5, indicatorY + 2, 0.8f, true) + skija.drawText(positionText, x + 5f, indicatorY + 2f, skija.argb(150, 200, 200, 200), smallFont) } } } diff --git a/src/main/kotlin/dev/oblongboot/sxp/core/Element.kt b/src/main/kotlin/dev/oblongboot/sxp/core/Element.kt index f45e7c2..3edd055 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/core/Element.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/core/Element.kt @@ -10,7 +10,7 @@ interface Element { val width: Int val height: Int - fun render(ctx: GuiGraphics) + fun render(mouseX: Int, mouseY: Int) fun onClick(mouseX: Int, mouseY: Int): Boolean fun onHover(mouseX: Int, mouseY: Int): Boolean fun isWithinBounds(mouseX: Int, mouseY: Int): Boolean { diff --git a/src/main/kotlin/dev/oblongboot/sxp/core/SwitchConfig.kt b/src/main/kotlin/dev/oblongboot/sxp/core/SwitchConfig.kt index 05de924..a0aa268 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/core/SwitchConfig.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/core/SwitchConfig.kt @@ -17,20 +17,40 @@ class SwitchConfig( private var isInitializing = false - override fun render(ctx: GuiGraphics) { + override fun render(mouseX: Int, mouseY: Int) { + val skija = dev.oblongboot.sxp.utils.skia.SkijaRenderer val toggleText = if (value) "ON" else "OFF" - val baseColor = if (value) Color(0, 120, 220, 200) else Color(50, 60, 90, 180) - val isHovered = isWithinBounds2(Render2D.Mouse.x.toInt(), Render2D.Mouse.y.toInt()) - val hoverColor = if (isHovered) baseColor.brighter() else baseColor + val isHovered = isWithinBounds2(mouseX, mouseY) + val baseColor = if (value) skija.argb(150, 0, 100, 200) else skija.argb(100, 20, 30, 50) + + if (value) { + skija.drawRoundedRectGradient(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, + skija.argb(160, 0, 120, 240), skija.argb(160, 0, 80, 180), + dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.LEFT_TO_RIGHT) + } else { + skija.drawRoundedRect(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, baseColor) + } + + if (isHovered) { + skija.drawRoundedGlow(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, skija.argb(50, 0, 150, 255), 10f, 1f) + } + + val alphaBorder = if (value) 200 else 80 + skija.drawRoundedRectBorderGradient( + x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), 4f, 1f, + skija.argb(alphaBorder, 0, 140, 255), skija.argb(alphaBorder, 0, 90, 200), + dev.oblongboot.sxp.utils.skia.SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT + ) - Render2D.drawWhateverTheFuckThisIs(ctx, x, y, width, height, 6, hoverColor) - Render2D.drawOutline(ctx, x, y, width, height, Color(0, 180, 255)) + val font = dev.oblongboot.sxp.ui.SettingsScreen.elementFont + val textY = y + height / 3.8f// - 5f - val textY = y + (height - Render2D.textRenderer.lineHeight) / 2 - Render2D.drawString(ctx, name, x + 10, textY, 1f, true) + val nameColor = if (value || isHovered) skija.argb(255, 255, 255, 255) else skija.argb(255, 200, 210, 225) + skija.drawText(name, x + 10f, textY, nameColor, font) - val toggleColor = if (value) Color(200, 240, 255).rgb else Color(170, 170, 190).rgb - Render2D.drawString(ctx, toggleText, x + width - 40, textY, 1f, true) + val toggleColor = if (value) skija.argb(255, 220, 240, 255) else skija.argb(255, 140, 150, 170) + val tw = skija.getTextWidth(toggleText, font) + skija.drawText(toggleText, x + width - tw - 10f, textY, toggleColor, font) } override fun onClick(mouseX: Int, mouseY: Int): Boolean { diff --git a/src/main/kotlin/dev/oblongboot/sxp/events/impl/SkiaDrawEvent.kt b/src/main/kotlin/dev/oblongboot/sxp/events/impl/SkiaDrawEvent.kt new file mode 100644 index 0000000..99cb244 --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/events/impl/SkiaDrawEvent.kt @@ -0,0 +1,12 @@ +package dev.oblongboot.sxp.events.impl + +import io.github.humbleui.skija.Canvas +import io.github.humbleui.skija.DirectContext +import dev.oblongboot.sxp.utils.skia.WrappedBackendRenderTarget + +class SkiaDrawEvent( + val context: DirectContext, + val renderTarget: WrappedBackendRenderTarget, + val surface: io.github.humbleui.skija.Surface, + val canvas: Canvas +) diff --git a/src/main/kotlin/dev/oblongboot/sxp/features/BossHighlightFeat.kt b/src/main/kotlin/dev/oblongboot/sxp/features/BossHighlightFeat.kt index deb961f..d1ccde7 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/features/BossHighlightFeat.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/features/BossHighlightFeat.kt @@ -2,18 +2,15 @@ package dev.oblongboot.sxp.features import dev.oblongboot.sxp.events.WorldRenderEvent import meteordevelopment.orbit.EventHandler +import dev.oblongboot.sxp.utils.ChatUtils +import dev.oblongboot.sxp.events.impl.SkiaDrawEvent +import dev.oblongboot.sxp.utils.skia.SkijaRenderer +import net.minecraft.client.Minecraft +import io.github.humbleui.skija.Font +import io.github.humbleui.skija.Typeface class BossHighlightFeat { @EventHandler - fun onWorldRenderLast(event: WorldRenderEvent.Last) { - val ctx = event.context - - // Render3D.renderFilledBox( // just to test rendering works - // ctx, - // x = 0.0, y = 70.0, z = 0.0, - // width = 1.0, height = 2.0, depth = 1.0, - // color = Color(255, 0, 0, 100), - // phase = true - // ) R.I.P the ominious debug box that sat in the void or something idk + fun onWorldRenderLast(event: SkiaDrawEvent) { } } \ No newline at end of file diff --git a/src/main/kotlin/dev/oblongboot/sxp/features/MiniBossAlert.kt b/src/main/kotlin/dev/oblongboot/sxp/features/MiniBossAlert.kt index 53819ff..ca764b3 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/features/MiniBossAlert.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/features/MiniBossAlert.kt @@ -19,7 +19,7 @@ class MiniBossAlert { if (!Config.isToggled("MiniBossAlert")) return val msg = packet.content().string.trim() - val regex = Regex("SLAYER MINI-BOSS (.+) has spawned!") + val regex = Regex("^SLAYER MINI-BOSS (.+) has spawned!") val match = regex.find(msg) ?: return val mini = match.groupValues[1] diff --git a/src/main/kotlin/dev/oblongboot/sxp/ui/SettingsScreen.kt b/src/main/kotlin/dev/oblongboot/sxp/ui/SettingsScreen.kt index 6e5364d..75e22d6 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/ui/SettingsScreen.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/ui/SettingsScreen.kt @@ -18,6 +18,7 @@ import net.minecraft.client.gui.screens.Screen import net.minecraft.network.chat.Component import org.lwjgl.glfw.GLFW import dev.oblongboot.sxp.utils.Render2D +import dev.oblongboot.sxp.utils.skia.SkijaRenderer import net.minecraft.client.input.MouseButtonEvent import net.minecraft.client.input.KeyEvent import java.awt.Color @@ -28,6 +29,11 @@ import kotlin.random.Random class SettingsScreen : Screen(Component.nullToEmpty("SlayerXPOverlay Config")) { companion object { + val titleFont by lazy { io.github.humbleui.skija.Font(io.github.humbleui.skija.FontMgr.getDefault().matchFamilyStyle(null, io.github.humbleui.skija.FontStyle.NORMAL), 28f) } + val catFont by lazy { io.github.humbleui.skija.Font(io.github.humbleui.skija.FontMgr.getDefault().matchFamilyStyle(null, io.github.humbleui.skija.FontStyle.NORMAL), 16f) } + val elementFont by lazy { io.github.humbleui.skija.Font(io.github.humbleui.skija.FontMgr.getDefault().matchFamilyStyle(null, io.github.humbleui.skija.FontStyle.NORMAL), 14f) } + val smallFont by lazy { io.github.humbleui.skija.Font(io.github.humbleui.skija.FontMgr.getDefault().matchFamilyStyle(null, io.github.humbleui.skija.FontStyle.NORMAL), 11f) } + fun open() { Scheduler.scheduleTask(1) { Minecraft.getInstance().setScreen(SettingsScreen()) @@ -37,7 +43,7 @@ class SettingsScreen : Screen(Component.nullToEmpty("SlayerXPOverlay Config")) { private val elements = mutableListOf() private val elementHeight = 25 - private val elementSpacing = 5 + private val elementSpacing = 10 private val sidebarWidth = 200 private val categories = mutableListOf() private var selectedCategory: Category? = null @@ -55,28 +61,36 @@ class SettingsScreen : Screen(Component.nullToEmpty("SlayerXPOverlay Config")) { private val animationTime: Double get() = (System.currentTimeMillis() - startTime).toDouble() - init { + private var dialogX = 0f + private var dialogY = 0f + private var dialogW = 0f + private var dialogH = 0f + private val sidebarW = 160f + private val animation = UIBounceAnimation(400) + + private val DESIGN_WIDTH = 960f + private val DESIGN_HEIGHT = 540f + + override fun init() { + animation.start() + super.init() + + dialogW = 800f + dialogH = 500f + dialogX = (DESIGN_WIDTH - dialogW) / 2f + dialogY = (DESIGN_HEIGHT - dialogH) / 2f + setupCategories() - selectedCategory = categories.firstOrNull() - selectedCategory?.let { updateElementsForCategory(it.name) } + val targetCatName = selectedCategory?.name ?: categories.firstOrNull()?.name + targetCatName?.let { updateElementsForCategory(it) } } - private fun renderParticles(ctx: GuiGraphics) { - val width = Render2D.scaledWidth - val height = Render2D.scaledHeight - - for ((index, particle) in particles.withIndex()) { - val x = ((sin(animationTime * 0.0003 * particle.speed + index + particle.phase) * 0.5 + 0.5) * width).toInt() - val y = ((cos(animationTime * 0.0004 * particle.speed + index * 0.5 + particle.phase) * 0.5 + 0.5) * height).toInt() - val alpha = ((sin(animationTime * 0.0008 + index) * 0.5 + 0.5) * 255).toInt().coerceIn(50, 255) - val color = Color(0, 100, particle.blueIntensity, alpha) - Render2D.drawRect(ctx, x, y, particle.size.toInt(), particle.size.toInt(), color) - } - } private fun updateElementsForCategory(name: String) { elements.clear() - var yPos = 100 + + val contentX = dialogX + sidebarW + 15f + var currentY = dialogY + 80f when (name) { "General" -> { @@ -85,89 +99,66 @@ class SettingsScreen : Screen(Component.nullToEmpty("SlayerXPOverlay Config")) { default = false, description = "Shows Slayer XP in a movable overlay" ).apply { - x = sidebarWidth + 20 - y = yPos + x = contentX.toInt() + y = currentY.toInt() } elements.add(overlaySwitch) - yPos += elementHeight + elementSpacing + currentY += elementHeight + elementSpacing val kphSwitch = SwitchConfig( name = "KPHOverlay", default = false, description = "Shows slayer kills per hour in a movable overlay" ).apply { - x = sidebarWidth + 20 - y = yPos + x = contentX.toInt() + y = currentY.toInt() } elements.add(kphSwitch) - yPos += elementHeight + elementSpacing + currentY += elementHeight + elementSpacing val openOtherGUI = ButtonSetting( name = "Open Overlay Manager", description = "Opens the overlay manager", - onClickAction = { - OverlayManager.open() - } + onClickAction = { OverlayManager.open() } ).apply { - x = sidebarWidth + 20 - y = yPos + x = contentX.toInt() + y = currentY.toInt() } elements.add(openOtherGUI) - yPos += elementHeight + elementSpacing + currentY += elementHeight + elementSpacing - - val bossInfoCheckbox = CheckboxSetting( - name = "BossInfoCheckbox", - options = listOf( - "XP", - "Kills", - "Time", - "KPH" - ), - defaultSelected = setOf(0, 2) - ).apply { - x = sidebarWidth + 20 - y = yPos - } - elements.add(bossInfoCheckbox) - yPos += elementHeight + elementSpacing + 20 - - val shortPrefix = SwitchConfig( name = "ShortPrefix", default = false, description = "Changes the prefix from SlayerXPOverlay to SXP", - onValueChangeAction = { - updatePrefix() - } + onValueChangeAction = { updatePrefix() } ).apply { - x = sidebarWidth + 20 - y = yPos + 65 + x = contentX.toInt() + y = currentY.toInt() } elements.add(shortPrefix) - yPos += elementHeight + elementSpacing + 60 + currentY += elementHeight + elementSpacing + + val bossInfoCheckbox = CheckboxSetting( + name = "BossInfoCheckbox", + options = listOf("XP", "Kills", "Time", "KPH"), + defaultSelected = setOf(0, 2) + ).apply { + x = contentX.toInt() + y = currentY.toInt() + } + elements.add(bossInfoCheckbox) + currentY += elementHeight + elementSpacing } "General QOL" -> { val autoCallMaddox = SwitchConfig( name = "AutoCallMaddox", default = false ).apply { - x = sidebarWidth +20 - y = yPos + x = contentX.toInt() + y = currentY.toInt() } elements.add(autoCallMaddox) - - // val HighlightsToggle = SwitchConfig( - // name = "BossHighlight", - // default = false, - // description = "Highlight bosses!!!" - // ).apply { - // x = sidebarWidth + 20 - // y = yPos - // } - // elements.add(HighlightsToggle) - // yPos += elementHeight + elementSpacing - // } // cba } @@ -198,38 +189,35 @@ class SettingsScreen : Screen(Component.nullToEmpty("SlayerXPOverlay Config")) { val messageColorSelector1 = ColorboxSetting( name = "MessageColorSelector1", defaultColor = Color(0, 255, 255), - description = "Start Color for the chat message gradient" + description = "Start Color for chat msgs" ).apply { - x = sidebarWidth + 20 - y = yPos + x = contentX.toInt() + y = currentY.toInt() } elements.add(messageColorSelector1) - yPos += elementHeight + elementSpacing val messageColorSelector2 = ColorboxSetting( name = "MessageColorSelector2", defaultColor = Color(0, 0, 255), - description = "End Color for the chat message gradient" + description = "End Color for chat msgs" ).apply { - x = sidebarWidth + 270 - y = yPos - 30 + x = (contentX + 250f).toInt() + y = currentY.toInt() } elements.add(messageColorSelector2) - yPos += elementHeight + elementSpacing + currentY += elementHeight + elementSpacing + 150f val gradientSwitch = SwitchConfig( name = "IsGradient", default = true, description = "Sends the message in a gradient", - onValueChangeAction = { - isGradient = Config.isToggled("IsGradient"); - } + onValueChangeAction = { isGradient = Config.isToggled("IsGradient") } ).apply { - x = sidebarWidth + 20; - y = yPos + 180 + x = contentX.toInt() + y = currentY.toInt() } elements.add(gradientSwitch) - yPos += elementHeight + elementSpacing + currentY += elementHeight + elementSpacing } } @@ -253,66 +241,130 @@ class SettingsScreen : Screen(Component.nullToEmpty("SlayerXPOverlay Config")) { } private fun setupCategories() { + val previousSelectedCategory = selectedCategory?.name ?: categories.firstOrNull()?.name categories.clear() - var yPos = 40 - val catNames = listOf("General", "General QOL", "Blaze", "Colors") + + var currentY = dialogY + 80f + val catNames = listOf("General", "General QOL", "Colors") catNames.forEachIndexed { index, name -> categories.add( Category( name = name, - x = 7, - y = yPos, - width = sidebarWidth - 14, + x = (dialogX + 10f).toInt(), + y = (currentY - 10f).toInt(), + width = sidebarW.toInt() - 20, height = 32, - selected = index == 0 + selected = (name == previousSelectedCategory || (previousSelectedCategory == null && index == 0)) ) ) - yPos += 37 + currentY += 40f } + + selectedCategory = categories.find { it.selected } ?: categories.firstOrNull() + selectedCategory?.selected = true } override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) { - context.fill(0, 0, sidebarWidth, height, Color(0, 0, 0, 128).rgb) - context.fill(sidebarWidth - 1, 0, sidebarWidth, height, Color(40, 40, 50, 255).rgb) - context.fill(sidebarWidth, 0, width, height, Color(40, 60, 120, 150).rgb) + val window = Minecraft.getInstance().window + val sw = window.guiScaledWidth.toFloat() + val sh = window.guiScaledHeight.toFloat() + val scaleX = sw / DESIGN_WIDTH + val scaleY = sh / DESIGN_HEIGHT + val finalUIScale = kotlin.math.min(scaleX, scaleY) + val offsetX = (sw - DESIGN_WIDTH * finalUIScale) / 2f + val offsetY = (sh - DESIGN_HEIGHT * finalUIScale) / 2f + val designMouseX = ((mouseX - offsetX) / finalUIScale).toInt() + val designMouseY = ((mouseY - offsetY) / finalUIScale).toInt() - val title = "§bSlayerXPOverlay §3Config" - val titleWidth = font.width(title) - context.drawString(font, title, (sidebarWidth - titleWidth) / 2, 20, Color.WHITE.rgb) + SkijaRenderer.beginFrame(sw, sh) + if (!SkijaRenderer.isDrawing) return - categories.forEach { cat -> - val bgColor = if (cat.selected) Color(0, 120, 255, 255).rgb else Color(30, 60, 120, 200).rgb - context.fill(cat.x, cat.y, cat.x + cat.width, cat.y + cat.height, bgColor) + try { + SkijaRenderer.drawBackdropBlur(0f, 0f, sw, sh, 0f, 20f, 0.5f) + SkijaRenderer.drawRoundedRect(0f, 0f, sw, sh, 0f, SkijaRenderer.argb(160, 5, 10, 15)) - val textX = cat.x + (cat.width - font.width(cat.name)) / 2 - val textY = cat.y + (cat.height - font.lineHeight) / 2 - context.drawString(font, cat.name, textX, textY, Color.WHITE.rgb) - } + val bounceScale = animation.get() - renderParticles(context) + val centerX = dialogX + dialogW / 2f + val centerY = dialogY + dialogH / 2f - elements.forEach { element -> - element.render(context) - } + SkijaRenderer.save() + SkijaRenderer.translate(offsetX, offsetY) + SkijaRenderer.scale(finalUIScale, finalUIScale) + SkijaRenderer.translate(centerX, centerY) + SkijaRenderer.scale(bounceScale, bounceScale) + SkijaRenderer.translate(-centerX, -centerY) + + SkijaRenderer.drawRoundedRect(dialogX, dialogY, dialogW, dialogH, 10f, SkijaRenderer.argb(230, 15, 20, 30)) - var instructionY = height - 80 - listOf( - "Click toggles to enable/disable features", - "Press 'O' to open overlay manager", - "Press ESC to close this menu" - ).forEach { - context.drawString(font, it, 15, instructionY, Color.GRAY.rgb) - instructionY += 15 - } + SkijaRenderer.drawRoundedRectBorderGradient(dialogX, dialogY, dialogW, dialogH, 10f, 1f, SkijaRenderer.argb(120, 0, 100, 200), SkijaRenderer.argb(80, 0, 50, 120), SkijaRenderer.GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT) + + val sbX = dialogX + SkijaRenderer.drawRoundedRect(sbX, dialogY, sidebarW, dialogH, 10f, SkijaRenderer.argb(60, 10, 15, 25)) + + val title = "SXP" + val titleW = SkijaRenderer.getTextWidth(title, titleFont) + SkijaRenderer.drawText(title, sbX + (sidebarW - titleW) / 2f, dialogY + 30f, SkijaRenderer.argb(255, 240, 245, 255), titleFont) - super.render(context, mouseX, mouseY, delta) + + categories.forEach { cat -> + val cx = cat.x.toFloat() + val cy = cat.y.toFloat() + val cw = cat.width.toFloat() + val ch = cat.height.toFloat() + + if (cat.selected) { + SkijaRenderer.drawRoundedGlow(cx, cy, cw, ch, 6f, SkijaRenderer.argb(50, 0, 120, 255), 10f) + SkijaRenderer.drawRoundedRectGradient(cx, cy, cw, ch, 6f, SkijaRenderer.argb(180, 0, 90, 200), SkijaRenderer.argb(180, 0, 60, 150)) + } else { + val isHovered = cat.contains(designMouseX, designMouseY) + val bgColor = if (isHovered) SkijaRenderer.argb(100, 30, 60, 100) else SkijaRenderer.argb(40, 20, 30, 50) + SkijaRenderer.drawRoundedRect(cx, cy, cw, ch, 6f, bgColor) + } + + val tw = SkijaRenderer.getTextWidth(cat.name, catFont) + val tx = cx + (cw - tw) / 2f + val ty = cy + ch / 2f - 7f + val tc = if (cat.selected) SkijaRenderer.argb(255, 255, 255, 255) else SkijaRenderer.argb(200, 200, 200, 220) + SkijaRenderer.drawText(cat.name, tx, ty, tc, catFont) + } + + elements.forEach { it.render(designMouseX, designMouseY) } + +// var iy = dialogY + dialogH - 45f +// listOf( +// "Click toggles to enable/disable features", +// "Press 'O' to open overlay manager", +// "Press ESC to close" +// ).forEach { +// SkijaRenderer.drawText(it, dialogX + 15f, iy, SkijaRenderer.argb(150, 200, 200, 200), smallFont) +// iy += 15f +// } + + SkijaRenderer.restore() + + } catch (e: Exception) { + e.printStackTrace() + } finally { + SkijaRenderer.endFrame() + } } override fun mouseClicked(click: MouseButtonEvent, doubled: Boolean): Boolean { + val window = Minecraft.getInstance().window + val sw = window.guiScaledWidth.toFloat() + val sh = window.guiScaledHeight.toFloat() + val finalUIScale = kotlin.math.min(sw / DESIGN_WIDTH, sh / DESIGN_HEIGHT) + val offsetX = (sw - DESIGN_WIDTH * finalUIScale) / 2f + val offsetY = (sh - DESIGN_HEIGHT * finalUIScale) / 2f + + val designMouseX = ((click.x - offsetX) / finalUIScale).toInt() + val designMouseY = ((click.y - offsetY) / finalUIScale).toInt() + if (click.button() == 0) { categories.forEach { cat -> - if (cat.contains(click.x.toInt(), click.y.toInt())) { + if (cat.contains(designMouseX, designMouseY)) { categories.forEach { it.selected = false } cat.selected = true selectedCategory = cat @@ -322,7 +374,7 @@ class SettingsScreen : Screen(Component.nullToEmpty("SlayerXPOverlay Config")) { } elements.forEach { element -> - if (element.onClick(click.x.toInt(), click.y.toInt())) return true + if (element.onClick(designMouseX, designMouseY)) return true } } return super.mouseClicked(click, doubled) @@ -343,8 +395,18 @@ class SettingsScreen : Screen(Component.nullToEmpty("SlayerXPOverlay Config")) { } override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean { + val window = Minecraft.getInstance().window + val sw = window.guiScaledWidth.toFloat() + val sh = window.guiScaledHeight.toFloat() + val finalUIScale = kotlin.math.min(sw / DESIGN_WIDTH, sh / DESIGN_HEIGHT) + val offsetX = (sw - DESIGN_WIDTH * finalUIScale) / 2f + val offsetY = (sh - DESIGN_HEIGHT * finalUIScale) / 2f + + val designMouseX = ((mouseX - offsetX) / finalUIScale).toInt() + val designMouseY = ((mouseY - offsetY) / finalUIScale).toInt() + elements.forEach { element -> - if (element is DropdownSetting && element.onScroll(mouseX.toInt(), mouseY.toInt(), verticalAmount)) { + if (element is DropdownSetting && element.onScroll(designMouseX, designMouseY, verticalAmount)) { return true } } diff --git a/src/main/kotlin/dev/oblongboot/sxp/ui/UIBounceAnimation.kt b/src/main/kotlin/dev/oblongboot/sxp/ui/UIBounceAnimation.kt new file mode 100644 index 0000000..c79d878 --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/ui/UIBounceAnimation.kt @@ -0,0 +1,44 @@ +package dev.oblongboot.sxp.ui + +class UIBounceAnimation(private val duration: Long = 400L) { + + private var startTime: Long = -1L + private var running = false + + fun start() { + startTime = System.currentTimeMillis() + running = true + } + + fun reset() { + startTime = System.currentTimeMillis() + } + + fun stop() { + running = false + } + + fun isRunning(): Boolean = running + + fun getProgress(): Float { + if (startTime == -1L) return 0f + + val elapsed = System.currentTimeMillis() - startTime + if (elapsed >= duration) { + running = false + return 1f + } + + return elapsed.toFloat() / duration + } + + fun get(): Float { + val t = getProgress() + return easeOutBack(t) + } +} + +fun easeOutBack(t: Float, s: Float = 1.70158f): Float { + val x = t - 1f + return 1f + (s + 1f) * x * x * x + s * x * x +} \ No newline at end of file diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/APIUtils.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/APIUtils.kt index 6e2f952..ec3c35e 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/utils/APIUtils.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/APIUtils.kt @@ -5,6 +5,17 @@ import java.net.URL import kotlinx.coroutines.* import kotlinx.serialization.* import kotlinx.serialization.json.* +import net.minecraft.core.Holder +import net.minecraft.core.component.DataComponents +import net.minecraft.core.registries.Registries +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.item.ItemStack +import net.minecraft.world.item.equipment.ArmorType +import net.minecraft.world.item.equipment.trim.ArmorTrim +import net.minecraft.world.item.equipment.trim.TrimMaterial +import net.minecraft.world.item.equipment.trim.TrimMaterials +import net.minecraft.world.item.equipment.trim.TrimPattern +import net.minecraft.world.item.equipment.trim.TrimPatterns object APIUtils { var BlazeXP: Long = 0 @@ -105,6 +116,18 @@ object APIUtils { } private fun parseXP(xpString: String): Long = xpString.replace(",", "").toLongOrNull() ?: 0L + +// fun applyContributorTrim(its: ItemStack, level: ServerLevel) { +// if (!(its.getItem() is ArmorType)) return +// val registryAccess = level.registryAccess() +// val materialRegistry = registryAccess.registry(Registries.TRIM_MATERIAL).orElseThrow() +// val patternRegistry = registryAccess.registry(Registries.TRIM_PATTERN).orElseThrow() +// val materialHolder = materialRegistry.getHolderOrThrow(TrimMaterials.DIAMOND) +// val patternHolder = patternRegistry.getHolderOrThrow(TrimPatterns.WARD) +// val trim = ArmorTrim(materialHolder, patternHolder) +// +// its.set(DataComponents.TRIM, trim) +// } @Serializable diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/CacheUtils.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/CacheUtils.kt index 5f4dc9f..5340939 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/utils/CacheUtils.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/CacheUtils.kt @@ -3,9 +3,6 @@ package dev.oblongboot.sxp.utils import java.util.LinkedHashMap import kotlin.time.Duration -/** -* beep boop -*/ class CacheUtils( private val maxSize: Int, private val ttl: Duration @@ -27,11 +24,8 @@ class CacheUtils( val entry = cache[key] if (entry != null && entry.expiryTimeMs > now) { - // Valid cached entry return entry.value } - - // Expired or missing, compute and cache val newValue = defaultValue() cache[key] = CacheEntry(newValue, now + ttl.inWholeMilliseconds) return newValue diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/Scheduler.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/Scheduler.kt index a39442a..690751b 100644 --- a/src/main/kotlin/dev/oblongboot/sxp/utils/Scheduler.kt +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/Scheduler.kt @@ -4,7 +4,6 @@ package dev.oblongboot.sxp.utils * Code from Devonian (https://github.com/Synnerz/devonian/blob/main/src/main/kotlin/com/github/synnerz/devonian/utils/Scheduler.kt) * Under GPL 3.0 License */ - import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents import net.minecraft.client.Minecraft import net.minecraft.world.entity.Entity diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/skia/SkiaContext.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/SkiaContext.kt new file mode 100644 index 0000000..444796d --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/SkiaContext.kt @@ -0,0 +1,70 @@ +package dev.oblongboot.sxp.utils.skia + +import io.github.humbleui.skija.* +import dev.oblongboot.sxp.events.EventManager +import dev.oblongboot.sxp.events.impl.SkiaDrawEvent +import dev.oblongboot.sxp.utils.skia.gl.States +import org.lwjgl.opengl.GL11 + +internal object SkiaContext { + + private val states = arrayOf( + BackendState.GL_BLEND, + BackendState.GL_VERTEX, + BackendState.GL_PIXEL_STORE, + BackendState.GL_TEXTURE_BINDING, + BackendState.GL_MISC + ) + + private var context: DirectContext? = null + private var renderTarget: WrappedBackendRenderTarget? = null + private var surface: Surface? = null + + var canvas: Canvas? = null + private set + + fun initSkia(width: Int, height: Int) { + if (context == null) { + context = DirectContext.makeGL() + } + + surface?.close() + renderTarget?.close() + + renderTarget = WrappedBackendRenderTarget.makeGL(width, height, 0, 8, 0, FramebufferFormat.GR_GL_RGBA8) + surface = Surface.wrapBackendRenderTarget( + requireNotNull(context), + requireNotNull(renderTarget), + SurfaceOrigin.BOTTOM_LEFT, + ColorType.RGBA_8888, + ColorSpace.getSRGB() + ) + + canvas = surface?.canvas + } + + fun draw() { + if (context == null || surface == null) return + + States.push() + GL11.glDisable(GL11.GL_CULL_FACE) + GL11.glClearColor(0f, 0f, 0f, 0f) + + context?.reset(*states) + + canvas?.let { canvas -> + context?.let { context -> + renderTarget?.let { renderTarget -> + surface?.let { surface -> + EventManager.post(SkiaDrawEvent(context, renderTarget, surface, canvas)) + } + } + } + } + + context?.flushAndSubmit(surface) + + States.pop() + } + +} diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/skia/SkijaRenderer.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/SkijaRenderer.kt new file mode 100644 index 0000000..df090b5 --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/SkijaRenderer.kt @@ -0,0 +1,575 @@ +package dev.oblongboot.sxp.utils.skia + +import com.mojang.blaze3d.opengl.GlDevice +import com.mojang.blaze3d.opengl.GlStateManager +import com.mojang.blaze3d.opengl.GlTexture +import com.mojang.blaze3d.systems.RenderSystem +import dev.oblongboot.sxp.utils.skia.gl.State +import io.github.humbleui.skija.* +import io.github.humbleui.skija.Font as SkijaFont +import io.github.humbleui.types.IRect +import io.github.humbleui.types.Rect +import io.github.humbleui.types.RRect +import net.minecraft.client.Minecraft +import org.lwjgl.opengl.GL11C +import org.lwjgl.opengl.GL12C +import org.lwjgl.opengl.GL30C +import java.util.concurrent.CopyOnWriteArrayList +//CREDIT TO @altEpsilonPhoenix on discord (hes goated) +object SkijaRenderer { + private const val GL_STATE_TEXTURE_UNITS = 12 + + enum class GradientDirection { + LEFT_TO_RIGHT, + TOP_TO_BOTTOM, + TOP_LEFT_TO_BOTTOM_RIGHT, + BOTTOM_LEFT_TO_TOP_RIGHT + } + + private val mc = Minecraft.getInstance() + private val renderCallbacks = CopyOnWriteArrayList() + private val topRenderCallbacks = CopyOnWriteArrayList() + + var context: DirectContext? = null + internal set + var surface: Surface? = null + internal set + var canvas: Canvas? = null + internal set + + var isDrawing = false + internal set + + private var hostGlState: State? = null + private var scissorStackDepth = 0 + private var lastRTWidth = -1 + private var lastRTHeight = -1 + private var skipBlurFrames = 0 + + fun bindEvent(event: dev.oblongboot.sxp.events.impl.SkiaDrawEvent) { + this.context = event.context + this.surface = event.surface + this.canvas = event.canvas + this.isDrawing = true + } + + fun unbindEvent() { + this.context = null + this.surface = null + this.canvas = null + this.isDrawing = false + } + + fun registerRender(runnable: Runnable) = renderCallbacks.add(runnable) + fun unregisterRender(runnable: Runnable) = renderCallbacks.remove(runnable) + fun registerTopRender(runnable: Runnable) = topRenderCallbacks.add(runnable) + fun unregisterTopRender(runnable: Runnable) = topRenderCallbacks.remove(runnable) + fun hasTopRenderCallbacks(): Boolean = topRenderCallbacks.isNotEmpty() + + fun runDrawables() { + renderCallbacks.forEach { + try { + it.run() + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + fun runTopDrawables() { + topRenderCallbacks.forEach { + try { + it.run() + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + fun renderTopCallbacks(width: Float, height: Float) { + if (topRenderCallbacks.isEmpty()) return + + beginFrame(width, height) + if (!isDrawing) return + + try { + runTopDrawables() + } finally { + endFrame() + } + } + + fun beginFrame(width: Float, height: Float) { + if (isDrawing) return + if (width <= 0f || height <= 0f) return + + if (context == null) { + context = DirectContext.makeGL() + } + val directContext = context ?: return + + val renderTarget = mc.mainRenderTarget + val device = RenderSystem.getDevice() as? GlDevice ?: return + val colorTexture = renderTarget.colorTexture as? GlTexture ?: return + val glFramebuffer = colorTexture.getFbo(device.directStateAccess(), renderTarget.depthTexture) + + hostGlState = State(330).push() + + try { + directContext.resetGLAll() + + GlStateManager._glBindFramebuffer(GL30C.GL_FRAMEBUFFER, glFramebuffer) + GlStateManager._viewport(0, 0, renderTarget.width, renderTarget.height) + GlStateManager._colorMask(true, true, true, true) + GlStateManager._disableCull() + GlStateManager._disableScissorTest() + GlStateManager._disableDepthTest() + GlStateManager._depthMask(false) + GlStateManager._enableBlend() + GlStateManager._blendFuncSeparate(GL11C.GL_SRC_ALPHA, GL11C.GL_ONE_MINUS_SRC_ALPHA, GL11C.GL_ONE, GL11C.GL_ONE_MINUS_SRC_ALPHA) + + GL11C.glPixelStorei(GL11C.GL_UNPACK_ALIGNMENT, 4) + GL11C.glPixelStorei(GL11C.GL_PACK_ALIGNMENT, 4) + GL11C.glPixelStorei(GL12C.GL_UNPACK_ROW_LENGTH, 0) + GL11C.glPixelStorei(GL12C.GL_UNPACK_SKIP_PIXELS, 0) + GL11C.glPixelStorei(GL12C.GL_UNPACK_SKIP_ROWS, 0) + + val backendRT = BackendRenderTarget.makeGL( + renderTarget.width, + renderTarget.height, + 0, + 8, + glFramebuffer, + FramebufferFormat.GR_GL_RGBA8 + ) + + val wrappedSurface = Surface.wrapBackendRenderTarget( + directContext, + backendRT, + SurfaceOrigin.BOTTOM_LEFT, + ColorType.RGBA_8888, + ColorSpace.getSRGB() + ) + + surface = wrappedSurface + canvas = wrappedSurface.canvas + isDrawing = true + scissorStackDepth = 0 + + val rtW = renderTarget.width + val rtH = renderTarget.height + if (rtW != lastRTWidth || rtH != lastRTHeight) { + lastRTWidth = rtW + lastRTHeight = rtH + skipBlurFrames = 5 + } else if (skipBlurFrames > 0) { + skipBlurFrames-- + } + + val guiScale = mc.window.guiScale.toFloat() + canvas?.scale(guiScale, guiScale) + } catch (_: Throwable) { + surface?.close() + surface = null + canvas = null + isDrawing = false + scissorStackDepth = 0 + restoreHostGLState() + } + } + + fun endFrame() { + if (!isDrawing) return + + try { + while (scissorStackDepth > 0) { + canvas?.restore() + scissorStackDepth-- + } + context?.flushAndSubmit(surface) + } finally { + surface?.close() + context?.resetGLAll() + restoreHostGLState() + + isDrawing = false + canvas = null + surface = null + scissorStackDepth = 0 + } + } + + fun save() = canvas?.save() + fun restore() = canvas?.restore() + fun translate(x: Float, y: Float) = canvas?.translate(x, y) + fun rotate(angleDeg: Float) = canvas?.rotate(angleDeg) + fun scale(x: Float, y: Float) = canvas?.scale(x, y) + + fun pushScissor(x: Float, y: Float, w: Float, h: Float) { + if (w <= 0f || h <= 0f) return + canvas?.save() + canvas?.clipRect(Rect.makeXYWH(x, y, w, h)) + scissorStackDepth++ + } + + fun popScissor() { + if (scissorStackDepth <= 0) return + canvas?.restore() + scissorStackDepth-- + } + + fun drawRoundedRect(x: Float, y: Float, w: Float, h: Float, radius: Float, colorARGB: Int) { + if (w <= 0f || h <= 0f) return + Paint().setColor(colorARGB).use { paint -> + canvas?.drawRRect(RRect.makeXYWH(x, y, w, h, radius.coerceAtLeast(0f)), paint) + } + } + + fun drawRoundedRectVaried( + x: Float, + y: Float, + w: Float, + h: Float, + topLeftRadius: Float, + topRightRadius: Float, + bottomRightRadius: Float, + bottomLeftRadius: Float, + colorARGB: Int + ) { + if (w <= 0f || h <= 0f) return + Paint().setColor(colorARGB).use { paint -> + canvas?.drawRRect( + RRect.makeComplexXYWH( + x, + y, + w, + h, + floatArrayOf( + topLeftRadius.coerceAtLeast(0f), topLeftRadius.coerceAtLeast(0f), + topRightRadius.coerceAtLeast(0f), topRightRadius.coerceAtLeast(0f), + bottomRightRadius.coerceAtLeast(0f), bottomRightRadius.coerceAtLeast(0f), + bottomLeftRadius.coerceAtLeast(0f), bottomLeftRadius.coerceAtLeast(0f) + ) + ), + paint + ) + } + } + + fun drawRoundedRectBorder( + x: Float, + y: Float, + w: Float, + h: Float, + radius: Float, + borderWidth: Float, + borderColorARGB: Int + ) { + if (w <= 0f || h <= 0f || borderWidth <= 0f) return + Paint() + .setColor(borderColorARGB) + .setMode(PaintMode.STROKE) + .setStrokeWidth(borderWidth) + .use { paint -> + canvas?.drawRRect(RRect.makeXYWH(x, y, w, h, radius.coerceAtLeast(0f)), paint) + } + } + + fun drawRoundedRectVariedBorder( + x: Float, + y: Float, + w: Float, + h: Float, + topLeftRadius: Float, + topRightRadius: Float, + bottomRightRadius: Float, + bottomLeftRadius: Float, + borderWidth: Float, + borderColorARGB: Int + ) { + if (w <= 0f || h <= 0f || borderWidth <= 0f) return + Paint() + .setColor(borderColorARGB) + .setMode(PaintMode.STROKE) + .setStrokeWidth(borderWidth) + .use { paint -> + canvas?.drawRRect( + RRect.makeComplexXYWH( + x, + y, + w, + h, + floatArrayOf( + topLeftRadius.coerceAtLeast(0f), topLeftRadius.coerceAtLeast(0f), + topRightRadius.coerceAtLeast(0f), topRightRadius.coerceAtLeast(0f), + bottomRightRadius.coerceAtLeast(0f), bottomRightRadius.coerceAtLeast(0f), + bottomLeftRadius.coerceAtLeast(0f), bottomLeftRadius.coerceAtLeast(0f) + ) + ), + paint + ) + } + } + + fun drawRoundedGlow( + x: Float, + y: Float, + w: Float, + h: Float, + radius: Float, + colorARGB: Int, + blurSigma: Float, + spread: Float = 0f + ) { + if (w <= 0f || h <= 0f || blurSigma <= 0f) return + + val activeCanvas = canvas ?: return + val padding = blurSigma * 3f + spread + val outerX = x - spread + val outerY = y - spread + val outerW = w + spread * 2f + val outerH = h + spread * 2f + val outerRadius = (radius + spread).coerceAtLeast(0f) + val layerBounds = Rect.makeXYWH( + x - padding, + y - padding, + w + padding * 2f, + h + padding * 2f + ) + + Paint().use { layerPaint -> + activeCanvas.saveLayer(layerBounds, layerPaint) + } + + try { + ImageFilter.makeBlur(blurSigma, blurSigma, FilterTileMode.DECAL).use { blurFilter -> + Paint() + .setColor(colorARGB) + .setImageFilter(blurFilter) + .use { glowPaint -> + activeCanvas.drawRRect( + RRect.makeXYWH(outerX, outerY, outerW, outerH, outerRadius), + glowPaint + ) + } + } + } finally { + activeCanvas.restore() + } + } + + fun drawBackdropBlur(x: Float, y: Float, w: Float, h: Float, radius: Float, blurSigma: Float, alpha: Float = 1f) { + if (w <= 0f || h <= 0f || blurSigma <= 0f) return + if (skipBlurFrames > 0) return + + val activeSurface = surface ?: return + val guiScale = mc.window.guiScale.toFloat().coerceAtLeast(1f) + val blurPadding = blurSigma * 3f + + val expandedX = x - blurPadding + val expandedY = y - blurPadding + val expandedW = w + blurPadding * 2f + val expandedH = h + blurPadding * 2f + + val snapshotX = kotlin.math.floor(expandedX * guiScale).toInt().coerceAtLeast(0) + val snapshotY = kotlin.math.floor(expandedY * guiScale).toInt().coerceAtLeast(0) + val snapshotRight = kotlin.math.ceil((expandedX + expandedW) * guiScale).toInt().coerceAtMost(activeSurface.width) + val snapshotBottom = kotlin.math.ceil((expandedY + expandedH) * guiScale).toInt().coerceAtMost(activeSurface.height) + val snapshotW = (snapshotRight - snapshotX).coerceAtLeast(1) + val snapshotH = (snapshotBottom - snapshotY).coerceAtLeast(1) + val skiaSnapshotY = activeSurface.height - snapshotY - snapshotH + + context?.flush() + activeSurface.makeImageSnapshot(IRect.makeXYWH(snapshotX, skiaSnapshotY, snapshotW, snapshotH))?.use { snapshot -> + val srcRect = Rect.makeWH(snapshot.width.toFloat(), snapshot.height.toFloat()) + val dstRect = Rect.makeXYWH(snapshotX / guiScale, snapshotY / guiScale, snapshotW / guiScale, snapshotH / guiScale) + + ImageFilter.makeBlur(blurSigma, blurSigma, FilterTileMode.DECAL).use { blurFilter -> + Paint() + .setAlphaf(alpha.coerceIn(0f, 1f)) + .setImageFilter(blurFilter) + .use { paint -> + canvas?.save() + canvas?.clipRRect(RRect.makeXYWH(x, y, w, h, radius.coerceAtLeast(0f)), true) + canvas?.drawImageRect(snapshot, srcRect, dstRect, paint, true) + canvas?.restore() + } + } + } + } + + fun drawRoundedRectGradient( + x: Float, + y: Float, + w: Float, + h: Float, + radius: Float, + colorStartARGB: Int, + colorEndARGB: Int, + direction: GradientDirection = GradientDirection.LEFT_TO_RIGHT + ) { + if (w <= 0f || h <= 0f) return + + val shader = createLinearGradientShader(x, y, w, h, colorStartARGB, colorEndARGB, direction) + shader.use { linearGradient -> + Paint().setShader(linearGradient).use { paint -> + canvas?.drawRRect(RRect.makeXYWH(x, y, w, h, radius.coerceAtLeast(0f)), paint) + } + } + } + + fun drawRoundedRectBorderGradient( + x: Float, + y: Float, + w: Float, + h: Float, + radius: Float, + borderWidth: Float, + colorStartARGB: Int, + colorEndARGB: Int, + direction: GradientDirection = GradientDirection.LEFT_TO_RIGHT + ) { + if (w <= 0f || h <= 0f || borderWidth <= 0f) return + + val shader = createLinearGradientShader(x, y, w, h, colorStartARGB, colorEndARGB, direction) + shader.use { linearGradient -> + Paint() + .setShader(linearGradient) + .setMode(PaintMode.STROKE) + .setStrokeWidth(borderWidth) + .use { paint -> + canvas?.drawRRect(RRect.makeXYWH(x, y, w, h, radius.coerceAtLeast(0f)), paint) + } + } + } + + fun drawText(text: String, x: Float, y: Float, colorARGB: Int, font: SkijaFont) { + TextLine.make(text, font).use { line -> + val baseline = y - line.ascent + Paint().setColor(colorARGB).use { paint -> + canvas?.drawTextLine(line, x, baseline, paint) + } + } + } + + fun getTextWidth(text: String, font: SkijaFont): Float { + TextLine.make(text, font).use { line -> + return line.width + } + } + + fun drawImage(skImage: Image, x: Float, y: Float, w: Float, h: Float, alpha: Float = 1f, radius: Float = 0f) { + if (w <= 0f || h <= 0f) return + + val srcRect = Rect.makeWH(skImage.width.toFloat(), skImage.height.toFloat()) + drawImageInternal(skImage, srcRect, x, y, w, h, alpha, radius) + } + + fun drawImageCropped( + skImage: Image, + srcX: Float, + srcY: Float, + srcW: Float, + srcH: Float, + x: Float, + y: Float, + w: Float, + h: Float, + alpha: Float = 1f, + radius: Float = 0f + ) { + if (w <= 0f || h <= 0f || srcW <= 0f || srcH <= 0f) return + + val imageW = skImage.width.toFloat() + val imageH = skImage.height.toFloat() + val clampedSrcX = srcX.coerceIn(0f, imageW) + val clampedSrcY = srcY.coerceIn(0f, imageH) + val clampedSrcW = srcW.coerceIn(0f, imageW - clampedSrcX) + val clampedSrcH = srcH.coerceIn(0f, imageH - clampedSrcY) + if (clampedSrcW <= 0f || clampedSrcH <= 0f) return + + val srcRect = Rect.makeXYWH(clampedSrcX, clampedSrcY, clampedSrcW, clampedSrcH) + drawImageInternal(skImage, srcRect, x, y, w, h, alpha, radius) + } + + fun destroy() { + scissorStackDepth = 0 + context?.close() + context = null + } + + private fun restoreHostGLState() { + val snapshot = hostGlState ?: return + snapshot.pop() + hostGlState = null + } + + private fun gradientEndpoints( + x: Float, + y: Float, + w: Float, + h: Float, + direction: GradientDirection + ): FloatArray { + return when (direction) { + GradientDirection.LEFT_TO_RIGHT -> floatArrayOf(x, y, x + w, y) + GradientDirection.TOP_TO_BOTTOM -> floatArrayOf(x, y, x, y + h) + GradientDirection.TOP_LEFT_TO_BOTTOM_RIGHT -> floatArrayOf(x, y, x + w, y + h) + GradientDirection.BOTTOM_LEFT_TO_TOP_RIGHT -> floatArrayOf(x, y + h, x + w, y) + } + } + + private fun createLinearGradientShader( + x: Float, + y: Float, + w: Float, + h: Float, + colorStartARGB: Int, + colorEndARGB: Int, + direction: GradientDirection + ): Shader { + val endpoints = gradientEndpoints(x, y, w, h, direction) + return Shader.makeLinearGradient( + endpoints[0], + endpoints[1], + endpoints[2], + endpoints[3], + intArrayOf(colorStartARGB, colorEndARGB) + ) + } + + private fun baselineForTopY(y: Float, font: SkijaFont): Float { + return y - font.metrics.ascent + } + + private fun drawImageInternal( + image: Image, + srcRect: Rect, + x: Float, + y: Float, + w: Float, + h: Float, + alpha: Float, + radius: Float + ) { + val dstRect = Rect.makeXYWH(x, y, w, h) + val clampedAlpha = alpha.coerceIn(0f, 1f) + + Paint().setAlphaf(clampedAlpha).use { paint -> + if (radius > 0f) { + canvas?.save() + canvas?.clipRRect(RRect.makeXYWH(x, y, w, h, radius)) + canvas?.drawImageRect(image, srcRect, dstRect, SamplingMode.LINEAR, paint, true) + canvas?.restore() + } else { + canvas?.drawImageRect(image, srcRect, dstRect, SamplingMode.LINEAR, paint, true) + } + } + } + + + + fun argb(a: Int, r: Int, g: Int, b: Int): Int { + return ((a and 255) shl 24) or ((r and 255) shl 16) or ((g and 255) shl 8) or (b and 255) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/skia/WrappedBackendRenderTarget.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/WrappedBackendRenderTarget.kt new file mode 100644 index 0000000..fe4e7ad --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/WrappedBackendRenderTarget.kt @@ -0,0 +1,37 @@ +package dev.oblongboot.sxp.utils.skia + +import io.github.humbleui.skija.BackendRenderTarget +import io.github.humbleui.skija.impl.Stats +import org.jetbrains.annotations.Contract +//CREDIT TO @altEpsilonPhoenix on discord (hes goated) +class WrappedBackendRenderTarget( + val width: Int, + val height: Int, + val sampleCnt: Int, + val stencilBits: Int, + val fbId: Int, + val fbFormat: Int, + ptr: Long +) : BackendRenderTarget(ptr) { + + companion object { + + @Contract("_, _, _, _, _, _ -> new") + fun makeGL( + width: Int, height: Int, sampleCnt: Int, stencilBits: Int, fbId: Int, fbFormat: Int + ): WrappedBackendRenderTarget { + Stats.onNativeCall() + return WrappedBackendRenderTarget( + width, + height, + sampleCnt, + stencilBits, + fbId, + fbFormat, + _nMakeGL(width, height, sampleCnt, stencilBits, fbId, fbFormat) + ) + } + + } + +} diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/skia/gl/Properties.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/gl/Properties.kt new file mode 100644 index 0000000..096b543 --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/gl/Properties.kt @@ -0,0 +1,85 @@ +package dev.oblongboot.sxp.utils.skia.gl + +import java.util.* + +class Properties { + + val lastActiveTexture = IntArray(1) + val lastProgram = IntArray(1) + val lastTexture = IntArray(1) + val lastSampler = IntArray(1) + val lastArrayBuffer = IntArray(1) + val lastVertexArrayObject = IntArray(1) + val lastPolygonMode = IntArray(2) + val lastViewport = IntArray(4) + val lastScissorBox = IntArray(4) + val lastBlendSrcRgb = IntArray(1) + val lastBlendDstRgb = IntArray(1) + val lastBlendSrcAlpha = IntArray(1) + val lastBlendDstAlpha = IntArray(1) + val lastBlendEquationRgb = IntArray(1) + val lastBlendEquationAlpha = IntArray(1) + + val lastPixelUnpackBufferBinding = IntArray(1) + val lastUnpackAlignment = IntArray(1) + val lastUnpackRowLength = IntArray(1) + val lastUnpackSkipPixels = IntArray(1) + val lastUnpackSkipRows = IntArray(1) + val lastPackSwapBytes = IntArray(1) + val lastPackLsbFirst = IntArray(1) + val lastPackRowLength = IntArray(1) + val lastPackImageHeight = IntArray(1) + val lastPackSkipPixels = IntArray(1) + val lastPackSkipRows = IntArray(1) + val lastPackSkipImages = IntArray(1) + val lastPackAlignment = IntArray(1) + val lastUnpackSwapBytes = IntArray(1) + val lastUnpackLsbFirst = IntArray(1) + val lastUnpackImageHeight = IntArray(1) + val lastUnpackSkipImages = IntArray(1) + + private val flags = BitSet(7) + + var lastEnableBlend + get() = flags[0] + set(value) { + flags[0] = value + } + + var lastEnableCullFace + get() = flags[1] + set(value) { + flags[1] = value + } + + var lastEnableDepthTest + get() = flags[2] + set(value) { + flags[2] = value + } + + var lastEnableStencilTest + get() = flags[3] + set(value) { + flags[3] = value + } + + var lastEnableScissorTest + get() = flags[4] + set(value) { + flags[4] = value + } + + var lastEnablePrimitiveRestart + get() = flags[5] + set(value) { + flags[5] = value + } + + var lastDepthMask + get() = flags[6] + set(value) { + flags[6] = value + } + +} diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/skia/gl/State.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/gl/State.kt new file mode 100644 index 0000000..ca491b4 --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/gl/State.kt @@ -0,0 +1,153 @@ +package dev.oblongboot.sxp.utils.skia.gl + +import org.lwjgl.opengl.GL +import org.lwjgl.opengl.GL45.* + +class State(private val glVersion: Int) { + + private val props = Properties() + + fun push(): State { + with(props) { + glGetIntegerv(GL_ACTIVE_TEXTURE, lastActiveTexture) + glActiveTexture(GL_TEXTURE0) + glGetIntegerv(GL_CURRENT_PROGRAM, lastProgram) + glGetIntegerv(GL_TEXTURE_BINDING_2D, lastTexture) + + if (glVersion >= 330 || GL.getCapabilities().GL_ARB_sampler_objects) { + glGetIntegerv(GL_SAMPLER_BINDING, lastSampler) + } + + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, lastArrayBuffer) + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, lastVertexArrayObject) + + if (glVersion >= 200) { + glGetIntegerv(GL_POLYGON_MODE, lastPolygonMode) + } + + glGetIntegerv(GL_VIEWPORT, lastViewport) + glGetIntegerv(GL_SCISSOR_BOX, lastScissorBox) + glGetIntegerv(GL_BLEND_SRC_RGB, lastBlendSrcRgb) + glGetIntegerv(GL_BLEND_DST_RGB, lastBlendDstRgb) + glGetIntegerv(GL_BLEND_SRC_ALPHA, lastBlendSrcAlpha) + glGetIntegerv(GL_BLEND_DST_ALPHA, lastBlendDstAlpha) + glGetIntegerv(GL_BLEND_EQUATION_RGB, lastBlendEquationRgb) + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, lastBlendEquationAlpha) + + lastEnableBlend = glIsEnabled(GL_BLEND) + lastEnableCullFace = glIsEnabled(GL_CULL_FACE) + lastEnableDepthTest = glIsEnabled(GL_DEPTH_TEST) + lastEnableStencilTest = glIsEnabled(GL_STENCIL_TEST) + lastEnableScissorTest = glIsEnabled(GL_SCISSOR_TEST) + + if (glVersion >= 310) { + lastEnablePrimitiveRestart = glIsEnabled(GL_PRIMITIVE_RESTART) + } + + lastDepthMask = glGetBoolean(GL_DEPTH_WRITEMASK) + + glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, lastPixelUnpackBufferBinding) + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0) + + glGetIntegerv(GL_PACK_SWAP_BYTES, lastPackSwapBytes) + glGetIntegerv(GL_PACK_LSB_FIRST, lastPackLsbFirst) + glGetIntegerv(GL_PACK_ROW_LENGTH, lastPackRowLength) + glGetIntegerv(GL_PACK_SKIP_PIXELS, lastPackSkipPixels) + glGetIntegerv(GL_PACK_SKIP_ROWS, lastPackSkipRows) + glGetIntegerv(GL_PACK_ALIGNMENT, lastPackAlignment) + + glGetIntegerv(GL_UNPACK_SWAP_BYTES, lastUnpackSwapBytes) + glGetIntegerv(GL_UNPACK_LSB_FIRST, lastUnpackLsbFirst) + glGetIntegerv(GL_UNPACK_ALIGNMENT, lastUnpackAlignment) + glGetIntegerv(GL_UNPACK_ROW_LENGTH, lastUnpackRowLength) + glGetIntegerv(GL_UNPACK_SKIP_PIXELS, lastUnpackSkipPixels) + glGetIntegerv(GL_UNPACK_SKIP_ROWS, lastUnpackSkipRows) + + if (glVersion >= 120) { + glGetIntegerv(GL_PACK_IMAGE_HEIGHT, lastPackImageHeight) + glGetIntegerv(GL_PACK_SKIP_IMAGES, lastPackSkipImages) + glGetIntegerv(GL_UNPACK_IMAGE_HEIGHT, lastUnpackImageHeight) + glGetIntegerv(GL_UNPACK_SKIP_IMAGES, lastUnpackSkipImages) + } + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1) + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0) + glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0) + glPixelStorei(GL_UNPACK_SKIP_ROWS, 0) + } + + return this + } + + fun pop(): State { + with(props) { + glUseProgram(lastProgram[0]) + glBindTexture(GL_TEXTURE_2D, lastTexture[0]) + + if (glVersion >= 330 || GL.getCapabilities().GL_ARB_sampler_objects) { + glBindSampler(0, lastSampler[0]) + } + + glActiveTexture(lastActiveTexture[0]) + glBindVertexArray(lastVertexArrayObject[0]) + glBindBuffer(GL_ARRAY_BUFFER, lastArrayBuffer[0]) + glBlendEquationSeparate(lastBlendEquationRgb[0], lastBlendEquationAlpha[0]) + glBlendFuncSeparate( + lastBlendSrcRgb[0], + lastBlendDstRgb[0], + lastBlendSrcAlpha[0], + lastBlendDstAlpha[0] + ) + + if (lastEnableBlend) glEnable(GL_BLEND) + else glDisable(GL_BLEND) + if (lastEnableCullFace) glEnable(GL_CULL_FACE) + else glDisable(GL_CULL_FACE) + if (lastEnableDepthTest) glEnable(GL_DEPTH_TEST) + else glDisable(GL_DEPTH_TEST) + if (lastEnableStencilTest) glEnable(GL_STENCIL_TEST) + else glDisable(GL_STENCIL_TEST) + if (lastEnableScissorTest) glEnable(GL_SCISSOR_TEST) + else glDisable(GL_SCISSOR_TEST) + + if (glVersion >= 310) { + if (lastEnablePrimitiveRestart) glEnable(GL_PRIMITIVE_RESTART) + else glDisable(GL_PRIMITIVE_RESTART) + } + + if (glVersion >= 200) { + glPolygonMode(GL_FRONT_AND_BACK, lastPolygonMode[0]) + } + + glViewport(lastViewport[0], lastViewport[1], lastViewport[2], lastViewport[3]) + glScissor(lastScissorBox[0], lastScissorBox[1], lastScissorBox[2], lastScissorBox[3]) + glDepthMask(lastDepthMask) + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, lastPixelUnpackBufferBinding[0]) + + glPixelStorei(GL_PACK_SWAP_BYTES, lastPackSwapBytes[0]) + glPixelStorei(GL_PACK_LSB_FIRST, lastPackLsbFirst[0]) + glPixelStorei(GL_PACK_ROW_LENGTH, lastPackRowLength[0]) + glPixelStorei(GL_PACK_SKIP_PIXELS, lastPackSkipPixels[0]) + glPixelStorei(GL_PACK_SKIP_ROWS, lastPackSkipRows[0]) + glPixelStorei(GL_PACK_ALIGNMENT, lastPackAlignment[0]) + + glPixelStorei(GL_UNPACK_SWAP_BYTES, lastUnpackSwapBytes[0]) + glPixelStorei(GL_UNPACK_LSB_FIRST, lastUnpackLsbFirst[0]) + glPixelStorei(GL_UNPACK_ALIGNMENT, lastUnpackAlignment[0]) + glPixelStorei(GL_UNPACK_ROW_LENGTH, lastUnpackRowLength[0]) + glPixelStorei(GL_UNPACK_SKIP_PIXELS, lastUnpackSkipPixels[0]) + glPixelStorei(GL_UNPACK_SKIP_ROWS, lastUnpackSkipRows[0]) + + if (glVersion >= 120) { + glPixelStorei(GL_PACK_IMAGE_HEIGHT, lastPackImageHeight[0]) + glPixelStorei(GL_PACK_SKIP_IMAGES, lastPackSkipImages[0]) + glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, lastUnpackImageHeight[0]) + glPixelStorei(GL_UNPACK_SKIP_IMAGES, lastUnpackSkipImages[0]) + } + } + + return this + } + +} diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/skia/gl/States.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/gl/States.kt new file mode 100644 index 0000000..0e91ce8 --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/gl/States.kt @@ -0,0 +1,28 @@ +package dev.oblongboot.sxp.utils.skia.gl + +import org.lwjgl.opengl.GL30.* +import java.util.* + +object States { + + private val glVersion: Int + private val states = Stack() + + fun push() { + states += State(glVersion).push() + } + + fun pop() { + require(states.isNotEmpty()) { "No state to restore." } + states.pop().pop() + } + + init { + val major = IntArray(1) + val minor = IntArray(1) + glGetIntegerv(GL_MAJOR_VERSION, major) + glGetIntegerv(GL_MINOR_VERSION, minor) + glVersion = major[0] * 100 + minor[0] * 10 + } + +} diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/skia/helper/FontHelper.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/helper/FontHelper.kt new file mode 100644 index 0000000..89f37fb --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/helper/FontHelper.kt @@ -0,0 +1,38 @@ +package dev.oblongboot.sxp.utils.skia.helper + +import io.github.humbleui.skija.* +import java.io.IOException +//CREDIT TO @altEpsilonPhoenix on discord (hes goated) +object FontHelper { + + const val DEFAULT_SIZE = 16f + val ROOT = "assets/slayerxpoverlay/font/" + + private val fonts by lazy { mutableMapOf() } + private val typefaces by lazy { mutableMapOf() } + + fun get(path: String, size: Float = DEFAULT_SIZE) = fonts.computeIfAbsent("$path:$size") { + Font( + loadTypeface(path), size + ).apply { + isSubpixel = false + hinting = FontHinting.NORMAL + edging = FontEdging.ANTI_ALIAS + } + } + + private fun loadTypeface(path: String) = typefaces.computeIfAbsent(path) { + val resourcePath = "$ROOT$path" + + val bytes = javaClass.classLoader + ?.getResourceAsStream(resourcePath) + ?.use { it.readAllBytes() } + ?: throw IOException("Font resource not found: $resourcePath") + + val font = FontMgr.getDefault().makeFromData(Data.makeFromBytes(bytes)) + ?: throw IllegalArgumentException("Invalid font data: $resourcePath") + + font + } + +} diff --git a/src/main/kotlin/dev/oblongboot/sxp/utils/skia/helper/ImageHelper.kt b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/helper/ImageHelper.kt new file mode 100644 index 0000000..3fbf0d6 --- /dev/null +++ b/src/main/kotlin/dev/oblongboot/sxp/utils/skia/helper/ImageHelper.kt @@ -0,0 +1,50 @@ +package dev.oblongboot.sxp.utils.skia.helper + +import io.github.humbleui.skija.ColorType +import io.github.humbleui.skija.DirectContext +import io.github.humbleui.skija.Image +import io.github.humbleui.skija.SurfaceOrigin +import org.lwjgl.opengl.GL11 +//CREDIT TO @altEpsilonPhoenix on discord (hes goated) +object ImageHelper { + private val textures = mutableMapOf() + + fun get( + context: DirectContext, + textureId: Int, + width: Int, + height: Int, + hasAlpha: Boolean = true, + origin: SurfaceOrigin = SurfaceOrigin.BOTTOM_LEFT, + ): Image { + require(width > 0 && height > 0) { "Width and height must be positive" } + + GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId) + return textures.getOrPut(textureId) { + create(context, textureId, width, height, origin, hasAlpha) + }.apply { + if (this.width != width || this.height != height) { + textures[textureId] = create(context, textureId, width, height, origin, hasAlpha) + } + } + } + + private fun create( + context: DirectContext, + textureId: Int, + width: Int, + height: Int, + origin: SurfaceOrigin, + hasAlpha: Boolean, + ) = Image.adoptGLTextureFrom( + context, + textureId, + GL11.GL_TEXTURE_2D, + width, + height, + GL11.GL_RGBA8, + origin, + if (hasAlpha) ColorType.RGBA_8888 else ColorType.RGB_888X + ) + +} diff --git a/src/main/resources/slayerxpoverlay.mixins.json b/src/main/resources/slayerxpoverlay.mixins.json index a7c7787..dea71db 100644 --- a/src/main/resources/slayerxpoverlay.mixins.json +++ b/src/main/resources/slayerxpoverlay.mixins.json @@ -5,7 +5,10 @@ "mixins": [ "ConnectionMixin", "Mixin3DRendering", - "MixinContributerColor" + "MixinContributerColor", + "MinecraftMixin", + "WindowMixin", + "HotbarMixin" ], "injectors": { "defaultRequire": 1