446 lines
13 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|