Height-for-width
This is where we enter the deeper parts of GTK+ size allocation. Height-for-width means that a widget does not have a single minimum size, but it might be able to accommodate a smaller width in return for getting a bigger height. Most widgets are not like this. The typical example for this behavior is a label that can wrap its text in multiple lines:
Height-for-width makes size allocation more expensive, so containers have to enable it explicitly, by setting a request mode. In general, containers should look at their children and use the request mode that is preferred by the majority of them. For simplicity, we just hardcode height-for-width here:
static GtkSizeRequestMode gtk_center_box_get_request_mode (GtkWidget *widget) { return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; }
Measure both ways
The idiomatic way to write a measure() function that can handle height-for-width is to break it down into two cases: one where we are measuring along the orientation of the layout, and one where we are measuring in the opposite direction.
if (orientation == GTK_ORIENTATION_HORIZONTAL) measure_orientation (widget, for_size, orientation, minimum, natural, minimum_baseline, natural_baseline); else measure_opposite (widget, for_size, orientation, minimum, natural, minimum_baseline, natural_baseline);
Measuring in the direction of the orientation is just like what our measure() function has done all along: we get a height, so we ask all children how much width they need for that height, and we sum up the answers.
Measuring in the opposite direction means to answer the question: given this width, how much height do you need ? We want to ask the children the same question, but what width should we give to each child ? We can’t just pass the full width to each child, since we don’t want them to overlap.
Distribute
To solve this, we need to distribute the available width among the children. This is just what our size_allocate() function is doing, so we need to break out the guts of size_allocate() out into a separate function.
Unsurprisingly, we will call the new function distribute().
static void distribute (GtkCenterBox *self, int for_size, int size, GtkRequestedSize *sizes) { /* Do whatever size_allocate() used to do * to determine sizes */ sizes[0].minimum_size = start_size; sizes[1].minimum_size = center_size; sizes[2].minimum_size = end_size; }
Now that we know how to get candidate widths for the children, we can complete the function to measure in the opposite direction. As before, we eventually return the maximum of the required heights for our children, since our layout is horizontal.
Note that orientation is GTK_ORIENTATION_VERTICAL in this case, so the min and nat values returned by the gtk_widget_measure() calls are heights.
distribute (self, -1, width, sizes); gtk_widget_measure (start_widget, orientation, sizes[0].minimum_size, &start_min, &start_nat, &min_baseline, &nat_baseline); gtk_widget_measure (center_widget, orientation, sizes[1].minimum_size, ¢er_min, ¢er_nat, &min_baseline, &nat_baseline); gtk_widget_measure (end_widget, orientation, sizes[2].minimum_size, &end_min, &end_nat, &min_baseline, &nat_baseline); *minimum = MAX (start_min, center_min, end_min); *natural = MAX (start_nat, center_nat, end_nat);
Since we have now broken out the bulk of size_allocate() into the distribute() function, we can just call it from there and then do the remaining work that is necessary to assign positions to the children (since distribute already gave us the sizes).
References
- Container secrets: size allocation
- Container secrets: size allocation, part 2
- Container secrets: size allocation, part 3
- The code with these changes
- Documentation of height-for-width geometry management