Search code examples
javascriptp5.js

Changing the fill/unfill state of an ellipse based on a click of a button while keeping track if the user selects another color


UPDATE: Nevermind, I found the bug that forces the double click. It was coming from inside the populateOptions function, because the button axis was already set to "fill" I would need to click twice on the button to change back to "nofill" so what I did was I am emptying the self.axis of the button now, every time I change the color so it works flawlessly. It might not be the best approach but it works. If you have any ideas for a better approach, let me know.

My approach, based on this global variable that turns true when I click another color inside the colorPalette, I've put this inside populateOptions so when it gets called from colorPalette, this runs first and clears the state of the button:

if (changedColor) {
     self.axis = "";
     changedColor = false;
} 

I'm working on a drawing app project for University, I am only allowed to use vanilla Javascript with p5.js. I have different constructors in separate files, for example the colourPalette where the user selects a color (and the fill of that color is declared inside this constructor), another circleTool constructor where I'm creating an ellipse and when this tool is selected, a button appears that can toggle between filled/unfilled.

Now my issue is that the toggle button works at first with no issues but when I'm selecting a new color while the button is "not filled", the fill inside colourPalette is enabled again but my button does not update so I came up with the idea of calling the populateOptions() function from the circleTool every time I change the color because this will recreate the button in the "filled" state. But the issue with this is that when I change color, I now need to click on the button twice until unfilled takes effect, see the below code:

colourPalette constructor: This is where I'm calling the populateOptions() function every time I select a new color so that my button resets every time with the fill state

//Displays and handles the colour palette.
function ColourPalette() {
    //a list of web colour strings
    this.colours = ["black", "silver", "gray", "white", "maroon", "red", "purple",
        "orange", "pink", "fuchsia", "green", "lime", "olive", "yellow", "navy",
        "blue", "teal", "aqua"
    ];
    //make the start colour be black
    this.selectedColour = "black";

    var self = this;

    var colourClick = function() {
        //remove the old border
        var current = select("#" + self.selectedColour + "Swatch");
        current.style("border", "0");

        //get the new colour from the id of the clicked element
        var c = this.id().split("Swatch")[0];

        //set the selected colour and fill and stroke
        self.selectedColour = c;
        fill(c);
        stroke(c);
        circleTool.unselectTool();
        circleTool.populateOptions();
        //add a new border to the selected colour
        this.style("border", "2px solid blue");
    }

    //load in the colours
    this.loadColours = function() {
        //set the fill and stroke properties to be black at the start of the programme
        //running
        fill(this.colours[0]);
        stroke(this.colours[0]);

        //for each colour create a new div in the html for the colourSwatches
        for (var i = 0; i < this.colours.length; i++) {
            var colourID = this.colours[i] + "Swatch";

            //using JQuery add the swatch to the palette and set its background colour
            //to be the colour value.
            var colourSwatch = createDiv()
            colourSwatch.class('colourSwatches');
            colourSwatch.id(colourID);

            select(".colourPalette").child(colourSwatch);
            select("#" + colourID).style("background-color", this.colours[i]);
            colourSwatch.mouseClicked(colourClick)
        }

        select(".colourSwatches").style("border", "2px solid blue");
    };
    //call the loadColours function now it is declared
    this.loadColours();
}

circleTool constructor: This is where the populateOptions function is, where the button is controlled.

//Tool to draw circles
function CircleTool() {
    this.name = "circleTool";
    this.icon = "/assets/circle.jpg";

    var startMouseX = -1;
    var startMouseY = -1;
    var drawing = false;
    this.draw = function() {
        //Function to draw the circle
        if(mouseIsPressed) {
            if(startMouseX == -1) {
                drawing = true;
                startMouseX = mouseX;
                startMouseY = mouseY;
                loadPixels();
            }    
            else {
                updatePixels();
                ellipse(startMouseX,startMouseY,dist(startMouseX,startMouseY,mouseX,mouseY));
            }        
        }
        else if(drawing) {
            drawing = false;
            startMouseX = -1;
            startMouseY = -1;
        }
    }
    //This will clear the button from the canvas when circleTool is unselected
    this.unselectTool = function() {
        updatePixels();
        //clear options
        select(".options").html("");
    };
    //adds a button and click handler to the options area. When clicked
    //toggle the fill of the circle
    this.populateOptions = function() {
        select(".options").html(
            "<button id='circleButton'>Filled Circle</button>");
        //  //click handler
        select("#circleButton").mouseClicked(function() {
            var button = select("#" + this.elt.id);            
            if (self.axis == "fill") {
                self.axis = "notFill";
                
                button.html('Filled Circle');   
                fill(colourP.selectedColour);             
            }
            else {                
                self.axis = "fill";
                self.lineOfSymmetry = width / 2;
                noFill();
                button.html('Not Filled');
            }
            
        });
    };
}

In order to eliminate the problem of the need to click the button twice so unfilled takes effect, I tried to control all this filled/unfilled with a global variable but it did not work because even if I controled the state of the button with a boolean, the button will not update its state unless I would call the populateOptions function again.

I just want this button to control and toggle the fill state of my circle and I'm not sure how to approach this, seems an easy problem but can't figure it out.


Solution

  • Perhaps you can get some ideas for completing your project by looking at the following example. The demo will create an array of circles base on a Circle class and then allow the user to change the color of each circle by using a colorPicker. Drawing the circles is performed by dragging the mouse with the 'shift' key down to create a bounding square with the circle centered inside. Subsequent hit testing is by checking mouse coordinates to see if they fall within the bounding square (displayed for demonstration purposes). Other techniques could be used to see if the mouse point falls within the circle, eg Pythagorean theorem. To use the demo use the following procedure:

    1. First click on the window to activate it.
    2. Press the 'shift' key then click on the window location and drag the mouse to create a square. When the mouse button is released the circle is added to an array and displayed.
    3. Go to the colorPicker and select the desired color.
    4. Click on the circle that you want to select.
    5. Click on the button beside the colorPicker to make the change.

    Second example shows how to construct a toggle button.

    let crc = [];
    let shiftDown = false;
    let x, y, x1, y1;
    let w, h;
    let selectedCrc;
    let myPicker;
    
    class Circle {
      constructor(xpos, ypos, diam, fillColor) {
        this.x = xpos;
        this.y = ypos;
        this.d = diam;
        this.bkgrnd = fillColor;
        this.selected = false;
      }
    
      display() {
        for (let i = 0; i < crc.length; i++) {
          fill(crc[i].bkgrnd);
          circle(crc[i].x, crc[i].y, crc[i].d);
        }
      }
    }
    
    function setup() {
      createCanvas(800, 800);
      background(209);
      myPicker = createColorPicker("deeppink");
      myPicker.position(width - 100, 10);
      let button = createButton("change color of selected circle");
      button.position(width - 320, 15);
      button.mousePressed(() => {
        if (crc[selectedCrc].selected) {
          crc[selectedCrc].bkgrnd = myPicker.color();
          fill(crc[selectedCrc].bkgrnd);
          circle(crc[selectedCrc].x, crc[selectedCrc].y, crc[selectedCrc].d);
        }
      });
    }
    
    function draw() {
      for (let i = 0; i < crc.length; i++) {
        crc[i].display();
      }
    }
    
    function mousePressed() {
      if (shiftDown) {
        x = mouseX;
        y = mouseY;
      }
      print("x:" + mouseX + ",y:" + mouseY);
      for (let i = 0; i < crc.length; i++) {   
        // Reconstruct bounding rectangle from circle coordinates and diameter
        if (
          mouseX >= crc[i].x - crc[i].d / 2 &&
          mouseX <= crc[i].x + crc[i].d / 2 &&
          mouseY >= crc[i].y - crc[i].d / 2 &&
          mouseY <= crc[i].y + crc[i].d / 2
        ) {
          print("isInCircleRect =", i);
          crc[i].selected = true;
          selectedCrc = i;
          print("selectedCircle id =", selectedCrc);
        } else {
          crc[i].selected = false;
          print("unselectedCircle =", i);
        }
      }
    }
    
    function mouseDragged() {
      if (shiftDown) {
        x1 = mouseX;
        y1 = mouseY;
    
        w = x1 - x;
        h = y1 - y;
      }
    }
    
    function mouseReleased() {
      if (shiftDown) {
        x1 = mouseX;
        y1 = mouseY;
        diam = x1 - x;
        w = x1 - x;
        h = y1 - y;
        // center circle in bounding rectangle
        crc.push(new Circle(x + w / 2, y + w / 2, diam, color(255)));
        // display bounding rectangle
        noFill();
        rect(x, y, w, w);
      }
      diam = 0; // reset to zero
      print(crc);
    }
    
    function keyPressed() {
      if (keyCode == SHIFT) {
        shiftDown = true;
      }
      print("shiftDown = ", shiftDown);
    }
    
    function keyReleased() {
      if (keyCode == SHIFT) {
        shiftDown = false;
      }
      print("shiftDown = ", shiftDown);
    }
    
    

    Toggle Button Demo

    var btnState = false;
    let colorPicker;
    
    function setup() {
      createCanvas(400, 400);
      background(209);
      fill(0);
      circle(200, 200, 100);
      myPicker = createColorPicker('deeppink');
      myPicker.position(110, 10);
      let button = createButton("fill/unfill");
      button.position(20, 10);
      // Use button to change circle's color.
      button.mousePressed(() => {
        if (!btnState) {
          btnState = true;
          fill(255);
          circle(200, 200, 100);
        } else {
          btnState = false;
          fill(myPicker.value());
          circle(200, 200, 100);
        }
      });
    }
    
    function draw() {
    }