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

446 lines
13 KiB
Java

package net.minecraft.client.gui.components;
import com.google.common.collect.Lists;
import java.util.AbstractList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.events.ContainerEventHandler;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
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.ScreenDirection;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import org.jetbrains.annotations.Nullable;
@Environment(EnvType.CLIENT)
public abstract class AbstractSelectionList<E extends AbstractSelectionList.Entry<E>> extends AbstractContainerWidget {
private static final ResourceLocation MENU_LIST_BACKGROUND = ResourceLocation.withDefaultNamespace("textures/gui/menu_list_background.png");
private static final ResourceLocation INWORLD_MENU_LIST_BACKGROUND = ResourceLocation.withDefaultNamespace("textures/gui/inworld_menu_list_background.png");
protected final Minecraft minecraft;
protected final int itemHeight;
private final List<E> children = new AbstractSelectionList.TrackedList();
protected boolean centerListVertically = true;
private boolean renderHeader;
protected int headerHeight;
@Nullable
private E selected;
@Nullable
private E hovered;
public AbstractSelectionList(Minecraft minecraft, int width, int height, int y, int itemHeight) {
super(0, y, width, height, CommonComponents.EMPTY);
this.minecraft = minecraft;
this.itemHeight = itemHeight;
}
public AbstractSelectionList(Minecraft minecraft, int width, int height, int y, int itemHeight, int headerHeight) {
this(minecraft, width, height, y, itemHeight);
this.renderHeader = true;
this.headerHeight = headerHeight;
}
@Nullable
public E getSelected() {
return this.selected;
}
public void setSelectedIndex(int selected) {
if (selected == -1) {
this.setSelected(null);
} else if (this.getItemCount() != 0) {
this.setSelected(this.getEntry(selected));
}
}
public void setSelected(@Nullable E selected) {
this.selected = selected;
}
public E getFirstElement() {
return (E)this.children.get(0);
}
@Nullable
public E getFocused() {
return (E)super.getFocused();
}
@Override
public final List<E> children() {
return this.children;
}
protected void clearEntries() {
this.children.clear();
this.selected = null;
}
public void replaceEntries(Collection<E> entries) {
this.clearEntries();
this.children.addAll(entries);
}
protected E getEntry(int index) {
return (E)this.children().get(index);
}
protected int addEntry(E entry) {
this.children.add(entry);
return this.children.size() - 1;
}
protected void addEntryToTop(E entry) {
double d = this.maxScrollAmount() - this.scrollAmount();
this.children.add(0, entry);
this.setScrollAmount(this.maxScrollAmount() - d);
}
protected boolean removeEntryFromTop(E entry) {
double d = this.maxScrollAmount() - this.scrollAmount();
boolean bl = this.removeEntry(entry);
this.setScrollAmount(this.maxScrollAmount() - d);
return bl;
}
protected int getItemCount() {
return this.children().size();
}
protected boolean isSelectedItem(int index) {
return Objects.equals(this.getSelected(), this.children().get(index));
}
@Nullable
protected final E getEntryAtPosition(double mouseX, double mouseY) {
int i = this.getRowWidth() / 2;
int j = this.getX() + this.width / 2;
int k = j - i;
int l = j + i;
int m = Mth.floor(mouseY - this.getY()) - this.headerHeight + (int)this.scrollAmount() - 4;
int n = m / this.itemHeight;
return (E)(mouseX >= k && mouseX <= l && n >= 0 && m >= 0 && n < this.getItemCount() ? this.children().get(n) : null);
}
public void updateSize(int width, HeaderAndFooterLayout layout) {
this.updateSizeAndPosition(width, layout.getContentHeight(), layout.getHeaderHeight());
}
public void updateSizeAndPosition(int width, int height, int y) {
this.setSize(width, height);
this.setPosition(0, y);
this.refreshScrollAmount();
}
@Override
protected int contentHeight() {
return this.getItemCount() * this.itemHeight + this.headerHeight + 4;
}
protected void renderHeader(GuiGraphics guiGraphics, int x, int y) {
}
protected void renderDecorations(GuiGraphics guiGraphics, int mouseX, int mouseY) {
}
@Override
public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
this.hovered = this.isMouseOver(mouseX, mouseY) ? this.getEntryAtPosition(mouseX, mouseY) : null;
this.renderListBackground(guiGraphics);
this.enableScissor(guiGraphics);
if (this.renderHeader) {
int i = this.getRowLeft();
int j = this.getY() + 4 - (int)this.scrollAmount();
this.renderHeader(guiGraphics, i, j);
}
this.renderListItems(guiGraphics, mouseX, mouseY, partialTick);
guiGraphics.disableScissor();
this.renderListSeparators(guiGraphics);
this.renderScrollbar(guiGraphics);
this.renderDecorations(guiGraphics, mouseX, mouseY);
}
protected void renderListSeparators(GuiGraphics guiGraphics) {
ResourceLocation resourceLocation = this.minecraft.level == null ? Screen.HEADER_SEPARATOR : Screen.INWORLD_HEADER_SEPARATOR;
ResourceLocation resourceLocation2 = this.minecraft.level == null ? Screen.FOOTER_SEPARATOR : Screen.INWORLD_FOOTER_SEPARATOR;
guiGraphics.blit(RenderType::guiTextured, resourceLocation, this.getX(), this.getY() - 2, 0.0F, 0.0F, this.getWidth(), 2, 32, 2);
guiGraphics.blit(RenderType::guiTextured, resourceLocation2, this.getX(), this.getBottom(), 0.0F, 0.0F, this.getWidth(), 2, 32, 2);
}
protected void renderListBackground(GuiGraphics guiGraphics) {
ResourceLocation resourceLocation = this.minecraft.level == null ? MENU_LIST_BACKGROUND : INWORLD_MENU_LIST_BACKGROUND;
guiGraphics.blit(
RenderType::guiTextured,
resourceLocation,
this.getX(),
this.getY(),
this.getRight(),
this.getBottom() + (int)this.scrollAmount(),
this.getWidth(),
this.getHeight(),
32,
32
);
}
protected void enableScissor(GuiGraphics guiGraphics) {
guiGraphics.enableScissor(this.getX(), this.getY(), this.getRight(), this.getBottom());
}
protected void centerScrollOn(E entry) {
this.setScrollAmount(this.children().indexOf(entry) * this.itemHeight + this.itemHeight / 2 - this.height / 2);
}
protected void ensureVisible(E entry) {
int i = this.getRowTop(this.children().indexOf(entry));
int j = i - this.getY() - 4 - this.itemHeight;
if (j < 0) {
this.scroll(j);
}
int k = this.getBottom() - i - this.itemHeight - this.itemHeight;
if (k < 0) {
this.scroll(-k);
}
}
private void scroll(int scroll) {
this.setScrollAmount(this.scrollAmount() + scroll);
}
@Override
protected double scrollRate() {
return this.itemHeight / 2.0;
}
@Override
protected int scrollBarX() {
return this.getRowRight() + 6 + 2;
}
@Override
public Optional<GuiEventListener> getChildAt(double mouseX, double mouseY) {
return Optional.ofNullable(this.getEntryAtPosition(mouseX, mouseY));
}
@Override
public void setFocused(@Nullable GuiEventListener focused) {
E entry = this.getFocused();
if (entry != focused && entry instanceof ContainerEventHandler containerEventHandler) {
containerEventHandler.setFocused(null);
}
super.setFocused(focused);
int i = this.children.indexOf(focused);
if (i >= 0) {
E entry2 = (E)this.children.get(i);
this.setSelected(entry2);
if (this.minecraft.getLastInputType().isKeyboard()) {
this.ensureVisible(entry2);
}
}
}
@Nullable
protected E nextEntry(ScreenDirection direction) {
return this.nextEntry(direction, entry -> true);
}
@Nullable
protected E nextEntry(ScreenDirection direction, Predicate<E> predicate) {
return this.nextEntry(direction, predicate, this.getSelected());
}
@Nullable
protected E nextEntry(ScreenDirection direction, Predicate<E> predicate, @Nullable E selected) {
int i = switch (direction) {
case RIGHT, LEFT -> 0;
case UP -> -1;
case DOWN -> 1;
};
if (!this.children().isEmpty() && i != 0) {
int j;
if (selected == null) {
j = i > 0 ? 0 : this.children().size() - 1;
} else {
j = this.children().indexOf(selected) + i;
}
for (int k = j; k >= 0 && k < this.children.size(); k += i) {
E entry = (E)this.children().get(k);
if (predicate.test(entry)) {
return entry;
}
}
}
return null;
}
protected void renderListItems(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
int i = this.getRowLeft();
int j = this.getRowWidth();
int k = this.itemHeight - 4;
int l = this.getItemCount();
for (int m = 0; m < l; m++) {
int n = this.getRowTop(m);
int o = this.getRowBottom(m);
if (o >= this.getY() && n <= this.getBottom()) {
this.renderItem(guiGraphics, mouseX, mouseY, partialTick, m, i, n, j, k);
}
}
}
protected void renderItem(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick, int index, int left, int top, int width, int height) {
E entry = this.getEntry(index);
entry.renderBack(guiGraphics, index, top, left, width, height, mouseX, mouseY, Objects.equals(this.hovered, entry), partialTick);
if (this.isSelectedItem(index)) {
int i = this.isFocused() ? -1 : -8355712;
this.renderSelection(guiGraphics, top, width, height, i, -16777216);
}
entry.render(guiGraphics, index, top, left, width, height, mouseX, mouseY, Objects.equals(this.hovered, entry), partialTick);
}
protected void renderSelection(GuiGraphics guiGraphics, int top, int width, int height, int outerColor, int innerColor) {
int i = this.getX() + (this.width - width) / 2;
int j = this.getX() + (this.width + width) / 2;
guiGraphics.fill(i, top - 2, j, top + height + 2, outerColor);
guiGraphics.fill(i + 1, top - 1, j - 1, top + height + 1, innerColor);
}
public int getRowLeft() {
return this.getX() + this.width / 2 - this.getRowWidth() / 2 + 2;
}
public int getRowRight() {
return this.getRowLeft() + this.getRowWidth();
}
public int getRowTop(int index) {
return this.getY() + 4 - (int)this.scrollAmount() + index * this.itemHeight + this.headerHeight;
}
public int getRowBottom(int index) {
return this.getRowTop(index) + this.itemHeight;
}
public int getRowWidth() {
return 220;
}
@Override
public NarratableEntry.NarrationPriority narrationPriority() {
if (this.isFocused()) {
return NarratableEntry.NarrationPriority.FOCUSED;
} else {
return this.hovered != null ? NarratableEntry.NarrationPriority.HOVERED : NarratableEntry.NarrationPriority.NONE;
}
}
@Nullable
protected E remove(int index) {
E entry = (E)this.children.get(index);
return this.removeEntry((E)this.children.get(index)) ? entry : null;
}
protected boolean removeEntry(E entry) {
boolean bl = this.children.remove(entry);
if (bl && entry == this.getSelected()) {
this.setSelected(null);
}
return bl;
}
@Nullable
protected E getHovered() {
return this.hovered;
}
void bindEntryToSelf(AbstractSelectionList.Entry<E> entry) {
entry.list = this;
}
protected void narrateListElementPosition(NarrationElementOutput narrationElementOutput, E entry) {
List<E> list = this.children();
if (list.size() > 1) {
int i = list.indexOf(entry);
if (i != -1) {
narrationElementOutput.add(NarratedElementType.POSITION, Component.translatable("narrator.position.list", i + 1, list.size()));
}
}
}
@Environment(EnvType.CLIENT)
protected abstract static class Entry<E extends AbstractSelectionList.Entry<E>> implements GuiEventListener {
@Deprecated
AbstractSelectionList<E> list;
@Override
public void setFocused(boolean focused) {
}
@Override
public boolean isFocused() {
return this.list.getFocused() == this;
}
public abstract void render(
GuiGraphics guiGraphics, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean hovering, float partialTick
);
public void renderBack(
GuiGraphics guiGraphics, int index, int top, int left, int width, int height, int mouseX, int mouseY, boolean isMouseOver, float partialTick
) {
}
@Override
public boolean isMouseOver(double mouseX, double mouseY) {
return Objects.equals(this.list.getEntryAtPosition(mouseX, mouseY), this);
}
}
@Environment(EnvType.CLIENT)
class TrackedList extends AbstractList<E> {
private final List<E> delegate = Lists.<E>newArrayList();
public E get(int index) {
return (E)this.delegate.get(index);
}
public int size() {
return this.delegate.size();
}
public E set(int index, E entry) {
E entry2 = (E)this.delegate.set(index, entry);
AbstractSelectionList.this.bindEntryToSelf(entry);
return entry2;
}
public void add(int index, E entry) {
this.delegate.add(index, entry);
AbstractSelectionList.this.bindEntryToSelf(entry);
}
public E remove(int index) {
return (E)this.delegate.remove(index);
}
}
}