A couple of posts ago, I hinted I might talk about super view layouts some more. This is not the exhaustive completion. But at least a little.
In just about all VisualWorks layout scenarios, we attach a specification of layout to a given view. These stick, because CompositePart, does it's best to honor those. It uses that information to decide where to place the object and honors its desire as near as it can. Not all layout scenarios fit this model well though. Take the new status bar in the 4xx RefactoringBrowser series. It has been made pluggable. In the browser's stock configuration, you get to two status views there. The nice layout is to fil the bar vertically, and split the bar 50/50 between the two views. If it remained static like this, we could specify these layouts using the fractional APIs of a LayoutFrame, or something like a FunctionalLayout. But if you load the latest SUnitToo(ls), suddenly there's 3 views in the status bar. The statically assigned layout of the two stock views would have to be adjusted.
This is a case where the super view wants to take complete control of the layout of subviews. Any layout information attached to subviews, becomes unimportant. Can we do this in VisualWorks? The answer is yes. And that it's very easy, allbeit a little hidden. Since we have a custom behavior for layout, making a subclass of CompositePart seems in order. Once we've done that we need only override one magical method:
computeDisplayBoxFor: aComponent inDisplayBox: aRectangle
This method gets called by the machinery of CompositePart each time it changes size. That new size is in the form of aRectangle. And then you can compute and assign any rectangle for a aComponent (subview) that you want as it's bounds.
The 4xx RefactoringBrowser does just this. It adds a class, HorizontalPanel, as a subclass of CompositePart. Then we can use said method to basically have all sub views fill from top to bottom, and be spread proportionally amongst themselves:
computeDisplayBoxFor: aComponent inDisplayBox: aRectangle
| componentIndex subViewWidth |
subViewWidth := aRectangle width / self components size.
componentIndex := self components indexOf: aComponent.
^(Rectangle
left: (componentIndex - 1) * subViewWidth
top: 0
width: subViewWidth
height: aRectangle height) translatedBy: aRectangle origin
That's it. Nothing more to it. I've used this little gem a couple times over the years to build custom layout things. Some that did proportional but non uniform grids. One that did like the above, but left padding on the ends AND was smart enough to do it either vertically or horizontally on the fly, based on whether aRectangle was tall and narrow or short and wide. One could do a "flow" layout. Or a clock layout. Or... whatever you can code really.
If we want to have it relayout whenever we add a new view on the fly, we can do as HorizontalPanel does with this override:
addComponent: aComponent
super addComponent: aComponent.
self layoutComponentsForBounds: self bounds
One subclass and two methods is all it took to make a proportional resizing horizontal panel for the status bar. Not bad.