From 6ee3ef229a4e001f6d99ac512e3e6bcef9ac1faf Mon Sep 17 00:00:00 2001 From: johni0702 Date: Fri, 27 Nov 2015 15:22:06 +0100 Subject: [PATCH] Re-implement GuiTextField without depending on the Minecraft Text Field --- .../replaymod/online/gui/GuiLoginPrompt.java | 5 +- .../com/replaymod/online/gui/GuiRegister.java | 3 +- .../replay/gui/screen/GuiReplayViewer.java | 4 +- .../gui/element/AbstractGuiTextField.java | 611 ++++++++++++++---- .../minecraft/gui/element/IGuiTextField.java | 130 +++- .../minecraft/gui/utils/Consumers.java | 34 + 6 files changed, 648 insertions(+), 139 deletions(-) create mode 100644 src/main/java/de/johni0702/minecraft/gui/utils/Consumers.java diff --git a/src/main/java/com/replaymod/online/gui/GuiLoginPrompt.java b/src/main/java/com/replaymod/online/gui/GuiLoginPrompt.java index 29c98b1e..6a7aa5db 100755 --- a/src/main/java/com/replaymod/online/gui/GuiLoginPrompt.java +++ b/src/main/java/com/replaymod/online/gui/GuiLoginPrompt.java @@ -8,6 +8,7 @@ import de.johni0702.minecraft.gui.element.GuiLabel; import de.johni0702.minecraft.gui.element.GuiPasswordField; import de.johni0702.minecraft.gui.element.GuiTextField; import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.utils.Consumer; import eu.crushedpixel.replaymod.gui.GuiConstants; public class GuiLoginPrompt extends AbstractGuiScreen { @@ -72,9 +73,9 @@ public class GuiLoginPrompt extends AbstractGuiScreen { new GuiRegister(apiClient, GuiLoginPrompt.this).display(); } }); - Runnable contentValidation = new Runnable() { + Consumer contentValidation = new Consumer() { @Override - public void run() { + public void consume(String obj) { loginButton.setEnabled(!username.getText().isEmpty() && !password.getText().isEmpty()); } }; diff --git a/src/main/java/com/replaymod/online/gui/GuiRegister.java b/src/main/java/com/replaymod/online/gui/GuiRegister.java index 952d88fe..223e40d2 100644 --- a/src/main/java/com/replaymod/online/gui/GuiRegister.java +++ b/src/main/java/com/replaymod/online/gui/GuiRegister.java @@ -9,6 +9,7 @@ import de.johni0702.minecraft.gui.element.*; import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.utils.Consumers; import eu.crushedpixel.replaymod.gui.GuiConstants; import eu.crushedpixel.replaymod.utils.EmailAddressUtils; import eu.crushedpixel.replaymod.utils.RegexUtils; @@ -137,7 +138,7 @@ public class GuiRegister extends AbstractGuiScreen { } } }; - inputs.forEach(IGuiTextField.class).onTextChanged(contentValidation); + inputs.forEach(IGuiTextField.class).onTextChanged(Consumers.from(contentValidation)); cancelButton.onClick(new Runnable() { @Override diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index 1f08924c..42247517 100755 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -165,9 +165,9 @@ public class GuiReplayViewer extends GuiScreen { popup.getYesButton().onClick(); } } - }).onTextChanged(new Runnable() { + }).onTextChanged(new Consumer() { @Override - public void run() { + public void consume(String obj) { popup.getYesButton().setEnabled(!nameField.getText().isEmpty()); } }); diff --git a/src/main/java/de/johni0702/minecraft/gui/element/AbstractGuiTextField.java b/src/main/java/de/johni0702/minecraft/gui/element/AbstractGuiTextField.java index e803e7e4..96102cdd 100644 --- a/src/main/java/de/johni0702/minecraft/gui/element/AbstractGuiTextField.java +++ b/src/main/java/de/johni0702/minecraft/gui/element/AbstractGuiTextField.java @@ -22,6 +22,7 @@ package de.johni0702.minecraft.gui.element; +import com.google.common.base.Preconditions; import com.google.common.base.Strings; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.RenderInfo; @@ -30,84 +31,401 @@ import de.johni0702.minecraft.gui.function.Clickable; import de.johni0702.minecraft.gui.function.Focusable; import de.johni0702.minecraft.gui.function.Tickable; import de.johni0702.minecraft.gui.function.Typeable; +import de.johni0702.minecraft.gui.utils.Consumer; import lombok.Getter; -import net.minecraft.client.Minecraft; +import lombok.NonNull; import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; import net.minecraft.client.resources.I18n; +import net.minecraft.util.ChatAllowedCharacters; import org.lwjgl.input.Keyboard; -import org.lwjgl.util.Dimension; -import org.lwjgl.util.ReadableColor; -import org.lwjgl.util.ReadableDimension; -import org.lwjgl.util.ReadablePoint; +import org.lwjgl.opengl.GL11; +import org.lwjgl.util.*; + +import static net.minecraft.client.renderer.GlStateManager.*; +import static net.minecraft.client.renderer.GlStateManager.disableColorLogic; +import static net.minecraft.client.renderer.GlStateManager.enableTexture2D; +import static net.minecraft.util.MathHelper.clamp_int; public abstract class AbstractGuiTextField> extends AbstractGuiElement implements Clickable, Tickable, Typeable, IGuiTextField { - private final net.minecraft.client.gui.GuiTextField wrapped; - - private Runnable textChanged; - private Runnable onEnter; + private static final ReadableColor BORDER_COLOR = new Color(160, 160, 160); + private static final ReadableColor CURSOR_COLOR = new Color(240, 240, 240); + private static final int BORDER = 4; + // Focus + @Getter + private boolean focused; + @Getter private Focusable next, previous; + // Content + @Getter + private int maxLength = 32; + + @Getter + @NonNull + private String text = ""; + + private int cursorPos; + private int selectionPos; + @Getter private String hint; - @Getter - private ReadableColor textColor; + // Rendering + private int currentOffset; + private int blinkCursorTick; + public ReadableColor textColorEnabled = new Color(224, 224, 224); + public ReadableColor textColorDisabled = new Color(112, 112, 112); + private ReadableDimension size = new Dimension(0, 0); // Size of last render + + private Consumer textChanged; + private Runnable onEnter; public AbstractGuiTextField() { - this.wrapped = new net.minecraft.client.gui.GuiTextField(0, Minecraft.getMinecraft().fontRendererObj, 0, 0, 0, 0); } public AbstractGuiTextField(GuiContainer container) { super(container); - this.wrapped = new net.minecraft.client.gui.GuiTextField(0, Minecraft.getMinecraft().fontRendererObj, 0, 0, 0, 0); } @Override - public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) { - ReadablePoint position = renderer.getOpenGlOffset(); - wrapped.xPosition = position.getX() + 1; - wrapped.yPosition = position.getY() + 1; - wrapped.width = size.getWidth() - 2; - wrapped.height = size.getHeight() - 2; - - if (wrapped.text.isEmpty() && !isFocused() && !Strings.isNullOrEmpty(hint)) { - wrapped.setEnabled(false); - wrapped.setDisabledTextColour(0xff404040); - wrapped.text = hint; - wrapped.drawTextBox(); - wrapped.text = ""; - wrapped.setDisabledTextColour(0xff707070); - wrapped.setEnabled(isEnabled()); + public T setText(String text) { + if (text.length() > maxLength) { + this.text = text.substring(0, maxLength); } else { - wrapped.drawTextBox(); + this.text = text; + } + selectionPos = cursorPos = text.length(); + return getThis(); + } + + @Override + public T setI18nText(@NonNull String text, @NonNull Object... args) { + return setText(I18n.format(text, args)); + } + + @Override + public T setMaxLength(int maxLength) { + Preconditions.checkArgument(maxLength >= 0, "maxLength must not be negative"); + this.maxLength = maxLength; + if (text.length() > maxLength) { + setText(text); + } + return getThis(); + } + + @Override + public String deleteText(int from, int to) { + Preconditions.checkArgument(from <= to, "from must not be greater than to"); + Preconditions.checkArgument(from >= 0, "from must be greater than zero"); + Preconditions.checkArgument(to < text.length(), "to must be less than test.length()"); + + String deleted = text.substring(from, to + 1); + text = text.substring(0, from) + text.substring(to + 1); + return deleted; + } + + @Override + public int getSelectionFrom() { + return cursorPos > selectionPos ? selectionPos : cursorPos; + } + + @Override + public int getSelectionTo() { + return cursorPos > selectionPos ? cursorPos : selectionPos; + } + + @Override + public String getSelectedText() { + return text.substring(getSelectionFrom(), getSelectionTo()); + } + + @Override + public String deleteSelectedText() { + if (cursorPos == selectionPos) { + return ""; // Nothing selected + } + int from = getSelectionFrom(); + String deleted = deleteText(from, getSelectionTo() - 1); + cursorPos = selectionPos = from; + updateCurrentOffset(); + return deleted; + } + + /** + * Update current text offset to make sure the cursor is always visible. + */ + private void updateCurrentOffset() { + currentOffset = Math.min(currentOffset, cursorPos); + String line = text.substring(currentOffset, cursorPos); + FontRenderer fontRenderer = getMinecraft().fontRendererObj; + int currentWidth = fontRenderer.getStringWidth(line); + if (currentWidth > size.getWidth() - 2*BORDER) { + currentOffset = cursorPos - fontRenderer.trimStringToWidth(line, size.getWidth() - 2*BORDER, true).length(); } } @Override - public ReadableDimension calcMinSize() { - FontRenderer fontRenderer = getMinecraft().fontRendererObj; - return new Dimension(0, fontRenderer.FONT_HEIGHT); + public T writeText(String append) { + for (char c : append.toCharArray()) { + writeChar(c); + } + return getThis(); + } + + @Override + public T writeChar(char c) { + if (!ChatAllowedCharacters.isAllowedCharacter(c)) { + return getThis(); + } + + deleteSelectedText(); + + if (text.length() >= maxLength) { + return getThis(); + } + + text = text.substring(0, cursorPos) + c + text.substring(cursorPos); + selectionPos = ++cursorPos; + + updateCurrentOffset(); + return getThis(); + } + + @Override + public T deleteNextChar() { + if (cursorPos < text.length()) { + text = text.substring(0, cursorPos) + text.substring(cursorPos + 1); + } + selectionPos = cursorPos; + return getThis(); + } + + /** + * Return the amount of characters to the next word (excluding). + * If this is the last word in the line, return the amount of characters remaining to till the end. + * Everything except the Space character is considered part of a word. + * @return Length in characters + */ + protected int getNextWordLength() { + int length = 0; + boolean inWord = true; + for (int i = cursorPos; i < text.length(); i++) { + if (inWord) { + if (text.charAt(i) == ' ') { + inWord = false; + } + } else { + if (text.charAt(i) != ' ') { + return length; + } + } + length++; + } + return length; + } + + @Override + public String deleteNextWord() { + int worldLength = getNextWordLength(); + if (worldLength > 0) { + return deleteText(cursorPos, cursorPos + worldLength); + } + return ""; + } + + @Override + public T deletePreviousChar() { + if (cursorPos > 0) { + text = text.substring(0, cursorPos - 1) + text.substring(cursorPos); + selectionPos = --cursorPos; + updateCurrentOffset(); + } + return getThis(); + } + + /** + * Return the amount of characters to the previous word (including). + * If this is the first word in the line, return the amount of characters till the start. + * Everything except the Space character is considered part of a word. + * @return Length in characters + */ + protected int getPreviousWordLength() { + int length = 0; + boolean inWord = false; + for (int i = cursorPos - 1; i >= 0; i--) { + if (inWord) { + if (text.charAt(i) == ' ') { + return length; + } + } else { + if (text.charAt(i) != ' ') { + inWord = true; + } + } + length++; + } + return length; + } + + @Override + public String deletePreviousWord() { + int worldLength = getPreviousWordLength(); + String deleted = ""; + if (worldLength > 0) { + deleted = deleteText(cursorPos - worldLength, cursorPos); + selectionPos = cursorPos -= worldLength; + updateCurrentOffset(); + } + return deleted; + } + + @Override + public T setCursorPosition(int pos) { + Preconditions.checkArgument(pos >= 0 && pos <= text.length()); + selectionPos = cursorPos = pos; + updateCurrentOffset(); + return getThis(); + } + + /** + * Inverts all colors on the screen. + * @param guiRenderer The GUI Renderer + * @param right Right border of the inverted rectangle + * @param bottom Bottom border of the inverted rectangle + * @param left Left border of the inverted rectangle + * @param top Top border of the inverted rectangle + */ + private void invertColors(GuiRenderer guiRenderer, int right, int bottom, int left, int top) { + int x = guiRenderer.getOpenGlOffset().getX(); + int y = guiRenderer.getOpenGlOffset().getY(); + right+=x; + left+=x; + bottom+=y; + top+=y; + + color(0, 0, 255, 255); + disableTexture2D(); + enableColorLogic(); + colorLogicOp(GL11.GL_OR_REVERSE); + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer renderer = tessellator.getWorldRenderer(); + renderer.startDrawingQuads(); + renderer.addVertex(right, top, 0); + renderer.addVertex(left, top, 0); + renderer.addVertex(left, bottom, 0); + renderer.addVertex(right, bottom, 0); + tessellator.draw(); + + disableColorLogic(); + enableTexture2D(); + } + + @Override + protected ReadableDimension calcMinSize() { + return new Dimension(0, 0); } @Override public boolean mouseClick(ReadablePoint position, int button) { - wrapped.mouseClicked(position.getX(), position.getY(), button); - return false; + if (getContainer() != null) { + getContainer().convertFor(this, (Point) (position = new Point(position))); + } + boolean hovering = isMouseHovering(position); + + if (hovering && isFocused() && button == 0) { + int mouseX = position.getX() - BORDER; + FontRenderer fontRenderer = getMinecraft().fontRendererObj; + String text = this.text.substring(currentOffset); + int textX = fontRenderer.trimStringToWidth(text, mouseX).length() + currentOffset; + setCursorPosition(textX); + } + + setFocused(hovering); + return hovering; + } + + protected boolean isMouseHovering(ReadablePoint pos) { + return pos.getX() > 0 && pos.getY() > 0 + && pos.getX() < size.getWidth() && pos.getY() < size.getHeight(); + } + + @Override + public T setFocused(boolean isFocused) { + if (isFocused && !this.focused) { + this.blinkCursorTick = 0; // Restart blinking to indicate successful focus + } + this.focused = isFocused; + return getThis(); + } + + @Override + public T setNext(Focusable next) { + this.next = next; + return getThis(); + } + + @Override + public T setPrevious(Focusable previous) { + this.previous = previous; + return getThis(); + } + + @Override + public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) { + this.size = size; + int width = size.getWidth(), height = size.getHeight(); + FontRenderer fontRenderer = getMinecraft().fontRendererObj; + int posY = height / 2 - fontRenderer.FONT_HEIGHT / 2; + + // Draw black rect once pixel smaller than gray rect + renderer.drawRect(0, 0, width, height, BORDER_COLOR); + renderer.drawRect(1, 1, width - 2, height - 2, ReadableColor.BLACK); + + if (text.isEmpty() && !isFocused() && !Strings.isNullOrEmpty(hint)) { + // Draw hint + String text = fontRenderer.trimStringToWidth(hint, width - 2*BORDER); + renderer.drawString(BORDER, posY, textColorDisabled, text); + } else { + // Draw text + String renderText = text.substring(currentOffset); + renderText = fontRenderer.trimStringToWidth(renderText, width - 2*BORDER); + ReadableColor color = isEnabled() ? textColorEnabled : textColorDisabled; + int lineEnd = renderer.drawString(BORDER, height / 2 - fontRenderer.FONT_HEIGHT / 2, color, renderText); + + // Draw selection + int from = getSelectionFrom(); + int to = getSelectionTo(); + String leftStr = text.substring(0, clamp_int(from - currentOffset, 0, text.length())); + String rightStr = text.substring(clamp_int(to - currentOffset, 0, text.length())); + int left = BORDER + fontRenderer.getStringWidth(leftStr); + int right = lineEnd - fontRenderer.getStringWidth(rightStr) - 1; + invertColors(renderer, right, height - 2, left, 2); + + // Draw cursor + if (blinkCursorTick / 6 % 2 == 0 && focused) { + String beforeCursor = text.substring(0, cursorPos - currentOffset); + int posX = BORDER + fontRenderer.getStringWidth(beforeCursor); + if (cursorPos == text.length()) { + renderer.drawString(posX, posY, CURSOR_COLOR, "_", true); + } else { + renderer.drawRect(posX, posY - 1, 1, 1 + fontRenderer.FONT_HEIGHT, CURSOR_COLOR); + } + } + } } @Override public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) { - if (!isFocused()) { + if (!this.focused) { return false; } - if (keyCode == Keyboard.KEY_RETURN) { - onEnter(); - return true; - } - if (keyCode == Keyboard.KEY_TAB) { Focusable other = shiftDown ? previous : next; if (other != null) { @@ -117,106 +435,131 @@ public abstract class AbstractGuiTextField> return true; } - String before = wrapped.getText(); - wrapped.textboxKeyTyped(keyChar, keyCode); - String after = wrapped.getText(); - if (!before.equals(after)) { - if (textChanged != null) { - textChanged.run(); + if (keyCode == Keyboard.KEY_RETURN) { + onEnter(); + return true; + } + + String textBefore = text; + try { + if (GuiScreen.isCtrlKeyDown()) { + switch (keyCode) { + case Keyboard.KEY_A: // Select all + cursorPos = 0; + selectionPos = text.length(); + updateCurrentOffset(); + return true; + case Keyboard.KEY_C: // Copy + GuiScreen.setClipboardString(getSelectedText()); + return true; + case Keyboard.KEY_V: // Paste + if (isEnabled()) { + writeText(GuiScreen.getClipboardString()); + } + return true; + case Keyboard.KEY_X: // Cut + if (isEnabled()) { + GuiScreen.setClipboardString(deleteSelectedText()); + } + return true; + } + } + + boolean words = GuiScreen.isCtrlKeyDown(); + boolean select = GuiScreen.isShiftKeyDown(); + switch (keyCode) { + case Keyboard.KEY_HOME: + cursorPos = 0; + break; + case Keyboard.KEY_END: + cursorPos = text.length(); + break; + case Keyboard.KEY_LEFT: + if (cursorPos != 0) { + if (words) { + cursorPos -= getPreviousWordLength(); + } else { + cursorPos--; + } + } + break; + case Keyboard.KEY_RIGHT: + if (cursorPos != text.length()) { + if (words) { + cursorPos += getNextWordLength(); + } else { + cursorPos++; + } + } + break; + case Keyboard.KEY_BACK: + if (isEnabled()) { + if (getSelectedText().length() > 0) { + deleteSelectedText(); + } else if (words) { + deletePreviousWord(); + } else { + deletePreviousChar(); + } + } + return true; + case Keyboard.KEY_DELETE: + if (isEnabled()) { + if (getSelectedText().length() > 0) { + deleteSelectedText(); + } else if (words) { + deleteNextWord(); + } else { + deleteNextChar(); + } + } + return true; + default: + if (isEnabled()) { + if (keyChar == '\r') { + keyChar = '\n'; + } + writeChar(keyChar); + } + return true; + } + + updateCurrentOffset(); + + if (!select) { + selectionPos = cursorPos; + } + return true; + } finally { + if (!textBefore.equals(text)) { + onTextChanged(textBefore); } } - return true; - } - - @Override - public T setText(String text) { - if (text.length() > wrapped.getMaxStringLength()) { - wrapped.text = text.substring(0, wrapped.getMaxStringLength()); - } else { - wrapped.text = text; - } - return getThis(); - } - - @Override - public T setI18nText(String text, Object... args) { - return setText(I18n.format(text, args)); - } - - @Override - public String getText() { - return wrapped.text; - } - - @Override - public boolean isFocused() { - return wrapped.isFocused(); - } - - @Override - public T setFocused(boolean focused) { - wrapped.setFocused(focused); - return getThis(); - } - - @Override - public Focusable getNext() { - return next; - } - - @Override - public T setNext(Focusable next) { - if (this.next == next) return getThis(); - if (this.next != null) { - this.next.setPrevious(null); - } - this.next = next; - if (this.next != null) { - this.next.setPrevious(this); - } - return getThis(); - } - - @Override - public Focusable getPrevious() { - return previous; - } - - @Override - public T setPrevious(Focusable previous) { - if (this.previous == previous) return getThis(); - if (this.previous != null) { - this.previous.setNext(null); - } - this.previous = previous; - if (this.previous != null) { - this.previous.setNext(this); - } - return getThis(); - } - - @Override - public int getMaxLength() { - return wrapped.getMaxStringLength(); - } - - @Override - public T setMaxLength(int maxLength) { - wrapped.setMaxStringLength(maxLength); - return getThis(); } @Override public void tick() { - wrapped.updateCursorCounter(); + blinkCursorTick++; } + /** + * Called when the user presses the Enter/Return key while this text field is focused. + */ protected void onEnter() { if (onEnter != null) { onEnter.run(); } } + /** + * Called when the text has changed due to user input. + */ + protected void onTextChanged(String from) { + if (textChanged != null) { + textChanged.consume(from); + } + } + @Override public T onEnter(Runnable onEnter) { this.onEnter = onEnter; @@ -224,7 +567,7 @@ public abstract class AbstractGuiTextField> } @Override - public T onTextChanged(Runnable textChanged) { + public T onTextChanged(Consumer textChanged) { this.textChanged = textChanged; return getThis(); } @@ -240,10 +583,20 @@ public abstract class AbstractGuiTextField> return setHint(I18n.format(hint)); } + @Override + public ReadableColor getTextColor() { + return textColorEnabled; + } + @Override public T setTextColor(ReadableColor textColor) { - this.textColor = textColor; - wrapped.setTextColor(textColor.getAlpha() << 24 | textColor.getRed() << 16 | textColor.getGreen() << 8 | textColor.getBlue()); + this.textColorEnabled = textColor; + return getThis(); + } + + @Override + public T setTextColorDisabled(ReadableColor textColorDisabled) { + this.textColorDisabled = textColorDisabled; return getThis(); } } diff --git a/src/main/java/de/johni0702/minecraft/gui/element/IGuiTextField.java b/src/main/java/de/johni0702/minecraft/gui/element/IGuiTextField.java index b89a41ed..567660e7 100644 --- a/src/main/java/de/johni0702/minecraft/gui/element/IGuiTextField.java +++ b/src/main/java/de/johni0702/minecraft/gui/element/IGuiTextField.java @@ -23,24 +23,144 @@ package de.johni0702.minecraft.gui.element; import de.johni0702.minecraft.gui.function.Focusable; +import de.johni0702.minecraft.gui.utils.Consumer; +import lombok.NonNull; import org.lwjgl.util.ReadableColor; public interface IGuiTextField> extends GuiElement, Focusable { - T setText(String text); - T setI18nText(String text, Object... args); - String getText(); + /** + * Set the text to the specified string. + * If the string is longer than {@link #getMaxLength()} it is truncated from the end. + * This method positions the cursor at the end of the text and removes any selections. + * @param text The new text + * @return {@code this} for chaining + */ + @NonNull T setText(@NonNull String text); + /** + * Set the text to the specified string. + * If the string is longer than {@link #getMaxLength()} it is truncated from the end. + * This method positions the cursor at the end of the text and removes any selections. + * @param text The language key for the new text + * @param args The arguments used in translating the language key + * @return {@code this} for chaining + */ + @NonNull T setI18nText(@NonNull String text, @NonNull Object... args); + + /** + * Return the whole text in this text field. + * @return The text, may be empty + */ + @NonNull String getText(); + + /** + * Return the maximum allowed length of the text in this text field. + * @return Maximum number of characters + */ int getMaxLength(); + + /** + * Set the maximum allowed length of the text in this text field. + * If the current test is longer than the new limit, it is truncated from the end (the cursor and selection + * are reset in that process, see {@link #setText(String)}). + * @param maxLength Maximum number of characters + * @return {@code this} for chaining + * @throws IllegalArgumentException When {@code maxLength} is negative + */ T setMaxLength(int maxLength); + /** + * Deletes the text between {@code from} and {@code to} (inclusive). + * @param from Index at which to start + * @param to Index to which to delete + * @return The deleted text + * @throws IllegalArgumentException If {@code from} is greater than {@code to} or either is out of bounds + */ + @NonNull String deleteText(int from, int to); + + /** + * Return the index at which the selection starts (inclusive) + * @return Index of first character + */ + int getSelectionFrom(); + + /** + * Return the index at which the selection ends (exclusive) + * @return Index after the last character + */ + int getSelectionTo(); + + /** + * Return the selected text. + * @return The selected text + */ + @NonNull String getSelectedText(); + + /** + * Delete the selected text. Positions the cursor at the beginning of the selection and clears the selection. + * @return The deleted text + */ + @NonNull String deleteSelectedText(); + + /** + * Appends the specified string to this text field character by character. + * Excess characters are ignored. + * @param append String to append + * @return {@code this} for chaining + * @see #writeChar(char) + */ + @NonNull T writeText(@NonNull String append); + + /** + * Appends the specified character to this text field replacing the current selection (if any). + * This does nothing if the maximum character limit is reached. + * @param c Character to append + * @return {@code this} for chaining + */ + @NonNull T writeChar(char c); + + /** + * Delete the nex character (if any). + * Clears the selection. + * @return {@code this} for chaining + */ + T deleteNextChar(); + + /** + * Delete everything from the cursor (inclusive) to the beginning of the next word (exclusive). + * If there are no more words, delete everything until the end of the line. + * @return The deleted text + */ + String deleteNextWord(); + + /** + * Delete the previous character (if any). + * @return {@code this} for chaining + */ + @NonNull T deletePreviousChar(); + + /** + * Delete everything from cursor to the first character of the previous word (or the start of the line). + * @return The deleted text + */ + @NonNull String deletePreviousWord(); + + /** + * Set the cursor position. + * @param pos Position of the cursor + * @return {@code this} for chaining + * @throws IllegalArgumentException If {@code pos} < 0 or pos > length + */ + @NonNull T setCursorPosition(int pos); + T onEnter(Runnable onEnter); - T onTextChanged(Runnable textChanged); + T onTextChanged(Consumer textChanged); String getHint(); T setHint(String hint); - T setI18nHint(String hint, Object... args); ReadableColor getTextColor(); T setTextColor(ReadableColor textColor); + T setTextColorDisabled(ReadableColor textColorDisabled); } diff --git a/src/main/java/de/johni0702/minecraft/gui/utils/Consumers.java b/src/main/java/de/johni0702/minecraft/gui/utils/Consumers.java new file mode 100644 index 00000000..0410ddc4 --- /dev/null +++ b/src/main/java/de/johni0702/minecraft/gui/utils/Consumers.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 johni0702 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package de.johni0702.minecraft.gui.utils; + +public class Consumers { + public static Consumer from(final Runnable runnable) { + return new Consumer() { + @Override + public void consume(U obj) { + runnable.run(); + } + }; + } +}