8

Subclassing Gtk widgets in Rust

 3 years ago
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: &gtk::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 a gtk::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 a gtk::ListStore : the LibFile 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 .


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK