Dragging a constrained object, and problems with conditional fill color

Hello. I have a sketch that draws a grid, and places a rectangular cursor which is constrained to the grid area, and snaps to the center of the nearest grid cell when the mouse is released. The center “Tile” for a grid cell will turn red when the center of the cursor object is within that cell.

There are a couple of things I’m having trouble understanding, and any suggestions would be appreciated.

EDIT: I found a solution to the first thing, I no longer need help with that.


One thing:
How can I prevent the “cursor” object from getting “stuck” if while it is being dragged, the mouse leaves the constrained area?
I would like to have it continue to move within the grid even if the mouse leaves the constrained area, until the mouse is released, and then let it snap to the cell center that it’s closest to.


Another thing:
When the “cursor” is moved to the bottom-right grid cell, it turns red, which I don’t understand.
If a fill color for the cursor (line 49) is applied, this effect can be disguised, but I am trying to understand how this red fill color is being applied to anything other than a very specific “Tile” object. It seems like there is some hole in the logic that I can’t find.
I have tried lots of different grid sizes, it’s always only the bottom-right grid cell where this happens.

  1. In the Cursor class, checkOverCell() sets the values for activeCell, and sends it to the Grid class via grid.setActive(activeCell).
  2. setActive finds a Tile instance with matching coordinates in the array of center points, sets it’s boolean highlightOn to true, and false for all other elements.
  3. The color is set in the Tile class, based on boolean highlightOn.
Grid grid;
Cursor cursor;
PVector m;
void setup() {
  //size(screen.width, screen.height);
  size(500, 500);
  grid = new Grid();
  cursor = new Cursor();
}
void draw() {
  background(92, 92, 255);
  m = new PVector(mouseX, mouseY);
  grid.display();
  cursor.drawCursor();
}

void mouseDragged() {
  if (m.x >= cursor.loc.x - cursor.cSize.x/2 && 
    m.x <= cursor.loc.x + cursor.cSize.x/2
    && m.y >= cursor.loc.y - cursor.cSize.y/2 && 
    m.y <= cursor.loc.y + cursor.cSize.y/2) {
    cursor.loc = m; // set the Cursor center to mouse
  }
}
void mouseReleased() {
  cursor.snapToCenter();
}
class Cursor {
  PVector loc, activeCell, cSize;
  Cursor() {
    loc = new PVector();
    activeCell = new PVector();
    cSize = new PVector(grid.colW, grid.rowH);
  }
  void snapToCenter() {
    loc = activeCell;
  }
  void drawCursor() {
    // find the grid coordinates of the cell center
    // which is closest to the Cursor location 
    // and store it in activeCell
    checkOverCell(); 

    // keep the center of the Cursor inside the grid
    loc.x = constrain(loc.x, grid.gCenter.x - grid.gSize.x/2-1 + cSize.x/2, 
      grid.gCenter.x + grid.gSize.x/2 - cSize.x/2);
    loc.y = constrain(loc.y, grid.gCenter.y - grid.gSize.y/2-1 + cSize.y/2, 
      grid.gCenter.y + grid.gSize.y/2 - cSize.y/2);

    stroke(255);
    //fill(225, 255, 225, 127);
    rectMode(CENTER);
    rect(loc.x, loc.y, cSize.x, cSize.y);
  }
  void checkOverCell() {
    checkOverColCenters();
    checkOverRowCenters();
    grid.setActive(activeCell); // send the PVector activeCell to the grid
  }
  void checkOverColCenters() {
    // determine which column the Cursor is over
    for (int i = 0; i < grid.cellCenters.length; i++) {
      if (loc.x >= grid.cellCenters[i][0].x - grid.colW/2 && 
        loc.x <= grid.cellCenters[i][0].x + grid.colW/2) {
        // set x in activeCell to the grid's column index
        activeCell.x = grid.cellCenters[i][0].x;
      }
    }
  }
  void checkOverRowCenters() {
    // determine which row the Cursor is over
    for (int i = 0; i < grid.cellCenters.length; i++) {
      if (loc.y >= grid.cellCenters[0][i].y - grid.rowH/2 && 
        loc.y <= grid.cellCenters[0][i].y + grid.rowH/2) {
        // set y in activeCell to the grid's row index
        activeCell.y = grid.cellCenters[0][i].y;
      }
    }
  }
}
class Grid {
  int colW, rowH, nCols, nRows;
  PVector gOrigin, gSize, gCenter, cellSize, linePointSize, centerPointSize;
  PVector[][] linePoints; // array of grid line locations
  Tile[][] points; // array of grid line "Tile" objects
  PVector[][] cellCenters; // array of grid cell center locations
  Tile[][] centers; // array of grid cell center "Tile" objects
  Grid() {
    nCols = 3;
    nRows = 3;
    colW = 100;
    rowH = 100;
    linePointSize = new PVector(10, 10); // size of "Tile" object
    centerPointSize = new PVector(5, 5); // size of "Tile" object

    gSize = new PVector(colW * nCols, rowH * nRows); // overall grid W/H
    gCenter = PVector.div(gSize, 2); // grid center point
    // gOrigin is used to translate the center of the grid to a specific coordinate
    gOrigin = new PVector(width/2 - gCenter.x, height/2 - gCenter.y);
    cellSize = new PVector(colW, rowH); 


    linePoints = new PVector[nCols + 1][nRows + 1]; // array of grid line locations
    points = new Tile[nCols + 1][nRows + 1]; // array of grid line "Tile" objects
    cellCenters = new PVector[nCols][nRows]; // array of grid cell center locations
    centers = new Tile[nCols][nRows]; // array of grid cell center "Tile" objects

    makeGrid();
    applyTranslation(gOrigin);
  }
  void makeGrid() {
    for (int i = 0; i < nCols + 1; i++) {
      for (int j = 0; j < nRows + 1; j++) {
        // initialize array of grid line locations
        linePoints[i][j] = new PVector(i * cellSize.x, j * cellSize.y);
        // initialize array of grid line "Tile" objects
        points[i][j] = new Tile(linePoints[i][j].x, 
          linePoints[i][j].y, linePointSize);
      }
    }
    for (int i = 0; i < nCols; i++) {
      for (int j = 0; j < nRows; j++) {
        // initialize array of grid cell center locations
        cellCenters[i][j] = new PVector(i * cellSize.x + cellSize.x/2, 
          j * cellSize.y + cellSize.y/2);
        // initialize array of grid cell center "Tile" objects
        centers[i][j] = new Tile(cellCenters[i][j].x, 
          cellCenters[i][j].y, centerPointSize);
      }
    }
  }
  void applyTranslation(PVector newOrigin) {
    gCenter.add(newOrigin);
    for (int i = 0; i < linePoints.length; i++) {
      for (int j = 0; j < linePoints.length; j++) {
        // translate each element in array of grid line locations
        linePoints[i][j].add(newOrigin);
        // translate each element in array of grid line "Tile" objects
        points[i][j].loc.add(newOrigin);
      }
    }
    for (int i = 0; i < cellCenters.length; i++) {
      for (int j = 0; j < cellCenters.length; j++) {
        // translate each element in array of grid cell center locations
        cellCenters[i][j].add(newOrigin);
        // translate each element in array of grid cell center "Tile" objects
        centers[i][j].loc.add(newOrigin);
      }
    }
  }
  void setActive(PVector activeCell) {
    for (int i = 0; i < centers.length; i++) {
      for (int j = 0; j < centers.length; j++) {
        // check the coordinates of each "Tile" instance 
        // in grid center "Tile" array to see if they 
        // match activeCell's coordinates
        if (centers[i][j].loc.x == activeCell.x && 
          centers[i][j].loc.y == activeCell.y) {
          // if they match, set highlightOn to true
          centers[i][j].highlightOn = true;
        } else {
          // if they don't match, set highlightOn to false
          centers[i][j].highlightOn = false;
        }
      }
    }
  }
  void display() {
    for (int i = 0; i < nCols + 1; i++) {
      for (int j = 0; j < nRows + 1; j++) {
        points[i][j].display(); // display the grid line "Tile" object
      }
    }
    for (int i = 0; i < nCols; i++) {
      for (int j = 0; j < nRows; j++) {
        centers[i][j].display(); // display the cell center "Tile" object
      }
    }
  }
}
class Tile {
  PVector loc, tileSize;
  boolean highlightOn;
  Tile(float x, float y, PVector tileSize) {
    this.loc = new PVector(x, y);
    this.tileSize = tileSize;
    highlightOn = false;
  }
  void display() {
    if (highlightOn) {
      // set fill to red if highlightOn is true
      stroke(255, 0, 0, 127);
      fill(255, 0, 0, 127);
    } else {
      // set fill to white if highlightOn is false
      stroke(255);
      fill(255, 255, 255, 127);
    }
    rectMode(CENTER);
    rect(loc.x, loc.y, tileSize.x, tileSize.y);
  }
}

Using the offset method for dragging an object cleared up the issue of the object stopping while being dragged when the mouse left the constrain area. This code fixed it.

void mousePressed() {
  if (m.x >= cursor.loc.x - cursor.cSize.x/2 && 
    m.x <= cursor.loc.x + cursor.cSize.x/2
    && m.y >= cursor.loc.y - cursor.cSize.y/2 && 
    m.y <= cursor.loc.y + cursor.cSize.y/2) {
    cursor.locked = true;
  }
  offset = PVector.sub(m, cursor.loc);
}
void mouseDragged() {
  if (cursor.locked) {
    cursor.loc = PVector.sub(m, offset);
  }
}
void mouseReleased() {
  cursor.locked = false;
  cursor.snapToCenter();
}

But if anyone can see about the problem with the fill color I could use help with that

Hello, could you please give a bit more detail on what you would need help with in terms of the color fill part?

EnhancedLoop7

I can’t figure out why the Cursor obect’s rectangle changes its color to red when it’s on the bottom right square of the grid.

Even if the number of rows and columns are changed to any other size grid, it always changes color in the bottom right corner.

Hi ddown,

You got it because of the way you determine your fill color.

In your draw function, you first call the grid.display() and then cursor.drawCursor()

void draw() {
  background(92, 92, 255);
  m = new PVector(mouseX, mouseY);
  grid.display();
  cursor.drawCursor();
}

In the grid.display() function you have that loop:

void display() {
    for (int i = 0; i < nCols + 1; i++) {
      for (int j = 0; j < nRows + 1; j++) {
        points[i][j].display(); // display the grid line "Tile" object
      }
    }
    for (int i = 0; i < nCols; i++) {
      for (int j = 0; j < nRows; j++) {
        centers[i][j].display(); // display the cell center "Tile" object
      }
    }
  }

So the last thing you are calling is centers[lastRow][lastColumn].display().
Now, if this center is not highlight, you set the color to a transparent white and thus your cursor as also the same color (because you don’t change the fill() color before drawing the square of the cursor).
And if this tile is highlighted then you fill it with a transparent red and so is your cursor.
That’s why you got that behavior.

If you uncomment the fill(225, 255, 225, 127); line in the drawCursorfunction of your cursor class it solves your problem

Thank you, I can see what you mean. I’m still learning and I wanted to make sure I could understand what was going on without just hiding it with another fill color.

It is not really hiding, it is just how it should be done!

1 Like