1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
package moe.nea.firmament.features.items.recipes
import java.util.Optional
import me.shedaniel.math.Dimension
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,
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)
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)
@OptIn(ExpensiveItemCacheApi::class)
override fun render(
guiGraphics: GuiGraphics,
mouseX: Int,
mouseY: Int,
partialTick: Float
) {
val stack = current().asImmutableItemStack()
// TODO: draw slot background
if (stack.isEmpty) return
guiGraphics.renderItem(stack, point.x, point.y)
guiGraphics.renderItemDecorations(
MC.font, stack, point.x, point.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
)
}
companion object {
val SHORT_NUM_CUTOFF = 1000
var canUseTooltipEvent = true
}
fun getTooltip(itemStack: ItemStack): List<Component> {
val lore = mutableListOf(itemStack.displayNameAccordingToNbt)
lore.addAll(itemStack.loreAccordingToNbt)
if (canUseTooltipEvent) {
try {
ItemTooltipCallback.EVENT.invoker().getTooltip(
itemStack, Item.TooltipContext.EMPTY,
TooltipFlag.NORMAL, lore
)
} catch (ex: Exception) {
canUseTooltipEvent = false
ErrorUtil.softError("Failed to use vanilla tooltips", ex)
}
} else {
ItemTooltipEvent.publish(
ItemTooltipEvent(
itemStack,
Item.TooltipContext.EMPTY,
TooltipFlag.NORMAL,
lore
)
)
}
if (itemStack.count >= SHORT_NUM_CUTOFF && lore.isNotEmpty())
lore.add(1, Component.literal("${itemStack.count}x").darkGrey())
return lore
}
override fun tick() {
if (SavedKeyBinding.isShiftDown()) return
if (content.size <= 1) return
if (MC.currentTick % 5 != 0) return
index = (index + 1) % content.size
}
var index = 0
var onUpdate: () -> Unit = {}
override fun onUpdate(action: () -> Unit) {
this.onUpdate = action
}
override fun current(): SBItemStack {
return content.getOrElse(index) { SBItemStack.EMPTY }
}
override fun update(newValue: SBItemStack) {
content = listOf(newValue)
// SAFE: content was just assigned to a non-empty list
index = index.coerceIn(content.indices)
}
}
|