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++;
    }
}

No comments:

Post a Comment