It’s now time for a new Gtk-rs release! As usual, a lot of improvements in a lot of places but subclasses in particular got a lot of updates. Time to dive in on some improvements. Enjoy!

Update of minimum supported versions §

The minimum supported version of the C library of various crates was updated to the versions available in Ubuntu 18.04:

  • GLib/GIO requires at least version 2.56 now and supports API up to version 2.74
  • gdk-pixbuf requires at least version 2.36.8 and supports API up to version 2.42
  • Pango requires at least version 1.40 and supports API up to version 1.52
  • GTK3 requires at least version 3.22.30 and supports API up to version 3.24.30
  • GTK4 requires at least version 4.0.0 and supports API up to version 4.8

The minimum supported Rust version by all crates was updated to version 1.63.

More async support §

A couple of futures helper functions were added with this release that should make futures usage easier in GTK applications.

  • Timeouts: glib::future_with_timeout(Duration::from_millis(20), fut) will resolve to the result of fut if it takes less than 20ms or otherwise to an error
  • Cancellation: gio::CancellableFuture::new(fut, cancellable) will resolve to the future unless the cancellable is cancelled before that

GTK 4 §

Along with plenty of bugfixes and small improvements, the 0.5 release of gtk4-rs brings a couple of useful features

  • gsk::Transform

Most of the C functions return NULL which represents an identity transformation. The Rust API nowadays returns an identity transformation instead of None

  • #[gtk::CompositeTemplate] runtime validation

If used with #[template(string=...)] or #[template(file=...)] and the xml_validation feature is enabled, the XML content will be validated to ensure the child widgets that the code is trying to retrieve exists.

  • #[gtk::test]

As GTK is single-threaded, using it in your tests is problematic as you can’t initialize GTK multiple times. The new attribute macro helps with that as it runs the tests in a thread pool instead of in parallel.

Details at https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4_macros/attr.test.html

  • WidgetClassSubclassExt::install_action_async

In a lot of use cases, people used to do

klass.install_action("some_group.action", None, |widget, _, _| {
    let ctx = glib::MainContext::default();
    ctx.spawn_local(clone!(@weak widget => async move {
        /// call some async function
        widget.async_function().await;
    }));
})

The new helper function allows you to write the code above like

klass.install_action_async("some_group.action", None, |widget, _, _| async move {
    /// call some async function
    widget.async_function().await;
})

Which helps avoiding the usage of the clone macro in some cases.

  • GTK 4.8 API additions which can be enabled with v4_8
  • Various fixes for the gdk-wayland & wayland-rs integration requiring wayland-client v0.30
  • Plenty of new examples:
    • Gif Paintable for rendering Gifs
    • ColumnView for displaying data in a table-like format
    • Confetti animation
    • Rotation / Squeezer example

glib::Object now has a more convenient object builder §

In addition to dynamic objects construction, or e.g. when implementing new GObject subclasses, via glib::Object::new() and related API, there is also a more convenient object builder available.

let obj = glib::Object::builder::<MyObject>()
    .property("prop1", 123)
    .property("prop2", "test")
    .property("prop3", false)
    .build();

This allows for slightly more idiomatic Rust code.

GIO objects completion closure doesn’t need to be Send anymore §

Asynchronous operations on GIO objects previously required the completion closure to be Send. This is not required anymore as the objects themselves are not Send-able either and the operation will be completed on the current thread via the thread’s own main context. This should make usage of the asynchronous operations easier from GTK applications, where all UI objects are not Send.

As a result of this, also futures provided by GIO objects based on these asynchronous operations do not implement the Send trait anymore, which was wrong to begin with and caused panics at runtime in any situation where the future was actually used on different threads.

Added support for #[derive(glib::Variant)] for enums §

Starting with the previous release it was possible to derive the required trait implementations for (de)serializing Rust structs from/to glib::Variants. With this release, support for enums is also added with different options for how the enum is going to be represented. Both C-style enums as well as enums with values in the variants are supported.

#[derive(Debug, PartialEq, Eq, glib::Variant)]
enum Foo {
    MyA,
    MyB(i32),
    MyC { some_int: u32, some_string: String }
}
let v = Foo::MyC { some_int: 1, some_string: String::from("bar") };
let var = v.to_variant();
assert_eq!(var.child_value(0).str(), Some("my-c"));
assert_eq!(var.get::<Foo>(), Some(v));

No need to implement Send and Sync for subclasses anymore §

In the past it was necessary to manually implement the Send and Sync traits via an unsafe impl block for the object types of GObject subclasses defined in Rust.

glib::wrapper! {
    pub struct MyObject(ObjectSubclass<imp::MyObject>);
}

unsafe impl Send for MyObject {}
unsafe impl Sync for MyObject {}

This is not necessary anymore and happens automatically if the implementation struct implements both traits. Like this it is also harder to accidentally implement the traits manually although the requirements of them are not fulfilled.

Simpler subclassing for virtual methods §

Previously when creating a GObject subclass, all virtual methods passed the implementation struct and the object itself as arguments, e.g.

pub trait ObjectImpl {
    fn constructed(&self, obj: &Self::Type);
}

This caused a lot of confusion and was also redundant. Instead, starting with this release only the implementation struct is passed by reference, e.g.

pub trait ObjectImpl {
    fn constructed(&self);
}

In most contexts the object itself was not actually needed, so this also simplifies the code. For the cases when the object is needed, it can be retrieved via the obj() method on the implementation struct

impl ObjectImpl for MyObject {
    fn constructed(&self) {
        self.parent_constructed();
        let obj = self.obj();
        obj.do_something();
    }
}

In a similar way it is also possible to retrieve the implementation struct from the instance via the imp() method. Both methods are cheap and only involve some pointer arithmetic.

Additionally, to make it easy to pass around the implementation struct into e.g. closures, there is now also a reference counted wrapper around it available (ObjectImplRef) that can be retrieved via imp.to_owned() or imp.ref_counted(), and a weak reference variant that can be retrieved via imp.downgrade(). Both are working in combination with the glib::clone! macro, too.

impl ObjectImpl for MyObject {
    fn constructed(&self) {
        self.parent_constructed();
        // for a strong reference
        self.button.connect_clicked(glib::clone!(@to-owned self as imp => move |button| {
            imp.do_something();
        }));

        // for a weak reference
        self.button.connect_clicked(glib::clone!(@weak self as imp => move |button| {
            imp.do_something();
        }));
    }
}

Simpler subclassing for properties §

When creating properties for GObject subclasses they need to be declared beforehand via a glib::ParamSpec. Previously these had simple new() functions with lots of parameters. These still exist but it’s usually more convenient to use the builder pattern to construct them, especially as most of the parameters have sensible defaults.

// Now
let pspec = glib::ParamSpecUInt::builder("name")
    .maximum(1000)
    .construct()
    .build();

// Previously
let pspec = glib::ParamSpecUInt::new("name", None, None, 0, 1000, 0, glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT);

In a similar spirit, signal definitions are available via a builder. This was available in the previous already but usage was simplified, for example by defaulting to no signal arguments and () signal return type.

// Now
let signal = glib::subclass::Signal::builder("closed").build();

// Before
let signal = glib::subclass::Signal::builder("closed", &[], glib::Type::UNIT).build();

Removing Result<> wrapping in some functions returned values §

glib::Object::new() returned a Result in previous versions. However, the only way how this could potentially fail was via a programming error: properties that don’t exist were tried to be passed, or values of the wrong type were set for a property. By returning a Result, the impression was given that this can also fail in a normal way that is supposed to be handled by the caller.

As this is not the case, glib::Object::new() always panics if the arguments passed to it are invalid and no longer returns a Result.

In the same spirit, object.try_set_property(), object.try_property(), object.try_emit() and object.try_connect() do not exist any longer and only the panicking variants are left as the only way they could fail was if invalid arguments are provided.

Transform functions for property bindings are now supported §

Object property bindings allow for transform functions to be defined, which convert the property value to something else for the other object whenever it changes. Previously these were defined on the generic glib::Value type, but as the types are generally fixed and known in advance it is now possible to define them directly with the correct types.

source
    .bind_property("name", &target, "name")
    .flags(crate::BindingFlags::SYNC_CREATE)
    .transform_to(|_binding, value: &str| Some(format!("{} World", value)))
    .transform_from(|_binding, value: &str| Some(format!("{} World", value)))
    .build();

If the types don’t match then this is considered a programming error and will panic at runtime.

The old way of defining transform functions via glib::Value is still available via new functions

source
    .bind_property("name", &target, "name")
    .flags(crate::BindingFlags::SYNC_CREATE)
    .transform_to_with_values(|_binding, value| {
        let value = value.get::<&str>().unwrap();
        Some(format!("{} World", value).to_value())
    })
    .transform_from_with_values(|_binding, value| {
        let value = value.get::<&str>().unwrap();
        Some(format!("{} World", value).to_value())
    })
    .build();

Construct SimpleAction with ActionEntryBuilder §

It is now possible to use gio::ActionEntryBuilder to construct a gio::SimpleAction, the advantage of using that is the gio::ActionMap type is passed as a first parameter to the activate callback and so avoids the usage of the clone! macro.

Before:

let action = gio::SimpleAction::new("some_action", None);
action.connect_activate(clone!(@weak some_obj => move |_, _| {
     // Do something
});
actions_group.add_action(&action);

After

let action = gio::ActionEntry::builder("some_action").activate(move |some_obj: &SomeType, _, _| {
    // Do something
}).build();
// It is safe to `unwrap` as we don't pass any parameter type that requires validation
actions_group.add_action_entries([action]).unwrap();

Changes §

For the interested ones, here is the list of the merged pull requests:

gtk-rs-core:

gtk3-rs:

gtk4-rs:

All this was possible thanks to the gtk-rs/gir project as well:

Thanks to all of our contributors for their (awesome!) work on this release: