FireGestures is one of my favourite Firefox extensions. I have become so accustomed to it of late that I now find web browsing without gesture navigation hopelessly slow.

I have been thinking that it would be nice to start baking gesture navigation into my own applications. Clearly, this is not the kind of feature you would want to shoehorn into any application, but used sparingly and where appropriate it is a great way of short-cutting user navigation and providing a more user-friendly UI experience.

It just so happened that I was working on a WPF application at the time which I felt would benefit from this feature, so this seemed like a good time to start writing my own gesture navigation implementation.

I began by encapsulating a gesture within a class. A gesture, after all, is simply a set of mouse directional movements, carried out in a specific order. As in FireGestures, I am only interested in 4 different gesture directions: up, down, left, right. I captured these in an enumeration named Direction- then implemented the Gesture class itself as a generic list of directions (i.e. a single gesture is a collection of directions). I also overrode the equals and not equal operators for good measure:

   1: public class Gesture : List<Direction>, IEquatable<Gesture>
   2: {
   3:  
   4:     public Gesture()
   5:     {
   6:     }
   7:  
   8:     /// <summary>
   9:     /// Overloaded equality operator.
  10:     /// </summary>
  11:     /// <param name="g1"></param>
  12:     /// <param name="g2"></param>
  13:     /// <returns></returns>
  14:     public static bool operator ==(Gesture g1, Gesture g2)
  15:     {
  16:         Gesture g = g1 ?? g2;
  17:  
  18:         if (object.ReferenceEquals(g, null))
  19:             return true;
  20:  
  21:         if (object.ReferenceEquals(g1, null) || object.ReferenceEquals(g2, null))
  22:             return false;
  23:  
  24:         return g1.Equals(g2);
  25:     }
  26:  
  27:     /// <summary>
  28:     /// Overloaded inequality operator.
  29:     /// </summary>
  30:     /// <param name="g1"></param>
  31:     /// <param name="g2"></param>
  32:     /// <returns></returns>
  33:     public static bool operator !=(Gesture g1, Gesture g2)
  34:     {
  35:         return !(g1 == g2);
  36:     }
  37:  
  38:     /// <summary>
  39:     /// Parses a string command representation to a gesture object.
  40:     /// </summary>
  41:     /// <param name="value"></param>
  42:     /// <returns></returns>
  43:     public static Gesture Parse(string value)
  44:     {
  45:         if (value == null) return null;
  46:  
  47:         Gesture g = new Gesture();
  48:  
  49:         char[] chars = value.ToCharArray();
  50:  
  51:         foreach (char c in chars)
  52:         {
  53:             switch (c.ToString().ToUpper())
  54:             {
  55:                 case "L":
  56:                     g.Add(Direction.Left);
  57:                     break;
  58:                 case "U":
  59:                     g.Add(Direction.Up);
  60:                     break;
  61:                 case "D":
  62:                     g.Add(Direction.Down);
  63:                     break;
  64:                 case "R":
  65:                     g.Add(Direction.Right);
  66:                     break;
  67:                 default:
  68:                     throw new ArgumentOutOfRangeException(string.Format(
  69:                         "The character value '{0}' could not be converted to a gesture direction.", c.ToString()));
  70:             }
  71:         }
  72:  
  73:         return g;
  74:     }
  75:  
  76:     #region IEquatable<Gesture> Members
  77:  
  78:     public bool Equals(Gesture other)
  79:     {
  80:         if (other == null || this.Count != other.Count) return false;
  81:  
  82:         bool equal = true;
  83:  
  84:         for (int i = 0; i < this.Count; i++)
  85:         {
  86:             if (this[i] != other[i])
  87:             {
  88:                 equal = false;
  89:                 break;
  90:             }
  91:         }
  92:  
  93:         return equal;
  94:     }
  95:  
  96:     #endregion
  97:  
  98:     public override int GetHashCode()
  99:     {
 100:         return this.ToString().GetHashCode();
 101:     }
 102:  
 103:     public override bool Equals(object obj)
 104:     {
 105:         if (obj == null || !(obj is Gesture))
 106:             return false;
 107:  
 108:         return this.Equals((Gesture)obj);
 109:     }
 110:  
 111:     public override string ToString()
 112:     {
 113:         return this.ToString(string.Empty);
 114:     }
 115:  
 116:     public string ToString(string delimiter)
 117:     {
 118:         StringBuilder text = new StringBuilder();
 119:         foreach (Direction dir in this)
 120:         {
 121:             if (text.Length > 0) text.Append(delimiter);
 122:             text.Append(dir.ToString().Substring(0, 1));
 123:         }
 124:         return text.ToString();
 125:     }
 126: }

The backbone of the entire application is the IGestureProvider interface, and its implementing class: GestureProvider. The purpose of the GestureProvider class is to capture mouse locations, and return a Gesture object when the gesture is completed. The interface is structured as follows:

   1: public interface IGestureProvider
   2: {
   3:  
   4:     /// <summary>
   5:     /// The event raised when a direction change is detected.
   6:     /// </summary>
   7:     event GestureEventHandler GestureDirectionChanged;
   8:  
   9:     /// <summary>
  10:     /// Gets a value indicating if the provider is currently in point capture mode.
  11:     /// </summary>
  12:     bool IsCapturing { get; }
  13:  
  14:     /// <summary>
  15:     /// Starts a new capture.
  16:     /// </summary>
  17:     /// <param name="startPoint"></param>
  18:     void StartCapture(Point startPoint);
  19:  
  20:     /// <summary>
  21:     /// Ends the current capture and returns the completed gesture.
  22:     /// </summary>
  23:     /// <returns></returns>
  24:     Gesture StopCapture();
  25:  
  26:     /// <summary>
  27:     /// Captures a point.
  28:     /// </summary>
  29:     /// <param name="point"></param>
  30:     void CapturePoint(Point point);
  31:  
  32: }

So, the idea is: the caller calls StartCapture, passing in a start location, and then calls CapturePoint for each location in the gesture they wish to capture. Each time the direction of the gesture changes, the GestureDirectionChanged event is raised. Finally, when the gesture is complete, the StopCapture method is called, and an object representing the resulting gesture is returned.

The calculation of direction itself is pretty straightforward. Any mouse movement that is heading roughly left (between 225 and 270 degrees from origin) = left, any mouse movement heading roughly right (between 45 and 135 degrees from origin) = right.

The next step was to create a WPF custom control which uses the GestureProvider class, and provides the user interface onto which the gesture will be drawn. This was achieved by deriving from the WPF Canvas class, overriding the OnPreviewMouseRightButtonDown and OnMouseMove methods, and passing the mouse locations while the mouse right button is pressed to an instance of the GestureProvider class.

The final task was to create a test application which ties all of this together. Ideally, I would have liked to have created a WPF FireFox replica, using the WPF tab control and web browser control. Unfortunately, this proved to be rather more tricky than I had bargained for.

The WPF web browser control is basically a WPF wrapper around a COM component. The WinForms web browser control was also a wrapper around the same COM component, and it sucked. I had high hopes for the WPF control, but in all honestly, it sucks too.

The main problem is that of airspace, which in short means that WPF can't draw on top of COM or WinForms controls. I'm sure there are plenty of canny component developers who are already hard at work on a native WPF web browser control, but using the built-in WPF there's no good way to do what I wanted. So, I instead settled for a 'browser-like' application in which you can enter the URL of the page you want to access, and the application retrieves and displays the raw HTML in a textbox...not a particularly useful bit of functionality, but is sufficient to illustrate the point.

In the test application, I have mapped 4 simple gestures: up, down, left and right. I could have extended the example by including some compound gestures i.e. Right-Down, Up-Left etc, and may do this at some point just to demonstrate that the GestureProvider is not limited to single-direction mouse gestures. The gestures are mapped as follows:

  • Up: opens a new tab.
  • Down: closes the current tab.
  • Left: navigates back through the 'browser' history.
  • Right: navigates forward though the 'browser' history.

The source code can be retrieved from the link at the top of the article- feel free to download and play with it.