Re-implement GuiTextField without depending on the Minecraft Text Field
This commit is contained in:
@@ -8,6 +8,7 @@ import de.johni0702.minecraft.gui.element.GuiLabel;
|
|||||||
import de.johni0702.minecraft.gui.element.GuiPasswordField;
|
import de.johni0702.minecraft.gui.element.GuiPasswordField;
|
||||||
import de.johni0702.minecraft.gui.element.GuiTextField;
|
import de.johni0702.minecraft.gui.element.GuiTextField;
|
||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
|
import de.johni0702.minecraft.gui.utils.Consumer;
|
||||||
import eu.crushedpixel.replaymod.gui.GuiConstants;
|
import eu.crushedpixel.replaymod.gui.GuiConstants;
|
||||||
|
|
||||||
public class GuiLoginPrompt extends AbstractGuiScreen<GuiLoginPrompt> {
|
public class GuiLoginPrompt extends AbstractGuiScreen<GuiLoginPrompt> {
|
||||||
@@ -72,9 +73,9 @@ public class GuiLoginPrompt extends AbstractGuiScreen<GuiLoginPrompt> {
|
|||||||
new GuiRegister(apiClient, GuiLoginPrompt.this).display();
|
new GuiRegister(apiClient, GuiLoginPrompt.this).display();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Runnable contentValidation = new Runnable() {
|
Consumer<String> contentValidation = new Consumer<String>() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void consume(String obj) {
|
||||||
loginButton.setEnabled(!username.getText().isEmpty() && !password.getText().isEmpty());
|
loginButton.setEnabled(!username.getText().isEmpty() && !password.getText().isEmpty());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import de.johni0702.minecraft.gui.element.*;
|
|||||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||||
|
import de.johni0702.minecraft.gui.utils.Consumers;
|
||||||
import eu.crushedpixel.replaymod.gui.GuiConstants;
|
import eu.crushedpixel.replaymod.gui.GuiConstants;
|
||||||
import eu.crushedpixel.replaymod.utils.EmailAddressUtils;
|
import eu.crushedpixel.replaymod.utils.EmailAddressUtils;
|
||||||
import eu.crushedpixel.replaymod.utils.RegexUtils;
|
import eu.crushedpixel.replaymod.utils.RegexUtils;
|
||||||
@@ -137,7 +138,7 @@ public class GuiRegister extends AbstractGuiScreen<GuiRegister> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
inputs.forEach(IGuiTextField.class).onTextChanged(contentValidation);
|
inputs.forEach(IGuiTextField.class).onTextChanged(Consumers.from(contentValidation));
|
||||||
|
|
||||||
cancelButton.onClick(new Runnable() {
|
cancelButton.onClick(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -165,9 +165,9 @@ public class GuiReplayViewer extends GuiScreen {
|
|||||||
popup.getYesButton().onClick();
|
popup.getYesButton().onClick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).onTextChanged(new Runnable() {
|
}).onTextChanged(new Consumer<String>() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void consume(String obj) {
|
||||||
popup.getYesButton().setEnabled(!nameField.getText().isEmpty());
|
popup.getYesButton().setEnabled(!nameField.getText().isEmpty());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
package de.johni0702.minecraft.gui.element;
|
package de.johni0702.minecraft.gui.element;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||||
import de.johni0702.minecraft.gui.RenderInfo;
|
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.Focusable;
|
||||||
import de.johni0702.minecraft.gui.function.Tickable;
|
import de.johni0702.minecraft.gui.function.Tickable;
|
||||||
import de.johni0702.minecraft.gui.function.Typeable;
|
import de.johni0702.minecraft.gui.function.Typeable;
|
||||||
|
import de.johni0702.minecraft.gui.utils.Consumer;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.minecraft.client.Minecraft;
|
import lombok.NonNull;
|
||||||
import net.minecraft.client.gui.FontRenderer;
|
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.client.resources.I18n;
|
||||||
|
import net.minecraft.util.ChatAllowedCharacters;
|
||||||
import org.lwjgl.input.Keyboard;
|
import org.lwjgl.input.Keyboard;
|
||||||
import org.lwjgl.util.Dimension;
|
import org.lwjgl.opengl.GL11;
|
||||||
import org.lwjgl.util.ReadableColor;
|
import org.lwjgl.util.*;
|
||||||
import org.lwjgl.util.ReadableDimension;
|
|
||||||
import org.lwjgl.util.ReadablePoint;
|
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<T extends AbstractGuiTextField<T>>
|
public abstract class AbstractGuiTextField<T extends AbstractGuiTextField<T>>
|
||||||
extends AbstractGuiElement<T> implements Clickable, Tickable, Typeable, IGuiTextField<T> {
|
extends AbstractGuiElement<T> implements Clickable, Tickable, Typeable, IGuiTextField<T> {
|
||||||
private final net.minecraft.client.gui.GuiTextField wrapped;
|
private static final ReadableColor BORDER_COLOR = new Color(160, 160, 160);
|
||||||
|
private static final ReadableColor CURSOR_COLOR = new Color(240, 240, 240);
|
||||||
private Runnable textChanged;
|
private static final int BORDER = 4;
|
||||||
private Runnable onEnter;
|
|
||||||
|
|
||||||
|
// Focus
|
||||||
|
@Getter
|
||||||
|
private boolean focused;
|
||||||
|
@Getter
|
||||||
private Focusable next, previous;
|
private Focusable next, previous;
|
||||||
|
|
||||||
|
// Content
|
||||||
|
@Getter
|
||||||
|
private int maxLength = 32;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@NonNull
|
||||||
|
private String text = "";
|
||||||
|
|
||||||
|
private int cursorPos;
|
||||||
|
private int selectionPos;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private String hint;
|
private String hint;
|
||||||
|
|
||||||
@Getter
|
// Rendering
|
||||||
private ReadableColor textColor;
|
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<String> textChanged;
|
||||||
|
private Runnable onEnter;
|
||||||
|
|
||||||
public AbstractGuiTextField() {
|
public AbstractGuiTextField() {
|
||||||
this.wrapped = new net.minecraft.client.gui.GuiTextField(0, Minecraft.getMinecraft().fontRendererObj, 0, 0, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbstractGuiTextField(GuiContainer container) {
|
public AbstractGuiTextField(GuiContainer container) {
|
||||||
super(container);
|
super(container);
|
||||||
this.wrapped = new net.minecraft.client.gui.GuiTextField(0, Minecraft.getMinecraft().fontRendererObj, 0, 0, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
public T setText(String text) {
|
||||||
ReadablePoint position = renderer.getOpenGlOffset();
|
if (text.length() > maxLength) {
|
||||||
wrapped.xPosition = position.getX() + 1;
|
this.text = text.substring(0, maxLength);
|
||||||
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());
|
|
||||||
} else {
|
} 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
|
@Override
|
||||||
public ReadableDimension calcMinSize() {
|
public T writeText(String append) {
|
||||||
FontRenderer fontRenderer = getMinecraft().fontRendererObj;
|
for (char c : append.toCharArray()) {
|
||||||
return new Dimension(0, fontRenderer.FONT_HEIGHT);
|
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
|
@Override
|
||||||
public boolean mouseClick(ReadablePoint position, int button) {
|
public boolean mouseClick(ReadablePoint position, int button) {
|
||||||
wrapped.mouseClicked(position.getX(), position.getY(), button);
|
if (getContainer() != null) {
|
||||||
return false;
|
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
|
@Override
|
||||||
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
|
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
|
||||||
if (!isFocused()) {
|
if (!this.focused) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyCode == Keyboard.KEY_RETURN) {
|
|
||||||
onEnter();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyCode == Keyboard.KEY_TAB) {
|
if (keyCode == Keyboard.KEY_TAB) {
|
||||||
Focusable other = shiftDown ? previous : next;
|
Focusable other = shiftDown ? previous : next;
|
||||||
if (other != null) {
|
if (other != null) {
|
||||||
@@ -117,106 +435,131 @@ public abstract class AbstractGuiTextField<T extends AbstractGuiTextField<T>>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String before = wrapped.getText();
|
if (keyCode == Keyboard.KEY_RETURN) {
|
||||||
wrapped.textboxKeyTyped(keyChar, keyCode);
|
onEnter();
|
||||||
String after = wrapped.getText();
|
return true;
|
||||||
if (!before.equals(after)) {
|
}
|
||||||
if (textChanged != null) {
|
|
||||||
textChanged.run();
|
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
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
wrapped.updateCursorCounter();
|
blinkCursorTick++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the user presses the Enter/Return key while this text field is focused.
|
||||||
|
*/
|
||||||
protected void onEnter() {
|
protected void onEnter() {
|
||||||
if (onEnter != null) {
|
if (onEnter != null) {
|
||||||
onEnter.run();
|
onEnter.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the text has changed due to user input.
|
||||||
|
*/
|
||||||
|
protected void onTextChanged(String from) {
|
||||||
|
if (textChanged != null) {
|
||||||
|
textChanged.consume(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T onEnter(Runnable onEnter) {
|
public T onEnter(Runnable onEnter) {
|
||||||
this.onEnter = onEnter;
|
this.onEnter = onEnter;
|
||||||
@@ -224,7 +567,7 @@ public abstract class AbstractGuiTextField<T extends AbstractGuiTextField<T>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T onTextChanged(Runnable textChanged) {
|
public T onTextChanged(Consumer<String> textChanged) {
|
||||||
this.textChanged = textChanged;
|
this.textChanged = textChanged;
|
||||||
return getThis();
|
return getThis();
|
||||||
}
|
}
|
||||||
@@ -240,10 +583,20 @@ public abstract class AbstractGuiTextField<T extends AbstractGuiTextField<T>>
|
|||||||
return setHint(I18n.format(hint));
|
return setHint(I18n.format(hint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReadableColor getTextColor() {
|
||||||
|
return textColorEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T setTextColor(ReadableColor textColor) {
|
public T setTextColor(ReadableColor textColor) {
|
||||||
this.textColor = textColor;
|
this.textColorEnabled = textColor;
|
||||||
wrapped.setTextColor(textColor.getAlpha() << 24 | textColor.getRed() << 16 | textColor.getGreen() << 8 | textColor.getBlue());
|
return getThis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T setTextColorDisabled(ReadableColor textColorDisabled) {
|
||||||
|
this.textColorDisabled = textColorDisabled;
|
||||||
return getThis();
|
return getThis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,24 +23,144 @@
|
|||||||
package de.johni0702.minecraft.gui.element;
|
package de.johni0702.minecraft.gui.element;
|
||||||
|
|
||||||
import de.johni0702.minecraft.gui.function.Focusable;
|
import de.johni0702.minecraft.gui.function.Focusable;
|
||||||
|
import de.johni0702.minecraft.gui.utils.Consumer;
|
||||||
|
import lombok.NonNull;
|
||||||
import org.lwjgl.util.ReadableColor;
|
import org.lwjgl.util.ReadableColor;
|
||||||
|
|
||||||
public interface IGuiTextField<T extends IGuiTextField<T>> extends GuiElement<T>, Focusable<T> {
|
public interface IGuiTextField<T extends IGuiTextField<T>> extends GuiElement<T>, Focusable<T> {
|
||||||
T setText(String text);
|
/**
|
||||||
T setI18nText(String text, Object... args);
|
* Set the text to the specified string.
|
||||||
String getText();
|
* 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();
|
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);
|
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 onEnter(Runnable onEnter);
|
||||||
T onTextChanged(Runnable textChanged);
|
T onTextChanged(Consumer<String> textChanged);
|
||||||
|
|
||||||
String getHint();
|
String getHint();
|
||||||
T setHint(String hint);
|
T setHint(String hint);
|
||||||
|
|
||||||
T setI18nHint(String hint, Object... args);
|
T setI18nHint(String hint, Object... args);
|
||||||
|
|
||||||
ReadableColor getTextColor();
|
ReadableColor getTextColor();
|
||||||
T setTextColor(ReadableColor textColor);
|
T setTextColor(ReadableColor textColor);
|
||||||
|
T setTextColorDisabled(ReadableColor textColorDisabled);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 <U> Consumer<U> from(final Runnable runnable) {
|
||||||
|
return new Consumer<U>() {
|
||||||
|
@Override
|
||||||
|
public void consume(U obj) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user