How to snap mouse to a shape/coordinate?

Hello my idea is to snap the mouse cursor to intersections on a grid when the user approaches them (this will center the mouse pointer to that cross on the grid). I did some digging but I haven’t found anything that can ‘force’ the cursor onto a point/shape. Is there a function or library I can use to do this?

library robot mouse - search here in the forum

Chrisir

How will they move the mouse off the grid point once it’s snapped on?

The easiest way is to press a key as a ‘release button’, but I will try to mimic those programs where if you move the mouse slow enough, it doesn’t snap in the first place, and you can also drag the mouse away from the snap if you ‘yank’ your mouse away fast enough

Thank you Chrisir, I will check it out!

in this i use a grid of RECT
and the STROKE to show MOUSE OVER
the mouse cursor is ON ( HAND ) but can be disabled by key [m] or change start default.

if no mouse cursor is visible, the stroke show over what RECT the mouse is,
can be called “mouse SNAP to RECT”

make the console window bigger to see all that options / possible operations

// https://discourse.processing.org/t/scalable-grid-with-rectangles/7256
// https://discourse.processing.org/t/better-grid-performance/7314
// https://discourse.processing.org/t/gryd-system-graphic-design/10457
// https://discourse.processing.org/t/change-rectangles-seperately-in-for-loop/10908/2
// https://discourse.processing.org/t/creating-a-grid-of-toggle-buttons/12162
// use backup memory array for resize/reinit grid but remember selected rects

// use mouse over on button stroke and disable cursor with [m]
// https://discourse.processing.org/t/how-to-snap-mouse-to-a-shape-coordinate/13414


int x = 100, y = x, w = 150, h = w, off= 15, grid = 3, many = grid*grid;
float rang = 0, strokew=5.0;
boolean auto = false;//false;//true;
boolean shownum    = false;    // key [n]
boolean showFPS    = true;
boolean togfill    = false;
boolean showmouse = true;

// color setup:
color bg  = color(200, 200, 0);
color stk = color(0, 0, 200);
color stkover = color(0, 200, 0);
color fillsel = color(0, 200, 200);
color fillnsel= color(200, 0, 200);

Myrect[]   myrects   = new Myrect[many];
Mybackup[] myselects = new Mybackup[many];  // temporary memory

void setup() {
  size(800, 520);
  noSmooth();
  set_grid(true);     // init
  String help = "";
  help += "use: mouse LEFT to select, mouse RIGHT to deselect\n";
  help += "key UP DOWN RIGHT LEFT for position\n";
  help += "key [+]  [-]  for grid size\n";
  help += "key [n] toggle shownumber\n";
  help += "key [f] toggle fill\n";
  help += "key [c] clear selected\n";
  help += "key [m] toggle cursor\n";
  help += "____MouseWheel Plus Plus ____\n";
  help += "use key [x] [y] [w] [h] [o] for position and size\n";
  help += "use key [r] rotate / key [s] stroke width\n";
  help += "and turn mouse wheel";
  println(help);
  if ( showmouse ) cursor(HAND); else noCursor();
}

void draw() {
  background(bg);
  fill(0);
  if (showFPS ) text(nf(frameRate, 1, 1), 10, 10);
  for (int i = 0; i < many; i++) myrects[i].drawit();               // draw each rect ( and check on mouse over + click )
}

class Mybackup {
  boolean selected=false;
}

class Myrect {
  int x, y, id;//, w, h, off, rang; from global
  boolean selected=false;
  Myrect (int x, int y, int id) {
    this.x = x; 
    this.y = y;
    this.id = id;
  }

  void drawit() {
    if ( strokew > 0.2 ) { 
      strokeWeight(strokew);
      stroke( stk );
      if ( over() ) stroke( stkover );
    } else                  noStroke();
    if ( selected )  fill(fillsel);
    else             fill(fillnsel);
    if ( togfill )   noFill();
    push();
    translate(x, y);
    rotate(rang);
    rect(-(w-off)/2, -(h-off)/2, (w-off), (h-off));
    fill(0);   // black text
    if (shownum) text(id, -w/8, +h/8);
    pop();
    sel();
  }

  boolean over() {
    return( x-(w-off)/2 < mouseX && mouseX < x+(w-off)/2 && y-(h-off)/2 < mouseY && mouseY < y+(h-off)/2 );
  }

  void sel() {
    if ( over() ) {
      if (  selected && mousePressed && mouseButton == RIGHT) selected=false;
      if ( !selected && mousePressed && mouseButton == LEFT)  selected=true;
    }
  }
}

void set_wh() {
  // use x,y,grid as master and auto adjust rectangles (w,h) to window:
  println(" width= "+width+", height= "+height+", grid= "+grid);
  w = ( width  - 2 * x ) / grid-off;   
  println(" w= "+w+", x= "+x+", ( grid * w + 2 * x )= "+(grid*w+2*x));
  h = ( height - 2 * y ) / grid-off;
  println(" h= "+h+", y= "+y+", ( grid * h + 2 * y )= "+(grid*h+2*y));
}

void set_grid(boolean init) {
  if ( auto ) set_wh();
  if ( init )  for (int i = 0; i < many; i++)  myselects[i]=new Mybackup();                     // init backup memory
  else         for (int i = 0; i < many; i++)  myselects[i].selected = myrects[i].selected;     // backup
  for (int i = 0; i < many; i++)  myrects[i]=new Myrect(x+(i%grid)*w, y+(floor(i/grid))*h, i);  // resize
  if ( !init ) for (int i = 0; i < many; i++)  myrects[i].selected = myselects[i].selected;     // restore
}

void clear_selected() {
  for (int i = 0; i < many; i++)  myrects[i].selected = false; 
  println("clear selected after key [c]");
}

void keyPressed() {
  if      ( keyCode == UP )    y--;
  else if ( keyCode == DOWN )  y++;
  else if ( keyCode == LEFT )  x--;
  else if ( keyCode == RIGHT ) x++;
  else if ( key     == '+' ) { 
    w++; 
    h++;
  } else if ( key     == '-' ) { 
    w--; 
    h--;
  }
  //h=w;              // quadrat only
  auto = false;     // confirm
  set_grid(false);  // resize
  if ( key == 'n' ) shownum = !shownum;
  if ( key == 'f' ) togfill = !togfill;
  if ( key == 'c' ) clear_selected();
  if ( key == 'm' ) showmouse = ! showmouse;
  if ( showmouse ) cursor(HAND); else noCursor();

}

void mouseWheel(MouseEvent event) {
  float e = event.getCount(); //println(e);
  if ( keyPressed && key == 'x' ) { 
    x += e ;
    println(" key x: x "+x);
  }
  if ( keyPressed && key == 'y' ) { 
    y += e;
    println(" key y: y "+y);
  }
  if ( keyPressed && key == 'w' ) { 
    w += e;
    w=constrain(w, 0, width);
    println(" key w: w "+w);
  }
  if ( keyPressed && key == 'h' ) { 
    h += e;
    h=constrain(h, 0, height);
    println(" key h: h "+h);
  }
  if ( keyPressed && key == 'o' ) { 
    off += e;
    off=constrain(off, 0, w);
    println(" key o: off "+off);
  }
  if ( keyPressed && key == 'r' ) { 
    rang += e*0.03;
    println(" key r: rang "+int(degrees(rang)));
  }
  if ( keyPressed && key == 's' ) { 
    strokew += e*0.1;
    strokew = constrain(strokew, 0, w/2);
    println(" key s: strokew "+nf(strokew, 1, 1));
  }
}

example without robot class lib


int x = 100, y = x, w = 150, h = w, off= 15, grid = 3, many = grid*grid;

Myrect[]   myrects   = new Myrect[many];

boolean auto = false;//false;//true;

void setup() {
  size(800, 520);
  // noSmooth();
  set_grid(true);     // init
  // noCursor();
}


void draw() {
  background(0);
  fill(0);

  for (int i = 0; i < many; i++)
    myrects[i].drawit();               // draw each rect ( and check on mouse over + click )

  // artificial mouse 
  float x=mouseX; 
  float y=mouseY; 

  // search snap points 
  for (int i = 0; i < many; i++) {
    if (dist(myrects[i].x, myrects[i].y, mouseX, mouseY) < 39) {
      // SNAP 
      x=myrects[i].x;
      y=myrects[i].y;
      break;
    }
  }

  // show artificial mouse 
  fill(255);
  // noStroke(); 
  ellipse(x, y, 
    10, 10);
  noFill();
  stroke(0); 
  ellipse(x, y, 
    17, 17);

  // Text 
  fill(255);
  text("snapping to center of rects", 16, 16);
}

void set_grid(boolean init) {
  if ( auto )
    set_wh();
  //if ( init )  for (int i = 0; i < many; i++)  myselects[i]=new Myb//ackup();                     // init backup memory
  // else         for (int i = 0; i < many; i++)  myselects[i].selected = myrects[i].selected;     // backup

  for (int i = 0; i < many; i++)  
    myrects[i]=new Myrect(x+(i%grid)*w, y+(floor(i/grid))*h, i);  // resize
  //if ( !init ) for (int i = 0; i < many; i++)  myrects[i].selected = myselects[i].selected;     // restore
}

void set_wh() {
  // use x,y,grid as master and auto adjust rectangles (w,h) to window:
  println(" width= "+width+", height= "+height+", grid= "+grid);
  w = ( width  - 2 * x ) / grid-off;   
  println(" w= "+w+", x= "+x+", ( grid * w + 2 * x )= "+(grid*w+2*x));
  h = ( height - 2 * y ) / grid-off;
  println(" h= "+h+", y= "+y+", ( grid * h + 2 * y )= "+(grid*h+2*y));
}

// ==========================================================================

class Myrect {
  int x, y, id;//, w, h, off, rang; from global
  boolean selected=false;

  Myrect (int x, int y, int id) {
    this.x = x; 
    this.y = y;
    this.id = id;
  }

  void drawit() {
    pushMatrix();
    translate(x, y);
    fill(244, 33, 0);
    rect(-(w-off)/2, -(h-off)/2, (w-off), (h-off));
    fill(0);   // black text
    popMatrix();
  }

  boolean over() {
    return( x-(w-off)/2 < mouseX && mouseX < x+(w-off)/2 && y-(h-off)/2 < mouseY && mouseY < y+(h-off)/2 );
  }
}

What exactly do you mean by snap?

That the cursor jumps to a certain point when near it (see my example) OR the selection of cells (see kll example)?

The simplest way to snap in 2D is %. Define your grid step, then subtract the modulo of that from your current location to snap, no modulo for free motion.

int gstep = 10;

void draw(){
  background(128);
  if (keyPressed){
    ellipse(mouseX, mouseY, 10, 10);
  } else {
    ellipse(mouseX - mouseX%gstep, mouseY - mouseY%gstep, 10, 10); // snap
  }
  lines();
}

void lines(){
  for(int x=0; x<=width; x+=gstep) line(x,0,x,width);
  for(int y=0; y<=height; y+=gstep) line(0,y,height,y);
}

This assumes your origin in 0,0, your step size is an integer, and your grid is whole-screen, but setting to an arbitrary range on the screen isn’t much more complicated. Good for basic sketches.

At a certain point, if you are, e.g. programming the game “Battleship” with two grids floating in screen, you might want to transition to an Object oriented example with a grid class.


The snap-down behavior, above, means that your mouse won’t be snapping to the closest point – it will be snapping to the closest point up-and-left. That may not be what you want. You can snap to closest in a simple way by adding a 0.49 step to your input. Try enabling and disabling the “change snap-down” line below to see the difference.

int gstep = 25;

void draw(){
  background(128);
  if (keyPressed){
    ellipse(mouseX, mouseY, 10, 10);
  } else {
    ellipse(snap(mouseX, gstep), snap(mouseY, gstep), 10, 10); // snap
  }
  lines();
}

float snap(float input, float step){
  input += (step*0.49); // change snap-down to snap-closest
  float snapped = input - (input % step);
  return snapped;
}

void lines(){
  for(int x=0; x<=width; x+=gstep) line(x,0,x,width);
  for(int y=0; y<=height; y+=gstep) line(0,y,height,y);
}
1 Like