UI polishes

Implement scrolling tabs
This commit is contained in:
trunksbomb
2026-03-23 00:41:46 -04:00
parent 638fce4116
commit 56fcb49f4d
4 changed files with 284 additions and 42 deletions

View File

@@ -44,6 +44,8 @@ public final class BagTabOverlay {
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");
private static final ResourceLocation GEAR_ICON_TEXTURE = BagTabs.id("textures/gui/dock_gear.png");
private static final ResourceLocation PREV_ICON_TEXTURE = BagTabs.id("textures/gui/dock_prev.png");
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_DOCK_WIDTH = 18;
@@ -60,6 +62,7 @@ public final class BagTabOverlay {
private static PendingClick pendingClick;
private static DragState dragState;
private static PendingCursorRestore pendingCursorRestore;
private static long nextAutoScrollAt;
private BagTabOverlay() {
}
@@ -74,8 +77,8 @@ public final class BagTabOverlay {
return;
}
List<RenderedTab> tabs = getRenderedTabs(screen, player);
if (tabs.isEmpty()) {
TabStrip strip = getTabStrip(screen, player);
if (strip.tabs().isEmpty()) {
return;
}
@@ -83,12 +86,10 @@ public final class BagTabOverlay {
int mouseX = event.getMouseX();
int mouseY = event.getMouseY();
ItemStack carried = screen.getMenu().getCarried();
DockLayout layout = getDockLayout(screen, player, tabs.size());
DockControl control = getDockControl(layout);
refreshInsertTargets(screen, carried);
RenderedTab dragged = null;
for (RenderedTab tab : tabs) {
RenderedTab dragged = getDraggedTab(strip);
for (RenderedTab tab : strip.tabs()) {
if (dragState != null && tab.entry().slot() == dragState.draggedSlot()) {
dragged = tab;
continue;
@@ -98,14 +99,16 @@ public final class BagTabOverlay {
renderPinOverlay(g, tab);
}
if (layout.dockSide() == DockConfigManager.DockSide.FLOATING_HORIZONTAL
|| layout.dockSide() == DockConfigManager.DockSide.FLOATING_VERTICAL) {
if (strip.layout().dockSide() == DockConfigManager.DockSide.FLOATING_HORIZONTAL
|| strip.layout().dockSide() == DockConfigManager.DockSide.FLOATING_VERTICAL) {
g.pose().pushPose();
g.pose().translate(0.0F, 0.0F, 150.0F);
renderDockControl(g, control, mouseX, mouseY);
renderDockControl(g, strip.control(), mouseX, mouseY);
renderScrollControl(g, strip.scrollControl(), mouseX, mouseY);
g.pose().popPose();
} else {
renderDockControl(g, control, mouseX, mouseY);
renderDockControl(g, strip.control(), mouseX, mouseY);
renderScrollControl(g, strip.scrollControl(), mouseX, mouseY);
}
if (dragged != null) {
@@ -124,17 +127,23 @@ public final class BagTabOverlay {
return;
}
DockIcon hovered = control.hoveredIcon(mouseX, mouseY);
DockIcon hovered = strip.control().hoveredIcon(mouseX, mouseY);
if (hovered != null) {
g.renderTooltip(Minecraft.getInstance().font, List.of(getDockTooltip(hovered).getVisualOrderText()), createTooltipPositioner(screen), mouseX, mouseY);
renderManagedTooltip(g, screen, List.of(getDockTooltip(hovered).getVisualOrderText()), mouseX, mouseY);
return;
}
for (RenderedTab tab : tabs) {
ScrollIcon hoveredScroll = hoveredScrollIcon(strip.scrollControl(), mouseX, mouseY);
if (hoveredScroll != null) {
renderManagedTooltip(g, screen, List.of(getScrollTooltip(hoveredScroll).getVisualOrderText()), mouseX, mouseY);
return;
}
for (RenderedTab tab : strip.tabs()) {
if (!tab.isHovered(mouseX, mouseY)) {
continue;
}
g.renderTooltip(Minecraft.getInstance().font, getTooltipLines(tab, tabs).stream().map(Component::getVisualOrderText).toList(), createTooltipPositioner(screen), mouseX, mouseY);
renderManagedTooltip(g, screen, getTooltipLines(tab, strip.allEntries()).stream().map(Component::getVisualOrderText).toList(), mouseX, mouseY);
break;
}
}
@@ -148,10 +157,9 @@ public final class BagTabOverlay {
return;
}
List<RenderedTab> tabs = getRenderedTabs(screen, player);
DockControl control = getDockControl(getDockLayout(screen, player, tabs.size()));
TabStrip strip = getTabStrip(screen, player);
if (event.getButton() == 0) {
DockIcon clicked = control.hoveredIcon(event.getMouseX(), event.getMouseY());
DockIcon clicked = strip.control().hoveredIcon(event.getMouseX(), event.getMouseY());
if (clicked == DockIcon.LOCK) {
DockConfigManager.toggleInteractionsLocked();
dragState = null;
@@ -166,17 +174,26 @@ public final class BagTabOverlay {
event.setCanceled(true);
return;
}
ScrollIcon scrollIcon = hoveredScrollIcon(strip.scrollControl(), event.getMouseX(), event.getMouseY());
if (scrollIcon != null) {
int offset = strip.scrollOffset() + (scrollIcon == ScrollIcon.NEXT ? 1 : -1);
setScrollOffset(screen, offset, strip.maxScrollOffset());
playScrollSound();
event.setCanceled(true);
return;
}
}
if (event.getButton() == 1) {
if (DockConfigManager.isInteractionsLocked()) {
return;
}
for (RenderedTab tab : tabs) {
for (RenderedTab tab : strip.tabs()) {
if (!tab.isHovered(event.getMouseX(), event.getMouseY())) {
continue;
}
TabPinManager.ToggleResult result = TabPinManager.togglePin(tab.entry(), tabs.stream().map(RenderedTab::entry).toList());
TabPinManager.ToggleResult result = TabPinManager.togglePin(tab.entry(), strip.allEntries());
if (result.changed()) {
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
} else {
@@ -191,7 +208,7 @@ public final class BagTabOverlay {
if (event.getButton() != 0 || !screen.getMenu().getCarried().isEmpty()) {
return;
}
for (RenderedTab tab : tabs) {
for (RenderedTab tab : strip.tabs()) {
if (!tab.isHovered(event.getMouseX(), event.getMouseY())) {
continue;
}
@@ -210,13 +227,14 @@ public final class BagTabOverlay {
return;
}
List<RenderedTab> tabs = getRenderedTabs(screen, player);
TabStrip strip = getTabStrip(screen, player);
List<RenderedTab> tabs = strip.tabs();
if (pendingClick != null && pendingClick.pinned()) {
RenderedTab dragged = tabs.stream().filter(tab -> tab.entry().slot() == pendingClick.slot()).findFirst().orElse(null);
if (dragged != null) {
double distance = Math.hypot(event.getMouseX() - (dragged.x() + pendingClick.grabOffsetX()), event.getMouseY() - (dragged.y() + pendingClick.grabOffsetY()));
if (distance > 3.0D) {
dragState = new DragState(pendingClick.slot(), pendingClick.grabOffsetX(), pendingClick.grabOffsetY(), TabPinManager.getPinnedIdentityOrder(tabs.stream().map(RenderedTab::entry).toList()));
dragState = new DragState(pendingClick.slot(), pendingClick.grabOffsetX(), pendingClick.grabOffsetY(), TabPinManager.getPinnedIdentityOrder(strip.allEntries()));
pendingClick = null;
}
}
@@ -225,6 +243,7 @@ public final class BagTabOverlay {
return;
}
updateDragPreview(event.getMouseX(), event.getMouseY(), tabs);
maybeAutoScrollDuringDrag(screen, strip, event.getMouseX(), event.getMouseY());
event.setCanceled(true);
}
@@ -239,9 +258,10 @@ public final class BagTabOverlay {
ItemStack carried = screen.getMenu().getCarried();
if (carried.isEmpty()) {
List<RenderedTab> tabs = getRenderedTabs(screen, player);
TabStrip strip = getTabStrip(screen, player);
List<RenderedTab> tabs = strip.tabs();
if (dragState != null) {
TabPinManager.applyPinnedOrder(dragState.previewOrder(), tabs.stream().map(RenderedTab::entry).toList());
TabPinManager.applyPinnedOrder(dragState.previewOrder(), strip.allEntries());
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
dragState = null;
pendingClick = null;
@@ -263,7 +283,7 @@ public final class BagTabOverlay {
return;
}
for (RenderedTab tab : getRenderedTabs(screen, player)) {
for (RenderedTab tab : getTabStrip(screen, player).tabs()) {
if (!tab.isHovered(event.getMouseX(), event.getMouseY())) {
continue;
}
@@ -307,26 +327,27 @@ public final class BagTabOverlay {
pendingInsertRequestId = -1;
}
private static List<RenderedTab> getRenderedTabs(AbstractContainerScreen<?> screen, Player player) {
private static TabStrip getTabStrip(AbstractContainerScreen<?> screen, Player player) {
List<BagEntry> bags = TabPinManager.sortTabs(BagAccess.findBags(player));
if (dragState != null) {
bags = applyPreviewOrder(bags, dragState.previewOrder());
}
DockLayout layout = getDockLayout(screen, player, bags.size());
int visibleCapacityWithoutScroll = getVisibleCapacity(screen, player, layout, false);
boolean overflow = bags.size() > Math.min(layout.maxTabs(), visibleCapacityWithoutScroll);
int visibleCount = Math.min(bags.size(), Math.min(layout.maxTabs(), getVisibleCapacity(screen, player, layout, overflow)));
int maxScrollOffset = Math.max(0, bags.size() - visibleCount);
int scrollOffset = clamp(DockConfigManager.getRememberedPage(getScreenKey(screen)), 0, maxScrollOffset);
if (scrollOffset != DockConfigManager.getRememberedPage(getScreenKey(screen))) {
DockConfigManager.setRememberedPage(getScreenKey(screen), scrollOffset);
}
List<RenderedTab> rendered = new ArrayList<>();
int activeBagSlot = getActiveBagSlot(screen);
int x = layout.firstTabX();
int y = layout.firstTabY();
int maxPrimary = getMaxPrimary(screen, player, layout);
int renderedCount = 0;
for (BagEntry bag : bags) {
if (renderedCount >= layout.maxTabs()) {
break;
}
if (!layout.floating() && (layout.vertical() ? y : x) > maxPrimary) {
break;
}
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()));
if (layout.vertical()) {
y += layout.tabHeight() + TAB_GAP;
@@ -335,7 +356,10 @@ public final class BagTabOverlay {
}
renderedCount++;
}
return rendered;
DockControl control = getDockControl(layout);
ScrollControl scrollControl = overflow ? getScrollControl(layout, renderedCount) : null;
return new TabStrip(bags, rendered, layout, control, scrollControl, scrollOffset, maxScrollOffset);
}
private static List<BagEntry> applyPreviewOrder(List<BagEntry> bags, List<String> previewOrder) {
@@ -457,12 +481,42 @@ public final class BagTabOverlay {
pendingCursorRestore = new PendingCursorRestore(guiX, guiY, Util.getMillis() + 1000L);
}
private static void playScrollSound() {
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 0.65F));
}
private static void expirePendingCursorRestore() {
if (pendingCursorRestore != null && Util.getMillis() > pendingCursorRestore.expiresAt()) {
pendingCursorRestore = null;
}
}
private static RenderedTab getDraggedTab(TabStrip strip) {
if (dragState == null) {
return null;
}
RenderedTab visibleDragged = strip.tabs().stream().filter(tab -> tab.entry().slot() == dragState.draggedSlot()).findFirst().orElse(null);
if (visibleDragged != null) {
return visibleDragged;
}
BagEntry draggedEntry = strip.allEntries().stream().filter(entry -> entry.slot() == dragState.draggedSlot()).findFirst().orElse(null);
if (draggedEntry == null) {
return null;
}
int activeBagSlot = strip.tabs().stream().filter(RenderedTab::selected).mapToInt(tab -> tab.entry().slot()).findFirst().orElse(Integer.MIN_VALUE);
return new RenderedTab(
draggedEntry,
strip.layout().firstTabX(),
strip.layout().firstTabY(),
draggedEntry.slot() == activeBagSlot,
TabPinManager.isPinned(draggedEntry, strip.allEntries()),
strip.layout().dockSide(),
strip.layout().scale(),
strip.layout().tabWidth(),
strip.layout().tabHeight()
);
}
private static void renderPinOverlay(GuiGraphics g, RenderedTab tab) {
if (!tab.pinned()) {
return;
@@ -474,7 +528,7 @@ public final class BagTabOverlay {
g.fill(x + 2, y + 4, x + 3, y + 5, 0xFFD94A4A);
}
private static List<Component> getTooltipLines(RenderedTab tab, List<RenderedTab> tabs) {
private static List<Component> getTooltipLines(RenderedTab tab, List<BagEntry> tabs) {
List<Component> lines = new ArrayList<>();
lines.add(tab.entry().stack().getHoverName());
lines.add(Component.literal("Left-click: open"));
@@ -484,7 +538,7 @@ public final class BagTabOverlay {
lines.add(Component.literal("Right-click: unpin"));
}
} else {
String failureReason = TabPinManager.getPinFailureReason(tab.entry(), tabs.stream().map(RenderedTab::entry).toList());
String failureReason = TabPinManager.getPinFailureReason(tab.entry(), tabs);
if (DockConfigManager.isInteractionsLocked()) {
return lines;
}
@@ -543,6 +597,38 @@ public final class BagTabOverlay {
}
}
private static void maybeAutoScrollDuringDrag(AbstractContainerScreen<?> screen, TabStrip strip, double mouseX, double mouseY) {
if (dragState == null || strip.scrollControl() == null || strip.tabs().isEmpty()) {
return;
}
long now = Util.getMillis();
if (now < nextAutoScrollAt) {
return;
}
RenderedTab firstTab = strip.tabs().getFirst();
RenderedTab lastTab = strip.tabs().get(strip.tabs().size() - 1);
double primary = strip.layout().vertical() ? mouseY : mouseX;
double leadingThreshold = strip.layout().vertical() ? firstTab.y() + 4.0D : firstTab.x() + 4.0D;
double trailingThreshold = strip.layout().vertical() ? lastTab.y() + lastTab.height() - 4.0D : lastTab.x() + lastTab.width() - 4.0D;
int dragScrollLimit = getPinnedDragScrollLimit(strip);
if (primary <= leadingThreshold && strip.scrollOffset() > 0) {
setScrollOffset(screen, strip.scrollOffset() - 1, dragScrollLimit);
playScrollSound();
nextAutoScrollAt = now + 125L;
} else if (primary >= trailingThreshold && strip.scrollOffset() < dragScrollLimit) {
setScrollOffset(screen, strip.scrollOffset() + 1, dragScrollLimit);
playScrollSound();
nextAutoScrollAt = now + 125L;
}
}
private static int getPinnedDragScrollLimit(TabStrip strip) {
int pinnedCount = (int) strip.allEntries().stream().filter(entry -> TabPinManager.isPinned(entry, strip.allEntries())).count();
return Math.max(0, pinnedCount - strip.tabs().size());
}
private static DockLayout getDockLayout(AbstractContainerScreen<?> screen, Player player, int bagCount) {
DockConfigManager.DockSettings settings = DockConfigManager.getEffectiveSettings(getScreenKey(screen));
float scale = 1.0F;
@@ -556,6 +642,8 @@ public final class BagTabOverlay {
int guiLeftBound = screen.getGuiLeft();
int guiRightBound = screen.getGuiLeft() + screen.getXSize();
int guiTopBound = screen.getGuiTop();
int visibleBagCount = Math.min(bagCount, settings.maxTabs());
boolean floatingOverflow = bagCount > settings.maxTabs();
int firstTabX;
int firstTabY;
int controlX;
@@ -631,7 +719,7 @@ public final class BagTabOverlay {
case FLOATING_HORIZONTAL -> {
controlWidth = baseControlWidth;
controlHeight = baseControlHeight;
int totalWidth = (Math.min(bagCount, settings.maxTabs()) * tabWidth) + controlWidth;
int totalWidth = controlWidth + 2 + (visibleBagCount * tabWidth) + (floatingOverflow ? controlWidth + 2 : 0);
controlX = (screen.width / 2) - (totalWidth / 2) + settings.xOffset();
firstTabX = controlX + controlWidth + 2;
firstTabY = (screen.height / 2) + settings.yOffset();
@@ -641,7 +729,7 @@ public final class BagTabOverlay {
case FLOATING_VERTICAL -> {
controlWidth = baseControlHeight;
controlHeight = baseControlWidth;
int totalHeight = (Math.min(bagCount, settings.maxTabs()) * tabHeight) + controlHeight;
int totalHeight = controlHeight + 2 + (visibleBagCount * tabHeight) + (floatingOverflow ? controlHeight + 2 : 0);
controlX = (screen.width / 2) - (tabWidth / 2) + settings.xOffset() + Math.max(0, (tabWidth - controlWidth) / 2);
controlY = (screen.height / 2) - (totalHeight / 2) + settings.yOffset();
firstTabX = (screen.width / 2) - (tabWidth / 2) + settings.xOffset();
@@ -661,7 +749,7 @@ public final class BagTabOverlay {
}
if (settings.dockSide() == DockConfigManager.DockSide.FLOATING_HORIZONTAL) {
int totalWidth = (Math.min(bagCount, settings.maxTabs()) * tabWidth) + controlWidth + 2;
int totalWidth = controlWidth + 2 + (visibleBagCount * tabWidth) + (floatingOverflow ? controlWidth + 2 : 0);
int minControlX = 0;
int maxControlX = Math.max(0, screen.width - totalWidth);
int minTabY = 0;
@@ -678,7 +766,7 @@ public final class BagTabOverlay {
controlY = firstTabY + Math.max(0, (tabHeight - controlHeight) / 2);
}
} else if (settings.dockSide() == DockConfigManager.DockSide.FLOATING_VERTICAL) {
int totalHeight = (Math.min(bagCount, settings.maxTabs()) * tabHeight) + controlHeight + 2;
int totalHeight = controlHeight + 2 + (visibleBagCount * tabHeight) + (floatingOverflow ? controlHeight + 2 : 0);
int minTabX = 0;
int maxTabX = Math.max(0, screen.width - tabWidth);
int minControlY = 0;
@@ -703,6 +791,40 @@ public final class BagTabOverlay {
return new DockControl(layout.controlX(), layout.controlY(), layout.controlWidth(), layout.controlHeight(), layout.scale(), layout.dockSide());
}
private static ScrollControl getScrollControl(DockLayout layout, int visibleCount) {
int primarySpan = visibleCount <= 0 ? 0 : visibleCount * (layout.vertical() ? layout.tabHeight() : layout.tabWidth());
if (layout.vertical()) {
return new ScrollControl(layout.firstTabX() + Math.max(0, (layout.tabWidth() - layout.controlWidth()) / 2),
layout.firstTabY() + primarySpan + 2,
layout.controlWidth(),
layout.controlHeight(),
layout.scale(),
layout.dockSide());
}
return new ScrollControl(layout.firstTabX() + primarySpan + 2,
layout.firstTabY() + Math.max(0, (layout.tabHeight() - layout.controlHeight()) / 2),
layout.controlWidth(),
layout.controlHeight(),
layout.scale(),
layout.dockSide());
}
private static int getVisibleCapacity(AbstractContainerScreen<?> screen, Player player, DockLayout layout, boolean reserveScrollControl) {
if (layout.floating()) {
return Math.max(1, layout.maxTabs());
}
int startPrimary = layout.vertical() ? layout.firstTabY() : layout.firstTabX();
int maxPrimary = getMaxPrimary(screen, player, layout);
int tabPrimary = layout.vertical() ? layout.tabHeight() : layout.tabWidth();
int availablePrimary = Math.max(0, maxPrimary - startPrimary + tabPrimary);
int reservedPrimary = reserveScrollControl ? (layout.vertical() ? layout.controlHeight() : layout.controlWidth()) + 2 : 0;
return Math.max(0, (availablePrimary - reservedPrimary) / tabPrimary);
}
private static void setScrollOffset(AbstractContainerScreen<?> screen, int scrollOffset, int maxScrollOffset) {
DockConfigManager.setRememberedPage(getScreenKey(screen), clamp(scrollOffset, 0, maxScrollOffset));
}
private static void renderDockControl(GuiGraphics g, DockControl control, int mouseX, int mouseY) {
DockIcon hovered = control.hoveredIcon(mouseX, mouseY);
blitRotated(g, DOCK_TEXTURE, control.x(), control.y(), control.width(), control.height(), 0, 0, BASE_DOCK_WIDTH, BASE_DOCK_HEIGHT, BASE_DOCK_WIDTH, BASE_DOCK_HEIGHT, control.rotationDegrees());
@@ -740,6 +862,46 @@ public final class BagTabOverlay {
}
}
private static void renderScrollControl(GuiGraphics g, ScrollControl control, int mouseX, int mouseY) {
if (control == null) {
return;
}
ScrollIcon hovered = control.hoveredIcon(mouseX, mouseY);
blitRotated(g, DOCK_TEXTURE, control.x(), control.y(), control.width(), control.height(), 0, 0, BASE_DOCK_WIDTH, BASE_DOCK_HEIGHT, BASE_DOCK_WIDTH, BASE_DOCK_HEIGHT, control.rotationDegrees());
if (hovered != null) {
if (control.horizontalButtons()) {
if (hovered == ScrollIcon.PREV) {
g.fill(control.x() + 1, control.y() + 2, control.x() + (control.width() / 2), control.y() + control.height() - 2, 0x22FFFFFF);
} else {
g.fill(control.x() + (control.width() / 2), control.y() + 2, control.x() + control.width() - 1, control.y() + control.height() - 2, 0x22FFFFFF);
}
} else if (hovered == ScrollIcon.PREV) {
g.fill(control.x() + 2, control.y() + 1, control.x() + control.width() - 2, control.y() + (control.height() / 2), 0x22FFFFFF);
} else {
g.fill(control.x() + 2, control.y() + (control.height() / 2), control.x() + control.width() - 2, control.y() + control.height() - 3, 0x22FFFFFF);
}
}
int iconSize = Math.max(6, Math.min(Math.round(8 * control.scale()), Math.min(control.width(), control.height()) - 4));
if (control.horizontalButtons()) {
int leftWidth = control.width() / 2;
int rightWidth = control.width() - leftWidth;
int iconY = control.y() + (control.height() - iconSize) / 2;
int prevX = control.x() + Math.max(1, (leftWidth - iconSize) / 2);
int nextX = control.x() + leftWidth + Math.max(1, (rightWidth - iconSize) / 2);
blitScaledRotated(g, PREV_ICON_TEXTURE, prevX, iconY, iconSize, 90.0F);
blitScaledRotated(g, NEXT_ICON_TEXTURE, nextX, iconY, iconSize, 90.0F);
} else {
int iconX = control.x() + (control.width() - iconSize) / 2;
int topHeight = control.height() / 2;
int bottomHeight = control.height() - topHeight;
int prevY = control.y() + Math.max(1, (topHeight - iconSize) / 2);
int nextY = control.y() + topHeight + Math.max(1, (bottomHeight - iconSize) / 2);
blitScaled(g, PREV_ICON_TEXTURE, iconX, prevY, iconSize, iconSize, 0, 0, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE);
blitScaled(g, NEXT_ICON_TEXTURE, iconX, nextY, iconSize, iconSize, 0, 0, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE);
}
}
private static Component getDockTooltip(DockIcon icon) {
return switch (icon) {
case CONFIG -> BagTabs.translation("dock.open");
@@ -747,6 +909,27 @@ public final class BagTabOverlay {
};
}
private static Component getScrollTooltip(ScrollIcon icon) {
return switch (icon) {
case PREV -> BagTabs.translation("dock.scroll_prev");
case NEXT -> BagTabs.translation("dock.scroll_next");
};
}
private static ScrollIcon hoveredScrollIcon(ScrollControl control, double mouseX, double mouseY) {
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<net.minecraft.util.FormattedCharSequence> formatted = (List<net.minecraft.util.FormattedCharSequence>) 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);
@@ -755,6 +938,15 @@ public final class BagTabOverlay {
g.pose().popPose();
}
private static void blitScaledRotated(GuiGraphics g, ResourceLocation texture, int x, int y, int size, float rotationDegrees) {
g.pose().pushPose();
g.pose().translate(x + (size / 2.0F), y + (size / 2.0F), 0.0F);
g.pose().mulPose(Axis.ZP.rotationDegrees(rotationDegrees));
g.pose().scale(size / (float) BASE_DOCK_ICON_SIZE, size / (float) BASE_DOCK_ICON_SIZE, 1.0F);
g.blit(texture, -(BASE_DOCK_ICON_SIZE / 2), -(BASE_DOCK_ICON_SIZE / 2), 0, 0, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE);
g.pose().popPose();
}
private static void blitRotated(GuiGraphics g, ResourceLocation texture, int x, int y, int width, int height, int u, int v, int regionWidth, int regionHeight, int textureWidth, int textureHeight, float rotationDegrees) {
boolean quarterTurn = Math.abs(Math.round(rotationDegrees)) % 180 == 90;
int rotatedWidth = quarterTurn ? regionHeight : regionWidth;
@@ -807,6 +999,13 @@ public final class BagTabOverlay {
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));
}
@@ -871,6 +1070,9 @@ public final class BagTabOverlay {
private record DragState(int draggedSlot, double grabOffsetX, double grabOffsetY, List<String> previewOrder) {
}
private record TabStrip(List<BagEntry> allEntries, List<RenderedTab> tabs, DockLayout layout, DockControl control, ScrollControl scrollControl, int scrollOffset, int maxScrollOffset) {
}
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) {
}
@@ -908,6 +1110,39 @@ public final class BagTabOverlay {
}
}
private record ScrollControl(int x, int y, int width, int height, float scale, DockConfigManager.DockSide dockSide) {
private ScrollIcon hoveredIcon(double mouseX, double mouseY) {
if (mouseX < this.x || mouseX >= this.x + this.width || mouseY < this.y || mouseY >= this.y + this.height) {
return null;
}
if (horizontalButtons()) {
return mouseX < this.x + (this.width / 2) ? ScrollIcon.PREV : ScrollIcon.NEXT;
}
return mouseY < this.y + (this.height / 2) ? ScrollIcon.PREV : ScrollIcon.NEXT;
}
private boolean horizontalButtons() {
return this.dockSide == DockConfigManager.DockSide.LEFT
|| this.dockSide == DockConfigManager.DockSide.RIGHT
|| this.dockSide == DockConfigManager.DockSide.SCREEN_LEFT
|| this.dockSide == DockConfigManager.DockSide.SCREEN_RIGHT
|| this.dockSide == DockConfigManager.DockSide.FLOATING_VERTICAL;
}
private float rotationDegrees() {
return switch (this.dockSide) {
case TOP -> 180.0F;
case LEFT -> 90.0F;
case RIGHT -> -90.0F;
case SCREEN_BOTTOM -> 180.0F;
case SCREEN_TOP -> 0.0F;
case SCREEN_LEFT -> -90.0F;
case SCREEN_RIGHT -> 90.0F;
default -> 0.0F;
};
}
}
private record ScreenPoint(int x, int y) {
}
@@ -915,4 +1150,9 @@ public final class BagTabOverlay {
CONFIG,
LOCK
}
private enum ScrollIcon {
PREV,
NEXT
}
}

View File

@@ -19,6 +19,8 @@
"bagtabs.dock.open": "Open dock settings",
"bagtabs.dock.lock": "Lock tab interactions",
"bagtabs.dock.unlock": "Unlock tab interactions",
"bagtabs.dock.scroll_prev": "Show previous tabs",
"bagtabs.dock.scroll_next": "Show more tabs",
"bagtabs.dock.offset_steps": "Click: 1, Shift: 5, Ctrl: 25, Ctrl+Shift: 100",
"bagtabs.dock.side.bottom": "Dock: Bottom",
"bagtabs.dock.side.top": "Dock: Top",

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B