449 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			449 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.RenderPipelines;
 | |
| 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);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Gets the focused GUI element.
 | |
| 	 */
 | |
| 	@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(RenderPipelines.GUI_TEXTURED, resourceLocation, this.getX(), this.getY() - 2, 0.0F, 0.0F, this.getWidth(), 2, 32, 2);
 | |
| 		guiGraphics.blit(RenderPipelines.GUI_TEXTURED, 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(
 | |
| 			RenderPipelines.GUI_TEXTURED,
 | |
| 			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);
 | |
| 		}
 | |
| 	}
 | |
| }
 |