How to know where mouse will be prior to MouseMove

856 Views Asked by At

I am trying to implement a functionality in my application to snap cursor to edge of a grid in my scene. As it stands I do have the framework to take the current MouseMove provided e.Location and converting to my world coordinates and back to screen - and the values match. See below basic code overview.

public void Scene_MouseMove(object sender, MouseEventArgs e)
{
    Vector2 world = ScreenToWorld(e.Location);

    ---> Check here to make sure the world coordinates returned
         fall inside my grid scene edges.
    if (world.X < Grid.Left) world.x = Grid.Left;

    Point target = WorldToScreen(world);

    // set cursr desired position
    Cursor.Position = (sender as PictureBox).PointToScreen( target );
}

The problem I am having is the fact that MouseMove is getting called AFTER the fact that mouse has moved, so when I hit the edges of my grid I see the mouse overshoot for a frame, and then correct itself. This causes the cursor to jitter past the edges as I move the mouse. I want to make it so when I hit the edge the cursor stops dead in its tracks, but I do not know how to capture the data prior to the mouse moving!

Perhaps I am going about this wrong, so any suggestions would be greatly appreciated.

FYI - This is the first part of a SnapToGrid functionality I am trying to implement.

EDIT : A simpler example:

You can see my problem running the simple example below. Notice how the cursor flickers every frame as you move it around?

bool g_Set = false;
public void Scene_MouseMove(object sender, MouseEventArgs e)
{
    // stop MouseMove from flooding the control recursively
    if(g_Set) { g_Set = false; return; }
    g_Set = true;
    Cursor.Position = new Point(400,400);
}

Does C# support anything in the API to capture MouseMove before it actually moves the Cursor, or should I just be looking into implementing my own Cursor class to hide the Form.Cursor and just render mine (something else I would need to look into as I have no clue on that functionality either).

4

There are 4 best solutions below

1
On

To snap to edge's give a little room for the snap, eg 2 pixels:

if (world.X - 2 < Grid.Left) world.x = Grid.Left;

To confine the cursor within the rectangle of a control, eg: aButton

Cursor.Clip = aButton.RectangleToScreen(aButton.ClientRectangle);

To free the cursor:

Cursor.Clip = null;
0
On

So after a good day looking into this I eventually just broke down and wrote a Cursor class. What is important here is that I am rendering in the PictureBox using Managed DirectX, so I had a way out.

Effectively, I hide the System.Cursor when it enters the control and start rendering my own cursor by taking the offset of the System.Cursor between each frame, applying my logic, and determining where I want to render "my" cursor. See below how I handle offset:

public bool g_RecalcCursor = false;
public Point g_Reference = new Point(400,400);
public void SceneView_MouseMove(object sender, MouseEventArgs e)
{
    // this logic avoids recursive calls into MouseMove
    if (g_RecalcCursor)
    { 
        g_RecalcCursor = false;
        return;
    }

    Point ee = (sender as PictureBox).PointToScreen(e.Location);
    Point delta = new Point(g_Reference.X - ee.X, g_Reference.Y - ee.Y);

    //------------------------------------------//
    // I can use delta now to move my cursor    //
    // and apply logic "BEFORE" it renders      //
    //------------------------------------------//

    g_RecalcCursor = true;
    Cursor.Position = g_Reference;
}

I am surprised that there is no calls like Form_Closing / Form_Closed on mouse movement (MouseMoving / MouseMove) - but then again, System.Cursor is probably not meant to be manipulated by application in order to not deteriorate user experience on how it should normally function, hence limited manipulation functionality in the API.

I am still open to any suggestions that would let me use the System.Cursor ...

1
On

Use linear extrapolation to calculate the last pixel prior to entering the frame. You would need two points P1 and P2. The point before entering, P0, can be approximated by

P0.X = P2.X - P1.X
P0.Y = P2.Y - P1.Y
0
On

You could create a UserControl which has a Scene on it. Place the Scene in the center, with a margin of known size around it. The UserControl.BackColor = Transparent. Handle the event

private void UserControl_MouseMove(Object sender,MouseEventArgs e)
{
    // check if mouse is entering the Scene, you know the size of the margin
}

From there you can come up with the logic to anticipate the mouse entering the Scene.