From 966e199a23406a5fedc4589cc2d411912a3b1406 Mon Sep 17 00:00:00 2001 From: trunksbomb Date: Mon, 23 Mar 2026 01:55:51 -0400 Subject: [PATCH] UI polishes add compact mode for all orientations --- .../com/trunksbomb/bagtabs/BagTabsClient.java | 30 ++ .../bagtabs/client/BagTabOverlay.java | 309 ++++++++++++++---- .../bagtabs/client/DockConfigManager.java | 12 + .../bagtabs/client/DockConfigScreen.java | 12 +- .../resources/assets/bagtabs/lang/en_us.json | 6 +- .../textures/gui/bag_tabs_compact_base.png | Bin 0 -> 399 bytes .../textures/gui/bag_tabs_compact_overlay.png | Bin 0 -> 401 bytes .../gui/bag_tabs_compact_side_base.png | Bin 0 -> 408 bytes .../gui/bag_tabs_compact_side_overlay.png | Bin 0 -> 423 bytes 9 files changed, 311 insertions(+), 58 deletions(-) create mode 100644 src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_base.png create mode 100644 src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_overlay.png create mode 100644 src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_side_base.png create mode 100644 src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_side_overlay.png diff --git a/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java b/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java index 5d3d04e..5af9459 100644 --- a/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java +++ b/src/main/java/com/trunksbomb/bagtabs/BagTabsClient.java @@ -3,27 +3,43 @@ package com.trunksbomb.bagtabs; import com.trunksbomb.bagtabs.client.BagScreen; import com.trunksbomb.bagtabs.client.BagNamerScreen; import com.trunksbomb.bagtabs.client.BagTabOverlay; +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.KeyMapping; import net.neoforged.bus.api.IEventBus; import net.minecraft.client.color.item.ItemColor; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.DyedItemColor; import net.neoforged.api.distmarker.Dist; import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.client.event.ClientTickEvent; +import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; import net.neoforged.neoforge.client.event.ScreenEvent; import net.neoforged.neoforge.common.NeoForge; +import org.lwjgl.glfw.GLFW; @Mod(value = BagTabs.MODID, dist = Dist.CLIENT) public class BagTabsClient { + private static final String KEY_CATEGORY = "key.categories.bagtabs"; + private static final KeyMapping OPEN_LAST_BAG = new KeyMapping( + "key.bagtabs.open_last_bag", + InputConstants.Type.KEYSYM, + GLFW.GLFW_KEY_BACKSLASH, + KEY_CATEGORY + ); + public BagTabsClient(IEventBus modEventBus) { modEventBus.addListener(BagTabsClient::registerScreens); modEventBus.addListener(BagTabsClient::registerItemColors); + modEventBus.addListener(BagTabsClient::registerKeyMappings); NeoForge.EVENT_BUS.addListener(BagTabsClient::renderTabs); NeoForge.EVENT_BUS.addListener(BagTabsClient::clickTabs); NeoForge.EVENT_BUS.addListener(BagTabsClient::dragTabs); NeoForge.EVENT_BUS.addListener(BagTabsClient::releaseTabs); + NeoForge.EVENT_BUS.addListener(BagTabsClient::scrollTabs); NeoForge.EVENT_BUS.addListener(BagTabsClient::initScreens); + NeoForge.EVENT_BUS.addListener(BagTabsClient::clientTick); } private static void registerScreens(RegisterMenuScreensEvent event) { @@ -36,6 +52,10 @@ public class BagTabsClient { event.register(bagColor, BagTabs.BAG.get()); } + private static void registerKeyMappings(RegisterKeyMappingsEvent event) { + event.register(OPEN_LAST_BAG); + } + private static void renderTabs(ScreenEvent.Render.Post event) { BagTabOverlay.render(event); } @@ -48,6 +68,10 @@ public class BagTabsClient { BagTabOverlay.mouseReleased(event); } + private static void scrollTabs(ScreenEvent.MouseScrolled.Pre event) { + BagTabOverlay.mouseScrolled(event); + } + private static void dragTabs(ScreenEvent.MouseDragged.Pre event) { BagTabOverlay.mouseDragged(event); } @@ -56,6 +80,12 @@ public class BagTabsClient { BagTabOverlay.screenInit(event); } + private static void clientTick(ClientTickEvent.Post event) { + while (OPEN_LAST_BAG.consumeClick()) { + BagTabOverlay.openLastBag(); + } + } + 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 b53e805..4a2414d 100644 --- a/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java +++ b/src/main/java/com/trunksbomb/bagtabs/client/BagTabOverlay.java @@ -19,16 +19,14 @@ import net.minecraft.Util; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; -import net.minecraft.client.gui.screens.inventory.tooltip.MenuTooltipPositioner; import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.core.component.DataComponents; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.FormattedCharSequence; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.Slot; import net.minecraft.world.item.ItemStack; @@ -40,6 +38,10 @@ import org.lwjgl.glfw.GLFW; public final class BagTabOverlay { private static final ResourceLocation TAB_BASE_TEXTURE = BagTabs.id("textures/gui/bag_tabs_base.png"); private static final ResourceLocation TAB_OVERLAY_TEXTURE = BagTabs.id("textures/gui/bag_tabs_overlay.png"); + private static final ResourceLocation COMPACT_TAB_BASE_TEXTURE = BagTabs.id("textures/gui/bag_tabs_compact_base.png"); + private static final ResourceLocation COMPACT_TAB_OVERLAY_TEXTURE = BagTabs.id("textures/gui/bag_tabs_compact_overlay.png"); + private static final ResourceLocation COMPACT_SIDE_TAB_BASE_TEXTURE = BagTabs.id("textures/gui/bag_tabs_compact_side_base.png"); + private static final ResourceLocation COMPACT_SIDE_TAB_OVERLAY_TEXTURE = BagTabs.id("textures/gui/bag_tabs_compact_side_overlay.png"); private static final ResourceLocation DOCK_TEXTURE = BagTabs.id("textures/gui/bag_tabs_dock.png"); private static final ResourceLocation LOCK_ICON_TEXTURE = BagTabs.id("textures/gui/dock_lock.png"); private static final ResourceLocation UNLOCK_ICON_TEXTURE = BagTabs.id("textures/gui/dock_unlock.png"); @@ -48,6 +50,8 @@ public final class BagTabOverlay { private static final ResourceLocation NEXT_ICON_TEXTURE = BagTabs.id("textures/gui/dock_next.png"); private static final int BASE_TAB_WIDTH = 22; private static final int BASE_TAB_HEIGHT = 22; + private static final int BASE_COMPACT_TAB_WIDTH = 22; + private static final int BASE_COMPACT_TAB_HEIGHT = 11; private static final int BASE_DOCK_WIDTH = 18; private static final int BASE_DOCK_HEIGHT = 22; private static final int BASE_DOCK_ICON_SIZE = 32; @@ -63,6 +67,8 @@ public final class BagTabOverlay { private static DragState dragState; private static PendingCursorRestore pendingCursorRestore; private static long nextAutoScrollAt; + private static String lastOpenedBagKey; + private static int lastOpenedBagSlot = -1; private BagTabOverlay() { } @@ -95,7 +101,9 @@ public final class BagTabOverlay { continue; } renderTab(g, tab, mouseX, mouseY, carried, false); - g.renderItem(tab.entry().stack(), tab.itemX(), tab.itemY()); + if (!tab.compact()) { + g.renderItem(tab.entry().stack(), tab.itemX(), tab.itemY()); + } renderPinOverlay(g, tab); } @@ -118,7 +126,9 @@ public final class BagTabOverlay { g.pose().pushPose(); g.pose().translate(0.0F, 0.0F, 200.0F); renderTab(g, floating, mouseX, mouseY, carried, true); - g.renderItem(dragged.entry().stack(), floating.itemX(), floating.itemY()); + if (!dragged.compact()) { + g.renderItem(dragged.entry().stack(), floating.itemX(), floating.itemY()); + } renderPinOverlay(g, floating); g.pose().popPose(); } @@ -129,13 +139,13 @@ public final class BagTabOverlay { DockIcon hovered = strip.control().hoveredIcon(mouseX, mouseY); if (hovered != null) { - renderManagedTooltip(g, screen, List.of(getDockTooltip(hovered).getVisualOrderText()), mouseX, mouseY); + g.renderTooltip(Minecraft.getInstance().font, List.of(getDockTooltip(hovered).getVisualOrderText()), mouseX, mouseY); return; } ScrollIcon hoveredScroll = hoveredScrollIcon(strip.scrollControl(), mouseX, mouseY); if (hoveredScroll != null) { - renderManagedTooltip(g, screen, List.of(getScrollTooltip(hoveredScroll).getVisualOrderText()), mouseX, mouseY); + g.renderTooltip(Minecraft.getInstance().font, List.of(getScrollTooltip(hoveredScroll).getVisualOrderText()), mouseX, mouseY); return; } @@ -143,7 +153,7 @@ public final class BagTabOverlay { if (!tab.isHovered(mouseX, mouseY)) { continue; } - renderManagedTooltip(g, screen, getTooltipLines(tab, strip.allEntries()).stream().map(Component::getVisualOrderText).toList(), mouseX, mouseY); + g.renderTooltip(Minecraft.getInstance().font, getTooltipLines(tab, strip.allEntries()).stream().map(Component::getVisualOrderText).toList(), mouseX, mouseY); break; } } @@ -247,6 +257,32 @@ public final class BagTabOverlay { event.setCanceled(true); } + public static void mouseScrolled(ScreenEvent.MouseScrolled.Pre event) { + if (!(event.getScreen() instanceof AbstractContainerScreen screen) || !supportsTabs(screen)) { + return; + } + Player player = Minecraft.getInstance().player; + if (player == null) { + return; + } + + TabStrip strip = getTabStrip(screen, player); + if (strip.scrollControl() == null || !isScrollHoverTarget(strip, event.getMouseX(), event.getMouseY())) { + return; + } + + int direction = event.getScrollDeltaY() < 0 ? 1 : -1; + if (direction == 0) { + return; + } + int nextOffset = clamp(strip.scrollOffset() + direction, 0, strip.maxScrollOffset()); + if (nextOffset != strip.scrollOffset()) { + setScrollOffset(screen, nextOffset, strip.maxScrollOffset()); + playScrollSound(); + } + event.setCanceled(true); + } + public static void mouseReleased(ScreenEvent.MouseButtonReleased.Pre event) { if (!(event.getScreen() instanceof AbstractContainerScreen screen) || !supportsTabs(screen) || event.getButton() != 0) { return; @@ -272,6 +308,7 @@ public final class BagTabOverlay { for (RenderedTab tab : tabs) { if (tab.entry().slot() == pendingClick.slot() && tab.isHovered(event.getMouseX(), event.getMouseY())) { scheduleCursorRestore(event.getMouseX(), event.getMouseY()); + rememberOpenedBag(tab.entry()); PacketDistributor.sendToServer(new OpenBagPayload(tab.entry().slot())); Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); event.setCanceled(true); @@ -318,6 +355,33 @@ public final class BagTabOverlay { pendingCursorRestore = null; } + public static void openLastBag() { + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.player == null) { + return; + } + + List bags = TabPinManager.sortTabs(BagAccess.findBags(minecraft.player)); + BagEntry target = null; + if (lastOpenedBagKey != null) { + target = bags.stream().filter(entry -> entry.identity() != null && lastOpenedBagKey.equals(entry.identity().key())).findFirst().orElse(null); + } + if (target == null && lastOpenedBagSlot >= 0 && lastOpenedBagSlot < minecraft.player.getInventory().getContainerSize()) { + final int slot = lastOpenedBagSlot; + target = bags.stream().filter(entry -> entry.slot() == slot).findFirst().orElse(null); + } + if (target == null) { + return; + } + + if (minecraft.screen instanceof AbstractContainerScreen) { + scheduleCursorRestore(minecraft.mouseHandler.xpos(), minecraft.mouseHandler.ypos()); + } + rememberOpenedBag(target); + PacketDistributor.sendToServer(new OpenBagPayload(target.slot())); + minecraft.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } + public static void updateInsertTargets(int requestId, List insertableSlots) { if (requestId != pendingInsertRequestId) { return; @@ -348,7 +412,7 @@ 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())); + rendered.add(new RenderedTab(bag, x, y, bag.slot() == activeBagSlot, TabPinManager.isPinned(bag, bags), layout.dockSide(), layout.scale(), layout.tabWidth(), layout.tabHeight(), layout.compact())); if (layout.vertical()) { y += layout.tabHeight() + TAB_GAP; } else { @@ -359,7 +423,7 @@ public final class BagTabOverlay { DockControl control = getDockControl(layout); ScrollControl scrollControl = overflow ? getScrollControl(layout, renderedCount) : null; - return new TabStrip(bags, rendered, layout, control, scrollControl, scrollOffset, maxScrollOffset); + return new TabStrip(bags, rendered, layout, control, scrollControl, scrollOffset, maxScrollOffset, activeBagSlot); } private static List applyPreviewOrder(List bags, List previewOrder) { @@ -404,30 +468,40 @@ public final class BagTabOverlay { green = ((color >> 8) & 0xFF) / 255.0F; blue = (color & 0xFF) / 255.0F; } - int uOffset = (hovered || selected) ? BASE_TAB_WIDTH : 0; + int baseWidth = tab.baseTextureWidth(); + int baseHeight = tab.baseTextureHeight(); + int uOffset = (hovered || selected) ? baseWidth : 0; + ResourceLocation baseTexture = tab.baseTexture(); + ResourceLocation overlayTexture = tab.overlayTexture(); RenderSystem.setShaderColor(red, green, blue, 1.0F); - blitRotated(g, TAB_BASE_TEXTURE, tab.x(), tab.y(), tab.width(), tab.height(), uOffset, 0, BASE_TAB_WIDTH, BASE_TAB_HEIGHT, BASE_TAB_WIDTH * 2, BASE_TAB_HEIGHT, tab.rotationDegrees()); + blitRotated(g, baseTexture, tab.x(), tab.y(), tab.width(), tab.height(), uOffset, 0, baseWidth, baseHeight, baseWidth * 2, baseHeight, tab.rotationDegrees()); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - blitRotated(g, TAB_OVERLAY_TEXTURE, tab.x(), tab.y(), tab.width(), tab.height(), uOffset, 0, BASE_TAB_WIDTH, BASE_TAB_HEIGHT, BASE_TAB_WIDTH * 2, BASE_TAB_HEIGHT, tab.rotationDegrees()); + blitRotated(g, overlayTexture, tab.x(), tab.y(), tab.width(), tab.height(), uOffset, 0, baseWidth, baseHeight, baseWidth * 2, baseHeight, tab.rotationDegrees()); g.pose().pushPose(); pushTabTransform(g, tab); if (dragged || tab.usesFloatingArt()) { - g.fill(1, 0, BASE_TAB_WIDTH - 1, 1, 0xFF000000); - g.fill(2, 1, BASE_TAB_WIDTH - 2, 2, 0x80FFFFFF); + g.fill(1, 0, baseWidth - 1, 1, 0xFF000000); + if (baseHeight > 2) { + g.fill(2, 1, baseWidth - 2, 2, 0x80FFFFFF); + } } if (selected) { - g.fill(3, BASE_TAB_HEIGHT - 4, BASE_TAB_WIDTH - 3, BASE_TAB_HEIGHT - 2, 0xFFD94A4A); + renderSelectedIndicator(g, tab, baseWidth, baseHeight); } if (!carried.isEmpty()) { if (INSERTABLE_SLOTS.contains(tab.entry().slot())) { - renderPlusIndicator(g, BASE_TAB_WIDTH - 7, 3); + renderPlusIndicator(g, baseWidth - 7, Math.min(3, Math.max(0, baseHeight - 6))); } else { - renderXIndicator(g, BASE_TAB_WIDTH - 7, 3); + renderXIndicator(g, baseWidth - 7, Math.min(3, Math.max(0, baseHeight - 6))); } } g.pose().popPose(); + + if (tab.compact()) { + renderCompactLabel(g, tab); + } } private static boolean supportsTabs(AbstractContainerScreen screen) { @@ -513,10 +587,31 @@ public final class BagTabOverlay { strip.layout().dockSide(), strip.layout().scale(), strip.layout().tabWidth(), - strip.layout().tabHeight() + strip.layout().tabHeight(), + strip.layout().compact() ); } + private static boolean isScrollHoverTarget(TabStrip strip, double mouseX, double mouseY) { + if (strip.scrollControl() != null && hoveredScrollIcon(strip.scrollControl(), mouseX, mouseY) != null) { + return true; + } + if (strip.control().hoveredIcon(mouseX, mouseY) != null) { + return true; + } + for (RenderedTab tab : strip.tabs()) { + if (tab.isHovered(mouseX, mouseY)) { + return true; + } + } + return false; + } + + private static void rememberOpenedBag(BagEntry entry) { + lastOpenedBagSlot = entry.slot(); + lastOpenedBagKey = entry.identity() == null ? null : entry.identity().key(); + } + private static void renderPinOverlay(GuiGraphics g, RenderedTab tab) { if (!tab.pinned()) { return; @@ -528,6 +623,65 @@ public final class BagTabOverlay { g.fill(x + 2, y + 4, x + 3, y + 5, 0xFFD94A4A); } + private static void renderSelectedIndicator(GuiGraphics g, RenderedTab tab, int baseWidth, int baseHeight) { + if (!tab.compact()) { + int highlightTop = Math.max(baseHeight - 3, 1); + int highlightBottom = Math.max(baseHeight - 1, highlightTop + 1); + g.fill(3, highlightTop, baseWidth - 3, highlightBottom, 0xFFD94A4A); + return; + } + + Edge edge = tab.compactLocalOuterEdge(); + switch (edge) { + case TOP -> g.fill(2, 0, baseWidth - 2, 2, 0xFFD94A4A); + case BOTTOM -> g.fill(2, baseHeight - 2, baseWidth - 2, baseHeight, 0xFFD94A4A); + case LEFT -> g.fill(0, 1, 2, baseHeight - 1, 0xFFD94A4A); + case RIGHT -> g.fill(baseWidth - 2, 1, baseWidth, baseHeight - 1, 0xFFD94A4A); + } + } + + private static void renderCompactLabel(GuiGraphics g, RenderedTab tab) { + Minecraft minecraft = Minecraft.getInstance(); + int maxTextWidth = Math.max(8, tab.width() - 4); + List wrapped = minecraft.font.split(tab.entry().stack().getHoverName(), maxTextWidth); + if (wrapped.isEmpty()) { + return; + } + List lines = wrapped.size() > 2 ? wrapped.subList(0, 2) : wrapped; + int widestLine = lines.stream().mapToInt(minecraft.font::width).max().orElse(1); + int lineCount = lines.size(); + int totalHeight = lineCount * minecraft.font.lineHeight; + float scale = Math.min(1.0F, Math.min(maxTextWidth / (float) Math.max(widestLine, 1), (tab.height() - 2) / (float) Math.max(totalHeight, 1))); + float scaledWidth = widestLine * scale; + float scaledHeight = totalHeight * scale; + float startX = tab.x() + ((tab.width() - scaledWidth) / 2.0F); + float startY = tab.y() + ((tab.height() - scaledHeight) / 2.0F); + int textColor = getCompactTextColor(tab); + + g.pose().pushPose(); + g.pose().translate(startX, startY, 0.0F); + g.pose().scale(scale, scale, 1.0F); + int y = 0; + for (FormattedCharSequence line : lines) { + int lineX = (int) ((widestLine - minecraft.font.width(line)) / 2.0F); + g.drawString(minecraft.font, line, lineX, y, textColor, false); + y += minecraft.font.lineHeight; + } + g.pose().popPose(); + } + + private static int getCompactTextColor(RenderedTab tab) { + if (!tab.entry().stack().has(DataComponents.DYED_COLOR)) { + return 0xFF2B2B2B; + } + int color = DyedItemColor.getOrDefault(tab.entry().stack(), BagItem.DEFAULT_COLOR); + int red = (color >> 16) & 0xFF; + int green = (color >> 8) & 0xFF; + int blue = color & 0xFF; + double luminance = (0.2126D * red) + (0.7152D * green) + (0.0722D * blue); + return luminance < 140.0D ? 0xFFF4F0E6 : 0xFF2B2B2B; + } + private static List getTooltipLines(RenderedTab tab, List tabs) { List lines = new ArrayList<>(); lines.add(tab.entry().stack().getHoverName()); @@ -632,8 +786,8 @@ public final class BagTabOverlay { private static DockLayout getDockLayout(AbstractContainerScreen screen, Player player, int bagCount) { DockConfigManager.DockSettings settings = DockConfigManager.getEffectiveSettings(getScreenKey(screen)); float scale = 1.0F; - int tabWidth = BASE_TAB_WIDTH; - int tabHeight = BASE_TAB_HEIGHT; + int tabWidth = settings.compact() ? BASE_COMPACT_TAB_WIDTH : BASE_TAB_WIDTH; + int tabHeight = settings.compact() ? BASE_COMPACT_TAB_HEIGHT : BASE_TAB_HEIGHT; int baseControlWidth = BASE_DOCK_WIDTH; int baseControlHeight = BASE_DOCK_HEIGHT; int inventoryLeftBound = getInventoryLeftBound(screen, player); @@ -659,7 +813,7 @@ public final class BagTabOverlay { firstTabX = inventoryLeftBound + TAB_X_OFFSET + settings.xOffset() + controlWidth; firstTabY = guiTopBound - tabHeight + 1 + settings.yOffset(); controlX = firstTabX - controlWidth - 2; - controlY = firstTabY + Math.max(0, (tabHeight - controlHeight) / 2); + controlY = getHorizontalControlY(firstTabY, tabHeight, controlHeight, settings.dockSide(), settings.compact()); vertical = false; } case LEFT -> { @@ -686,7 +840,7 @@ public final class BagTabOverlay { firstTabX = controlWidth + 2 + settings.xOffset(); firstTabY = screen.height - tabHeight + settings.yOffset(); controlX = settings.xOffset(); - controlY = firstTabY + Math.max(0, (tabHeight - controlHeight) / 2); + controlY = getHorizontalControlY(firstTabY, tabHeight, controlHeight, settings.dockSide(), settings.compact()); vertical = false; } case SCREEN_TOP -> { @@ -695,7 +849,7 @@ public final class BagTabOverlay { firstTabX = controlWidth + 2 + settings.xOffset(); firstTabY = settings.yOffset(); controlX = settings.xOffset(); - controlY = firstTabY + Math.max(0, (tabHeight - controlHeight) / 2); + controlY = getHorizontalControlY(firstTabY, tabHeight, controlHeight, settings.dockSide(), settings.compact()); vertical = false; } case SCREEN_LEFT -> { @@ -723,7 +877,7 @@ public final class BagTabOverlay { controlX = (screen.width / 2) - (totalWidth / 2) + settings.xOffset(); firstTabX = controlX + controlWidth + 2; firstTabY = (screen.height / 2) + settings.yOffset(); - controlY = firstTabY + Math.max(0, (tabHeight - controlHeight) / 2); + controlY = getHorizontalControlY(firstTabY, tabHeight, controlHeight, settings.dockSide(), settings.compact()); vertical = false; } case FLOATING_VERTICAL -> { @@ -742,7 +896,7 @@ public final class BagTabOverlay { firstTabX = inventoryLeftBound + TAB_X_OFFSET + settings.xOffset() + controlWidth; firstTabY = bottomBound + TAB_Y_OFFSET + settings.yOffset() + 8; controlX = firstTabX - controlWidth - 2; - controlY = firstTabY + Math.max(0, (tabHeight - controlHeight) / 2); + controlY = getHorizontalControlY(firstTabY, tabHeight, controlHeight, settings.dockSide(), settings.compact()); vertical = false; } default -> throw new IllegalStateException("Unexpected dock side"); @@ -784,7 +938,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); + 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()); } private static DockControl getDockControl(DockLayout layout) { @@ -802,13 +956,20 @@ public final class BagTabOverlay { layout.dockSide()); } return new ScrollControl(layout.firstTabX() + primarySpan + 2, - layout.firstTabY() + Math.max(0, (layout.tabHeight() - layout.controlHeight()) / 2), + getHorizontalControlY(layout.firstTabY(), layout.tabHeight(), layout.controlHeight(), layout.dockSide(), layout.compact()), layout.controlWidth(), layout.controlHeight(), layout.scale(), layout.dockSide()); } + private static int getHorizontalControlY(int firstTabY, int tabHeight, int controlHeight, DockConfigManager.DockSide dockSide, boolean compact) { + if (compact && (dockSide == DockConfigManager.DockSide.TOP || dockSide == DockConfigManager.DockSide.SCREEN_BOTTOM)) { + return firstTabY + tabHeight - controlHeight; + } + return firstTabY + Math.max(0, (tabHeight - controlHeight) / 2); + } + private static int getVisibleCapacity(AbstractContainerScreen screen, Player player, DockLayout layout, boolean reserveScrollControl) { if (layout.floating()) { return Math.max(1, layout.maxTabs()); @@ -920,16 +1081,6 @@ public final class BagTabOverlay { return control == null ? null : control.hoveredIcon(mouseX, mouseY); } - private static void renderManagedTooltip(GuiGraphics g, AbstractContainerScreen screen, List lines, int mouseX, int mouseY) { - @SuppressWarnings("unchecked") - List formatted = (List) lines; - if (usesDefaultTooltipPlacement(screen)) { - g.renderTooltip(Minecraft.getInstance().font, formatted, mouseX, mouseY); - return; - } - g.renderTooltip(Minecraft.getInstance().font, formatted, createTooltipPositioner(screen), mouseX, mouseY); - } - private static void blitScaled(GuiGraphics g, ResourceLocation texture, int x, int y, int width, int height, int u, int v, int regionWidth, int regionHeight, int textureWidth, int textureHeight) { g.pose().pushPose(); g.pose().translate(x, y, 0.0F); @@ -962,8 +1113,8 @@ public final class BagTabOverlay { private static void pushTabTransform(GuiGraphics g, RenderedTab tab) { g.pose().translate(tab.x() + (tab.width() / 2.0F), tab.y() + (tab.height() / 2.0F), 0.0F); g.pose().mulPose(Axis.ZP.rotationDegrees(tab.rotationDegrees())); - g.pose().scale(tab.width() / (float) BASE_TAB_WIDTH, tab.height() / (float) BASE_TAB_HEIGHT, 1.0F); - g.pose().translate(-(BASE_TAB_WIDTH / 2.0F), -(BASE_TAB_HEIGHT / 2.0F), 0.0F); + g.pose().scale(tab.width() / (float) tab.baseTextureWidth(), tab.height() / (float) tab.baseTextureHeight(), 1.0F); + g.pose().translate(-(tab.baseTextureWidth() / 2.0F), -(tab.baseTextureHeight() / 2.0F), 0.0F); } private static int getInventoryTopBound(AbstractContainerScreen screen, Player player) { @@ -995,22 +1146,11 @@ public final class BagTabOverlay { return screen.getClass().getName(); } - private static ClientTooltipPositioner createTooltipPositioner(AbstractContainerScreen screen) { - return new MenuTooltipPositioner(new ScreenRectangle(screen.getGuiLeft(), screen.getGuiTop(), screen.getXSize(), screen.getYSize())); - } - - private static boolean usesDefaultTooltipPlacement(AbstractContainerScreen screen) { - DockConfigManager.DockSide dockSide = DockConfigManager.getEffectiveSettings(getScreenKey(screen)).dockSide(); - return dockSide == DockConfigManager.DockSide.BOTTOM - || dockSide == DockConfigManager.DockSide.SCREEN_BOTTOM - || dockSide == DockConfigManager.DockSide.FLOATING_HORIZONTAL; - } - private static int clamp(int value, int min, int max) { 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) { + 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 boolean isHovered(double mouseX, double mouseY) { return mouseX >= this.x && mouseX < this.x + this.width && mouseY >= this.y && mouseY < this.y + this.height; } @@ -1018,7 +1158,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); + return new RenderedTab(this.entry, newX, newY, this.selected, this.pinned, this.dockSide, this.scale, this.width, this.height, this.compact); } private boolean isVertical() { return this.dockSide == DockConfigManager.DockSide.LEFT @@ -1028,6 +1168,13 @@ public final class BagTabOverlay { || this.dockSide == DockConfigManager.DockSide.FLOATING_VERTICAL; } private float rotationDegrees() { + if (this.compact) { + return switch (this.dockSide) { + case TOP, SCREEN_BOTTOM -> 180.0F; + case RIGHT, SCREEN_LEFT -> 180.0F; + default -> 0.0F; + }; + } return switch (this.dockSide) { case TOP -> 180.0F; case LEFT -> 90.0F; @@ -1044,6 +1191,40 @@ public final class BagTabOverlay { return this.dockSide == DockConfigManager.DockSide.FLOATING_HORIZONTAL || this.dockSide == DockConfigManager.DockSide.FLOATING_VERTICAL; } + private boolean usesCompactSideArt() { + return this.compact && this.isVertical(); + } + private int baseTextureWidth() { + return this.compact ? BASE_COMPACT_TAB_WIDTH : BASE_TAB_WIDTH; + } + private int baseTextureHeight() { + return this.compact ? BASE_COMPACT_TAB_HEIGHT : BASE_TAB_HEIGHT; + } + private ResourceLocation baseTexture() { + if (!this.compact) { + return TAB_BASE_TEXTURE; + } + return this.usesCompactSideArt() ? COMPACT_SIDE_TAB_BASE_TEXTURE : COMPACT_TAB_BASE_TEXTURE; + } + private ResourceLocation overlayTexture() { + if (!this.compact) { + return TAB_OVERLAY_TEXTURE; + } + return this.usesCompactSideArt() ? COMPACT_SIDE_TAB_OVERLAY_TEXTURE : COMPACT_TAB_OVERLAY_TEXTURE; + } + private Edge compactScreenOuterEdge() { + return switch (this.dockSide) { + case TOP, SCREEN_TOP -> Edge.TOP; + case BOTTOM, SCREEN_BOTTOM -> Edge.BOTTOM; + case LEFT, SCREEN_LEFT -> Edge.LEFT; + case RIGHT, SCREEN_RIGHT -> Edge.RIGHT; + case FLOATING_VERTICAL -> Edge.RIGHT; + default -> Edge.BOTTOM; + }; + } + private Edge compactLocalOuterEdge() { + return this.rotationDegrees() == 180.0F ? this.compactScreenOuterEdge().opposite() : this.compactScreenOuterEdge(); + } private ScreenPoint transformedPoint(int localX, int localY) { double radians = Math.toRadians(this.rotationDegrees()); double centerX = this.x + (this.width / 2.0D); @@ -1070,10 +1251,10 @@ public final class BagTabOverlay { private record DragState(int draggedSlot, double grabOffsetX, double grabOffsetY, List previewOrder) { } - private record TabStrip(List allEntries, List tabs, DockLayout layout, DockControl control, ScrollControl scrollControl, int scrollOffset, int maxScrollOffset) { + private record TabStrip(List allEntries, List 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) { + 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 PendingCursorRestore(double guiX, double guiY, long expiresAt) { @@ -1105,6 +1286,7 @@ public final class BagTabOverlay { case SCREEN_TOP -> 0.0F; case SCREEN_LEFT -> -90.0F; case SCREEN_RIGHT -> 90.0F; + case FLOATING_VERTICAL -> 90.0F; default -> 0.0F; }; } @@ -1138,6 +1320,7 @@ public final class BagTabOverlay { case SCREEN_TOP -> 0.0F; case SCREEN_LEFT -> -90.0F; case SCREEN_RIGHT -> 90.0F; + case FLOATING_VERTICAL -> 90.0F; default -> 0.0F; }; } @@ -1146,6 +1329,22 @@ public final class BagTabOverlay { private record ScreenPoint(int x, int y) { } + private enum Edge { + TOP, + BOTTOM, + LEFT, + RIGHT; + + private Edge opposite() { + return switch (this) { + case TOP -> BOTTOM; + case BOTTOM -> TOP; + case LEFT -> RIGHT; + case RIGHT -> LEFT; + }; + } + } + private enum DockIcon { CONFIG, LOCK diff --git a/src/main/java/com/trunksbomb/bagtabs/client/DockConfigManager.java b/src/main/java/com/trunksbomb/bagtabs/client/DockConfigManager.java index 2586f32..b27a3e6 100644 --- a/src/main/java/com/trunksbomb/bagtabs/client/DockConfigManager.java +++ b/src/main/java/com/trunksbomb/bagtabs/client/DockConfigManager.java @@ -183,6 +183,7 @@ public final class DockConfigManager { private int xOffset = 0; private int yOffset = 0; private int maxTabs = 8; + private boolean compact = false; public DockSide dockSide() { return dockSide; @@ -200,6 +201,10 @@ public final class DockConfigManager { return maxTabs; } + public boolean compact() { + return compact; + } + public DockSettings withDockSide(DockSide nextDockSide) { DockSettings copy = copy(); copy.dockSide = nextDockSide; @@ -224,12 +229,19 @@ public final class DockConfigManager { return copy; } + public DockSettings withCompact(boolean nextCompact) { + DockSettings copy = copy(); + copy.compact = nextCompact; + return copy; + } + public DockSettings copy() { DockSettings copy = new DockSettings(); copy.dockSide = this.dockSide; copy.xOffset = this.xOffset; copy.yOffset = this.yOffset; copy.maxTabs = this.maxTabs; + copy.compact = this.compact; return copy; } diff --git a/src/main/java/com/trunksbomb/bagtabs/client/DockConfigScreen.java b/src/main/java/com/trunksbomb/bagtabs/client/DockConfigScreen.java index ac90f74..11e12a4 100644 --- a/src/main/java/com/trunksbomb/bagtabs/client/DockConfigScreen.java +++ b/src/main/java/com/trunksbomb/bagtabs/client/DockConfigScreen.java @@ -22,6 +22,7 @@ public class DockConfigScreen extends Screen { private Button yPlusButton; private Button maxTabsMinusButton; private Button maxTabsPlusButton; + private Button compactButton; private Button resetButton; public DockConfigScreen(Screen parent) { @@ -65,15 +66,21 @@ public class DockConfigScreen extends Screen { this.maxTabsPlusButton = this.addRenderableWidget(Button.builder(Component.literal("+"), button -> adjustMaxTabs(1)) .bounds(centerX + 70, top + 108, 20, 20).build()); + this.compactButton = this.addRenderableWidget(Button.builder(Component.empty(), button -> { + this.dockSettings = this.dockSettings.withCompact(!this.dockSettings.compact()); + saveCurrent(); + syncLabels(); + }).bounds(centerX - 90, top + 134, 180, 20).build()); + this.resetButton = this.addRenderableWidget(Button.builder(BagTabs.translation("dock.reset"), button -> { DockConfigManager.clearOverride(this.screenKey); this.editOverride = false; this.dockSettings = DockConfigManager.getEditableSettings(this.screenKey, false).copy(); syncLabels(); - }).bounds(centerX - 90, top + 138, 180, 20).build()); + }).bounds(centerX - 90, top + 160, 180, 20).build()); this.addRenderableWidget(Button.builder(BagTabs.translation("dock.done"), button -> onClose()) - .bounds(centerX - 90, top + 164, 180, 20).build()); + .bounds(centerX - 90, top + 186, 180, 20).build()); syncLabels(); Tooltip offsetTooltip = Tooltip.create(BagTabs.translation("dock.offset_steps")); @@ -151,6 +158,7 @@ public class DockConfigScreen extends Screen { ? BagTabs.translation("dock.target.override") : BagTabs.translation("dock.target.global")); this.sideButton.setMessage(BagTabs.translation("dock.side." + this.dockSettings.dockSide().name().toLowerCase())); + this.compactButton.setMessage(BagTabs.translation(this.dockSettings.compact() ? "dock.compact.on" : "dock.compact.off")); this.resetButton.active = this.editOverride || DockConfigManager.hasOverride(this.screenKey); } diff --git a/src/main/resources/assets/bagtabs/lang/en_us.json b/src/main/resources/assets/bagtabs/lang/en_us.json index 4b97a34..bb53ad5 100644 --- a/src/main/resources/assets/bagtabs/lang/en_us.json +++ b/src/main/resources/assets/bagtabs/lang/en_us.json @@ -16,6 +16,8 @@ "bagtabs.dock.x_offset": "X Offset", "bagtabs.dock.y_offset": "Y Offset", "bagtabs.dock.max_tabs": "Max Tabs", + "bagtabs.dock.compact.on": "Layout: Compact", + "bagtabs.dock.compact.off": "Layout: Normal", "bagtabs.dock.open": "Open dock settings", "bagtabs.dock.lock": "Lock tab interactions", "bagtabs.dock.unlock": "Unlock tab interactions", @@ -31,5 +33,7 @@ "bagtabs.dock.side.screen_left": "Dock: Screen Left", "bagtabs.dock.side.screen_right": "Dock: Screen Right", "bagtabs.dock.side.floating_horizontal": "Dock: Floating Horizontal", - "bagtabs.dock.side.floating_vertical": "Dock: Floating Vertical" + "bagtabs.dock.side.floating_vertical": "Dock: Floating Vertical", + "key.categories.bagtabs": "Bag Tabs", + "key.bagtabs.open_last_bag": "Open Last Bag" } diff --git a/src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_base.png b/src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_base.png new file mode 100644 index 0000000000000000000000000000000000000000..a96b3edbcbfade6c7a7bbf469188d5b1a385f1de GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0vp^IzY_L!3HE*bo2Xx6k~CayA#8@b22YMV(E^)jtmSN z`?>!lvI6;>1s;*b3=DjSK$uZf!>a)(C|TkfQ4*Y=R#Ki=l*$m0n3-3i=jR%tV5(=R zXK2{PA$AU^Xh&*iasoR83s9|rk+A{e0*JXFJ6RV%%$f#dg8&oI zWG1l6AWI7%3#!Y|zyKs0^EUAp=ZjUlflO;p7sn6}@3-d^`4|j1SPm|oCI0dM2c??V z7bk?=IUzjVwBgJio-PjN6{{}Bmv6s({Jv(z?56D?VrgZ(IETogS4YD@MtZvXxvX!lvI6;>1s;*b3=DjSK$uZf!>a)(C|TkfQ4*Y=R#Ki=l*$m0n3-3i=jR%tV5(=R zXK2{PA$AU^Xh&*iasoR83s9|rk+A{e0*JXFJ6RV%%$f#dg8&oI zWG1l6AWI7%3#!Y|zyKs0^EUAp=ZjUlflOOZ7sn6}@8pDpgdgV}YCp^mduFt-o2Sg+ zRYFvAAp2#h9;+GG5>8l3u&szZp*-8Qv{>>Y>k5VmXHynz+sWWo$HjHt?tCuDP)}Ds Jmvv4FO#oGHROkQz literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_side_base.png b/src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_side_base.png new file mode 100644 index 0000000000000000000000000000000000000000..223624f5081e4846e4209e1b4218b6ca8176de32 GIT binary patch literal 408 zcmeAS@N?(olHy`uVBq!ia0vp^IzY_L!3HE*bo2Xx6k~CayA#8@b22YMV(E^)jtmSN z`?>!lvI6;>1s;*b3=DjSK$uZf!>a)(C|TkfQ4*Y=R#Ki=l*$m0n3-3i=jR%tV5(=R zXK2{PA$AU^Xh&*iasoR83s9|rk+A{e0*JXFJ6RV%%$f#dg8&oI zWG1l6AWI7%3#!Y|zyKs0^EUAp=ZjUlflL=q7sn6}@3-d^`4|j1SPm|oCI0dM2c??T z23IF+;Z*jI31hfZ8+_sni-M9@eQ0P{Y0j!X-`8?+<_GVp?YPZS{#);dHT$Gyeur!I R-}Zyd_H^}gS?83{1OSiIScCuo literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_side_overlay.png b/src/main/resources/assets/bagtabs/textures/gui/bag_tabs_compact_side_overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..f5af451eecf9eb9e07f82968337671f4380a4e5b GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0vp^IzY_L!3HE*bo2Xx6k~CayA#8@b22YMV(E^)jtmSN z`?>!lvI6;>1s;*b3=DjSK$uZf!>a)(C|TkfQ4*Y=R#Ki=l*$m0n3-3i=jR%tV5(=R zXK2{PA$AU^Xh&*iasoR83s9|rk+A{e0*JXFJ6RV%%$f#dg8&oI zWG1l6AWI7%3#!Y|zyKs0^EUAp=ZjUlfy`h}7sn6}@3&J8`4|*9n7RM|FP3F-o^$)i zL1~3T3n@0voQPQ0km=Qnm)JTS~6@vd$@?2>@vPUE=@% literal 0 HcmV?d00001