All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bigcustard.scene2dplus.textarea.TextAreaModel Maven / Gradle / Ivy

There is a newer version: 1.4.0
Show newest version
package com.bigcustard.scene2dplus.textarea;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Disposable;
import com.bigcustard.scene2dplus.XY;
import com.bigcustard.util.Watchable;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;


public class TextAreaModel implements Disposable {
	private static final String END = "$END$";
	private String text;
	private Caret caret;
	private ColorCoder colorCoder;
	private Watchable changeWatchable = new Watchable<>();
	private BiFunction preInsertVetoer;

	public TextAreaModel(String text, ColorCoder colorCoder) {
		this.text = text;
		this.colorCoder = colorCoder;
		caret = new Caret();
        caret.moveToBottom();
    }

	public TextAreaModel(ColorCoder colorCoder) {
		this("", colorCoder);
	}

	public void addChangeListener(Consumer listener) {
		changeWatchable.watch(listener);
	}

	public void preInsertVetoer(BiFunction preInsertVetoer) {
		this.preInsertVetoer = preInsertVetoer;
	}

	public Caret caret() {
		return caret;
	}

    public State state() {
        return new State();
    }

    public void setState(State state) {
        text = state.text;
        caret().setLocation(state.caretLocation);
        if (state.caretSelection != null) {
            caret().setSelection(state.caretSelection.getLeft(), state.caretSelection.getRight());
        }
        changeWatchable.broadcast(this);
    }

	public void clear() {
		setText("");
	}

    public String text() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
		changeWatchable.broadcast(this);
	}

	public String coloredText() {
		return colorCoder.encode(text);
	}

    public Map getColoredLines() {
        return colorCoder.colorLines(text);
    }

	public String insert(String characters) {
		if (preInsertVetoer != null) characters = preInsertVetoer.apply(characters, this);

		int indexfOfEnd = characters.contains(END) ? characters.indexOf(END) : characters.length();
		characters = characters.replace(END, "");

		int fromIndex, toIndex;
        if (caret().isAreaSelected()) {
            fromIndex = getIndex(caret().selection().getLeft());
            toIndex = getIndex(caret().selection().getRight());
            caret().clearSelection();
        } else {
            fromIndex = getIndex(caret.location());
            toIndex = fromIndex;
        }
        String deleted = text.substring(fromIndex, toIndex);
        setText(text.substring(0, fromIndex) + characters + text.substring(toIndex, text.length()));
        positionCaret(fromIndex + indexfOfEnd);
        return deleted;
	}

	public String deleteCharacter() {
        int fromIndex, toIndex;
        if (caret().isAreaSelected()) {
            fromIndex = getIndex(caret().selection().getLeft());
            toIndex = getIndex(caret().selection().getRight());
            positionCaret(fromIndex);
        } else {
            toIndex = getIndex(caret.location());
            fromIndex = Math.max(0, toIndex - 1);
            positionCaret(fromIndex);
        }
        String deleted = text.substring(fromIndex, toIndex);
        setText(text.substring(0, fromIndex) + text.substring(toIndex, text.length()));
        return deleted;
	}

    public String getSelection() {
        if (caret().isAreaSelected()) {
            int fromIndex = getIndex(caret().selection().getLeft());
            int toIndex = getIndex(caret().selection().getRight());
            return text.substring(fromIndex, toIndex);
        }
        return null;
    }

    public String getCurrentLine() {
        XY location = caret().location();
        int fromIndex = getIndex(new XY(0, location.y));
        int toIndex = fromIndex + currentLineLength();
        return text.substring(fromIndex, toIndex);
    }

	private int currentLineLength() {
		int startRowIndex = getIndexForRow(caret.y());
		int endOfRowIndex = text.indexOf('\n', startRowIndex);
		if (endOfRowIndex == -1) {
			endOfRowIndex = text.length();
		}
		return endOfRowIndex - startRowIndex;
	}

    private void positionCaret(int textIndex) {
		int row = 0;
		int index = 0;
		while (true) {
			int newlineIndex = text.indexOf('\n', index);
			if (newlineIndex == -1 || newlineIndex >= textIndex) {
				break;
			}
			row++;
			index = newlineIndex + 1;
		}
		caret.setLocation(textIndex - index, row);
	}

	private int getIndexForRow(int row) {
		int index = 0;
		for (int y = 0; y < row; y++) {
			index = text.indexOf('\n', index);
			if (index == -1) {
				text = text.concat("\n");
				index = text.length() - 1;
			}
			index++;
		}
		return index;
	}

    private int getIndex(XY location) {
        int index = getIndexForRow(location.y);
        index += location.x;
        return Math.min(index, text.length());
    }

	private int numberOfRows() {
		return StringUtils.countMatches(text, "\n");
	}

	@Override
	public void dispose() {
		changeWatchable.dispose();
	}

	public class Caret {
		private XY location;
        private Pair selection;
		
		private Caret() {
			this(0, 0);
		}

        private Caret(int x, int y) {
			this.location = new XY(x, y);
		}

		public XY location() {
			return location;
		}

        public Pair selection() {
            return selection;
        }

        public void setLocation(XY caretLocation) {
			this.location = caretLocation;
            changeXIfBeyondEndOfLine();
            clearSelection();
		}

        private void setLocation(int x, int y) {
			setLocation(new XY(x, y));
		}

        public void clearSelection() {
            selection = null;
            changeWatchable.broadcast(TextAreaModel.this);
        }

        public void setSelection(XY start, XY end) {
            setLocation(end);
            if (start.y < end.y || (Objects.equals(start.y, end.y) && start.x < end.x)) {
                selection = Pair.of(start, end);
            } else {
                selection = Pair.of(end, start);
			}
			changeWatchable.broadcast(TextAreaModel.this);
        }

		private int x() {
			return location.x;
		}

		private void setX(int x) {
			setLocation(x, location.y);
		}

		private int y() {
			return location.y;
		}

		private void setY(int y) {
            setLocation(location.x, y);
			changeXIfBeyondEndOfLine();
		}

		public void moveLeft() {
			if (x() != 0) {
				setX(x() - 1);
			}
		}

		public void moveRight() {
            moveRight(1);
		}

		public void moveRight(int n) {
            setX(x() + n);
			if (x() > currentLineLength()) {
                setX(currentLineLength());
			}
		}

		public void moveUp() {
			if (y() != 0) {
				setY(y() - 1);
			}
		}
		
		public void moveDown() {
			setY(y() + 1);
		}

		public void moveToBottom() {
			setY(numberOfRows());
		}

		public boolean isAreaSelected() {
			return selection != null;
		}

		private void changeXIfBeyondEndOfLine() {
			int lineLength = currentLineLength();
			if (x() > lineLength) {
				setX(lineLength);
			}
		}

		@Override
		public String toString() {
			return "Caret " + location;
		}
    }

    public class State {
        private String text;
        private XY caretLocation;
        private Pair caretSelection;

        private State() {
            this.text = text();
            this.caretLocation = caret().location();
            this.caretSelection = caret().selection();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy