UI polishes
Implement scrolling tabs
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
BIN
src/main/resources/assets/bagtabs/textures/gui/dock_next.png
Normal file
BIN
src/main/resources/assets/bagtabs/textures/gui/dock_next.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 491 B |
BIN
src/main/resources/assets/bagtabs/textures/gui/dock_prev.png
Normal file
BIN
src/main/resources/assets/bagtabs/textures/gui/dock_prev.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 496 B |
Reference in New Issue
Block a user