New release – gtk-rs
source link: https://gtk-rs.org/blog/2022/10/18/new-release.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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 offut
if it takes less than 20ms or otherwise to an error - Cancellation:
gio::CancellableFuture::new(fut, cancellable)
will resolve to the future unless thecancellable
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::Variant
s. 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 redudant. 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) {
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) {
// 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:
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:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK