fullness indicator optional on tabs

This commit is contained in:
trunksbomb
2026-03-23 03:04:58 -04:00
parent e201c8c674
commit 274e4f6dec
4 changed files with 190 additions and 18 deletions

View File

@@ -4,6 +4,7 @@ import com.trunksbomb.bagtabs.menu.BagMenu;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.minecraft.core.NonNullList;
import java.util.List;
import java.util.Locale;
import net.minecraft.server.level.ServerPlayer;
@@ -14,6 +15,7 @@ import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.item.component.ItemContainerContents;
public final class BagCompat {
private static final List<BagHandler> HANDLERS = List.of(
@@ -83,6 +85,24 @@ public final class BagCompat {
return -1;
}
public static float getFullness(ItemStack stack) {
if (stack.isEmpty()) {
return -1.0F;
}
if (stack.has(DataComponents.CONTAINER)) {
return getContainerFullness(stack.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY));
}
BagHandler handler = findHandler(stack);
if (handler == null) {
return -1.0F;
}
float fullness = handler.getFullness(stack);
return fullness < 0.0F ? -1.0F : Math.min(1.0F, fullness);
}
public interface BagHandler {
boolean supports(ItemStack stack);
@@ -99,6 +119,10 @@ public final class BagCompat {
default int getActiveBagSlot(AbstractContainerMenu menu) {
return -1;
}
default float getFullness(ItemStack stack) {
return -1.0F;
}
}
private static final class NativeBagHandler implements BagHandler {
@@ -139,6 +163,11 @@ public final class BagCompat {
return BagContainer.canInsertInto(new BagContainer(player, slot), carriedStack);
}
@Override
public float getFullness(ItemStack stack) {
return getContainerFullness(stack.getOrDefault(DataComponents.CONTAINER, ItemContainerContents.EMPTY));
}
}
private static Integer getDankFrequency(ItemStack stack) {
@@ -227,6 +256,18 @@ public final class BagCompat {
throw new RuntimeException("Failed to access Traveler's Backpack storage", exception);
}
}
@Override
public float getFullness(ItemStack stack) {
try {
Class<?> wrapperClass = Class.forName(WRAPPER_CLASS);
Object wrapper = wrapperClass.getMethod("fromStack", ItemStack.class).invoke(null, stack);
Object storage = wrapper == null ? null : invoke(wrapper, "getStorage");
return storage == null ? -1.0F : getItemHandlerFullness(storage);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException exception) {
return -1.0F;
}
}
}
private static final class DankStorageHandler implements BagHandler {
@@ -346,6 +387,11 @@ public final class BagCompat {
stack.shrink(inserted);
return true;
}
@Override
public float getFullness(ItemStack stack) {
return -1.0F;
}
}
private static final class SophisticatedBackpacksHandler implements BagHandler {
@@ -425,6 +471,18 @@ public final class BagCompat {
return wrapper == null ? null : invoke(wrapper, "getInventoryHandler");
}
@Override
public float getFullness(ItemStack stack) {
try {
Class<?> wrapperClass = Class.forName("net.p3pp3rf1y.sophisticatedbackpacks.backpack.wrapper.BackpackWrapper");
Object wrapper = wrapperClass.getMethod("fromStack", ItemStack.class).invoke(null, stack);
Object inventoryHandler = wrapper == null ? null : invoke(wrapper, "getInventoryHandler");
return inventoryHandler == null ? -1.0F : getItemHandlerFullness(inventoryHandler);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException exception) {
return -1.0F;
}
}
}
private static int findMatchingPlayerSlot(AbstractContainerMenu menu, ItemStack bagStack) {
@@ -612,4 +670,35 @@ public final class BagCompat {
throw new RuntimeException("Failed to invoke compatibility method " + className + "#" + methodName, exception);
}
}
private static float getContainerFullness(ItemContainerContents contents) {
NonNullList<ItemStack> items = NonNullList.withSize(27, ItemStack.EMPTY);
contents.copyInto(items);
return calculateFullness(items);
}
private static float getItemHandlerFullness(Object itemHandler) {
int slots = (Integer) invoke(itemHandler, "getSlots");
List<ItemStack> items = new java.util.ArrayList<>(slots);
for (int slot = 0; slot < slots; slot++) {
items.add((ItemStack) invoke(itemHandler, "getStackInSlot", new Class<?>[] {int.class}, slot));
}
return calculateFullness(items);
}
private static float calculateFullness(List<ItemStack> items) {
if (items.isEmpty()) {
return -1.0F;
}
float usedCapacity = 0.0F;
for (ItemStack item : items) {
if (item.isEmpty()) {
continue;
}
usedCapacity += item.getCount() / (float) Math.max(1, item.getMaxStackSize());
}
return Math.min(1.0F, usedCapacity / items.size());
}
}

View File

@@ -471,7 +471,20 @@ public final class BagTabOverlay {
int y = layout.firstTabY();
int renderedCount = 0;
for (BagEntry bag : bags.subList(scrollOffset, scrollOffset + visibleCount)) {
rendered.add(new RenderedTab(bag, x, y, bag.slot() == activeBagSlot, TabPinManager.isPinned(bag, bags), layout.dockSide(), layout.scale(), layout.tabWidth(), layout.tabHeight(), layout.compact()));
rendered.add(new RenderedTab(
bag,
x,
y,
bag.slot() == activeBagSlot,
TabPinManager.isPinned(bag, bags),
layout.dockSide(),
layout.scale(),
layout.tabWidth(),
layout.tabHeight(),
layout.compact(),
layout.showFullnessIndicator(),
BagCompat.getFullness(bag.stack())
));
if (layout.vertical()) {
y += layout.tabHeight() + TAB_GAP;
} else {
@@ -558,6 +571,10 @@ public final class BagTabOverlay {
}
g.pose().popPose();
if (tab.showFullnessIndicator() && tab.fullness() >= 0.0F) {
renderFullnessIndicator(g, tab, tab.fullness());
}
if (tab.compact()) {
renderCompactLabel(g, tab);
}
@@ -610,6 +627,51 @@ public final class BagTabOverlay {
g.fill(x + 4, y + 4, x + 5, y + 5, 0xFFFF6C6C);
}
private static void renderFullnessIndicator(GuiGraphics g, RenderedTab tab, float fullness) {
int color = interpolateColor(0xFF65D96D, 0xFFD94A4A, fullness);
if (tab.isVertical()) {
int trackHeight = tab.height() <= 11 ? 1 : 2;
int trackWidth = Math.max(6, tab.width() - 8);
int trackX = tab.x() + ((tab.width() - trackWidth) / 2);
int trackY = tab.y() + tab.height() - trackHeight - 2;
int fillWidth = Math.max(1, Math.round(trackWidth * fullness));
g.fill(trackX, trackY, trackX + trackWidth, trackY + trackHeight, 0xFF2A2A2A);
g.fill(trackX, trackY, trackX + fillWidth, trackY + trackHeight, color);
g.fill(trackX - 1, trackY - 1, trackX + trackWidth + 1, trackY, 0xFF000000);
g.fill(trackX - 1, trackY + trackHeight, trackX + trackWidth + 1, trackY + trackHeight + 1, 0xFF000000);
g.fill(trackX - 1, trackY, trackX, trackY + trackHeight, 0xFF000000);
g.fill(trackX + trackWidth, trackY, trackX + trackWidth + 1, trackY + trackHeight, 0xFF000000);
return;
}
int trackWidth = tab.height() <= 11 ? 1 : 2;
int trackHeight = Math.max(4, tab.height() - 8);
int trackX = tab.x() + tab.width() - trackWidth - 2;
int trackY = tab.y() + 3;
int fillHeight = Math.max(1, Math.round(trackHeight * fullness));
int fillTop = trackY + trackHeight - fillHeight;
g.fill(trackX, trackY, trackX + trackWidth, trackY + trackHeight, 0xFF2A2A2A);
g.fill(trackX, fillTop, trackX + trackWidth, trackY + trackHeight, color);
g.fill(trackX - 1, trackY - 1, trackX + trackWidth + 1, trackY, 0xFF000000);
g.fill(trackX - 1, trackY + trackHeight, trackX + trackWidth + 1, trackY + trackHeight + 1, 0xFF000000);
g.fill(trackX - 1, trackY, trackX, trackY + trackHeight, 0xFF000000);
g.fill(trackX + trackWidth, trackY, trackX + trackWidth + 1, trackY + trackHeight, 0xFF000000);
}
private static int interpolateColor(int start, int end, float t) {
float clamped = Math.max(0.0F, Math.min(1.0F, t));
int sr = (start >> 16) & 0xFF;
int sg = (start >> 8) & 0xFF;
int sb = start & 0xFF;
int er = (end >> 16) & 0xFF;
int eg = (end >> 8) & 0xFF;
int eb = end & 0xFF;
int r = Math.round(sr + ((er - sr) * clamped));
int g = Math.round(sg + ((eg - sg) * clamped));
int b = Math.round(sb + ((eb - sb) * clamped));
return 0xFF000000 | (r << 16) | (g << 8) | b;
}
private static void scheduleCursorRestore(double guiX, double guiY) {
pendingCursorRestore = new PendingCursorRestore(guiX, guiY, Util.getMillis() + 1000L);
}
@@ -647,7 +709,9 @@ public final class BagTabOverlay {
strip.layout().scale(),
strip.layout().tabWidth(),
strip.layout().tabHeight(),
strip.layout().compact()
strip.layout().compact(),
strip.layout().showFullnessIndicator(),
BagCompat.getFullness(draggedEntry.stack())
);
}
@@ -730,6 +794,7 @@ public final class BagTabOverlay {
case MAX_MINUS -> mutateConfigPopup(settings -> settings.withMaxTabs(settings.maxTabs() - 1));
case MAX_PLUS -> mutateConfigPopup(settings -> settings.withMaxTabs(settings.maxTabs() + 1));
case COMPACT -> mutateConfigPopup(settings -> settings.withCompact(!settings.compact()));
case FULLNESS -> mutateConfigPopup(settings -> settings.withShowFullnessIndicator(!settings.showFullnessIndicator()));
case RESET -> {
DockConfigManager.clearOverride(configPopup.screenKey());
configPopup = configPopup.withMode(false, DockConfigManager.getEditableSettings(configPopup.screenKey(), false).copy());
@@ -780,13 +845,14 @@ public final class BagTabOverlay {
renderPopupButton(g, PopupElement.MAX_MINUS, Component.literal("-"), mouseX, mouseY, false);
renderPopupButton(g, PopupElement.MAX_PLUS, Component.literal("+"), mouseX, mouseY, false);
renderPopupButton(g, PopupElement.COMPACT, getPopupButtonText(PopupElement.COMPACT), mouseX, mouseY, false);
renderPopupButton(g, PopupElement.FULLNESS, getPopupButtonText(PopupElement.FULLNESS), mouseX, mouseY, false);
renderPopupButton(g, PopupElement.RESET, BagTabs.translation("dock.reset"), mouseX, mouseY, !(configPopup.editOverride() || DockConfigManager.hasOverride(configPopup.screenKey())));
renderPopupButton(g, PopupElement.DONE, BagTabs.translation("dock.done"), mouseX, mouseY, false);
int centerX = x + (POPUP_WIDTH / 2);
int labelTop = y + 24;
g.drawCenteredString(Minecraft.getInstance().font, Component.literal("X: " + configPopup.settings().xOffset()), centerX, labelTop + 42, 0xFFFFFF);
g.drawCenteredString(Minecraft.getInstance().font, Component.literal("Y: " + configPopup.settings().yOffset()), centerX, labelTop + 64, 0xFFFFFF);
g.drawCenteredString(Minecraft.getInstance().font, Component.literal("Tabs: " + configPopup.settings().maxTabs()), centerX, labelTop + 86, 0xFFFFFF);
g.drawCenteredString(Minecraft.getInstance().font, Component.literal("X: " + configPopup.settings().xOffset()), x + 31, labelTop + 38, 0xFFFFFF);
g.drawCenteredString(Minecraft.getInstance().font, Component.literal("Y: " + configPopup.settings().yOffset()), x + 101, labelTop + 38, 0xFFFFFF);
g.drawCenteredString(Minecraft.getInstance().font, Component.literal("Tabs: " + configPopup.settings().maxTabs()), centerX, labelTop + 58, 0xFFFFFF);
PopupElement hovered = getPopupElementAt(mouseX, mouseY);
if (hovered == PopupElement.X_MINUS || hovered == PopupElement.X_PLUS || hovered == PopupElement.Y_MINUS || hovered == PopupElement.Y_PLUS) {
@@ -825,6 +891,7 @@ public final class BagTabOverlay {
: BagTabs.translation("dock.target.global");
case SIDE -> BagTabs.translation("dock.side." + configPopup.settings().dockSide().name().toLowerCase());
case COMPACT -> BagTabs.translation(configPopup.settings().compact() ? "dock.compact.on" : "dock.compact.off");
case FULLNESS -> BagTabs.translation(configPopup.settings().showFullnessIndicator() ? "dock.fullness.on" : "dock.fullness.off");
default -> Component.empty();
};
}
@@ -835,15 +902,16 @@ public final class BagTabOverlay {
return switch (element) {
case TARGET -> new Rect(left, top, 128, 14);
case SIDE -> new Rect(left, top + 18, 128, 14);
case X_MINUS -> new Rect(left, top + 40, 14, 14);
case X_PLUS -> new Rect(left + 114, top + 40, 14, 14);
case Y_MINUS -> new Rect(left, top + 62, 14, 14);
case Y_PLUS -> new Rect(left + 114, top + 62, 14, 14);
case MAX_MINUS -> new Rect(left, top + 84, 14, 14);
case MAX_PLUS -> new Rect(left + 114, top + 84, 14, 14);
case COMPACT -> new Rect(left, top + 100, 128, 14);
case RESET -> new Rect(left, top + 120, 61, 14);
case DONE -> new Rect(left + 67, top + 120, 61, 14);
case X_MINUS -> new Rect(left, top + 40, 12, 12);
case X_PLUS -> new Rect(left + 44, top + 40, 12, 12);
case Y_MINUS -> new Rect(left + 72, top + 40, 12, 12);
case Y_PLUS -> new Rect(left + 116, top + 40, 12, 12);
case MAX_MINUS -> new Rect(left, top + 60, 12, 12);
case MAX_PLUS -> new Rect(left + 116, top + 60, 12, 12);
case COMPACT -> new Rect(left, top + 78, 128, 14);
case FULLNESS -> new Rect(left, top + 96, 128, 14);
case RESET -> new Rect(left, top + 116, 61, 14);
case DONE -> new Rect(left + 67, top + 116, 61, 14);
};
}
@@ -1235,7 +1303,7 @@ public final class BagTabOverlay {
}
}
return new DockLayout(firstTabX, firstTabY, tabWidth, tabHeight, controlX, controlY, controlWidth, controlHeight, scale, settings.dockSide(), vertical, settings.maxTabs(), settings.dockSide() == DockConfigManager.DockSide.FLOATING_HORIZONTAL || settings.dockSide() == DockConfigManager.DockSide.FLOATING_VERTICAL, settings.compact());
return new DockLayout(firstTabX, firstTabY, tabWidth, tabHeight, controlX, controlY, controlWidth, controlHeight, scale, settings.dockSide(), vertical, settings.maxTabs(), settings.dockSide() == DockConfigManager.DockSide.FLOATING_HORIZONTAL || settings.dockSide() == DockConfigManager.DockSide.FLOATING_VERTICAL, settings.compact(), settings.showFullnessIndicator());
}
private static DockControl getDockControl(DockLayout layout) {
@@ -1455,7 +1523,7 @@ public final class BagTabOverlay {
return Math.max(min, Math.min(max, value));
}
private record RenderedTab(BagEntry entry, int x, int y, boolean selected, boolean pinned, DockConfigManager.DockSide dockSide, float scale, int width, int height, boolean compact) {
private record RenderedTab(BagEntry entry, int x, int y, boolean selected, boolean pinned, DockConfigManager.DockSide dockSide, float scale, int width, int height, boolean compact, boolean showFullnessIndicator, float fullness) {
private boolean isHovered(double mouseX, double mouseY) {
return mouseX >= this.x && mouseX < this.x + this.width && mouseY >= this.y && mouseY < this.y + this.height;
}
@@ -1463,7 +1531,7 @@ public final class BagTabOverlay {
return this.isVertical() ? this.y + (this.height / 2.0D) : this.x + (this.width / 2.0D);
}
private RenderedTab withPosition(int newX, int newY) {
return new RenderedTab(this.entry, newX, newY, this.selected, this.pinned, this.dockSide, this.scale, this.width, this.height, this.compact);
return new RenderedTab(this.entry, newX, newY, this.selected, this.pinned, this.dockSide, this.scale, this.width, this.height, this.compact, this.showFullnessIndicator, this.fullness);
}
private boolean isVertical() {
return this.dockSide == DockConfigManager.DockSide.LEFT
@@ -1559,7 +1627,7 @@ public final class BagTabOverlay {
private record TabStrip(List<BagEntry> allEntries, List<RenderedTab> tabs, DockLayout layout, DockControl control, ScrollControl scrollControl, int scrollOffset, int maxScrollOffset, int activeBagSlot) {
}
private record DockLayout(int firstTabX, int firstTabY, int tabWidth, int tabHeight, int controlX, int controlY, int controlWidth, int controlHeight, float scale, DockConfigManager.DockSide dockSide, boolean vertical, int maxTabs, boolean floating, boolean compact) {
private record DockLayout(int firstTabX, int firstTabY, int tabWidth, int tabHeight, int controlX, int controlY, int controlWidth, int controlHeight, float scale, DockConfigManager.DockSide dockSide, boolean vertical, int maxTabs, boolean floating, boolean compact, boolean showFullnessIndicator) {
}
private record ConfigPopupState(String screenKey, boolean editOverride, DockConfigManager.DockSettings settings, int x, int y, boolean dragging, int dragOffsetX, int dragOffsetY) {
@@ -1698,6 +1766,7 @@ public final class BagTabOverlay {
MAX_MINUS,
MAX_PLUS,
COMPACT,
FULLNESS,
RESET,
DONE
}

View File

@@ -184,6 +184,7 @@ public final class DockConfigManager {
private int yOffset = 0;
private int maxTabs = 8;
private boolean compact = false;
private boolean showFullnessIndicator = false;
public DockSide dockSide() {
return dockSide;
@@ -205,6 +206,10 @@ public final class DockConfigManager {
return compact;
}
public boolean showFullnessIndicator() {
return showFullnessIndicator;
}
public DockSettings withDockSide(DockSide nextDockSide) {
DockSettings copy = copy();
copy.dockSide = nextDockSide;
@@ -235,6 +240,12 @@ public final class DockConfigManager {
return copy;
}
public DockSettings withShowFullnessIndicator(boolean nextShowFullnessIndicator) {
DockSettings copy = copy();
copy.showFullnessIndicator = nextShowFullnessIndicator;
return copy;
}
public DockSettings copy() {
DockSettings copy = new DockSettings();
copy.dockSide = this.dockSide;
@@ -242,6 +253,7 @@ public final class DockConfigManager {
copy.yOffset = this.yOffset;
copy.maxTabs = this.maxTabs;
copy.compact = this.compact;
copy.showFullnessIndicator = this.showFullnessIndicator;
return copy;
}

View File

@@ -18,6 +18,8 @@
"bagtabs.dock.max_tabs": "Max Tabs",
"bagtabs.dock.compact.on": "Layout: Compact",
"bagtabs.dock.compact.off": "Layout: Normal",
"bagtabs.dock.fullness.on": "Fullness: Shown",
"bagtabs.dock.fullness.off": "Fullness: Hidden",
"bagtabs.dock.open": "Open dock settings",
"bagtabs.dock.lock": "Lock tab interactions",
"bagtabs.dock.unlock": "Unlock tab interactions",