diff options
| author | Linnea Gräf <nea@nea.moe> | 2025-11-24 00:07:07 +0100 |
|---|---|---|
| committer | Linnea Gräf <nea@nea.moe> | 2025-11-24 00:07:07 +0100 |
| commit | 20458e27c44ac498950cfaf408c35e7170d2a875 (patch) | |
| tree | 8f73378dec15f7df21b69d466ce684c4b7a23a12 | |
| parent | 709c32ed2788c15f9f03a1bcbe6648e533e662c6 (diff) | |
| download | Firmament-20458e27c44ac498950cfaf408c35e7170d2a875.tar.gz Firmament-20458e27c44ac498950cfaf408c35e7170d2a875.tar.bz2 Firmament-20458e27c44ac498950cfaf408c35e7170d2a875.zip | |
feat: scrollable recipe viewer
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()) } |
