aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-11-24 00:07:07 +0100
committerLinnea Gräf <nea@nea.moe>2025-11-24 00:07:07 +0100
commit20458e27c44ac498950cfaf408c35e7170d2a875 (patch)
tree8f73378dec15f7df21b69d466ce684c4b7a23a12
parent709c32ed2788c15f9f03a1bcbe6648e533e662c6 (diff)
downloadFirmament-20458e27c44ac498950cfaf408c35e7170d2a875.tar.gz
Firmament-20458e27c44ac498950cfaf408c35e7170d2a875.tar.bz2
Firmament-20458e27c44ac498950cfaf408c35e7170d2a875.zip
feat: scrollable recipe viewer
-rw-r--r--src/main/kotlin/features/items/recipes/ArrowWidget.kt10
-rw-r--r--src/main/kotlin/features/items/recipes/ComponentWidget.kt8
-rw-r--r--src/main/kotlin/features/items/recipes/EntityWidget.kt9
-rw-r--r--src/main/kotlin/features/items/recipes/FireWidget.kt7
-rw-r--r--src/main/kotlin/features/items/recipes/ItemSlotWidget.kt35
-rw-r--r--src/main/kotlin/features/items/recipes/RecipeRegistry.kt7
-rw-r--r--src/main/kotlin/features/items/recipes/RecipeScreen.kt122
-rw-r--r--src/main/kotlin/features/items/recipes/RecipeWidget.kt6
-rw-r--r--src/main/kotlin/features/items/recipes/RenderableRecipe.kt2
-rw-r--r--src/main/kotlin/features/items/recipes/StandaloneRecipeRenderer.kt8
-rw-r--r--src/main/kotlin/features/items/recipes/TooltipWidget.kt5
-rw-r--r--src/main/kotlin/util/render/TranslatedScissors.kt5
12 files changed, 171 insertions, 53 deletions
diff --git a/src/main/kotlin/features/items/recipes/ArrowWidget.kt b/src/main/kotlin/features/items/recipes/ArrowWidget.kt
index 93c768a..db0cf60 100644
--- a/src/main/kotlin/features/items/recipes/ArrowWidget.kt
+++ b/src/main/kotlin/features/items/recipes/ArrowWidget.kt
@@ -7,9 +7,9 @@ import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.RenderPipelines
import net.minecraft.resources.ResourceLocation
-class ArrowWidget(val point: Point) : RecipeWidget() {
- override val rect: Rectangle
- get() = Rectangle(point, Dimension(14, 14))
+class ArrowWidget(override var position: Point) : RecipeWidget() {
+ override val size: Dimension
+ get() = Dimension(14, 14)
companion object {
val arrowSprite = ResourceLocation.withDefaultNamespace("container/furnace/lit_progress")
@@ -28,8 +28,8 @@ class ArrowWidget(val point: Point) : RecipeWidget() {
14,
0,
0,
- point.x,
- point.y,
+ position.x,
+ position.y,
14,
14
)
diff --git a/src/main/kotlin/features/items/recipes/ComponentWidget.kt b/src/main/kotlin/features/items/recipes/ComponentWidget.kt
index b5c4211..08a2aa2 100644
--- a/src/main/kotlin/features/items/recipes/ComponentWidget.kt
+++ b/src/main/kotlin/features/items/recipes/ComponentWidget.kt
@@ -8,13 +8,13 @@ import net.minecraft.network.chat.Component
import moe.nea.firmament.repo.recipes.RecipeLayouter
import moe.nea.firmament.util.MC
-class ComponentWidget(val point: Point, var text: Component) : RecipeWidget(), RecipeLayouter.Updater<Component> {
+class ComponentWidget(override var position: Point, var text: Component) : RecipeWidget(), RecipeLayouter.Updater<Component> {
override fun update(newValue: Component) {
this.text = newValue
}
- override val rect: Rectangle
- get() = Rectangle(point, Dimension(MC.font.width(text), MC.font.lineHeight))
+ override val size: Dimension
+ get() = Dimension(MC.font.width(text), MC.font.lineHeight)
override fun render(
guiGraphics: GuiGraphics,
@@ -22,6 +22,6 @@ class ComponentWidget(val point: Point, var text: Component) : RecipeWidget(), R
mouseY: Int,
partialTick: Float
) {
- guiGraphics.drawString(MC.font, text, point.x, point.y, -1)
+ guiGraphics.drawString(MC.font, text, position.x, position.y, -1)
}
}
diff --git a/src/main/kotlin/features/items/recipes/EntityWidget.kt b/src/main/kotlin/features/items/recipes/EntityWidget.kt
index 88d91ad..4a087e5 100644
--- a/src/main/kotlin/features/items/recipes/EntityWidget.kt
+++ b/src/main/kotlin/features/items/recipes/EntityWidget.kt
@@ -1,11 +1,16 @@
package moe.nea.firmament.features.items.recipes
-import me.shedaniel.math.Rectangle
+import me.shedaniel.math.Dimension
+import me.shedaniel.math.Point
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.world.entity.LivingEntity
import moe.nea.firmament.gui.entity.EntityRenderer
-class EntityWidget(override val rect: Rectangle, val entity: LivingEntity) : RecipeWidget() {
+class EntityWidget(
+ override var position: Point,
+ override val size: Dimension,
+ val entity: LivingEntity
+) : RecipeWidget() {
override fun render(
guiGraphics: GuiGraphics,
mouseX: Int,
diff --git a/src/main/kotlin/features/items/recipes/FireWidget.kt b/src/main/kotlin/features/items/recipes/FireWidget.kt
index 0594d0a..565152b 100644
--- a/src/main/kotlin/features/items/recipes/FireWidget.kt
+++ b/src/main/kotlin/features/items/recipes/FireWidget.kt
@@ -2,12 +2,11 @@ package moe.nea.firmament.features.items.recipes
import me.shedaniel.math.Dimension
import me.shedaniel.math.Point
-import me.shedaniel.math.Rectangle
import net.minecraft.client.gui.GuiGraphics
-class FireWidget(val point: Point, val animationTicks: Int) : RecipeWidget() {
- override val rect: Rectangle
- get() = Rectangle(point, Dimension(10, 10))
+class FireWidget(override var position: Point, val animationTicks: Int) : RecipeWidget() {
+ override val size: Dimension
+ get() = Dimension(10, 10)
override fun render(
guiGraphics: GuiGraphics,
diff --git a/src/main/kotlin/features/items/recipes/ItemSlotWidget.kt b/src/main/kotlin/features/items/recipes/ItemSlotWidget.kt
index ef0b7d6..b9832b7 100644
--- a/src/main/kotlin/features/items/recipes/ItemSlotWidget.kt
+++ b/src/main/kotlin/features/items/recipes/ItemSlotWidget.kt
@@ -6,41 +6,39 @@ import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback
import net.minecraft.client.gui.GuiGraphics
-import net.minecraft.client.gui.components.Tooltip
import net.minecraft.network.chat.Component
-import net.minecraft.util.FormattedCharSequence
-import net.minecraft.world.inventory.tooltip.TooltipComponent
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.TooltipFlag
import moe.nea.firmament.events.ItemTooltipEvent
-import moe.nea.firmament.keybindings.GenericInputAction
import moe.nea.firmament.keybindings.SavedKeyBinding
import moe.nea.firmament.repo.ExpensiveItemCacheApi
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.repo.recipes.RecipeLayouter
import moe.nea.firmament.util.ErrorUtil
-import moe.nea.firmament.util.FirmFormatters
import moe.nea.firmament.util.FirmFormatters.shortFormat
import moe.nea.firmament.util.MC
-import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.darkGrey
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
import moe.nea.firmament.util.mc.loreAccordingToNbt
class ItemSlotWidget(
- val point: Point,
+ point: Point,
var content: List<SBItemStack>,
val slotKind: RecipeLayouter.SlotKind
) : RecipeWidget(),
RecipeLayouter.CyclingItemSlot {
- val backgroundTopLeft =
- if (slotKind.isBig) Point(point.x - 4, point.y - 4)
- else Point(point.x - 1, point.y - 1)
+ override var position = point
+ override val size get() = Dimension(16, 16)
+ val itemRect get() = Rectangle(position, Dimension(16, 16))
+
+ val backgroundTopLeft
+ get() =
+ if (slotKind.isBig) Point(position.x - 4, position.y - 4)
+ else Point(position.x - 1, position.y - 1)
val backgroundSize =
if (slotKind.isBig) Dimension(16 + 8, 16 + 8)
else Dimension(18, 18)
- val itemRect = Rectangle(point, Dimension(16, 16))
override val rect: Rectangle
get() = Rectangle(backgroundTopLeft, backgroundSize)
@@ -54,17 +52,18 @@ class ItemSlotWidget(
val stack = current().asImmutableItemStack()
// TODO: draw slot background
if (stack.isEmpty) return
- guiGraphics.renderItem(stack, point.x, point.y)
+ guiGraphics.renderItem(stack, position.x, position.y)
guiGraphics.renderItemDecorations(
- MC.font, stack, point.x, point.y,
+ MC.font, stack, position.x, position.y,
if (stack.count >= SHORT_NUM_CUTOFF) shortFormat(stack.count.toDouble())
else null
)
- if (itemRect.contains(mouseX, mouseY))
- guiGraphics.setTooltipForNextFrame(
- MC.font, getTooltip(stack), Optional.empty(),
- mouseX, mouseY
- )
+ if (itemRect.contains(mouseX, mouseY)
+ && guiGraphics.containsPointInScissor(mouseX, mouseY)
+ ) guiGraphics.setTooltipForNextFrame(
+ MC.font, getTooltip(stack), Optional.empty(),
+ mouseX, mouseY
+ )
}
companion object {
diff --git a/src/main/kotlin/features/items/recipes/RecipeRegistry.kt b/src/main/kotlin/features/items/recipes/RecipeRegistry.kt
index 158f9a4..cbe1558 100644
--- a/src/main/kotlin/features/items/recipes/RecipeRegistry.kt
+++ b/src/main/kotlin/features/items/recipes/RecipeRegistry.kt
@@ -33,7 +33,8 @@ object RecipeRegistry {
if (event.matches(SavedKeyBinding.keyWithoutMods(InputConstants.KEY_R))) {
val stack = event.screen.focusedItemStack ?: return
val recipes = getRecipesFor(SBItemStack(stack))
- MC.screen = RecipeScreen(recipes.firstOrNull() ?: return)
+ if (recipes.isEmpty()) return
+ MC.screen = RecipeScreen(recipes.toList())
}
}
@@ -66,8 +67,8 @@ object RecipeRegistry {
return m
}
- lateinit var recipesForIndex : Map<SkyblockId, List<RenderableRecipe<*>>>
- lateinit var usagesForIndex : Map<SkyblockId, List<RenderableRecipe<*>>>
+ lateinit var recipesForIndex: Map<SkyblockId, List<RenderableRecipe<*>>>
+ lateinit var usagesForIndex: Map<SkyblockId, List<RenderableRecipe<*>>>
override fun reload(recipe: NEURepository) {
recipesForIndex = createIndex(true)
usagesForIndex = createIndex(false)
diff --git a/src/main/kotlin/features/items/recipes/RecipeScreen.kt b/src/main/kotlin/features/items/recipes/RecipeScreen.kt
index c94839d..d365c0e 100644
--- a/src/main/kotlin/features/items/recipes/RecipeScreen.kt
+++ b/src/main/kotlin/features/items/recipes/RecipeScreen.kt
@@ -1,29 +1,129 @@
package moe.nea.firmament.features.items.recipes
+import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
+import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
-import moe.nea.firmament.repo.SBItemStack
+import net.minecraft.client.renderer.RenderPipelines
+import moe.nea.firmament.util.mc.CommonTextures
+import moe.nea.firmament.util.render.enableScissorWithTranslation
import moe.nea.firmament.util.tr
class RecipeScreen(
- val recipes: RenderableRecipe<*>,
+ val recipes: List<RenderableRecipe<*>>,
) : Screen(tr("firmament.recipe.screen", "SkyBlock Recipe")) {
- lateinit var layoutedRecipe: StandaloneRecipeRenderer
+ data class PlacedRecipe(
+ val bounds: Rectangle,
+ val layoutedRecipe: StandaloneRecipeRenderer,
+ ) {
+ fun moveTo(position: Point) {
+ val Δx = position.x - bounds.x
+ val Δy = position.y - bounds.y
+ bounds.translate(Δx, Δy)
+ layoutedRecipe.widgets.forEach { widget ->
+ widget.position = widget.position.clone().also {
+ it.translate(Δx, Δy)
+ }
+ }
+ }
+ }
+
+ lateinit var placedRecipes: List<PlacedRecipe>
+ var scrollViewport: Int = 0
+ var scrollOffset: Int = 0
+ var scrollPortWidth: Int = 0
+ var heightEstimate: Int = 0
+ val gutter = 10
override fun init() {// TODO: wrap all of this in a scroll layout
super.init()
- val bounds = Rectangle(
- width / 2 - recipes.renderer.displayWidth / 2,
- height / 2 - recipes.renderer.displayHeight / 2,
- recipes.renderer.displayWidth,
- recipes.renderer.displayHeight
+ scrollViewport = minOf(height - 20, 250)
+ scrollPortWidth = 0
+ heightEstimate = 0
+ var offset = height / 2 - scrollViewport / 2
+ placedRecipes = recipes.map {
+ val effectiveWidth = minOf(it.renderer.displayWidth, width - 20)
+ val bounds = Rectangle(
+ width / 2 - effectiveWidth / 2,
+ offset,
+ effectiveWidth,
+ it.renderer.displayHeight
+ )
+ if (heightEstimate > 0)
+ heightEstimate += gutter
+ heightEstimate += bounds.height
+ scrollPortWidth = maxOf(effectiveWidth, scrollPortWidth)
+ offset += bounds.height + gutter
+ val layoutedRecipe = it.render(bounds)
+ layoutedRecipe.widgets.forEach(this::addRenderableWidget)
+ PlacedRecipe(bounds, layoutedRecipe)
+ }
+ }
+
+ fun scrollRect() =
+ Rectangle(
+ width / 2 - scrollPortWidth / 2, height / 2 - scrollViewport / 2,
+ scrollPortWidth, scrollViewport
+ )
+
+ fun scissorScrollPort(guiGraphics: GuiGraphics) {
+ guiGraphics.enableScissorWithTranslation(scrollRect())
+ }
+
+ override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean {
+ if (!scrollRect().contains(mouseX, mouseY))
+ return false
+ scrollOffset = (scrollOffset + scrollY * -4)
+ .coerceAtMost(heightEstimate - scrollViewport.toDouble())
+ .coerceAtLeast(.0)
+ .toInt()
+ var offset = height / 2 - scrollViewport / 2 - scrollOffset
+ placedRecipes.forEach {
+ it.moveTo(Point(it.bounds.x, offset))
+ offset += it.bounds.height + gutter
+ }
+ return true
+ }
+
+ override fun renderBackground(
+ guiGraphics: GuiGraphics,
+ mouseX: Int,
+ mouseY: Int,
+ partialTick: Float
+ ) {
+ super.renderBackground(guiGraphics, mouseX, mouseY, partialTick)
+
+ val srect = scrollRect()
+ srect.grow(8, 8)
+ guiGraphics.blitSprite(
+ RenderPipelines.GUI_TEXTURED,
+ CommonTextures.genericWidget(),
+ srect.x, srect.y,
+ srect.width, srect.height
)
- layoutedRecipe = recipes.render(bounds)
- layoutedRecipe.widgets.forEach(this::addRenderableWidget)
+
+ scissorScrollPort(guiGraphics)
+ placedRecipes.forEach {
+ guiGraphics.blitSprite(
+ RenderPipelines.GUI_TEXTURED,
+ CommonTextures.genericWidget(),
+ it.bounds.x, it.bounds.y,
+ it.bounds.width, it.bounds.height
+ )
+ }
+ guiGraphics.disableScissor()
+ }
+
+ override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
+ scissorScrollPort(guiGraphics)
+ super.render(guiGraphics, mouseX, mouseY, partialTick)
+ guiGraphics.disableScissor()
}
override fun tick() {
super.tick()
- layoutedRecipe.tick()
+ placedRecipes.forEach {
+ it.layoutedRecipe.tick()
+ }
}
}
diff --git a/src/main/kotlin/features/items/recipes/RecipeWidget.kt b/src/main/kotlin/features/items/recipes/RecipeWidget.kt
index 2818d05..5884129 100644
--- a/src/main/kotlin/features/items/recipes/RecipeWidget.kt
+++ b/src/main/kotlin/features/items/recipes/RecipeWidget.kt
@@ -1,5 +1,7 @@
package moe.nea.firmament.features.items.recipes
+import me.shedaniel.math.Dimension
+import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
import net.minecraft.client.gui.components.Renderable
import net.minecraft.client.gui.components.events.GuiEventListener
@@ -18,7 +20,9 @@ abstract class RecipeWidget : GuiEventListener, Renderable, NarratableEntry {
open fun tick() {}
private var _focused = false
- abstract val rect: Rectangle
+ abstract var position: Point
+ abstract val size: Dimension
+ open val rect: Rectangle get() = Rectangle(position, size)
override fun setFocused(focused: Boolean) {
this._focused = focused
}
diff --git a/src/main/kotlin/features/items/recipes/RenderableRecipe.kt b/src/main/kotlin/features/items/recipes/RenderableRecipe.kt
index 3601e9e..20ca17e 100644
--- a/src/main/kotlin/features/items/recipes/RenderableRecipe.kt
+++ b/src/main/kotlin/features/items/recipes/RenderableRecipe.kt
@@ -11,7 +11,7 @@ class RenderableRecipe<T : Any>(
val mainItemStack: SBItemStack?,
) {
fun render(bounds: Rectangle): StandaloneRecipeRenderer {
- val layouter = StandaloneRecipeRenderer()
+ val layouter = StandaloneRecipeRenderer(bounds)
renderer.render(recipe, bounds, layouter, mainItemStack)
return layouter
}
diff --git a/src/main/kotlin/features/items/recipes/StandaloneRecipeRenderer.kt b/src/main/kotlin/features/items/recipes/StandaloneRecipeRenderer.kt
index df7c4de..fb11592 100644
--- a/src/main/kotlin/features/items/recipes/StandaloneRecipeRenderer.kt
+++ b/src/main/kotlin/features/items/recipes/StandaloneRecipeRenderer.kt
@@ -10,7 +10,7 @@ import net.minecraft.world.entity.LivingEntity
import moe.nea.firmament.repo.SBItemStack
import moe.nea.firmament.repo.recipes.RecipeLayouter
-class StandaloneRecipeRenderer : AbstractContainerEventHandler(), RecipeLayouter {
+class StandaloneRecipeRenderer(val bounds: Rectangle) : AbstractContainerEventHandler(), RecipeLayouter {
fun tick() {
widgets.forEach { it.tick() }
@@ -30,11 +30,13 @@ class StandaloneRecipeRenderer : AbstractContainerEventHandler(), RecipeLayouter
return addWidget(ItemSlotWidget(Point(x, y), content, slotKind))
}
+ val Rectangle.topLeft get() = Point(x, y)
+
override fun createTooltip(
rectangle: Rectangle,
label: List<Component>
) {
- addWidget(TooltipWidget(rectangle, label))
+ addWidget(TooltipWidget(rectangle.topLeft, rectangle.size, label))
}
override fun createLabel(
@@ -64,7 +66,7 @@ class StandaloneRecipeRenderer : AbstractContainerEventHandler(), RecipeLayouter
}
override fun createEntity(rectangle: Rectangle, entity: LivingEntity) {
- addWidget(EntityWidget(rectangle, entity))
+ addWidget(EntityWidget(rectangle.topLeft, rectangle.size, entity))
}
val widgets: MutableList<RecipeWidget> = mutableListOf()
diff --git a/src/main/kotlin/features/items/recipes/TooltipWidget.kt b/src/main/kotlin/features/items/recipes/TooltipWidget.kt
index cf9c09a..87feb61 100644
--- a/src/main/kotlin/features/items/recipes/TooltipWidget.kt
+++ b/src/main/kotlin/features/items/recipes/TooltipWidget.kt
@@ -1,12 +1,15 @@
package moe.nea.firmament.features.items.recipes
+import me.shedaniel.math.Dimension
+import me.shedaniel.math.Point
import me.shedaniel.math.Rectangle
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import moe.nea.firmament.repo.recipes.RecipeLayouter
class TooltipWidget(
- override val rect: Rectangle,
+ override var position: Point,
+ override val size: Dimension,
label: List<Component>
) : RecipeWidget(), RecipeLayouter.Updater<List<Component>> {
override fun update(newValue: List<Component>) {
diff --git a/src/main/kotlin/util/render/TranslatedScissors.kt b/src/main/kotlin/util/render/TranslatedScissors.kt
index 5c7e31d..e337cf0 100644
--- a/src/main/kotlin/util/render/TranslatedScissors.kt
+++ b/src/main/kotlin/util/render/TranslatedScissors.kt
@@ -1,9 +1,14 @@
package moe.nea.firmament.util.render
+import me.shedaniel.math.Rectangle
import org.joml.Matrix3x2f
import org.joml.Vector3f
import net.minecraft.client.gui.GuiGraphics
+fun GuiGraphics.enableScissorWithTranslation(rect: Rectangle) {
+ enableScissor(rect.minX, rect.minY, rect.maxX, rect.maxY)
+}
+
fun GuiGraphics.enableScissorWithTranslation(x1: Float, y1: Float, x2: Float, y2: Float) {
enableScissor(x1.toInt(), y1.toInt(), x2.toInt(), y2.toInt())
}