If you use Gtk in Rust, you probably will need to write custom widgets. This document will show you how it is possible, and what tasks you need to go through.
At the time of writing this, gtk-rs 0.9.0 is being used. It is a set of Rust bindings for Gtk 3. Your mileage may vary on a later version of Gtk Rust. As an example, the original version of this document used 0.8 and only one section needed to be edited, and largely simplified.
We want to create MyAwesomeWidget
to be a container, a subclass of
GtkBox
.
In gtk-rs each the gobject types are wrapped into a Rust type. For
example gtk::Widget
is such a wrapper and is the type we use for any
Rust function that expect a widget instance.
Declaring the wrapper for your subclassed gobject is done using the
glib_wrapper!()
macro. You need make sure that the extern crate glib;
statement is
preceeded by #[macro_use]
, or, if you use Rust 2018, just address it
using the module namespace: glib::glib_wrapper!()
. In the examples
we’ll assume the former.
You also need to use the following:
glib::subclass
glib::translate::*
gtk::prelude::*
gtk::subclass::prelude::*
glib_wrapper! {
pub struct MyAwesomeWidget(
Object<subclass::simple::InstanceStruct<MyAwesomeWidgetPriv>,
subclass::simple::ClassStruct<MyAwesomeWidgetPriv>,
MyAwesomeWidgetClass>)
@extends gtk::Box, gtk::Container, gtk::Widget;
match fn {
get_type => || MyAwesomeWidgetPriv::get_type().to_glib(),
}
}
This tells us that we have MyAwesomeWidget
, MyAwesomeWidgetPriv
and MyAwesomeWidgetClass
. It also indicates the hierarchy:
gtk::Box
, gtk::Container
, gtk::Widget
. The order is important
and goes from down to top (the direct parent first). And then we
indicate how to implement get_type()
. The macro will take care of
most of the boilerplate based on this.
It also indicates that the type MyAwesomeWidgetPriv
will be the one
implementing the GObject boilerplate, it is the struct that will store
your private data as well.
This pattern is not the only way to do this, there are others that can be used. This is left as an exercise to the reader.
There is the object instance implementation.
impl ObjectImpl for MyAwesomeWidgetPriv {
glib_object_impl!();
fn constructed(&self, obj: &glib::Object) {
self.parent_constructed(obj);
/* ... */
}
fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
/* ... */
}
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
let prop = &PROPERTIES[id];
/* ... */
}
}
Use constructed
as an opportunity to do anything after the
glib::Object
instance has been constructed.
PROPERTIES
is an array of subclass::Property
that you
declare. This example declares one single property auto-update
that
is a boolean read and writable:
static PROPERTIES: [subclass::Property; 1] = [
subclass::Property("auto-update", |auto_update| {
glib::ParamSpec::boolean(
auto_update,
"Auto-update",
"Whether to auto-update or not",
true, // Default value
glib::ParamFlags::READWRITE,
)
})
];
Then there is the object subclassing trait to implement the class methods.
impl ObjectSubclass for MyAwesomeWidgetPriv {
const NAME: &'static str = "MyAwesomeWidget";
type ParentType = gtk::Box;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn class_init(klass: &mut Self::Class) {
klass.install_properties(&PROPERTIES);
klass.add_signal(
"added",
glib::SignalFlags::RUN_LAST,
&[Type::U32],
Type::Unit,
);
}
fn new() -> Self {
Self {}
}
}
Here we set ParentType
to be gtk::Box
.
Use class_init
to install properties and add signals. This will be
called automatically to initialise the class.
The public constructor is part of MyAwesomeWidget
.
impl MyAwesomeWidget {
pub fn new() -> MyAwesomeWidget {
glib::Object::new(Self::static_type(), &[])
.expect("Failed to create MyAwesome Widget")
.downcast()
.expect("Created MyAwesome Widget is of wrong type")
}
}
Then you need to have an explicit implementation for the widget struct
(the Priv
) of each parent class. In that case, since it is a
GtkBox
subclass (as per Self::ParentType
we defined previously),
BoxImpl
, ContainerImpl
and WidgetImpl
.
impl BoxImpl for MyAwesomeWidgetPriv {}
impl ContainerImpl for MyAwesomeWidgetPriv {}
impl WidgetImpl for MyAwesomeWidgetPriv {}
Just in case, you need to import these traits from the prelude use gtk::subclass::prelude::*;
Note: if the Impl class isn’t found, then it is possible that the class is not yet subclassable. Gtk Rust is still a work in progress at the time of writing. Don’t hesitate to file an issue if you are missing something. Or even submit a pull request!
Now we are hitting the parts that actually do the work specific to your widget.
If you need to override the virtual methods (also known as vfuncs in
GObject documentation), it is done in their respective Impl
traits,
that would otherwise use the default implementation. Notably the
draw
method
is, as expected, in gtk::WidgetImpl
:
impl WidgetImpl for MyAwesomeWidgetPriv {
fn draw(&self, _widget: >k::Widget, cr: &cairo::Context) -> Inhibit {
/* ... */
Inhibit(false)
}
}
In general the function signatures are mostly identical to the native
C API, except that self
is the private type and the widget is the
second argument.
Here are some quick recipes of how to do things.
Getting the private data from the actual widget struct:
let w: MyAwesomeWidget;
/* ... */
let priv_ = MyAwesomeWidgetPriv::from_instance(&w);
Now the reverse, getting the widget struct from the private:
let priv_: MyAwesomeWidgetPriv;
/* ... */
let w = priv_.get_instance();
w
is of type MyAwesomeWidget::ParentType
, which in that case is a
gtk::Box
. MyAwesomeWidgetPriv
doesn’t know MyAwesomeWidget
but
you can obtain it by downcasting with downcast::<MyAwesomeWidget>()
.
To store a Rust type into a property, you need it to be clonable and
GBoxed
. From gtk-rs 0.9 all you need is to derive GBoxed
and you
can do that automatically. Just make sure the crate glib
is imported
for macro use.
Example with the type MyPropertyType
#[derive(Clone, GBoxed)]
#[gboxed(type_name = "MyPropertyType")]
pub struct MyPropertyType {
}
When you declare the property as boxed
the glib type is obtained
with MyPropertyType::get_type()
.
In the set_property()
handler, you do:
let property = value
.get_some::<&MyPropertyType>()
.expect("type checked by set_property");
In that case property
is of the type &MyPropertyType
. We have to
use
glib::Value::get_some()
since MyPropertyType
isn’t nullable.
If you need to use a type that you don’t have control of, and you can’t
implement the traits in the same module as the type or the trait, make
MyPropertyType
a tuple struct that contain said type.
Example:
#[derive(Clone, GBoxed)]
#[gboxed(type_name = "MyPropertyType"]
pub struct MyPropertyType(OtherType);
The only requirement here is that OtherType
also implements Clone
as well, or that you be able to implement Clone
for MyPropertyType
safely. You can also wrap the orignal type inside an Arc
Note that this is not friendly to other languages. Unless you are prepared to write more interface code, don’t try to use a Rust type outside of Rust code. Keep this in mind when designing your widget API.
You can see an example of wrapping a type to use as a list store value
Gtk-rs itself has plenty of Gtk Rust examples. Notably:
gtk::ListBox
.gtk::ApplicationWindow
and a gtk::Application
.And then, some real examples of widgets in Rust that I wrote.
Niepce is prototype for a photo management application. Started in C++ it is being rewritten progressively in Rust, including the UI.
GtkIconView
.GtkIconView
GtkPixbufCellRenderer
to have a custom rendering
in an icon view. This is not a widget, but this still applies.GtkBox
to compose a few widgets together with a
scrolling area.GtkDrawingArea
to display a “star rating”.glib::Value
in a gtk::ListStore
: the LibFile
type from another crate is
wrapped to be used in the list store.Minuit is small digital piano application written in Rust.
GtkDrawingArea
that implements a Piano like widget
including managing events.Writing GStreamer element in Rust is possible and the GStreamer team has a tutorial. The repository itself contains over 50 examples of elements subclasses.
Thanks to the reviewers: Sebastian Dröge
for his thorough comments, and #gtk-rs
IRC user piegames2
.