package speiger.src.coreengine.rendering.gui.components; import java.util.function.Predicate; import org.lwjgl.glfw.GLFW; import speiger.src.coreengine.math.MathUtils; import speiger.src.coreengine.math.misc.ColorObject; import speiger.src.coreengine.rendering.gui.GuiComponent; import speiger.src.coreengine.rendering.gui.base.IButtonComponent; import speiger.src.coreengine.rendering.gui.base.IKeyComponent; import speiger.src.coreengine.rendering.gui.helper.Align; import speiger.src.coreengine.rendering.gui.helper.box.IGuiBox; import speiger.src.coreengine.rendering.gui.helper.box.ParentBox; import speiger.src.coreengine.rendering.gui.helper.constrains.ComponentConstrains; import speiger.src.coreengine.rendering.gui.helper.constrains.Constraints; import speiger.src.coreengine.rendering.gui.helper.constrains.DynamicConstrain; import speiger.src.coreengine.rendering.gui.helper.constrains.ParentConstrain; import speiger.src.coreengine.rendering.gui.renderer.UIRenderer; import speiger.src.coreengine.rendering.gui.renderer.lexer.TextMetadata; import speiger.src.coreengine.rendering.gui.renderer.lexer.Word; import speiger.src.coreengine.rendering.gui.renderer.lexer.WordType; import speiger.src.coreengine.rendering.input.Keyboard; import speiger.src.coreengine.rendering.utils.Cursor; import speiger.src.coreengine.utils.functions.Functions; public class TextFieldComponent extends GuiComponent implements IButtonComponent, IKeyComponent { public static final int FLAG_FOCUS = 1 << 20; public static final int FLAG_CAN_LOSE_FOCUS = 1 << 21; public static final int FLAG_DOUBLE_CLICK = 1 << 22; public static final int FLAG_INFINITE_WIDTH = 1 << 23; public static final int FLAG_AUTO_VALIDATE = 1 << 24; TextComponent text = new TextComponent().setHorizontalAlignment(Align.LEFT_TOP).setForcedSingleLine(true).setSpecialRendering(false).cast(); ColorObject color; int curserPosition = 0; int selectionPosition = 0; long lastClickTime = 0; int maxTextLength = 32; Predicate validator = Functions.getAlwaysTrue(); int lineOffset = 0; boolean direction = false; int largestPos = 0; IGuiBox viewPort = new ParentBox(1F); public TextFieldComponent(ColorObject color) { super(0F, 0F, 0F, 0F); setFlag(FLAG_CAN_LOSE_FOCUS); this.color = color; } public TextFieldComponent(float x, float y, float width, float height, ColorObject color) { super(x, y, width, height); setFlag(FLAG_CAN_LOSE_FOCUS); this.color = color; } @Override public void init() { addBox(viewPort); addChild(text, createConstraints()); if(text.getText().length() > 0 && isFlagNotSet(FLAG_INFINITE_WIDTH)) { String s = text.getText(); float scale = text.getTextScale(); float width = text.getBox().getWidth(); while(text.getFont().getTextLength(s) * scale > width && s.length() > 0) { s = s.substring(0, s.length() - 1); } text.setText(s); } } private ComponentConstrains createConstraints() { return isFlagNotSet(FLAG_INFINITE_WIDTH) ? Constraints.getParentConstrains(1F) : new ComponentConstrains(new DynamicConstrain(this::getOffset), new ParentConstrain(1F), new ParentConstrain(1F), new ParentConstrain(1F)); } public TextFieldComponent setValidator(Predicate validator) { this.validator = validator; return this; } public TextFieldComponent setMaxTextLength(int charLimit) { maxTextLength = charLimit; return this; } public TextFieldComponent setCanLoseFocus(boolean value) { setFlag(FLAG_CAN_LOSE_FOCUS, value); return this; } public TextFieldComponent setFocused(boolean value) { setFlag(FLAG_FOCUS, value); return this; } public TextFieldComponent setAutoUpdating(boolean value) { setFlag(FLAG_AUTO_VALIDATE, value); return this; } public TextFieldComponent setColor(ColorObject color) { this.color = color; return this; } public final TextFieldComponent setInfiniteText(boolean value) { if(setFlag(FLAG_INFINITE_WIDTH, value)) { text.setLimitedBounds(!value); if(getGui() != null) { addConstrains(text, createConstraints()); } } return this; } public TextComponent getRawText() { return text; } public String getText() { return text.getText(); } public String getSelectedText() { return text.getText(Math.min(getCurserPosition(), getSelectionPosition()), Math.max(getCurserPosition(), getSelectionPosition())); } public TextFieldComponent setText(String s) { if(s == null) { return this; } if(validator.test(s)) { if(s.length() > maxTextLength) { s = s.substring(0, maxTextLength); } if(isFlagNotSet(FLAG_INFINITE_WIDTH) && getGui() != null) { float scale = text.getTextScale(); float width = text.getBox().getWidth(); while(text.getFont().getTextLength(s) * scale > width) { s = s.substring(0, s.length() - 1); } } text.setText(s); } return this; } @Override protected boolean updateSelf(int mouseX, int mouseY, float particalTicks) { if(text.isTopHovered(mouseX, mouseY)) { bindCursor(Cursor.CURSOR_IBEAM); } return true; } @Override protected boolean renderSelf(int mouseX, int mouseY, float particalTicks) { float brightness = getActiveBrightness(); UIRenderer render = getRenderer(); render.setBrightness(brightness * 0.7F).drawQuad(getBox(), color).setBrightness(brightness); IGuiBox box = text.getBox(); render.drawQuad(viewPort, 0.001F, color); if(isFlagSet(FLAG_INFINITE_WIDTH)) { enableScissorsBox(getBox().getMinX(1F), getBox().getMinY(1F), getBox().getMaxX(-1F), getBox().getMaxY(-1F)); } if(isFlagSet(FLAG_FOCUS) && (getGlobalClock() / 15) % 2L == 0) { TextMetadata data = text.getMetadata(); if(hasSelectedText()) { float extra = text.getHorizontal().align(box.getWidth(), data.getMaxWidth()); render.drawQuad(box.getMinX() + extra + data.getWidth(Math.min(getCurserPosition(), getSelectionPosition())), box.getMinY(), box.getMinX() + extra + data.getWidth(Math.max(getCurserPosition(), getSelectionPosition())), box.getMaxY(), 0.02F, text.getTextColor()); } else { float width = data.getWidth(curserPosition) + text.getHorizontal().align(box.getWidth(), data.getMaxWidth()); render.drawQuad(box.getMinX() + width, box.getMinY(), box.getMinX() + width + text.getTextScale(), box.getMaxY(), 0.02F, text.getTextColor()); } } if(isFlagSet(FLAG_INFINITE_WIDTH)) { renderChildren(mouseX, mouseY, particalTicks); disableScissors(); return false; } return true; } @Override public boolean onClick(int button, int mouseX, int mouseY) { setFocused(true); if(hasSelectedText() && isFlagNotSet(FLAG_DOUBLE_CLICK)) { setSelectionPosition(-1); setCurserPosition(getMousePosition(mouseX)); return true; } int pos = getMousePosition(mouseX); if(pos == getCurserPosition() || isFlagSet(FLAG_DOUBLE_CLICK)) { if(System.currentTimeMillis() - lastClickTime < 500L) { if(isFlagSet(FLAG_DOUBLE_CLICK)) { clearFlag(FLAG_DOUBLE_CLICK); setSelectionPosition(0); setCurserPosition(text.length()); } else { setFlag(FLAG_DOUBLE_CLICK); handleDoubleClick(pos); } } else { clearFlag(FLAG_DOUBLE_CLICK); } lastClickTime = System.currentTimeMillis(); return true; } clearFlag(FLAG_DOUBLE_CLICK); setCurserPosition(pos); lastClickTime = System.currentTimeMillis(); return true; } @Override public boolean onDrag(int mouseX, int mouseY) { if(!hasSelectedText()) { setSelectionPosition(getCurserPosition()); } setCurserPosition(getMousePosition(mouseX)); return true; } @Override public boolean isAcceptingInput() { return isFlagSet(FLAG_FOCUS); } @Override public boolean isBlockingMovement() { return true; } @Override public boolean onKeyPressed(int key) { if(isFlagNotSet(FLAG_FOCUS)) { return false; } if(key == GLFW.GLFW_KEY_ENTER) { notifyListeners(LISTENER_USER_ACTION); if(isFlagSet(FLAG_CAN_LOSE_FOCUS)) { setFocused(false); return true; } } if(key == GLFW.GLFW_KEY_BACKSPACE) { if(Keyboard.isCtrlDown()) { Word word = text.getMetadata().getWord(getCurserPosition()); if(word == null) { return true; } if(getCurserPosition() == word.getStartIndex()) { if(word.getPrev() != null) { deleteAtCurser(word.getPrev().getStartIndex() - word.getStartIndex()); } } else { setCurserPosition(word.getStartIndex()); deleteAtCurser(word.getEndIndex() - word.getStartIndex()); } return true; } else if(deleteAtCurser(-1)) { return true; } } else if(key == GLFW.GLFW_KEY_DELETE) { if(Keyboard.isCtrlDown()) { Word word = text.getMetadata().getWord(getCurserPosition()); if(word == null) { return true; } if(getCurserPosition() == word.getEndIndex()) { if(word.getNext() != null) { deleteAtCurser(word.getNext().getEndIndex() - word.getEndIndex()); } } else { setCurserPosition(word.getStartIndex()); deleteAtCurser(word.getEndIndex() - word.getStartIndex()); } return true; } else if(deleteAtCurser(1)) { return true; } } else if(key == GLFW.GLFW_KEY_LEFT) { if(getCurserPosition() >= 0) { if(Keyboard.isShiftDown() && getSelectionPosition() == -1) { setSelectionPosition(getCurserPosition()); } else if(!Keyboard.isShiftDown() && getSelectionPosition() != -1) { setSelectionPosition(-1); } if(Keyboard.isCtrlDown()) { Word word = text.getMetadata().getWord(getCurserPosition()); if(word.getStartIndex() == getCurserPosition()) { if(word.getPrev() != null) { setCurserPosition(word.getPrev().getStartIndex()); } } else { setCurserPosition(word.getStartIndex()); } } else { setCurserPosition(getCurserPosition() - 1); } return true; } } else if(key == GLFW.GLFW_KEY_RIGHT) { if(getCurserPosition() < text.length()) { if(Keyboard.isShiftDown() && getSelectionPosition() == -1) { setSelectionPosition(getCurserPosition()); } else if(!Keyboard.isShiftDown() && getSelectionPosition() != -1) { setSelectionPosition(-1); } if(Keyboard.isCtrlDown()) { Word word = text.getMetadata().getWord(getCurserPosition()); if(word.getEndIndex() == getCurserPosition()) { if(word.getNext() != null) { setCurserPosition(word.getNext().getEndIndex()); } } else { setCurserPosition(word.getEndIndex()); } } else { setCurserPosition(getCurserPosition() + 1); } return true; } else { if(!Keyboard.isShiftDown() && hasSelectedText()) { setSelectionPosition(-1); } return true; } } else if(key == GLFW.GLFW_KEY_END) { if(Keyboard.isShiftDown()) { if(!hasSelectedText()) { setSelectionPosition(getCurserPosition()); } } else { setSelectionPosition(-1); } setCurserToEnd(); return true; } else if(key == GLFW.GLFW_KEY_HOME) { if(Keyboard.isShiftDown()) { if(getSelectionPosition() == -1) { setSelectionPosition(getCurserPosition()); } } else { setSelectionPosition(-1); } setCurserPosition(0); return true; } else if(key == GLFW.GLFW_KEY_TAB) { writeText(Character.toString('\t')); return true; } else if(isSelect(key)) { setSelectionPosition(0); setCurserToEnd(); return true; } else if(isCopy(key)) { if(hasSelectedText()) { getWindow().setClipboardString(getSelectedText()); } return true; } else if(isPaste(key)) { String text = getWindow().getClipboardString(); if(text != null) { writeText(text); } return true; } else if(isCut(key)) { if(hasSelectedText()) { getWindow().setClipboardString(getSelectedText()); writeText(""); } return true; } else if(Keyboard.isPrintableKey(key)) { return true; } return false; } @Override public boolean onKeyTyped(char letter, int keyCode) { return isFlagSet(FLAG_FOCUS) && text.getFont().isCharValid(letter) && writeText(Character.toString(letter)); } public boolean deleteAtCurser(int amount) { if(text.length() > 0) { if(hasSelectedText()) { int startPos = Math.min(getCurserPosition(), getSelectionPosition()); writeText(""); setCurserPosition(startPos); } else { int startPos = Math.min(getCurserPosition(), getCurserPosition() + amount); int endPos = Math.max(getCurserPosition(), getCurserPosition() + amount); StringBuilder builder = new StringBuilder(); if(startPos >= 0) { builder.append(text.getText(0, startPos)); } if(endPos < text.length()) { builder.append(text.getText(endPos)); } String s = builder.toString(); if(validator.test(s)) { setText(s); setCurserPosition(Math.min(startPos, text.length())); if(isFlagSet(FLAG_AUTO_VALIDATE)) notifyListeners(LISTENER_USER_ACTION); } } return true; } return false; } public boolean writeText(String toWrite) { toWrite = text.getFont().clearInvalidLetters(toWrite); StringBuilder builder = new StringBuilder(); int startPos = hasSelectedText() ? Math.min(getSelectionPosition(), getCurserPosition()) : getCurserPosition(); int endPos = hasSelectedText() ? Math.max(getSelectionPosition(), getCurserPosition()) : getCurserPosition(); int room = maxTextLength - text.length() - (startPos - endPos); if(text.length() > 0) builder.append(text.getText(0, startPos)); int moved = 0; if(room < toWrite.length()) { builder.append(toWrite.substring(0, room)); moved = room; } else { builder.append(toWrite); moved = toWrite.length(); } if(text.length() > 0 && endPos < text.length()) builder.append(text.getText(endPos)); String s = builder.toString(); if(validator.test(s)) { setText(s); setCurserPosition(Math.min(endPos + moved, text.length())); setSelectionPosition(-1); if(isFlagSet(FLAG_AUTO_VALIDATE)) notifyListeners(LISTENER_USER_ACTION); return true; } return false; } @Override public void onFocusLost() { if(isFlagSet(FLAG_CAN_LOSE_FOCUS)) { if(isFlagSet(FLAG_FOCUS)) { notifyListeners(LISTENER_USER_ACTION); } setFocused(false); } } protected void handleDoubleClick(int position) { Word word = text.getMetadata().getWord(position); if(word == null) { return; } if(!word.isSpecial()) { setSelectionPosition(word.getStartIndex()); setCurserPosition(word.getEndIndex()); return; } WordType type = word.getType(); if(type.isStartWord()) { Word other = type.findEndWord(word); if(other != null) { setSelectionPosition(word.getStartIndex()); setCurserPosition(other.getEndIndex()); } } else if(type.isEndWord()) { Word other = type.findStartWord(word); if(other != null) { setSelectionPosition(other.getStartIndex()); setCurserPosition(word.getEndIndex()); } } else if(type.isDualWord()) { if(word.getStartIndex() == position && word.getPrev() != null) { Word other = type.findStartWord(word); if(other != null) { setSelectionPosition(other.getStartIndex()); setCurserPosition(word.getEndIndex()); } } else { Word other = type.findEndWord(word); if(other != null) { setSelectionPosition(word.getStartIndex()); setCurserPosition(other.getEndIndex()); } } } else { setSelectionPosition(word.getStartIndex()); setCurserPosition(word.getEndIndex()); } } protected int getMousePosition(int mouseX) { return text.getMetadata().getIndex(mouseX - (text.getBox().getMinX()+text.getHorizontal().align(text.getBox().getWidth(), text.getMetadata().getMaxWidth()))); } public void setCurserToEnd() { setCurserPosition(text.length()); } public void setCurserPosition(int curserPosition) { this.curserPosition = Math.max(0, curserPosition); if(isFlagSet(FLAG_INFINITE_WIDTH) && text.getFont() != null) { int lastLine = lineOffset; if(lineOffset > text.length()) { lineOffset = text.length(); } String s = text.getFont().trimStringToWidth(text.getText(lineOffset), text.getWidth()); int k = s.length() + lineOffset; if(curserPosition > k) { lineOffset += curserPosition - k; direction = true; largestPos = curserPosition; } else if(curserPosition < largestPos) { int diff = largestPos - curserPosition; lineOffset -= diff; largestPos -= diff; if(lineOffset <= 0) direction = false; } lineOffset = MathUtils.clamp(0, text.length(), lineOffset); if(lastLine != lineOffset) { text.onComponentChanged(false); } } } public int getCurserPosition() { return curserPosition; } public void setSelectionPosition(int selectionPosition) { this.selectionPosition = selectionPosition; } public int getSelectionPosition() { return selectionPosition; } public boolean hasSelectedText() { return selectionPosition != -1; } protected float getOffset() { if(direction && text.getFont() != null) { return -(text.getMetadata().getWidth(largestPos)) + text.getBox().getWidth() - 1F; } return 1F - (text.getMetadata().getWidth(lineOffset)); } }