Container secrets: size allocation, part 2

Right-to-left languages

As the first thing in this series, we add back support for right-to-left languages.

It may be a little surprising if you are only used to writing software in English, but GTK+ has traditionally tried to ‘do the right thing’ automatically for languages that are written from right to left like Hebrew. Concretely, that means that we interpret the starting position in horizontal layouts to be on the right in these languages, i.e. we ‘flip’ horizontal arrangements. This can of course be overridden, by setting the ::text-direction property of the container. By default, the text direction gets determined from the locale.

This is of course very easy to do. The code I showed in the first post assumes left-to-right order of the children. We keep it that way and just reorder the children when necessary. Note that the measure() code doesn’t need any change, since it does not depend on the order at all. So, we just change size_allocate():

if (text_direction == rtl) {
  child[0] = last;
  child[1] = center;
  child[2] = first;
} else {
  child[0] = first;
  child[1] = center;
  child[2] = last;
}

One small gotcha that I haven’t mentioned yet: CSS assumes that :first-child is always the leftmost element, regardless of text direction. So, when we are moving child widgets from the left side to the right side in RTL context, we need to reorder the corresponding CSS nodes when the text direction changes.

if (direction == GTK_TEXT_DIR_LTR) {
  first = gtk_widget_get_css_node (start_widget);
  last = gtk_widget_get_css_node (end_widget);
} else {
  first = gtk_widget_get_css_node (end_widget);
  last = gtk_widget_get_css_node (start_widget);
}

parent = gtk_widget_get_css_node (box);
gtk_css_node_insert_after (parent, first, NULL);
gtk_css_node_insert_before (parent, last, NULL);

Natural size

Since this was easy, we’ll press on and make our size allocation respect natural size too.

In GTK+ every widget has not just a minimum size, which is the smallest size that it can usefully present itself in, but also a preferred or natural size. The measure() function returns both of these sizes.

For natural size, we can do slightly better than the code we showed last time. Back then, we simply calculated the natural size of the box by adding up the natural sizes of all children. But we want to center the middle child, so lets ask for enough room to give all children their natural size and put the middle child in the center:

*natural = child2_nat + 2 * MAX (child1_nat, child3_nat);

The size_allocate() function needs more work, and here we have some decisions to make. With 3 children vying for the available space, and the extra constraint imposed by centering, we could prefer to give more space to the outer children, or make the center child bigger. Since centering is the defining feature of this widget, I went with the latter choice.

So, how much space can we give to the center widget ? We don’t want to make it smaller than the minimum size, or bigger than the natural size, and we need to leave at least the minimum required space for the outer children.

center_size = CLAMP (width - (left_min + right_min),
                     center_min, center_nat);

Next, lets figure out how much space we can give to the outer children. We obviously can’t hand out more than we have left after giving the center child its part, and we have to split the remaining space evenly in order for centering to work (which is what avail in the code below is about). Again, want to respect the childs minimum and natural size.

avail = MIN ( (width - center_size) / 2,
              width - (center_size + right_min));
left_size = CLAMP (avail, left_min, left_nat);

And similarly for the right child. After determining the child sizes, all that is left is to assign the positions in the same way we’ve seen in part one: put the outer children at the very left and right, then center the middle child and push it to the right or left to avoid overlap.

Expanded
Natural size
Below natural size
Smaller
Minimum size

References

  1. Container secrets, size allocation
  2. The code with these changes