package speiger.src.coreengine.rendering.gui.components; import java.util.function.Predicate; import org.lwjgl.glfw.GLFW; import speiger.src.coreengine.math.vector.ints.Vec2i; 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.constrains.Constraints; import speiger.src.coreengine.rendering.gui.renderer.UIRenderer; import speiger.src.coreengine.rendering.gui.renderer.lexer.Line; 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 TextPanelComponent extends GuiComponent implements IButtonComponent, IKeyComponent { public static final int FLAG_FOCUS = 1024; public static final int FLAG_CAN_LOSE_FOCUS = 2048; TextComponent text = new TextComponent().align(Align.LEFT_TOP, Align.LEFT_TOP).singleLine(true).special(false).cast(); int color; int curserPosition = 0; Vec2i curserPos = Vec2i.newMutable(); int selectionPosition = 0; Vec2i selectionPos = Vec2i.newMutable(); long lastClickTime = 0; int maxTextLength = 64; int clickCount = 0; Predicate validator = Functions.getAlwaysTrue(); public TextPanelComponent(int color) { super(0F, 0F, 0F, 0F); this.color = color; } public TextPanelComponent(float x, float y, float width, float height, int color) { super(x, y, width, height); this.color = color; } @Override public void init() { addChild(text, Constraints.getParentConstrains(1F)); } public TextPanelComponent setValidator(Predicate validator) { this.validator = validator; return this; } public TextPanelComponent setMaxTextLength(int charLimit) { maxTextLength = charLimit; return this; } public TextPanelComponent setColor(int color) { this.color = color; 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 TextPanelComponent setText(String s) { if(s == null) { return this; } if(validator.test(s)) { if(s.length() > maxTextLength) { s = s.substring(0, maxTextLength); } float height = text.getBox().getHeight(); float scale = text.getTextScale(); while(s.length() > 0 && text.getFont().height(s) * scale > height) { s = s.substring(0, s.length() - 1); } text.setText(s); } return this; } @Override protected boolean updateSelf(int mouseX, int mouseY, float particalTicks) { if(text.isMouseOver(mouseX, mouseY) && !text.isOverChild(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(box, 0.001F, color); if(isFlagSet(FLAG_FOCUS) && (getGlobalClock() / 15) % 2L == 0) { TextMetadata data = text.getMetadata(); float height = text.getFont().height() * text.getTextScale(); if(hasSelectedText()) { if(selectionPos.getY() == curserPos.getY()) { render.drawQuad(box.getMinX() + data.getWidth(Math.min(curserPos.getX(), selectionPos.getX()), curserPos.getY()), box.getMinY() + (height * curserPos.getY()), box.getMinX() + data.getWidth(Math.max(curserPos.getX(), selectionPos.getX()), curserPos.getY()), box.getMinY() + (height * (curserPos.getY()+1)), 0.02F, text.getTextColor()); } else { Vec2i min = selectionPos.getY() < curserPos.getY() ? selectionPos : curserPos; Vec2i max = selectionPos.getY() > curserPos.getY() ? selectionPos : curserPos; for(int i = min.getY(),m=max.getY();i<=m;i++) { if(i == min.getY()) { render.drawQuad(box.getMinX() + data.getWidth(min.getX()), box.getMinY() + (height * i), box.getMinX() + data.getLineWidth(i), box.getMinY() + (height * (i+1)), 0.02F, text.getTextColor()); } else if(i == max.getY()) { render.drawQuad(box.getMinX(), box.getMinY() + (height * i), box.getMinX() + data.getWidth(max.getX()), box.getMinY() + (height * (i+1)), 0.02F, text.getTextColor()); } else { render.drawQuad(box.getMinX(), box.getMinY() + (height * i), box.getMinX() + data.getLineWidth(i), box.getMinY() + (height * (i+1)), 0.02F, text.getTextColor()); } } } return true; } float width = data.getWidth(curserPos.getX(), curserPos.getY()); render.drawQuad(box.getMinX()+width, box.getMinY() + (height * curserPos.getY()), box.getMinX()+width+text.getTextScale(), box.getMinY() + (height * (curserPos.getY()+1)), 0.02F, text.getTextColor()); } return true; } @Override public boolean onClick(int button, int mouseX, int mouseY) { setFlag(FLAG_FOCUS); if(hasSelectedText() && clickCount <= 0) { setSelectionPosition(-1); setCurserPosition(getMousePosition(mouseX, mouseY)); return true; } int pos = getMousePosition(mouseX, mouseY); if(pos == getCurserPosition()) { if(System.currentTimeMillis() - lastClickTime < 500L) { if(clickCount == 1) { Line line = text.getMetadata().getLine(curserPos.getY()); setSelectionPosition(line.getStart()); setCurserPosition(line.getEnd()); clickCount = 2; } else if(clickCount == 2) { clickCount = 3; setSelectionPosition(0); setCurserToEnd(); } else { clickCount = 1; handleDoubleClick(pos); } } else { clickCount = 0; } lastClickTime = System.currentTimeMillis(); return true; } lastClickTime = System.currentTimeMillis(); setCurserPosition(pos); setSelectionPosition(-1); clickCount = 0; return false; } @Override public boolean onDrag(int mouseX, int mouseY) { if(!hasSelectedText()) { setSelectionPosition(getCurserPosition()); } setCurserPosition(getMousePosition(mouseX, mouseY)); return true; } @Override public void onFocusLost() { clearFlag(FLAG_FOCUS); } @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_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 == null) { return true; } 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 == null) { return true; } 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_UP) { if(Keyboard.isShiftDown() && !hasSelectedText()) { setSelectionPosition(getCurserPosition()); } else if(!Keyboard.isShiftDown() && hasSelectedText()) { setSelectionPosition(-1); } setCurserPosition(text.getMetadata().moveDown(curserPos)); } else if(key == GLFW.GLFW_KEY_DOWN) { if(Keyboard.isShiftDown() && !hasSelectedText()) { setSelectionPosition(getCurserPosition()); } else if(!Keyboard.isShiftDown() && hasSelectedText()) { setSelectionPosition(-1); } setCurserPosition(text.getMetadata().moveUp(curserPos)); } 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(key == GLFW.GLFW_KEY_ENTER) { writeText(Character.toString('\n')); 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 codepoint) { return isFlagSet(FLAG_FOCUS) && text.getFont().isCharValid(codepoint) && writeText(new String(Character.toChars(codepoint))); } 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())); 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); notifyListeners(LISTENER_USER_ACTION); return true; } return 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()); } } public int getMousePosition(int mouseX, int mouseY) { return text.getMetadata().getIndex(mouseX - text.getBox().getMinX(), mouseY - text.getBox().getMinY()); } public void setCurserToEnd() { setCurserPosition(curserPosition); } public void setCurserPosition(int curserPosition) { if(this.curserPosition == curserPosition) { return; } this.curserPosition = curserPosition; text.getMetadata().convert(curserPosition, curserPos); } public int getCurserPosition() { return curserPosition; } public void setSelectionPosition(int selectionPosition) { if(this.selectionPosition == selectionPosition) { return; } this.selectionPosition = selectionPosition; if(selectionPosition == -1) { selectionPos.set(Vec2i.MINUS_ONE); return; } text.getMetadata().convert(selectionPosition, selectionPos); } public int getSelectionPosition() { return selectionPosition; } public boolean hasSelectedText() { return selectionPosition != -1; } }