tidying up UI and UX
Add dock tab for locking the dock and opening settings, where menu dock location and offset can be moved.
@@ -16,7 +16,9 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -32,8 +34,15 @@ import net.neoforged.neoforge.network.PacketDistributor;
|
||||
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 int TAB_WIDTH = 22;
|
||||
private static final int TAB_HEIGHT = 22;
|
||||
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");
|
||||
private static final ResourceLocation GEAR_ICON_TEXTURE = BagTabs.id("textures/gui/dock_gear.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;
|
||||
private static final int BASE_DOCK_HEIGHT = 22;
|
||||
private static final int BASE_DOCK_ICON_SIZE = 32;
|
||||
private static final int TAB_GAP = 0;
|
||||
private static final int TAB_Y_OFFSET = -3;
|
||||
private static final int TAB_X_OFFSET = -6;
|
||||
@@ -67,9 +76,15 @@ public final class BagTabOverlay {
|
||||
int mouseX = event.getMouseX();
|
||||
int mouseY = event.getMouseY();
|
||||
ItemStack carriedStack = screen.getMenu().getCarried();
|
||||
DockLayout layout = getDockLayout(screen, player, tabs.size());
|
||||
DockControl control = getDockControl(layout);
|
||||
|
||||
refreshInsertTargets(screen, carriedStack);
|
||||
|
||||
if (control != null) {
|
||||
renderDockControl(guiGraphics, control, mouseX, mouseY);
|
||||
}
|
||||
|
||||
RenderedTab draggedTab = null;
|
||||
for (RenderedTab tab : tabs) {
|
||||
if (dragState != null && tab.entry().slot() == dragState.draggedSlot()) {
|
||||
@@ -77,7 +92,7 @@ public final class BagTabOverlay {
|
||||
continue;
|
||||
}
|
||||
renderTab(guiGraphics, tab, mouseX, mouseY, carriedStack, false);
|
||||
guiGraphics.renderItem(tab.entry().stack(), tab.x() + 3, tab.y() + 2);
|
||||
guiGraphics.renderItem(tab.entry().stack(), tab.itemX(), tab.itemY());
|
||||
}
|
||||
|
||||
if (draggedTab != null) {
|
||||
@@ -86,11 +101,24 @@ public final class BagTabOverlay {
|
||||
guiGraphics.pose().pushPose();
|
||||
guiGraphics.pose().translate(0.0F, 0.0F, 200.0F);
|
||||
renderTab(guiGraphics, floatingTab, mouseX, mouseY, carriedStack, true);
|
||||
guiGraphics.renderItem(draggedTab.entry().stack(), dragX + 3, draggedTab.y() + 2);
|
||||
guiGraphics.renderItem(draggedTab.entry().stack(), floatingTab.itemX(), floatingTab.itemY());
|
||||
guiGraphics.pose().popPose();
|
||||
}
|
||||
|
||||
if (dragState == null) {
|
||||
if (control != null) {
|
||||
DockIcon hoveredIcon = control.hoveredIcon(mouseX, mouseY);
|
||||
if (hoveredIcon != null) {
|
||||
guiGraphics.renderTooltip(
|
||||
Minecraft.getInstance().font,
|
||||
List.of(getDockTooltip(hoveredIcon).getVisualOrderText()),
|
||||
mouseX,
|
||||
mouseY
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (RenderedTab tab : tabs) {
|
||||
if (!tab.isHovered(mouseX, mouseY)) {
|
||||
continue;
|
||||
@@ -118,8 +146,32 @@ public final class BagTabOverlay {
|
||||
}
|
||||
|
||||
List<RenderedTab> tabs = getRenderedTabs(screen, player);
|
||||
DockControl control = getDockControl(getDockLayout(screen, player, tabs.size()));
|
||||
|
||||
if (event.getButton() == 0) {
|
||||
if (control != null) {
|
||||
DockIcon clickedIcon = control.hoveredIcon(event.getMouseX(), event.getMouseY());
|
||||
if (clickedIcon == DockIcon.LOCK) {
|
||||
DockConfigManager.toggleInteractionsLocked();
|
||||
dragState = null;
|
||||
pendingClick = null;
|
||||
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
|
||||
event.setCanceled(true);
|
||||
return;
|
||||
}
|
||||
if (clickedIcon == DockIcon.CONFIG) {
|
||||
Minecraft.getInstance().setScreen(new DockConfigScreen(screen));
|
||||
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
|
||||
event.setCanceled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getButton() == 1) {
|
||||
if (DockConfigManager.isInteractionsLocked()) {
|
||||
return;
|
||||
}
|
||||
for (RenderedTab tab : tabs) {
|
||||
if (!tab.isHovered(event.getMouseX(), event.getMouseY())) {
|
||||
continue;
|
||||
@@ -143,7 +195,7 @@ public final class BagTabOverlay {
|
||||
|
||||
for (RenderedTab tab : tabs) {
|
||||
if (tab.isHovered(event.getMouseX(), event.getMouseY())) {
|
||||
pendingClick = new PendingClick(tab.entry().slot(), event.getMouseX() - tab.x(), event.getMouseY() - tab.y(), tab.pinned());
|
||||
pendingClick = new PendingClick(tab.entry().slot(), event.getMouseX() - tab.x(), event.getMouseY() - tab.y(), tab.pinned() && !DockConfigManager.isInteractionsLocked());
|
||||
event.setCanceled(true);
|
||||
return;
|
||||
}
|
||||
@@ -257,11 +309,10 @@ public final class BagTabOverlay {
|
||||
}
|
||||
List<RenderedTab> renderedTabs = new ArrayList<>();
|
||||
int activeBagSlot = getActiveBagSlot(screen);
|
||||
int leftBound = getInventoryLeftBound(screen, player);
|
||||
int rightBound = getInventoryRightBound(screen, player);
|
||||
int x = leftBound + TAB_X_OFFSET;
|
||||
int y = screen.getGuiTop() + screen.getYSize() + TAB_Y_OFFSET;
|
||||
int maxX = rightBound - TAB_WIDTH;
|
||||
DockLayout layout = getDockLayout(screen, player, bags.size());
|
||||
int x = layout.firstTabX();
|
||||
int y = layout.tabY();
|
||||
int maxX = getMaxTabX(screen, player, layout);
|
||||
|
||||
for (BagEntry bag : bags) {
|
||||
if (x > maxX) {
|
||||
@@ -269,8 +320,18 @@ public final class BagTabOverlay {
|
||||
}
|
||||
|
||||
boolean pinned = TabPinManager.isPinned(bag, bags);
|
||||
renderedTabs.add(new RenderedTab(bag, x, y, bag.slot() == activeBagSlot, pinned));
|
||||
x += TAB_WIDTH + TAB_GAP;
|
||||
renderedTabs.add(new RenderedTab(
|
||||
bag,
|
||||
x,
|
||||
y,
|
||||
bag.slot() == activeBagSlot,
|
||||
pinned,
|
||||
x + Math.max(2, Math.round(layout.scale() * 3.0F)),
|
||||
y + Math.max(1, Math.round(layout.scale() * 2.0F)),
|
||||
layout.tabWidth(),
|
||||
layout.tabHeight()
|
||||
));
|
||||
x += layout.tabWidth() + TAB_GAP;
|
||||
}
|
||||
|
||||
return renderedTabs;
|
||||
@@ -321,24 +382,29 @@ public final class BagTabOverlay {
|
||||
private static void renderTab(GuiGraphics guiGraphics, RenderedTab tab, int mouseX, int mouseY, ItemStack carriedStack, boolean dragged) {
|
||||
boolean hovered = tab.isHovered(mouseX, mouseY);
|
||||
boolean selected = tab.selected();
|
||||
int color = DyedItemColor.getOrDefault(tab.entry().stack(), BagItem.DEFAULT_COLOR);
|
||||
float red = ((color >> 16) & 0xFF) / 255.0F;
|
||||
float green = ((color >> 8) & 0xFF) / 255.0F;
|
||||
float blue = (color & 0xFF) / 255.0F;
|
||||
int uOffset = (hovered || selected) ? TAB_WIDTH : 0;
|
||||
float red = 1.0F;
|
||||
float green = 1.0F;
|
||||
float blue = 1.0F;
|
||||
if (tab.entry().stack().has(DataComponents.DYED_COLOR)) {
|
||||
int color = DyedItemColor.getOrDefault(tab.entry().stack(), BagItem.DEFAULT_COLOR);
|
||||
red = ((color >> 16) & 0xFF) / 255.0F;
|
||||
green = ((color >> 8) & 0xFF) / 255.0F;
|
||||
blue = (color & 0xFF) / 255.0F;
|
||||
}
|
||||
int uOffset = (hovered || selected) ? BASE_TAB_WIDTH : 0;
|
||||
|
||||
RenderSystem.setShaderColor(red, green, blue, 1.0F);
|
||||
guiGraphics.blit(TAB_BASE_TEXTURE, tab.x(), tab.y(), uOffset, 0, TAB_WIDTH, TAB_HEIGHT, TAB_WIDTH * 2, TAB_HEIGHT);
|
||||
blitScaled(guiGraphics, 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);
|
||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
guiGraphics.blit(TAB_OVERLAY_TEXTURE, tab.x(), tab.y(), uOffset, 0, TAB_WIDTH, TAB_HEIGHT, TAB_WIDTH * 2, TAB_HEIGHT);
|
||||
blitScaled(guiGraphics, 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);
|
||||
|
||||
if (dragged) {
|
||||
guiGraphics.fill(tab.x() + 1, tab.y(), tab.x() + TAB_WIDTH - 1, tab.y() + 1, 0xFF000000);
|
||||
guiGraphics.fill(tab.x() + 2, tab.y() + 1, tab.x() + TAB_WIDTH - 2, tab.y() + 2, 0x80FFFFFF);
|
||||
guiGraphics.fill(tab.x() + 1, tab.y(), tab.x() + tab.width() - 1, tab.y() + 1, 0xFF000000);
|
||||
guiGraphics.fill(tab.x() + 2, tab.y() + 1, tab.x() + tab.width() - 2, tab.y() + 2, 0x80FFFFFF);
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
guiGraphics.fill(tab.x() + 2, tab.y() + 2, tab.x() + TAB_WIDTH - 2, tab.y() + 4, 0x90FFFFFF);
|
||||
guiGraphics.fill(tab.x() + 3, tab.y() + tab.height() - 4, tab.x() + tab.width() - 3, tab.y() + tab.height() - 2, 0xFFD94A4A);
|
||||
}
|
||||
|
||||
if (tab.pinned()) {
|
||||
@@ -349,9 +415,9 @@ public final class BagTabOverlay {
|
||||
|
||||
if (!carriedStack.isEmpty()) {
|
||||
if (INSERTABLE_SLOTS.contains(tab.entry().slot())) {
|
||||
renderPlusIndicator(guiGraphics, tab.x() + 15, tab.y() + 3);
|
||||
renderPlusIndicator(guiGraphics, tab.x() + tab.width() - 7, tab.y() + 3);
|
||||
} else {
|
||||
renderXIndicator(guiGraphics, tab.x() + 15, tab.y() + 3);
|
||||
renderXIndicator(guiGraphics, tab.x() + tab.width() - 7, tab.y() + 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -410,10 +476,16 @@ public final class BagTabOverlay {
|
||||
lines.add(tab.entry().stack().getHoverName());
|
||||
lines.add(Component.literal("Left-click: open"));
|
||||
if (tab.pinned()) {
|
||||
lines.add(Component.literal("Drag: reorder pinned tabs"));
|
||||
lines.add(Component.literal("Right-click: unpin"));
|
||||
if (!DockConfigManager.isInteractionsLocked()) {
|
||||
lines.add(Component.literal("Drag: reorder pinned tabs"));
|
||||
lines.add(Component.literal("Right-click: unpin"));
|
||||
}
|
||||
} else {
|
||||
String failureReason = TabPinManager.getPinFailureReason(tab.entry(), tabs.stream().map(RenderedTab::entry).toList());
|
||||
if (DockConfigManager.isInteractionsLocked()) {
|
||||
return lines;
|
||||
}
|
||||
|
||||
if (failureReason == null) {
|
||||
lines.add(Component.literal("Right-click: pin"));
|
||||
} else {
|
||||
@@ -484,17 +556,157 @@ public final class BagTabOverlay {
|
||||
}
|
||||
}
|
||||
|
||||
private record RenderedTab(BagEntry entry, int x, int y, boolean selected, boolean pinned) {
|
||||
private static DockLayout getDockLayout(AbstractContainerScreen<?> screen, Player player, int bagCount) {
|
||||
DockConfigManager.DockSettings dockSettings = DockConfigManager.getEffectiveSettings(getScreenKey(screen));
|
||||
float scale = dockSettings.scalePercent() / 100.0F;
|
||||
int tabWidth = Math.max(16, Math.round(BASE_TAB_WIDTH * scale));
|
||||
int tabHeight = Math.max(16, Math.round(BASE_TAB_HEIGHT * scale));
|
||||
int controlWidth = Math.max(14, Math.round(BASE_DOCK_WIDTH * scale));
|
||||
int controlHeight = Math.max(16, Math.round(BASE_DOCK_HEIGHT * scale));
|
||||
int leftBound = getInventoryLeftBound(screen, player);
|
||||
int rightBound = getInventoryRightBound(screen, player);
|
||||
int topBound = getInventoryTopBound(screen, player);
|
||||
int bottomBound = getInventoryBottomBound(screen, player);
|
||||
int controlStripWidth = controlWidth;
|
||||
int firstTabX;
|
||||
int tabY;
|
||||
|
||||
switch (dockSettings.dockSide()) {
|
||||
case TOP -> {
|
||||
firstTabX = leftBound + TAB_X_OFFSET + dockSettings.xOffset() + controlStripWidth;
|
||||
tabY = topBound - tabHeight + dockSettings.yOffset();
|
||||
}
|
||||
case LEFT -> {
|
||||
firstTabX = leftBound - (bagCount * tabWidth) + dockSettings.xOffset() + controlStripWidth - controlStripWidth;
|
||||
tabY = bottomBound - tabHeight + dockSettings.yOffset();
|
||||
}
|
||||
case RIGHT -> {
|
||||
firstTabX = rightBound + dockSettings.xOffset() + controlStripWidth;
|
||||
tabY = bottomBound - tabHeight + dockSettings.yOffset();
|
||||
}
|
||||
case FLOATING -> {
|
||||
int totalWidth = (bagCount * tabWidth) + controlStripWidth;
|
||||
int controlX = (screen.width / 2) - (totalWidth / 2) + dockSettings.xOffset();
|
||||
firstTabX = controlX + controlStripWidth;
|
||||
tabY = (screen.height / 2) + dockSettings.yOffset();
|
||||
}
|
||||
case BOTTOM -> {
|
||||
firstTabX = leftBound + TAB_X_OFFSET + dockSettings.xOffset() + controlStripWidth;
|
||||
tabY = bottomBound + TAB_Y_OFFSET + dockSettings.yOffset() + 8;
|
||||
}
|
||||
default -> {
|
||||
firstTabX = leftBound + TAB_X_OFFSET + dockSettings.xOffset() + controlStripWidth;
|
||||
tabY = bottomBound + TAB_Y_OFFSET + dockSettings.yOffset() + 8;
|
||||
}
|
||||
}
|
||||
|
||||
return new DockLayout(firstTabX, tabY, tabWidth, tabHeight, controlWidth, controlHeight, scale);
|
||||
}
|
||||
|
||||
private static DockControl getDockControl(DockLayout layout) {
|
||||
int y = layout.tabY() + Math.max(0, (layout.tabHeight() - layout.controlHeight()) / 2);
|
||||
int x = layout.firstTabX() - layout.controlWidth() - 2;
|
||||
return new DockControl(x, y, layout.controlWidth(), layout.controlHeight(), layout.scale());
|
||||
}
|
||||
|
||||
private static void renderDockControl(GuiGraphics guiGraphics, DockControl control, int mouseX, int mouseY) {
|
||||
DockIcon hoveredIcon = control.hoveredIcon(mouseX, mouseY);
|
||||
blitScaled(
|
||||
guiGraphics,
|
||||
DOCK_TEXTURE,
|
||||
control.x(),
|
||||
control.y(),
|
||||
control.width(),
|
||||
control.height(),
|
||||
0,
|
||||
0,
|
||||
BASE_DOCK_WIDTH,
|
||||
BASE_DOCK_HEIGHT,
|
||||
BASE_DOCK_WIDTH,
|
||||
BASE_DOCK_HEIGHT
|
||||
);
|
||||
|
||||
if (hoveredIcon != null) {
|
||||
if (hoveredIcon == DockIcon.CONFIG) {
|
||||
guiGraphics.fill(control.x() + 2, control.y() + 1, control.x() + control.width() - 2, control.y() + (control.height() / 2), 0x22FFFFFF);
|
||||
} else {
|
||||
guiGraphics.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()), control.width() - 4));
|
||||
int iconX = control.x() + (control.width() - iconSize) / 2;
|
||||
int topSectionHeight = control.height() / 2;
|
||||
int bottomSectionHeight = control.height() - topSectionHeight;
|
||||
int gearY = control.y() + Math.max(1, (topSectionHeight - iconSize) / 2);
|
||||
int lockY = control.y() + topSectionHeight + Math.max(1, (bottomSectionHeight - iconSize) / 2);
|
||||
blitScaled(guiGraphics, GEAR_ICON_TEXTURE, iconX, gearY, iconSize, iconSize, 0, 0, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE, BASE_DOCK_ICON_SIZE);
|
||||
blitScaled(guiGraphics, DockConfigManager.isInteractionsLocked() ? UNLOCK_ICON_TEXTURE : LOCK_ICON_TEXTURE, iconX, lockY, 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");
|
||||
case LOCK -> DockConfigManager.isInteractionsLocked() ? BagTabs.translation("dock.unlock") : BagTabs.translation("dock.lock");
|
||||
};
|
||||
}
|
||||
|
||||
private static void blitScaled(
|
||||
GuiGraphics guiGraphics,
|
||||
ResourceLocation texture,
|
||||
int x,
|
||||
int y,
|
||||
int width,
|
||||
int height,
|
||||
int u,
|
||||
int v,
|
||||
int regionWidth,
|
||||
int regionHeight,
|
||||
int textureWidth,
|
||||
int textureHeight
|
||||
) {
|
||||
guiGraphics.pose().pushPose();
|
||||
guiGraphics.pose().translate(x, y, 0.0F);
|
||||
guiGraphics.pose().scale(width / (float) regionWidth, height / (float) regionHeight, 1.0F);
|
||||
guiGraphics.blit(texture, 0, 0, u, v, regionWidth, regionHeight, textureWidth, textureHeight);
|
||||
guiGraphics.pose().popPose();
|
||||
}
|
||||
|
||||
private static int getInventoryTopBound(AbstractContainerScreen<?> screen, Player player) {
|
||||
return screen.getGuiTop() + getPlayerInventorySlots(screen, player).stream()
|
||||
.mapToInt(slot -> slot.y)
|
||||
.min()
|
||||
.orElse(84);
|
||||
}
|
||||
|
||||
private static int getInventoryBottomBound(AbstractContainerScreen<?> screen, Player player) {
|
||||
return screen.getGuiTop() + getPlayerInventorySlots(screen, player).stream()
|
||||
.mapToInt(slot -> slot.y + 16)
|
||||
.max()
|
||||
.orElse(screen.getYSize());
|
||||
}
|
||||
|
||||
private static int getMaxTabX(AbstractContainerScreen<?> screen, Player player, DockLayout layout) {
|
||||
int guiRight = screen.getGuiLeft() + screen.getXSize();
|
||||
int inventoryRight = getInventoryRightBound(screen, player);
|
||||
return Math.min(guiRight, inventoryRight) - layout.tabWidth();
|
||||
}
|
||||
|
||||
private static String getScreenKey(Screen screen) {
|
||||
return screen.getClass().getName();
|
||||
}
|
||||
|
||||
private record RenderedTab(BagEntry entry, int x, int y, boolean selected, boolean pinned, int itemX, int itemY, int width, int height) {
|
||||
private boolean isHovered(double mouseX, double mouseY) {
|
||||
return mouseX >= this.x && mouseX < this.x + TAB_WIDTH && mouseY >= this.y && mouseY < this.y + TAB_HEIGHT;
|
||||
return mouseX >= this.x && mouseX < this.x + this.width && mouseY >= this.y && mouseY < this.y + this.height;
|
||||
}
|
||||
|
||||
private double centerX() {
|
||||
return this.x + (TAB_WIDTH / 2.0D);
|
||||
return this.x + (this.width / 2.0D);
|
||||
}
|
||||
|
||||
private RenderedTab withPosition(int newX, int newY) {
|
||||
return new RenderedTab(this.entry, newX, newY, this.selected, this.pinned);
|
||||
return new RenderedTab(this.entry, newX, newY, this.selected, this.pinned, newX + (this.itemX - this.x), newY + (this.itemY - this.y), this.width, this.height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,4 +715,22 @@ public final class BagTabOverlay {
|
||||
|
||||
private record DragState(int draggedSlot, double grabOffsetX, List<String> previewOrder) {
|
||||
}
|
||||
|
||||
private record DockLayout(int firstTabX, int tabY, int tabWidth, int tabHeight, int controlWidth, int controlHeight, float scale) {
|
||||
}
|
||||
|
||||
private record DockControl(int x, int y, int width, int height, float scale) {
|
||||
private DockIcon hoveredIcon(double mouseX, double mouseY) {
|
||||
if (mouseX < this.x || mouseX >= this.x + this.width || mouseY < this.y || mouseY >= this.y + this.height) {
|
||||
return null;
|
||||
}
|
||||
int split = this.y + (this.height / 2);
|
||||
return mouseY < split ? DockIcon.CONFIG : DockIcon.LOCK;
|
||||
}
|
||||
}
|
||||
|
||||
private enum DockIcon {
|
||||
CONFIG,
|
||||
LOCK,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
package com.trunksbomb.bagtabs.client;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.trunksbomb.bagtabs.BagTabs;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ServerData;
|
||||
|
||||
public final class DockConfigManager {
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
private static SessionSettings cachedSettings = new SessionSettings();
|
||||
private static Path cachedPath;
|
||||
|
||||
private DockConfigManager() {
|
||||
}
|
||||
|
||||
public static DockSettings getEffectiveSettings(String screenKey) {
|
||||
SessionSettings settings = getSessionSettings();
|
||||
DockSettings override = settings.screenOverrides.get(screenKey);
|
||||
return override == null ? settings.globalDefault : override;
|
||||
}
|
||||
|
||||
public static DockSettings getEditableSettings(String screenKey, boolean editOverride) {
|
||||
SessionSettings settings = getSessionSettings();
|
||||
if (!editOverride) {
|
||||
return settings.globalDefault;
|
||||
}
|
||||
|
||||
return settings.screenOverrides.getOrDefault(screenKey, settings.globalDefault.copy());
|
||||
}
|
||||
|
||||
public static boolean hasOverride(String screenKey) {
|
||||
return getSessionSettings().screenOverrides.containsKey(screenKey);
|
||||
}
|
||||
|
||||
public static void setEditableSettings(String screenKey, boolean editOverride, DockSettings dockSettings) {
|
||||
SessionSettings settings = getSessionSettings();
|
||||
if (editOverride) {
|
||||
settings.screenOverrides.put(screenKey, dockSettings.copy());
|
||||
} else {
|
||||
settings.globalDefault = dockSettings.copy();
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
public static void clearOverride(String screenKey) {
|
||||
SessionSettings settings = getSessionSettings();
|
||||
settings.screenOverrides.remove(screenKey);
|
||||
save();
|
||||
}
|
||||
|
||||
public static boolean isInteractionsLocked() {
|
||||
return getSessionSettings().interactionsLocked;
|
||||
}
|
||||
|
||||
public static void setInteractionsLocked(boolean locked) {
|
||||
SessionSettings settings = getSessionSettings();
|
||||
settings.interactionsLocked = locked;
|
||||
save();
|
||||
}
|
||||
|
||||
public static void toggleInteractionsLocked() {
|
||||
setInteractionsLocked(!isInteractionsLocked());
|
||||
}
|
||||
|
||||
public static int getRememberedPage(String screenKey) {
|
||||
return getSessionSettings().rememberedPages.getOrDefault(screenKey, 0);
|
||||
}
|
||||
|
||||
public static void setRememberedPage(String screenKey, int page) {
|
||||
SessionSettings settings = getSessionSettings();
|
||||
settings.rememberedPages.put(screenKey, Math.max(0, page));
|
||||
save();
|
||||
}
|
||||
|
||||
private static SessionSettings getSessionSettings() {
|
||||
Path path = getConfigPath();
|
||||
if (!Objects.equals(path, cachedPath)) {
|
||||
cachedPath = path;
|
||||
cachedSettings = load(path);
|
||||
}
|
||||
return cachedSettings;
|
||||
}
|
||||
|
||||
private static SessionSettings load(Path path) {
|
||||
if (!Files.exists(path)) {
|
||||
return new SessionSettings();
|
||||
}
|
||||
|
||||
try (Reader reader = Files.newBufferedReader(path)) {
|
||||
SessionSettings settings = GSON.fromJson(reader, SessionSettings.class);
|
||||
return settings == null ? new SessionSettings() : settings.normalize();
|
||||
} catch (IOException exception) {
|
||||
BagTabs.LOGGER.warn("Failed to load dock config", exception);
|
||||
return new SessionSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private static void save() {
|
||||
if (cachedPath == null) {
|
||||
cachedPath = getConfigPath();
|
||||
}
|
||||
|
||||
try {
|
||||
Files.createDirectories(cachedPath.getParent());
|
||||
try (Writer writer = Files.newBufferedWriter(cachedPath)) {
|
||||
GSON.toJson(cachedSettings, writer);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
BagTabs.LOGGER.warn("Failed to save dock config", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getConfigPath() {
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
String playerKey = minecraft.player != null ? minecraft.player.getUUID().toString() : "unknown-player";
|
||||
String worldKey = "default";
|
||||
if (minecraft.hasSingleplayerServer() && minecraft.getSingleplayerServer() != null) {
|
||||
worldKey = "singleplayer-" + sanitize(minecraft.getSingleplayerServer().getWorldData().getLevelName());
|
||||
} else {
|
||||
ServerData currentServer = minecraft.getCurrentServer();
|
||||
if (currentServer != null) {
|
||||
worldKey = "server-" + sanitize(currentServer.ip);
|
||||
}
|
||||
}
|
||||
|
||||
return minecraft.gameDirectory.toPath()
|
||||
.resolve("config")
|
||||
.resolve("bagtabs")
|
||||
.resolve(playerKey)
|
||||
.resolve(worldKey + ".json");
|
||||
}
|
||||
|
||||
private static String sanitize(String value) {
|
||||
return value == null ? "unknown" : value.replaceAll("[^a-zA-Z0-9._-]", "_");
|
||||
}
|
||||
|
||||
public enum DockSide {
|
||||
BOTTOM,
|
||||
TOP,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
FLOATING;
|
||||
|
||||
public DockSide next() {
|
||||
DockSide[] values = values();
|
||||
return values[(ordinal() + 1) % values.length];
|
||||
}
|
||||
}
|
||||
|
||||
public static final class DockSettings {
|
||||
private DockSide dockSide = DockSide.BOTTOM;
|
||||
private int xOffset = 0;
|
||||
private int yOffset = 0;
|
||||
private int scalePercent = 100;
|
||||
|
||||
public DockSide dockSide() {
|
||||
return dockSide;
|
||||
}
|
||||
|
||||
public int xOffset() {
|
||||
return xOffset;
|
||||
}
|
||||
|
||||
public int yOffset() {
|
||||
return yOffset;
|
||||
}
|
||||
|
||||
public int scalePercent() {
|
||||
return scalePercent;
|
||||
}
|
||||
|
||||
public DockSettings withDockSide(DockSide nextDockSide) {
|
||||
DockSettings copy = copy();
|
||||
copy.dockSide = nextDockSide;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public DockSettings withXOffset(int nextXOffset) {
|
||||
DockSettings copy = copy();
|
||||
copy.xOffset = nextXOffset;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public DockSettings withYOffset(int nextYOffset) {
|
||||
DockSettings copy = copy();
|
||||
copy.yOffset = nextYOffset;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public DockSettings withScalePercent(int nextScalePercent) {
|
||||
DockSettings copy = copy();
|
||||
copy.scalePercent = Math.max(75, Math.min(150, nextScalePercent));
|
||||
return copy;
|
||||
}
|
||||
|
||||
public DockSettings copy() {
|
||||
DockSettings copy = new DockSettings();
|
||||
copy.dockSide = this.dockSide;
|
||||
copy.xOffset = this.xOffset;
|
||||
copy.yOffset = this.yOffset;
|
||||
copy.scalePercent = this.scalePercent;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SessionSettings {
|
||||
private boolean interactionsLocked = false;
|
||||
private DockSettings globalDefault = new DockSettings();
|
||||
private Map<String, DockSettings> screenOverrides = new HashMap<>();
|
||||
private Map<String, Integer> rememberedPages = new HashMap<>();
|
||||
|
||||
private SessionSettings normalize() {
|
||||
if (globalDefault == null) {
|
||||
globalDefault = new DockSettings();
|
||||
}
|
||||
if (screenOverrides == null) {
|
||||
screenOverrides = new HashMap<>();
|
||||
}
|
||||
if (rememberedPages == null) {
|
||||
rememberedPages = new HashMap<>();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.trunksbomb.bagtabs.client;
|
||||
|
||||
import com.trunksbomb.bagtabs.BagTabs;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class DockConfigScreen extends Screen {
|
||||
private final Screen parent;
|
||||
private final String screenKey;
|
||||
private boolean editOverride;
|
||||
private DockConfigManager.DockSettings dockSettings;
|
||||
|
||||
private Button targetButton;
|
||||
private Button sideButton;
|
||||
private Button xMinusButton;
|
||||
private Button xPlusButton;
|
||||
private Button yMinusButton;
|
||||
private Button yPlusButton;
|
||||
private Button scaleMinusButton;
|
||||
private Button scalePlusButton;
|
||||
private Button resetButton;
|
||||
|
||||
public DockConfigScreen(Screen parent) {
|
||||
super(BagTabs.translation("dock.title"));
|
||||
this.parent = parent;
|
||||
this.screenKey = parent.getClass().getName();
|
||||
this.editOverride = DockConfigManager.hasOverride(this.screenKey);
|
||||
this.dockSettings = DockConfigManager.getEditableSettings(this.screenKey, this.editOverride).copy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
int centerX = this.width / 2;
|
||||
int top = this.height / 2 - 70;
|
||||
|
||||
this.targetButton = this.addRenderableWidget(Button.builder(Component.empty(), button -> {
|
||||
saveCurrent();
|
||||
this.editOverride = !this.editOverride;
|
||||
this.dockSettings = DockConfigManager.getEditableSettings(this.screenKey, this.editOverride).copy();
|
||||
syncLabels();
|
||||
}).bounds(centerX - 90, top, 180, 20).build());
|
||||
|
||||
this.sideButton = this.addRenderableWidget(Button.builder(Component.empty(), button -> {
|
||||
this.dockSettings = this.dockSettings.withDockSide(this.dockSettings.dockSide().next());
|
||||
saveCurrent();
|
||||
syncLabels();
|
||||
}).bounds(centerX - 90, top + 26, 180, 20).build());
|
||||
|
||||
this.xMinusButton = this.addRenderableWidget(Button.builder(Component.literal("-"), button -> adjustX(-4))
|
||||
.bounds(centerX - 90, top + 56, 20, 20).build());
|
||||
this.xPlusButton = this.addRenderableWidget(Button.builder(Component.literal("+"), button -> adjustX(4))
|
||||
.bounds(centerX + 70, top + 56, 20, 20).build());
|
||||
|
||||
this.yMinusButton = this.addRenderableWidget(Button.builder(Component.literal("-"), button -> adjustY(-4))
|
||||
.bounds(centerX - 90, top + 82, 20, 20).build());
|
||||
this.yPlusButton = this.addRenderableWidget(Button.builder(Component.literal("+"), button -> adjustY(4))
|
||||
.bounds(centerX + 70, top + 82, 20, 20).build());
|
||||
|
||||
this.scaleMinusButton = this.addRenderableWidget(Button.builder(Component.literal("-"), button -> adjustScale(-5))
|
||||
.bounds(centerX - 90, top + 108, 20, 20).build());
|
||||
this.scalePlusButton = this.addRenderableWidget(Button.builder(Component.literal("+"), button -> adjustScale(5))
|
||||
.bounds(centerX + 70, top + 108, 20, 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());
|
||||
|
||||
this.addRenderableWidget(Button.builder(BagTabs.translation("dock.done"), button -> onClose())
|
||||
.bounds(centerX - 90, top + 164, 180, 20).build());
|
||||
|
||||
syncLabels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
|
||||
this.renderBackground(guiGraphics, mouseX, mouseY, partialTick);
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTick);
|
||||
|
||||
int centerX = this.width / 2;
|
||||
int top = this.height / 2 - 70;
|
||||
guiGraphics.drawCenteredString(this.font, this.title, centerX, top - 18, 0xFFFFFF);
|
||||
guiGraphics.drawCenteredString(this.font, Component.literal("X Offset: " + this.dockSettings.xOffset()), centerX, top + 62, 0xFFFFFF);
|
||||
guiGraphics.drawCenteredString(this.font, Component.literal("Y Offset: " + this.dockSettings.yOffset()), centerX, top + 88, 0xFFFFFF);
|
||||
guiGraphics.drawCenteredString(this.font, Component.literal("Tab Size: " + this.dockSettings.scalePercent() + "%"), centerX, top + 114, 0xFFFFFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
saveCurrent();
|
||||
Minecraft.getInstance().setScreen(this.parent);
|
||||
}
|
||||
|
||||
private void adjustX(int delta) {
|
||||
this.dockSettings = this.dockSettings.withXOffset(this.dockSettings.xOffset() + delta);
|
||||
saveCurrent();
|
||||
syncLabels();
|
||||
}
|
||||
|
||||
private void adjustY(int delta) {
|
||||
this.dockSettings = this.dockSettings.withYOffset(this.dockSettings.yOffset() + delta);
|
||||
saveCurrent();
|
||||
syncLabels();
|
||||
}
|
||||
|
||||
private void adjustScale(int delta) {
|
||||
this.dockSettings = this.dockSettings.withScalePercent(this.dockSettings.scalePercent() + delta);
|
||||
saveCurrent();
|
||||
syncLabels();
|
||||
}
|
||||
|
||||
private void syncLabels() {
|
||||
this.targetButton.setMessage(this.editOverride
|
||||
? BagTabs.translation("dock.target.override")
|
||||
: BagTabs.translation("dock.target.global"));
|
||||
this.sideButton.setMessage(BagTabs.translation("dock.side." + this.dockSettings.dockSide().name().toLowerCase()));
|
||||
this.resetButton.active = this.editOverride || DockConfigManager.hasOverride(this.screenKey);
|
||||
}
|
||||
|
||||
private void saveCurrent() {
|
||||
DockConfigManager.setEditableSettings(this.screenKey, this.editOverride, this.dockSettings);
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,21 @@
|
||||
"bagtabs.tooltip.click_to_open": "Open from your inventory tabs",
|
||||
"bagtabs.gui.bag_namer.name": "New Name",
|
||||
"bagtabs.gui.bag_namer.placeholder": "Leave blank to clear",
|
||||
"bagtabs.gui.bag_namer.rename": "Rename"
|
||||
"bagtabs.gui.bag_namer.rename": "Rename",
|
||||
"bagtabs.dock.title": "Tab Dock Settings",
|
||||
"bagtabs.dock.done": "Done",
|
||||
"bagtabs.dock.reset": "Reset Screen Override",
|
||||
"bagtabs.dock.target.global": "Editing: Global Default",
|
||||
"bagtabs.dock.target.override": "Editing: This Screen Override",
|
||||
"bagtabs.dock.x_offset": "X Offset",
|
||||
"bagtabs.dock.y_offset": "Y Offset",
|
||||
"bagtabs.dock.scale": "Tab Size",
|
||||
"bagtabs.dock.open": "Open dock settings",
|
||||
"bagtabs.dock.lock": "Lock tab interactions",
|
||||
"bagtabs.dock.unlock": "Unlock tab interactions",
|
||||
"bagtabs.dock.side.bottom": "Dock: Bottom",
|
||||
"bagtabs.dock.side.top": "Dock: Top",
|
||||
"bagtabs.dock.side.left": "Dock: Left",
|
||||
"bagtabs.dock.side.right": "Dock: Right",
|
||||
"bagtabs.dock.side.floating": "Dock: Floating"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 426 B |
BIN
src/main/resources/assets/bagtabs/textures/gui/bag_tabs_dock.png
Normal file
|
After Width: | Height: | Size: 429 B |
|
Before Width: | Height: | Size: 227 B After Width: | Height: | Size: 452 B |
BIN
src/main/resources/assets/bagtabs/textures/gui/dock_gear.png
Normal file
|
After Width: | Height: | Size: 592 B |
BIN
src/main/resources/assets/bagtabs/textures/gui/dock_lock.png
Normal file
|
After Width: | Height: | Size: 470 B |
BIN
src/main/resources/assets/bagtabs/textures/gui/dock_unlock.png
Normal file
|
After Width: | Height: | Size: 477 B |