Dock: More technical details on scaling, XInputShape, and icon highlighting

The dock, as of March 18th, 2007


While that beautiful scaling looks like it would be hard to calculate, it is actually super easy. At first glance, the curve created by the function looks like a simple Gaussian Distribution and believe it or not, that is exactly what it is. The base function for Gaussian Distribution is y = exp(-x^2), and the modified form for this is actually size[i] = normal_size + (big_size – normal_size) * exp (-((pointer.X – (x[i] + size[i]/2))^2). Since x[i] = sum(size[k]) for k = 0..i-1, and size[i] is dependent on itself, all we have to do is perform this calculation iteratively until the values converge (with a reasonable tolerance).

private void CalculateIconSizes (ref double [] sizes, double x, double pointer)
  double x_current = x;
  for (int i = 0; i < sizes.Length; i ++)
     while (true)
        double old_size = sizes [i];
        sizes [i] = StandardSize + (HoverSize - StandardSize) * Math.Exp (-Math.Pow ((pointer - (x_current + sizes [i] / 2)) / HoverSize, 2));
        if (Math.Abs (sizes [i] - old_size) <= 0.25)
     x_current += sizes [i];


InputShapeMask is a very nice feature, but it has a few weeknesses. The first thing that screwed me is that it doesn’t work with override redirected windows. In retrospect, that makes sense, but I don’t like it. The second thing is that you should not do any more masking than you need to. For example, if you wanted to mask an area like this:

Mask for entire window

The easiest thing to do would just be to make a pixmap with the same dimensions as the window, draw your rectangles, and set the mask. Unfortunately, this causes a funny problem. When you move over the window, in the hidden area, sometimes it focuses for a split second, causing all sorts of problems and annoyances. To overcome this, you should only render a mask for the area you’re interested in. For example, since you’re only interested in:

Region that needs masking

What I do is loop through all the rectangles finding the top-most, bottom-most, left-most, and right-most points, then create a pixmap (bottom – top) tall and (right – left) wide, rendering the pixmaps onto it offsetted by (top) and (left) so the end result looks like this:

Actual pixmap to use

Then I use win.InputShapeCombineMask (pixmap, left, top); to get it in the right place.

      private void DrawInputMask (int width, int height, Cairo.Rectangle [] rects)
         int top = height;
         int bottom = 0;
         int right = 0;
         int left = width;

         foreach (Cairo.Rectangle rect in rects)
            if (rect.X  right)  right  = (int) Math.Ceiling (rect.X + rect.Width);
            if (rect.Y + rect.Height > bottom) bottom = (int) Math.Ceiling (rect.Y + rect.Height);

         Gdk.Pixmap pixmap = new Gdk.Pixmap (null, right - left, bottom - top, 1);
         Cairo.Context context =  Gdk.CairoHelper.Create (pixmap);
         foreach (Cairo.Rectangle rect in rects)
            context.Rectangle (rect.X - left, rect.Y - top, rect.Width, rect.Height);
         context.FillPreserve ();
         context.Stroke ();
         InputShapeCombineMask (pixmap, left, top);

Icon highlighting

Icon highlighting is probably one of the trickiest things to do. While it is relatively easy to render SVG onto a context, it is pretty tricky to highlight just the image. The following is my solution:

  1. Figure out which region you want to work with on an unscaled cairo-context. It is very important that you use an unscaled context to avoid pixilizing the icon you’re working with.
  2. Create a new similar surface from the context’s target. In C#, this is context.Target.CreateSimilar (Cairo.Content.ColorAlpha, width, height);
  3. Create a new context on the surface and save it.
  4. Do whatever icon rendering you want with the new context.
  5. Restore the context.
  6. Create a new rectangle over the entire canvas, using whatever color you want with whatever alpha may be appropriate.
  7. Use icon_context.MaskSurface (icon_context.Target, 0, 0); This function takes whatever you were about to draw, in this case a colored rectangle, and prints it using the alpha channel of the surface as a mask. Thus every pixel of image will be covered with color proportional to its transparency.
      private double DrawIcon (Icon icon, Cairo.Context context, double x, double y, double size)
         if (!LeftAligned) x -= size;
         if (!TopAligned)  y -= size;
         context.Save ();
         context.Translate (x + PaddingSize, y + PaddingSize);
         Cairo.Surface icon_surface = context.Target.CreateSimilar (Cairo.Content.ColorAlpha, (int) Math.Ceiling (size - 2*PaddingSize), (int) Math.Ceiling (size - 2*PaddingSize));
         Cairo.Context icon_context = new Cairo.Context (icon_surface);
         icon_context.Save ();
         icon_context.Scale ((size - 2*PaddingSize) / 100.0, (size - 2*PaddingSize) / 100.0);
         icon.Draw (icon_context, 0, 0);
         icon_context.Restore ();
         if (Mode == DisplayMode.Full && PointIsInRect (pointer, x, y, size, size))
            icon_context.Rectangle (0, 0, size - 2*PaddingSize, size - 2*PaddingSize);
            icon_context.Color = new Cairo.Color (1, 1, 1, 0.6);
            icon_context.MaskSurface (icon_surface, 0, 0);
         context.SetSourceSurface (icon_surface, 0, 0);
         context.Paint ();
         context.Restore ();
         return size;

Leave a Reply

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

You are commenting using your 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