A GTK+ 3 update

Plans

When we started development towards GTK+ 4, we laid out a plan that said GTK+ 3.22 would be the final, stable branch of GTK+ 3.  And we’ve stuck to this for a while.

I has served us reasonably well — GTK+ 3 stopped changing in drastic ways, which was well-received, and we are finally seeing applications moving from GTK+ 2.

Reality

But, GTK+ 4 is taking its time to mature (more on that in another post), and some nice new features (such as font variation support, or Emoji completion) languish unused in master. We also get requests for critical APIs from some of the ported applications.

Therefore, we have decided that it is better to change course and allow a limited amount of new features and API in GTK+ 3.x, by doing a GTK+ 3.24 release in September.

There is now a gtk-3-24 branch in git. GTK+ 3.x maintenance has moved to that branch, and we won’t be doing any further 3.22.x releases.

Highlights

The first release off this new branch is GTK+ 3.23.0, which can be found here:

https://download.gnome.org/sources/gtk+/3.23/gtk+-3.23.0.tar.xz

The highlights of this release include new font chooser features,

  • Allow setting OpenType font features
  • Show examples for OpenType font features
  • Allow selecting OpenType font variations
  • Support levels of details for selection

new Emoji features,

  • Support a completion popup for Emoji
  • Drop Ctrl-Shift-e shortcut

gdk_window_move_to_rect as public API,

and the Wayland backend using anonymous shared memory on FreeBSD.

Numerology

A side-effect of doing one more 3.x cycle is that we will have GTK+ 3.24 to be the final GTK+ 3, which is a pleasant parallel to GTK+ 2.24 being the final GTK+ 2.

textures and paintables

With GTK4, we’ve been trying to find better solution for image data. In GTK3 the objects we used for this were pixbufs and Cairo surfaces. But they don’t fit the bill anymore, so now we have GdkTexture and GdkPaintable.

GdkTexture

GdkTexture is the replacement for GdkPixbuf. Why is it better?
For a start, it is a lot simpler. The API looks like this:

int gdk_texture_get_width (GdkTexture *texture);
int gdk_texture_get_height (GdkTexture *texture);

void gdk_texture_download (GdkTexture *texture,
                           guchar     *data,
                           gsize       stride);

So it is a 2D pixel array and if you want to, you can download the pixels. It is also guaranteed immutable, so the pixels will never change. Lots of constructors exist to create textures from files, resources, data or pixbufs.

But the biggest difference between textures and pixbufs is that they don’t expose the memory that they use to store the pixels. In fact, before gdk_texture_download() is called, that data doesn’t even need to exist.
And this is used in the GL texture. The GtkGLArea widget for example uses this method to pass data around. GStreamer is expected to pass video in the form of GL textures, too.

GdkPaintable

But sometimes, you have something more complex than an immutable bunch of pixels. For example you could have an animated GIF or a scalable SVG. That’s where GdkPaintable comes in.
In abstract terms, GdkPaintable is an interface for objects that know how to render themselves at any size. Inspired by CSS images, they can optionally provide intrinsic sizing information that GTK widgets can use to place them.
So the core of the GdkPaintable interface are the function make the paintable render itself and the 3 functions that provide sizing information:

void gdk_paintable_snapshot (GdkPaintable *paintable,
                             GdkSnapshot  *snapshot,
                             double        width,
                             double        height);

int gdk_paintable_get_intrinsic_width (GdkPaintable *paintable);
int gdk_paintable_get_intrinsic_height (GdkPaintable *paintable);
double gdk_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable);

On top of that, the paintable can emit the “invalidate-contents” and “invalidate-size” signals when its contents or size changes.

To make this more concrete, let’s take a scalable SVG as an example: The paintable implementation would return no intrinsic size (the return value 0 for those sizing function achieves that) and whenever it is drawn, it would draw itself pixel-exact at the given size.
Or take the example of the animated GIF: It would provide its pixel size as its intrinsic size and draw the current frame of the animation scaled to the given size. And whenever the next frame of the animation should be displayed, it would emit the “invalidate-size” signal.
And last but not least, GdkTexture implements this interface.

We’re currently in the process of changing all the code that in GTK3 accepted GdkPixbuf to now accept GdkPaintable. The GtkImage widget of course has been changed already, as have the drag’n’drop icons or GtkAboutDialog. Experimental patches exist to let applications provide paintables to the GTK CSS engine.

And if you now put all this information together about GStreamer potentially providing textures backed by GL images and creating paintables that do animations that can then be uploaded to CSS, you can maybe see where this is going

Input methods in GTK+ 4

GTK’s support for loadable modules dates back to the beginning of time, which is why GTK has a lot of code to deal with GTypeModules and with search paths, etc. Much later on, Alex revisited this topic for GVfs, and came up with the concept of extension points and GIO modules, which implement them.  This is a much nicer framework, and GTK 4 is the perfect opportunity for us to switch to using it.

Changes in GTK+ 4

Therefore, I’ve recently spent some time on the module support in GTK. The major changes here are the following:

  • We no longer support general-purpose loadable modules. One of the few remaining users of this facility is libcanberra, and we will look at implementing ‘event sound’ functionality directly in GTK+ instead of relying on a module for it. If you rely on loading GTK+ modules, please come and talk to us about other ways to achieve what you are doing.
  • Print backends are now defined using an extension point named “gtk-print-backend”, which requires the type GtkPrintBackend.  The existing print backends have been converted to GIO modules implementing this extension point. Since we have never supported out-of-tree print backends, this should not affect anybody else.
  • Input methods are also defined using an extension point, named “gtk-im-module”, which requires the GtkIMContext type.  We have dropped all the non-platform IM modules, and moved the platform IM modules into GTK+ proper, while also implementing the extension point.

Adapting existing input methods

Since we still support out-of-tree IM modules, I want to use the rest of this post to give a quick sketch of how an out-of-tree IM module for GTK+ 4 has to look.

There are a few steps to convert a traditional GTypeModule-based IM module to the new extension point. The example code below is taken from the Broadway input method.

Use G_DEFINE_DYNAMIC_TYPE

We are going to load a type from a module, and G_DEFINE_DYNAMIC_TYPE is the proper way to define such types:

G_DEFINE_DYNAMIC_TYPE (GtkIMContextBroadway,
                       gtk_im_context_broadway,
                       GTK_TYPE_IM_CONTEXT)

Note that this macro defines a gtk_im_context_broadway_register_type() function, which we will use in the next step.

Note that dynamic types are expected to have a class_finalize function in addition to the more common class_init, which can be trivial:

static void
gtk_im_context_broadway_class_finalize
               (GtkIMContextBroadwayClass *class)
{
}

Implement the GIO module API

In order to be usable as a GIOModule, a module must implement three functions: g_io_module_load(), g_io_module_unload() and g_io_module_query() (strictly speaking, the last one is optional, but we’ll implement it here anyway).

void
g_io_module_load (GIOModule *module)
{
  g_type_module_use (G_TYPE_MODULE (module));
  gtk_im_context_broadway_register_type  
                        (G_TYPE_MODULE (module));
  g_io_extension_point_implement
             (GTK_IM_MODULE_EXTENSION_POINT_NAME,
              GTK_TYPE_IM_CONTEXT_BROADWAY,
              "broadway",
              10);
 }
void
g_io_module_unload (GIOModule *module)
{
}
char **
g_io_module_query (void)
{
  char *eps[] = {
    GTK_IM_MODULE_EXTENSION_POINT_NAME,
    NULL
  };
  return g_strdupv (eps);
}

Install your module properly

GTK+ will still look in $LIBDIR/gtk-4.0/immodules/ for input methods to load, but GIO only looks at shared objects whose name starts with “lib”, so make sure you follow that convention.

Debugging

And thats it!

Now GTK+ 4 should load your input method, and if you run a GTK+ 4 application with GTK_DEBUG=modules, you should see your module show up in the debug output.

 

GTK+ 3.92

Yesterday, we released GTK+ 3.92.1, 重庆市. Since it has been a while since the last 3.91 release, here is a brief look at the major changes.

This release is another milestone on our way towards GTK+ 4. And while a lot still needs to be done, this release allows a first glimpse at some of the things we hope to achieve in GTK+ 4.

GSK

Much of the work since the last release has gone into GSK. The Vulkan renderer is now close to complete, as far as avoiding cairo fallbacks goes. The only missing piece are blurred shadows (admittedly, an important piece).

One major step forward since the 3.91.2 release is that we no longer use cairo fallbacks for all text. Instead, text (in labels and entries, sadly not in text views yet) gets translated into text nodes. Each text node contains a PangoGlyphString and a PangoFont. The Vulkan renderer uses a glyph cache to avoid re-rendering the glyphs for each frame.

The internal logic of the Vulkan renderer has been reworked to use textures instead of cairo surfaces for intermediate results and thus avoid more cairo fallbacks.

Other node types that have gained support in the Vulkan renderer include blurs, repeat nodes, blend modes and cross-fades. In some cases, the shaders we use are very naive implementations. Help with improving them would be more than welcome!

As a first example of what we can do with render nodes, we have implemented a blur-under feature for GtkOverlay. This works by capturing the ‘main child’ of the overlay as a render node, and then reusing it several times, with the right clipping, and sometimes with a blur node.

Inspector

To help you explore GSK, the inspector now shows Vulkan information and the recorder shows a lot more information about render nodes.

Input

On the input side, events have gained accessors, and we are no longer accessing their fields directly. This is an intermediate step, cleaning up events is still a work in progress. We have moved the traditional widget signals for events (such as ::key-press-event) to an event controller, and most widgets inside GTK+ have stopped using them altogether.

Build System

We have switched over to using Meson exclusively for GTK+, and the 3.92.1 release is the first one done using Meson’s dist support. To get the release out the door, we also had to port the documentation, the test suite and the installed tests to using Meson.

There are still some rough edges (we don’t get all dependencies 100% right), but overall, Meson worked out well for us.

The rest

Of course, everybody loves Emoji, and the same color Emoji support that has landed in GTK+ 3.22 is also available in this release. Beyond that, font support in CSS has improved a bit with support for the CSS3 font-variant property.

Of course, this relies on fonts that have the corresponding features.

Try it out

With GTK+ 3.92.1, it should be easy to try some of these things out for yourself.

And if you’ve always wanted to get into GTK+ development but never found the right opportunity, now is a great time to get involved!

A scrolling primer

A few years ago, I wrote a post about scrolling in GTK+ 3. Time for another look!

The common case

The basic idea of the changes described back then is still the same. We expect touchpad (or track point) scrolling to be one of the most common forms of scrolling, and the scrollbar operates as a narrow indicator for this.

As you can see, we change the cursor to indicate scrolling, and it you can freely scroll in all directions. It is kinetic, too.

Classical scrolling

Of course, it is still possible to just click and drag the slider, for classical scrolling.

Another aspect of ‘classical’ scrolling is that you can click on the trough outside the slider, and either warp the position to where you clicked, or jump in page-size increments.

By default, a primary click warps, and Shift-primary click goes by pages. We just added back middle click as an alternative to Shift-primary click, since this is a common alternative that many people are used to. For mainly historical reasons, GTK+ has a setting, gtk-primary-button-warps-slider, which switches the roles of primary click and Shift-primary click for this.

The typical keyboard shortcuts (Page Up, Page Down, Home, End) let you control scrolling with the keyboard.

Smooth scrolling

There’s more to scrolling in GTK+ that you may not know about. One feature that we introduced long ago is a ‘zoom’ or ‘fine adjustment’ mode, which slows the scrolling down to allow pixel-precise positioning.

To trigger this mode you can either use a long press or shift-click in the slider. As you can see in the video, once you move the pointer below or above the scrollbar, it will keep scrolling at the same relaxed speed until you let go.

As a variation on this theme, more recently we added a variable speed variant of smooth scrolling.

To trigger it, you secondary click in the trough outside the slider. Once the scrolling starts, you can control the speed by moving the pointer closer or farther away from the scrollbar. This is pretty addictive, once you’ve discovered it!

Custom locations

As the last feature, applications can add a context menu that gets triggered by secondary click on the slider, and make it scroll to important positions.

Thats it, scroll on!

Progress towards GTK+ 4

Last week at GUADEC in Manchester, the GTK+ maintainers and interested folks met for a working session during the unconference days.

Georges already did a nice job summarizing the results in his blog post, which you should read (if only to see some pictures of the assembled GTK+ folks).

GTK+ 3

We did briefly discuss GTK+ 3. Our impression is that most people are enjoying the stability that came with GTK+ 3.22 and are not in a rush to jump on a new, less stable toolkit version.

The general consensus was that we should maintain a pretty strict stance on API additions in GTK+ 3, but allow new features in when their is a high enough benefit. Examples for this that came up are the client-side versus server-side negotiation protocol support for Wayland, or color emoji support.

GTK+ 4

The bulk of the time was devoted to discussing all the things that we want or need to complete for GTK+ 4. We have a pretty good idea who is going to work on each of these items, but we did not nail down a very detailed timeline for completing them.

At the end, we collected a list of the items that we consider blockers:

  • Constraint-based layout
  • Support for defining states and transitions in ui files
  • Designer support
  • Convert keyboard handling to event controllers
  • Non-fallback text rendering
  • A finished GL renderer
  • Clean support for subsurfaces in GDK
  • No more root window in GDK
  • Event cleanup

Some of these bullet points deserve a more detailed discussion.

Constraint-based layout, states and designer support

Layout with constraints is a flexible system, and it has been successful on other platforms. More importantly, it is much closer to the way most humans thing about laying out things on a screen or piece of paper, and it will hopefully provide a common language that designers and developers of GTK+ applications can communicate in.

The Emeus widget that Emmanuele and others have been working on for a while is using constraints to find positions and sizes for child widgets of an individual container.

The plan for integrating it into GTK+ is a bit more ambitious: We envision a single constraints solver per toplevel that all the containers inside the window add their constraints to. This will require the current containers in GTK+ to express their layout algorithms in terms of constraints, which should not be too hard in most cases, and can be done piecemeal.

States and transitions between them is something that Christian Hergert has prototyped in libdazzle. The idea here is to define not just a complex widget such as a dialog, but also its main states and how transitions between them should work, in a ui file. This will lets us have a UI designer tool that is not just about arranging widgets on a canvas, but goes towards story-boarding and designing transitions. This is of course much easier said than done…

Keyboard handling

Christian took some time to describe the shortcuts engine that he has written for gnome builder, which currently lives in libdazzle. It has some interesting features, like capture-bubble event handling, chording (i.e. multi-key sequences such as Ctrl-C Ctrl-X), tight integration with actions, and the ability to automatically generate help for keyboard shortcuts.

The plan in this area is to take the best features from Christians engine and turn them into one or more GtkEventControllers. Once this work is complete, we will convert all widgets to use event controllers instead of key-press signal handlers.

GtkBindingSet will also be replaced by event controllers.

Text rendering

The Vulkan renderer for GSK is more or less complete. It can render most of what the CSS machinery produces efficiently, using shaders. The big exception is text: what happens for text currently is we render it to a surface using cairo, then upload the surface to a texture, and then use that in render node. For every frame.

What needs to happen here is that we upload the glyphs we need into a larger texture that we keep around as atlas, and then create text render nodes that refer to the atlas.

Since text is a pretty important ingredient in user interfaces, we can’t really claim that we’ve validated the render node approach until we have proper text rendering implemented for Vulkan.

The GL renderer

Benjamin has done most of the work to get the Vulkan renderer to an almost complete state. While he was doing that, the GL renderer has fallen behind – it does not have the shaders that are used in Vulkan.

What needs to happen here is to abstract out the common parts, and backport the rest from Vulkan to its GL equivalent. A not so fun aspect of this is that we may eventually need more than one variant of GL renderer, for legacy GL and GLES platforms. But we can probably get away with just a modern GL renderer, at least initially.

Fonts and Text

A separate session was devoted to new features in our text rendering stack. The features that were the topic here are variable fonts and color emoji. Unfortunately, I missed most of the discussion, but a summary of the outcome is that:

  • Behdad has a rough plan for what needs to be done in pango and fontconfig for supporting variable fonts. This involves new syntax in PangoFontDescription for specifying axis values and new API in PangoFontFamily to obtain information about available axes.
  • During GUADEC, Behdad merged support for color emoji in cairo, fontconfig and pango and I started to work on some simple emoji input in GTK+. That has landed now too, in both GTK+ 3 and master.

Other

We touched on too many other topics to summarize them all here. One of them was the state of accessibility, but that is a topic for another time.

Container secrets: size allocation, part 6

Baselines

We are entering another of the more mysterious areas of GTK+ size allocation. Baselines move widgets from a simple box-with-width-and-height model to one where widgets can be aligned vertically in more interesting ways. The main place where this is matters is text. The readers eye is very sensitive to words moving up and down as you move along a line of text. Baselines are there to avoid that.

 

Since this is about aligning children vertically wrt. to each other, baselines are only relevant when the container is in horizontal orientation.

Measure above and below

Since children can now have a ‘forced’ alignment, simply t aking the maximum of the children’s heights is no longer sufficient. The alignment might cause children to ‘stick out’ at the top or the bottom, requiring a greater overall height. In order to handle this, we measure the parts ‘above the baseline’ and the parts ‘below the baseline’ separately, and maximize them separately.

for (i = 0; i < 3; i++) {
  gtk_widget_measure (child[i],
                      orientation,
                      sizes[i].minimum_size,
                      &child_min, &child_nat,
                      &child_min_baseline, &child_nat_baseline);

   below_min = MAX (below_min, child_min - child_min_baseline);
   above_min = MAX (above_min, child_min_baseline);
   below_nat = MAX (below_nat, child_nat - child_nat_baseline);
   above_nat = MAX (above_nat, child_nat_baseline);
}

total_min = above_min + below_min;
total_nat = above_nat + below_nat;

This code leaves out some details, such as dealing with children that don’t return a baseline.

Allocate on baseline

On the allocation side, there are two cases: either we are given a baseline that we have to align our children to, or we have to determine a baseline ourselves. In the latter case, we need to do essentially the same we already did for measuring: determine the below and above sizes separately, and use them to find our baseline:

for (i = 0; i < 3; i++) {
  if (gtk_widget_get_valign (child[i]) != GTK_ALIGN_BASELINE)
    continue;

  gtk_widget_measure (child[i],
                      GTK_ORIENTATION_VERTICAL,
                      child_size[i],
                      &child_min, &child_nat,
                      &child_min_baseline, &child_nat_baseline);

  below_min = MAX (below_min, child_min - child_min_baseline);
  below_nat = MAX (below_nat, child_nat - child_nat_baseline);
  above_min = MAX (above_min, child_min_baseline);
  above_nat = MAX (above_nat, child_nat_baseline);
}

When it comes to determining the baseline, we again have a choice to make. When there is more space available than the minimum, do we place the baseline as high as possible, or as low as possible, or somewhere in the middle? GtkBox has a ::baseline-position property to leave this choice to the user, and we do the same here.

switch (baseline_position) {
  case GTK_BASELINE_POSITION_TOP:
    baseline = above_min;
    break;
  case GTK_BASELINE_POSITION_CENTER:
    baseline = above_min + (height - (above_min + below_min)) / 2;
    break;
  case GTK_BASELINE_POSITION_BOTTOM:
    baseline = height - below_min;
    break;
}
Expanded, baseline position: center
Compressed, baseline position: top
Compressed, baseline position: center
Compressed, baseline position: bottom

Summary

This ends our journey through GTK+’s size allocation machinery. I hope you enjoyed it.

References

  1. Container secrets: size allocation
  2. Container secrets: size allocation, part 2
  3. Container secrets: size allocation, part 3
  4. Container secrets: size allocation, part 4
  5. Container secrets: size allocation, part 5
  6. The code with these changes

Container secrets: size allocation, part 5

Orientation

Many widgets in GTK+ can be oriented either horizontally or vertically. Anything from a separator to a toolbar is implementing the GtkOrientable interface to allow this to be changed at runtime, by setting the ::orientation property. So, obviously, GtkCenterBox should follow this pattern too.

I’m not explaining in detail how to add the interface and implement the property. The interesting part for us is how we are going to use the orientation property during size allocation.

Thankfully, much of our machinery is already written in terms of a single dimension, and can be applied to a height just as well as a width. What remains to be done is going through all the functions, and making sure that we take the orientation into account whenever we do something that depends on it. For example, we introduce a little helper to query the proper expand property.

static gboolean
get_expand (GtkWidget *widget,
            GtkOrientation orientation)
{
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    return gtk_widget_get_hexpand (widget);
  else
    return gtk_widget_get_vexpand (widget);
}

One thing to keep in mind is that some of the features we implement here only apply in horizontal orientation, such as right-to-left flipping, or baselines.

The measure() function changes to avoid hardcoding horizontal orientation:

if (orientation == self->orientation)
  gtk_center_box_measure_orientation (widget, orientation, for_size,
                                      minimum, natural,
                                      min_baseline, nat_baseline);
else
  gtk_center_box_measure_opposite (widget, orientation, for_size,
                                   minimum, natural,
                                   min_baseline, nat_baseline);

The size_allocate() function calls distribute() to distribute either the width or the height, depending on orientation:

if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
  size = width;
  for_size = height;
} else {
  size = height;
  for_size = width;
}
distribute (self, for_size, size, sizes);

After these straightforward, but tedious changes, we can orient a center box vertically:

References

  1. Container secrets: size allocation
  2. Container secrets: size allocation, part 2
  3. Container secrets: size allocation, part 3
  4. Container secrets: size allocation, part 4
  5. The code with these changes

Container secrets: size allocation, part 4

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,
                    &center_min, &center_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).

Expanded
Slightly below natural size
Smaller
and smaller
and smaller

References

  1. Container secrets: size allocation
  2. Container secrets: size allocation, part 2
  3. Container secrets: size allocation, part 3
  4. The code with these changes
  5. Documentation of height-for-width geometry management

Container secrets: size allocation, part 3

 Expanding children

The next stop in our quest for featureful size allocation is the ::expand property. There are actually two of them,  ::hexpand and ::vexpand, and they have a somewhat interesting behavior of propagating upwards in the widget hierarchy. But that is not what we are going to discuss today, we simply want to give the children of our center box widget all the available space if they have the ::hexpand flag set.

Once again, the measure() implementation can stay as it is. And once again, we need to make a decision about who to expand first, if more than one child has an expand flag set. GtkBox tries to treat all its children the same, and evenly distributes the available extra space among all expanding children. We, on the other hand, prefer the center child, since it is the most important one.

But how do we go about this ? After some experimentation, I figured that if we already have to push the center child to the left or right because it does not fit, there is not point in making it even larger. Therefore we only respect the expand flag if that is not the case:

center_expand = gtk_widget_get_hexpand (center);

if (left_size > center_x)
  center_x = left_size;
else if (width - right_size < center_pos + center_size)
  center_x = width - center_width - right_size;
else if (center_expand) {
  center_width = width - 2 * MAX (left_size, right_size);
  center_x = (width / 2) - (center_width / 2);
}

After doing this, there may still be some space left that we can give to the outer children, if they are expanding:

if (left_expand)
  left_size = center_pos - left_pos;
if (right_expand)
  right_size = pos + width - (center_pos + center_width);
No expanding children
Center child expanding
End child expanding
Center and end children expanding

References

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