Pages

Thursday, July 31, 2014

Elliptic context menu in WPF

Today I've decided to try build a simple elliptic context menu, something like in games. While you holding right mouse button you can pick something. As it is just a prototype I will build the simplest drawing application. With that menu user would be able to select a background color.

So, first thing first. Basic layout: just a canvas with a predefined background color and 2 events for right mouse up and down. OnDown should create context menu elements and OnUp should destroy them. And those elements should be created around mouse cursor.
The easiest way to create that circular shape is to use Path with ArcSegment. And the only trick here is trigonometry. We need to provide start and end points for the arc and they should be on a circle. Basically, x = cos and y = sin. Plus we should calculate the correct angle - in radians. Thats the all mathematics we will need!

Here is my method to create a menu segment:
private static Path CreateEllipticMenu(Point center, Brush color, int currentNumber, int maxNumber)
{
    var menu = new MyMenu {MyBrush = color};
    const int size = 50;
    var angleStart = currentNumber*2*Math.PI/maxNumber;
    var angleEnd = (currentNumber + 1)*2*Math.PI/maxNumber;

    var startX = center.X + size * Math.Cos(angleStart);
    var startY = center.Y + size * Math.Sin(angleStart);

    var endX = center.X + size * Math.Cos(angleEnd);
    var endY = center.Y + size * Math.Sin(angleEnd);

    var myPath = new Path();
    myPath.Stroke = menu.MyBrush;
    myPath.StrokeThickness = 20;
    myPath.HorizontalAlignment = HorizontalAlignment.Left;
    myPath.VerticalAlignment = VerticalAlignment.Center;
    var arc = new ArcSegment();
    arc.IsLargeArc = false;
    arc.Size = new Size(size, size);
    arc.SweepDirection = SweepDirection.Clockwise;
    arc.Point = new Point(endX, endY);
    var pathFigure = new PathFigure();
    pathFigure.StartPoint = new Point(startX, startY);
    pathFigure.Segments.Add(arc);
    myPath.Data = new PathGeometry(new List<PathFigure> {pathFigure});

    myPath.MouseEnter += (e, s) => { myPath.Stroke = Brushes.Red; menu.IsSelected = true; };
    myPath.MouseLeave += (e, s) => { myPath.Stroke = menu.MyBrush; menu.IsSelected = false; };

    myPath.Tag = menu;
    return myPath;
}

I have also defined mouse enter and mouse leave events so I can select active menu item and every menu item has MyMenu object associated with it. That class is just a helper:
public class MyMenu
{
    public Brush MyBrush { get; set; }
    public bool IsSelected { get; set; }
}

Well, thats all! Just as an addition here is an example of usage:

private void MyCanvas_OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
    var center = new Point(e.GetPosition(MyCanvas).X, e.GetPosition(MyCanvas).Y);

    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.MediumSlateBlue, 0, 4));
    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.MediumAquamarine, 1, 4));
    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.MediumVioletRed, 2, 4));
    MyCanvas.Children.Add(CreateEllipticMenu(center, Brushes.LightYellow, 3, 4));
}

private void MyCanvas_OnMouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
    int i = 0;
    while (i < MyCanvas.Children.Count)
    {
        var child = MyCanvas.Children[i];
        if (child is Path && (child as Path).Tag is MyMenu)
        {
            var menu = (child as Path).Tag as MyMenu;
            if (menu.IsSelected) MyCanvas.Background = menu.MyBrush;

            MyCanvas.Children.Remove(child as Path);
            continue;
        }
        i++;
    }
}

Tuesday, July 29, 2014

FTDI device monitor

As usual I want to describe one of the last things I've done. Yeah I have not written anything to the blog for a while but thats not because I have not done anything but because I am planing something new!

Still, for now I want to describe how to get the list of connected USB devices using FTDI drivers. It is quite easy. Everything is done through the FTDI class.
First of all - you can check the number of connected devices with a call:

var monitor = new FTDI();
UInt32 newDeviceCount = 0;
var status = monitor.GetNumberOfDevices(ref newDeviceCount);
if (status != FTDI.FT_STATUS.FT_OK) //something wrong!

The last check is there just for exceptions. Then you can get the actual list of connected devices. As this library follows c++ style - you have to pass an initialized array of FT_DEVICE_INFO_NODE:

var ftdiDeviceList = new FTDI.FT_DEVICE_INFO_NODE[newDeviceCount];
ftStatus = newDevice.GetDeviceList(ftdiDeviceList);
if (ftStatus != FTDI.FT_STATUS.FT_OK) //something wrong!

And finally we can iterate through devices and get some information about them:

for (var i = 0; i < newDeviceCount; i++)
{
    _devices.Add(new DeviceDTO(i, //_devices here is a List of a simple DTO class
    ftdiDeviceList[i].Flags,
    ftdiDeviceList[i].Type,
    ftdiDeviceList[i].ID,
    ftdiDeviceList[i].LocId,
    ftdiDeviceList[i].SerialNumber,
    ftdiDeviceList[i].Description));
}

And thats all! As a side node - for my app I had to implement this as a infinite loop - watching for a new connections. It was something like this:

 var deviceFinder = new BackgroundWorker();
 deviceFinder.DoWork += (ev, sn) =>
 {
    UInt32 prevDeviceCount = 0;
    var monitor = new FTDI();

    while (true)
    {
        UInt32 newDeviceCount = 0;
        var status = monitor.GetNumberOfDevices(ref newDeviceCount);
        if (status != FTDI.FT_STATUS.FT_OK) continue;

        var dDevice = newDeviceCount - prevDeviceCount;
        if (dDevice != 0)
        {
            UpdateList(); //updates that _devices List
        }

        prevDeviceCount = newDeviceCount;

        Thread.Sleep(5000);
    }
};
deviceFinder.RunWorkerAsync();