Subclassing Gtk widgets in Rust
source link: https://www.figuiere.net/technotes/notes/tn002/
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.
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
.
Declarations
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.
Implementing the subclass
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.
Recipes
Here are some quick recipes of how to do things.
Widget to Private struct and back
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>()
.
Storing a Rust type in a property
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
Examples
Gtk-rs itself has plenty of Gtt Rust examples . Notably:
-
ListBox
model
show how to write a custom model via subclassing for a
gtk::ListBox
. -
Basic
subclass
which is the first example I looked at, showing how to subclass a
gtk::ApplicationWindow
and agtk::Application
.
And then, some real examples of widgets in Rust that I wrote.
Niepce
Niepce is prototype for a photo management application. Started in C++ it is being rewritten progressively in Rust, including the UI.
-
ImageGridView
a subclass of
GtkIconView
. -
ThumbStripView
another subclass of
GtkIconView
-
LibraryCellRenderer
a subclass of
GtkPixbufCellRenderer
to have a customer rendering in an icon view. This is not a widget, but this still applies. -
ThumbNav
a subclass of
GtkBox
to compose a few widget together with a scrolling area. -
RatingLabel
a subclass for a
GtkDrawingArea
to display a “star rating”. -
Wrapping a type for use in
glib::Value
in agtk::ListStore
: theLibFile
type from another crate is wrapped to be used in the list store.
Minuit
Minuit is small digital piano application written in Rust.
-
PianoWidget
a subclass of
GtkDrawingArea
that implements a Piano like widget including managing events.
GStreamer
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
Thanks to the reviewers: Sebastian Dröge
for his thorough comments, and #gtk-rs
IRC user piegames2
.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK