minecraft-src/net/minecraft/client/gui/components/tabs/TabNavigationBar.java
2025-07-04 03:15:13 +03:00

300 lines
8.9 KiB
Java

package net.minecraft.client.gui.components.tabs;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.ComponentPath;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.components.TabButton;
import net.minecraft.client.gui.components.events.AbstractContainerEventHandler;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.layouts.LinearLayout;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.client.gui.narration.NarratedElementType;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.navigation.FocusNavigationEvent;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.client.gui.navigation.FocusNavigationEvent.TabNavigation;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import org.jetbrains.annotations.Nullable;
@Environment(EnvType.CLIENT)
public class TabNavigationBar extends AbstractContainerEventHandler implements Renderable, NarratableEntry {
private static final int NO_TAB = -1;
private static final int MAX_WIDTH = 400;
private static final int HEIGHT = 24;
private static final int MARGIN = 14;
private static final Component USAGE_NARRATION = Component.translatable("narration.tab_navigation.usage");
private final LinearLayout layout = LinearLayout.horizontal();
private int width;
private final TabManager tabManager;
private final ImmutableList<Tab> tabs;
private final ImmutableList<TabButton> tabButtons;
TabNavigationBar(int width, TabManager tabManager, Iterable<Tab> tabs) {
this.width = width;
this.tabManager = tabManager;
this.tabs = ImmutableList.copyOf(tabs);
this.layout.defaultCellSetting().alignHorizontallyCenter();
ImmutableList.Builder<TabButton> builder = ImmutableList.builder();
for (Tab tab : tabs) {
builder.add(this.layout.addChild(new TabButton(tabManager, tab, 0, 24)));
}
this.tabButtons = builder.build();
}
public static TabNavigationBar.Builder builder(TabManager tabManager, int width) {
return new TabNavigationBar.Builder(tabManager, width);
}
public void setWidth(int width) {
this.width = width;
}
@Override
public boolean isMouseOver(double mouseX, double mouseY) {
return mouseX >= this.layout.getX()
&& mouseY >= this.layout.getY()
&& mouseX < this.layout.getX() + this.layout.getWidth()
&& mouseY < this.layout.getY() + this.layout.getHeight();
}
@Override
public void setFocused(boolean focused) {
super.setFocused(focused);
if (this.getFocused() != null) {
this.getFocused().setFocused(focused);
}
}
@Override
public void setFocused(@Nullable GuiEventListener focused) {
super.setFocused(focused);
if (focused instanceof TabButton tabButton) {
this.tabManager.setCurrentTab(tabButton.tab(), true);
}
}
@Nullable
@Override
public ComponentPath nextFocusPath(FocusNavigationEvent event) {
if (!this.isFocused()) {
TabButton tabButton = this.currentTabButton();
if (tabButton != null) {
return ComponentPath.path(this, ComponentPath.leaf(tabButton));
}
}
return event instanceof TabNavigation ? null : super.nextFocusPath(event);
}
@Override
public List<? extends GuiEventListener> children() {
return this.tabButtons;
}
@Override
public NarratableEntry.NarrationPriority narrationPriority() {
return (NarratableEntry.NarrationPriority)this.tabButtons
.stream()
.map(AbstractWidget::narrationPriority)
.max(Comparator.naturalOrder())
.orElse(NarratableEntry.NarrationPriority.NONE);
}
@Override
public void updateNarration(NarrationElementOutput narrationElementOutput) {
Optional<TabButton> optional = this.tabButtons.stream().filter(AbstractWidget::isHovered).findFirst().or(() -> Optional.ofNullable(this.currentTabButton()));
optional.ifPresent(tabButton -> {
this.narrateListElementPosition(narrationElementOutput.nest(), tabButton);
tabButton.updateNarration(narrationElementOutput);
});
if (this.isFocused()) {
narrationElementOutput.add(NarratedElementType.USAGE, USAGE_NARRATION);
}
}
/**
* Narrates the position of a list element (tab button).
*
* @param narrationElementOutput the narration output to update.
* @param tabButton the tab button whose position is being narrated.
*/
protected void narrateListElementPosition(NarrationElementOutput narrationElementOutput, TabButton tabButton) {
if (this.tabs.size() > 1) {
int i = this.tabButtons.indexOf(tabButton);
if (i != -1) {
narrationElementOutput.add(NarratedElementType.POSITION, Component.translatable("narrator.position.tab", i + 1, this.tabs.size()));
}
}
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
guiGraphics.blit(
RenderType::guiTextured,
Screen.HEADER_SEPARATOR,
0,
this.layout.getY() + this.layout.getHeight() - 2,
0.0F,
0.0F,
((TabButton)this.tabButtons.get(0)).getX(),
2,
32,
2
);
int i = ((TabButton)this.tabButtons.get(this.tabButtons.size() - 1)).getRight();
guiGraphics.blit(RenderType::guiTextured, Screen.HEADER_SEPARATOR, i, this.layout.getY() + this.layout.getHeight() - 2, 0.0F, 0.0F, this.width, 2, 32, 2);
for (TabButton tabButton : this.tabButtons) {
tabButton.render(guiGraphics, mouseX, mouseY, partialTick);
}
}
@Override
public ScreenRectangle getRectangle() {
return this.layout.getRectangle();
}
/**
* Arranges the elements within the tabbed layout.
*/
public void arrangeElements() {
int i = Math.min(400, this.width) - 28;
int j = Mth.roundToward(i / this.tabs.size(), 2);
for (TabButton tabButton : this.tabButtons) {
tabButton.setWidth(j);
}
this.layout.arrangeElements();
this.layout.setX(Mth.roundToward((this.width - i) / 2, 2));
this.layout.setY(0);
}
/**
* Selects the tab at the specified index.
*
* @param index the index of the tab to select.
* @param playClickSound whether to play a click sound when selecting the tab.
*/
public void selectTab(int index, boolean playClickSound) {
if (this.isFocused()) {
this.setFocused((GuiEventListener)this.tabButtons.get(index));
} else {
this.tabManager.setCurrentTab((Tab)this.tabs.get(index), playClickSound);
}
}
/**
* Handles key pressed events.
* <p>
* @return {@code true} if the key press was handled, {@code false} otherwise.
*
* @param keycode the keycode of the pressed key.
*/
public boolean keyPressed(int keycode) {
if (Screen.hasControlDown()) {
int i = this.getNextTabIndex(keycode);
if (i != -1) {
this.selectTab(Mth.clamp(i, 0, this.tabs.size() - 1), true);
return true;
}
}
return false;
}
/**
* Returns the index of the next tab based on the given key code.
* <p>
* @return the index of the next tab, or -1 if no valid tab index is found.
*
* @param keycode the keycode of the pressed key.
*/
private int getNextTabIndex(int keycode) {
if (keycode >= 49 && keycode <= 57) {
return keycode - 49;
} else {
if (keycode == 258) {
int i = this.currentTabIndex();
if (i != -1) {
int j = Screen.hasShiftDown() ? i - 1 : i + 1;
return Math.floorMod(j, this.tabs.size());
}
}
return -1;
}
}
/**
* Returns the index of the current tab.
* <p>
* @return the index of the current tab, or -1 if no current tab is set.
*/
private int currentTabIndex() {
Tab tab = this.tabManager.getCurrentTab();
int i = this.tabs.indexOf(tab);
return i != -1 ? i : -1;
}
/**
* Returns the current tab button.
* <p>
* @return the current tab button, or null if no current tab is set.
*/
@Nullable
private TabButton currentTabButton() {
int i = this.currentTabIndex();
return i != -1 ? (TabButton)this.tabButtons.get(i) : null;
}
/**
* Builder class for creating a TabNavigationBar instance.
*/
@Environment(EnvType.CLIENT)
public static class Builder {
private final int width;
private final TabManager tabManager;
private final List<Tab> tabs = new ArrayList();
Builder(TabManager tabManager, int width) {
this.tabManager = tabManager;
this.width = width;
}
/**
* Adds multiple tabs to the TabNavigationBar.
* <p>
* @return the {@link Builder} instance.
*
* @param tabs the tabs to add.
*/
public TabNavigationBar.Builder addTabs(Tab... tabs) {
Collections.addAll(this.tabs, tabs);
return this;
}
/**
* Builds and returns a new TabNavigationBar instance.
* <p>
* @return a new TabNavigationBar instance.
*/
public TabNavigationBar build() {
return new TabNavigationBar(this.width, this.tabManager, this.tabs);
}
}
}