/*
* Copyright 2016 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.nui.widgets;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.input.Keyboard;
import org.terasology.input.Keyboard.KeyId;
import org.terasology.input.MouseInput;
import org.terasology.input.device.KeyboardDevice;
import org.terasology.math.TeraMath;
import org.terasology.math.geom.Rect2i;
import org.terasology.math.geom.Vector2i;
import org.terasology.rendering.FontColor;
import org.terasology.rendering.FontUnderline;
import org.terasology.rendering.assets.font.Font;
import org.terasology.rendering.assets.texture.TextureRegion;
import org.terasology.rendering.nui.BaseInteractionListener;
import org.terasology.rendering.nui.Canvas;
import org.terasology.rendering.nui.Color;
import org.terasology.rendering.nui.CoreWidget;
import org.terasology.rendering.nui.InteractionListener;
import org.terasology.rendering.nui.LayoutConfig;
import org.terasology.rendering.nui.SubRegion;
import org.terasology.rendering.nui.TextLineBuilder;
import org.terasology.rendering.nui.databinding.Binding;
import org.terasology.rendering.nui.databinding.DefaultBinding;
import org.terasology.rendering.nui.events.NUIKeyEvent;
import org.terasology.rendering.nui.events.NUIMouseClickEvent;
import org.terasology.rendering.nui.events.NUIMouseDragEvent;
import org.terasology.rendering.nui.events.NUIMouseReleaseEvent;
import org.terasology.utilities.Assets;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* This class describes a generic text-box widget.
*/
public class UIText extends CoreWidget {
private static final Logger logger = LoggerFactory.getLogger(UIText.class);
private static final float BLINK_RATE = 0.25f;
/** The text contained by the text box. */
@LayoutConfig
protected Binding<String> text = new DefaultBinding<>("");
/* The placeholder hint text. */
@LayoutConfig
private String hintText = "";
/* Whether the box is currently showing the hint text. */
private boolean isShowingHintText = true;
/** Whether the content needs to be displayed on multiple lines. */
@LayoutConfig
protected boolean multiline;
/** Whether the text box is read-only. */
@LayoutConfig
protected boolean readOnly;
/** The position of the cursor in the text box. */
protected int cursorPosition;
/** The index in the text where the selection starts. */
protected int selectionStart;
/** The last assigned width of the text box. */
protected int lastWidth;
/** The font in which text was drawn the last time before the current update. */
protected Font lastFont;
/** A list of all activation event listeners (handle what to do when the text box is activated) of the text box. */
protected List<ActivateEventListener> activationListeners = Lists.newArrayList();
/** A list of all cursor update event listeners (handle what to do when the cursor is moved) of the text box. */
protected List<CursorUpdateEventListener> cursorUpdateListeners = Lists.newArrayList();
/** A list of text change event listeners (handle what to do when the text in the widget is changed) of the text box. */
protected List<TextChangeEventListener> textChangeListeners = Lists.newArrayList();
/** The number of characters between the start of the text in the widget and the current position of the cursor. */
protected int offset;
/**
* The interaction listener of the widget. This handles how the widget reacts to different stimuli from the user.
*/
protected InteractionListener interactionListener = new BaseInteractionListener() {
boolean dragging;
/**
* Defines what to do when the user clicks a mouse button while pointing at the widget. More specifically, it
* moves the cursor and sets "dragging" to true.
*
* @param event The event corresponding to the mouse click
* @return Whether a left mouse click was successfully detected and handled.
*/
@Override
public boolean onMouseClick(NUIMouseClickEvent event) {
if (event.getMouseButton() == MouseInput.MOUSE_LEFT) {
moveCursor(event.getRelativeMousePosition(), false, event.getKeyboard());
dragging = true;
return true;
}
return false;
}
/**
* Defines what to do when the user drags the mouse in the widget. Specifically, it moves the cursor if the
* "dragging" variable is set to true (that is, the user is dragging with the left mouse button pressed)
*
* @param event The event corresponding to the mouse drag.
*/
@Override
public void onMouseDrag(NUIMouseDragEvent event) {
if (dragging) {
moveCursor(event.getRelativeMousePosition(), true, event.getKeyboard());
}
}
/**
* Defines what to do when a mouse button is released. Specifically, it sets "dragging" to false if the button
* that was released is the left mouse button.
*
* @param event The event corresponding to the releasing of the mouse button.
*/
@Override
public void onMouseRelease(NUIMouseReleaseEvent event) {
if (event.getMouseButton() == MouseInput.MOUSE_LEFT) {
dragging = false;
}
}
};
private float blinkCounter;
private TextureRegion cursorTexture;
/**
* Default constructor.
*/
public UIText() {
cursorTexture = Assets.getTexture("engine:white").get();
}
/**
* Parametrized constructor.
*
* @param id The ID to assign to the widget
*/
public UIText(String id) {
super(id);
cursorTexture = Assets.getTexture("engine:white").get();
}
/**
* Handles how the widget is drawn.
*
* @param canvas The canvas on which the widget resides.
*/
@Override
public void onDraw(Canvas canvas) {
if (text.get() == null) {
text.set("");
}
if (text.get().equals("")) {
text.set(hintText);
isShowingHintText = true;
}
if (isShowingHintText) {
setCursorPosition(0);
if (!text.get().equals(hintText) && text.get().endsWith(hintText)) {
text.set(text.get().substring(0, text.get().length()-hintText.length()));
setCursorPosition(text.get().length());
isShowingHintText = false;
}
}
lastFont = canvas.getCurrentStyle().getFont();
lastWidth = canvas.size().x;
if (isEnabled()) {
canvas.addInteractionRegion(interactionListener, canvas.getRegion());
}
correctCursor();
int widthForDraw = (multiline) ? canvas.size().x : lastFont.getWidth(getText());
try (SubRegion ignored = canvas.subRegion(canvas.getRegion(), true);
SubRegion ignored2 = canvas.subRegion(Rect2i.createFromMinAndSize(-offset, 0, widthForDraw + 1, Integer.MAX_VALUE), false)) {
if (isShowingHintText && !readOnly) {
canvas.drawTextRaw(text.get(), lastFont, canvas.getCurrentStyle().getHintTextColor(), canvas.getRegion());
} else {
canvas.drawText(text.get(), canvas.getRegion());
}
if (isFocused()) {
if (hasSelection()) {
drawSelection(canvas);
} else {
drawCursor(canvas);
}
}
}
}
/**
* Draws the selection indication which indicates that a certain part of the text is selected.
*
* @param canvas The canvas on which the widget resides
*/
protected void drawSelection(Canvas canvas) {
Font font = canvas.getCurrentStyle().getFont();
String currentText = getText();
int start = Math.min(getCursorPosition(), selectionStart);
int end = Math.max(getCursorPosition(), selectionStart);
Color textColor = canvas.getCurrentStyle().getTextColor();
int canvasWidth = (multiline) ? canvas.size().x : Integer.MAX_VALUE;
// TODO: Support different text alignments
List<String> rawLinesAfterCursor = TextLineBuilder.getLines(font, currentText, Integer.MAX_VALUE);
int currentChar = 0;
int lineOffset = 0;
for (int lineIndex = 0; lineIndex < rawLinesAfterCursor.size() && currentChar <= end; ++lineIndex) {
String line = rawLinesAfterCursor.get(lineIndex);
List<String> innerLines = TextLineBuilder.getLines(font, line, canvasWidth);
for (int innerLineIndex = 0; innerLineIndex < innerLines.size() && currentChar <= end; ++innerLineIndex) {
String innerLine = innerLines.get(innerLineIndex);
String selectionString;
int offsetX = 0;
if (currentChar + innerLine.length() < start) {
selectionString = "";
} else if (currentChar < start) {
offsetX = font.getWidth(innerLine.substring(0, start - currentChar));
selectionString = innerLine.substring(start - currentChar, Math.min(end - currentChar, innerLine.length()));
} else if (currentChar + innerLine.length() >= end) {
selectionString = innerLine.substring(0, end - currentChar);
} else {
selectionString = innerLine;
}
if (!selectionString.isEmpty()) {
int selectionWidth = font.getWidth(selectionString);
Vector2i selectionTopLeft = new Vector2i(offsetX, (lineOffset) * font.getLineHeight());
Rect2i region = Rect2i.createFromMinAndSize(selectionTopLeft.x, selectionTopLeft.y, selectionWidth, font.getLineHeight());
canvas.drawTexture(cursorTexture, region, textColor);
canvas.drawTextRaw(FontUnderline.strip(FontColor.stripColor(selectionString)), font, textColor.inverse(), region);
}
currentChar += innerLine.length();
lineOffset++;
}
currentChar++;
}
}
/**
* Draws the cursor in the text field.
*
* @param canvas The canvas on which the widget resides
*/
protected void drawCursor(Canvas canvas) {
if (blinkCounter < BLINK_RATE) {
Font font = canvas.getCurrentStyle().getFont();
String beforeCursor = text.get();
if (getCursorPosition() < text.get().length()) {
beforeCursor = beforeCursor.substring(0, getCursorPosition());
}
List<String> lines = TextLineBuilder.getLines(font, beforeCursor, canvas.size().x);
// TODO: Support different alignments
int lastLineWidth = font.getWidth(lines.get(lines.size() - 1));
Rect2i region = Rect2i.createFromMinAndSize(lastLineWidth, (lines.size() - 1) * font.getLineHeight(),
1, font.getLineHeight());
canvas.drawTexture(cursorTexture, region, canvas.getCurrentStyle().getTextColor());
}
}
/**
* Get the preferred content size of the widget.
*
* @param canvas The canvas on which the widget resides
* @param areaHint A suggestion for the preferred size of the widget
* @return The preferred content size of the widget
*/
@Override
public Vector2i getPreferredContentSize(Canvas canvas, Vector2i areaHint) {
Font font = canvas.getCurrentStyle().getFont();
if (isMultiline()) {
List<String> lines = TextLineBuilder.getLines(font, text.get(), areaHint.x);
return font.getSize(lines);
} else {
return new Vector2i(font.getWidth(getText()), font.getLineHeight());
}
}
/**
* Get the maximum content size of the widget.
*
* @param canvas The canvas on which the widget resides
* @return The maximum content size of the widget
*/
@Override
public Vector2i getMaxContentSize(Canvas canvas) {
Font font = canvas.getCurrentStyle().getFont();
if (isMultiline()) {
return new Vector2i(Integer.MAX_VALUE, Integer.MAX_VALUE);
} else {
return new Vector2i(Integer.MAX_VALUE, font.getLineHeight());
}
}
/**
* Handles what to do when a key is pressed while the text box is active.
*
* @param event The event corresponding to the key being pressed
* @return Whether the event was handled successfully or not.
*/
@Override
public boolean onKeyEvent(NUIKeyEvent event) {
correctCursor();
boolean eventHandled = false;
if (isEnabled() && event.isDown() && lastFont != null) {
if (isShowingHintText && !readOnly) {
if (event.getKeyboard().isKeyDown(Keyboard.KeyId.LEFT_CTRL)
|| event.getKeyboard().isKeyDown(Keyboard.KeyId.RIGHT_CTRL)) {
if (event.getKey() == Keyboard.Key.V) {
removeSelection();
paste();
eventHandled = true;
}
}
if (event.getKeyCharacter() != 0 && lastFont.hasCharacter(event.getKeyCharacter())) {
String fullText = text.get();
String before = fullText.substring(0, Math.min(getCursorPosition(), selectionStart));
String after = fullText.substring(Math.max(getCursorPosition(), selectionStart));
setText(before + event.getKeyCharacter() + after);
setCursorPosition(Math.min(getCursorPosition(), selectionStart) + 1);
}
} else {
String fullText = text.get();
switch (event.getKey().getId()) {
case KeyId.LEFT: {
if (hasSelection() && !isSelectionModifierActive(event.getKeyboard())) {
setCursorPosition(Math.min(getCursorPosition(), selectionStart));
} else if (getCursorPosition() > 0) {
decreaseCursorPosition(1, !isSelectionModifierActive(event.getKeyboard()));
}
eventHandled = true;
break;
}
case KeyId.RIGHT: {
if (hasSelection() && !isSelectionModifierActive(event.getKeyboard())) {
setCursorPosition(Math.max(getCursorPosition(), selectionStart));
} else if (getCursorPosition() < fullText.length()) {
increaseCursorPosition(1, !isSelectionModifierActive(event.getKeyboard()));
}
eventHandled = true;
break;
}
case KeyId.HOME: {
setCursorPosition(0, !isSelectionModifierActive(event.getKeyboard()));
offset = 0;
eventHandled = true;
break;
}
case KeyId.END: {
setCursorPosition(fullText.length(), !isSelectionModifierActive(event.getKeyboard()));
eventHandled = true;
break;
}
default: {
if (event.getKeyboard().isKeyDown(KeyId.LEFT_CTRL)
|| event.getKeyboard().isKeyDown(KeyId.RIGHT_CTRL)) {
if (event.getKey() == Keyboard.Key.C) {
copySelection();
eventHandled = true;
break;
}
}
}
}
if (!readOnly) {
switch (event.getKey().getId()) {
case KeyId.BACKSPACE: {
if (hasSelection()) {
removeSelection();
} else if (getCursorPosition() > 0) {
String before = fullText.substring(0, getCursorPosition() - 1);
String after = fullText.substring(getCursorPosition());
if (getCursorPosition() < fullText.length()) {
decreaseCursorPosition(1);
}
setText(before + after);
}
eventHandled = true;
break;
}
case KeyId.DELETE: {
if (hasSelection()) {
removeSelection();
} else if (getCursorPosition() < fullText.length()) {
String before = fullText.substring(0, getCursorPosition());
String after = fullText.substring(getCursorPosition() + 1);
setText(before + after);
}
eventHandled = true;
break;
}
case KeyId.ENTER:
case KeyId.NUMPAD_ENTER: {
for (ActivateEventListener listener : activationListeners) {
listener.onActivated(this);
}
eventHandled = true;
break;
}
default: {
if (event.getKeyboard().isKeyDown(KeyId.LEFT_CTRL)
|| event.getKeyboard().isKeyDown(KeyId.RIGHT_CTRL)) {
if (event.getKey() == Keyboard.Key.V) {
removeSelection();
paste();
eventHandled = true;
break;
} else if (event.getKey() == Keyboard.Key.X) {
copySelection();
removeSelection();
eventHandled = true;
break;
}
}
if (event.getKeyCharacter() != 0 && lastFont.hasCharacter(event.getKeyCharacter())) {
String before = fullText.substring(0, Math.min(getCursorPosition(), selectionStart));
String after = fullText.substring(Math.max(getCursorPosition(), selectionStart));
setText(before + event.getKeyCharacter() + after);
setCursorPosition(Math.min(getCursorPosition(), selectionStart) + 1);
eventHandled = true;
}
break;
}
}
}
}
}
updateOffset();
return eventHandled;
}
/**
* Updates the cursor offset.
*/
protected void updateOffset() {
if (lastFont != null && !multiline) {
String before = getText().substring(0, getCursorPosition());
int cursorDist = lastFont.getWidth(before);
if (cursorDist < offset) {
offset = cursorDist;
}
if (cursorDist > offset + lastWidth) {
offset = cursorDist - lastWidth + 1;
}
}
}
/**
* Checks whether the keyboard modifier for text selection (Shift) is being used.
*
* @param keyboard A reference to the active keyboard device
* @return Whether the keyboard modifier for selection is active
*/
protected boolean isSelectionModifierActive(KeyboardDevice keyboard) {
return keyboard.isKeyDown(KeyId.LEFT_SHIFT) || keyboard.isKeyDown(KeyId.RIGHT_SHIFT);
}
/**
* Check whether any part of the text in the text box is currently selected.
*
* @return Whether any part of the text is currently selected
*/
protected boolean hasSelection() {
return getCursorPosition() != selectionStart;
}
/**
* Removes the selected text from the text field.
*/
protected void removeSelection() {
if (hasSelection()) {
String before = getText().substring(0, Math.min(getCursorPosition(), selectionStart));
String after = getText().substring(Math.max(getCursorPosition(), selectionStart));
setText(before + after);
setCursorPosition(Math.min(getCursorPosition(), selectionStart));
}
}
/**
* Copies the selected text to the clipboard.
*/
protected void copySelection() {
if (hasSelection()) {
String fullText = getText();
String selection = fullText.substring(Math.min(selectionStart, getCursorPosition()), Math.max(selectionStart, getCursorPosition()));
setClipboardContents(FontUnderline.strip(FontColor.stripColor(selection)));
}
}
/**
* Pastes the text currently in the clipboard.
*/
protected void paste() {
String fullText = getText();
String before = fullText.substring(0, getCursorPosition());
String after = fullText.substring(getCursorPosition());
String pasted = getClipboardContents();
setText(before + pasted + after);
increaseCursorPosition(pasted.length());
}
/**
* Get the current clipboard contents.
*
* @return The string currently in the clipboard
*/
protected String getClipboardContents() {
Transferable t = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
try {
if (t != null && t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
return (String) t.getTransferData(DataFlavor.stringFlavor);
}
} catch (UnsupportedFlavorException | IOException e) {
logger.warn("Failed to get data from clipboard", e);
}
return "";
}
/**
* Set the contents of the clipboard to a given value.
*
* @param str The new value of the clipboard contents
*/
protected void setClipboardContents(String str) {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(str), null);
}
/**
* Moves the cursor to a given position.
*
* @param pos The final position of the cursor
* @param selecting Whether the user is selecting text as he moves the cursor
* @param keyboard The keyboard device that is currently active
*/
protected void moveCursor(Vector2i pos, boolean selecting, KeyboardDevice keyboard) {
if (lastFont != null) {
pos.x += offset;
String rawText = getText();
List<String> lines = TextLineBuilder.getLines(lastFont, rawText, Integer.MAX_VALUE);
int targetLineIndex = pos.y / lastFont.getLineHeight();
int passedLines = 0;
int newCursorPos = 0;
for (int lineIndex = 0; lineIndex < lines.size() && passedLines <= targetLineIndex; lineIndex++) {
List<String> subLines;
if (multiline) {
subLines = TextLineBuilder.getLines(lastFont, lines.get(lineIndex), lastWidth);
} else {
subLines = Arrays.asList(lines.get(lineIndex));
}
if (subLines.size() + passedLines > targetLineIndex) {
for (String subLine : subLines) {
if (passedLines == targetLineIndex) {
int totalWidth = 0;
for (char c : subLine.toCharArray()) {
int charWidth = lastFont.getWidth(c);
if (totalWidth + charWidth / 2 >= pos.x) {
break;
}
newCursorPos++;
totalWidth += charWidth;
}
passedLines++;
break;
} else {
newCursorPos += subLine.length();
passedLines++;
}
}
} else {
passedLines += subLines.size();
newCursorPos += lines.get(lineIndex).length() + 1;
}
}
setCursorPosition(Math.min(newCursorPos, rawText.length()), !isSelectionModifierActive(keyboard) && !selecting);
updateOffset();
}
}
/**
* Change the binding associated with the text in the widget.
*
* @param binding The new binding to associate with the text in the widget
*/
public void bindText(Binding<String> binding) {
text = binding;
}
/**
* Get the text contained by the text box.
*
* @return The text contained by the text box
*/
public String getText() {
return text.get();
}
/**
* Set the text in the text box to a given value.
*
* @param val The new value of the text in the widget
*/
public void setText(String val) {
String prevText = getText();
boolean callEvent = !prevText.equals(val);
text.set(val != null ? val : "");
correctCursor();
if (callEvent) {
for (TextChangeEventListener listener : textChangeListeners) {
listener.onTextChange(prevText, val);
}
}
}
/**
* Get the mode (default or disabled) of the text box.
*
* @return The String ID associated with the mode in which the text box currently is
*/
@Override
public String getMode() {
if (!isEnabled()) {
return DISABLED_MODE;
}
return DEFAULT_MODE;
}
/**
* @return Whether the text in the text box is multiline
*/
public boolean isMultiline() {
return multiline;
}
/**
* @param multiline Whether the text in the text box should be multiline
*/
public void setMultiline(boolean multiline) {
this.multiline = multiline;
}
/**
* @return Whether the text box is read-only
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* @param readOnly Whether the text box should be read-only
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Add a new activate event listener to the widget.
*
* @param listener The activate event listener to add to the widget.
*/
public void subscribe(ActivateEventListener listener) {
Preconditions.checkNotNull(listener);
activationListeners.add(listener);
}
/**
* Remove a new activate event listener from the widget.
*
* @param listener The activate event listener to remove from the widget.
*/
public void unsubscribe(ActivateEventListener listener) {
Preconditions.checkNotNull(listener);
activationListeners.remove(listener);
}
/**
* Add a new cursor update event listener to the widget.
*
* @param listener The cursor update event listener to add to the widget.
*/
public void subscribe(CursorUpdateEventListener listener) {
Preconditions.checkNotNull(listener);
cursorUpdateListeners.add(listener);
}
/**
* Remove a new cursor update event listener from the widget.
*
* @param listener The cursor update event listener to remove from the widget.
*/
public void unsubscribe(CursorUpdateEventListener listener) {
Preconditions.checkNotNull(listener);
cursorUpdateListeners.remove(listener);
}
/**
* Add a new text change event listener to the widget.
*
* @param listener The text change event listener to add to the widget.
*/
public void subscribe(TextChangeEventListener listener) {
Preconditions.checkNotNull(listener);
textChangeListeners.add(listener);
}
/**
* Remove a new text change event listener from the widget.
*
* @param listener The text change event listener to remove from the widget.
*/
public void unsubscribe(TextChangeEventListener listener) {
Preconditions.checkNotNull(listener);
textChangeListeners.remove(listener);
}
/**
* Defines what to do at every engine update. Specifically, this updates the text and makes the cursor blink.
*
* @param delta
*/
@Override
public void update(float delta) {
super.update(delta);
blinkCounter += delta;
while (blinkCounter > 2 * BLINK_RATE) {
blinkCounter -= 2 * BLINK_RATE;
}
}
/**
* Increase the cursor position.
*
* @param delta The amount by which the cursor position is to be increased
* @param moveSelectionStart Whether the start of the selected text should be moved with the cursor
* @return The new position of the cursor
*/
public int increaseCursorPosition(int delta, boolean moveSelectionStart) {
int newPosition = getCursorPosition() + delta;
setCursorPosition(newPosition, moveSelectionStart);
return newPosition;
}
/**
* Increase the cursor position. This method moves the start of the selected text along with the cursor.
*
* @param delta The amount by which the cursor position is to be increased
* @return The new position of the cursor
*/
public int increaseCursorPosition(int delta) {
return increaseCursorPosition(delta, true);
}
/**
* Decrease the cursor position.
*
* @param delta The amount by which the cursor position is to be decreased
* @param moveSelectionStart Whether the start of the selected text should be moved with the cursor
* @return The new position of the cursor
*/
public int decreaseCursorPosition(int delta, boolean moveSelectionStart) {
return increaseCursorPosition(-delta, moveSelectionStart);
}
/**
* Decrease the cursor position. This method moves the start of the selected text along with the cursor.
*
* @param delta The amount by which the cursor position is to be decreased
* @return The new position of the cursor
*/
public int decreaseCursorPosition(int delta) {
return decreaseCursorPosition(delta, true);
}
/**
* @return The current cursor position
*/
public int getCursorPosition() {
return cursorPosition;
}
/**
* @param position The new cursor position
*/
public void setCursorPosition(int position) {
setCursorPosition(position, true, true);
}
/**
* @param position The new cursor position
* @param moveSelectionStart Whether the start of the selected text should be moved with the cursor
* @param callEvent Whether this action should be reported as an event
*/
public void setCursorPosition(int position, boolean moveSelectionStart, boolean callEvent) {
int previousPosition = cursorPosition;
cursorPosition = position;
if (moveSelectionStart) {
selectionStart = position;
}
correctCursor();
if (callEvent) {
for (CursorUpdateEventListener listener : cursorUpdateListeners) {
listener.onCursorUpdated(previousPosition, cursorPosition);
}
}
}
/**
* @param position The new cursor position
* @param moveSelectionStart Whether the start of the selected text should be moved with the cursor
*/
public void setCursorPosition(int position, boolean moveSelectionStart) {
setCursorPosition(position, moveSelectionStart, true);
}
/**
* Make sure that the cursor position lies within 0 and the length of the text in the widget.
*/
protected void correctCursor() {
cursorPosition = TeraMath.clamp(cursorPosition, 0, getText().length());
selectionStart = TeraMath.clamp(selectionStart, 0, getText().length());
}
}