Angry Red PlanetLayout Library |
|
This layout library was started back in the days of DR8. It didn't change for a long time after that first prototype because, until R4, manually resizing BViews has been too slow to (IMHO) make a layout library based on them practical. However, now with the coming of R4 and its new Begin/EndViewTransaction() mechanism -- as well as the fact that there still doesn't seem to be a layout library that completely meets our needs -- I have cleaned up the code so that it is semi-usable (at least for my needs), and decided to release what I have as open source.
The main features/goals of this library are:
Be sure to look at the source code in ArpTest/ArpLayout
to get a feel for one (possible) way to use the library!
The ArpLayout library implements a traditional "layout manager / control" GUI architecture. That is, there are two fairly distinct types of classes:
These are the parts of the interface that the user interacts with. A control always a BView, because it needs to see user events such as the keyboard and mouse to provide something the user can interact with.
In addition to all of the functionality of a BView, a control must be able to report accurate information about its dimensions based on its current font and other parts of the environment. This is similar to the existing BView::GetPreferredSize() method, but includes a bit more information and must return a useful value, whatever the object's current state.
(Unfortunately, the GetPreferredSize() implemented in a lot of the Be-supplied classes is either touchy about returning correct information or doesn't return anything useful at all.)
These are the objects used to construct an actual layout hierarchy. A layout manager contains one or more child objects, and implements a specific algorithm to determine where the children should be placed within the manager's screen space (i.e. its Bounds() rectangle) and how big they should be. Ideally, the library architecture should allow a layout manager class to implement any arbitrary layout algorithm it likes.
The children of a layout manager may be either basic controls or other layout managers. Building a tree of layout managers and controls defines an explicit relationship between the various user interface objects, so that they will automatically be correctly positioned based on the font(s) being used and the size of the containing area.
A layout manager is often not associated with an actual BView -- it does not need to directly interact with the user, so there is no need to include all of that overhead. Two layout managers currently come with the ArpLayout library, ArpRunningBar and ArpScrollArea.
The core piece of the ArpLayout library is a class called ArpLayoutable. This is a simple base class (derived only from BArchivable) that defines the abstract interface between parent and child in the layout hierarchy. Its major pieces of functionality are:
At the most fundamental level, ArpLayoutable provides a set of functions for managing a hierarchy of object. These directly match the ones found in BView:
AddLayoutChild() AddChild() RemoveLayoutChild() RemoveChild() LayoutParent() Parent() CountLayoutChildren() CountChildren() LayoutChildAt() ChildAt() NextLayoutSibling() NextSibling() PreviousLayoutSibling() PreviousSibling() LayoutRemoveSelf() LayoutRemoveSelf() FindLayoutable() FindView()
This is an important point: The ArpLayoutable class defines an additional hierarchy, on top of the existing BView hierarchy. This relationship is easy to derive -- the underlying BView hierarchy is simply the ArpLayoutable hierarchy with all of the objects that do not have an associated BView object removed.
Another way to think of this is as light-weight ArpLayoutable objects sitting "in-between" the full BViews, guiding them in the layout. For example, consider a simple BView hierarchy such as this:
BView Root -+- BMenuBar +- BTextView +- BBox ------+- BButton +- BButton +- BButton
This may be embedded within a larger ArpLayoutable hierarchy that runs like this:
View/Layout Root - VerticalGroup -+- Wrap - BMenuBar +- Wrap - BTextView +- Wrap - BBox --- HorizontalGroup -+- Wrap - BButton +- Wrap - BButton +- Wrap - BButton
Which would probably result in a window that looks something like this:
_____________ /Layout Window\_____________________________ | | | BMenuBar | |-------------------------------------------| | | | | | BTextView | | | | | |-------------------------------------------| | | | /--- BBox ------------------------------\ | | | | | | | +---------+ +---------+ +---------+ | | | | | BButton | | BButton | | BButton | | | | | +---------+ +---------+ +---------+ | | | | | | | \---------------------------------------/ | | | |___________________________________________|
A few things to note:
Wrapping is an important operation. It is accomplished with the ArpViewWrapper class, which is-a ArpLayoutable that knows how to do useful things with a raw BView. In particular, it can massage the BView in such a way that a number of the Be-supplied controls will return useful information from GetPreferredSize(), and then fill this information into the ArpLayoutable dimensions. It also uses special functions of ArpLayoutable to mark its object as one that owns a BView, so that the ArpLayoutable will do the correct thing with coordinate spaces and other details.
The ArpViewWrapper can be used in one of two ways. The simplest is to instantiate it, passing in an instance of the BView it is to wrap in its constructor. This will magically include the BView in the layout hierarchy (assuming it reports a useful preferred size). Alternatively, one can create a subclass that is a mix-in of the ArpViewWrapper and BView-based object it is associated with. This method is used in places where special code must be written to report size information about the BView or otherwise modify its default behavior.
In order to perform any kind of complicated layout operation, a layout manager will need to associate some special information with its children. This is additional information attached to a specific child, which is used to control how it is placed in the container's area. Examples of constraints are such things as a "weight", which controls how much of the parent's space is given to one child in relation to its others, or "gravity", which determines how a child will be placed in a larger area that it does not need.
Constraints are a difficult issue because they have two conflicting needs: the child must own the constraints, but only the parent knows what they are.
ArpLayout solves this problem by implementing constraints as a BMessage. Every ArpLayoutable includes a BMessage that contains its constraints. The ArpLayoutable object owns this BMessage object as part of its data, but doesn't touch it: instead, there is an interface for its parent to retrieve it and examine or modify it as needed.
The ArpLayoutable class includes an additional capability, the management of "parameters" associated with a single object and "globals" associated with an entire layout hierarchy.
Note: this is NOT an intrinsic part of the layout architecture. In fact, I am tempted to remove it -- it adds a lot of additional complexity to the library that could just be dropped and, as a first prototype of an idea, I am not sure that I am happy with how it turned out. (I am particularily not satisfied with how poorly the Be-supplied controls react to the dynamic environment I was trying to create, that is another story...)
On the other hand, this does make it quite a bit easier to write an example program that shows off how the ArpLayout library works. So for now, I am leaving it in... but I am not going to include any more real documentation on it. The ArpLayoutable class header includes fairly extensive comments on how these two things work, if you are interested.
This is the base class for the layout architecture, defining the interaction between objects in a hierarchy. It is not intended to be directly instantiated; instead, you will want to use one of the pre-built subclasses, or make your own to implement either a layout manager or, combined with a BView, a control.
The ArpLayoutable header file is extensively commented, so that is currently the place to go for information on this class.
ArpLView is-a mix-in of a BView and an ArpViewWrapper. Essentially, it is the moral equivalent of BView, but for classes that want to implement controls that automatically work with the ArpLayout architecture. When implementing a subclass, you should implement all of the normal BView stuff (except GetPreferredSize()), as well as the ArpLayoutable methods you need to override. In particular, you should always override ComputeDimens() to return the appropriate dimensions of your object. Layout managers also must override Layout().
ArpRootLayout is-a ArpLView that is specialized to be the root of a layout hierarchy. There are four important parts to this:
An ArpRootLayout can only contain one child. This is typically a manager widget that knows how to place multiple children in its area.
This is the only general-purpose manager widget that is currently included with the ArpLayout library. It simply places all of its children next to each other, in either a horizontal or vertical direction. When the running bar has extra space that goes beyond the childrens' preferred size, it will distribute this amongst them according to a weighting factor: each child is given a single float weight; the amount of space it gets is directly proportional to its weight relative to its other children. If a child's weight is zero, its size is never grown beyond its preferred size.
For example, consider three children with weights (0, 1, and 2). The running bar reports it preferred size to be that of the sum of its children sizes, so that it is always given enough space to contain them. If it is made larger than that, it will distribute any extra space beyond that summed preferred size based on their weights. Say we add 60 pixels to its size. The additional space assigned the children is, in order, (0, 20, 40).
ArpScrollArea is a specialized layout manager for a child that will be placed within a larger scrollable area. It is a complete reimplementation of the BScrollArea, because BScrollArea does not work at all well with the ArpLayout library.
This layout manager can only have one child, which is the view to scroll around in. The child ArpLayoutable must have an associated BView object for it to attach to. This class uses normal BScrollBar objects as the controls around the scrollable view, and these require the BView for something to hold on to.
This is a simple subclass of ArpLayoutable that allows you to "wrap up" a raw BView-based object into an ArpLayoutable that the layout library can manipulate. The resulting combination (either as one instance holding another, or a mix-in of the two classes) can then be seamlessly used within a layout hierarchy.
This class also implements some special magic to try to get usable preferred size dimensions from Be's supplied control classes. However, very few of them actually work. The ones that I do know work (and hence don't require subclassing) are:
This is actually a file that contains a collection of stub classes to fix problems that keep some Be-supplied controls from working with the ArpLayout library. In other words, it does what we'd really like to avoid: subclasses Be objects with thin wrappers.
The following classes are included (their corresponding Be class should be easy to figure out):
A number of others will have to be created (I know, for example, that BStringView doesn't work by itself), but these are the only ones I have needed so far.
All files and code contained in this package are Copyright ©1998 Angry Red Planet, except where otherwise noted.
The files contained here-in are distrubuted under a modifed
form of the Artistic License.
See the file @License.html
or @License.txt
for a copy of this license.