I recently had an opportunity to reimplement size allocation for a container with all the things that GTK+ supports:
- RTL support
- Natural size
- Align and expand
- Height-for-width
- Orientation support
- Baselines
If you do a one-off container in an application, most of these may not matter, but in a general-purpose GTK+ widget, all of them are likely to become relevant sooner or later.
Since this is quite a lot of ground to cover, it will take a few posts to get through this. Lets get started!
The starting point
GtkCenterBox is a simple widget that can contain three child widgets – it is not a GtkContainer, at least not currently. In GTK+ 4, any widget can be a parent of other widgets. The layout that GtkCenterBox applies to its children is to center the middle child, as long as that is possible. This is functionality that GtkBox provides in GTK+ 3, but the center child handling complicates an already complex container. Therefore we are moving it into a separate widget in GTK+ 4.
When I started looking at the GtkCenterBox size allocation code, it was very simple. The two methods to look at are measure() and size_allocate().
The measure implementation was just measuring the three children, adding up the minimum and natural sizes in horizontal direction, and taking their maximum in vertical direction. In rough pseudo-code:
if (orientation == GTK_ORIENTATION_HORIZONTAL) { *minimum = child1_min + child2_min + child3_min; *natural = child1_nat + child2_nat + child3_nat; } else { *minimum = MAX(child1_min, child2_min, child3_min); *natural = MAX(child1_min, child2_min, child3_min); }
The size_allocate implementation was putting the first child to the left, the last child to the right, and then placed the middle child in the center, eliminating overlaps by pushing it to the right or left, as needed.
child1_width = child1_min; child2_width = child2_min; child3_width = child3_min; child1_x = 0; child3_x = total_width - child3_width; child2_x = total_width/2 - child2_width/2; if (child2_x < child1_x + child1_width) child2_2 = child1_x + child1_width; else if (child2_x + child2_width > child3_x) child2_x = child3_x - child2_width;
As you can see, this is pretty straightforward. Sadly, it does not have any of the features I listed above:
- Children always get their minimum size
- The ::expand property is not taken into account
- The first child is always placed at the left, regardless of text direction
- No vertical orientation
- No height-for-width support
- Baselines are ignored
Over the next few posts, I’ll try to show how to add these features back, hopefully clarifying some of the mysteries of how GTK+ size allocation works, along the way.
References
- First commit in the series
- Documentation for GtkWidget size allocation
- The code that we start from
One thought on “Container secrets: size allocation”
Comments are closed.