reorder pinned bags

This commit is contained in:
trunksbomb
2026-03-22 20:30:54 -04:00
parent 15be03e055
commit 85a7e1a48b
3 changed files with 228 additions and 6 deletions

View File

@@ -21,6 +21,7 @@ public class BagTabsClient {
modEventBus.addListener(BagTabsClient::registerItemColors);
NeoForge.EVENT_BUS.addListener(BagTabsClient::renderTabs);
NeoForge.EVENT_BUS.addListener(BagTabsClient::clickTabs);
NeoForge.EVENT_BUS.addListener(BagTabsClient::dragTabs);
NeoForge.EVENT_BUS.addListener(BagTabsClient::releaseTabs);
}
@@ -46,6 +47,10 @@ public class BagTabsClient {
BagTabOverlay.mouseReleased(event);
}
private static void dragTabs(ScreenEvent.MouseDragged.Pre event) {
BagTabOverlay.mouseDragged(event);
}
private static final class BagItemColor {
private static final int DEFAULT_TINT = 0x9E7B4F;

View File

@@ -42,6 +42,8 @@ public final class BagTabOverlay {
private static int lastMenuContainerId = Integer.MIN_VALUE;
private static int lastInsertRequestId = 0;
private static int pendingInsertRequestId = -1;
private static PendingClick pendingClick;
private static DragState dragState;
private BagTabOverlay() {
}
@@ -68,13 +70,32 @@ public final class BagTabOverlay {
refreshInsertTargets(screen, carriedStack);
RenderedTab draggedTab = null;
for (RenderedTab tab : tabs) {
renderTab(guiGraphics, tab, mouseX, mouseY, carriedStack);
if (dragState != null && tab.entry().slot() == dragState.draggedSlot()) {
draggedTab = tab;
continue;
}
renderTab(guiGraphics, tab, mouseX, mouseY, carriedStack, false);
guiGraphics.renderItem(tab.entry().stack(), tab.x() + 3, tab.y() + 2);
}
for (RenderedTab tab : tabs) {
if (tab.isHovered(mouseX, mouseY)) {
if (draggedTab != null) {
int dragX = (int) Math.round(mouseX - dragState.grabOffsetX());
RenderedTab floatingTab = draggedTab.withPosition(dragX, draggedTab.y());
guiGraphics.pose().pushPose();
guiGraphics.pose().translate(0.0F, 0.0F, 200.0F);
renderTab(guiGraphics, floatingTab, mouseX, mouseY, carriedStack, true);
guiGraphics.renderItem(draggedTab.entry().stack(), dragX + 3, draggedTab.y() + 2);
guiGraphics.pose().popPose();
}
if (dragState == null) {
for (RenderedTab tab : tabs) {
if (!tab.isHovered(mouseX, mouseY)) {
continue;
}
guiGraphics.renderTooltip(
Minecraft.getInstance().font,
getTooltipLines(tab, tabs).stream().map(Component::getVisualOrderText).toList(),
@@ -122,14 +143,50 @@ public final class BagTabOverlay {
for (RenderedTab tab : tabs) {
if (tab.isHovered(event.getMouseX(), event.getMouseY())) {
PacketDistributor.sendToServer(new OpenBagPayload(tab.entry().slot()));
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
pendingClick = new PendingClick(tab.entry().slot(), event.getMouseX() - tab.x(), event.getMouseY() - tab.y(), tab.pinned());
event.setCanceled(true);
return;
}
}
}
public static void mouseDragged(ScreenEvent.MouseDragged.Pre event) {
if (!(event.getScreen() instanceof AbstractContainerScreen<?> screen) || !supportsTabs(screen) || event.getMouseButton() != 0) {
return;
}
Player player = Minecraft.getInstance().player;
if (player == null) {
return;
}
List<RenderedTab> tabs = getRenderedTabs(screen, player);
if (pendingClick != null && pendingClick.pinned()) {
RenderedTab draggedTab = tabs.stream()
.filter(tab -> tab.entry().slot() == pendingClick.slot())
.findFirst()
.orElse(null);
if (draggedTab != null) {
double dragDistance = Math.hypot(event.getMouseX() - (draggedTab.x() + pendingClick.grabOffsetX()), event.getMouseY() - (draggedTab.y() + pendingClick.grabOffsetY()));
if (dragDistance > 3.0D) {
dragState = new DragState(
pendingClick.slot(),
pendingClick.grabOffsetX(),
TabPinManager.getPinnedIdentityOrder(tabs.stream().map(RenderedTab::entry).toList())
);
pendingClick = null;
}
}
}
if (dragState == null) {
return;
}
updateDragPreview(event.getMouseX(), tabs);
event.setCanceled(true);
}
public static void mouseReleased(ScreenEvent.MouseButtonReleased.Pre event) {
if (!(event.getScreen() instanceof AbstractContainerScreen<?> screen) || !supportsTabs(screen) || event.getButton() != 0) {
return;
@@ -142,6 +199,27 @@ public final class BagTabOverlay {
ItemStack carriedStack = screen.getMenu().getCarried();
if (carriedStack.isEmpty()) {
List<RenderedTab> tabs = getRenderedTabs(screen, player);
if (dragState != null) {
TabPinManager.applyPinnedOrder(dragState.previewOrder(), tabs.stream().map(RenderedTab::entry).toList());
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
dragState = null;
pendingClick = null;
event.setCanceled(true);
return;
}
if (pendingClick != null) {
for (RenderedTab tab : tabs) {
if (tab.entry().slot() == pendingClick.slot() && tab.isHovered(event.getMouseX(), event.getMouseY())) {
PacketDistributor.sendToServer(new OpenBagPayload(tab.entry().slot()));
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
event.setCanceled(true);
break;
}
}
pendingClick = null;
}
return;
}
@@ -174,6 +252,9 @@ public final class BagTabOverlay {
private static List<RenderedTab> getRenderedTabs(AbstractContainerScreen<?> screen, Player player) {
List<BagEntry> bags = TabPinManager.sortTabs(BagAccess.findBags(player));
if (dragState != null) {
bags = applyPreviewOrder(bags, dragState.previewOrder());
}
List<RenderedTab> renderedTabs = new ArrayList<>();
int activeBagSlot = getActiveBagSlot(screen);
int leftBound = getInventoryLeftBound(screen, player);
@@ -195,6 +276,27 @@ public final class BagTabOverlay {
return renderedTabs;
}
private static List<BagEntry> applyPreviewOrder(List<BagEntry> bags, List<String> previewOrder) {
List<BagEntry> pinned = bags.stream().filter(bag -> TabPinManager.isPinned(bag, bags)).toList();
List<BagEntry> unpinned = bags.stream().filter(bag -> !TabPinManager.isPinned(bag, bags)).toList();
List<BagEntry> orderedPinned = new ArrayList<>();
for (String identityKey : previewOrder) {
pinned.stream()
.filter(bag -> bag.identity() != null && identityKey.equals(bag.identity().key()))
.findFirst()
.ifPresent(orderedPinned::add);
}
for (BagEntry bag : pinned) {
if (!orderedPinned.contains(bag)) {
orderedPinned.add(bag);
}
}
List<BagEntry> ordered = new ArrayList<>(orderedPinned);
ordered.addAll(unpinned);
return ordered;
}
private static int getInventoryLeftBound(AbstractContainerScreen<?> screen, Player player) {
return screen.getGuiLeft() + getPlayerInventorySlots(screen, player).stream()
.mapToInt(slot -> slot.x)
@@ -216,7 +318,7 @@ public final class BagTabOverlay {
return slots.isEmpty() ? screen.getMenu().slots : slots;
}
private static void renderTab(GuiGraphics guiGraphics, RenderedTab tab, int mouseX, int mouseY, ItemStack carriedStack) {
private static void renderTab(GuiGraphics guiGraphics, RenderedTab tab, int mouseX, int mouseY, ItemStack carriedStack, boolean dragged) {
boolean hovered = tab.isHovered(mouseX, mouseY);
boolean selected = tab.selected();
int color = DyedItemColor.getOrDefault(tab.entry().stack(), BagItem.DEFAULT_COLOR);
@@ -230,6 +332,11 @@ public final class BagTabOverlay {
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
guiGraphics.blit(TAB_OVERLAY_TEXTURE, tab.x(), tab.y(), uOffset, 0, TAB_WIDTH, TAB_HEIGHT, TAB_WIDTH * 2, TAB_HEIGHT);
if (dragged) {
guiGraphics.fill(tab.x() + 1, tab.y(), tab.x() + TAB_WIDTH - 1, tab.y() + 1, 0xFF000000);
guiGraphics.fill(tab.x() + 2, tab.y() + 1, tab.x() + TAB_WIDTH - 2, tab.y() + 2, 0x80FFFFFF);
}
if (selected) {
guiGraphics.fill(tab.x() + 2, tab.y() + 2, tab.x() + TAB_WIDTH - 2, tab.y() + 4, 0x90FFFFFF);
}
@@ -303,6 +410,7 @@ public final class BagTabOverlay {
lines.add(tab.entry().stack().getHoverName());
lines.add(Component.literal("Left-click: open"));
if (tab.pinned()) {
lines.add(Component.literal("Drag: reorder pinned tabs"));
lines.add(Component.literal("Right-click: unpin"));
} else {
String failureReason = TabPinManager.getPinFailureReason(tab.entry(), tabs.stream().map(RenderedTab::entry).toList());
@@ -315,9 +423,84 @@ public final class BagTabOverlay {
return lines;
}
private static void updateDragPreview(double mouseX, List<RenderedTab> tabs) {
if (dragState == null) {
return;
}
List<RenderedTab> pinnedTabs = tabs.stream().filter(RenderedTab::pinned).toList();
RenderedTab draggedTab = pinnedTabs.stream()
.filter(tab -> tab.entry().slot() == dragState.draggedSlot())
.findFirst()
.orElse(null);
if (draggedTab == null) {
return;
}
List<String> reordered = new ArrayList<>(dragState.previewOrder());
String draggedIdentity = draggedTab.entry().identity().key();
int draggedIndex = reordered.indexOf(draggedIdentity);
if (draggedIndex < 0) {
return;
}
double draggedCenter = mouseX;
boolean changed = false;
while (draggedIndex > 0) {
String leftIdentity = reordered.get(draggedIndex - 1);
RenderedTab leftTab = pinnedTabs.stream()
.filter(tab -> tab.entry().identity() != null && leftIdentity.equals(tab.entry().identity().key()))
.findFirst()
.orElse(null);
if (leftTab == null || draggedCenter >= leftTab.centerX()) {
break;
}
reordered.set(draggedIndex, leftIdentity);
reordered.set(draggedIndex - 1, draggedIdentity);
draggedIndex--;
changed = true;
}
while (draggedIndex < reordered.size() - 1) {
String rightIdentity = reordered.get(draggedIndex + 1);
RenderedTab rightTab = pinnedTabs.stream()
.filter(tab -> tab.entry().identity() != null && rightIdentity.equals(tab.entry().identity().key()))
.findFirst()
.orElse(null);
if (rightTab == null || draggedCenter <= rightTab.centerX()) {
break;
}
reordered.set(draggedIndex, rightIdentity);
reordered.set(draggedIndex + 1, draggedIdentity);
draggedIndex++;
changed = true;
}
if (changed) {
dragState = new DragState(dragState.draggedSlot(), dragState.grabOffsetX(), reordered);
}
}
private record RenderedTab(BagEntry entry, int x, int y, boolean selected, boolean pinned) {
private boolean isHovered(double mouseX, double mouseY) {
return mouseX >= this.x && mouseX < this.x + TAB_WIDTH && mouseY >= this.y && mouseY < this.y + TAB_HEIGHT;
}
private double centerX() {
return this.x + (TAB_WIDTH / 2.0D);
}
private RenderedTab withPosition(int newX, int newY) {
return new RenderedTab(this.entry, newX, newY, this.selected, this.pinned);
}
}
private record PendingClick(int slot, double grabOffsetX, double grabOffsetY, boolean pinned) {
}
private record DragState(int draggedSlot, double grabOffsetX, List<String> previewOrder) {
}
}

View File

@@ -89,6 +89,40 @@ public final class TabPinManager {
return new ToggleResult(true, null);
}
public static List<String> getPinnedIdentityOrder(List<BagEntry> allBags) {
ensureLoaded();
List<String> order = new ArrayList<>();
for (PinnedTab pinnedTab : PINNED_TABS) {
resolvePinnedBag(pinnedTab.identityKey(), allBags).ifPresent(resolved -> order.add(resolved.identity().key()));
}
return order;
}
public static void applyPinnedOrder(List<String> orderedIdentityKeys, List<BagEntry> allBags) {
ensureLoaded();
List<PinnedTab> reordered = new ArrayList<>();
for (String identityKey : orderedIdentityKeys) {
PinnedTab existing = PINNED_TABS.stream()
.filter(tab -> tab.identityKey().equals(identityKey))
.findFirst()
.orElse(null);
BagEntry resolved = resolvePinnedBag(identityKey, allBags).orElse(null);
if (existing != null && resolved != null) {
reordered.add(new PinnedTab(identityKey, existing.stable(), resolved.slot()));
}
}
for (PinnedTab existing : PINNED_TABS) {
if (reordered.stream().noneMatch(tab -> tab.identityKey().equals(existing.identityKey()))) {
reordered.add(existing);
}
}
PINNED_TABS.clear();
PINNED_TABS.addAll(reordered);
save();
}
private static int getPinnedOrder(BagEntry bag, List<BagEntry> allBags) {
for (int i = 0; i < PINNED_TABS.size(); i++) {
BagEntry resolved = resolvePinnedBag(PINNED_TABS.get(i).identityKey(), allBags).orElse(null);