Search code examples
javaooparraylistprocessing

How to create a new object(rectangle) everytime when the button is clicked?


I want to create a shape Object(in my case the object is a rectangle). Each time I click a button. Currently, I'm able to make it appear just once. The idea would be that each time I click the button, a new rectangle Object is created, additional to the old one. Therefore, if I click the button 5 times, I should have 5 rectangles.

I tried to do it with an ArrayList, but still, there is just one rectangle appearing. Does someone know how to do it!

Thank you very much in advance!

This is the main class, FYI there is also a rectangle Class(not attached)

    import controlP5.*;
    ControlP5 cp5;
    
    Rectangle rect; // rect begins as null
    Button rc;
    
    ArrayList<Rectangle> rectList;
    
    void setup(){
    
    size(1000, 1000);
    rectList = new ArrayList<Rectangle>();
     
    cp5 = new ControlP5(this);
    
    rc = cp5.addButton("Rectangle").
             setPosition(5, 4).
             setColorBackground(color(52, 55, 76));
             
    rc.onRelease(new CallbackListener() {    
        public void controlEvent(CallbackEvent theEvent) {
          // only create the rectangle when the button is clicked
          rect = new Rectangle(100, 100, 100, 100);
        }
    });
    
    }
    
    void draw(){
    background(255);
    
    // if the rect exists, draw it on the screen
      if(rect != null) {
         rect.displayRect();
         showRect();
      } 
      
      for(int i = 0; i < rectList.size(); i++){
        //((Rectangle)rectList.get(i)).update();
        ((Rectangle)rectList.get(i)).displayRect();
      }  
    }
    
    public void showRect(){
      for(Rectangle r: rectList){
      r.displayRect();
      rect(r.getXvalue(), r.getYvalue(), r.getWvalue(), r.getHvalue());
    }
    
    }

Solution

  • You should post your Rectangle class to make it easier for others to test and help out.

    As Basil pointed out (+1) you're only rendering a new rectangle for one frame when there's a click event.

    The idea would be that each time I click the button, a new rectangle Object is created, additional to the old one. Therefore, if I click the button 5 times, I should have 5 rectangles.

    This statement is a bit ambiguous. I get you'd like to render a rectangle per click, however in the click handler the rect has the exact same dimensions and coordinates. Even if you would make minor fixes, rendering 5 identical rectangles on top of each other will likely appear as if it's a single rectangle.

    Regarding the code you posted, this stands out to me:

    • Rectangle rect; // rect begins as null: what is the purpose of the rect if you have this underneath: ArrayList<Rectangle> rectList; ?
    • showRect(); is called in draw(): it loops over rectList and not only calls displayRect() which I'd assume would render the current rect, but also re-renders the same data on the following line (rect(r.getXvalue(), r.getYvalue(), r.getWvalue(), r.getHvalue());)
    • underneath, there's a for loop over the same list calling displayRect() yet again. My guess is 2 out 3 calls to render rectangles are redundant. (Also the array list is typed therefore, no need to cast like this: (Rectangle)rectList.get(i)), rectList.get(i) should suffice)

    The only other minor caveat I have is around naming: ideally you would want to stick to Java naming conventions in Processing. (For example getXValue() instead of getXvalue(), etc.)

    Regarding the ControlP5 button you could use controlEvent() which is a bit simpler than setting a callback. Even simpler is to use this automatic variable plugging functionality. In short, if a function has the same name as a button's name it will be called automatically:

    Automatic controller-event detection ControlP5 offers a range of controllers that allow you to easily change and adjust values while your sketch is running. Each controller is identified by a unique name assigned when creating a controller. ControlP5 locates variables and functions inside your sketch and will link controllers to matching variables or functions automatically

    (from controlP5 reference)

    Here's a basic example that prints a message to console each time the button is clicked:

    import controlP5.*;
    ControlP5 cp5;
    
    void setup() {
    
      size(1000, 1000);
      
      cp5 = new ControlP5(this);
      
      cp5.addButton("rectangle").
        setPosition(5, 4).
        setColorBackground(color(52, 55, 76));
    }
    
    void draw(){
      background(255);
    }
    
    void rectangle(){
      println("rectangle button clicked");
    }
    

    (I've kept the name rectangle instead of Rectangle to keep in line with Java naming conventions. The text label is displayed in uppercase anyway)

    Back to your main question, if you want to add new rectangle per button press and render them the code be as simple as:

    import controlP5.*;
    ControlP5 cp5;
    
    ArrayList<Rectangle> rectList = new ArrayList<Rectangle>();
    
    int x = 100;
    int y = 100;
    
    void setup() {
    
      size(1000, 1000);
      rectList = new ArrayList<Rectangle>();
    
      cp5 = new ControlP5(this);
    
      cp5.addButton("rectangle").
      setPosition(5, 4).
      setColorBackground(color(52, 55, 76));
    
    }
    
    void draw() {
      background(255);
    
      for (int i = 0; i < rectList.size(); i++) {
        rectList.get(i).display();
      }
    }
    
    void rectangle(){
      rectList.add(new Rectangle(x, y, 100, 100));
      // increment x, y to avoid superimposed rectangles
      x += 50;
      y += 50;
    }
    
    class Rectangle{
      
      private int x, y, w, h;
      
      Rectangle(int x, int y, int w, int h){
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
      }
      
      void display(){
        rect(x, y, w, h);
      }
      // currently not used
      public int getX(){
        return x;
      }
      
      public int getY(){
        return x;
      }
      
      public int getWidth(){
        return x;
      }
      
      public int getHeight(){
        return x;
      }
      
    }
    

    Rectangle rect; stands out. If this was an OOP homework assignment or exercise perhaps the intention was to have a basic drawing app functionality where the user could have a rectangle drawing tool ?

    If that's the case rect could be selection rectangle which could cloned into rectList so it persists.

    You could implement rectangle selection like so:

    1. when the mouse is pressed remember the coordinates: these are the starting point of the selection
    2. as the mouse is dragged the end point coordinates are the current mouse coordinates, hence the selection rectangle dimensions are the difference between the current mouse coordinates and the previously stored mouse coordinates
    3. as the mouse is released, reset the selection rectangle (so it no longer displays)

    Here's a basic example sketch:

    Rectangle selection = new Rectangle(0, 0, 0, 0);
    
    void setup(){
      size(1000, 1000);
    }
    
    void draw(){
      background(255);
      selection.display();
    }
    
    void mousePressed(){
      // store selection start
      selection.x = mouseX;
      selection.y = mouseY;
    }
    
    void mouseDragged(){
      // update selection dimension as the difference between the current mouse coordinates and the previous ones (selection x, y)
      selection.w = mouseX - selection.x;
      selection.h = mouseY - selection.y;
    }
    
    void mouseReleased(){
      selection.w = selection.h = selection.x = selection.y = 0;
    }
    
    class Rectangle{
      
      private int x, y, w, h;
      
      Rectangle(int x, int y, int w, int h){
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
      }
      
      void display(){
        rect(x, y, w, h);
      }
      
    }
    

    Resetting the Rectangle properties to 0 could be nicely encapsulated into a method:

    void reset(){
        x = y = w = h = 0;
      }
    

    The release handler would also be useful to add a new rectangle to rectList which has the same properties (x, y, w, h) as the selection, but before the selection is reset. something like:

    void mouseReleased(){
      // add a copy of the selection to rectList
      rectList.add(new Rectangle(selection.x, selection.y, selection.w, selection.h));
      // reset selection
      selection.reset();
    }
    

    Again, the copy functionality is something that could be nicely encapsulated as a method as well:

    Rectangle copy(){
        return new Rectangle(x, y, w, h);
      }
    

    Putting it all together would look like this:

    import controlP5.*;
    ControlP5 cp5;
    
    ArrayList<Rectangle> rectList = new ArrayList<Rectangle>();
    
    Rectangle selection = new Rectangle(0, 0, 0, 0);
    
    void setup() {
    
      size(1000, 1000);
      rectList = new ArrayList<Rectangle>();
    
      cp5 = new ControlP5(this);
    
      cp5.addButton("rectangle").
      setPosition(5, 4).
      setColorBackground(color(52, 55, 76));
    
    }
    
    void draw() {
      background(255);
      // draw previous rectangles (black)
      stroke(0);
      for (int i = 0; i < rectList.size(); i++) {
        rectList.get(i).display();
      }
      // draw current selection (green)
      stroke(0, 192, 0);
      selection.display();
    }
    
    void rectangle(){
      println("selected drawing tool is rectangle");
    }
    
    void mousePressed(){
      // store selection start
      selection.x = mouseX;
      selection.y = mouseY;
    }
    
    void mouseDragged(){
      // update selection dimension as the difference between the current mouse coordinates and the previous ones (selection x, y)
      selection.w = mouseX - selection.x;
      selection.h = mouseY - selection.y;
    }
    
    void mouseReleased(){
      // add a new rectangle to the list: a copy of the selection
      rectList.add(selection.copy());
      // reset selection
      selection.reset();
    }
    
    class Rectangle{
      
      private int x, y, w, h;
      
      Rectangle(int x, int y, int w, int h){
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
      }
      
      void display(){
        rect(x, y, w, h);
      }
      
      Rectangle copy(){
        return new Rectangle(x, y, w, h);
      }
      
      void reset(){
        x = y = w = h = 0;
      }
    
    }
    

    At this stage the button is a bit redundant, however it could useful in the future if other shapes are required (e.g. an Ellipse would be an obviously simple one to implement since ellipse() has the same parameters as rect(), just need to ensure ellipseMode(CORNER) is set to use the same selection x,y coordinates the rectangle does)

    Hopefully this is useful. The initial code, as mentioned before looks a bit messy, as if it's put together in a haste before a deadline. (I assume this because it reminds me of my code as a student :)) I'd recommend taking a short walk away from the computer, remembering what the task is and pen on paper breaking the task into small, clear, easy to implement subtasks. Once that is as clear as it can be, implement one subtask at a time in isolation. Initially the code may break or get messy, but eventually it will work (and it will be easier to write compared to the whole drawing program). Once that works, cleanup the code so that all unnecessary code is removed and it's easy to move the code to another sketch and run it without errors. Repeat the process for each subtask which should result in multiple minimal sketches solving one single problem. Once all parts are solved in isolation you can start putting the code together into one main sketch, however I recommend adding one subtask code as a time and testing first. When mixing code from multiple sketches conflicts/errors may arise and it will far easier to tackle merging two sketches a time than all of the subtask sketches in one go. Best of luck !