minecraft-src/net/minecraft/client/gui/screens/Screen.java
2025-07-04 03:45:38 +03:00

642 lines
22 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.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.CycleButton;
import net.minecraft.client.gui.components.Renderable;
import net.minecraft.client.gui.components.TabOrderedElement;
import net.minecraft.client.gui.components.Tooltip;
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.narration.NarratableEntry.NarrationPriority;
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.gui.navigation.FocusNavigationEvent.ArrowNavigation;
import net.minecraft.client.gui.navigation.FocusNavigationEvent.InitialFocus;
import net.minecraft.client.gui.navigation.FocusNavigationEvent.TabNavigation;
import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner;
import net.minecraft.client.gui.screens.inventory.tooltip.DefaultTooltipPositioner;
import net.minecraft.client.renderer.CubeMap;
import net.minecraft.client.renderer.PanoramaRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.Music;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag.Default;
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");
protected static final CubeMap CUBE_MAP = new CubeMap(ResourceLocation.withDefaultNamespace("textures/gui/title/background/panorama"));
protected static final PanoramaRenderer PANORAMA = new PanoramaRenderer(CUBE_MAP);
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 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;
@Nullable
private Screen.DeferredTooltipRendering deferredTooltipRendering;
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) {
this.render(guiGraphics, mouseX, mouseY, partialTick);
if (this.deferredTooltipRendering != null) {
guiGraphics.renderTooltip(this.font, this.deferredTooltipRendering.tooltip(), this.deferredTooltipRendering.positioner(), mouseX, mouseY);
this.deferredTooltipRendering = null;
}
}
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) {
this.renderBackground(guiGraphics, mouseX, mouseY, 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 TabNavigation) {
this.clearFocus();
componentPath = super.nextFocusPath(focusNavigationEvent);
}
if (componentPath != null) {
this.changeFocus(componentPath);
}
}
return false;
}
}
private TabNavigation createTabEvent() {
boolean bl = !hasShiftDown();
return new TabNavigation(bl);
}
private ArrowNavigation createArrowEvent(ScreenDirection direction) {
return new ArrowNavigation(direction);
}
protected void setInitialFocus() {
if (this.minecraft.getLastInputType().isKeyboard()) {
TabNavigation tabNavigation = new 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 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 ? Default.ADVANCED : Default.NORMAL
);
}
protected void insertText(String text, boolean overwrite) {
}
public boolean handleComponentClicked(@Nullable Style style) {
if (style == null) {
return false;
} else {
ClickEvent clickEvent = style.getClickEvent();
if (hasShiftDown()) {
if (style.getInsertion() != null) {
this.insertText(style.getInsertion(), false);
}
} else if (clickEvent != null) {
switch (clickEvent) {
case ClickEvent.OpenUrl(URI var28):
URI var19 = var28;
if (!this.minecraft.options.chatLinks().get()) {
return false;
}
if (this.minecraft.options.chatLinksPrompt().get()) {
this.minecraft.setScreen(new ConfirmLinkScreen(bl -> {
if (bl) {
Util.getPlatform().openUri(var19);
}
this.minecraft.setScreen(this);
}, var19.toString(), false));
} else {
Util.getPlatform().openUri(var19);
}
break;
case ClickEvent.OpenFile openFile:
Util.getPlatform().openFile(openFile.file());
break;
case ClickEvent.SuggestCommand(String var20):
this.insertText(var20, true);
break;
case ClickEvent.RunCommand(String var24):
String var21 = var24;
String string2 = var21;
if (var21.startsWith("/")) {
string2 = var21.substring(1);
}
if (!this.minecraft.player.connection.sendUnsignedCommand(string2)) {
LOGGER.error("Not allowed to run command with signed argument from click event: '{}'", string2);
}
break;
case ClickEvent.CopyToClipboard(String var14):
this.minecraft.keyboardHandler.setClipboard(var14);
break;
default:
LOGGER.error("Don't know how to handle {}", clickEvent);
}
return true;
}
return false;
}
}
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();
}
@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();
this.renderMenuBackground(guiGraphics);
}
protected void renderBlurredBackground() {
this.minecraft.gameRenderer.processBlurEffect();
}
protected void renderPanorama(GuiGraphics guiGraphics, float partialTick) {
PANORAMA.render(guiGraphics, this.width, this.height, 1.0F, partialTick);
}
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(RenderType::guiTextured, 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().sayNow(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 == 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);
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 : 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());
}
}
protected void clearTooltipForNextRenderPass() {
this.deferredTooltipRendering = null;
}
public void setTooltipForNextRenderPass(List<FormattedCharSequence> tooltip) {
this.setTooltipForNextRenderPass(tooltip, DefaultTooltipPositioner.INSTANCE, true);
}
public void setTooltipForNextRenderPass(List<FormattedCharSequence> tooltip, ClientTooltipPositioner positioner, boolean override) {
if (this.deferredTooltipRendering == null || override) {
this.deferredTooltipRendering = new Screen.DeferredTooltipRendering(tooltip, positioner);
}
}
public void setTooltipForNextRenderPass(Component tooltip) {
this.setTooltipForNextRenderPass(Tooltip.splitTooltip(this.minecraft, tooltip));
}
public void setTooltipForNextRenderPass(Tooltip tooltip, ClientTooltipPositioner positioner, boolean override) {
this.setTooltipForNextRenderPass(tooltip.toCharSequence(this.minecraft), positioner, override);
}
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)
record DeferredTooltipRendering(List<FormattedCharSequence> tooltip, ClientTooltipPositioner positioner) {
}
@Environment(EnvType.CLIENT)
public static class NarratableSearchResult {
public final NarratableEntry entry;
public final int index;
public final NarrationPriority priority;
public NarratableSearchResult(NarratableEntry entry, int index, NarrationPriority priority) {
this.entry = entry;
this.index = index;
this.priority = priority;
}
}
}