Reactive Mouse Headings

Every now and then, one meets an interesting stranger on a plane and you have an impromptu pair-programming session. That’s what happened when I met @praeclarum (aka Frank Krueger) on a recent flight. We swapped knowledge about PID controllers and Rx, The Reactive Framework, and went to work on a little UI problem: identify the “heading” of the mouse.

Heading is normally expressed in degrees, with 0 to the East, 90 to the North, 180 West, and 270 South. We figured if we could track mouse deltas, dy and dx, then Atan2(dy, dx) would give us an angle that we could transform into a heading. The technique for tracking mouse deltas is a recurring theme in Rx. First, get an observable from system-supplied mouse-move events supported by WinForms:

    var mms = Observable
        .FromEventPattern<MouseEventArgs>(form, "MouseMove")
        ;

This observable will produce observations containing mouse coordinates relative to the windows form. We need deltas, and here is a standard way to get them:

    var headings = mms.Zip(mms.Skip(1), (@from, @to) => {
        var dx = @to.EventArgs.X - @from.EventArgs.X; 
        var dy = @to.EventArgs.Y - @from.EventArgs.Y;
        return new { dx = dx, dy = dy };
        });

Just refer to the mouse-move observable, mms, twice, once with a .Skip(1). That gives a pair of observable streams offset by one event, which we .Zip together through a binary function, represented by the lambda expression with arguments (@from, @to). In this binary function, just subtract the coordinates component-wise. The result is an observable of instances of an anonymous type with properties dx and dy.

Atan2(dy, dx) works in a right-handed coordinate system, yielding angles between -pi (-180 degrees) and +pi (+180 degrees). We’d rather have positive angles all the time, so we just add 2 pi and mod by 2 pi:

    Func<double, double, double> RadiansFromDyDx =
         (dy, dx) => ((Math.Atan2(dy, dx) + 2 * Math.PI) % 
             (2 * Math.PI));

Adding a version that yields degrees just so we can visually check a console log:

    Func<double, double, int> DegreesFromDyDx =
         (dy, dx) => ((int)(RadiansFromDyDx(dy, dx) * 180 / Math.PI));

Now we just modify our .Zip function so that it yields a bunch of data we can use down the line to render System.Drawing.Points in the form and log the degrees:

    var headings = mms.Zip(mms.Skip(1), (@from, @to) => {
        var dx = @to.EventArgs.X - @from.EventArgs.X;
        var dy = @to.EventArgs.Y - @from.EventArgs.Y;
        return new
        {   @from = new Point(@from.EventArgs.X, @from.EventArgs.Y),
            @to = new Point(@to.EventArgs.X, @to.EventArgs.Y),
            angle = DegreesFromDyDx(-dy, dx),
            heading = RotatedQuadrantFromDyDx(-dy, dx)
        };   });

Notice how we have accounted for the WinForms left-handed coordinate system simply by reversing the algebraic sign of dy, plus we have added a new function:

    Func<double, double, int> RotatedQuadrantFromDyDx =
        (dy, dx) => ((int)((Math.Atan2(dy, dx) + 9 * Math.PI / 4) 
            * 2 / Math.PI)) % 4;

wherein we add pi/4 (45 degrees) to the angle, so we can count as East all angles between -45 and 45, North as all angles between 45 and 135, and so on; then multiply by 4, divide by 2/pi, convert to int, and mod by 4 for safety, so we always get a number between 0 and 3: 0 for East, 1 for North, 2 for West, and 3 for South.

Now Subscribe a few observers: one that logs the angle and heading, but only when the angle changes:

    headings
        .Select(h => new { h.angle, h.heading })
        .DistinctUntilChanged(h => h.angle)
        .Subscribe(h =>
        {   Console.WriteLine("[{2}]: {0}, {1}", h.heading, h.angle,
                Thread.CurrentThread.ManagedThreadId);
        })
        ;

(I like to log threadId’s, too, since one can often be fooled about the thread that is running some piece of code. This must all be on the UI thread).

Another observer that draws fat colored lines, color depending on direction:

    var pens = new [] 
    {   new Pen(Color.FromKnownColor(KnownColor.Red), 10),
        new Pen(Color.FromKnownColor(KnownColor.Green), 10),
        new Pen(Color.FromKnownColor(KnownColor.Blue), 10),
        new Pen(Color.FromKnownColor(KnownColor.Orange), 10)
    };
    headings
        .Subscribe(h => g.DrawLine(pens[h.heading], h.@from, h.@to))
        ;

and one that draws text denoting the direction

    var headingNames = new [] {"E", "N", "W", "S"};
    headings   
        .Select(d => d.heading)
        .DistinctUntilChanged()
        .Subscribe(h =>
        {   g.FillRectangle(backgroundBrush, 
                textPoint.X, textPoint.Y, 48, 48);
            g.DrawString(headingNames[h], font, brush, textPoint);
        });
        ;

You’ll forgive my ham-handed UI coding: this was more-or-less thrown together on the plane. You can see the whole code at this gist.

Also, the headings are a little jumpy. We’d like to smooth them out… perhaps with a PID controller?

Advertisements

~ by rebcabin on November 11, 2012.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: