package cofh.lib.gui.element;
import static org.lwjgl.opengl.GL11.*;
import cofh.lib.gui.GuiBase;
import cofh.lib.gui.GuiColor;
import cofh.lib.util.helpers.MathHelper;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.util.ChatAllowedCharacters;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.GL11;
public class ElementTextField extends ElementBase {
public int borderColor = new GuiColor( 55, 55, 55).getColor();
public int backgroundColor = new GuiColor(139, 139, 139).getColor();
public int disabledColor = new GuiColor(198, 198, 198).getColor();
public int selectedLineColor = new GuiColor(160, 160, 224).getColor();
public int textColor = new GuiColor(224, 224, 224).getColor();
public int selectedTextColor = new GuiColor(224, 224, 224).getColor();
public int defaultCaretColor = new GuiColor(255, 255, 255).getColor();
protected char[] text;
protected int textLength;
protected int selectionStart, selectionEnd;
protected int renderStart, caret;
private boolean isFocused;
private boolean canFocusChange = true;
private boolean selecting;
private byte caretCounter;
protected boolean caretInsert;
protected boolean smartCaret = true;
protected boolean smartCaretCase = true;
protected boolean enableStencil = true;
public ElementTextField(GuiBase gui, int posX, int posY, int width, int height) {
this(gui, posX, posY, width, height, (short) 32);
}
public ElementTextField(GuiBase gui, int posX, int posY, int width, int height, short limit) {
super(gui, posX, posY, width, height);
setMaxLength(limit);
}
public ElementTextField setTextColor(Number textColor, Number selectedTextColor) {
if (textColor != null)
this.textColor = textColor.intValue();
if (selectedTextColor != null)
this.selectedTextColor = selectedTextColor.intValue();
return this;
}
public ElementTextField setSelectionColor(Number selectedLineColor, Number defaultCaretColor) {
if (selectedLineColor != null)
this.selectedLineColor = selectedLineColor.intValue();
if (defaultCaretColor != null)
this.defaultCaretColor = defaultCaretColor.intValue();
return this;
}
public ElementTextField setBackgroundColor(Number backgroundColor, Number disabledColor, Number borderColor) {
if (backgroundColor != null)
this.backgroundColor = backgroundColor.intValue();
if (disabledColor != null)
this.disabledColor = disabledColor.intValue();
if (borderColor != null)
this.borderColor = borderColor.intValue();
return this;
}
public ElementTextField setFocusable(boolean focusable) {
canFocusChange = focusable;
return this;
}
public ElementTextField setFocused(boolean focused) {
if (canFocusChange) {
isFocused = focused;
caretCounter = 0;
}
return this;
}
public ElementTextField setText(String text) {
selectionStart = 0;
selectionEnd = textLength;
writeText(text);
return this;
}
public ElementTextField setMaxLength(short limit) {
char[] oldText = text;
text = new char[limit];
textLength = Math.min(limit, textLength);
if (oldText != null)
System.arraycopy(oldText, 0, text, 0, textLength);
findRenderStart();
return this;
}
public int getMaxStringLength() {
return text.length;
}
public boolean isFocused() {
return isEnabled() && isFocused;
}
public boolean isFocusable() {
return canFocusChange;
}
public int getContentWidth() {
FontRenderer font = getFontRenderer();
int width = 0;
for (int i = 0; i < textLength; ++i) {
width += font.getCharWidth(text[i]);
}
return width;
}
public int getVisibleWidth() {
FontRenderer font = getFontRenderer();
int width = 0, endX = sizeX - 1;
for (int i = renderStart; i < textLength; ++i) {
int charW = font.getCharWidth(text[i]);
if (!enableStencil && (width + charW) > endX)
break;
width += charW;
if (width >= endX) {
width = Math.min(width, endX);
break;
}
}
return width;
}
public String getText() {
return new String(text, 0, textLength);
}
public String getSelectedText() {
if (selectionStart != selectionEnd) {
return new String(text, selectionStart, selectionEnd);
}
return getText();
}
public void writeText(String text) {
int i = 0;
for (int e = text.length(); i < e; ++i)
if (!insertCharacter(text.charAt(i)))
break;
clearSelection();
findRenderStart();
onCharacterEntered(i > 0);
}
public boolean isAllowedCharacter(char charTyped) {
return ChatAllowedCharacters.isAllowedCharacter(charTyped);
}
protected boolean onEnter() {
return false;
}
protected void onFocusLost() {
}
protected void onCharacterEntered(boolean success) {
}
protected boolean insertCharacter(char charTyped) {
if (isAllowedCharacter(charTyped)) {
if (selectionStart != selectionEnd) {
if (caret == selectionStart)
++caret;
text[selectionStart++] = charTyped;
return true;
}
if ((caretInsert && caret == text.length) || textLength == text.length)
return false;
if (!caretInsert) {
if (caret < textLength)
System.arraycopy(text, caret, text, caret + 1, textLength - caret);
++textLength;
}
text[caret++] = charTyped;
return true;
} else
return true;
}
protected void findRenderStart() {
caret = MathHelper.clampI(caret, 0, textLength);
if (caret < renderStart) {
renderStart = caret;
return;
}
FontRenderer font = getFontRenderer();
int endX = sizeX - 2;
for (int i = renderStart, width = 0; i < caret; ++i) {
width += font.getCharWidth(text[i]);
while (width >= endX) {
width -= font.getCharWidth(text[renderStart++]);
if (renderStart >= textLength)
return;
}
}
}
protected void clearSelection() {
if (selectionStart != selectionEnd) {
if (selectionEnd < textLength)
System.arraycopy(text, selectionEnd, text, selectionStart, textLength - selectionEnd);
textLength -= selectionEnd - selectionStart;
selectionEnd = caret = selectionStart;
findRenderStart();
}
}
protected final int seekNextCaretLocation(int pos) {
return seekNextCaretLocation(pos, true);
}
protected int seekNextCaretLocation(int pos, boolean forward) {
int dir = forward ? 1 : -1;
int e = forward ? textLength : 0;
if (pos == textLength) --pos;
char prevChar = text[pos];
while (pos != e && Character.isSpaceChar(prevChar))
prevChar = text[pos += dir];
if (smartCaret) {
for (int i = pos; i != e; i += dir) {
char curChar = text[i];
boolean caze = Character.isUpperCase(curChar) != Character.isUpperCase(prevChar);
if (caze || Character.isSpaceChar(curChar) != Character.isSpaceChar(prevChar) ||
Character.isLetterOrDigit(curChar) != Character.isLetterOrDigit(prevChar))
if ((pos + dir) != i || !Character.isLetterOrDigit(curChar))
return i + (smartCaretCase && caze && Character.isUpperCase(prevChar) ? -dir : 0);
prevChar = curChar;
}
}
for (int i = pos; i != e; i += dir) {
char curChar = text[i];
if (Character.isSpaceChar(curChar) != Character.isSpaceChar(prevChar))
return i;
}
return forward ? textLength : 0;
}
@Override
public boolean onKeyTyped(char charTyped, int keyTyped) {
if (!isFocused())
return false;
switch (charTyped) {
case 1: // ^A
selectionEnd = caret = textLength;
selectionStart = 0;
findRenderStart();
return true;
case 3: // ^C
if (selectionStart != selectionEnd) {
GuiScreen.setClipboardString(getSelectedText());
}
return true;
case 24: // ^X
if (selectionStart != selectionEnd) {
GuiScreen.setClipboardString(getSelectedText());
clearSelection();
}
return true;
case 22: // ^V
writeText(GuiScreen.getClipboardString());
return true;
default:
switch (keyTyped) {
case Keyboard.KEY_ESCAPE:
setFocused(false);
return !isFocused();
case Keyboard.KEY_RETURN:
case Keyboard.KEY_NUMPADENTER:
return onEnter();
case Keyboard.KEY_INSERT:
if (GuiScreen.isShiftKeyDown()) {
writeText(GuiScreen.getClipboardString());
} else {
caretInsert = !caretInsert;
}
return true;
case Keyboard.KEY_CLEAR: // mac only (clear selection)
clearSelection();
return true;
case Keyboard.KEY_DELETE: // delete
if (!GuiScreen.isShiftKeyDown()) {
if (selectionStart != selectionEnd)
clearSelection();
else if (GuiScreen.isCtrlKeyDown()) {
int size = seekNextCaretLocation(caret, true) - caret;
selectionStart = caret;
selectionEnd = caret + size;
clearSelection();
} else {
if (caret < textLength && textLength > 0) {
--textLength;
System.arraycopy(text, caret + 1, text, caret, textLength - caret);
}
}
if (caret <= renderStart)
renderStart = MathHelper.clampI(caret - 3, 0, textLength);
findRenderStart();
return true;
}
// continue.. (shift+delete = backspace)
case Keyboard.KEY_BACK: // backspace
if (selectionStart != selectionEnd)
clearSelection();
else if (GuiScreen.isCtrlKeyDown()) {
int size = seekNextCaretLocation(caret, false) - caret;
selectionStart = caret + size;
selectionEnd = caret;
clearSelection();
} else {
if (caret > 0 && textLength > 0) {
--caret;
System.arraycopy(text, caret + 1, text, caret, textLength - caret);
--textLength;
}
}
if (caret <= renderStart)
renderStart = MathHelper.clampI(caret - 3, 0, textLength);
findRenderStart();
return true;
case Keyboard.KEY_HOME: // home
if (GuiScreen.isShiftKeyDown()) {
if (caret > selectionEnd)
selectionEnd = selectionStart;
selectionStart = 0;
} else
selectionStart = selectionEnd = 0;
renderStart = caret = 0;
return true;
case Keyboard.KEY_END: // end
if (GuiScreen.isShiftKeyDown()) {
if (caret < selectionStart)
selectionStart = selectionEnd;
selectionEnd = textLength;
} else
selectionStart = selectionEnd = textLength;
caret = textLength;
findRenderStart();
return true;
case Keyboard.KEY_LEFT: // left arrow
case Keyboard.KEY_RIGHT: // right arrow
int size = keyTyped == 203 ? -1 : 1;
if (GuiScreen.isCtrlKeyDown())
size = seekNextCaretLocation(caret, keyTyped == 205) - caret;
if (selectionStart == selectionEnd || !GuiScreen.isShiftKeyDown())
selectionStart = selectionEnd = caret;
{
int t = caret;
caret = MathHelper.clampI(caret + size, 0, textLength);
size = caret - t;
}
findRenderStart();
if (GuiScreen.isShiftKeyDown()) {
if (caret == selectionStart + size)
selectionStart = caret;
else if (caret == selectionEnd + size)
selectionEnd = caret;
// this logic is 'broken' in that the selection doesn't wrap
// such that a|bc|def becomes abc|def| but it will highlight
// the rest of the word the caret is on i.e., a|bc|def -> a|bcdef|
// i don't know that it matters (home+end exhibit the former)
if (selectionStart > selectionEnd) {
int t = selectionStart;
selectionStart = selectionEnd;
selectionEnd = t;
}
}
return true;
default:
if (isAllowedCharacter(charTyped)) {
boolean typed = insertCharacter(charTyped);
clearSelection();
findRenderStart();
onCharacterEntered(typed);
return true;
} else
return false;
}
}
}
@Override
public boolean onMousePressed(int mouseX, int mouseY, int mouseButton) {
selecting = mouseButton == 0;
if (selecting) {
FontRenderer font = getFontRenderer();
int pos = mouseX - posX - 1;
for (int i = renderStart, width = 0; i < textLength; ++i) {
int charW = font.getCharWidth(text[i]);
if ((width += charW) > pos) {
selectionStart = selectionEnd = caret = i;
break;
}
}
}
setFocused(true);
return true;
}
@Override
public void update(int mouseX, int mouseY) {
++caretCounter;
//if (selecting) {
// FontRenderer font = getFontRenderer();
// int pos = mouseX - posX - 1;
// for (int i = renderStart, width = 0; i < textLength; ++i) {
// }
//}
}
@Override
public void onMouseReleased(int mouseX, int mouseY) {
if (!selecting) {
boolean focus = isFocused();
setFocused(false);
if (focus && !isFocused())
onFocusLost();
}
selecting = false;
}
@Override
public void drawBackground(int mouseX, int mouseY, float gameTicks) {
drawModalRect(posX - 1, posY - 1, posX + sizeX + 1, posY + sizeY + 1, borderColor);
drawModalRect(posX, posY, posX + sizeX, posY + sizeY, isEnabled() ? backgroundColor : disabledColor);
}
@Override
public void drawForeground(int mouseX, int mouseY) {
if (enableStencil) {
glEnable(GL_STENCIL_TEST);
glClear(GL_STENCIL_BUFFER_BIT);
drawStencil(posX + 1, posY + 1, posX + sizeX - 1, posY + sizeY - 1, 1);
}
FontRenderer font = getFontRenderer();
char[] text = this.text;
int startX = posX + 1, endX = sizeX - 1, startY = posY + 1, endY = startY + font.FONT_HEIGHT;
for (int i = renderStart, width = 0; i <= textLength; ++i) {
boolean end = i == textLength;
int charW = 2;
if (!end) {
charW = font.getCharWidth(text[i]);
if (!enableStencil && (width + charW) > endX)
break;
}
boolean drawCaret = i == caret && (caretCounter %= 24) < 12 && isFocused();
if (drawCaret) {
int caretEnd = width + 2;
if (caretInsert)
caretEnd = width + charW;
drawModalRect(startX + width, startY - 1, startX + caretEnd, endY,
(0xFF000000 & defaultCaretColor) | (~defaultCaretColor & 0xFFFFFF));
}
if (!end) {
boolean selected = i >= selectionStart & i < selectionEnd;
if (selected)
drawModalRect(startX + width, startY, startX + width + charW, endY, selectedLineColor);
font.drawString(String.valueOf(text[i]), startX + width, startY, selected ? selectedTextColor : textColor);
}
if (drawCaret) {
int caretEnd = width + 2;
if (caretInsert)
caretEnd = width + charW;
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_ONE_MINUS_DST_COLOR, GL11.GL_ZERO);
gui.drawSizedRect(startX + width, startY - 1, startX + caretEnd, endY, -1);
GL11.glDisable(GL11.GL_BLEND);
}
width += charW;
if (width > endX)
break;
}
if (enableStencil)
glDisable(GL_STENCIL_TEST);
}
}