I would like to write code that does the following: User clicks and drags on the canvas, and when they release a square has been drawn. The code, now has a rect() function, but no rect() function existed before the sketch was run. Is this possible using p5.js?
The only way that I can think of to do this with my current knoweldge is if you have a rect function already in your code and then you poplulate the parameters with data based on the clicks and release. However, what if you want the user to be able to do more than draw just one rectangle. What if you want them to be able to draw 30 of them?
So essentially Iâm wondering if itâs possible for a user to be able to add functions to the code through their interaction with the canvas, such that thereâs no rect(a, b, c, d); when they begin the sketch, but after interacting, theyâve managed to ADD, say, 4 rect(); functions and their corresponding parameters.
Iâm not sure I understand what you want to do. Perhaps give an example of what you have in mind. You only need one rectangle function (which is the constructor in the Rect class) and then the user can use it to create as many subsequent rectangles as desired.
The following demo shows how to use a Rect class to create multiple rectangles. If you want to move the rectangles around then you would need to add an array to keep track of rectangle locations/size and mouse functions for selection/repositioning.
let r;
let button;
function setup() {
createCanvas(600, 600);
button = createButton('Add Rect');
button.position(0, 0);
button.mousePressed(addRect);
background(209);
}
function draw() {
}
function addRect() {
r = new Rect(random(400),random(400),random(200),random(200), color(random(255),random(255),random(255)));
r.display();
}
class Rect {
constructor(x,y,w,h,bkgrnd) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.bkgrnd = bkgrnd;
}
display() {
fill(this.bkgrnd);
rect(this.x, this.y, this.w, this.h);
}
}
Your question is almost on a Metalevel, since itâs about how we can write a program.
To change a program that is already running is not possible (to my knowledge) since itâs already loaded from the website into the RAM of the computer. So even when you could add code to the code / text on your website, you would need to restart the altered program. Or the program would need to restart itself. Maybe the gurus can do this.
As has been described, you can of course let the user draw rectangles and store them like in a drawing program. Here the code is not changed though.
Additionally you can of course generate code with the program (generate an entire Sketch with the new rectangles for example) and store it separately (into the console). So next time you have to run the new generated code (instead of the old code).
Finally you can write a language where the user types code, saves it runs it within your code. Like your own programming language. I did this once in processing (not p5.js).
Thank you both for your time and answers on this. You each provided something that was quite helpful.
Ultimately, what I was wanting to be able to do was to write a program that allowed me to draw something that generated the written functions for those shapes, so that I could copy the written functions and use them in a different sketch. (I was using a rectangle as an example here, but I would like to be able to draw complex shapes.)
So Chrisir, you answered this for me: itâs possible, but possibly too difficult a challenge for me at this time.
And svan, thanks for providing that example code. I used it, and tweaked it to create the sort of sketch that I was envisioning (minus the fact that it doesnât write out the function for the rectangle that I just drew). Again, doing this with a rectangle is one thing, but doing it with a shape that requires multiple functions (like beginShape(), etc.) is a whole other challenge.
Hereâs the code that I made:
let r;
let xPos;
let yPos;
let xWidth;
let yWidth;
function setup() {
createCanvas(600, 600);
background(209);
}
function draw() {
}
function addRect() {
r = new Rect(
xPos,
yPos,
xWidth,
yWidth,
color(random(255), random(255), random(255))
);
r.display();
}
class Rect {
constructor(x, y, w, h, bkgrnd) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.bkgrnd = bkgrnd;
}
display() {
fill(this.bkgrnd);
rect(this.x, this.y, this.w, this.h);
}
}
function mousePressed() {
xPos = mouseX;
yPos = mouseY;
}
function mouseReleased() {
xWidth = mouseX - xPos;
yWidth = mouseY - yPos;
addRect();
}
Here is a new version oriented on my bullet point #3.
First, internally it fills an array with rect data.
So we can start draw() with background() command and draw the entire array on screen every time.
Second, while you draw, the new changing rect is displayed (and you can draw over the old rects, but they re-appear when you make it smaller. This is the advantage of having the list in combination with background() command).
3rd, delete entire list with c (clear)
4th you can export your rects as Code with e
The code is: make a rect object and append it to the array. Instead we could also print: fill(color(.....)); rect(......, ....., ....., ....); for each rect but since we have the array, I thought we can use it.
Export is to the console and you can copy paste the console into the setup() of your Sketch.
This requires that you have the array and the class in your Sketch
Of course you could print the entire code for a new Sketch in the console like console.log("let r, r2;"); console.log("let xPos;"); ..... .... console.log("function setup() {"); etc., but I just print the rect data of the entire array.
I hope this helps.
Regards,
Chrisir
Full Sketch
let r, r2;
let xPos;
let yPos;
let xWidth;
let yWidth;
// this is useful to draw the rect in progress
let mouseDown = false;
// list of all rects
let myArray = [];
// ----------------------------------------------
function setup() {
createCanvas(600, 600);
background(209);
} // setup
function draw() {
background(209); // delete screen
// display from rect
for (let i = 0; i < myArray.length; i++) {
myArray[i].display();
}
// this is useful to draw the rect in progress
if (mouseDown) {
r2 = new Rect(xPos, yPos, mouseX - xPos, mouseY - yPos, color(213));
r2.display();
}
}
// ----------------------------------------------
// Inputs
function mousePressed() {
xPos = mouseX;
yPos = mouseY;
mouseDown = true;
}
function mouseReleased() {
xWidth = mouseX - xPos;
yWidth = mouseY - yPos;
mouseDown = false;
addRect();
}
function keyPressed() {
if (key == "e") {
// Export code to console
console.log("// Code");
for (let i = 0; i < myArray.length; i++) {
console.log("r2 = new Rect(" + myArray[i].myToString() + " );");
console.log("append(myArray, r2);");
}
} else if (key == "c") {
// DELETE list of all rects (clear)
myArray = [];
} //else if
} //func
// ----------------------------------------------------------
// Tools
function addRect() {
r = new Rect(
xPos,
yPos,
xWidth,
yWidth,
color(random(255), random(255), random(255))
);
// r.display();
append(myArray, r);
}
//===============================================================
class Rect {
constructor(x, y, w, h, bkgrnd) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.bkgrnd = color(bkgrnd);
}
display() {
fill(this.bkgrnd);
rect(this.x, this.y, this.w, this.h);
}
myToString() {
// Export data for console export.
// Exporting the color seems to work only this way.
let s1 =
this.x +
", " +
this.y +
", " +
this.w +
", " +
this.h +
", color(" +
red(this.bkgrnd) +
", " +
green(this.bkgrnd) +
", " +
blue(this.bkgrnd) +
")";
return s1;
}
} //class
//
function setup() {
r = int(random(256))
g = int(random(256))
b = int(random(256))
c = color(r, g, b)
console.log(r, g, b)
console.log(c.toString() ) // This string can be used! :)
}
You could also use the r, g and b and concatenate into a desired string.
AFAIK there is no way to modify the source code of a running Javascript application. Others have put forward suggestions but it boils down to
having a function or object to draw a particular shape
collecting user supplied data related to the shape geometry
pass the information collected in (2) to the function in (1) via its parameters.
There maybe a library that enables modification of running JS app but you will have to search for it.
In Java there are libraries that allow the creation, compilation and execution of Java classes at runtime. I used one called ASM in my library Jasmine but I found it hard to use even though it was only at the most basic levelâŚ
I think the âgenerate code and log itâ approach is a good idea.
While you can modify an HTML entity to add a js function when âonload()â or âonclick()â are triggered, it is dangerous.
If you donât want to stay on rects and want other shapes, you could create an array that logs shape type, number of parameters, and the parameters themselves. I made something similar in the nineties to create a vector-based illustration program. But I was using QBASIC, so objects were not an option.
The following source code demonstrates how to save rectangle array data to the userâs system (Downloads folder on a Mac) and subsequently retrieve the data to re-populate the sketch window rectangles. To use as intended follow these steps:
Run the code and create as many rectangles as desired by hitting âAdd Rectâ button for each.
Hit âSaveArrayâ button to write rectangle array data to a file titled ârect.txtâ in the Downloads folder.
Stop the code to close the window.
Run the code a second time to display an empty window.
Hit the âChoose Fileâ button and select the appropriate rect.txt file to repopulate the window. Be aware that a new rect.txt file is saved with sequential numbering for each run (i.e., rect(x).txt); the original file is not over-written. Pay attention to the name of the file when it is saved and open the same file in order to see your last array.
I would like to have saved the file to a sketch folder inside the p5.js web editor, but apparently this is not possible. The source code has not been tested extensively so there could be bugs, but should work as intended as long as the above steps are followed.
How it works:
A rectangle array holds the data for each added rectangle according to a Rect class: x,y location coordinates, w,h size values, plus the r,g,b values for the fill color (7 values/rectangle).
createWriter() is then used to write these comma separated values to a file using a separate line for each rectangle (each line is automatically line feed terminated, i.e., followed by â\nâ)
To retrieve the data createFileInput() locates the file with a dialog screen and opens it. The rectangle array is reconstructed by two split() calls. The first split() breaks the file at the line feeds to produce an element for each rectangle. The second split() breaks each rectangle element at its commas to reproduce the rectangle class values which are then used to re-construct and display the rectangles.
let r = [];
let rOut = [];
let saveBtn;
let addRectBtn;
let counter;
let input;
let rectArray = [];
function handleFile(file) {
print(file);
if (file.type === "text") {
rectArray = split(file.data, "\n");
print(rectArray.length); // one more than no. rects (counts last line feed)
counter = rect.length - 1;
for (let i = 0; i < rectArray.length - 1; i++) {
rOut = split(rectArray[i], ",");
print(rOut);
r[i] = new Rect(
rOut[0],
rOut[1],
rOut[2],
rOut[3],
color(rOut[4], rOut[5], rOut[6])
);
r[i].display();
}
}
}
function setup() {
createCanvas(600, 600);
addRectBtn = createButton("Add Rect");
addRectBtn.position(0, 0);
addRectBtn.mousePressed(addRect);
saveBtn = createButton("Save Array");
saveBtn.position(100, 0);
saveBtn.mousePressed(saveArray);
input = createFileInput(handleFile);
input.position(220, 0);
background(220);
counter = -1;
}
function draw() {}
function saveArray() {
print("rectArray length =", r.length);
print(r);
writer = createWriter("rect.txt");
for (let i = 0; i < r.length; i++) {
writer.print(
r[i].x +
"," +
r[i].y +
"," +
r[i].w +
"," +
r[i].h +
"," +
red(r[i].bkgrnd) +
"," +
green(r[i].bkgrnd) +
"," +
blue(r[i].bkgrnd)
);
}
writer.close();
writer.clear();
}
function addRect() {
counter++;
r[counter] = new Rect(
random(400),
random(400),
random(200),
random(200),
color(random(255), random(255), random(255))
);
r[counter].display();
}
class Rect {
constructor(x, y, w, h, bkgrnd) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.bkgrnd = color(bkgrnd);
}
display() {
fill(this.bkgrnd);
rect(this.x, this.y, this.w, this.h);
}
}