Libadwaita 1.2

So, half a year after 1.1, libadwaita 1.2 has been released.

While it doesn’t contain everything I had planned (since I ended up being mostly unavailable for about half of the cycle for reasons outside my control), it still has a bunch of additions, so let’s take a look at the changes.

Entry Rows

First, we have a widget that was planned for 1.0, but didn’t make it because it still needed work. This cycle I took time to finish and land it.

Originally implemented by Maximiliano, AdwEntryRow is a new type of boxed list rows, containing an inline entry. Its title doubles as the entry placeholder.

Entry rows in Libadwaita 1.2 demo

Entry rows can require confirmation via an apply button shown when its contents are edited. Otherwise, it’s similar to GtkEntry API, and can have prefix and suffix widgets like AdwActionRow.

There is also a companion widget AdwPasswordEntryRow, mirroring GtkPasswordEntry.

Message Dialogs

AdwMessageDialog with heading: Save Changes?, body text: Open document contains unsaved changes. Changes which are not saved will be permanently lost., and buttons: Cancel, Discard, Save. The Discard button has destructive appearance, the Save button has suggested appearance

AdwMessageDialog is a new widget that replaces GtkMessageDialog. On the first glance, it looks more or less the same. However, it has a few important differences.

Adaptive Layout

GtkMessageDialog tends to overflow off the screen very easily. AdwMessageDialog doesn’t because it’s fully adaptive: when scaling down the window, it first restricts its own size to the parent’s size:

AdwMessageDialog with heading: Save Changes?, body text: Open document contains unsaved changes. Changes which are not saved will be permanently lost., and buttons: Cancel, Discard, Save. The Discard button has destructive appearance, the Save button has suggested appearance. When the window is shrunk down to mobile size, it becomes narrower

And if that’s not enough to fit, it arranges its buttons vertically:

AdwMessageDialog with heading: Save Changes?, body text: Open document contains unsaved changes. Changes which are not saved will be permanently lost., and buttons: Cancel, Close without Saving, Save As…. The Close without Saving button has destructive appearance, the Save As… button has suggested appearance. When the window is shrunk down to mobile size, it rearranges buttons vertically

Additionally, it always uses the vertical arrangement if the dialog would end up ridiculously wide otherwise, even when there is enough space to display it like that.

API

The second important thing is that it’s not a GtkDialog subclass and, as such, has completely new API.

  • GtkMessageDialog inherits the response API from GtkDialog. It relies on integer IDs that are provided by applications, except when they aren’t – the GtkResponseType enum provides a few predefined responses. This is a very C-centric API (as it assumes enum values being interchangeable with integers), and it’s not overly convenient to use. AdwMessageDialog replaces it with simple string IDs that are always provided by applications.

    This also allowed the response signal to be detailed, using the response ID as the detail.

    In addition, AdwMessageDialog is automatically closed on response, and doesn’t need to be closed manually. As such, common code like this:

    static void
    dialog_response_cb (GtkDialog       *dialog,
                        GtkResponseType  response,
                        MyObject        *self)
    {
      if (response == GTK_RESPONSE_ACCEPT)
        my_object_foo (self);
    
      gtk_window_destroy (GTK_WINDOW (dialog);
    }
    
    ...
    
    g_signal_connect (dialog, "response",
                      G_CALLBACK (dialog_response_cb), self);
    

    can be replaced with just:

    g_signal_connect_swapped (dialog, "response::accept",
                              G_CALLBACK (my_object_foo), self);
    
  • Just as AdwMessageDialog doesn’t have predefined responses and leaves that to apps, it doesn’t use GtkButtonsType. It seems counterproductive to have API to easily create buttons like “OK”, “Cancel”, “Yes” and “No” when we’ve been discouraging labels like this for just about two decades now, doesn’t it?
  • No direct widget access. GtkMessageDialog required apps to poke into its widgetry to pack additional widgets, add style classes to its buttons and so on. AdwMessageDialog can have a single extra child, displayed below the body text and managed with the extra-child property, and natively supports changing button appearance instead.

    AdwMessageDialog from GNOME Text Editor, with heading: Save Changes?, body text: Open document contains unsaved changes. Changes which are not saved will be permanently lost., and buttons: Cancel, Discard, Save. The Discard button has destructive appearance, the Save button has suggested appearance. It has an additional child under the body text - a boxed list with one unsaved file called "123"

  • While the intention of GtkMessageDialog is to use its primary and secondary text, it also displays the window title, and many applications get it wrong, ending up with three labels or wrong styling:

    GtkMessageDialog from LibreOffice Writer. It has three labels: title: Save Document? primary text: Save changes to document "Untitled 1" before closing? secondary text: Your changes will be lost if you don't save them.

    GtkMessageDialog from Rnote. It has title: Quit Application, and primary text: Any unsaved changes will be lost. Do you want to quit anyways?, but no secondary text, so both labels appear wrong.

    Primary text also changes its styles to look like a heading or body text, depending on whether secondary text is set.

    AdwMessageDialog does not do any of that. It just has heading and body, that’s it.

  • Finally, it’s derivable. While making GtkMessageDialog final in GTK4 was consistent with most of the other GTK widgets, it has generally resulted in vastly more convoluted code for apps with complex dialogs – for example, the save dialog in GNOME Text Editor with its list of open files.

So now app developers have no excuses for shipping unpolished message dialogs anymore. 😉️

About Window

Another widget originally planned for 1.0 that didn’t make it was Adrien’s AdwAboutWindow, replacing GtkAboutDialog. This cycle I finished it up and merged it.

The new window is adaptive and natively provides a lot of details people were doing hacks with GtkAboutDialog for before:

  • Legal information for dependencies and other components;

    A screenshot of legal information from GNOME Maps, listing information for map data provider, map tile provider and search provider

  • A list of acknowledgements in addition to credits — for example, for crowdfunding backers;
  • A place to list recent changes in the application;
  • Links to the application’s website, issue tracker, support forum, as well as any additional links, as opposed to a single link in GtkAboutDialog;
  • Debugging information, along with easy ways to save or copy it.

    Screenshot of debugging information from GNOME Text Editor's AdwAboutWindow

Christopher Davis has ported core apps using libadwaita to AdwAboutWindow, so it’s also pretty consistently used by now.

Tab View and Tab Bar Additions

AdwTabView and AdwTabBar have seen quite a few changes this cycle.

The most important change is with shortcut handling. AdwTabView provides a lot of shortcuts for switching and reordering tabs, for example, Ctrl+Tab to switch to the next tab, or Alt+1Alt+0 to switch to the first 10 tabs.

In Libhandy, these shortcuts were added via the shortcut-widget property, with the idea that you set it to your toplevel window unless you have multiple tab views.

In Libadwaita 1.0, that property was removed, and instead the shortcuts were switched to the GTK4 shortcut engine, using global scope (really MANAGED scope, but doesn’t matter). This means that they were available for the whole window automatically.

However, this also brought issues. For example, the Ctrl+Tab shortcut stopped working, because GTK defines that shortcut itself, for changing widget focus. Similarly, GTK4 port of the VTE library handles all input on the CAPTURE propagation phase, meaning none of the tab view shortcuts could work at all in the GTK4 port of GNOME Console.

Meanwhile in both libhandy and libadwaita 1.0.x-1.1.x, apps that want to disable some of the shortcuts had to do pretty terrible hacks to achieve it, for example, registering the same shortcuts on the toplevel window and redirecting input to the tab contents or whatever widget needs to handle them.

So, to fix that, AdwTabView has switched its shortcuts to the CAPTURE phase as well, and added an easy way to enable or disable shortcuts.

Other than that, AdwTabPage now has a property to set the tooltip for its indicator icon, and AdwTabBar tabs similarly has a tooltip on the tab close buttons – not customizable this time.

Toast Additions

  • Kévin Commaille added a way to have custom widgets as toast titles. This allows, for example, to have avatar+name pills inside toasts in Fractal.
  • Emmanuele Bassi added a helper function for creating a toast with formatted title.
  • Jamie added AdwToast::button-clicked signal to make it possible to have toast buttons without actions.
  • Jonathan Blandford made it possible to add or dismiss toasts multiple times to make them easier to use. Dismissing toasts that have already been dismissed does nothing now, while adding toasts that have already been added resets the timeout for toasts that are currently shown, or bumps them forward in the queue otherwise. Previously doing that resulted in criticals being printed.

Other Additions

  • AdwPropertyAnimationTarget has been added to simplify animating object properties.
  • AdwAnimation can now change targets on the fly.
  • Sophie Herold added a property to allow disabling Pango markup in boxed list row titles (as well as subtitles in case of AdwActionRow etc).
  • AdwSplitButton can now have a separate tooltip for its dropdown button.
  • AdwViewStack has gained a helper function to add a child along with a title and an icon.
  • JCWasmx86 added plumbing to AdwStyleManager to allow GNOME Builder to launch apps with light/default/dark color scheme, as well as in high contrast. This has also been backported to Libhandy, and is available in 1.8.0 (which doesn’t have a blog post as it’s the only noteworthy change there).

Styles

AdwTabBar in Libadwaita demo 1.2

A few styles have been tweaked. In particular, AdwTabBar has seen big changes, mainly to improve contrast in dark variant and make it clearer which tab is selected.

A comparison between AdwTabBar in Libadwaita 1.1 and 1.2, in dark style

The lack of full height separators has also allowed to remove the distracting light borders from header bars and search bars in favor of dark borders using @headerbar_shade_color and add a proper backdrop style consistent with header bars.

In addition, GtkActionbar and AdwViewSwitcherBar now have the same style as header bars, search bars and tab bars, while boxed lists use dark separators.

Alarms page from GNOME Clocks with Libadwaita 1.2

The .large-title style class is now documented as deprecated and apps should use .title-1 instead.

There are also a few smaller changes, for example:

  • Toasts without buttons are smaller now, instead of taking a fixed width.
  • AdwActionRow child spacing is now smaller, matching toolbars and the new AdwEntryRow.
  • AdwExpanderRow arrows have changed orientation to look less like rows that open subpages on click.
  • Lots of fixes, some of them have been backported to libadwaita 1.1.x as well.

As it happens, quite a lot didn’t make it because it needed more work, or the timing just didn’t work out. This also means that there’s a lot to look forward to in the next release though, so stay tuned.

Thanks to all the contributors, and thanks to my employer, Purism, for letting me work on Libadwaita and GTK to make this release happen.