653 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			653 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.client.gui.screens;
 | |
| 
 | |
| import com.google.common.annotations.VisibleForTesting;
 | |
| import com.google.common.collect.Lists;
 | |
| import com.mojang.blaze3d.platform.InputConstants;
 | |
| import com.mojang.logging.LogUtils;
 | |
| import java.net.URI;
 | |
| import java.nio.file.Path;
 | |
| import java.util.Comparator;
 | |
| import java.util.List;
 | |
| import java.util.Objects;
 | |
| import java.util.concurrent.Executor;
 | |
| import java.util.concurrent.TimeUnit;
 | |
| import net.fabricmc.api.EnvType;
 | |
| import net.fabricmc.api.Environment;
 | |
| import net.minecraft.CrashReport;
 | |
| import net.minecraft.CrashReportCategory;
 | |
| import net.minecraft.CrashReportDetail;
 | |
| import net.minecraft.Util;
 | |
| import net.minecraft.client.Minecraft;
 | |
| import net.minecraft.client.NarratorStatus;
 | |
| import net.minecraft.client.gui.ComponentPath;
 | |
| import net.minecraft.client.gui.Font;
 | |
| import net.minecraft.client.gui.GuiGraphics;
 | |
| import net.minecraft.client.gui.components.AbstractWidget;
 | |
| import net.minecraft.client.gui.components.CycleButton;
 | |
| import net.minecraft.client.gui.components.Renderable;
 | |
| import net.minecraft.client.gui.components.TabOrderedElement;
 | |
| import net.minecraft.client.gui.components.events.AbstractContainerEventHandler;
 | |
| import net.minecraft.client.gui.components.events.GuiEventListener;
 | |
| 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.narration.ScreenNarrationCollector;
 | |
| import net.minecraft.client.gui.navigation.FocusNavigationEvent;
 | |
| import net.minecraft.client.gui.navigation.ScreenDirection;
 | |
| import net.minecraft.client.gui.navigation.ScreenRectangle;
 | |
| import net.minecraft.client.player.LocalPlayer;
 | |
| import net.minecraft.client.renderer.RenderPipelines;
 | |
| import net.minecraft.commands.Commands;
 | |
| import net.minecraft.network.chat.ClickEvent;
 | |
| import net.minecraft.network.chat.Component;
 | |
| import net.minecraft.network.chat.Style;
 | |
| import net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket;
 | |
| import net.minecraft.resources.ResourceLocation;
 | |
| import net.minecraft.sounds.Music;
 | |
| import net.minecraft.world.item.Item;
 | |
| import net.minecraft.world.item.ItemStack;
 | |
| import net.minecraft.world.item.TooltipFlag;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| @Environment(EnvType.CLIENT)
 | |
| public abstract class Screen extends AbstractContainerEventHandler implements Renderable {
 | |
| 	private static final Logger LOGGER = LogUtils.getLogger();
 | |
| 	private static final Component USAGE_NARRATION = Component.translatable("narrator.screen.usage");
 | |
| 	public static final ResourceLocation MENU_BACKGROUND = ResourceLocation.withDefaultNamespace("textures/gui/menu_background.png");
 | |
| 	public static final ResourceLocation HEADER_SEPARATOR = ResourceLocation.withDefaultNamespace("textures/gui/header_separator.png");
 | |
| 	public static final ResourceLocation FOOTER_SEPARATOR = ResourceLocation.withDefaultNamespace("textures/gui/footer_separator.png");
 | |
| 	private static final ResourceLocation INWORLD_MENU_BACKGROUND = ResourceLocation.withDefaultNamespace("textures/gui/inworld_menu_background.png");
 | |
| 	public static final ResourceLocation INWORLD_HEADER_SEPARATOR = ResourceLocation.withDefaultNamespace("textures/gui/inworld_header_separator.png");
 | |
| 	public static final ResourceLocation INWORLD_FOOTER_SEPARATOR = ResourceLocation.withDefaultNamespace("textures/gui/inworld_footer_separator.png");
 | |
| 	protected static final float FADE_IN_TIME = 2000.0F;
 | |
| 	protected final Component title;
 | |
| 	private final List<GuiEventListener> children = Lists.<GuiEventListener>newArrayList();
 | |
| 	private final List<NarratableEntry> narratables = Lists.<NarratableEntry>newArrayList();
 | |
| 	@Nullable
 | |
| 	protected Minecraft minecraft;
 | |
| 	private boolean initialized;
 | |
| 	public int width;
 | |
| 	public int height;
 | |
| 	private final List<Renderable> renderables = Lists.<Renderable>newArrayList();
 | |
| 	protected Font font;
 | |
| 	private static final long NARRATE_SUPPRESS_AFTER_INIT_TIME = TimeUnit.SECONDS.toMillis(2L);
 | |
| 	private static final long NARRATE_DELAY_NARRATOR_ENABLED = NARRATE_SUPPRESS_AFTER_INIT_TIME;
 | |
| 	private static final long NARRATE_DELAY_MOUSE_MOVE = 750L;
 | |
| 	private static final long NARRATE_DELAY_MOUSE_ACTION = 200L;
 | |
| 	private static final long NARRATE_DELAY_KEYBOARD_ACTION = 200L;
 | |
| 	private final ScreenNarrationCollector narrationState = new ScreenNarrationCollector();
 | |
| 	private long narrationSuppressTime = Long.MIN_VALUE;
 | |
| 	private long nextNarrationTime = Long.MAX_VALUE;
 | |
| 	@Nullable
 | |
| 	protected CycleButton<NarratorStatus> narratorButton;
 | |
| 	@Nullable
 | |
| 	private NarratableEntry lastNarratable;
 | |
| 	protected final Executor screenExecutor = runnable -> this.minecraft.execute(() -> {
 | |
| 		if (this.minecraft.screen == this) {
 | |
| 			runnable.run();
 | |
| 		}
 | |
| 	});
 | |
| 
 | |
| 	protected Screen(Component title) {
 | |
| 		this.title = title;
 | |
| 	}
 | |
| 
 | |
| 	public Component getTitle() {
 | |
| 		return this.title;
 | |
| 	}
 | |
| 
 | |
| 	public Component getNarrationMessage() {
 | |
| 		return this.getTitle();
 | |
| 	}
 | |
| 
 | |
| 	public final void renderWithTooltip(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
 | |
| 		guiGraphics.nextStratum();
 | |
| 		this.renderBackground(guiGraphics, mouseX, mouseY, partialTick);
 | |
| 		guiGraphics.nextStratum();
 | |
| 		this.render(guiGraphics, mouseX, mouseY, partialTick);
 | |
| 		guiGraphics.renderDeferredTooltip();
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
 | |
| 		for (Renderable renderable : this.renderables) {
 | |
| 			renderable.render(guiGraphics, mouseX, mouseY, partialTick);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
 | |
| 		if (keyCode == 256 && this.shouldCloseOnEsc()) {
 | |
| 			this.onClose();
 | |
| 			return true;
 | |
| 		} else if (super.keyPressed(keyCode, scanCode, modifiers)) {
 | |
| 			return true;
 | |
| 		} else {
 | |
| 			FocusNavigationEvent focusNavigationEvent = (FocusNavigationEvent)(switch (keyCode) {
 | |
| 				case 258 -> this.createTabEvent();
 | |
| 				default -> null;
 | |
| 				case 262 -> this.createArrowEvent(ScreenDirection.RIGHT);
 | |
| 				case 263 -> this.createArrowEvent(ScreenDirection.LEFT);
 | |
| 				case 264 -> this.createArrowEvent(ScreenDirection.DOWN);
 | |
| 				case 265 -> this.createArrowEvent(ScreenDirection.UP);
 | |
| 			});
 | |
| 			if (focusNavigationEvent != null) {
 | |
| 				ComponentPath componentPath = super.nextFocusPath(focusNavigationEvent);
 | |
| 				if (componentPath == null && focusNavigationEvent instanceof FocusNavigationEvent.TabNavigation) {
 | |
| 					this.clearFocus();
 | |
| 					componentPath = super.nextFocusPath(focusNavigationEvent);
 | |
| 				}
 | |
| 
 | |
| 				if (componentPath != null) {
 | |
| 					this.changeFocus(componentPath);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return false;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private FocusNavigationEvent.TabNavigation createTabEvent() {
 | |
| 		boolean bl = !hasShiftDown();
 | |
| 		return new FocusNavigationEvent.TabNavigation(bl);
 | |
| 	}
 | |
| 
 | |
| 	private FocusNavigationEvent.ArrowNavigation createArrowEvent(ScreenDirection direction) {
 | |
| 		return new FocusNavigationEvent.ArrowNavigation(direction);
 | |
| 	}
 | |
| 
 | |
| 	protected void setInitialFocus() {
 | |
| 		if (this.minecraft.getLastInputType().isKeyboard()) {
 | |
| 			FocusNavigationEvent.TabNavigation tabNavigation = new FocusNavigationEvent.TabNavigation(true);
 | |
| 			ComponentPath componentPath = super.nextFocusPath(tabNavigation);
 | |
| 			if (componentPath != null) {
 | |
| 				this.changeFocus(componentPath);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected void setInitialFocus(GuiEventListener listener) {
 | |
| 		ComponentPath componentPath = ComponentPath.path(this, listener.nextFocusPath(new FocusNavigationEvent.InitialFocus()));
 | |
| 		if (componentPath != null) {
 | |
| 			this.changeFocus(componentPath);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void clearFocus() {
 | |
| 		ComponentPath componentPath = this.getCurrentFocusPath();
 | |
| 		if (componentPath != null) {
 | |
| 			componentPath.applyFocus(false);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@VisibleForTesting
 | |
| 	protected void changeFocus(ComponentPath path) {
 | |
| 		this.clearFocus();
 | |
| 		path.applyFocus(true);
 | |
| 	}
 | |
| 
 | |
| 	public boolean shouldCloseOnEsc() {
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	public void onClose() {
 | |
| 		this.minecraft.setScreen(null);
 | |
| 	}
 | |
| 
 | |
| 	protected <T extends GuiEventListener & Renderable & NarratableEntry> T addRenderableWidget(T widget) {
 | |
| 		this.renderables.add(widget);
 | |
| 		return this.addWidget(widget);
 | |
| 	}
 | |
| 
 | |
| 	protected <T extends Renderable> T addRenderableOnly(T renderable) {
 | |
| 		this.renderables.add(renderable);
 | |
| 		return renderable;
 | |
| 	}
 | |
| 
 | |
| 	protected <T extends GuiEventListener & NarratableEntry> T addWidget(T listener) {
 | |
| 		this.children.add(listener);
 | |
| 		this.narratables.add(listener);
 | |
| 		return listener;
 | |
| 	}
 | |
| 
 | |
| 	protected void removeWidget(GuiEventListener listener) {
 | |
| 		if (listener instanceof Renderable) {
 | |
| 			this.renderables.remove((Renderable)listener);
 | |
| 		}
 | |
| 
 | |
| 		if (listener instanceof NarratableEntry) {
 | |
| 			this.narratables.remove((NarratableEntry)listener);
 | |
| 		}
 | |
| 
 | |
| 		this.children.remove(listener);
 | |
| 	}
 | |
| 
 | |
| 	protected void clearWidgets() {
 | |
| 		this.renderables.clear();
 | |
| 		this.children.clear();
 | |
| 		this.narratables.clear();
 | |
| 	}
 | |
| 
 | |
| 	public static List<Component> getTooltipFromItem(Minecraft minecraft, ItemStack item) {
 | |
| 		return item.getTooltipLines(
 | |
| 			Item.TooltipContext.of(minecraft.level),
 | |
| 			minecraft.player,
 | |
| 			minecraft.options.advancedItemTooltips ? TooltipFlag.Default.ADVANCED : TooltipFlag.Default.NORMAL
 | |
| 		);
 | |
| 	}
 | |
| 
 | |
| 	protected void insertText(String text, boolean overwrite) {
 | |
| 	}
 | |
| 
 | |
| 	public boolean handleComponentClicked(Style style) {
 | |
| 		ClickEvent clickEvent = style.getClickEvent();
 | |
| 		if (hasShiftDown()) {
 | |
| 			if (style.getInsertion() != null) {
 | |
| 				this.insertText(style.getInsertion(), false);
 | |
| 			}
 | |
| 		} else if (clickEvent != null) {
 | |
| 			this.handleClickEvent(this.minecraft, clickEvent);
 | |
| 			return true;
 | |
| 		}
 | |
| 
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	protected void handleClickEvent(Minecraft minecraft, ClickEvent clickEvent) {
 | |
| 		defaultHandleGameClickEvent(clickEvent, minecraft, this);
 | |
| 	}
 | |
| 
 | |
| 	protected static void defaultHandleGameClickEvent(ClickEvent clickEvent, Minecraft minecraft, @Nullable Screen screen) {
 | |
| 		LocalPlayer localPlayer = (LocalPlayer)Objects.requireNonNull(minecraft.player, "Player not available");
 | |
| 		switch (clickEvent) {
 | |
| 			case ClickEvent.RunCommand(String var11):
 | |
| 				clickCommandAction(localPlayer, var11, screen);
 | |
| 				break;
 | |
| 			case ClickEvent.ShowDialog showDialog:
 | |
| 				localPlayer.connection.showDialog(showDialog.dialog(), screen);
 | |
| 				break;
 | |
| 			case ClickEvent.Custom custom:
 | |
| 				localPlayer.connection.send(new ServerboundCustomClickActionPacket(custom.id(), custom.payload()));
 | |
| 				if (minecraft.screen != screen) {
 | |
| 					minecraft.setScreen(screen);
 | |
| 				}
 | |
| 				break;
 | |
| 			default:
 | |
| 				defaultHandleClickEvent(clickEvent, minecraft, screen);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected static void defaultHandleClickEvent(ClickEvent clickEvent, Minecraft minecraft, @Nullable Screen screen) {
 | |
| 		boolean bl = switch (clickEvent) {
 | |
| 			case ClickEvent.OpenUrl(URI var17) -> {
 | |
| 				clickUrlAction(minecraft, screen, var17);
 | |
| 				yield false;
 | |
| 			}
 | |
| 			case ClickEvent.OpenFile openFile -> {
 | |
| 				Util.getPlatform().openFile(openFile.file());
 | |
| 				yield true;
 | |
| 			}
 | |
| 			case ClickEvent.SuggestCommand(String var22) -> {
 | |
| 				String var18 = var22;
 | |
| 				if (screen != null) {
 | |
| 					screen.insertText(var18, true);
 | |
| 				}
 | |
| 
 | |
| 				yield true;
 | |
| 			}
 | |
| 			case ClickEvent.CopyToClipboard(String var13) -> {
 | |
| 				minecraft.keyboardHandler.setClipboard(var13);
 | |
| 				yield true;
 | |
| 			}
 | |
| 			default -> {
 | |
| 				LOGGER.error("Don't know how to handle {}", clickEvent);
 | |
| 				yield true;
 | |
| 			}
 | |
| 		};
 | |
| 		if (bl && minecraft.screen != screen) {
 | |
| 			minecraft.setScreen(screen);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected static boolean clickUrlAction(Minecraft minecraft, @Nullable Screen screen, URI url) {
 | |
| 		if (!minecraft.options.chatLinks().get()) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			if (minecraft.options.chatLinksPrompt().get()) {
 | |
| 				minecraft.setScreen(new ConfirmLinkScreen(bl -> {
 | |
| 					if (bl) {
 | |
| 						Util.getPlatform().openUri(url);
 | |
| 					}
 | |
| 
 | |
| 					minecraft.setScreen(screen);
 | |
| 				}, url.toString(), false));
 | |
| 			} else {
 | |
| 				Util.getPlatform().openUri(url);
 | |
| 			}
 | |
| 
 | |
| 			return true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected static void clickCommandAction(LocalPlayer player, String command, @Nullable Screen screen) {
 | |
| 		player.connection.sendUnattendedCommand(Commands.trimOptionalPrefix(command), screen);
 | |
| 	}
 | |
| 
 | |
| 	public final void init(Minecraft minecraft, int width, int height) {
 | |
| 		this.minecraft = minecraft;
 | |
| 		this.font = minecraft.font;
 | |
| 		this.width = width;
 | |
| 		this.height = height;
 | |
| 		if (!this.initialized) {
 | |
| 			this.init();
 | |
| 			this.setInitialFocus();
 | |
| 		} else {
 | |
| 			this.repositionElements();
 | |
| 		}
 | |
| 
 | |
| 		this.initialized = true;
 | |
| 		this.triggerImmediateNarration(false);
 | |
| 		this.suppressNarration(NARRATE_SUPPRESS_AFTER_INIT_TIME);
 | |
| 	}
 | |
| 
 | |
| 	protected void rebuildWidgets() {
 | |
| 		this.clearWidgets();
 | |
| 		this.clearFocus();
 | |
| 		this.init();
 | |
| 		this.setInitialFocus();
 | |
| 	}
 | |
| 
 | |
| 	protected void fadeWidgets(float fade) {
 | |
| 		for (GuiEventListener guiEventListener : this.children()) {
 | |
| 			if (guiEventListener instanceof AbstractWidget abstractWidget) {
 | |
| 				abstractWidget.setAlpha(fade);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public List<? extends GuiEventListener> children() {
 | |
| 		return this.children;
 | |
| 	}
 | |
| 
 | |
| 	protected void init() {
 | |
| 	}
 | |
| 
 | |
| 	public void tick() {
 | |
| 	}
 | |
| 
 | |
| 	public void removed() {
 | |
| 	}
 | |
| 
 | |
| 	public void added() {
 | |
| 	}
 | |
| 
 | |
| 	public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
 | |
| 		if (this.minecraft.level == null) {
 | |
| 			this.renderPanorama(guiGraphics, partialTick);
 | |
| 		}
 | |
| 
 | |
| 		this.renderBlurredBackground(guiGraphics);
 | |
| 		this.renderMenuBackground(guiGraphics);
 | |
| 	}
 | |
| 
 | |
| 	protected void renderBlurredBackground(GuiGraphics guiGraphics) {
 | |
| 		float f = this.minecraft.options.getMenuBackgroundBlurriness();
 | |
| 		if (f >= 1.0F) {
 | |
| 			guiGraphics.blurBeforeThisStratum();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected void renderPanorama(GuiGraphics guiGraphics, float partialTick) {
 | |
| 		this.minecraft.gameRenderer.getPanorama().render(guiGraphics, this.width, this.height, true);
 | |
| 	}
 | |
| 
 | |
| 	protected void renderMenuBackground(GuiGraphics partialTick) {
 | |
| 		this.renderMenuBackground(partialTick, 0, 0, this.width, this.height);
 | |
| 	}
 | |
| 
 | |
| 	protected void renderMenuBackground(GuiGraphics guiGraphics, int x, int y, int width, int height) {
 | |
| 		renderMenuBackgroundTexture(guiGraphics, this.minecraft.level == null ? MENU_BACKGROUND : INWORLD_MENU_BACKGROUND, x, y, 0.0F, 0.0F, width, height);
 | |
| 	}
 | |
| 
 | |
| 	public static void renderMenuBackgroundTexture(
 | |
| 		GuiGraphics guiGraphics, ResourceLocation texture, int x, int y, float uOffset, float vOffset, int width, int height
 | |
| 	) {
 | |
| 		int i = 32;
 | |
| 		guiGraphics.blit(RenderPipelines.GUI_TEXTURED, texture, x, y, uOffset, vOffset, width, height, 32, 32);
 | |
| 	}
 | |
| 
 | |
| 	public void renderTransparentBackground(GuiGraphics guiGraphics) {
 | |
| 		guiGraphics.fillGradient(0, 0, this.width, this.height, -1072689136, -804253680);
 | |
| 	}
 | |
| 
 | |
| 	public boolean isPauseScreen() {
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	public static boolean hasControlDown() {
 | |
| 		return Minecraft.ON_OSX
 | |
| 			? InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), 343)
 | |
| 				|| InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), 347)
 | |
| 			: InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), 341)
 | |
| 				|| InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), 345);
 | |
| 	}
 | |
| 
 | |
| 	public static boolean hasShiftDown() {
 | |
| 		return InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), 340)
 | |
| 			|| InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), 344);
 | |
| 	}
 | |
| 
 | |
| 	public static boolean hasAltDown() {
 | |
| 		return InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), 342)
 | |
| 			|| InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), 346);
 | |
| 	}
 | |
| 
 | |
| 	public static boolean isCut(int keyCode) {
 | |
| 		return keyCode == 88 && hasControlDown() && !hasShiftDown() && !hasAltDown();
 | |
| 	}
 | |
| 
 | |
| 	public static boolean isPaste(int keyCode) {
 | |
| 		return keyCode == 86 && hasControlDown() && !hasShiftDown() && !hasAltDown();
 | |
| 	}
 | |
| 
 | |
| 	public static boolean isCopy(int keyCode) {
 | |
| 		return keyCode == 67 && hasControlDown() && !hasShiftDown() && !hasAltDown();
 | |
| 	}
 | |
| 
 | |
| 	public static boolean isSelectAll(int keyCode) {
 | |
| 		return keyCode == 65 && hasControlDown() && !hasShiftDown() && !hasAltDown();
 | |
| 	}
 | |
| 
 | |
| 	protected void repositionElements() {
 | |
| 		this.rebuildWidgets();
 | |
| 	}
 | |
| 
 | |
| 	public void resize(Minecraft minecraft, int width, int height) {
 | |
| 		this.width = width;
 | |
| 		this.height = height;
 | |
| 		this.repositionElements();
 | |
| 	}
 | |
| 
 | |
| 	public void fillCrashDetails(CrashReport crashReport) {
 | |
| 		CrashReportCategory crashReportCategory = crashReport.addCategory("Affected screen", 1);
 | |
| 		crashReportCategory.setDetail("Screen name", (CrashReportDetail<String>)(() -> this.getClass().getCanonicalName()));
 | |
| 	}
 | |
| 
 | |
| 	protected boolean isValidCharacterForName(String text, char charTyped, int cursorPos) {
 | |
| 		int i = text.indexOf(58);
 | |
| 		int j = text.indexOf(47);
 | |
| 		if (charTyped == ':') {
 | |
| 			return (j == -1 || cursorPos <= j) && i == -1;
 | |
| 		} else {
 | |
| 			return charTyped == '/'
 | |
| 				? cursorPos > i
 | |
| 				: charTyped == '_' || charTyped == '-' || charTyped >= 'a' && charTyped <= 'z' || charTyped >= '0' && charTyped <= '9' || charTyped == '.';
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public boolean isMouseOver(double mouseX, double mouseY) {
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	public void onFilesDrop(List<Path> packs) {
 | |
| 	}
 | |
| 
 | |
| 	private void scheduleNarration(long delay, boolean stopSuppression) {
 | |
| 		this.nextNarrationTime = Util.getMillis() + delay;
 | |
| 		if (stopSuppression) {
 | |
| 			this.narrationSuppressTime = Long.MIN_VALUE;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void suppressNarration(long time) {
 | |
| 		this.narrationSuppressTime = Util.getMillis() + time;
 | |
| 	}
 | |
| 
 | |
| 	public void afterMouseMove() {
 | |
| 		this.scheduleNarration(750L, false);
 | |
| 	}
 | |
| 
 | |
| 	public void afterMouseAction() {
 | |
| 		this.scheduleNarration(200L, true);
 | |
| 	}
 | |
| 
 | |
| 	public void afterKeyboardAction() {
 | |
| 		this.scheduleNarration(200L, true);
 | |
| 	}
 | |
| 
 | |
| 	private boolean shouldRunNarration() {
 | |
| 		return this.minecraft.getNarrator().isActive();
 | |
| 	}
 | |
| 
 | |
| 	public void handleDelayedNarration() {
 | |
| 		if (this.shouldRunNarration()) {
 | |
| 			long l = Util.getMillis();
 | |
| 			if (l > this.nextNarrationTime && l > this.narrationSuppressTime) {
 | |
| 				this.runNarration(true);
 | |
| 				this.nextNarrationTime = Long.MAX_VALUE;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void triggerImmediateNarration(boolean onlyNarrateNew) {
 | |
| 		if (this.shouldRunNarration()) {
 | |
| 			this.runNarration(onlyNarrateNew);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void runNarration(boolean onlyNarrateNew) {
 | |
| 		this.narrationState.update(this::updateNarrationState);
 | |
| 		String string = this.narrationState.collectNarrationText(!onlyNarrateNew);
 | |
| 		if (!string.isEmpty()) {
 | |
| 			this.minecraft.getNarrator().saySystemNow(string);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected boolean shouldNarrateNavigation() {
 | |
| 		return true;
 | |
| 	}
 | |
| 
 | |
| 	protected void updateNarrationState(NarrationElementOutput output) {
 | |
| 		output.add(NarratedElementType.TITLE, this.getNarrationMessage());
 | |
| 		if (this.shouldNarrateNavigation()) {
 | |
| 			output.add(NarratedElementType.USAGE, USAGE_NARRATION);
 | |
| 		}
 | |
| 
 | |
| 		this.updateNarratedWidget(output);
 | |
| 	}
 | |
| 
 | |
| 	protected void updateNarratedWidget(NarrationElementOutput narrationElementOutput) {
 | |
| 		List<? extends NarratableEntry> list = this.narratables
 | |
| 			.stream()
 | |
| 			.flatMap(narratableEntry -> narratableEntry.getNarratables().stream())
 | |
| 			.filter(NarratableEntry::isActive)
 | |
| 			.sorted(Comparator.comparingInt(TabOrderedElement::getTabOrderGroup))
 | |
| 			.toList();
 | |
| 		Screen.NarratableSearchResult narratableSearchResult = findNarratableWidget(list, this.lastNarratable);
 | |
| 		if (narratableSearchResult != null) {
 | |
| 			if (narratableSearchResult.priority.isTerminal()) {
 | |
| 				this.lastNarratable = narratableSearchResult.entry;
 | |
| 			}
 | |
| 
 | |
| 			if (list.size() > 1) {
 | |
| 				narrationElementOutput.add(NarratedElementType.POSITION, Component.translatable("narrator.position.screen", narratableSearchResult.index + 1, list.size()));
 | |
| 				if (narratableSearchResult.priority == NarratableEntry.NarrationPriority.FOCUSED) {
 | |
| 					narrationElementOutput.add(NarratedElementType.USAGE, this.getUsageNarration());
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			narratableSearchResult.entry.updateNarration(narrationElementOutput.nest());
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	protected Component getUsageNarration() {
 | |
| 		return Component.translatable("narration.component_list.usage");
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public static Screen.NarratableSearchResult findNarratableWidget(List<? extends NarratableEntry> entries, @Nullable NarratableEntry target) {
 | |
| 		Screen.NarratableSearchResult narratableSearchResult = null;
 | |
| 		Screen.NarratableSearchResult narratableSearchResult2 = null;
 | |
| 		int i = 0;
 | |
| 
 | |
| 		for (int j = entries.size(); i < j; i++) {
 | |
| 			NarratableEntry narratableEntry = (NarratableEntry)entries.get(i);
 | |
| 			NarratableEntry.NarrationPriority narrationPriority = narratableEntry.narrationPriority();
 | |
| 			if (narrationPriority.isTerminal()) {
 | |
| 				if (narratableEntry != target) {
 | |
| 					return new Screen.NarratableSearchResult(narratableEntry, i, narrationPriority);
 | |
| 				}
 | |
| 
 | |
| 				narratableSearchResult2 = new Screen.NarratableSearchResult(narratableEntry, i, narrationPriority);
 | |
| 			} else if (narrationPriority.compareTo(narratableSearchResult != null ? narratableSearchResult.priority : NarratableEntry.NarrationPriority.NONE) > 0) {
 | |
| 				narratableSearchResult = new Screen.NarratableSearchResult(narratableEntry, i, narrationPriority);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return narratableSearchResult != null ? narratableSearchResult : narratableSearchResult2;
 | |
| 	}
 | |
| 
 | |
| 	public void updateNarratorStatus(boolean narratorEnabled) {
 | |
| 		if (narratorEnabled) {
 | |
| 			this.scheduleNarration(NARRATE_DELAY_NARRATOR_ENABLED, false);
 | |
| 		}
 | |
| 
 | |
| 		if (this.narratorButton != null) {
 | |
| 			this.narratorButton.setValue(this.minecraft.options.narrator().get());
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public Font getFont() {
 | |
| 		return this.font;
 | |
| 	}
 | |
| 
 | |
| 	public boolean showsActiveEffects() {
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	@Override
 | |
| 	public ScreenRectangle getRectangle() {
 | |
| 		return new ScreenRectangle(0, 0, this.width, this.height);
 | |
| 	}
 | |
| 
 | |
| 	@Nullable
 | |
| 	public Music getBackgroundMusic() {
 | |
| 		return null;
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	public static class NarratableSearchResult {
 | |
| 		public final NarratableEntry entry;
 | |
| 		public final int index;
 | |
| 		public final NarratableEntry.NarrationPriority priority;
 | |
| 
 | |
| 		public NarratableSearchResult(NarratableEntry entry, int index, NarratableEntry.NarrationPriority priority) {
 | |
| 			this.entry = entry;
 | |
| 			this.index = index;
 | |
| 			this.priority = priority;
 | |
| 		}
 | |
| 	}
 | |
| }
 |