Data Driven UI With Closures

It’s highly recommended to read my previous blog post first to understand some of the topics discussed here.

UI can be hard to keep track of when changed imperatively, preferably it just follows the code’s state. Closures provide an intuitive way to do so by having data as input, and the desired value as output. They couple data with UI, but decouple the specific piece of UI that’s changed, making closures very modular. The example in this post uses Python and Blueprint.

Technicalities

First, it’s good to be familiar with the technical details behind closures. To quote from Blueprint’s documentation:

Expressions are only reevaluated when their inputs change. Because Blueprint doesn’t manage a closure’s application code, it can’t tell what changes might affect the result. Therefore, closures must be pure, or deterministic. They may only calculate the result based on their immediate inputs, not properties of their inputs or outside variables.

To elaborate, expressions know when their inputs have changed due to the inputs being GObject properties, which emit the “notify” signal when modified.

Another thing to note is where casting is necessary. To again quote Blueprint’s documentation:

Blueprint doesn’t know the closure’s return type, so closure expressions must be cast to the correct return type using a cast expression.

Just like Blueprint doesn’t know about the return type, it also doesn’t know the type of ambiguous properties. To provide an example:

Button simple_button {
  label: _("Click");
}

Button complex_button {
  child: Adw.ButtonContent {
    label: _("Click");
  };
}

Getting the label of simple_button in a lookup does not require a cast, since label is a known property of Gtk.Button with a known type:

simple_button.label

While getting the label of complex_button does require a cast, since child is of type Gtk.Widget, which does not have the label property:

complex_button.child as <Adw.ButtonContent>.label

Example

To set the stage, there’s a window with a Gtk.Stack which has two Gtk.StackPages, one for the content and one for the loading view:

from gi.repository import Adw, Gtk


@Gtk.Template.from_resource("/org/example/App/window.ui")
class Window(Adw.ApplicationWindow):
    """The main window."""

    __gtype_name__ = "Window"
using Gtk 4.0;
using Adw 1;

template $Window: Adw.ApplicationWindow {
  title: _("Demo");

  content: Adw.ToolbarView {
    [top]
    Adw.HeaderBar {}

    content: Stack {
      StackPage {
        name: "content";

        child: Label {
          label: _("Meow World!");
        };
      }

      StackPage {
        name: "loading";

        child: Adw.Spinner {};
      }
    };
  };
}

Switching Views Conventionally

One way to manage the views would be to rely on signals to communicate when another view should be shown:

from typing import Any

from gi.repository import Adw, GObject, Gtk


@Gtk.Template.from_resource("/org/example/App/window.ui")
class Window(Adw.ApplicationWindow):
    """The main window."""

    __gtype_name__ = "Window"

    stack: Gtk.Stack = Gtk.Template.Child()

    loading_finished = GObject.Signal()

    @Gtk.Template.Callback()
    def _show_content(self, *_args: Any) -> None:
        self.stack.set_visible_child_name("content")

A reference to the stack has been added, as well as a signal to communicate when loading has finished, and a callback to run when that signal is emitted.

using Gtk 4.0;
using Adw 1;

template $Window: Adw.ApplicationWindow {
  title: _("Demo");
  loading-finished => $_show_content();

  content: Adw.ToolbarView {
    [top]
    Adw.HeaderBar {}

    content: Stack stack {
      StackPage {
        name: "content";

        child: Label {
          label: _("Meow World!");
        };
      }

      StackPage {
        name: "loading";

        child: Adw.Spinner {};
      }
    };
  };
}

A signal handler has been added, as well as a name for the Gtk.Stack.

Only a couple of changes had to be made to switch the view when loading has finished, but all of them are sub-optimal:

  1. A reference in the code to the stack would be nice to avoid
  2. Imperatively changing the view makes following state harder
  3. This approach doesn’t scale well when the data can be reloaded, it would require another signal to be added

Switching Views With a Closure

To use a closure, the class needs data as input and a method to return the desired value:

from typing import Any

from gi.repository import Adw, GObject, Gtk


@Gtk.Template.from_resource("/org/example/App/window.ui")
class Window(Adw.ApplicationWindow):
    """The main window."""

    __gtype_name__ = "Window"

    loading = GObject.Property(type=bool, default=True)

    @Gtk.Template.Callback()
    def _get_visible_child_name(self, _obj: Any, loading: bool) -> str:
        return "loading" if loading else "content"

The signal has been replaced with the loading property, and the template callback has been replaced by a method that returns a view name depending on the value of that property. _obj here is the template class, which is unused.

using Gtk 4.0;
using Adw 1;

template $Window: Adw.ApplicationWindow {
  title: _("Demo");

  content: Adw.ToolbarView {
    [top]
    Adw.HeaderBar {}

    content: Stack {
      visible-child-name: bind $_get_visible_child_name(template.loading) as <string>;

      StackPage {
        name: "content";

        child: Label {
          label: _("Meow World!");
        };
      }

      StackPage {
        name: "loading";

        child: Adw.Spinner {};
      }
    };
  };
}

In Blueprint, the signal handler has been removed, as well as the unnecessary name for the Gtk.Stack. The visible-child-name property is now bound to a closure, which takes in the loading property referenced with template.loading.

This fixed the issues mentioned before:

  1. No reference in code is required
  2. State is bound to a single property
  3. If the data reloads, the view will also adapt

Closing Thoughts

Views are just one UI element that can be managed with closures, but there’s plenty of other elements that should adapt to data, think of icons, tooltips, visibility, etc. Whenever you’re writing a widget with moving parts and data, think about how the two can be linked, your future self will thank you!

Cleaner Code With GObject

I see a lot of users approaching GNOME app development with prior language-specific experience, be it Python, Rust, or something else. But there’s another way to approach it: GObject-oriented and UI first.

This introduces more declarative code, which is generally considered cleaner and easier to parse. Since this approach is inherent to GTK, it can also be applied in every language binding. The examples in this post stick to Python and Blueprint.

Properties

While normal class properties for data work fine, using GObject properties allows developers to do more in UI through expressions.

Handling Properties Conventionally

Let’s look at a simple example: there’s a progress bar that needs to be updated. The conventional way of doing this would look something like the following:

using Gtk 4.0;
using Adw 1;

template $ProgressBar: Adw.Bin {
  child: ProgressBar progress_bar {};
}

This defines a template called ProgressBar which extends Adw.Bin and contains a Gtk.ProgressBar called progress_bar.

The reason why it extends Adw.Bin instead of Gtk.ProgressBar directly is because Gtk.ProgressBar is a final class, and final classes can’t be extended.

from gi.repository import Adw, GLib, Gtk

@Gtk.Template(resource_path="/org/example/App/progress-bar.ui")
class ProgressBar(Adw.Bin):

    __gtype_name__ = "ProgressBar"

    progress_bar: Gtk.ProgressBar = Gtk.Template.Child()

    progress = 0.0

    def __init__() -> None:
        super().__init__()

        self.load()

    def load(self) -> None:
        self.progress += 0.1
        self.progress_bar.set_fraction(self.progress)

        if int(self.progress) == 1:
            return

        GLib.timeout_add(200, self.load)

This code references the earlier defined progress_bar and defines a float called progress. When initialized, it runs the load method which fakes a loading operation by recursively incrementing progress and setting the fraction of progress_bar. It returns once progress is 1.

This code is messy, as it splits up the operation into managing data and updating the UI to reflect it. It also requires a reference to progress_bar to set the fraction property using its setter method.

Handling Properties With GObject

Now, let’s look at an example of this utilizing a GObject property:

using Gtk 4.0;
using Adw 1;

template $ProgressBar: Adw.Bin {
  child: ProgressBar {
    fraction: bind template.progress;
  };
}

Here, the progress_bar name was removed since it isn’t needed anymore. fraction is bound to the template’s (ProgressBar‘s) progress property, meaning its value is synced.

from gi.repository import Adw, GLib, GObject, Gtk

@Gtk.Template(resource_path="/org/example/App/progress-bar.ui")
class ProgressBar(Adw.Bin):

    __gtype_name__ = "ProgressBar"

    progress = GObject.Property(type=float)

    def __init__() -> None:
        super().__init__()

        self.load()

    def load(self) -> None:
        self.progress += 0.1

        if int(self.progress) == 1:
            return

        GLib.timeout_add(200, self.load)

The reference to progress_bar was removed in the code too, and progress was turned into a GObject property instead. fraction doesn’t have to be manually updated anymore either.

So now, managing the data and updating the UI merged into a single property through a binding, and part of the logic was put into a declarative UI file.

In a small example like this, it doesn’t matter too much which approach is used. But in a larger app, using GObject properties scales a lot better than having widget setters all over the place.

Communication

Properties are extremely useful on a class level, but once an app grows, there’s going to be state and data communication across classes. This is where GObject signals come in handy.

Handling Communication Conventionally

Let’s expand the previous example a bit. When the loading operation is finished, a new page has to appear. This can be done with a callback, a method that is designed to be called by another method, like so:

using Gtk 4.0;
using Adw 1;

template $NavigationView: Adw.Bin {
  child: Adw.NavigationView navigation_view {
    Adw.NavigationPage {
      child: $ProgressBar progress_bar {};
    }

    Adw.NavigationPage {
      tag: "finished";

      child: Box {};
    }
  };
}

There’s now a template for NavigationView, which extends an Adw.Bin for the same reason as earlier, which holds an Adw.NavigationView with two Adw.NavigationPages.

The first page has ProgressBar as its child, the other one holds a placeholder and has the tag “finished”. This tag allows for pushing the page without referencing the Adw.NavigationPage in the code.

from gi.repository import Adw, Gtk

from example.progress_bar import ProgressBar

@Gtk.Template(resource_path="/org/example/App/navigation-view.ui")
class NavigationView(Adw.Bin):

    __gtype_name__ = "NavigationView"

    navigation_view: Adw.NavigationView = Gtk.Template.Child()
    progress_bar: ProgressBar = Gtk.Template.Child()

    def __init__(self) -> None:
        super().__init__()

        def on_load_finished() -> None:
            self.navigation_view.push_by_tag("finished")

        self.progress_bar.load(on_load_finished)

The code references both navigation_view and progress_bar. When initialized, it runs the load method of progress_bar with a callback as an argument.

This callback pushes the Adw.NavigationPage with the tag “finished” onto the screen.

from typing import Callable

from gi.repository import Adw, GLib, GObject, Gtk

@Gtk.Template(resource_path="/org/example/App/progress-bar.ui")
class ProgressBar(Adw.Bin):

    __gtype_name__ = "ProgressBar"

    progress = GObject.Property(type=float)

    def load(self, callback: Callable) -> None:
        self.progress += 0.1

        if int(self.creation_progress) == 1:
            callback()
            return

        GLib.timeout_add(200, self.load, callback)

ProgressBar doesn’t run load itself anymore when initialized. The method also got an extra argument, which is the callback we passed in earlier. This callback gets run when the loading has finished.

This is pretty ugly, because the parent class has to run the operation now.

Another way to approach this is using a Gio.Action. However, this makes illustrating the point a bit more difficult, which is why a callback is used instead.

Handling Communication With GObject

With a GObject signal the logic can be reversed, so that the child class can communicate when it’s finished to the parent class:

using Gtk 4.0;
using Adw 1;

template $NavigationView: Adw.Bin {
  child: Adw.NavigationView navigation_view {
    Adw.NavigationPage {
      child: $ProgressBar {
        load-finished => $_on_load_finished();
      };
    }

    Adw.NavigationPage {
      tag: "finished";

      child: Box {};
    }
  };
}

Here, we removed the name of progress_bar once again since we won’t need to access it anymore. It also has a signal called load-finished, which runs a callback called _on_load_finished.

from gi.repository import Adw, Gtk

from example.progress_bar import ProgressBar

@Gtk.Template(resource_path="/org/example/App/navigation-view.ui")
class NavigationView(Adw.Bin):

    __gtype_name__ = "NavigationView"

    navigation_view: Adw.NavigationView = Gtk.Template.Child()

    @Gtk.Template.Callback()
    def _on_load_finished(self, _obj: ProgressBar) -> None:
        self.navigation_view.push_by_tag("finished")

In the code for NavigationView, the reference to progress_bar was removed, and a template callback was added, which gets the unused object argument. It runs the same navigation action as before.

from gi.repository import Adw, GLib, GObject, Gtk

@Gtk.Template(resource_path="/org/example/App/progress-bar.ui")
class ProgressBar(Adw.Bin):

    __gtype_name__ = "ProgressBar"

    progress = GObject.Property(type=float)
    load_finished = GObject.Signal()

    def __init__(self) -> None:
        super().__init__()

        self.load()

    def load(self) -> None:
        self.progress += 0.1

        if int(self.creation_progress) == 1:
            self.emit("load-finished")
            return

        GLib.timeout_add(200, self.load)

In the code for ProgressBar, a signal was added which is emitted when the loading is finished. The responsibility of starting the load operation can be moved back to this class too. The underscore and dash are interchangeable in the signal name in PyGObject.

So now, the child class communicates to the parent class that the operation is complete, and part of the logic is moved to a declarative UI file. This means that different parent classes can run different operations, while not having to worry about the child class at all.

Next Steps

Refine is a great example of an app experimenting with this development approach, so give that a look!

I would also recommend looking into closures, since it catches some cases where an operation needs to be performed on a property before using it in a binding.

Learning about passing data from one class to the other through a shared object with a signal would also be extremely useful, it comes in handy in a lot of scenarios.

And finally, experiment a lot, that’s the best way to learn after all.

Thanks to TheEvilSkeleton for refining the article, and Zoey for proofreading it.

Happy hacking!

Introducing Adwaita Fonts

Cantarell has been used as the default interface font since November 2010, but unfortunately, font technology is moving forward, while Cantarell isnʼt.

Similarly, Source Code Pro was used as the default monospace font, but its maintenance hasnʼt been well. Aesthetically, it has fallen out of taste too.

GNOME was ready to move on, which is why the Design Team has been putting effort into making the switch to different fonts in recent cycles.

The Sans

Inter was quite a straightforward choice, due to its modern design, active maintenance, and font feature support. It might be the most popular open source sans font, being used in Figma, GitLab, and many other places.

An issue was created to discuss the font. From this, a single design tweak was decided on: the lowercase L should be disambiguated.

A formal initiative was made for the broader community to try out the font, catch issues that had to be resolved, and look at the platform to see where we need to change anything in terms of visuals. Notably, the Shell lock screen got bolder text.

At this point, some issues started popping up, including some nasty Cantarell-specific hacks in Shell, and broken small caps in Software. These were quickly fixed thereafter, and due to GTKʼs robust font adaptivity, apps were mostly left untouched.

However, due to Interʼs aggressive use of calt, some unintended behavior arose in arbitrary strings as a result of ligatures. There were two fixes for this, but they would both add maintenance costs which is what weʼre trying to move away from:

  1. Subset the font to remove calt entirely
  2. Fork the font to remove the specific ligature that caused issues

This blocked the font from being the default in GNOME 47, as Rasmus, the Inter maintainer, was busy at the time, and the lack of contact brought some uncertainty into the Design Team. Luckily, when Rasmus returned during the 48 development cycle, he removed the problematic ligature and Inter was back in the race.

No further changes were required after this, and Inter, now as Adwaita Sans, was ready for GNOME 48.

The Mono

After the sans font was decided on as Inter, we wanted a matching monospace font. Our initial font selection consisted of popular monospace fonts and recommendations from Rasmus.

We also made a list of priorities, the new font would need:

  1. A style similar to Adwaita Sans
  2. Active maintenance
  3. Good legibility
  4. Large language coverage

Some fonts on our initial font selection fell off due to shortcomings in this list, and we were left with IBM Plex Mono, Commit Mono and Iosevka.

Just like for the sans font, we made a call for testing for these three fonts. The difference in monospace fonts can be quite hard to notice, so the non-visual benefits of the fonts were important.

The favorite among users was Commit Mono, due to its fairly close neutral design to Adwaita Sans. However, the font that we ended up with was Iosevka. This made some people upset, but this decision was made for a couple of reasons:

  1. Iosevka has more active maintenance
  2. Iosevkaʼs configuration might have the best free tooling out there
  3. When configured, Iosevka can look extremely similar to Adwaita Sans
  4. The language coverage of Iosevka is considerably larger

So, in the end, kramo and me went through all its glyphs, configured them to look as close to Adwaita Sans as possible, and made that Adwaita Mono.

Naming

We wanted unique names for the fonts, because it will allow us to more easily switch them out in the future if necessary. Only the underlying repository will have to change, nothing else.

The configured Inter was originally named GNOME UI Font, but due to the introduction of the monospace font and our design system being called Adwaita, we moved the fonts under its umbrella as Adwaita Fonts.

Technical Details

We use OpenType Feature Freezer to get the disambiguated lowercase L in Inter, as recommended by upstream.

Iosevka has their own configuration system which allows you to graphically customize the font, and export a configuration file that can be used later down the line.

The repository which hosts the fonts originally started out with the goal to allow distributions to build the fonts themselves, which is why it used Makefiles with the help of Rose.

Due to Iosevka requiring NPM packages to be configured, the scope was changed to shipping the TTF files themselves. Florian Müllner therefore ported the repository to shell scripts which allows us to update the files only, heavily simplifying the maintenance process.

The repository and fonts are licensed under the SIL Open Font License.

Conclusion

We want to thank everyone that contributed to this font switch by testing, discussing, and coding!

Adwaita Fonts will be the default in GNOME 48, and we hope youʼre as happy with this change as we are.