300 lines
8.9 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|