[Previous] [Contents] [Index] [Next]

Geometry Management

This chapter discusses how to set up or fine-tune your widgets' geometry.

It includes:

Container widgets

A container widget is a child of the PtContainer widget class. Container widgets are the only widgets that can have children. Any widget that doesn't have a window of its own is always rendered within the boundaries of its parent. Only widgets belonging to a subclass of the PtWindow widget class get a window of their own.

Container widgets are responsible for performing geometry management. A container widget's primary responsibility is to position each child and size itself appropriately to accommodate all its children at their desired locations. The container may also impose size constraints on its children (for example, forcing them all to be the same size). The container must also constrain the children so that they don't appear outside the container's boundaries. This is normally done by clipping the child.

To understand how different containers handle geometry management, it's important to understand the geometry of a widget. See "Widget Geometry" in the Introduction to this guide.

Geometry negotiation

When a widget is realized, a geometry negotiation process is initiated in all the widgets in the widget family hierarchy. Each child of the widget is given the opportunity to calculate its size. This ripples down through all the widgets in the family, resulting in a calculation of the size of each of the descendants first.

Once each child has calculated its desired size, the parent widget may attempt to determine the layout for its children. The layout that the widget performs depends on:

If the application has specified a size for the widget, then it may choose to lay out the children using only that available space. This is influenced by the resize policy set for the widget. The Pt_ARG_RESIZE_FLAGS resource is a set of flags that determine the resizing policy for the widget. The flags specify a separate resizing policy for the width and height of the widget. If no policy is specified for either dimension, the widget won't attempt to resize itself in that dimension when performing the layout. Any other resize policy will allow the widget to grow in that dimension to accommodate its children. Resize policies are described in greater detail in the "Resize policy" section.

If the widget doesn't have a predetermined size, it will try to size itself to accommodate all the children using the appropriate layout policy. It does so by first attempting to determine a correct layout and then determining the space required to accommodate it.

The layout process determines the desired location of each child. The layout policy used by the widget controls how the layout will attempt to position the children relative to each other. It must take into account the dimensions of the children. The container is responsible for fixing the position of each child, so the layout policy may choose whether or not to take into account the position attributes of the children.

In performing the layout, the widget may also take the resize policy into account. Based on this policy, it determines whether it must adjust its width or height, or change the layout to account for space restrictions. The widget tries to choose a layout that best meets the constraints imposed by any size restrictions and by the layout policy.

After determining the desired position of each of its children, the widget calculates the width and height it needs to accommodate the children at these locations. It changes its dimensions, if necessary, to fit each of the children at the desired position. If this isn't possible because the resize policy doesn't allow it, the widget recalculates the positions to fit the children within the space available.

Once the layout is successfully established, the widget sets the position of each of the children by altering the child's position attribute.

Resize policy

Any change to a widget that may affect the amount of space required to display its contents can result in the widget's resizing itself to fit its contents. This is governed by the resize policy enforced by the widget.

The resize policy affects both basic widgets and containers. A container checks its resize policy when it lays out its children to determine whether it should resize itself to accommodate all the children at their desired locations. Through the geometry negotiation process, this effect is propagated up the widget family until the size of the window widget is determined.

The Pt_ARG_RESIZE_FLAGS resource controls the resize policy. This resource consists of a separate set of flags for the width and the height. The values of the flags determine the conditions under which the widget will recalculate the corresponding dimension. The values are checked whenever the widget is realized or its contents change.

The resize flags are as follows:

Pt_RESIZE_X_ALWAYS
Recalculates the widget's size whenever the value of the x dimension changes. This will grow or shrink the widget as its contents change.

For example, the following figure shows a button with the Pt_RESIZE_X_ALWAYS flag set as the label changes from Hello to Hello, world to Hi.

resize always

Pt_RESIZE_Y_ALWAYS
Recalculates the widget's size whenever the value of the y dimension changes. This will grow or shrink the widget as its contents change.
Pt_RESIZE_XY_ALWAYS
Recalculates the widget's size whenever the value of the x or y dimension changes. This will grow or shrink the widget as its contents change.
Note: The Pt_RESIZE_XY_ALWAYS flag isn't defined in PhAB. It's provided for your convenience when setting resize flags from your code.

Pt_RESIZE_X_AS_REQUIRED
Recalculates the the widget's size whenever the x dimension changes and will not fit within the current space available.

For example, the following figure shows a button with the Pt_RESIZE_X_AS_REQUIRED flag set as the label changes from Hello to Hello, world to Hi.

resize as required

Pt_RESIZE_Y_AS_REQUIRED
Recalculates the the widget's size whenever the y dimension changes and will not fit within the current space available.
Pt_RESIZE_XY_AS_REQUIRED
Recalculates the the widget's size whenever the x or y dimension changes and will not fit within the current space available.
Note: The Pt_RESIZE_XY_AS_REQUIRED flag isn't defined in PhAB. It's provided for your convenience when setting resize flags from your code.

These flags may also be modified by the values of another set of flags, namely:

If you set any of these "initial" flags, the widget doesn't resize in response to a change in the data - it will change its size only during the geometry negotiation process whenever it is realized. The widget will either make itself exactly the right size for its contents, or grow to fit its contents if the dimensions it has at the time aren't large enough.


Note: If none of the resize flags is set, the widget won't try to calculate its own dimensions, but will use whatever dimensions have been set by the application (thereby possibly clipping the widget's contents as a result).

For example, the following figure shows a button with no resize flags set as the label changes from Hello to Hello, world to Hi.

no resize policy


Setting the resize policy in PhAB

You can set these flags in PhAB by editing the container's resize flags, Pt_ARG_RESIZE_FLAGS, as shown below:

editing resize flags

Setting the resize policy in your application's code

You can also set the container's resize flags in your code, if required, using the method described in the Manipulating Resources in Application Code chapter.

Bit masks are provided for controlling which bits will be set. There's one bit mask for each of the x and y resize policies:

For example, to make a container grow to fit all its children if it isn't large enough when it's realized, set both the initial and required resize flags for x and y, by setting up the argument list as follows:

    PtSetArg(&arg[0], Pt_ARG_RESIZE_FLAGS,
        (Pt_RESIZE_XY_INITIAL|Pt_RESIZE_XY_AS_REQUIRED),
        Pt_RESIZE_X_BITS|Pt_RESIZE_Y_BITS);
    PtSetResources (ABW_my_container, 1, args);

To set up the argument list to clear the x resize policy:

    PtSetArg(&arg[0], Pt_ARG_RESIZE_FLAGS, Pt_FALSE, 
             Pt_RESIZE_X_BITS);
    PtSetResources (ABW_my_container, 1, args);

There are also some constants that simplify the setting of these flags. For example, there's a constant representing the bitmask for setting both the x and y flags simultaneously, and there are constants for each flag with the x or y shift applied. All these constants are defined in the <photon/PtWidget.h> header file.

Absolute positioning

The most basic form of layout a container can provide is to position its children without imposing any size or positioning constraints on them. In such a situation, a child widget is pinned to a particular location within the container, and the container doesn't change its size.

A widget employing this layout policy is somewhat analogous to a bulletin board. You can pin items to the bulletin board, and they stay wherever they're pinned. All container widgets can perform absolute positioning.

The easiest way to position and size each child is to use the mouse in PhAB.

To position each of the children from your application's code, you must set the Pt_ARG_POS resource for each child. If the widgets must be a consistent or predetermined size, you must also set the Pt_ARG_DIM resource for each child. The position you specify is relative to the upper left corner of the parent's canvas, so you can disregard the parent's margins when positioning children.

By default, all widgets that perform absolute positioning use a resize policy of Pt_AS_REQUIRED and Pt_INITIAL. In other words, the container's initial size is chosen when it's realized. The container is made large enough to fit all the children at their specified locations, given their size after they've been realized.

The simplest way to do absolute positioning is to place and position widgets within the main PtWindow widget of the application. If you need to create a container widget that does absolute positioning as part of another container, you can use a PtPane widget.

Aligning widgets using groups

PtGroup widgets are container-class widgets that can manage the geometry of their children. You'll find them useful for aligning widgets horizontally, vertically, or in a matrix. They also have the unique ability to stretch child widgets.

PhAB extends the usefulness of this widget class by turning it into an action-oriented "Group" command. Using this command, you can select several widgets within a module and group them together to form a single group widget. If you try to select any widget in the group by clicking on it, the entire group is selected instead.

When you select a group, the Control Panel shows the resources available to the PtGroup widget class. These include resources that allow you to align the widgets inside the group and to set up exclusive-selection behavior.

The PtGroup widget can be used to arrange a number of widgets in a row, column, or matrix. Several resources are used to control this, and they're interpreted slightly differently depending on the desired arrangement of the children.

Joining widgets into a group

To join widgets into a group:

  1. Select the widgets using either a bounding box or the "Shift and click" method (as described in the Creating Widgets in PhAB chapter).

    You should use "Shift-click" if you plan to align the widgets in order using the Orientation resource. The first widget you select will become first within the group. If order isn't important or alignment isn't required, the bounding box method will work just fine.

  2. Do one of the following:

    PhAB will group the widgets and select the group.

Accessing widgets in a group

Although PhAB treats a group as a single widget, you can still access any of the individual widgets that make up the group. To do this, use the Control Panel's next and previous buttons or module tree. For more info, see "Selecting widgets" in the Creating Widgets in PhAB chapter.

Aligning widgets horizontally or vertically

The orientation resource, Pt_ARG_GROUP_ORIENTATION, controls whether the group widget's children are arranged as rows or columns. The default value, Pt_GROUP_VERTICAL, causes the children to be arranged vertically. A value of Pt_GROUP_HORIZONTAL causes the children to be arranged horizontally.

You can control the amount of space to be left between the widgets arranged in the group widget by using the Pt_ARG_GROUP_SPACING resource. The value of the resource gives the number of pixels to be left between widgets.

The following example shows how several children are laid out if the default vertical orientation is used and a space of five pixels is left between children:

Figure showing 3 widgets arranged vertically

If the orientation is changed to horizontal, the group appears like this:

Figure showing 3 widgets arranged vertically

When first realized, the group widget initially sizes itself to be large enough to hold all the children after they've been arranged.

Aligning widgets in rows and columns

The group widget may also be used to layout children in both rows and columns for creating tables or spreadsheets by setting the value of Pt_ARG_GROUP_ROWS_COLS resource to some value other than one.

The interpretation of this resource depends on the orientation:

The last row or column may have fewer widgets than the others.

When the elements of a group are laid out in rows and columns, the widgets themselves may either be tightly packed or they may be spaced out equally as rows and/or columns. This is controlled by the Pt_ARG_GROUP_SPACING resource.

Using the Group flags

The PtGroup widget includes a set of flags, Pt_ARG_GROUP_FLAGS, that can be used to control how the child widgets can be selected, sized, and stretched:

Pt_GROUP_EXCLUSIVE
Allow only one child to be set at a time. This flag can be used to make a group of toggle buttons into radio buttons (that is, a set of mutually exclusive choices).
Pt_GROUP_EQUAL_SIZE
Lay out the widgets in a grid, using a cell size chosen by the group based on the width of the widest child and the height of the tallest child. The dimensions of all the children are set to this size when they're laid out.
Pt_GROUP_EQUAL_SIZE_HORIZONTAL
Make all the children the width of the widest one.
Pt_GROUP_EQUAL_SIZE_VERTICAL
Make all the children the height of the tallest one.
Pt_GROUP_NO_SELECT_ALLOWED
Set this flag for an exclusive group if it's valid not to have any child set. The user can unselect the currently set child by clicking on it again.
Pt_GROUP_NO_KEYS
Don't allow the user to move inside the group by pressing the arrow keys.
Pt_GROUP_NO_KEY_WRAP_HORIZONTAL
Don't wrap around to the other side of the group when using the left and right arrow keys.
Pt_GROUP_NO_KEY_WRAP_VERTICAL
Don't wrap around to the top or bottom of the group when using the up and down arrow keys.
Pt_GROUP_STRETCH_VERTICAL
Stretch the bottom row of widgets as the group expands.

stretch vertically

Pt_GROUP_STRETCH_HORIZONTAL
Stretch the right column of widgets as the group expands.

stretch horizontally

Pt_GROUP_STRETCH_FILL
Stretch the last widget(s) to fill the available space in the direction indicated by the orientation.

stretch to fill


Note: Don't set the Pt_GROUP_EQUAL_SIZE_... and Pt_GROUP_STRETCH_... flags for the same direction - the group will expand every time its extent is calculated.

For more information, see the description of the PtGroup widget in the Widget Reference.

Splitting apart a group

To split a group into its individual widgets:

  1. Select the group.
  2. Do one of the following:

    PhAB will split apart the widgets and remove the PtGroup container.

Constraint management using anchors

Here's a common layout situation that's not handled by any of the layout policies we've examined. Suppose a container is divided into a number of panes that have constraints on their size and position. Normally, we don't want the panes to overlap, and we want control over how the panes are resized if the container itself grows or shrinks. A constraint mechanism provides this control.

Anchors are provided as a constraint mechanism on the position and size of a scroll area, group widget, or pane widget used as a pane within another container. The position attribute and the anchors of each of the children will always be used to determine their positions.

An anchor may be specified for any side of a child widget. The anchor is attached to one of the sides of the parent. It maintains the corresponding side of the child at a fixed distance, the anchor offset from the anchoring side of the parent. The anchor offset may also be expressed as a proportion of the canvas width or height of the parent. Any time the parent is resized, the child's position (and possibly size) is altered to maintain this relationship. If any side of the child is not anchored to the parent, it's allowed to float freely.


Note: When using PhAB, you don't specify anchor offsets. Instead you position the widgets at the desired offset by setting the position (Pt_ARG_POS) and dimension (Pt_ARG_DIM) resources. PhAB calculates the anchor offsets automatically, based on the relative sizes and positions of the parent and the anchored child.

The width of the child widget is influenced by the anchors for its left and right sides; the height is influenced by the anchors for the top and bottom. If either of an opposing pair of edges is allowed to float, the constraints will be met by altering only the position of the widget in the corresponding dimension. This means that the widget may slide in any of the four directions to satisfy the anchor constraints. If both edges are anchored, the widget must be resized in that dimension as well.


Figure showing anchoring


Example of anchoring.



Note: Anchor size determination takes precedence over the resize policy or any other resize mechanism in place.

Creating an application's main window provides a simple example of using anchor resources. The window commonly has at least two parts to it: the menu bar and the work area. If we consider an application that has a group widget for the work area, we can identify the types of anchors necessary to make it resize correctly in response to a change in the size of the window widget.

Each edge of the work area is anchored to the corresponding edge of the window. The left and top anchor offsets are set to be the same as the position attribute for the widget. This must be calculated to sit below the menu bar. The dimensions of the widget are set to the desired amount of work space.

When realized, the window positions the work area at the location specified by its position attribute. The window's size is set to be large enough to contain the work area.

If the window is resized, the width and height of the work area are resized accordingly, since all the edges are anchored. If the anchor offsets were specified correctly, the position of the widget won't be altered.

We don't have to do anything for the menu bar, because it's automatically anchored to the top and sides of the window.

Anchor resources

The Pt_ARG_ANCHOR_FLAGS resource controls the anchor attachments. Within the container flags, there are three flags associated with each edge of the widget for anchoring purposes.

The three flags available for each edge allow that edge to be anchored in one of three possible ways:

Each of these flags follows a consistent naming scheme:

Pt_edge_ANCHORED_anchor

where

edge
is the name of the edge to be anchored, and must be TOP, LEFT, RIGHT, or BOTTOM.
anchor
is the name of the parent edge it's to be anchored to, or RELATIVE for a proportional anchor.

Thus, the following flags are defined:

A proportional anchor specifies the edge's position as a percentage of one of the parent widget's dimensions. The left or right edges are specified as a proportion of the parent's width, and the top and bottom edges are specified as a proportion of the parent's height. The proportion is provided by the anchor offset and is expressed in tenths of a percentage point.

Setting anchor flags in PhAB

To set the anchor flags, click on the anchor flags (Pt_ARG_ANCHOR_FLAGS) resource and use PhAB's flag editor:

Editing anchor flags

Setting anchor flags in your application's code

You can also set these flags from your code, using the method described in the Manipulating Resources in Application Code chapter. For convenience, each set of flags has an associated bit mask:

So to set the left and right edge for our menu bar in the example above, the argument list element would be initialized as follows:

    PtSetArg(&arg[n], Pt_ARG_ANCHOR_FLAGS,
        Pt_LEFT_ANCHORED_LEFT|Pt_RIGHT_ANCHORED_RIGHT|
        Pt_TOP_ANCHORED_TOP,
        Pt_LEFT_IS_ANCHORED|Pt_RIGHT_IS_ANCHORED|
        Pt_TOP_IS_ANCHORED);

When setting anchor flags from your application's code, all the anchor offsets are specified using the Pt_ARG_ANCHOR_OFFSETS resource. This resource takes a PhRect_t as a value. The upper left corner of the rectangle is used to specify the anchor offset for the top and left edges of the widget, and the lower right corner of the rectangle indicates the anchor offset for the right and bottom edges.

So, for example, to make the work area 90% of the width of the window with equal space on either side, the left and right edges are anchored using the following:

    PhRect_t offsets;
    offsets.ul.x = 50;
    offsets.lr.x = 950;
    PtSetArg(&arg[n], Pt_ARG_ANCHOR_FLAGS,
        Pt_LEFT_ANCHORED_RELATIVE|
        Pt_RIGHT_ANCHORED_RELATIVE,
        Pt_LEFT_IS_ANCHORED|Pt_RIGHT_IS_ANCHORED);
    PtSetArg(&arg[n+1], Pt_ARG_ANCHOR_OFFSETS, &offsets, 0);

Enforcing position or size constraints without anchors

If you wish to maintain more complex relationships among the positions of children relative to the container, or relative to each other, you must catch resize events for the container. The PtContainer widget class provides a resize callback, Pt_CB_RESIZE, that you can use for this purpose.

The cbdata member of the PtCallbackInfo_t structure is a pointer to a PtContainerCallback_t structure that contains at least the following:

PhRect_t old_size
the former size of the container
PhRect_t new_size
the new size of the container

[Previous] [Contents] [Index] [Next]