package net.minecraft.client.gui.components; import java.util.function.Consumer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.Util; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.MultilineTextField.StringView; import net.minecraft.client.gui.narration.NarratedElementType; import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.renderer.RenderType; import net.minecraft.network.chat.Component; import net.minecraft.util.StringUtil; @Environment(EnvType.CLIENT) public class MultiLineEditBox extends AbstractTextAreaWidget { private static final int CURSOR_INSERT_WIDTH = 1; private static final int CURSOR_INSERT_COLOR = -3092272; private static final String CURSOR_APPEND_CHARACTER = "_"; private static final int TEXT_COLOR = -2039584; private static final int PLACEHOLDER_TEXT_COLOR = -857677600; private static final int CURSOR_BLINK_INTERVAL_MS = 300; private final Font font; private final Component placeholder; private final MultilineTextField textField; private long focusedTime = Util.getMillis(); public MultiLineEditBox(Font font, int x, int y, int width, int height, Component placeholder, Component message) { super(x, y, width, height, message); this.font = font; this.placeholder = placeholder; this.textField = new MultilineTextField(font, width - this.totalInnerPadding()); this.textField.setCursorListener(this::scrollToCursor); } public void setCharacterLimit(int characterLimit) { this.textField.setCharacterLimit(characterLimit); } public void setValueListener(Consumer valueListener) { this.textField.setValueListener(valueListener); } public void setValue(String fullText) { this.textField.setValue(fullText); } public String getValue() { return this.textField.value(); } @Override public void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { narrationElementOutput.add(NarratedElementType.TITLE, Component.translatable("gui.narrate.editBox", this.getMessage(), this.getValue())); } @Override public void onClick(double mouseX, double mouseY) { this.textField.setSelecting(Screen.hasShiftDown()); this.seekCursorScreen(mouseX, mouseY); } @Override protected void onDrag(double mouseX, double mouseY, double dragX, double dragY) { this.textField.setSelecting(true); this.seekCursorScreen(mouseX, mouseY); this.textField.setSelecting(Screen.hasShiftDown()); } @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return this.textField.keyPressed(keyCode); } @Override public boolean charTyped(char codePoint, int modifiers) { if (this.visible && this.isFocused() && StringUtil.isAllowedChatCharacter(codePoint)) { this.textField.insertText(Character.toString(codePoint)); return true; } else { return false; } } @Override protected void renderContents(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { String string = this.textField.value(); if (string.isEmpty() && !this.isFocused()) { guiGraphics.drawWordWrap(this.font, this.placeholder, this.getInnerLeft(), this.getInnerTop(), this.width - this.totalInnerPadding(), -857677600); } else { int i = this.textField.cursor(); boolean bl = this.isFocused() && (Util.getMillis() - this.focusedTime) / 300L % 2L == 0L; boolean bl2 = i < string.length(); int j = 0; int k = 0; int l = this.getInnerTop(); for (StringView stringView : this.textField.iterateLines()) { boolean bl3 = this.withinContentAreaTopBottom(l, l + 9); if (bl && bl2 && i >= stringView.beginIndex() && i <= stringView.endIndex()) { if (bl3) { j = guiGraphics.drawString(this.font, string.substring(stringView.beginIndex(), i), this.getInnerLeft(), l, -2039584) - 1; guiGraphics.fill(j, l - 1, j + 1, l + 1 + 9, -3092272); guiGraphics.drawString(this.font, string.substring(i, stringView.endIndex()), j, l, -2039584); } } else { if (bl3) { j = guiGraphics.drawString(this.font, string.substring(stringView.beginIndex(), stringView.endIndex()), this.getInnerLeft(), l, -2039584) - 1; } k = l; } l += 9; } if (bl && !bl2 && this.withinContentAreaTopBottom(k, k + 9)) { guiGraphics.drawString(this.font, "_", j, k, -3092272); } if (this.textField.hasSelection()) { StringView stringView2 = this.textField.getSelected(); int m = this.getInnerLeft(); l = this.getInnerTop(); for (StringView stringView3 : this.textField.iterateLines()) { if (stringView2.beginIndex() > stringView3.endIndex()) { l += 9; } else { if (stringView3.beginIndex() > stringView2.endIndex()) { break; } if (this.withinContentAreaTopBottom(l, l + 9)) { int n = this.font.width(string.substring(stringView3.beginIndex(), Math.max(stringView2.beginIndex(), stringView3.beginIndex()))); int o; if (stringView2.endIndex() > stringView3.endIndex()) { o = this.width - this.innerPadding(); } else { o = this.font.width(string.substring(stringView3.beginIndex(), stringView2.endIndex())); } this.renderHighlight(guiGraphics, m + n, l, m + o, l + 9); } l += 9; } } } } } @Override protected void renderDecorations(GuiGraphics guiGraphics) { super.renderDecorations(guiGraphics); if (this.textField.hasCharacterLimit()) { int i = this.textField.characterLimit(); Component component = Component.translatable("gui.multiLineEditBox.character_limit", this.textField.value().length(), i); guiGraphics.drawString(this.font, component, this.getX() + this.width - this.font.width(component), this.getY() + this.height + 4, 10526880); } } @Override public int getInnerHeight() { return 9 * this.textField.getLineCount(); } @Override protected double scrollRate() { return 9.0 / 2.0; } private void renderHighlight(GuiGraphics guiGraphics, int minX, int minY, int maxX, int maxY) { guiGraphics.fill(RenderType.guiTextHighlight(), minX, minY, maxX, maxY, -16776961); } private void scrollToCursor() { double d = this.scrollAmount(); StringView stringView = this.textField.getLineView((int)(d / 9.0)); if (this.textField.cursor() <= stringView.beginIndex()) { d = this.textField.getLineAtCursor() * 9; } else { StringView stringView2 = this.textField.getLineView((int)((d + this.height) / 9.0) - 1); if (this.textField.cursor() > stringView2.endIndex()) { d = this.textField.getLineAtCursor() * 9 - this.height + 9 + this.totalInnerPadding(); } } this.setScrollAmount(d); } private void seekCursorScreen(double mouseX, double mouseY) { double d = mouseX - this.getX() - this.innerPadding(); double e = mouseY - this.getY() - this.innerPadding() + this.scrollAmount(); this.textField.seekCursorToPoint(d, e); } @Override public void setFocused(boolean focused) { super.setFocused(focused); if (focused) { this.focusedTime = Util.getMillis(); } } }