package io.github.minecraftgui.models.components; import io.github.minecraftgui.controllers.Render; import io.github.minecraftgui.models.Updatable; import io.github.minecraftgui.models.attributes.AttributeDouble; import io.github.minecraftgui.models.fonts.Font; import java.awt.*; import java.util.ArrayList; /** * Created by Samuel on 2015-11-14. */ public class Text implements Updatable, Drawable { public enum TextAlignement {LEFT, CENTER, RIGHT}; private final ComponentText componentText; private ArrayList<Line> lines; private ArrayList<Line> visibleLines; private ArrayList<ArrayList<Char>> linesChar; private final ArrayList<GroupChar> groupChars; private Char cursorLocation = null; private boolean updateText = false; private boolean textUpdated = false; private Font fontLastUpdate = null; private double widthLastUpdate = 0; private String text = ""; private int lineIndex = 0;//La premi�re ligne qui va �tre affich� private int nbLinesToDisplay = Integer.MAX_VALUE;//La premi�re ligne qui va �tre affich� private TextAlignement alignement = TextAlignement.LEFT; private final AttributeDouble textHeight; public Text(ComponentText componentText) { this.componentText = componentText; lines = new ArrayList<>(); linesChar = new ArrayList<>(); groupChars = new ArrayList<>(); this.textHeight = new AttributeDouble(0.0); } public AttributeDouble getTextHeight() { return textHeight; } public void setAlignement(TextAlignement alignement) { this.alignement = alignement; } public int getLineIndex() { return lineIndex; } public void setNbLinesToDisplay(int nbLinesToDisplay) { if(nbLinesToDisplay != 0) this.nbLinesToDisplay = nbLinesToDisplay-1; } @Override public String toString() { return text; } public boolean isTextUpdated() { return textUpdated; } public int getCursorLine(){ return cursorLocation != null?cursorLocation.lineIndex:0; } public double getCursorX(){ if(cursorLocation != null) return lines.get(cursorLocation.lineIndex).xOffset+cursorLocation.cursorX; else return 0; } public double getCursorY(){ if(cursorLocation != null) return cursorLocation.cursorY; else return 0; } public void setCursorLocation(double x, double y){ double lineHeight = componentText.getStringHeight(); int lineIndex = (int) (y/lineHeight); int firstLineIndex = lines.indexOf(visibleLines.get(0)); if(lineIndex >= 0 && lineIndex - firstLineIndex <= nbLinesToDisplay) { ArrayList<Char> charLine; String currentLine = ""; if (lineIndex >= lines.size()) charLine = linesChar.get(lines.size() - 1); else charLine = linesChar.get(lineIndex); for (Char c : charLine) { if(c.getIntValue() != 13 && c.getIntValue() != 10) { currentLine = currentLine + c.getValue(); if (componentText.getStringWidth(currentLine) >= x) { cursorLocation = c; return; } } } if(charLine.size() != 0) cursorLocation = charLine.get(charLine.size() - 1); else cursorLocation = null; } } public void moveCursorUp(){ int cursorLineBefore = getCursorLine(); if(cursorLocation != null) setCursorLocation(getCursorX(), getCursorY()-5); if(cursorLineBefore != getCursorLine()) if(getCursorLine() < lineIndex) showLineBefore(); } public void moveCursorDown(){ int cursorLineBefore = getCursorLine(); if(cursorLocation != null) setCursorLocation(getCursorX(), getCursorY()+componentText.getStringHeight()+5); if(cursorLineBefore != getCursorLine()) if(getCursorLine() > lineIndex+nbLinesToDisplay) showLineAfter(); } public void showLineBefore(){ if(lineIndex != 0) { lineIndex--; updateVisibleLines(); } } public void showLineAfter(){ if(lineIndex +1 != lines.size()) { lineIndex++; updateVisibleLines(); } } public void moveCursorLeft(){ int cursorLineBefore = getCursorLine(); if(cursorLocation != null) cursorLocation = cursorLocation.before; if(cursorLineBefore != getCursorLine()) if(getCursorLine() < lineIndex) showLineBefore(); } public void moveCursorRight(){ int cursorLineBefore = getCursorLine(); if(cursorLocation != null && cursorLocation.after != null) cursorLocation = cursorLocation.after; else if(cursorLocation == null) if(groupChars.size() != 0 && linesChar.get(0).size() != 0 && linesChar.get(0) != null && linesChar.get(0).get(0) != null) cursorLocation = linesChar.get(0).get(0); if(cursorLineBefore != getCursorLine()) if(getCursorLine() > lineIndex+nbLinesToDisplay) showLineAfter(); } public void setText(String text){ lineIndex = 0; ArrayList<String> words = split(text); groupChars.clear(); for(String word : words) { if(word.charAt(0) > 32) { GroupChar groupChar = new GroupChar(); for (char c : word.toCharArray()) { Char ch = new Char(c, groupChar); cursorLocation = ch; groupChar.add(ch); } groupChars.add(groupChar); } else{ GroupChar groupChar = new GroupChar(); groupChar.wordGroup = false; Char ch = new Char(word.charAt(0), groupChar); cursorLocation = ch; groupChar.add(ch); groupChars.add(groupChar); } } updateText = true; } public void deleteNextChar(){ if(cursorLocation != null) { if (cursorLocation.after != null) { Char c = cursorLocation.after; c.groupChar.remove(c); if (c.groupChar.size() == 0) groupChars.remove(c); updateText = true; } } else if(groupChars.size() != 0){ for(int i = 0; i < linesChar.size(); i++) { if (linesChar.get(i).size() != 0 && linesChar.get(i) != null && linesChar.get(i).get(0) != null) { Char c = linesChar.get(i).get(0); if (c != null) { c.groupChar.remove(c); if (c.groupChar.size() == 0) groupChars.remove(c); updateText = true; return; } } } } } public void deleteChar(){ if(cursorLocation != null){ Char c = cursorLocation; c.groupChar.remove(c); if(c.groupChar.size() == 0) groupChars.remove(c); cursorLocation = c.before; updateText = true; } } public void addInput(char input){ if(cursorLocation != null){ GroupChar currentGroup = cursorLocation.groupChar; if(input > 32) { if (currentGroup.wordGroup) { Char c = new Char(input, currentGroup); c.lineIndex = currentGroup.get(currentGroup.indexOf(cursorLocation)).lineIndex; currentGroup.add(currentGroup.indexOf(cursorLocation) + 1, c); cursorLocation = c; updateText = true; } else if (currentGroup.index + 1 != groupChars.size() && groupChars.get(currentGroup.index + 1).wordGroup) { GroupChar nextGroup = groupChars.get(currentGroup.index + 1); Char c = new Char(input, nextGroup); c.lineIndex = currentGroup.get(0).lineIndex; nextGroup.add(0, c); cursorLocation = c; updateText = true; } else{ GroupChar groupChar = new GroupChar(); groupChars.add(currentGroup.index+1, groupChar); Char c = new Char(input, groupChar); c.lineIndex = currentGroup.get(currentGroup.size()-1).lineIndex; groupChar.add(c); cursorLocation = c; updateText = true; } } else{ if(currentGroup.wordGroup){ if(cursorLocation.after != null && cursorLocation.after.groupChar == currentGroup){ int index = currentGroup.indexOf(cursorLocation); GroupChar groupChar = new GroupChar(); for(int i = 0; i <= index; i++){ groupChar.add(currentGroup.get(i)); currentGroup.get(i).groupChar = groupChar; } while(index-- != -1) currentGroup.remove(0); groupChars.add(currentGroup.index, groupChar); GroupChar groupCharCharAdded = new GroupChar(); groupCharCharAdded.wordGroup = false; groupChars.add(currentGroup.index+1, groupCharCharAdded); Char c = new Char(input, groupCharCharAdded); c.lineIndex = currentGroup.get(currentGroup.size()-1).lineIndex; groupCharCharAdded.add(c); cursorLocation = c; updateText = true; } else { GroupChar groupChar = new GroupChar(); groupChar.wordGroup = false; groupChars.add(currentGroup.index+1, groupChar); Char c = new Char(input, groupChar); c.lineIndex = currentGroup.get(currentGroup.size()-1).lineIndex; groupChar.add(c); cursorLocation = c; updateText = true; } } else{ GroupChar groupChar = new GroupChar(); groupChar.wordGroup = false; groupChars.add(currentGroup.index+1, groupChar); Char c = new Char(input, groupChar); c.lineIndex = currentGroup.get(currentGroup.size()-1).lineIndex; groupChar.add(c); cursorLocation = c; updateText = true; } } } else if(groupChars.size() != 0 && linesChar.get(0).size() != 0 && linesChar.get(0) != null && linesChar.get(0).get(0) != null){ GroupChar groupChar = linesChar.get(0).get(0).groupChar; if(groupChar.wordGroup) { Char c = new Char(input, groupChar); groupChar.add(0, c); cursorLocation = c; updateText = true; } else{ groupChar = new GroupChar(); groupChar.wordGroup = input > 32; groupChars.add(0, groupChar); Char c = new Char(input, groupChar); groupChar.add(c); cursorLocation = c; updateText = true; } } else setText(String.valueOf(input)); } private void updateGroupCharsWidth(){ for(GroupChar groupChar : groupChars) groupChar.update(); } @Override public void draw(Render render) { double fontHeight = componentText.getStringHeight(); ArrayList<Line> lines = visibleLines; Font font = componentText.getFont(); int fontSize = componentText.getFontSize(); Color fontColor = componentText.getFontColor(); for(int i = 0; i < lines.size(); i++) { Line line = lines.get(i); font.drawString(line.text, componentText.getY() + fontHeight * i, componentText.getX() +line.xOffset, fontSize, fontColor); } } @Override public void update(long updateId) { textUpdated = false; if(lines.size() == 0) lines.add(new Line()); if(widthLastUpdate != componentText.getWidth() || updateText){ widthLastUpdate = componentText.getWidth(); updateText = true; } if(fontLastUpdate != componentText.getFont() || updateText){ fontLastUpdate = componentText.getFont(); updateGroupCharsWidth(); updateText = true; } for(GroupChar groupChar : groupChars) groupChar.update(updateId); if(updateText){ lines.clear(); linesChar.clear(); ArrayList<Char> chars = new ArrayList<>(); int cursorLineBeforeUpdate = getCursorLine(); updateLines(); text = ""; for(Line line : lines) text += line.text; for(ArrayList<Char> charsLine : linesChar) chars.addAll(charsLine); for(int i = 0; i < chars.size(); i++){ Char c = chars.get(i); if(i == 0) c.before = null; else c.before = chars.get(i-1); if(i+1 == chars.size()) c.after = null; else c.after = chars.get(i+1); } if(cursorLineBeforeUpdate != getCursorLine()){ if(getCursorLine() < lineIndex) showLineBefore(); else if(getCursorLine() > lineIndex+nbLinesToDisplay) showLineAfter(); } while(lineIndex >= lines.size() && lineIndex != 0) lineIndex--; updateVisibleLines(); textHeight.setValue(visibleLines.size()*componentText.getStringHeight()); updateText = false; textUpdated = true; } } private void updateVisibleLines(){ visibleLines = new ArrayList<>(); int nbLineToRender = lineIndex+nbLinesToDisplay >= lines.size()?lines.size()-1: lineIndex+nbLinesToDisplay; for(int i = lineIndex; i <= nbLineToRender; i++) visibleLines.add(lines.get(i)); } private double getXOffset(double textWidth){ switch (alignement){ case LEFT: return 0; case CENTER: return (componentText.getWidth() - textWidth)/2; case RIGHT: return componentText.getWidth() - textWidth; } return 0; } private void updateLines(){ double lineHeight = componentText.getStringHeight(); String currentLine = ""; int lineIndex = 0; ArrayList<Char> currentCharLine = new ArrayList<>(); for(int g = 0; g < groupChars.size(); g++) { GroupChar groupChar = groupChars.get(g); groupChar.index = g; if(componentText.getWidth() < groupChar.getWidth() || groupChar.size() == 1){ for (int i = 0; i < groupChar.size(); i++) { if(groupChar.get(i).getIntValue() == 10 || groupChar.get(i).getIntValue() == 13){ linesChar.add(currentCharLine); addLine(currentLine); lineIndex++; currentLine = ""; currentCharLine = new ArrayList<>(); currentCharLine.add(groupChar.get(i)); groupChar.get(i).cursorY = lines.size()*lineHeight; groupChar.get(i).cursorX = 0; groupChar.get(i).lineIndex = lineIndex; } else if(componentText.getStringWidth(currentLine + groupChar.get(i).getValue()) <= componentText.getWidth()){ currentLine += groupChar.get(i).getValue(); currentCharLine.add(groupChar.get(i)); groupChar.get(i).cursorY = lines.size()*lineHeight; groupChar.get(i).cursorX = componentText.getStringWidth(currentLine); groupChar.get(i).lineIndex = lineIndex; } else{ linesChar.add(currentCharLine); addLine(currentLine); lineIndex++; currentLine = groupChar.get(i).getValue(); currentCharLine = new ArrayList<>(); currentCharLine.add(groupChar.get(i)); groupChar.get(i).cursorY = lines.size()*lineHeight; groupChar.get(i).cursorX = componentText.getStringWidth(currentLine); groupChar.get(i).lineIndex = lineIndex; } } } else if(componentText.getStringWidth(currentLine)+groupChar.getWidth() <= componentText.getWidth()){ for (Char c : groupChar) { currentLine += c.getValue(); currentCharLine.add(c); c.cursorY = lines.size()*lineHeight; c.cursorX = componentText.getStringWidth(currentLine); c.lineIndex = lineIndex; } } else{ addLine(currentLine); linesChar.add(currentCharLine); lineIndex++; currentCharLine = new ArrayList<>(); currentLine = ""; for (Char c : groupChar) { currentLine += c.getValue(); currentCharLine.add(c); c.cursorY = lines.size()*lineHeight; c.cursorX = componentText.getStringWidth(currentLine); c.lineIndex = lineIndex; } } } linesChar.add(currentCharLine); addLine(currentLine); } private void addLine(String lineStr){ Line line = new Line(); line.text = lineStr; line.xOffset = getXOffset(componentText.getStringWidth(lineStr)); lines.add(line); } private ArrayList<String> split(String text){ ArrayList<String> words = new ArrayList<String>(); String word = ""; for(int i = 0; i < text.length(); i++){ if(text.charAt(i) == 13 || text.charAt(i) == 10) { if(!word.equals("")) { words.add(word); word = ""; } words.add((char) 10+""); } else if(text.charAt(i) == 32) { if(!word.equals("")) { words.add(word); word = ""; } words.add(" "); } else word += text.charAt(i); if(i+1 == text.length() && text.charAt(i) != 32 && text.charAt(i) != 13) words.add(word); } return words; } private class GroupChar extends ArrayList<Char> implements Updatable{ private boolean wordGroup = true; private int index; private int sizeLastWidth = 0; private double width = 0; private String line; private double getWidth(){ return width; } @Override public void update(long updateId) { if(size() != sizeLastWidth){ sizeLastWidth = size(); update(); } } public void update(){ line = ""; for(Char c : this) line += c.getValue(); width = componentText.getStringWidth(line); } } private class Line{ private String text = ""; private double xOffset = 0; } private class Char { private GroupChar groupChar; private double cursorX; private double cursorY; private int lineIndex; private Char before; private Char after; private final String value; private Char(char value, GroupChar groupChar) { this.value = String.valueOf(value); this.groupChar = groupChar; } public int getIntValue() { return (int) value.charAt(0); } public String getValue() { return value; } } }