package net.minecraft.client.gui.screens; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.GuiMessageTag; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.ChatComponent; import net.minecraft.client.gui.components.CommandSuggestions; import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.narration.NarratedElementType; import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.chat.Style; import net.minecraft.util.Mth; import net.minecraft.util.StringUtil; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class ChatScreen extends Screen { public static final double MOUSE_SCROLL_SPEED = 7.0; private static final Component USAGE_TEXT = Component.translatable("chat_screen.usage"); private static final int TOOLTIP_MAX_WIDTH = 210; private String historyBuffer = ""; /** * keeps position of which chat message you will select when you press up, (does not increase for duplicated messages sent immediately after each other) */ private int historyPos = -1; /** * Chat entry field */ protected EditBox input; /** * is the text that appears when you press the chat key and the input box appears pre-filled */ private String initial; CommandSuggestions commandSuggestions; public ChatScreen(String initial) { super(Component.translatable("chat_screen.title")); this.initial = initial; } @Override protected void init() { this.historyPos = this.minecraft.gui.getChat().getRecentChat().size(); this.input = new EditBox(this.minecraft.fontFilterFishy, 4, this.height - 12, this.width - 4, 12, Component.translatable("chat.editBox")) { @Override protected MutableComponent createNarrationMessage() { return super.createNarrationMessage().append(ChatScreen.this.commandSuggestions.getNarrationMessage()); } }; this.input.setMaxLength(256); this.input.setBordered(false); this.input.setValue(this.initial); this.input.setResponder(this::onEdited); this.input.setCanLoseFocus(false); this.addWidget(this.input); this.commandSuggestions = new CommandSuggestions(this.minecraft, this, this.input, this.font, false, false, 1, 10, true, -805306368); this.commandSuggestions.setAllowHiding(false); this.commandSuggestions.updateCommandInfo(); } @Override protected void setInitialFocus() { this.setInitialFocus(this.input); } @Override public void resize(Minecraft minecraft, int width, int height) { String string = this.input.getValue(); this.init(minecraft, width, height); this.setChatLine(string); this.commandSuggestions.updateCommandInfo(); } @Override public void removed() { this.minecraft.gui.getChat().resetChatScroll(); } private void onEdited(String value) { String string = this.input.getValue(); this.commandSuggestions.setAllowSuggestions(!string.equals(this.initial)); this.commandSuggestions.updateCommandInfo(); } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (this.commandSuggestions.keyPressed(keyCode, scanCode, modifiers)) { return true; } else if (super.keyPressed(keyCode, scanCode, modifiers)) { return true; } else if (keyCode == 256) { this.minecraft.setScreen(null); return true; } else if (keyCode == 257 || keyCode == 335) { this.handleChatInput(this.input.getValue(), true); this.minecraft.setScreen(null); return true; } else if (keyCode == 265) { this.moveInHistory(-1); return true; } else if (keyCode == 264) { this.moveInHistory(1); return true; } else if (keyCode == 266) { this.minecraft.gui.getChat().scrollChat(this.minecraft.gui.getChat().getLinesPerPage() - 1); return true; } else if (keyCode == 267) { this.minecraft.gui.getChat().scrollChat(-this.minecraft.gui.getChat().getLinesPerPage() + 1); return true; } else { return false; } } @Override public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { scrollY = Mth.clamp(scrollY, -1.0, 1.0); if (this.commandSuggestions.mouseScrolled(scrollY)) { return true; } else { if (!hasShiftDown()) { scrollY *= 7.0; } this.minecraft.gui.getChat().scrollChat((int)scrollY); return true; } } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (this.commandSuggestions.mouseClicked((int)mouseX, (int)mouseY, button)) { return true; } else { if (button == 0) { ChatComponent chatComponent = this.minecraft.gui.getChat(); if (chatComponent.handleChatQueueClicked(mouseX, mouseY)) { return true; } Style style = this.getComponentStyleAt(mouseX, mouseY); if (style != null && this.handleComponentClicked(style)) { this.initial = this.input.getValue(); return true; } } return this.input.mouseClicked(mouseX, mouseY, button) ? true : super.mouseClicked(mouseX, mouseY, button); } } @Override protected void insertText(String text, boolean overwrite) { if (overwrite) { this.input.setValue(text); } else { this.input.insertText(text); } } /** * Input is relative and is applied directly to the sentHistoryCursor so -1 is the previous message, 1 is the next message from the current cursor position. */ public void moveInHistory(int msgPos) { int i = this.historyPos + msgPos; int j = this.minecraft.gui.getChat().getRecentChat().size(); i = Mth.clamp(i, 0, j); if (i != this.historyPos) { if (i == j) { this.historyPos = j; this.input.setValue(this.historyBuffer); } else { if (this.historyPos == j) { this.historyBuffer = this.input.getValue(); } this.input.setValue(this.minecraft.gui.getChat().getRecentChat().get(i)); this.commandSuggestions.setAllowSuggestions(false); this.historyPos = i; } } } @Override public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { this.minecraft.gui.getChat().render(guiGraphics, this.minecraft.gui.getGuiTicks(), mouseX, mouseY, true); guiGraphics.fill(2, this.height - 14, this.width - 2, this.height - 2, this.minecraft.options.getBackgroundColor(Integer.MIN_VALUE)); this.input.render(guiGraphics, mouseX, mouseY, partialTick); super.render(guiGraphics, mouseX, mouseY, partialTick); guiGraphics.pose().pushPose(); guiGraphics.pose().translate(0.0F, 0.0F, 200.0F); this.commandSuggestions.render(guiGraphics, mouseX, mouseY); guiGraphics.pose().popPose(); GuiMessageTag guiMessageTag = this.minecraft.gui.getChat().getMessageTagAt(mouseX, mouseY); if (guiMessageTag != null && guiMessageTag.text() != null) { guiGraphics.renderTooltip(this.font, this.font.split(guiMessageTag.text(), 210), mouseX, mouseY); } else { Style style = this.getComponentStyleAt(mouseX, mouseY); if (style != null && style.getHoverEvent() != null) { guiGraphics.renderComponentHoverEffect(this.font, style, mouseX, mouseY); } } } @Override public void renderBackground(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { } @Override public boolean isPauseScreen() { return false; } private void setChatLine(String chatLine) { this.input.setValue(chatLine); } @Override protected void updateNarrationState(NarrationElementOutput output) { output.add(NarratedElementType.TITLE, this.getTitle()); output.add(NarratedElementType.USAGE, USAGE_TEXT); String string = this.input.getValue(); if (!string.isEmpty()) { output.nest().add(NarratedElementType.TITLE, Component.translatable("chat_screen.message", string)); } } @Nullable private Style getComponentStyleAt(double mouseX, double mouseY) { return this.minecraft.gui.getChat().getClickedComponentStyleAt(mouseX, mouseY); } public void handleChatInput(String message, boolean addToRecentChat) { message = this.normalizeChatMessage(message); if (!message.isEmpty()) { if (addToRecentChat) { this.minecraft.gui.getChat().addRecentChat(message); } if (message.startsWith("/")) { this.minecraft.player.connection.sendCommand(message.substring(1)); } else { this.minecraft.player.connection.sendChat(message); } } } public String normalizeChatMessage(String message) { return StringUtil.trimChatMessage(StringUtils.normalizeSpace(message.trim())); } }