Programming

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

The dock, as of March 18th, 2007

Scaling:

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)
           break;
     }
     x_current += sizes [i];
  }
}

Shaping:

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

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