I get loads of traffic and email thanks the Ribbon. It’s truly an alpha project that went completely beyond what I had imagined. It’s exciting of course, but as much as it should give me a warm fuzzy feeling, it actually makes me rather nauseous. The thing is, a lot of the code is a total mess, it’s embarrassing, and I’ll freely admit it. It was never written as a “thought out” project, it was simply written as a not-so-quick “test” to see if it was possible to do a Ribbon component in pure SWT. The answer was “kind of”, and that’s where it got left off.
After having worked with Microsoft Silverlight and XAML lately I realized how simple XAML makes it to do stuff that I spent hours writing code for in the Ribbon. For example; rounded corners, gradient borders, multi-gradient fills, etc. It’s a pleasure to work with, easy to change, and it moves you forward at a good pace – and that’s how I feel it should be. I doubt anyone would download the Ribbon source as it is now, look at the render classes and think “great, I’ll make a change riiiight here”, it’s just too complicated and non-customizable. A user wanting to change colors would have to spend hours, days even, just to figure out what pixel is drawn where, and when I look at the code now, I get the same feelings.
Instead of continuing work on the Ribbon to finish “the mess”, I decided that if it was ever going to finish the project and take it out of Alpha, I was going to do all the rendering stuff the right way. And thus started a little test project with Brushes with the intention that if I could make things as easy as writing XAML, I could write all the Ribbon widgets in no-time.
A Brush object in XAML (and in a lot of other languages) is like a paint brush, it defines a direction and a stroke, colors, etc. The Ribbon basically uses a whole ton of Brushes.
Here’s a quick list of the things that the Ribbon currently uses lots of:
- 2-color and Multi-color gradients in Horizontal and Vertical orientation, there are no diagonal or radial gradients (although brushes in XAML support any angle).
- “Border” controls that contain other controls (pretty much all controls have some sort of border around them). A Border control in WPF is a control that can contain only 1 other control, and it can display a border and a background and allows for rounding of the borders.
- Various paddings and margins between controls to space them out between one another.
- Colors, colors and more colors.
If you think about it, that’s pretty much any control in an normal GUI library as at some level of the OS, there will be a programmatic “pen” that is drawing everything you see, pixel by pixel. Everything built on top of that is convenience to the user. And how many people know how to programatically draw a gradient between 2 colors if there isn’t an API to do it with? Probably very few.
(If you want to see a fully functional Ribbon in Silverlight (built on XAML) do a google search for “SandRibbon”. I’d link to it here but it’s commercial and costs a fortune, so I don’t feel like promoting it with a cross-link.)
My “Pseudo” code that I started from to “realize” this was the following:
LinearGradientBrush yrb = new LinearGradientBrush(new DPoint(0.5, 0), new DPoint(0.5, 1)); yrb.addGradientStop(new AlphaColor("#FFfff900"), 0); yrb.addGradientStop(new AlphaColor("#FFf90005"), 0.5); yrb.addGradientStop(new AlphaColor("#FF1100ad"), 1);
It’s very WPF/XAML, and what it says is “Create a brush that will draw a gradient vertically (each DPoint is x/y coordinates of how the fill should draw), add 3 color stops, one at 0%, one at 50% and one at 100%”.
XAML, by the way, for a typical LinearGradientBrush looks like this:<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0" x:Key="ToolbarButtonBackgroundStrokeNormal"> <GradientStop Color="#FFfff900" Offset="0"/> <GradientStop Color="#FFf90005" Offset="0.5"/> <GradientStop Color="#FF1100ad" Offset="1"/> </LinearGradientBrush>
If we were to do this in Photoshop it’d look exactly like this (on a 400×400 canvas):
To support multi-color gradients when using pattern fills (as SWT doesn’t), I had to write some code that split each section that I intend to fill into smaller rectangles and fill each one. Each section is 2 colors. So in the above case, we have 2 patterns we need to create, one from 0% to 50% and one from 50% to 100%. In the picture this would be from 1 to 2, and from 2 to 3.
Implementing it wasn’t that hard, and despite the code not being that long I think it’s easier you look at the source itself if you want the details as it’s a bit spread out. The source is at the bottom of the article.
A SWT Border Control
The “Border” control in WPF takes a few interesting attributes. One is the Brush that is used to fill the border, the second is the Brush used to fill the background. Then it allows for a setting the border “roundness”, the padding and the margin, and a few other things. All of that is mostly getters/setters and in the end we will have a
GraphicsObjectRendererclass that deals with actually drawing it. If there was a border control in SWT it might look a bit like this:Border b = new Border(parentControl); b.setBorderBrush(ourBorderBrush); b.setFillBrush(ourFillBrush); b.setBorderThickness(1); b.setCornerRadius(20); b.setWidth(100); b.setHeight(100); b.setMargin(new Margin(50));
Pretty straight forward.
The result from running this code looks something like this:
Oh no, Issues…
This is how far I got before I hit a wall. I actually started writing the article as I wrote the code because I was quite excited how seemingly well it worked. So what halted me? A few bugs in SWT. Normally a lot of things can be worked around if they don’t work 100% as intended, however, the bugs I came across were not of that type. The main issues are as follows:
- Pattern fills in SWT start drawing the pattern in the wrong place. I’ve filed a bug for this which can be found here. This one is rather critical as most of the brush drawing depends on it.
- A rounded rectangle is not drawn in the same way as a round rectangle, causing the border around it to be in weird places around the rectangle itself. An old bug is filed here.
- Other GC draw-related bugs.
Probably not the conclusion you or I wanted, but as I see it, I cannot really continue this project until the bugs are fixed (or worked around), and considering GC-related bugs are probably low on the fix-list, the Ribbon will have to sleep for a while longer, along with this project. AWT has a very good
MultipleGradientPaint implementation and I successfully manged to convert AWT drawings to SWT, but the AWT code is rather slow and converting it over is also slow, so it’s not really an option.
If you want to play with the code I have so far, you can download it here. Under the “test” package directory you’ll find various classes.
BorderTester.java is the main test class. The code is truly “as is”, it’s just zipped up from what I had at the time I ran into issues.