So basically, my aim it to create a program like the MS Paint software, where I can draw shapes with mouse dragging and change its colors. This is a very long script of mine
It is hard to evaluate the correctness of your code without seeing the main method. Your 'main' class is set up in such a way that it appears the method mousePressed() is intended to poll the buttons and set the drawing mode based on their state.
It is impossible for me to know if you are polling the buttons correctly, however, without viewing the main method.
If you are looking to use an Object-Oriented approach, you are going to want to use the Observer pattern. In essence, your buttons will all have a reference to the 'main' object which has a buttonClicked(Button btn) method. When a button is clicked, it runs the 'main' object's buttonClicked(Button btn) method. The argument provided will be a reference to the button that is clicked so that the main can choose the appropriate mode to use. Demo code follows:
In Main class:
//Give the button a reference to the main object
button1 = new button(this);
//receive notifications from the button
public buttonClicked(Button btn) {
if(btn.equals(button1))
mode = 1;
if(...)
...
}
Having a quick at your code it looks like there is some confusion over using drawing commands, writing classes and how these interact. I would start really really basic:
For example, let's take the task of interactively drawing a rectangle using the mouse. If you store the position of the mouse when it was pressed, you can take the difference between them and the coordinates to the most recent mouse coordinates: the dimensions of the rectangle:
//an object to store current mouse coordiates
PVector mouse = new PVector();
//...and the previous mouse coordinates
PVector pmouse = new PVector();
void setup(){
size(400,400);
}
void draw(){
background(255);
//compute width,height as difference between current and previous mouse positions
float w = mouse.x - pmouse.x;
float h = mouse.y - pmouse.y;
//draw the shape according to it's mode
rect(pmouse.x,pmouse.y,w,h);
}
//set both previous and current mouse coordinates - this helps reset coordinates and finalize a shape
void mouseSet(){
pmouse.set(mouseX,mouseY);
mouse.set(mouseX,mouseY);
}
void mousePressed(){
mouseSet();
}
void mouseDragged(){//update only the current mouse position, leaving pmouse outdated
mouse.set(mouseX,mouseY);
}
void mouseReleased(){
mouseSet();
}
If you want to render a preview of the shape, and the shape itself, using layers(like in Photoshop) might be handy: one layers renders what's been drawn and another temporarily renders a preview on top.
Luckily there is something like in Processing via PGraphics.
Once you initialize a PGraphics
instance, you can draw into it using the same drawing commands you're used to. The only catch is that you need to call beginDraw() first, then endDraw()
at the end.
The other cool thing about PGraphics
is that it extends PImage
which means you can display it as one (not to mention, do image maniplations):
PGraphics canvas;
size(400,400);
//create a PGrahpics layer
canvas = createGraphics(width,height);
//intialize drawing
canvas.beginDraw();
//draw something, pretty similar you'd draw in Processing
canvas.background(255);
canvas.rect(200,200,150,100);
//finish drawing
canvas.endDraw();
//PGraphics extends PImage, hence it can drawn as one
image(canvas,0,0);
In your code the button class keeps references to rectangles and other objects. Ideally you want objects to be loosely coupled. The idea is to keep classes dealing with their own functionality, independent of the main program or other classes. Try taking copying your Button class into a new sketch and using straight away. At the moment you need to copy the other unrelated classes as well just to compile. The goal would be to write a Button class that can easily be reused in any sketch.
Going back to the main functionality, you would need:
This implies there are multiple shapes and multiple colours, but one of each used at a time. Putting the above ingredients together, you can prototype the functionality without a GUI or classes initially if it's easier. Simply use keyboard shortcuts to replace GUI buttons for this testing phase (use 1/2/3 to control colours, r for rectangle and c for circle):
PGraphics canvas;//a layer to persist shapes onto
//shape modes
int MODE_RECTANGLE = 0;
int MODE_ELLIPSE = 1;
//a reference to the currently selected shape mode
int mode = MODE_RECTANGLE;
//various colours
color c1 = color(192,0,0);
color c2 = color(225,225,0);
color c3 = color(0,0,192);
//a reference to the currently selected colour
color current = c1;
//an object to store current mouse coordiates
PVector mouse = new PVector();
//...and the previous mouse coordinates
PVector pmouse = new PVector();
void setup(){
size(400,400);
//setup ellipse mode to draw from corner like the rect()'s default setting
ellipseMode(CORNER);
strokeWeight(3);
//initialise the canvas - this allows you to draw with the same commands, but as a separate layer
canvas = createGraphics(width,height);
canvas.beginDraw();
//replicate ellipse mode and stroke weight in canvas layer as well (so what's being drawn matches preview)
canvas.ellipseMode(CORNER);
canvas.strokeWeight(3);
canvas.background(255);
canvas.endDraw();
}
void draw(){
//draw the layer first
image(canvas,0,0);
//overlay the preview on top using 50% transparency (as a visual hint it's a preview)
draw(g,127);
}
//a function that draws into a PGraphics layer (be it our canvas or Processing's)
void draw(PGraphics g,int transparency){
g.fill(current,transparency);
//compute width,height as difference between current and previous mouse positions
float w = mouse.x - pmouse.x;
float h = mouse.y - pmouse.y;
//draw the shape according to it's mode
if(mode == MODE_ELLIPSE) {
g.ellipse(pmouse.x,pmouse.y,w,h);
}
if(mode == MODE_RECTANGLE) {
g.rect(pmouse.x,pmouse.y,w,h);
}
}
//set both previous and current mouse coordinates - this helps reset coordinates and finalize a shape
void mouseSet(){
pmouse.set(mouseX,mouseY);
mouse.set(mouseX,mouseY);
}
void mousePressed(){
mouseSet();
}
void mouseDragged(){//update only the current mouse position, leaving pmouse outdated
mouse.set(mouseX,mouseY);
}
void mouseReleased(){
//commit the shape to the canvas layer
canvas.beginDraw();
draw(canvas,255);
canvas.endDraw();
//set both mouse positions
mouseSet();
}
//use keys to test: 1,2,3 = colours, r/c = shape mode
void keyPressed(){
if(key == '1') current = c1;
if(key == '2') current = c2;
if(key == '3') current = c3;
if(key == 'r') mode = MODE_RECTANGLE;
if(key == 'c') mode = MODE_ELLIPSE;
}
Remember how we used PGraphics before ? Each Processing applet has one already, named g
, so that's a quick and dirty way of using a single function to draw into multiple PGraphics instance (Processing's and our canvas
) but with different transparencies for each.
With this approach it's worth noting that rather than storing multiple Circle/Rectangle instances for each shape drawn, we simple render once into the canvas and have it (one PGraphics
) object store the pixels. The downside is once it's drawn, you can't retrieve what shape was drawn in which order and with what coordinates/dimensions, but if you don't need these details it's simpler.
In case you do need these, might be worth checking out PShape (and especially it's GROUP,RECT,ELLIPSE
options).
Now let's add buttons! Anthony's suggestion is great and the simplified buttonClicked suggestion lends itself well to Processing. Normally you'd use an Interface to define a listener for the button and have the Processing sketch implement this interface, but simply having a single function responsible for handling buttons in a a nice workaround.
Here's a basic implementation:
Button a = new Button("Button A",5,5,90,20,color(200),color(0));
Button b = new Button("Button B",5,30,90,20,color(200),color(0));
void draw(){
background(255);
//update button states based on mouse interaction
a.update(mouseX,mouseY,mousePressed);
b.update(mouseX,mouseY,mousePressed);
//render buttons on screen
a.draw();
b.draw();
}
void onButtonClicked(Button btn){
println(btn.label + " was pressed");
}
class Button{
float w,h,x,y;//width, height and position
color bg = color(200);//background colour
color fg = color(0);//foreground colour
String label;//text displayed
boolean isOver,wasPressed;//button states
int pw = 10;//padding on width
boolean outline;//draw an outline or not
Button(String label,float x,float y,float w,float h,color fg,color bg){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.label = label;
this.fg = fg;
this.bg = bg;
}
void update(int mx,int my,boolean md){
//check bounding box
isOver = ((mx >= x && mx <= (x+w))&&(my >= y && my <= (y+h)));
if(isOver && md){
//check if it was not previously pressed to call the onButtonClicked function only once (similar to debouncing)
if(!wasPressed){
onButtonClicked(this);
wasPressed = true;
}
}else wasPressed = false;
}
void draw(){
//pushStyle()/popStyle() isolates drawing styles (similar to how pushMatrix()/popMatrix() isolates coordinate transformations
pushStyle();
if(outline){
strokeWeight(3);
stroke(127);
}else{
noStroke();
}
fill(isOver ? fg : bg);//the ? : is a lazy one liner way of doing if/else: (booleanExpression ? doIfTrue : doIfFalse)
rect(x,y,w,h);
fill(isOver ? bg : fg);
text(label,x+pw,y+h*.75);
popStyle();
}
}
Now it's fairly straight forward to add this Button class to the previous code using keyboard shortcuts only:
PGraphics canvas;//a layer to persist shapes onto
//shape modes
int MODE_RECTANGLE = 0;
int MODE_ELLIPSE = 1;
//a reference to the currently selected shape mode
int mode = MODE_RECTANGLE;
//various colours
color c1 = color(192,0,0);
color c2 = color(225,225,0);
color c3 = color(0,0,192);
//a reference to the currently selected colour
color current = c1;
//an object to store current mouse coordiates
PVector mouse = new PVector();
//...and the previous mouse coordinates
PVector pmouse = new PVector();
//UI
//Button constructor: label, x,y, width, height, foreground, background
Button rectMode = new Button("\u25A0",5,5,30,20,color(200),color(0));//beying lazy/having fun with text: the \u25A0 is using the unicode for a square shape as text http://www.fileformat.info/info/unicode/char/25a0/index.htm
Button ellipseMode = new Button("\u25CF",5,30,30,20,color(200),color(0));//http://www.fileformat.info/info/unicode/char/25CF/index.htm
Button color1 = new Button("",5,55,30,20,color(255,0,0),c1);
Button color2 = new Button("",5,80,30,20,color(255,255,0),c2);
Button color3 = new Button("",5,105,30,20,color(00,0,255),c3);
Button[] gui = new Button[] {rectMode, ellipseMode, color1, color2, color3};
//reference to previous mode button and previous colour button
Button prevMode = rectMode;
Button prevColor = color1;
void setup(){
size(400,400);
//setup ellipse mode to draw from corner like the rect()'s default setting
ellipseMode(CORNER);
strokeWeight(3);
//initialise the canvas - this allows you to draw with the same commands, but as a separate layer
canvas = createGraphics(width,height);
canvas.beginDraw();
//replicate ellipse mode and stroke weight in canvas layer as well (so what's being drawn matches preview)
canvas.ellipseMode(CORNER);
canvas.strokeWeight(3);
canvas.background(255);
canvas.endDraw();
//ui outline current options
rectMode.outline = true;
color1.outline = true;
}
void draw(){
//draw the layer first
image(canvas,0,0);
//overlay the preview on top using 50% transparency (as a visual hint it's a preview)
draw(g,127);
//update and draw UI
for(int i = 0; i < gui.length; i++){
gui[i].update(mouseX,mouseY,mousePressed);
gui[i].draw();
}
}
//a function that draws into a PGraphics layer (be it our canvas or Processing's)
void draw(PGraphics g,int transparency){
g.fill(current,transparency);
//compute width,height as difference between current and previous mouse positions
float w = mouse.x - pmouse.x;
float h = mouse.y - pmouse.y;
//draw the shape according to it's mode
if(mode == MODE_ELLIPSE) {
g.ellipse(pmouse.x,pmouse.y,w,h);
}
if(mode == MODE_RECTANGLE) {
g.rect(pmouse.x,pmouse.y,w,h);
}
}
//set both previous and current mouse coordinates - this helps reset coordinates and finalize a shape
void mouseSet(){
pmouse.set(mouseX,mouseY);
mouse.set(mouseX,mouseY);
}
void mousePressed(){
mouseSet();
}
void mouseDragged(){//update only the current mouse position, leaving pmouse outdated
mouse.set(mouseX,mouseY);
}
void mouseReleased(){
//commit the shape to the canvas layer
canvas.beginDraw();
draw(canvas,255);
canvas.endDraw();
//set both mouse positions
mouseSet();
}
void onButtonClicked(Button b){
if(b == color1) current = c1;
if(b == color2) current = c2;
if(b == color3) current = c3;
if(b == rectMode) mode = MODE_RECTANGLE;
if(b == ellipseMode) mode = MODE_ELLIPSE;
if(b == color1 || b == color2 || b == color3){
b.outline = true;
if(prevColor != null) prevColor.outline = false;
prevColor = b;
}
if(b == rectMode || b == ellipseMode){
b.outline = true;
if(prevMode != null) prevMode.outline = false;
prevMode = b;
}
}
class Button{
float w,h,x,y;//width, height and position
color bg = color(200);//background colour
color fg = color(0);//foreground colour
String label;//text displayed
boolean isOver,wasPressed;//button states
int pw = 10;//padding on width
boolean outline;//draw an outline or not
Button(String label,float x,float y,float w,float h,color fg,color bg){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.label = label;
this.fg = fg;
this.bg = bg;
}
void update(int mx,int my,boolean md){
//check bounding box
isOver = ((mx >= x && mx <= (x+w))&&(my >= y && my <= (y+h)));
if(isOver && md){
//check if it was not previously pressed to call the onButtonClicked function only once (similar to debouncing)
if(!wasPressed){
onButtonClicked(this);
wasPressed = true;
}
}else wasPressed = false;
}
void draw(){
//pushStyle()/popStyle() isolates drawing styles (similar to how pushMatrix()/popMatrix() isolates coordinate transformations
pushStyle();
if(outline){
strokeWeight(3);
stroke(127);
}else{
noStroke();
}
fill(isOver ? fg : bg);//the ? : is a lazy one liner way of doing if/else: (booleanExpression ? doIfTrue : doIfFalse)
rect(x,y,w,h);
fill(isOver ? bg : fg);
text(label,x+pw,y+h*.75);
popStyle();
}
}
Note that some of code is handling the previously selected colour and shape mode buttons. This isn't actually needed, but it's nice to show the user some feedback to what shape/colour is currently selected (in the form of an outline in this case).
In terms of UI there is a lot more to explore. For example, although there are multiple buttons in terms of the functionality they allow there mainly work as two radio button groups. Once you get the hang of OOP Basics you can look into creating a common GUIElement class for example, which other UI elements like buttons/checkboxes/radio buttons/sliders can use (e.g. a draw()
function, x,y, width, height, etc.). Then each class would specialise this super class. For example Button will extend GUIElement and ToggleButton will extend Button. Perhaps an HBox or VBox would be handy to easily group elements in a horizontal or vertical group. etc. Have fun!