More on lists in GTK 4

The previous post introduced the overall list view framework that was introduced in the 3.98.5 release, and showed some examples. In this post, we’ll dive deeper into some technical areas, and look at some related topics.

Trees

One important feature of GtkTreeView is that it can display trees. That is what it is named for, after all. GtkListView puts the emphasis on plain lists, and makes handling those easier. In particular the GListModel api is much simpler than GtkTreeModel—which is why there have been comparatively few custom tree model implementations, but GTK 4 already has a whole zoo of list models.

But we still need to display trees, sometimes. There are some complexities around this, but we’ve figured out a way to do it.

Models

The first ingredient that we need is a model. Since GListModel represents a linear list of items, we have to work a bit harder to make it handle trees.

GtkTreeListModel does this by adding a way to expand items, by creating new child list models on demand. The items in a GtkTreeListModel are instances of GtkTreeListRow which wrap the actual items in your model, and there are functions such as gtk_tree_list_row_get_children() to get the items of the child model and gtk_tree_list_row_get_item() to get the original item. GtkTreeListRow has an :expanded property that is used to keep track of whether the children are currently shows or not.

At the core of  a tree list model is the GtkTreeListModelCreateModelFunc which takes an item from your list and returns a new list model containing items of the same type that should be the children of the given item in the tree.

Here is an example for a tree list model of GSettings objects. The function enumerates the child settings of a given GSettings object, and returns a new list model for them:

static GListModel *
create_settings_model (gpointer item,
                       gpointer unused)
{
  GSettings *settings = item;
  char **schemas;
  GListStore *result;
  guint i;

  schemas = g_settings_list_children (settings);

  if (schemas == NULL || schemas[0] == NULL)
    {
      g_free (schemas);
      return NULL;
    }

  result = g_list_store_new (G_TYPE_SETTINGS);
  for (i = 0; schemas[i] != NULL; i++)
    {
      GSettings *child = g_settings_get_child (settings, schemas[i]);
      g_list_store_append (result, child);
      g_object_unref (child);
    }

  g_strfreev (schemas);

  return G_LIST_MODEL (result);
}

Expanders

The next ingredient that we need is a widget that displays the expander arrow that users can click to control the :expanded property. This is provided by the GtkTreeExpander widget. Just as the GtkTreeListRow items wrap the underlying items in your model, you use GtkTreeExpander widgets to wrap the widgets used to display your items.

Here is how the tree expanders look in action, for our GSettings example:

The full example can be found here.

Sorting

One last topic to touch on before we leave trees is sorting. Lists often have more than one way of being sorted: a-z, z-a, ignoring case, etc, etc. Column views support this by letting you associate sorters with columns, which the user can then activate by clicking on the column headers. The API for this is

gtk_column_view_column_set_sorter (column, sorter)

When sorting trees, you typically want the sort order to apply to the items at a given level in the tree, but not across levels, since that would scramble the tree structure. GTK supports this with the GtkTreeListRowSorter, which wraps an existing sorter and makes it respect the tree structure:

sorter = gtk_column_view_get_sorter (view);
tree_sorter = gtk_tree_list_row_sorter_new (sorter);
sort_model = gtk_sort_list_model_new (tree_model,           
                                      tree_sorter);
gtk_column_view_set_model (view, sort_model);

In conclusion, trees have been de-emphasized a bit in the new list widgets, and they add quite a bit of complication to the machinery, but they are fully supported, in list views as well as in column views:

Combo boxes

One of the more problematic areas where cell renderers are used is our single-selection control: GtkComboBox.  This was never a great fit, in particular in combination with nested menus. So, we were eager to try the new list view machinery on a replacement for GtkComboBox.

From the design side, there has also been a longstanding wishlist item to do better for combo boxes, as can be seen in this mockup from 2015:

Five years later, we finally have a replacement widget. It is called GtkDropDown. The API of the new widget is as minimal as possible, almost all the work is done by list model and item factory machinery.  Basically, you create a dropdown with gtk_drop_down_new(), then you give it a factory and a model, and you are done.

Since most choices consist of simple strings, there is a convenience method that creates the model and factory for you, from a string array:

const char * const times[] = {
  "1 minute",
  "2 minutes",
  "5 minutes",
  "20 minutes",
  NULL
};

button = drop_down_new ();
gtk_drop_down_set_from_strings (GTK_DROP_DOWN (button), times);

This convenience API is very similar to GtkComboBoxText, and the GtkBuilder support is also very similar. You can specify a list of strings in the ui file like this:

<object class="GtkDropDown">
  <items>
    <item translatable="yes">Factory</item>
    <item translatable="yes">Home</item>
    <item translatable="yes">Subway</item>
  </items>
</object>

Here are a few GtkDropDowns in action:

Summary

To stress this point again: all of this is brandnew API, and we’d love to hear your feedback on what works well, what doesn’t, and what is missing.

Scalable lists in GTK 4

One of the last big missing pieces in GTK 4 is the infrastructure for new list and grid widgets. It has just been merged and is included in the 3.98.5 release. So it is time to take a closer look.

History: tree views and list boxes

Since ancient times (ie GTK 2), GtkTreeView has been the go-to data display widget in GTK. It uses a model-view pattern to keep the data separate from the display infrastructure. Over the years, it has grown a grid-display sibling (GtkIconView) and a selection cousin (GtkComboBox), using the same infrastructure (tree models and cell renderers).

Unfortunately, the approach taken for rendering in GtkTreeView with cell renderers left us with a schism: widgets use one set of vfuncs and technologies for size allocation and rendering, and cell renderers use another. One of the unfortunate consequences of this split is that it is very hard to do animations in tree views (since cell renderers don’t keep state). Another is that most of the advances of the GTK CSS rendering machinery are unavailable in cell renderers.

Therefore, we would really like to use widgets for displaying the data in lists. During the GTK 3 era, we have introduced a number of containers for this purpose: GtkListBox for lists, and GtkFlowBox for grids. They don’t use cell renderers, so the aforementioned limitations are not a concern. And they can even use list models to hold the data. But they generate a widget for each data item, and this severely limits their scalability. As a rule of thumb, GtkListBox can handle 1 000 items well, while GtkTreeView works fine for 100 000.

Overcoming the scalability limitations, while still using widgets for all rendering has been on our roadmap for a long time.

Scalability limits
Widget Scalability
GtkIconView 100
GtkListBox 1 000
GtkTreeView 100 000
GtkListView unlimited

New infrastructure

With list view family of widgets, we hope to  finally achieve that. The goal is to handle unlimited numbers of items well. If you’ve worked with things like the Android recycler view, you will recognize the basic ideas behind list views:

  • The data for items is provided in the form of a model (containing objects)
  • Widgets are created just for the viewable range of items
  • Widgets can be recycled by binding them to different items
  • Avoid iterating over all items in the model as much as possible, and just deal with the items in the visible range which are bound to widgets

Models

For the model side of our model-view architecture, we’ve moved away from GtkTreeModel, and are now using GListModel. There are several reasons for this. One is that we want the items to be objects with properties, so we can use property bindings.  Another one is that GtkTreeModel is not all that scalable in the first place (see e.g. this case of unintentional quadratic behavior).

GTK 4 comes with a rich assortment of GListModel implementations, from various ways to combine or modify existing models to filtering and sorting.  Filtering and sorting is supported by several GtkFilter and GtkSorter classes. Selections are also handled on the model side, with GtkSelectionModel and its subclasses.

Finally, there are concrete models for the kinds of data that we are dealing with: GtkDirectoryList for files, PangoFontMap for fonts.

APIs that have traditionally returns GLists of various items have been changed or supplemented with APIs returning a GListModel, for example gdk_display_get_monitors(), gtk_window_get_toplevels() and gtk_stack_get_pages().

Factories

Since we are talking about automatically creating widgets on demand, there will be factories involved. GtkListItemFactory is the object that is tasked with creating widgets for the items in the model.

There are different implementations of this factory. One of them, GtkBuilderListItemFactory, is using ui files as templates for the list item widgets.  Here is a typical example:

<interface>
  <template class="GtkListItem">
    <property name="child">
      <object class="GtkLabel">
        <property name="xalign">0</property>
        <property name="wrap">1</property>
        <property name="width-chars">50</property>
        <property name="max-width-chars">50</property>
        <binding name="label">
          <lookup name="summary" type="SettingsKey">
            <lookup name="item">GtkListItem</lookup>
          </lookup>
        </binding>
      </object>
    </property>
  </template>
</interface>

The full example can be found in gtk4-demo.

Another list item factory implementation, GtkSignalListItemFactory, takes callbacks to setup, teardown, bind and unbind widgets from the items.

static void
setup_listitem_cb (GtkListItemFactory *factory,
                   GtkListItem        *list_item)
{
  GtkWidget *image;

  image = gtk_image_new ();
  gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE);
  gtk_list_item_set_child (list_item, image);
}

static void
bind_listitem_cb (GtkListItemFactory *factory,
                  GtkListItem *list_item)
{
  GtkWidget *image;
  GAppInfo *app_info;

  image = gtk_list_item_get_child (list_item);
  app_info = gtk_list_item_get_item (list_item);
  gtk_image_set_from_gicon (GTK_IMAGE (image),
                            g_app_info_get_icon (app_info));
}

static void
activate_cb (GtkListView  *list,
             guint         position,
             gpointer      unused)
{
  GListModel *model;
  GAppInfo *app_info;

  model = gtk_list_view_get_model (list);
  app_info = g_list_model_get_item (model, position);
  g_app_info_launch (app_info, NULL, NULL, NULL);
  g_object_unref (app_info);
}

...

factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup", setup_listitem_cb, NULL);
g_signal_connect (factory, "bind", bind_listitem_cb, NULL);

list = gtk_list_view_new_with_factory (factory);
g_signal_connect (list, "activate", activate_cb, NULL);

model = create_application_list ();
gtk_list_view_set_model (GTK_LIST_VIEW (list), model);
g_object_unref (model);

gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list);

The full example can be found here.

Expressions

To bind the widgets that are created by the factory to the data in your items, we need a flexible mechanism for binding properties. GObject’s GBinding mechanism goes in the right direction, but is not flexible enough to handle situations where you may need to bind properties of sub-objects or widgets deep inside the hierarchy of your widget, and where the objects in questions may not even exist at the time you are setting up the binding.

To handle this, we’ve introduced GtkExpression as a more flexible binding system which can express things like:

label = this->item->value

where this is a GtkListItem, which has an item property (of type SettingsKey), whose value property we want to bind to the label property. Expressing the same in a GtkBuilder ui file looks a bit more unwieldy:

<binding name="label">
  <lookup name="value" type="SettingsKey">
    <lookup name="item">GtkListItem</lookup>
  </lookup>
</binding>

New widgets

GtkListView is a simple list, without columns or headers. An example where this kind of list is used is GtkFontChooser. One little way in which GtkListView breaks new ground is that it can be set up as a horizontal list, as well as the usual vertical orientation.

GtkGridView puts the widgets in a reflowing grid, much like GtkFlowBox or GtkIconView.

GtkColumnView is the equivalent of a full GtkTreeView, with multiple columns and headers, and features such as interactive resizing and reordering.  Just like a GtkTreeView has GtkTreeViewColumns, a GtkColumnView has a list of GtkColumnViewColumns. Each column has a factory that produces a cell for each item. The cells are then combined into the row for the item.

Examples

Many of the lists in complex GTK dialogs (although not all yet) have been replaced with the new list widgets. For example, the font chooser is now using a GtkListView, and most of the lists in the GTK inspector use GtkColumnView.

But gtk4-demo contains a number of examples for the new widgets. Here are a few:

The clocks examples shows the advantages of having the full flexibility of widget rendering available.

The colors example shows the a medium-size dataset rendered in  various ways.

The settings example shows that the column view more or less matches GtkTreeView, feature-wise.

Summary

This post gave an introduction to the new list widgets. There’s more that we haven’t touched on here, such as trees or the combo box replacement. One place to learn more about the new apis is the detailed introduction in the GTK documentation.

We’ve merged the listview infrastructure to master now. That doesn’t mean that it is finished. But we think it is ready for some wider use, and we hope to get feedback from you on what works, what doesn’t and what is missing.

And, to be clear, it also does not mean that we are removing treeviews and combo boxes from GTK 4—it is too late for that, and they are still used in many places inside GTK. That may be a GTK 5 goal.

Media in GTK 4

Showing moving pictures is ever more important. GTK 4 will make it easier for GTK apps to show animations; be that a programmatic animation, a webm file or a live stream.

Everything is paintable

Before looking at animations, it is worth spending a little bit of time on the underlying abstractions that GTK uses for content that can be drawn. In GTK 2 and 3, that was mainly GdkPixbuf: you load a file, and you get a block of pixel data (more or less in a single format).  If you wanted to animate it, there is GdkPixbufAnimation, but it is fair to say that it was not a very successful API.

GTK 4 brings a new API called GdkPaintable that was inspired by the CSS Houdini effort. It is very flexible—anything that you can plausibly draw can be a GdkPaintable. The content can be resizable (like svg), or change over time (like webm).

Widgets that typically show image content, like GtkImage or GtkPicture know how to use paintables. And many things that in the past would have produced pixel data in some form can now be represented as paintables: textures, icons, and even widgets.

If you have more specialized needs, anything that can be captured in a GtkSnapshot can be turned into a paintable with gtk_snapshot_to_paintable(). If you make a custom widget that wants to draw a paintable, that is very straightforward. Just call gdk_paintable_snapshot().

Getting animated

As I’ve said earlier, paintables can change their content over time. All it takes is for them to emit the ::contents-changed signal, and widgets like GtkPicture will do the right thing and update their display.

So, where do we get a GdkPaintable that changes its content? We can load it from a file, using GTK 4’s builtin GtkMediaFile api. This is a high-level api, comparable to GstPlayer: you stuff in a uri, and you get an object that has a play() function and a pause() function, and works as a paintable.

GTK ships with two implementations of GtkMediaFile, one using gstreamer and another one using ffmpeg. Since we don’t want to make either of these a hard dependency of GTK,  they are loadable modules.

You can open the GTK inspector to find out which one is in use:

Keeping control

The GtkMediaFile API is what gtk4-widget-factory demos with the animated GTK logo on its frontpage:

As you can see, it is not just a moving picture, there are media controls there too – you get these for free by using the GtkVideo widget.

Beyond the basics

Loading animations from files is maybe not that exciting, so here is another example that goes a little further. It is a little weekend project that combines GtkVideo, libportal and pipewire to demonstrate how to show a video stream in a GTK application.

The bad news is that we haven’t found a permanent home for the supporting glue code yet (a GstSink, a GdkPaintable and a GtkMediaStream). It doesn’t fit into GTK since, as mentioned, we don’t want to depend on gstreamer, and it doesn’t fit into gstreamer since GTK 4 isn’t released yet. We will certainly work that out before too long, since it is very convenient to turn a gstreamer pipeline into a paintable with a few lines of code.

The good news is that the core of the code is just a few lines:

fd = xdp_portal_open_pipewire_remote_for_camera (portal);
stream = gtk_gst_media_stream_new_for_pipewire_fd (fd, NULL);
gtk_video_set_media_stream (video, stream);

 

Custom widgets in GTK 4 – Actions

(This is the fifth part of a series about custom widgets in GTK 4. Part 1, part 2, part 3, part 4).

Activate all the things

Many things in GTK can be activated: buttons, check boxes, switches, menu items, and so on. Often, the same task can be achieved in multiple ways, for example copying the selection to the clipboard is available both via the Control-C shortcut and an item in the context menu.

Inside  GTK, there are many ways things can proceed: a signal may be emitted (::activate, or ::mnemonic-activate, or a keybinding signal), a callback may be called, or a GAction may be activated. None of this is entirely new in GTK 4, but we are moving towards using GActions as the primary mechanism for connecting actions.

Actions

Actions can appear in various forms in a GTK application.

First, there are global application actions, added to GtkApplication or GtkApplicationWindow (both of these implement the GActionGroup interface). This is where actions first appeared in GTK 3, mainly for the purpose of exporting them on the session bus for use with the app menu.

We also allow to associate actions with widgets by calling gtk_widget_insert_action_group(). Actions that are added in this way are only considered for activation when it originates in below the widget in the hierarchy.

A new way to create actions in GTK 4 is to declare actions in the class_init function, via gtk_widget_class_install_action(), similar to how properties are declared with g_object_class_install_property(). Actions created in this way are available for every instance of the widget.

Here is an example from GtkColorSwatch:

gtk_widget_class_install_action (widget_class,
                                 "color.customize", "(dddd)",
                                 customize_color);

The customize_color function is called when the color.customize action is activated. As you can see, actions can declare that they expect parameters. This is using GVariant syntax; you need to provide four double values.

A convenient shorthand allows you to create a stateful action to  set a property of your widget class:

gtk_widget_class_install_property_action (widget_class,
                                         "misc.toggle-visibility",
                                         "visibility");

This declares an action with the name misc.toggle-visibility, which toggles the value of the boolean visibility property.

Actionables and Menus

Declaring actions only goes so far, you also need to connect your actions to the UI in some form. For widgets like buttons or switches that implement the actionable interface, this is as easy as setting the action-name property:

gtk_actionable_set_action_name (GTK_ACTIONABLE (button),
                                "misc.toggle-visibility");

Of course, you can also do this in a ui file.

If you want to activate your actions from a menu, you will likely use a menu model that is constructed from XML, such as this:

<menu id="menu">
  <section>
    <item>
      <attribute name="label">Show text</attribute>
      <attribute name="action">misc.toggle-visibility</attribute>
    </item>
  </section>
</menu>

In GTK 3, you would connect to the ::populate-popup signal to add items to the context menus of labels or entries. In GTK 4, this is done by adding a menu model to the widget:

gtk_entry_set_extra_menu (entry, menu_model);

Going deeper

To learn more about actions in GTK 4, you can read the action overview in the GTK documentation.

Custom widgets in GTK 4 – Input

(This is the fourth part of a series about custom widgets in GTK 4. Part 1, part 2, part 3).

Event handlers take over

In the previous parts, we’ve seen a few examples where handling GtkWidget signals was replaced by some auxiliary objects. This trend is even stronger in the input area, where we’ve traditionally had a number of signals to handle: ::button-press-event, ::key-press-event,  ::touch-event, and so on. All of these signals are gone in GTK 4, and instead you are expected to add event controllers to your widget, and listen to their signals. For example, there are GtkGestureClick, GtkEventControllerKey, GtkGestureLongPress, and many more.

Event controllers can be created in ui files, but it is more common to do that in the init() function:

static void click_cb (GtkGestureClick *gesture,
                      int              n_press,
                      double           x,
                      double           y)
{
  GtkEventController *controller = GTK_EVENT_CONTROLLER (gesture);
  GtkWidget *widget = gtk_event_controller_get_widget (controller);

  if (x < gtk_widget_get_width (widget) / 2.0 &&
      y < gtk_widget_get_height (widget) / 2.0)
     g_print ("Red!\n");
}

...

  controller = gtk_gesture_click_new ();
  g_signal_handler_connect (controller, "pressed",
                            G_CALLBACK (click_cb), NULL);
  gtk_widget_add_controller (widget, controller);

gtk_widget_add_controller() takes ownership of the controller and GTK cleans controllers up automatically when the widget is finalized, so there is nothing more to do.

Complex event handlers

The examples of event handlers in the previous sections are simple and handle only individual events, one at a time. Gestures are a bit more involved, since they handle sequences of related events, and generally keep state.

Examples of much more complex event handlers include things like DND, and keyboard shortcuts.   We may cover some of these in a later article.

Going deeper

The unifying  principle behind all the different event handlers is that GTK propagates the events it receives from the windowing system from the root of the widget tree to a target widget, and back up again, in a pattern commonly referred to as capture-bubble.

In the case of keyboard events, the target widget is the current focus. For pointer events, it is the hovered widget under the pointer.

To read more about input handling in GTK, visit the input handling overview in the GTK documentation.

Outlook

We’ve reached the end of the prepared material for this series. It may continue at some point in the future, if there is interest. Possible topics include: shortcuts, actions and activation, drag-and-drop, focus handling, or accessibility.

Custom widgets in GTK 4 – Layout

(This is the third part of a series about custom widgets in GTK 4. Part 1, part 2).

Widgets are recommended

As we said earlier, “everything is a widget.” For example, we recommend that you use a GtkLabel instead of manually rendering a pango layout, or a GtkImage instead of manually loading and rendering a pixbuf.  Using a ready-made widget ensures that you get all of the expected behaviors, such as selection handling, context menus or hi-dpi support. And it is much easier than doing it all yourself.

Delegating Layout

The default implementations of the snapshot() and measure() functions are handling child widgets automatically. The main responsibility for a custom widget is to arrange the child widgets as required. In GTK 3, this would have been done by implementing the size_allocate() function. You can still do that. But in  GTK 4, a more convenient alternative is to use a layout manager. GTK comes with a number of predefined layout managers, such as GtkBoxLayout, GtkCenterLayout, GtkGridLayout, to name just a few.

A layout manager can be set up in various ways, the easiest is to set a layout manager type in your class_init function:

gtk_widget_class_set_layout_manager_type (widget_class, 
                                          GTK_TYPE_GRID_LAYOUT);

GTK will then automatically instantiate and use a layout manager of this type.

Layout managers wrap your child widgets in their own “layout child” objects, which can have properties that affect the layout. This is a replacement for child properties. And just like child properties, you can set these  “layout properties” in ui files:

<child>
  <object class="GtkLabel">
    <property name="label">Image:</property>
    <layout>
      <property name="left-attach">0</property>
    </layout>
  </object>
</child>

Adding children

Using templates is the most convenient way to add children to a widget. In GTK 4 that works for any widget, not just for containers. If for some reason, you need to create your child widgets manually, this is best done in your init() function:

void
demo_init (DemoWidget *demo)
{
  demo->label = gtk_label_new ("Image:");
  gtk_widget_set_parent (demo->label, GTK_WIDGET (demo));
}

When doing that, it is important to set up the correct parent-child relationships to make your child widgets part of the overall widget heirarchy. And this setup needs to be undone  in your dispose() function:

void
demo_dispose (GObject *object)
{
  DemoWidget *demo = DEMO_WIDGET (object);

  g_clear_pointer (&demo->label, gtk_widget_unparent);

  GTK_WIDGET_CLASS (demo_widget_parent_class)->dispose (object);
}

New possibilities

Layout managers nicely isolate the layout tasks from the rest of the widget machinery, which makes it easier to experiment with new layouts.

For example, GTK 4 includes GtkConstraintLayout, which uses a constraint solver to create layouts according to a set of constraints on widget sizes and positions.

To learn more about constraints in GTK 4, read the documentation for GtkConstraintLayout.

Outlook

In the next post, we’ll look how widgets in GTK 4 handle input.

Custom widgets in GTK 4 – Drawing

(This is the second part of a series about custom widgets in GTK 4. Part 1).

Drawing the old-fashioned way

Before looking at how widgets do their own drawing, it is worth pointing out that GtkDrawingArea is still a valid option if all you need is some self-contained cairo drawing.

The only difference between GTK 3 and GTK 4 is that you call gtk_drawing_area_set_draw_func() to provide your drawing function instead of connecting a signal handler to the the ::draw signal. Everything else is the same: GTK provides you with a cairo context, and you can just draw to it.

void
draw_func (GtkDrawingArea *da,
           cairo_t        *cr,
           int             width,
           int             height,
           gpointer        data)
{
  GdkRGBA red, green, yellow, blue;
  double w, h;

  w = width / 2.0;
  h = height / 2.0;

  gdk_rgba_parse (&red, "red");
  gdk_rgba_parse (&green, "green");
  gdk_rgba_parse (&yellow, "yellow");
  gdk_rgba_parse (&blue, "blue");

  gdk_cairo_set_source_rgba (cr, &red);
  cairo_rectangle (cr, 0, 0, w, h);
  cairo_fill (cr);

  gdk_cairo_set_source_rgba (cr, &green);
  cairo_rectangle (cr, w, 0, w, h);
  cairo_fill (cr);

  gdk_cairo_set_source_rgba (cr, &yellow);
  cairo_rectangle (cr, 0, h, w, h);
  cairo_fill (cr);

  gdk_cairo_set_source_rgba (cr, &blue);
  cairo_rectangle (cr, w, h, w, h);
  cairo_fill (cr);
}

...

gtk_drawing_area_set_draw_func (area, draw, NULL, NULL);

The rendering model

One of the major differences between GTK 3 and GTK 4 is that we are now targeting GL / Vulkan instead of cairo. As part of this switch, we have moved from an immediate mode rendering model to a retained mode one. In GTK 3, we were using cairo commands to render onto a surface. In GTK 4, we create a scene graph that contains render nodes, and those render nodes can be passed to renderer, or processed in some other way, or saved to a file.

In the widget API, this change is reflected in the difference between

gboolean (* draw) (GtkWidget *widget, cairo_t *cr)

and

 void (* snapshot) (GtkWidget *widget, GtkSnapshot *snapshot)

GtkSnapshot is an auxiliary object that turns your drawing commands into render nodes and adds them to the scene graph.

The CSS style information for a widget describes how to render its background, border, and so on. GTK translates this a series of function calls that add suitable render nodes to the scene graph, before and after the render nodes for the widgets’ content. So your widget automatically complies with the CSS drawing model, without any extra work.

Providing the render nodes for the content is the reponsibility of the widgets snapshot() implementation. GtkSnapshot has convenience API to make it easy.  For example, use gtk_snapshot_append_texture() to render a texture. Use gtk_snapshot_append_layout() to render text. If you want to use custom cairo drawing, gtk_snapshot_append_cairo() lets you do so.

A drawing widget

To implement a  widget that does some custom drawing, you need to implement the snapshot() function that creates the render nodes for your drawing:

void
demo_snapshot (GtkWidget *widget, GtkSnapshot *snapshot)
{
  GdkRGBA red, green, yellow, blue;
  float w, h;

  gdk_rgba_parse (&red, "red");
  gdk_rgba_parse (&green, "green");
  gdk_rgba_parse (&yellow, "yellow");
  gdk_rgba_parse (&blue, "blue");

  w = gtk_widget_get_width (widget) / 2.0;
  h = gtk_widget_get_height (widget) / 2.0;

  gtk_snapshot_append_color (snapshot, &red,
                             &GRAPHENE_RECT_INIT(0, 0, w, h));
  gtk_snapshot_append_color (snapshot, &green,
                             &GRAPHENE_RECT_INIT(w, 0, w, h));
  gtk_snapshot_append_color (snapshot, &yellow,
                             &GRAPHENE_RECT_INIT(0, h, w, h));
  gtk_snapshot_append_color (snapshot, &blue,
                             &GRAPHENE_RECT_INIT(w, h, w, h));
}

...

widget_class->snapshot = demo_snapshot;

This example produces four color nodes:

If your drawing needs a certain size, you should implement the measure() function too:

void
demo_measure (GtkWidget      *widget,
              GtkOrientation  orientation,
              int             for_size,
              int            *minimum_size,
              int            *natural_size,
              int            *minimum_baseline,
              int            *natural_baseline)
{
  *minimum_size = 100;
  *natural_size = 200;
}

...

widget_class->measure = demo_measure;

GTK keeps the render nodes produced by your snapshot() function and reuses them until you tell it that your widget needs to be drawn again by calling gdk_widget_queue_draw().

Going deeper

The GTK documentation has an overview of the GTK drawing model, if you are interested in reading more about this topic.

Outlook

In the next post, we’ll look how widgets in GTK 4 handle child widgets.

Custom widgets in GTK 4 – Introduction

With GTK 4 getting closer to completion, now is a good time to provide an overview of how custom widgets will look in GTK 4.

This series of posts will look at the major aspects of writing a widget, and how they changed compared to GTK 3. The articles will provide a high-level overview; for a detailed checklist for porting an application to GTK 4, look at the  migration guide.

Introduction

The general direction of our API changes has been to emphasize delegation over subclassing. One of the motivations for this is to make writing your own widgets easier and less error-prone. As a consequence, you will see a lot more auxiliary objects that take over aspects of functionality from core widget classes. And many widgets are now final classes – deriving directly from GtkWidget is expected.

Another general trend in our API is that “everything is a widget.” In GTK 3, we slowly broke up complex widgets into their constituent parts, first with CSS nodes and then with gadgets. In GTK 4, for example the trough and the slider of a GtkScale are fully formed sub-widgets which can maintain their own state and receive input like any other widget.

A big loser in the GTK 4 transition is the GtkContainer base class. It has become much less important. Any widget can have child widgets now. Child properties have been replaced by layout children and their properties. And all focus handling has been moved from GtkContainer to GtkWidget.

Another big loser is GtkWindow. In GTK 3, all the “popups”  (entry completion, menus, tooltips, etc) were using a GtkWindow underneath. In GTK 4, most of them have been converted to popovers, and the GtkPopover implementation has been untangled from GtkWindow. In addition, many pieces of toplevel-specific functionality have been broken out in separate interfaces called GtkRoot and GtkNative.

Outlook

In the next post, we’ll look how widgets in GTK 4 do their own drawing with render nodes.

GTK 3.98.2

When we released 3.98.0, we promised more frequent snapshots, as the remaining GTK 4 features are landing. Here we are a few weeks later, and 3.98.1 and 3.98.2 snapshots have quietly made it out.

So, what is new ?

Features

There is still work left to do, but a few more big features have landed.

The first is that we have completed the reimplementation of GtkPopovers as xdg-popup surfaces, and split up the GdkSurface API into separate GdkToplevel and GdkPopup interfaces (there’s a GdkDragSurface interface too), which reflect the different roles of surfaces:

  • Toplevels are sovereign windows that are placed by the user and can be maximized, fullscreened, etc.
  • Popups are positioned relative to a parent surface and often grab input, e.g. when used for menus.

In GTK, popovers have lost their :relative-to property, since they are now part of the regular hierarchy like any other widget, and GtkWindow has lost its :window-type property, since all instances of GTK_WINDOW_POPUP have been converted to popovers, and windows are just used for proper toplevels.

Another major feature is the new infrastructure for keyboard shortcuts. In the past, GTK has had a plethora of APIs to implement key bindings, mnemonics and accelerators. In GTK 4, all of this is handled by event controllers. GtkShortcutController is a bit more complex than typical event controllers, since it handles all the different kinds of shortcuts with a unified API.

Thankfully, most of the complexity is hidden. For widget implementors, the important APIs are the variants of gtk_widget_class_add_shortcut(), which are used to add key bindings. For applications, mnemonics and global accels (with gtk_application_set_accels_for_action()) work the same as before. Additionally, it is possible to create shortcut controllers and shortcuts in ui files.

A set of smaller features has landed in the form of a few GtkTextTag properties that expose new pango features such as overlines, visible rendering of spaces and control over hyphenation. These can now be controlled in a GtkTextView via tags. In entries, they can already be controlled by directly adding pango attributes.

Completions

When I wrote about 3.98, I said that the Drag-and-Drop refactoring was complete. That turned out to be not quite correct, and another round of DND work has landed since. These changes were informed by developer feedback on the Drag-and-Drop API. Yay for user testing!

We introduced separate GtkDropTarget and GtkDropTargetAsync event controllers, with the former being simplified to avoid all async API, which makes it very easy to handle local cases.

We also cleaned up internals of the DND implementation to group DND events into event sequences, handle them in just the same way as normal motion events,  and introduced GtkDropControllerMotion, which is an event controller that is designed to handle things like tab switching during a DND operation.

Finally, we could remove the remnants of X11-style property and selection APIs; GtkSelectionData and GdkAtom are gone.

Cleanups and fixes

As always, there’s a large number of smaller cleanups and fixes that have happened.

The biggest group of cleanups happened in the file chooser, where a number of marginally useful APIs (extra widgets, overwrite confirmation, :local-only, GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER, etc) have been dropped. To make up for it, the portal implementation of the native file chooser supports selecting folders now.

Another big cleanup was that GdkEvent is now an immutable boxed type. This was mainly an internal cleanup; the effect on application-level APIs is small, since event controllers have replaced direct event handling for the most part.

One new such event controller is GdkEventControllerFocus, which was split of from the key event controller to provide just focus handling.

GtkMenuButton lost its ability to have mnemonics when it was turned from a GtkButton subclass into a plain widget. This functionality has been reinstated, with a :use-underline property.

The HighContrast and HighContrastInverse themes that are included in GTK are now derived from Adwaita, for a much reduced maintainance burden and improved quality. Trying these themes out in gtk4-widget-factory is now easier, since we added a style menu.

The new HighContrast theme has also been backported to GTK 3.

Whats ahead

We will continue our snapshots and hope to get more developer feedback on the new APIs and features described above.

Here are things that we still want to integrate before GTK 4:

  • Row-recycling list and grid views
  • Revamped accessibility infrastructure
  • Animation API

If you want to follow the GTK 4 work, go here.

GTK 3.98

A few days ago, I’ve released a GTK 3.98 tarball. This is another step towards GTK 4. It is a little bit behind schedule, and does not quite include all the things we wanted to get into it, but it gets a lot closer to what we want to ship in GTK 4.

Almost 9 months have passed since the 3.96 snapshot, so there are quite a few new things to look at. Too many to cover them all, but here are some highlights:

Performance

The GL renderer has seen a steady flow of optimizations and performance improvements.

After the Westcoast hackfest last year, the GtkTextView scrolling performance has been greatly improved by making it cache render nodes for the visible range. At the same hackfest, the text caret blinking was changed to be smoothly animated, which is not relevant for performance at all, but looks cool.

Since the new year, a big focus has been on improving the performance of the CSS machinery. The CSS value implementation has been optimized to avoid computing values whenever possible. CSS lookups are using a Bloom filter now. And the IO for icon loading has been moved to a thread.

Most of the recent work was made possible by the sysprof profiling support that was added after the performance hackfest, and has recently been enhanced to report more information. To use it, simply start a GTK application with GTK_TRACE=1 in the environment, and load the resulting syscap file with sysprof.

DND

The DND refactoring has been completed. The GTK API for DND has been turned into event controllers: GtkDragSource and GtkDropTarget. Support for file transfers via file transfer portal has been added for both DND and the clipboard.  The underlying new infrastructure for data transfers has been covered in detail before.

GDK

The move of GDK towards Wayland concepts is continuing. This cleanup is not 100% complete yet.

Child surfaces have been removed. GDK only supports toplevel and popup surfaces now. The client-side window implementation has been removed too. Global positions and related APIs such as gdk_surface_move() are no longer available.

Grabs are no longer exposed as API. As a replacement, popup surfaces can be configured to hide on outside clicks.

XI2 is now mandatory when building the X11 backend, and support for the xim input method has been removed in favor of IBus.

The Wayland backend no longer relies on libwayland-cursor to load cursor themes, and loads individual cursors on demand.

GTK removals

Many classes have been made explicitly non-subclassable, and the widget hierarchy has been simplified, by making widgets derive directly from GtkWidget where possible.

GtkMenu, GtkMenuBar, GtkToolbar and related classes have been removed. They are being replaced by GMenu and popover-based variants. Popover menus can now do traditional, nested menus, and also show accelerators.

Context menus are no longer created with ::populate-popup signals, but also use menu models and actions. Creating those actions has been made easier with APIs like gtk_widget_class_install_action(), to create them in class_init.

GtkGestureMultiPress has been renamed to GtkGestureClick, to make it more obvious what this event controller is for.

GTK additions

We did not just remove things. Some new things have been added too.

The GtkNative interface has been introduced for widgets that have their own surface. This has been split off from the GtkRoot interface, which is exclusively for toplevel widgets without a parent.

A constraint-based layout manager has been added. It would be great to see people try this out. Please give us feedback if you do.

GtkTextView and other text widgets have gained a simple undo stack that can be used with Ctrl-Z.

The Emoji chooser widget has been made public.

Whats ahead

After 3.98, I’m planning to do more frequent snapshots, as the remaining outstanding items are landing. What are those items, you are asking ?

Here are the things that we still want to integrate before GTK 4:

– Event controllers for keyboard shortcuts
– Movable popovers
– Row-recycling list and grid views
– Revamped accessibility infrastructure
– Animation API