Thursday, May 6, 2010

Quick and Easy Scalable Rounded Bevels

Too Much Photoshop
I was watching a UI design video yesterday where the presenter imported a Photoshop design into Blend and turned it into a Silverlight GUI. One thing I noticed was that he kept a lot of the raster layers from the Photoshop image as graphical elements inside the Silverlight app. If you have a carefully crafted design that uses filters and effects that aren't available in Blend then you have no choice if you want to retain the fidelity of the design mock-up. That usually means, though, that the Silverlight app is going to be at a fixed width/height to match the Photoshop design - since raster graphics don't usually scale well.

I don't like fixed width and height Silverlight apps. I kept thinking "there must be a way to do that in nothing but XAML". Some of the graphics were understandably left as raster since there would be no equivalent way to achieve the same result in Blend. But the elements that bothered me were usually simple panels or buttons that had a common visual theme - they had rounded corners, and they had some kind of bevel that made them appear raised or lowered. This is the sort of thing I mean:


Let's pretend it was a proof done in Photoshop (it was actually an old copy of Paint Shop Pro) and we brought it into Blend. The gradients are easy enough to do in Blend, but what about the shading around the edges of the buttons and panel, giving them a raised appearance?

A lot of the button styles that I've seen people blog about tend to follow 2 or 3 themes: glassy styles with inner glows and highlights; ellipses with radial gradients; or linear gradients. These buttons are either all elliptical buttons, or they don't bother trying to give rounded shading at the edges like the image above.

The problem is the corners. If it weren't for the rounded corners on those rectangles, you could just use thin rectangles hard up against each edge with an appropriate linear gradient. But even then you run into difficulty at the corners where those rectangles meet. There's no easy way to make the gradients from each edge's rectangle meet smoothly.

You could probably do it on a fixed size button with paths shaped specifically for the corners and edges, but then it wouldn't scale well.

This got me thinking about how to achieve the kind of look in the top image, in a way that scales to any kind of size, or roundedness. While the method described below doesn't have the flexibility of the filters and effects that you find in Photoshop, it is a fairly good approximation, and is easy to achieve.

Fuzzy Borders
The approach I came up with is to use Border controls with two sides missing, and the Blur effect applied to make it fuzzy. Here is a working example:


The idea is to use a Border control with a White BorderBrush, the top and left edge thickness set to 3, and the bottom and right edge thicknesses set to 0. Then add another Border control, but with a Black BorderBrush, and the opposite edges showing. The blur effect is set at 4 and the borders are offset by 1 (using the Margin) from the edges. The Opacity of the borders is down at about 30%. That's it - just two borders on top of whatever background you want. And it scales to any size.

The example above uses the same technique for the buttons and for the panel containing the bigger buttons. The only difference in the object tree for the buttons is that there are a couple of extra rectangles to manage States (eg MouseOver, Disabled etc). The buttons swap the colors from black to white and vice-versa for the MouseDown state, and offsets the ContentPresenter element too.

Here are the object trees for the panel and for the button:



You can see that I have copied the elements from the button template into the grid on the right to be used as a panel (should have renamed the element "PanelBorder" instead of "ButtonBorder").

You could also use this as a Control Template for a ContentControl and have a nice reusable 3D panel style.

Here are the files in case you want to play with it.

4 comments:

  1. nice writeup, and inventive approach - you may consider posting this on silverzine.com, seems like it would be a good fit..

    it is worth noting that if you CAN do a fixed size app, an image with these effects on them as part of the image will likely be many times more performant than applying blur effects yourself at runtime - the blur effect carries a pretty high performance hit in silverlight unfortunately

    ReplyDelete
  2. Hi SmartyP. Thanks for the comment.

    I agree, the blur is not very performant even though it's supposed to be executed on the GPU. Fortunately it's not a big deal for a button. It would be interesting to see if it's a big deal for a panel when it's size is being animated.

    ReplyDelete
  3. thanks for your excellent explanation.
    This is a very useful post, one of the first I've seen that actually shows how to create the bevel effect for rectangular types of objects in pure XAML.

    I believe that this approach should be extensible to path-type of objects as well!

    cheers,

    ReplyDelete
  4. Thanks for the feedback Herre. If you can make this work with paths too I would be quite interested. The rectangular shape of the border makes this approach simple because you only need to turn off two sides for each bevel Border. With a complex path I would imagine you would need several copies of the path broken into bits (depending on the shape of it) to get the right 3D look.

    ReplyDelete