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.

Widget hierarchies in GTK+ 4.0

Today we’re going to have guest author Timm Bäder, maintainer of Corebird and a contributor of GTK+, talk about the changes in store for writing composite widgets in GTK+ 4.0.

(Note: Some of the information here is based on branches that have not been merged into master yet, but I’m confident that they will be in the near future)

In GTK+3, only GtkContainer subclasses can have child widgets. This makes a lot of sense for “public” container children like we know them, e.g. GtkBox — i.e. the developer can add, remove and reorder child widgets arbitrarily and the container just does layout.

However, there are more complicated widgets in GTK+3 which don’t inherit from GtkContainer, e.g. GtkSpinButton or GtkSwitch. These never have real GtkWidget children. Consider, for example, the two clickable areas in a GtkSpinButton. I’m not calling them “buttons” here for a reason, since in GTK+3, they are not actual GtkButton instances, as GtkSpinButton is not a GtkContainer. Instead, GtkSpinButton has to work around that fact, and create two GdkWindows for the up/down areas and then render two icons in there; care about hover and CSS states; various button up/down events; and the GdkWindow lifetime, etc. In order to work around the GtkContainer requirement, in GTK+3 we introduced gadgets (GtkCssGadget). On the styling side, a gadget corresponds to a CSS box and therefore represents one node in the CSS tree. On the widget side they were being used to have “widget-like”, CSS-stylable, children for non-container widgets.

GtkWidget Changes

Of course there were plenty of changes needed to support these use cases in GTK+4. I’m not going to list all of them here (in particular the more ugly ones like focus handling), but I think most of them are quire interesting and important for application developers and custom widget authors. Generally, we’re trying to get a away from special cases and go a more general way by re-using widgets wherever we can. So, instead of using a PangoLayout to display text, widgets should use a GtkLabel. If you have a clickable area with button-like semantics, try to use a GtkButton. If you want to lay out widgets in horizontal or vertical orientation, use a GtkBox. This way we have one widget tree that both input and rendering can operate on. In practice, this means mostly getting rid of all the gadgets used inside widgets, as well as standalone GtkCssNode instances.

Iterating over child widgets

While in GTK+3, every container had to implement GtkContainer::forall and one could easily iterate over all the child widgets using gtk_container_foreach() and gtk_container_forall(), in GTK+4, every GtkWidget can have child widgets, so every widget potentially has a list of children we need to draw, measure, size-allocate, etc. In the widget hierarchy, these children and sibling widgets can be accessed through:
  • gtk_widget_get_first_child()
  • gtk_widget_get_last_child()
  • gtk_widget_get_prev_sibling()
  • gtk_widget_get_next_sibling()
So, the easiest possible way of iterating over the child widgets of a given GtkWidget would be (in C):
GtkWidget *widget;
GtkWidget *child;
for (child = gtk_widget_get_first_child (widget);
     child != NULL;
     child = gtk_widget_get_next_sibling (child))
  {
    /* Do stuff with @child */
    g_assert (gtk_widget_get_parent (child) == widget);
  }

Adding widgets to a non-container parent

Every widget in GTK+ (both 3 and 4) saves a pointer to its parent widget. This parent can be set using gtk_widget_set_parent(), and all the GtkContainer::add implementations eventually had to use this function to set the parent of the given child widget.

In GTK+4, gtk_widget_set_parent() still works and adds the widget to the end of the child widget list of the parent. However, we clearly also want to manage the order of the child widgets, as well as where we add new children in the list, so we have:

  • gtk_widget_insert_before()
  • gtk_widget_insert_after()
to add new child widgets before or after a child widget that’s already in the parent’s list. These can also be used to reorder children child widgets by passing a child that already has the given parent set.
Since lots of widgets inside GTK+ currently use composite widget templates in XML form, GtkWidget now also has its very own GtkBuildable::add_child() implementation to support this use case. This is what e.g. GtkFileChooserWidget uses which is almost exclusively defined in XML.

Widget CSS names

Since we frequently need to use arbitrary CSS node names for arbitrary widgets, GtkWidget now has a construct-only property called GtkWidget:css-name that will be used as the css node name if specified. If not, the one passed to the widget’s gtk_widget_class_set_css_name() call will be used. If none of the 2 have been used, the CSS node will simply be called “widget”.

Examples of Converted Widgets

There are already a few widgets in current master (and side-branches) that were converted from using a variety of GtkCssGadgets, GdkWindows and PangoLayouts to using actual widgets. The end-goal being, of course, to re-use widgets as much as possible, therefore reducing code size and maintenance burden.

GtkSwitch

In GTK+3, GtkSwitch is a direct GtkWidget subclass (yay!) that uses a GdkWindow for the input (clicking on the switch will enable/disable it), one GtkCssGadget for the widget itself, two PangoLayouts for the ON/OFF text and another GtkCssGadget for the slider.
In GTK+ master, the switch still has its widget-level GtkCssGadget so it supports min-width/min-height CSS properties and CSS margins, but the slider gadget has been replaced by a GtkButton and the two PangoLayouts are replaced with GtkLabels. This way we can save roughly 300 lines in gtkswitch.c. Theoretically we also have more features and can e.g. use the limited support for the text-decoration CSS property that GtkLabel supports, but I’m just going to doubt that this is very useful.

GtkSpinButton

As noted earlier, GtkSpinButton could easily use actual GtkButtons for the up/down areas, and it does so in GTK+ master (which will become GTK+4 at some point). This gets rid of another 300 lines in gtkspinbutton.c. Through using GtkButtons, the old icon helper gadgets also become actual GtkImage instances. Unfortunately we have to implement some of the GtkGesture magic ourselves here since GtkSpinButton also supports middle and right clicks on its buttons while GtkButton::clicked only reacts to single, primary mouse button clicks.

GtkLevelBar

GtkLevelBar manages a set of blocks and assigns different style classes to them. In GTK+3, these blocks are all GtkCssGadget instances. All of them are “dumb” in the sense that they don’t do anything special — they are just CSS boxes and that’s it. That’s why converting it to use GtkWidgets for all blocks didn’t gain much of a reduction in file size.

 

GtkProgressBar

GtkProgressBar uses gadgets for the trough and the progress nodes. It also uses a PangoLayout to display the percentage or a user-given string.

In master, both trough and progress are widgets and the PangoLayout is a GtkLabel of course. Not having to listen to GtkWidget::style-changed (which gets done automatically for widgets) and not having to draw the PangoLayout ourselves (which the GtkLabel now takes care of) yields a nice code size saving of around 200 lines, however.

 

GtkExpander

GtkExpander is more complex than it looks like. In GTK+3, it consists of 2 GtkBoxGadgets (which is like a GtkBox, but not a widget…), one gadget for the arrow to the left of the title widget, the title widget and the actual content widget. In master, this is done using an actual GtkBox, and a GtkIcon (an internal widget) for the arrow. I’m not sure if this is the best way to express the GtkExpander functionality, e.g. we could also use a GtkButton for the arrow+title widget combination.
Since GtkBoxGadget is already almost a perfect GtkBox clone, the code savings here aren’t very interesting, but not having to listen to GtkWidget::direction-changed once again saves around 30 lines.

Accidental GtkBox & GtkButton subclasses

GTK+3 contains quit a lot of widgets that inherit from another widget for the sole purpose of looking and behaving like them. The problem here is that these widgets also inherit all the API of the parent class, which is only rarely wanted.
For GtkBox, almost all subclasses in GTK+3 are “accidental” in the sense that actually using them as a GtkBox doesn’t make any sense and people usually don’t do it, but they had to be GtkBox subclasses to satisfy GTK+’s GtkContainer requirement. One example of such a widget would be GtkFileChooserWidget. This is already one of the most complex widget to ever exist, but have you ever considered using gtk_container_add() or gtk_box_pack_{start,end}() to add widgets to it? It doesn’t make a lot of sense. It’s a closed entity with its own API. So, in GTK+4 it will be a direct GtkWidget subclass that contains a GtkBox. Or maybe not. That’s just an implementation detail you don’t have to care about. (Fun Fact on the side: GtkFileChooserButton is a GtkBox in GTK+3)
The same applies to GtkButton. In GTK+3, GtkButton has lots of subclasses that inherited all the GtkButton API without actually supporting it. What happens if you remove the child widget from a GtkLinkButton? What if you set the GtkButton:label property of a GtkFontButton? Again, these are closed entities that have their own API to set and get various data and change behavior and/or looks based on them, but that doesn’t mean they support all the GtkButton/GtkContainer shenanigans.

General Restructuring Rules and Future

For this refactoring work we try to keep the CSS node structure as it was in GTK+3, i.e. we try not to break the CSS node tests we currently have in testsuite/css/nodes.c.
A few of the more complex widgets inside GTK+ still heavily rely on gadgets and porting them away to use only actual widgets will be quite a lot of work. GtkRange is historically one of the most complex non-container widgets inside GTK+. It’s used both for scrollbars and scales so porting it to widgets might first need another round of refactoring.
Another interesting case is GtkNotebook, which combines gadget and widget usage. Here we could e.g. use a real GtkStack to switch between pages and effortlessly support page switching transitions.
Another exciting look into the future is of course Carlos’ wip/carlosg/event-delivery branch that gets rid of a ton of GdkWindow instances and makes widget input easier than ever before.