Last active
August 29, 2015 14:26
-
-
Save Caellian/3c0fa5b6dc053924a855 to your computer and use it in GitHub Desktop.
TextArea Showcase
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.caellian.codeBlocks.client.gui; | |
import com.google.common.collect.Lists; | |
import net.minecraft.client.Minecraft; | |
import net.minecraft.client.gui.FontRenderer; | |
import net.minecraft.client.gui.Gui; | |
import net.minecraft.client.gui.GuiScreen; | |
import net.minecraft.client.renderer.GlStateManager; | |
import net.minecraft.client.renderer.Tessellator; | |
import net.minecraft.client.renderer.WorldRenderer; | |
import net.minecraft.util.ChatAllowedCharacters; | |
import org.apache.commons.lang3.ArrayUtils; | |
import org.lwjgl.input.Keyboard; | |
import org.lwjgl.opengl.GL11; | |
import java.awt.*; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.Objects; | |
/** | |
* @author Caellian | |
*/ | |
public class GUITextArea extends Gui | |
{ | |
/** | |
* Font renderer used by this GUITextArea. | |
*/ | |
public final FontRenderer fontRendererInstance; | |
/** | |
* The x position of this text field. | |
*/ | |
public int xPosition; | |
/** | |
* The y position of this text field. | |
*/ | |
public int yPosition; | |
/** | |
* The width of this text field. | |
*/ | |
public int width; | |
/** | |
* The height of this text field. | |
*/ | |
public int height; | |
/** | |
* Text area content. | |
*/ | |
private ArrayList<String> text = new ArrayList<>(0); | |
/** | |
* Default string length limit. | |
*/ | |
public int maxLineCount = Short.MAX_VALUE; | |
/** | |
* Default string length limit. | |
*/ | |
public int maxLineLength = Short.MAX_VALUE; | |
/** | |
* Number of pixels to put between lines. | |
*/ | |
public int lineMargin = 2; | |
/** | |
* True if text area background should be drawn. | |
*/ | |
public boolean enableBackgroundDrawing = true; | |
/** | |
* If true the text area can lose focus by clicking elsewhere on the screen. | |
*/ | |
public boolean canLoseFocus = true; | |
/** | |
* If this value is true along with isEnabled, keyTyped will process the keys. | |
*/ | |
public boolean isFocused = false; | |
/** | |
* If this value is true along with isFocused, keyTyped will process the keys. | |
*/ | |
public boolean isEnabled = true; | |
/** | |
* The current character index that should be used as start of the rendered text on the top right corner of GUI. | |
*/ | |
public int[] offsetDisplay = new int[]{0, 0}; | |
/** | |
* Current cursor position. | |
*/ | |
public int[] cursor = new int[]{0, 0}; | |
/** | |
* Selection. | |
*/ | |
public int[][] selection = new int[][]{{0, 0}, {0, 0}}; | |
/** | |
* Color used to display text inside of enabled text area. | |
*/ | |
public Color enabledColor = new Color(224, 224, 224); | |
/** | |
* Color used to display text inside of disabled text area. | |
*/ | |
public Color disabledColor = new Color(112, 112, 112); | |
/** | |
* Color used to display cursor. | |
*/ | |
public Color cursorColor = new Color(255, 255, 255); | |
/** | |
* Color used to display text behind selection. | |
*/ | |
public Color highlightColor = new Color(0, 0, 255); | |
/** | |
* Color used to display text behind selection. | |
*/ | |
public int highlightColorOperator = GL11.GL_OR_REVERSE; | |
/** | |
* True if this text area is visible. | |
*/ | |
public boolean visible = true; | |
/** | |
* Characters that are considered whitespace running trough text. | |
*/ | |
public char[] whitespaceCharacters = new char[]{' ', '_'}; | |
/** | |
* Map of custom key bindings. | |
*/ | |
private final HashMap<Integer, Action> keyBindings = new HashMap<>(0); | |
public GUITextArea(FontRenderer fontRendererInstance, int width, int height, boolean isEnabled) | |
{ | |
this(fontRendererInstance, (Minecraft.getMinecraft().displayWidth - width) / 2, (Minecraft.getMinecraft().displayHeight - height) / 2, width, height, isEnabled); | |
} | |
public GUITextArea(FontRenderer fontRendererInstance, int xPosition, int yPosition, int width, int height, boolean isEnabled) | |
{ | |
this(fontRendererInstance, xPosition, yPosition, width, height, 64, 32, isEnabled); | |
} | |
public GUITextArea(FontRenderer fontRendererInstance, int xPosition, int yPosition, int width, int height, int maxLineCount, int maxLineLength, boolean isEnabled) | |
{ | |
this.fontRendererInstance = fontRendererInstance; | |
this.xPosition = xPosition; | |
this.yPosition = yPosition; | |
this.width = width; | |
this.height = height; | |
this.maxLineCount = maxLineCount; | |
this.maxLineLength = maxLineLength; | |
this.isEnabled = isEnabled; | |
this.text.add(""); | |
} | |
public void addKeyBinding(int characterKey, Action action) | |
{ | |
keyBindings.put(characterKey, action); | |
} | |
/** | |
* This method returns text using line break characters ('\n') in between the lines. | |
* | |
* @return Text string. | |
*/ | |
public String getRawText() | |
{ | |
String result = ""; | |
if (!isEmpty()) | |
{ | |
for (String line : this.text) | |
{ | |
result += line + "\n"; | |
} | |
} | |
return result.substring(0, result.length() - 1); | |
} | |
/** | |
* This method returns selected text. | |
* | |
* @return Selected text. | |
*/ | |
public String getSelectedText() | |
{ | |
String result = ""; | |
if (!isEmpty()) | |
{ | |
for (int line = selection[0][1]; line <= selection[1][1]; line++) | |
{ | |
if (line == selection[0][1]) | |
{ | |
result += text.get(line).substring(selection[0][0]); | |
} | |
else if (line == selection[1][1]) | |
{ | |
result += text.get(line).substring(0, selection[1][0]); | |
} | |
else | |
{ | |
result += text.get(line); | |
} | |
} | |
} | |
return result; | |
} | |
/** | |
* This method replaces selected area with specified text or puts the specified text at cursor position if there is | |
* no area selected. | |
* | |
* @param input | |
* Text to replace selected area with or put at cursor position if no area is selected. | |
* | |
* @return True if area was selected. | |
*/ | |
public boolean writeText(String... input) | |
{ | |
ArrayList<String> inputManager = new ArrayList<>(0); | |
for (String line : input) | |
{ | |
inputManager.addAll(Lists.newArrayList(line.split("\n"))); | |
} | |
String[] inputModified = inputManager.toArray(new String[inputManager.size()]); | |
boolean replace = false; | |
if (selection[0] != selection[1]) | |
{ | |
replace = true; | |
} | |
int firstLine; | |
int lastLine; | |
String lineStart; | |
String lineEnd; | |
if (replace) | |
{ | |
firstLine = selection[0][1]; | |
lastLine = selection[1][1]; | |
lineStart = text.get(firstLine).substring(0, selection[0][0]); | |
lineEnd = text.get(lastLine).substring(selection[1][0]); | |
} | |
else | |
{ | |
firstLine = lastLine = cursor[1]; | |
lineStart = text.get(firstLine).substring(0, cursor[0]); | |
lineEnd = text.get(lastLine).substring(cursor[0]); | |
} | |
for (int line = firstLine; line < lastLine; line++) | |
{ | |
this.text.remove(firstLine); | |
} | |
inputModified[0] = lineStart + inputModified[0]; | |
inputModified[inputModified.length - 1] = inputModified[inputModified.length - 1] + lineEnd; | |
this.text.addAll(firstLine, Lists.newArrayList(inputModified)); | |
this.moveCursor(new int[]{1, 0}); | |
return replace; | |
} | |
/** | |
* This method moves the cursor around on specified axis. Both arguments can be negative. | |
* | |
* @param amount | |
* This is number to characters/words to move the cursor from specified position. | |
* This argument members can be negative.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
*/ | |
public void moveCursor(int[] amount) | |
{ | |
if (!isEmpty()) | |
{ | |
if (this.text.size() - 1 < this.cursor[1] + amount[1]) | |
{ | |
this.cursor[1] = this.text.size() - 1; | |
} | |
else if (this.cursor[1] + amount[1] < 0) | |
{ | |
this.cursor[1] = 0; | |
} | |
else | |
{ | |
this.cursor[1] = this.cursor[1] + amount[1]; | |
} | |
if (this.text.get(this.cursor[1]).length() - 1 < this.cursor[0] + amount[0]) | |
{ | |
int tmp = this.text.get(this.cursor[1]).length() - 1; | |
this.cursor[0] = tmp < 0 ? 0 : tmp; | |
} | |
else if (this.cursor[0] + amount[0] < 0) | |
{ | |
this.cursor[0] = 0; | |
} | |
else | |
{ | |
this.cursor[0] = this.cursor[0] + amount[0]; | |
} | |
if (this.offsetDisplay[0] > this.cursor[0]) | |
{ | |
this.offsetDisplay[0] = this.cursor[0]; | |
} | |
else if (this.offsetDisplay[0] + this.width < this.cursor[0]) | |
{ | |
this.offsetDisplay[0] = this.cursor[0] - this.width; | |
} | |
if (this.offsetDisplay[1] > this.cursor[1]) | |
{ | |
this.offsetDisplay[1] = this.cursor[1]; | |
} | |
else if (this.offsetDisplay[1] + this.height < this.cursor[1]) | |
{ | |
this.offsetDisplay[1] = this.cursor[1] - this.height; | |
} | |
} | |
} | |
/** | |
* This method moves the cursor around on specified axis. Both arguments can be negative. | |
* | |
* @param amount | |
* This is number to characters/words to move the cursor from specified position. | |
* This argument members can be negative.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param whiteSpaces | |
* If true method will iterate over whitespace characters instead of all characters.</br> | |
* This allows skipping over words instead of individual characters. | |
*/ | |
public void moveCursor(int[] amount, boolean whiteSpaces) | |
{ | |
if (whiteSpaces) | |
{ | |
int[] newPosition = this.relativePos(amount, this.cursor, true); | |
moveCursor(new int[]{newPosition[0] - this.cursor[0], newPosition[1] - this.cursor[1]}); | |
} | |
else | |
{ | |
moveCursor(amount); | |
} | |
} | |
/** | |
* This method moves the cursor to specified position. | |
* | |
* @param newPosition | |
* Position to put cursor at.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
*/ | |
public void setCursorSafely(int[] newPosition) | |
{ | |
moveCursor(new int[]{newPosition[0] - this.cursor[0], newPosition[1] - this.cursor[1]}); | |
} | |
/** | |
* @param moveAmount | |
* This is number to characters/words to move from specified position. This argument can be negative.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param position | |
* This is the starting position used by this method.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param whiteSpaces | |
* If true method will iterate over whitespace characters instead of all characters.</br> | |
* This allows skipping over words instead of individual characters. | |
* | |
* @return New position relative to specified on by specified amount of characters/words. | |
*/ | |
public int[] relativePos(int[] moveAmount, int[] position, boolean whiteSpaces) | |
{ | |
return relativePos(moveAmount, position, whiteSpaces, this.text.toArray(new String[this.text.size()])); | |
} | |
/** | |
* Returns position relative to specified one by specified one in direction dependant on amount sign. | |
* | |
* @param moveAmount | |
* This is number to characters/words to move from specified position.<br/> | |
* This argument can be negative.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param position | |
* This is the starting position used by this method.<br/> | |
* If X coordinate of this argument is -1, | |
* the coordinate of the last letter in line defined by Y coordinate will be used.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param whiteSpaces | |
* If true method will iterate over whitespace characters instead of all characters.<br/> | |
* This allows skipping over words instead of individual characters. | |
* @param text | |
* Text to iterate over. | |
* | |
* @return New position relative to specified on by specified amount of characters/words. | |
*/ | |
public int[] relativePos(int[] moveAmount, int[] position, boolean whiteSpaces, String... text) | |
{ | |
//noinspection deprecation | |
return relativePos(moveAmount, position, whiteSpaces, false, text); | |
} | |
//TODO: Test wrapLineSearch. | |
//TODO: See if relativePos code can be compacted further. | |
/** | |
* Returns position relative to specified one by specified one in direction dependant on amount sign. | |
* | |
* @param moveAmount | |
* This is number to characters/words to move from specified position.<br/> | |
* This argument can be negative.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param position | |
* This is the starting position used by this method.<br/> | |
* If X coordinate of this argument is -1, | |
* the coordinate of the last letter in line defined by Y coordinate will be used.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param whiteSpaces | |
* If true method will iterate over whitespace characters instead of all characters.<br/> | |
* This allows skipping over words instead of individual characters. | |
* @param wrapLineSearch | |
* If true method will use exhaustive/multiline X coordinate mode.<br/> | |
* This means that if X coordinate of moveAmount is bigger than resulting line in specified direction, | |
* this method will recursively return value from line below/above this one.<br/> | |
* <p> | |
* <b>This is untested and could possibly create an infinite loop.<b/> | |
* @param text | |
* Text to iterate over. | |
* | |
* @return New position relative to specified on by specified amount of characters/words. | |
*/ | |
@Deprecated | |
public int[] relativePos(int[] moveAmount, int[] position, boolean whiteSpaces, boolean wrapLineSearch, String... text) | |
{ | |
if (!isEmpty()) | |
{ | |
if (position[0] == -1) | |
{ | |
position[0] = text[position[1]].length() - 1; | |
} | |
if (whiteSpaces) | |
{ | |
int[] result = position.clone(); | |
if (text.length < result[1] + moveAmount[1]) | |
{ | |
result[1] = text.length; | |
} | |
else if (result[1] + moveAmount[1] < 0) | |
{ | |
result[1] = 0; | |
} | |
else | |
{ | |
result[1] = result[1] + moveAmount[1]; | |
} | |
if (text[result[1]].length() - 1 < result[0] + moveAmount[0]) | |
{ | |
if (wrapLineSearch && position != new int[]{text[text.length - 1].length() - 1, text.length - 1}) | |
{ | |
//noinspection deprecation | |
this.relativePos(new int[]{moveAmount[0] - text[result[1]].substring(position[0], (text[result[1]].length() - 1)).length(), 0}, this.relativePos(new int[]{0, 1}, new int[]{0, ++position[1]}, true, true, text), true, true, text); | |
} | |
else | |
{ | |
result[0] = text[result[1]].length() - 1; | |
} | |
} | |
else if (result[0] + moveAmount[0] < 0) | |
{ | |
if (wrapLineSearch && position != new int[]{0, 0}) | |
{ | |
//noinspection deprecation | |
this.relativePos(new int[]{moveAmount[0] + text[result[1]].substring(0, position[0]).length(), 0}, this.relativePos(new int[]{0, -1}, new int[]{-1, --position[1]}, true, true, text), true, true, text); | |
} | |
else | |
{ | |
result[0] = 0; | |
} | |
} | |
else | |
{ | |
result[0] = result[0] + moveAmount[0]; | |
} | |
return result; | |
} | |
else | |
{ | |
int[] result = position.clone(); | |
if (text.length < result[1] + moveAmount[1]) | |
{ | |
result[1] = text.length; | |
} | |
else if (result[1] + moveAmount[1] < 0) | |
{ | |
result[1] = 0; | |
} | |
else | |
{ | |
result[1] = result[1] + moveAmount[1]; | |
} | |
int movementLeft = moveAmount[0]; | |
while (movementLeft != 0) | |
{ | |
try | |
{ | |
while (!ArrayUtils.contains(this.whitespaceCharacters, text[result[1]].charAt(result[0]))) | |
{ | |
result[0] += movementLeft < 0 ? -1 : 1; | |
} | |
movementLeft = movementLeft < 0 ? movementLeft + 1 : movementLeft - 1; | |
} catch (IndexOutOfBoundsException ignored) | |
{ | |
//Code has reached the end of line. | |
result[1] += movementLeft < 0 ? -1 : 1; | |
if (result[1] < 0) | |
{ | |
return new int[]{0, 0}; | |
} | |
else if (result[1] > text.length) | |
{ | |
return new int[]{text[text.length - 1].length(), text.length - 1}; | |
} | |
else | |
{ | |
result[0] = movementLeft < 0 ? text[result[1]].length() - 1 : 0; | |
} | |
} | |
} | |
} | |
} | |
return new int[]{0, 0}; | |
} | |
/** | |
* This method will remove specified selection from specified text. | |
* | |
* @param selection | |
* Selection to remove from text.<br/> | |
* <p> | |
* <b>This integer array of arrays must contain two arrays of integers representing first and second coordinate locations,<br/> | |
* and both coordinate locations must contain two integers representing X and Y coordinate.<b/> | |
* @param text | |
* Text to modify. | |
* | |
* @return Removed text from selection. | |
*/ | |
public String[] removeText(int[][] selection, String... text) | |
{ | |
ArrayList<String> result = Lists.newArrayList(text); | |
int firstLine = selection[0][1]; | |
int lastLine = selection[1][1]; | |
String start = ""; | |
String end = ""; | |
for (int line = firstLine; line <= lastLine; line++) | |
{ | |
if (line == firstLine) | |
{ | |
start = text[firstLine].substring(0, selection[0][0]); | |
} | |
else if (line == lastLine) | |
{ | |
end = text[lastLine].substring(selection[1][0], text[lastLine].length() - 1); | |
} | |
result.remove(firstLine); | |
} | |
if (!Objects.equals(start + end, "")) | |
{ | |
result.add(firstLine, start + end); | |
} | |
return result.toArray(new String[result.size()]); | |
} | |
/** | |
* Selects all text in text area. | |
*/ | |
public void selectEverything() | |
{ | |
this.selection[0][0] = 0; | |
this.selection[0][1] = 0; | |
this.selection[1][1] = this.text.size() - 1; | |
this.selection[1][0] = this.text.get(this.selection[1][1]).length() - 1; | |
this.cursor = this.selection[1]; | |
} | |
/** | |
* @return True if text doesn't contain anything. | |
*/ | |
public boolean isEmpty() | |
{ | |
return this.text.size() == 0 || this.text == new ArrayList<String>(0); | |
} | |
/** | |
* Removes characters from specified string. | |
* | |
* @param amount | |
* Number of characters to delete. This argument components can be negative.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param currentPosition | |
* Current position.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param text | |
* Text to remove characters currentPosition. | |
*/ | |
public void deleteCharacters(int[] amount, int[] currentPosition, String[] text) | |
{ | |
this.removeText(new int[][]{this.relativePos(amount, currentPosition, false, text), currentPosition}, text); | |
} | |
/** | |
* Removes words from specified string in direction defined by amount signs. | |
* | |
* @param amount | |
* Number of words to delete. This argument components can be negative.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param currentPosition | |
* Current position.<br/> | |
* <p> | |
* <b>This integer array must contain two integers representing X and Y coordinate.<b/> | |
* @param text | |
* Text to remove characters currentPosition. | |
*/ | |
public void deleteWords(int[] amount, int[] currentPosition, String[] text) | |
{ | |
this.removeText(new int[][]{this.relativePos(amount, currentPosition, true, text), currentPosition}, text); | |
} | |
/** | |
* Call this method from your GUI if you want keys to be handled automatically by this class. | |
* | |
* @param typedCharacter | |
* Character typed. | |
* @param keyCode | |
* Integer value of key typed. | |
* | |
* @return True if key was handled properly. | |
*/ | |
public boolean keyTyped(char typedCharacter, int keyCode) | |
{ | |
if (!this.isFocused) | |
{ | |
return false; | |
} | |
else | |
{ | |
if (isControlDown()) | |
{ | |
switch (keyCode) | |
{ | |
case Keyboard.KEY_A: | |
selectEverything(); | |
return true; | |
case Keyboard.KEY_C: | |
GuiScreen.setClipboardString(this.getSelectedText()); | |
return true; | |
case Keyboard.KEY_V: | |
if (this.isEnabled) | |
{ | |
this.writeText(GuiScreen.getClipboardString()); | |
} | |
return true; | |
case Keyboard.KEY_X: | |
GuiScreen.setClipboardString(this.getSelectedText()); | |
if (this.isEnabled) | |
{ | |
this.writeText(""); | |
} | |
return true; | |
} | |
} | |
switch (keyCode) | |
{ | |
case Keyboard.KEY_BACK: | |
if (this.isEnabled) | |
{ | |
if (this.cursor[0] == 0 && this.cursor[1] != 0) | |
{ | |
this.text.set(this.cursor[1] - 1, this.text.get(this.cursor[1] - 1) + this.text.remove(this.cursor[1])); | |
} | |
else if (isControlDown()) | |
{ | |
this.deleteWords(new int[]{-1, 0}, this.cursor, this.text.toArray(new String[this.text.size()])); | |
} | |
else | |
{ | |
if (selection[0] == selection[1]) | |
{ | |
this.deleteCharacters(new int[]{-1, 0}, this.cursor, this.text.toArray(new String[this.text.size()])); | |
} | |
else | |
{ | |
this.text = Lists.newArrayList(this.removeText(selection, this.text.toArray(new String[this.text.size()]))); | |
} | |
} | |
} | |
return true; | |
case Keyboard.KEY_DELETE: | |
if (this.isEnabled) | |
{ | |
if (isControlDown()) | |
{ | |
this.deleteWords(new int[]{1, 0}, this.cursor, this.text.toArray(new String[this.text.size()])); | |
} | |
else | |
{ | |
this.deleteCharacters(new int[]{1, 0}, this.cursor, this.text.toArray(new String[this.text.size()])); | |
} | |
} | |
return true; | |
case Keyboard.KEY_HOME: | |
if (isShiftDown()) | |
{ | |
this.selection = new int[][]{{0, this.cursor[1]}, this.cursor}; | |
} | |
else | |
{ | |
this.cursor = new int[]{0, this.cursor[1]}; | |
} | |
return true; | |
case Keyboard.KEY_END: | |
if (isShiftDown()) | |
{ | |
this.selection = new int[][]{this.cursor, {0, this.text.get(this.cursor[1]).length() - 1}}; | |
} | |
else | |
{ | |
this.cursor = new int[]{this.text.get(this.cursor[1]).length() - 1, this.cursor[1]}; | |
} | |
return true; | |
case Keyboard.KEY_LEFT: | |
if (isShiftDown()) | |
{ | |
if (isControlDown()) | |
{ | |
this.selection = new int[][]{this.cursor, this.relativePos(new int[]{-1, 0}, this.selection[1], true, (String[]) this.text.toArray(new String[this.text.size()]))}; | |
} | |
else | |
{ | |
this.selection = new int[][]{this.cursor, this.relativePos(new int[]{-1, 0}, this.selection[1], false, (String[]) this.text.toArray(new String[this.text.size()]))}; | |
} | |
} | |
else if (isControlDown()) | |
{ | |
this.moveCursor(new int[]{-1, 0}, true); | |
} | |
else | |
{ | |
this.moveCursor(new int[]{-1, 0}); | |
} | |
return true; | |
case Keyboard.KEY_RIGHT: | |
if (isShiftDown()) | |
{ | |
if (isControlDown()) | |
{ | |
this.selection = new int[][]{this.cursor, this.relativePos(new int[]{1, 0}, this.selection[1], true, (String[]) this.text.toArray(new String[this.text.size()]))}; | |
} | |
else | |
{ | |
this.selection = new int[][]{this.cursor, this.relativePos(new int[]{1, 0}, this.selection[1], false, (String[]) this.text.toArray(new String[this.text.size()]))}; | |
} | |
} | |
else if (isControlDown()) | |
{ | |
this.moveCursor(new int[]{1, 0}, true); | |
} | |
else | |
{ | |
this.moveCursor(new int[]{1, 0}); | |
} | |
return true; | |
case Keyboard.KEY_DOWN: | |
if (isShiftDown()) | |
{ | |
if (isControlDown()) | |
{ | |
this.selection = new int[][]{this.cursor, this.relativePos(new int[]{1, 1}, this.selection[1], true, (String[]) this.text.toArray(new String[this.text.size()]))}; | |
} | |
else | |
{ | |
this.selection = new int[][]{this.cursor, this.relativePos(new int[]{0, 1}, this.selection[1], false, (String[]) this.text.toArray(new String[this.text.size()]))}; | |
} | |
} | |
else if (isControlDown()) | |
{ | |
this.moveCursor(new int[]{0, 1}, true); | |
} | |
else | |
{ | |
this.moveCursor(new int[]{0, 1}); | |
} | |
return true; | |
case Keyboard.KEY_UP: | |
if (isShiftDown()) | |
{ | |
if (isControlDown()) | |
{ | |
this.selection = new int[][]{this.cursor, this.relativePos(new int[]{-1, -1}, this.selection[1], true, (String[]) this.text.toArray(new String[this.text.size()]))}; | |
} | |
else | |
{ | |
this.selection = new int[][]{this.cursor, this.relativePos(new int[]{0, -1}, this.selection[1], false, (String[]) this.text.toArray(new String[this.text.size()]))}; | |
} | |
} | |
else if (isControlDown()) | |
{ | |
this.moveCursor(new int[]{0, -1}, true); | |
} | |
else | |
{ | |
this.moveCursor(new int[]{0, -1}); | |
} | |
return true; | |
default: | |
if (keyBindings.containsKey(keyCode)) | |
{ | |
if (!keyBindings.get(keyCode).execute()) | |
{ | |
return handleInput(typedCharacter); | |
} | |
} | |
else | |
{ | |
return handleInput(typedCharacter); | |
} | |
} | |
} | |
return false; | |
} | |
protected boolean handleInput(char typedCharacter) | |
{ | |
if (ChatAllowedCharacters.isAllowedCharacter(typedCharacter)) | |
{ | |
if (this.isEnabled) | |
{ | |
this.writeText(Character.toString(typedCharacter)); | |
} | |
return true; | |
} | |
else | |
{ | |
return false; | |
} | |
} | |
/** | |
* Call this method from your GUI if you want mouse to be handled automatically by this class. | |
* | |
* @param xPos | |
* Clicked X position. | |
* @param yPos | |
* Clicked Y position. | |
* @param buttonClicked | |
* Clicked mouse button code. | |
*/ | |
public void mouseClicked(int xPos, int yPos, int buttonClicked) | |
{ | |
boolean inside = xPos >= this.xPosition && | |
xPos < this.xPosition + this.width && | |
yPos >= this.yPosition && | |
yPos < this.yPosition + this.height; | |
if (this.canLoseFocus) | |
{ | |
this.isFocused = inside; | |
} | |
if (this.isFocused && inside && buttonClicked == 0 && !isEmpty()) | |
{ | |
int[] posClicked = new int[2]; | |
posClicked[1] = this.height - (xPos - this.xPosition) / this.fontRendererInstance.FONT_HEIGHT; | |
if (this.text.size() >= posClicked[1] + offsetDisplay[1]) | |
{ | |
String clickedLine = this.fontRendererInstance.trimStringToWidth(this.text.get(posClicked[1] + offsetDisplay[1]).substring(this.offsetDisplay[0]), this.width); | |
int length = xPos - this.xPosition; | |
if (this.enableBackgroundDrawing) | |
{ | |
length -= 4; | |
} | |
posClicked[0] = this.fontRendererInstance.trimStringToWidth(clickedLine, length).length() + this.offsetDisplay[0]; | |
this.setCursorSafely(posClicked); | |
this.selection[0] = this.cursor; | |
} | |
else | |
{ | |
this.setCursorSafely(new int[]{this.text.get(this.text.size() - 1).length(), this.text.size() - 1}); | |
this.selection[0] = this.cursor; | |
} | |
} | |
} | |
/** | |
* @param mouseX | |
* Current mouse X position. | |
* @param mouseY | |
* Current mouse Y position. | |
* @param clickedMouseButton | |
* Clicked mouse button code. | |
* @param timeSinceLastClick | |
* Value representing ticks mouse button was held for. | |
*/ | |
public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) | |
{ | |
boolean inside = mouseX >= this.xPosition && | |
mouseX < this.xPosition + this.width && | |
mouseY >= this.yPosition && | |
mouseY < this.yPosition + this.height; | |
if (this.canLoseFocus) | |
{ | |
this.isFocused = inside; | |
} | |
if (this.isFocused && inside && clickedMouseButton == 0 && !isEmpty()) | |
{ | |
int[] posDragged = new int[2]; | |
posDragged[1] = this.height - (mouseX - this.xPosition) / this.fontRendererInstance.FONT_HEIGHT; | |
if (this.text.size() >= posDragged[1] + offsetDisplay[1]) | |
{ | |
String currentLine = this.fontRendererInstance.trimStringToWidth(this.text.get(posDragged[1] + offsetDisplay[1]).substring(this.offsetDisplay[0]), this.width - this.lineMargin * 2); | |
int length = mouseX - this.xPosition; | |
if (this.enableBackgroundDrawing) | |
{ | |
length -= 4; | |
} | |
posDragged[0] = this.fontRendererInstance.trimStringToWidth(currentLine, length).length() + this.offsetDisplay[0]; | |
this.selection[1] = posDragged; | |
} | |
else | |
{ | |
this.selection[1] = new int[]{this.text.get(this.text.size() - 1).length(), this.text.size() - 1}; | |
} | |
} | |
} | |
public void render() | |
{ | |
if (this.visible) | |
{ | |
if (this.enableBackgroundDrawing) | |
{ | |
drawRect(this.xPosition - 1, this.yPosition - 1, this.xPosition + this.width + 1, this.yPosition + this.height + 1, new Color(95, 95, 95).getRGB()); | |
drawRect(this.xPosition, this.yPosition, this.xPosition + this.width, this.yPosition + this.height, new Color(16, 16, 16).getRGB()); | |
} | |
if (!isEmpty()) | |
{ | |
int textColor = this.isEnabled ? this.enabledColor.getRGB() : this.disabledColor.getRGB(); | |
int firstLine = this.offsetDisplay[1]; | |
int lineCount = this.height % (this.fontRendererInstance.FONT_HEIGHT + this.lineMargin) >= this.lineMargin ? (this.height / (this.fontRendererInstance.FONT_HEIGHT + this.lineMargin)) : (this.height / (this.fontRendererInstance.FONT_HEIGHT + this.lineMargin)) - 1; | |
int lastLine = firstLine + lineCount; | |
for (int line = firstLine; line < lastLine && line <= this.text.size() - 1; line++) | |
{ | |
String renderedLine = this.fontRendererInstance.trimStringToWidth(this.text.get(line).substring(this.offsetDisplay[0]), this.width - this.lineMargin * 2); | |
if (renderedLine != null && !Objects.equals(renderedLine, "")) | |
{ | |
int xPos = this.xPosition + this.lineMargin; | |
int yPos = this.yPosition + (this.lineMargin + (line * (this.fontRendererInstance.FONT_HEIGHT + this.lineMargin))); | |
this.fontRendererInstance.drawString(renderedLine, xPos, yPos, textColor); | |
//DEBUGGING | |
this.fontRendererInstance.drawString(String.valueOf(line), xPos - 15, yPos, textColor); | |
//DEBUGGING | |
this.fontRendererInstance.drawString("CHAR:" + String.valueOf(this.cursor[0]) + ", LINE:" + String.valueOf(this.cursor[1]), 50, 50, textColor); | |
} | |
if (cursorLogic() && this.cursor[1] == line) | |
{ | |
int cursorX; | |
if (this.text.get(line).length() > 0) | |
{ | |
//DEBUGGING | |
System.out.println(this.text.get(line)); | |
cursorX = this.xPosition + this.lineMargin + this.fontRendererInstance.getStringWidth(this.text.get(line).substring(this.offsetDisplay[0], this.cursor[0])); | |
} | |
else | |
{ | |
cursorX = this.xPosition + this.lineMargin; | |
} | |
int cursorY = this.yPosition + (this.lineMargin + (line * (this.fontRendererInstance.FONT_HEIGHT + this.lineMargin))); | |
Gui.drawRect(cursorX, cursorY, cursorX, cursorY + (this.lineMargin * 2) + this.fontRendererInstance.FONT_HEIGHT, cursorColor.getRGB()); | |
} | |
} | |
if (this.selection[0] != this.selection[1]) | |
{ | |
for (int line = this.selection[0][1]; line < this.selection[1][1]; line++) | |
{ | |
int drawnWidth; | |
int lineX; | |
int lineY = this.yPosition + this.height - (this.lineMargin + ((firstLine - line) * (this.fontRendererInstance.FONT_HEIGHT + this.lineMargin))); | |
if (line == this.selection[0][1]) | |
{ | |
drawnWidth = this.fontRendererInstance.getStringWidth(this.fontRendererInstance.trimStringToWidth(this.text.get(line).substring(this.offsetDisplay[0]), this.width - this.lineMargin * 2).substring(this.selection[0][0])); | |
lineX = this.xPosition + this.width - (drawnWidth + this.lineMargin); | |
} | |
else if (line == this.selection[1][1]) | |
{ | |
drawnWidth = this.fontRendererInstance.getStringWidth(this.text.get(line).substring(this.offsetDisplay[0], this.selection[1][0])); | |
lineX = this.xPosition + this.lineMargin; | |
} | |
else | |
{ | |
drawnWidth = this.width - this.lineMargin * 2; | |
lineX = this.xPosition + this.lineMargin; | |
} | |
this.drawBoxOverlay(new int[][]{{lineX, lineY}, {lineX + drawnWidth, lineY + this.lineMargin + this.fontRendererInstance.FONT_HEIGHT}}); | |
} | |
} | |
} | |
} | |
} | |
private int cursorTimer; | |
private boolean cursorLogic() | |
{ | |
cursorTimer = cursorTimer > 40 ? 0 : cursorTimer + 1; | |
return this.isFocused && this.cursorTimer / 6 % 2 == 0; | |
} | |
public void updateCursorTimer() | |
{ | |
cursorTimer++; | |
} | |
/** | |
* Draws the vertical line cursor in the text area. | |
* | |
* @param selection | |
* This is an integer array of arrays containing box display vertices.<br/> | |
* <p> | |
* <b>This integer array of arrays must contain two arrays of integers representing first and second coordinate locations,<br/> | |
* and both coordinate locations must contain two integers representing X and Y coordinate.<b/> | |
*/ | |
private void drawBoxOverlay(int[][] selection) | |
{ | |
Tessellator tessellator = Tessellator.getInstance(); | |
WorldRenderer worldrenderer = tessellator.getWorldRenderer(); | |
GlStateManager.color(highlightColor.getRed(), highlightColor.getGreen(), highlightColor.getBlue(), 255.0F); | |
GlStateManager.disableTexture2D(); | |
GlStateManager.enableColorLogic(); | |
GlStateManager.colorLogicOp(highlightColorOperator); | |
worldrenderer.startDrawingQuads(); | |
worldrenderer.addVertex((double) selection[0][0], (double) selection[1][0], 0.0D); | |
worldrenderer.addVertex((double) selection[1][0], (double) selection[1][1], 0.0D); | |
worldrenderer.addVertex((double) selection[1][1], (double) selection[0][1], 0.0D); | |
worldrenderer.addVertex((double) selection[0][1], (double) selection[0][0], 0.0D); | |
tessellator.draw(); | |
GlStateManager.disableColorLogic(); | |
GlStateManager.enableTexture2D(); | |
} | |
/** | |
* @return True if control key is being held down. | |
*/ | |
private static boolean isControlDown() | |
{ | |
return Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL); | |
} | |
/** | |
* @return True if shift key is being held down. | |
*/ | |
private static boolean isShiftDown() | |
{ | |
return Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT); | |
} | |
/** | |
* @return True if alt key is being held down. | |
*/ | |
private static boolean isAltDown() | |
{ | |
return Keyboard.isKeyDown(Keyboard.KEY_LMENU); | |
} | |
public FontRenderer getFontRendererInstance() | |
{ | |
return fontRendererInstance; | |
} | |
public int getXPosition() | |
{ | |
return xPosition; | |
} | |
public void setXPosition(int xPosition) | |
{ | |
this.xPosition = xPosition; | |
} | |
public int getYPosition() | |
{ | |
return yPosition; | |
} | |
public void setYPosition(int yPosition) | |
{ | |
this.yPosition = yPosition; | |
} | |
public int getWidth() | |
{ | |
return width; | |
} | |
public void setWidth(int width) | |
{ | |
this.width = width; | |
} | |
public int getHeight() | |
{ | |
return height; | |
} | |
public void setHeight(int height) | |
{ | |
this.height = height; | |
} | |
public ArrayList<String> getText() | |
{ | |
return text; | |
} | |
public void setText(ArrayList<String> text) | |
{ | |
this.text = text; | |
} | |
public int getMaxLineCount() | |
{ | |
return maxLineCount; | |
} | |
public void setMaxLineCount(int maxLineCount) | |
{ | |
this.maxLineCount = maxLineCount; | |
} | |
public int getMaxLineLength() | |
{ | |
return maxLineLength; | |
} | |
public void setMaxLineLength(int maxLineLength) | |
{ | |
this.maxLineLength = maxLineLength; | |
} | |
public int getLineMargin() | |
{ | |
return lineMargin; | |
} | |
public void setLineMargin(int lineMargin) | |
{ | |
this.lineMargin = lineMargin; | |
} | |
public boolean isBackgroundDrawingEnabled() | |
{ | |
return enableBackgroundDrawing; | |
} | |
public void setEnableBackgroundDrawing(boolean enableBackgroundDrawing) | |
{ | |
this.enableBackgroundDrawing = enableBackgroundDrawing; | |
} | |
public boolean canLoseFocus() | |
{ | |
return canLoseFocus; | |
} | |
public void setCanLoseFocus(boolean canLoseFocus) | |
{ | |
this.canLoseFocus = canLoseFocus; | |
} | |
public boolean isFocused() | |
{ | |
return isFocused; | |
} | |
public void setIsFocused(boolean isFocused) | |
{ | |
this.isFocused = isFocused; | |
} | |
public boolean isEnabled() | |
{ | |
return isEnabled; | |
} | |
public void setIsEnabled(boolean isEnabled) | |
{ | |
this.isEnabled = isEnabled; | |
} | |
public int[] getOffsetDisplay() | |
{ | |
return offsetDisplay; | |
} | |
public void setOffsetDisplay(int[] offsetDisplay) | |
{ | |
this.offsetDisplay = offsetDisplay; | |
} | |
public int[] getCursor() | |
{ | |
return cursor; | |
} | |
public void setCursor(int[] cursor) | |
{ | |
this.cursor = cursor; | |
} | |
public int[][] getSelection() | |
{ | |
return selection; | |
} | |
public void setSelection(int[][] selection) | |
{ | |
this.selection = selection; | |
} | |
public Color getEnabledColor() | |
{ | |
return enabledColor; | |
} | |
public void setEnabledColor(Color enabledColor) | |
{ | |
this.enabledColor = enabledColor; | |
} | |
public Color getDisabledColor() | |
{ | |
return disabledColor; | |
} | |
public void setDisabledColor(Color disabledColor) | |
{ | |
this.disabledColor = disabledColor; | |
} | |
public boolean isVisible() | |
{ | |
return visible; | |
} | |
public void setVisible(boolean visible) | |
{ | |
this.visible = visible; | |
} | |
public char[] getWhitespaceCharacters() | |
{ | |
return whitespaceCharacters; | |
} | |
public void setWhitespaceCharacters(char[] whitespaceCharacters) | |
{ | |
this.whitespaceCharacters = whitespaceCharacters; | |
} | |
public HashMap<Integer, Action> getKeyBindings() | |
{ | |
return keyBindings; | |
} | |
public Color getCursorColor() | |
{ | |
return cursorColor; | |
} | |
public void setCursorColor(Color cursorColor) | |
{ | |
this.cursorColor = cursorColor; | |
} | |
public Color getHighlightColor() | |
{ | |
return highlightColor; | |
} | |
public void setHighlightColor(Color highlightColor) | |
{ | |
this.highlightColor = highlightColor; | |
} | |
public int getHighlightColorOperator() | |
{ | |
return highlightColorOperator; | |
} | |
public void setHighlightColorOperator(int highlightColorOperator) | |
{ | |
this.highlightColorOperator = highlightColorOperator; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment