From 85a7e1a48baa37fbebd3750e79411ca188a6c323 Mon Sep 17 00:00:00 2001 From: trunksbomb Date: Sun, 22 Mar 2026 20:30:54 -0400 Subject: [PATCH] reorder pinned bags --- .../com/trunksbomb/bagtabs/BagTabsClient.java | 5 + .../bagtabs/client/BagTabOverlay.java | 195 +++++++++++++++++- .../bagtabs/client/TabPinManager.java | 34 +++ 3 files changed, 228 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java b/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java index 09f5759..bc3a4d5 100644 --- a/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java +++ b/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java @@ -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; diff --git a/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java b/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java index 6a60941..4f1455b 100644 --- a/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java +++ b/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java @@ -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 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 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 getRenderedTabs(AbstractContainerScreen screen, Player player) { List bags = TabPinManager.sortTabs(BagAccess.findBags(player)); + if (dragState != null) { + bags = applyPreviewOrder(bags, dragState.previewOrder()); + } List 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 applyPreviewOrder(List bags, List previewOrder) { + List pinned = bags.stream().filter(bag -> TabPinManager.isPinned(bag, bags)).toList(); + List unpinned = bags.stream().filter(bag -> !TabPinManager.isPinned(bag, bags)).toList(); + List 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 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 tabs) { + if (dragState == null) { + return; + } + + List 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 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 previewOrder) { } } diff --git a/src/main/java/com/trunksbomb/bagtabs/client/TabPinManager.java b/src/main/java/com/trunksbomb/bagtabs/client/TabPinManager.java index d5fe811..cb35882 100644 --- a/src/main/java/com/trunksbomb/bagtabs/client/TabPinManager.java +++ b/src/main/java/com/trunksbomb/bagtabs/client/TabPinManager.java @@ -89,6 +89,40 @@ public final class TabPinManager { return new ToggleResult(true, null); } + public static List getPinnedIdentityOrder(List allBags) { + ensureLoaded(); + List 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 orderedIdentityKeys, List allBags) { + ensureLoaded(); + List 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 allBags) { for (int i = 0; i < PINNED_TABS.size(); i++) { BagEntry resolved = resolvePinnedBag(PINNED_TABS.get(i).identityKey(), allBags).orElse(null);