How to create a rubber-esque drag and move of 2D object in c++ glut

I have an object that I want to be able to move. I want it to move only if i click within in, then drag my mouse to a place, then upon release, it starts moving towards it. so if i click inside it, I can move my mouse anywhere as much as I want while holding mouse button, but only when I release does it start to move.

Currently, the best ive been able to do is make the object follow (a couple seconds behind btw, NOT on the mouse position) as long as im holding the mouse button and moving it. it doesnt matter where I start the click from, as long as I click and move, the object moves towards it as long as im holding the mouse button. any other attempts leave the object staying still/not moving at all.

void mousemotion(int x, int yc){
globals.mouse_x = x;
globals.mouse_y = HEIGHT - yc;
}

and

int main(int argc, char** argv){
glutInit(&argc, argv);
....
//glutMouseFunc(processMouse);
glutMotionFunc(mousemotion);

are the only mouse functions/callbacks that are currently being used to allow the result above. I have tried things like adding a glutMouseFunc callback but from changing the state parameter in it produces bad results. E.G:

//glutMouseFunc callback 
void processMouse(int button, int state, int x, int yc){
    if ( state == GLUT_UP){
    globals.centre_x = globals.mouse_x;
    globals.centre_y = globals.mouse_y;
}

GLUT_DOWN doesnt change the main behaviour, but when the object is in motion, and I just click once, the object will snap to the position it was going to. GLUT_UP just makes it so once I release the mouse, the object will immediately snap to the position it was going. These behaviours make sense as to they are behaving the way they are, but I cant manipulate it to work the way I want to. I also made a function to check if a point is inside the object but I dont know where its applicable

bool inside(int x, int y){
if(x >= globals.centre_x - 20
    && x <= globals.centre_x +20
    && y >= globals.centre_y - 20
    && y <= globals.centre_y+ 20){
    return true;
}
else
    return false;
}

pressumably it would be used inside once of the mouse functions, using the x and y mouse coordinates as parameters.

all the drag and drop examples Ive seen online involve immediate dragging of the object, i.e. click on object and object follows the exact x,y mouse coordinates as you move it around, but I want to make it so only when I let go of the mouse will the object start to move.

Any Help appreciated. let me know if i can clarify anything. thanks

2 answers

  • answered 2018-04-17 14:17 Spektre

    I do not use GLUT but Based on these:

    To detect mouse event type You need to do something like this:

    //glutMouseFunc callback 
    int state0l=GLUT_UP; // last left mouse buttons state
    int state0r=GLUT_UP; // last right mouse buttons state
    void processMouse(int button, int state1, int x, int y)
        {
    
        if (button == GLUT_LEFT_BUTTON)
         {
         // decode and handle the mouse events by type
         if ((state0l == GLUT_UP  )&&(state1 == GLUT_DOWN)) // mouse down (click)
          {
          // here do your stuff
          }
         if ((state0l == GLUT_DOWN)&&(state1 == GLUT_DOWN)) // mouse move while clicked
          {
          // here do your stuff
          }
         if ((state0l == GLUT_DOWN)&&(state1 == GLUT_UP  )) // mouse up (release)
          {
          // here do your stuff
          }
         if ((state0l == GLUT_UP  )&&(state1 == GLUT_UP  )) // mouse move without buttons
          {
          // here do your stuff
          }
         // store actual buttons state for next time
         state0l = state1;
         }
    
        if (button == GLUT_RIGHT_BUTTON)
         {
         // decode and handle the mouse events by type
         if ((state0r == GLUT_UP  )&&(state1 == GLUT_DOWN)) // mouse down (click)
          {
          // here do your stuff
          }
         if ((state0r == GLUT_DOWN)&&(state1 == GLUT_DOWN)) // mouse move while clicked
          {
          // here do your stuff
          }
         if ((state0r == GLUT_DOWN)&&(state1 == GLUT_UP  )) // mouse up (release)
          {
          // here do your stuff
          }
         if ((state0r == GLUT_UP  )&&(state1 == GLUT_UP  )) // mouse move without buttons
          {
          // here do your stuff
          }
         // store actual buttons state for next time
         state0r = state1;
         }
        }
    

    As you can see I just inspect last and actual state of the buttons to detect 4 possibilities +/- the same as in the link I gave you before:

    I just have q0,q1 instead of the state0,state1 when you inspect the methods like: editor::mov,add_kruh,add_stvorec,... they all use this same technique (but only those events they use of coarse).

  • answered 2018-04-17 15:05 genpfault

    • Do selection detection in your mouse callback on button-down
    • Set up animation parameters (start & end position + duration) in your mouse callback on button-up
    • Interpolate animation positions in a timer callback & update selected position

    All together (right-click to add rectangles):

    #include <GL/glut.h>
    // https://glm.g-truc.net/
    #include <glm/glm.hpp>
    #include <vector>
    
    struct Rect
    {
        glm::vec2 pos;
        glm::vec2 dim;
    
        bool IsInside( glm::vec2 point ) const
        {
            const bool inX = pos.x < point.x && point.x < pos.x + dim.x;
            const bool inY = pos.y < point.y && point.y < pos.y + dim.y;
            return inX && inY;
        }
    };
    
    // rect list & selection
    std::vector< Rect > rects;
    int selected = -1;
    
    // animation state
    int timeBeg = -1;
    int timeEnd = -1;
    glm::vec2 src;
    glm::vec2 dst;
    
    void mouse( int button, int state, int x, int y )
    {
        if( GLUT_RIGHT_BUTTON == button )
        {
            // add rect
            if( GLUT_UP == state )
            {
                rects.push_back( Rect{ glm::vec2( x, y ), glm::vec2( 60, 60 ) } );
                glutPostRedisplay();
            }
    
            return;
        }
    
        if( GLUT_LEFT_BUTTON == button && ( timeBeg < 0 || timeEnd < 0 ) )
        {
            // select rect
            if( GLUT_DOWN == state )
            {
                for( size_t i = 0; i < rects.size(); ++i )
                {
                    if( !rects[i].IsInside( glm::vec2( x, y ) ) )
                        continue;
    
                    selected = i;
                    glutPostRedisplay();
                    return;
                }
            }
    
            // finish select
            if( GLUT_UP == state && selected >= 0 )
            {
                timeBeg = glutGet( GLUT_ELAPSED_TIME );
                timeEnd = timeBeg + 1000;
                src = rects[ selected ].pos;
                dst = glm::vec2( x, y );
            }
    
            return;
        }
    }
    
    void timer( int value )
    {
        glutTimerFunc( 16, timer, 0 );
    
        // don't repaint if we aren't animating
        if( timeBeg < 0 || timeEnd < 0 || selected < 0 )
            return;
    
        const int timeCur = glutGet( GLUT_ELAPSED_TIME );
        if( timeCur > timeEnd )
        {
            // animation done
            timeBeg = -1;
            timeEnd = -1;
            selected = -1;
            glutPostRedisplay();
            return;
        }
    
        float pct = ( timeCur - timeBeg ) / static_cast< float >( timeEnd - timeBeg );
        rects[ selected ].pos = glm::mix( src, dst, pct );
    
        glutPostRedisplay();
    }
    
    void display()
    {
        glClearColor( 0, 0, 0, 1 );
        glClear(GL_COLOR_BUFFER_BIT);
    
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();
        double w = glutGet( GLUT_WINDOW_WIDTH );
        double h = glutGet( GLUT_WINDOW_HEIGHT );
        glOrtho( 0, w, h, 0, -1, 1 );
    
        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
    
        for( size_t i = 0; i < rects.size(); ++i )
        {
            if( selected == i )
                glColor3ub( 255, 0, 0 );
            else
                glColor3ub( 255, 255, 255 );
    
            const Rect& rect = rects[i];
            glRectf( rect.pos.x, rect.pos.y, rect.pos.x + rect.dim.x, rect.pos.y + rect.dim.y );
        }
    
        glutSwapBuffers();
    }
    
    int main( int argc, char** argv )
    {
        glutInit( &argc, argv );
        glutInitWindowSize( 800, 600 );
        glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE );
        glutCreateWindow( "GLUT" );
        glutMouseFunc( mouse );
        glutTimerFunc( 0, timer, 0 );
        glutDisplayFunc( display );
        glutMainLoop();
        return 0;
    }