I already look this option but for my goal your code is better.
Interesting project.
Processing’s text display primitives are not designed with text editing in mind.
If you want to mix text editor features with Processing features (drawing / multimedia etc.) then you can incorporate Java swing / awt in order to include non-processing text edit and display components. Here is a good previous discussion with @quark that includes practical examples.
If you just want to write a text editor like Notepad++, and you want to use a Processing-like language, I’d recommend doing it directly in Java. There are plenty of starter “text editor in Java” projects out there that you can use as a beginning template, then customize with the features you want.
Trying to create a programming text editor inside a Processing sketch is a bit like trying to build a swimming pool inside a limousine. It is absolutely possible! …just more difficult and more limited.
I’m a little late so it looks like the problem has been solved already, but thanks for the compliment haha. I just used an array of strings with a cursor x and y location, the x being the character in the string and the y being the string in the array.
Did you work further on this?
Can you share your code please?
Sorry for the very very late reply. I was busy with school then I forgot.
I did. I found this code from 2012 and modified it a bit:
/**
* Multiline Text Box
*
* 2012 William Smith
* aka calsign
*
* Implementation requires mousePressed(), mouseDragged(),
* mouseReleased(), and keyPressed() to call their respective
* functions in the class. This class has been extracted from one of my
* ongoing projects and is in no way complete, although it features
* numerous ideas that may be of use to beginners in this area.
*
* Text box supports basic text input, multiple lines, lines returning when
* becoming too long (TODO), selection, and copy + paste.
**/
class MultilineTextBox {
String prompt;
String[] text;
int xpos;
int ypos;
boolean alreadyPressed;
boolean selecting;
int xpos2;
int ypos2;
PVector loc;
float WIDTH;
float HEIGHT;
boolean inverseBackground;
boolean hasFocus;
boolean hasInputFocus;
int lastPress = -500;
MultilineTextBox(String prompt, float x, float y, float w, float h) {
WIDTH = w;
HEIGHT = h;
this.prompt = prompt;
text = new String[1];
text[0] = "";
ypos = 0;
xpos = 0;
loc = new PVector(x, y);
}
MultilineTextBox(String prompt, float x, float y, float w, float h, boolean inverseBackground) {
WIDTH = w;
HEIGHT = h;
this.prompt = prompt;
text = new String[1];
text[0] = "";
ypos = 0;
xpos = 0;
loc = new PVector(x, y);
this.inverseBackground = inverseBackground;
}
void setXY(float x, float y) {
loc = new PVector(x, y);
}
void update() {
}
void updatePress() {
if (mousePressed) {
if (mouseX > loc.x && mouseX < loc.x + WIDTH && mouseY > loc.y && mouseY < loc.y + HEIGHT) {
hasFocus = true;
hasInputFocus = true;
testPos();
alreadyPressed = true;
}
else {
hasFocus = (text.length <= 1 && text[0].length() <= 0 ? false : true);
hasInputFocus = false;
if (!alreadyPressed) selecting = false;
}
}
}
void updateDrag() {
if (mouseX > loc.x && mouseX < loc.x + WIDTH && mouseY > loc.y && mouseY < loc.y + HEIGHT) testPos();
else if (selecting) testPos();
}
void updateRelease() {
alreadyPressed = false;
}
void updateKeys() {
if (keyPressed) {
if (key == CODED) {
if (millis() - lastPress > 0) {
if (keyCode == LEFT) {
if (xpos <= 0 && ypos > 0) {
ypos --;
xpos = text[ypos].length();
}
else xpos = constrain(xpos - 1, 0, text[ypos].length());
}
if (keyCode == RIGHT) {
if (xpos >= text[ypos].length() && ypos < text.length - 1) {
ypos ++;
xpos = 0;
}
else xpos = constrain(xpos + 1, 0, text[ypos].length());
}
if (keyCode == UP && ypos > 0) {
ypos --;
xpos = constrain(xpos, 0, text[ypos].length());
}
if (keyCode == DOWN && ypos < text.length - 1) {
ypos ++;
xpos = constrain(xpos, 0, text[ypos].length());
}
if (keyCode == KeyEvent.VK_HOME) xpos = 0;
if (keyCode == KeyEvent.VK_END) xpos = text[ypos].length();
if (!(keyCode == SHIFT)) {
ypos2 = ypos;
xpos2 = xpos;
}
lastPress = millis();
}
}
else {
switch(key) {
case ESC:
case TAB:
break;
case ENTER:
case RETURN:
newline();
break;
case BACKSPACE:
case DELETE:
if (selecting && (ypos != ypos2 || xpos != xpos2)) {
int minypos = min(ypos, ypos2);
int maxypos = max(ypos, ypos2);
int minxpos;
int maxxpos;
if (minypos == maxypos) {
minxpos = min(xpos, xpos2);
maxxpos = max(xpos, xpos2);
}
else {
minxpos = (minypos == ypos ? xpos : xpos2);
maxxpos = (maxypos == ypos ? xpos : xpos2);
}
if (minypos == maxypos) text[ypos] = text[ypos].substring(0, minxpos) + text[ypos].substring(maxxpos, text[ypos].length());
else {
String combine = text[minypos].substring(0, minxpos) + text[maxypos].substring(maxxpos, text[maxypos].length());
String[] pre = append(subset(text, 0, minypos), combine);
text = concat(pre, subset(text, maxypos + 1, text.length - (maxypos + 1)));
}
ypos = minypos;
xpos = minxpos;
ypos2 = ypos;
xpos2 = xpos;
selecting = false;
lastPress = millis();
}
else {
if (millis() - lastPress > 0 && xpos > 0) {
text[ypos] = text[ypos].substring(0, xpos - 1) + text[ypos].substring(xpos, text[ypos].length());
xpos --;
xpos2 = xpos;
lastPress = millis();
}
else if (ypos > 0 && xpos == 0) {
int over = text[ypos - 1].length();
String combine = text[ypos - 1] + text[ypos];
String[] pre = append(subset(text, 0, ypos - 1), combine);
text = concat(pre, subset(text, ypos + 1, text.length - (ypos + 1)));
ypos --;
xpos = over;
ypos2 = ypos;
xpos2 = xpos;
}
}
break;
default:
if (selecting && (ypos != ypos2 || xpos != xpos2)) {
int minypos = min(ypos, ypos2);
int maxypos = max(ypos, ypos2);
int minxpos;
int maxxpos;
if (minypos == maxypos) {
minxpos = min(xpos, xpos2);
maxxpos = max(xpos, xpos2);
}
else {
minxpos = (minypos == ypos ? xpos : xpos2);
maxxpos = (maxypos == ypos ? xpos : xpos2);
}
println(text[ypos].substring(minxpos,maxxpos));
if (millis() - lastPress > 0) {
if (minypos == maxypos) text[ypos] = text[ypos].substring(0, minxpos) + key + text[ypos].substring(maxxpos, text[ypos].length());
else {
String combine = text[minypos].substring(0, minxpos) + key + text[maxypos].substring(maxxpos, text[maxypos].length());
String[] pre = append(subset(text, 0, minypos), combine);
text = concat(pre, subset(text, maxypos + 1, text.length - (maxypos + 1)));
}
ypos = minypos;
xpos = minxpos + 1;
ypos2 = ypos;
xpos2 = xpos;
selecting = false;
lastPress = millis();
}
}
else {
if (millis() - lastPress > 0 && textWidth(text[ypos].substring(0, xpos) + key + text[ypos].substring(xpos, text[ypos].length())) < WIDTH - 8) {
text[ypos] = text[ypos].substring(0, xpos) + key + text[ypos].substring(xpos, text[ypos].length());
xpos ++;
xpos2 = xpos;
lastPress = millis();
}
}
break;
}
}
}
}
void newline() {
String after = text[ypos].substring(xpos, text[ypos].length());
text[ypos] = text[ypos].substring(0, xpos);
text = splice(text, after, ypos + 1);
ypos ++;
xpos = 0;
ypos2 = ypos;
xpos2 = xpos;
}
void testPos() {
if (alreadyPressed) {
selecting = true;
ypos2 = int(constrain((mouseY - loc.y) / 18.0, 0, text.length - 1));
for (int i = 0; i < text[ypos2].length(); i ++) {
if (mouseX - loc.x - 4 <= textWidth(text[ypos2].substring(0, i)) + textWidth(text[ypos2].charAt(i)) / 2) {
xpos2 = i;
return;
}
}
xpos2 = text[ypos].length();
}
else {
selecting = false;
ypos = int(constrain((mouseY - loc.y) / 18.0, 0, text.length - 1));
for (int i = 0; i < text[ypos].length(); i ++) {
if (mouseX - loc.x - 4 <= textWidth(text[ypos].substring(0, i)) + textWidth(text[ypos].charAt(i)) / 2) {
xpos = i;
return;
}
}
xpos = text[ypos].length();
}
}
void display() {
strokeWeight(1);
noStroke();
rectMode(CORNER);
noStroke();
fill(0, 41, 96);
rect(loc.x, loc.y, WIDTH, HEIGHT);
stroke(255);
textFont(createFont("Source Code Pro",15));
textAlign(LEFT, TOP);
if (hasFocus) fill(255);
else fill(102);
if (hasFocus) {
for (int i = 0; i < text.length; i ++)
text(text[i], loc.x + 4, loc.y + 6 + i * 18);
}
else text(prompt, loc.x + 4, loc.y + 6);
ypos = constrain(ypos, 0, text.length);
ypos2 = constrain(ypos2, 0, text.length);
xpos = constrain(xpos, 0, text[ypos].length());
xpos2 = constrain(xpos2, 0, text[ypos2].length());
if (keyPressed) line(loc.x + 5 + textWidth(text[ypos].substring(0, xpos)), loc.y + 4 + ypos * 18, (loc.x + 5 + textWidth(text[ypos].substring(0, xpos))), loc.y + 18 + ypos * 18);
if (selecting && (xpos != xpos2 || ypos != ypos2)) {
fill(162, 234, 255, 102);
noStroke();
int minypos = min(ypos, ypos2);
int maxypos = max(ypos, ypos2);
int minxpos;
int maxxpos;
if (minypos == maxypos) {
minxpos = min(xpos, xpos2);
maxxpos = max(xpos, xpos2);
}
else {
minxpos = (minypos == ypos ? xpos : xpos2);
maxxpos = (maxypos == ypos ? xpos : xpos2);
}
if (minypos == maxypos) rect(loc.x + 4 + textWidth(text[minypos].substring(0, minxpos)), loc.y + 4 + minypos * 18, textWidth(text[maxypos].substring(0, maxxpos)) - textWidth(text[maxypos].substring(0, minxpos)), 18);
else {
for (int y = minypos; y <= maxypos; y ++) {
for (int x = 0; x < text[y].length(); x ++) {
if ((y == minypos ? x >= minxpos : true) && (y == maxypos ? x < maxxpos : true)) rect(loc.x + 4 + textWidth(text[y].substring(0, x)), loc.y + 4 + y * 18, textWidth(text[y].charAt(x)), 18);
}
if (text[y].length() <= 0) rect(loc.x + 4, loc.y + 4 + y * 18, textWidth(" ") / 2, 18);
}
}
}
else if (hasInputFocus && millis() / 300 % 2 == 1) line(loc.x + 5 + textWidth(text[ypos].substring(0, xpos)), loc.y + 4 + ypos * 18, (loc.x + 5 + textWidth(text[ypos].substring(0, xpos))), loc.y + 18 + ypos * 18);
}
String getText(int i) {
if (text.length > i) return text[i];
else return "";
}
void setText(String toSet) {
String[] output = new String[0];
int last = -1;
for (int i = 0; i < toSet.length(); i ++) {
if (toSet.charAt(i) == '\n') {
output = append(output, toSet.substring(last + 1, i));
last = i;
}
}
output = append(output, toSet.substring(last + 1, toSet.length()));
text = output;
}
String consolidate() {
String toReturn = "";
for (int i = 0; i < text.length; i ++) {
toReturn += text[i];
if (i < text.length - 1) toReturn += "\n";
}
return toReturn;
}
}
On the top comment it says it included copy/paste but it doesn’t so I was working on that and exporting the text with saveStrings(). It was coming out pretty good, I did a new gui and added some typing sounds then I thought about possibly trying to add syntax highlighting but I realized if I wanted to do that I should probably try to rewrite the top text area and make it so every word is stored in an array instead of every line. Then I saw @jeremydouglass’ comment and I decided to try my luck with java. Then I found some code on stackoverflow that does syntax highlighting by using a JTextPane with a StyledDocument. I took it, modified it and used it as a base. Then I added a tab system and made a custom undecorated JFrame. I also found a class which adds line numbers to a JTextPane.
It was coming out better than processing as far as function goes but it looked way worse since swing’s default look and feel doesn’t look very good. So I did some research and found FlatLaF. A custom look and feel for swing that looks way better than metal.
My current version looks like this:
and I’m pretty happy with it.
But I will definitely return back to the processing one after completing this one since I feel like java is more limited than processing on making interfaces. I just wish there was a way to use swing components with processing.
Links:
The text area: https://forum.processing.org/two/discussion/12094/does-anyone-have-a-textbox-function-i-could-use
Line numbers for JTextPane: https://tips4java.wordpress.com/2009/05/23/text-component-line-number/
I tried to find the post for the syntax highlighting but I couldn’t find the correct one, I will put it here if I find it.
Thanks a lot for this and thanks for coming back to me!
Chrisir