Friday, June 1, 2012

Some farewell thoughts/code on widget layout/placement

A couple of releases ago, an object called Panel was introduced by me to VisualWorks. New UIs were put together with it, including among others: BundleOrderTools, PrereqTool, and new Change Tools, as well as the new Skinny widgets.

Panel was a hard swing away from the traditional VisualWorks layout facilities. It took the position that layout was entirely a container responsibility (whereas the traditional framework puts a lot of emphasis on encapsulating the layout parameters of a widget with it on a one-to-one basis). The advantage of doing it this way, was that you could build layout algorithms that took into account the interplay between the different children better. It was a hard swing, because rather than a rich set of as-yet-understood-ideas, I made egregious use of blocks to pull it off.

A couple of months back (maybe 6 even), I sat down and began playing with some ideas that were a little more "half way." Having done VisualWorks for many years, having explored ideas with Panel, I was interested in addressing the following ideas:

  • Neither Panel nor CompositePart have a good way of differentiating between what their ideally composed size would be from what their layout ended up being. In other words, the preferredExtent of a Composite is just whatever it ended up laying things out as.
  • While various widgets in the system can answer preferredHeight/preferredBounds/preferredWidth/preferredExtent, they don't deal with the need to some times set or tune these values on a per instance basis
  • 95% of layouts are follow one or two axes (e.g. i want a "row" of buttons)
The unfinished product of this is published as WidgetRowsColumnsAndWeaves, replicated into the OR.

It has two primary types of classes in it. One set is the ViewStripe and ViewWeave classes. ViewStripe is a one axis layout container. It can be configured as a row or a column. And ViewWeave is a two axis container, what some might think of as a LayoutGridBag or whatever Java calls those things.

The other half of the classes, are the EdgePlacement classes. These are the worker objects that a ViewStripe or ViewWeave might use to place it's child widgets. They make heavy use of the RectangleEdge classes that were integrated in VisualWorks 7.9.

One of the things I realized when working on this, was that when I think programmatically about widget placement, I *don't* think in rectangles. A widget's frame may be a rectangle, but I don't compose them that way. I think in axes. I might a though process that goes something like "I need a row of widgets. Vertically, I want them all to to hang from the top, inset down by 2 pixels. And horizontally, I want them evenly distributed, so that they're all the same size, with a gap of 4 between them, and edge insets of 5." See how the reasoning is about the axes separately?

So when you configure the layout of a VisualStripe or a ViewWeave, you see messages like

row leftRight stretchAll
row topBottom alignTop

The leftRight or topBottom message will return either a SingleCellPlacememt or a MultiCellPlacement depending on how it's configured.

There are a variety of tests and examples in the code that are worth perusing. Here's a piece of the ViewWeave exampleTicTacToe method:

me leftRight stretchAllCells.
me leftRight perCell alignCenter.
me topBottom stretchAllCells.
me topBottom perCell alignCenter.

That single "two-scrollbar-long" method is able to define a complete TicTacToe game. There's also an exampleCalendar which actually does quite a bit more with the features in there.

I used properties a bit to pull some of this off. You can attach a #cellWidth or #cellHeight property to a widget that can be used to tune it's cell size along an axis. You can do things like set #cellBackground and #cellBorder as well.

Over all, I was somewhat pleased with this. I regret that I won't be staying around to see it to completion. Maybe someone will pick it up and run with it.