Post-initialization of the view

If you run the application, you'll see that the images are not shown on the toolbar buttons. This is because of the way relm works. When it generates the code, it calls the show() method on every widget, instead of show_all(). So, the toolbar and the tool buttons will be shown, but not the images, as they are only attributes of the buttons, they are not created using the widget syntax. To solve this issue, we'll call show_all() on the toolbar in the init_view() method:

#[widget]
impl Widget for App {
    fn init_view(&mut self) {
        self.toolbar.show_all();
    }

    // …
}

That's why we gave a name to the toolbar widget earlier: we needed to call a method on this widget here. The init_view() method is called after the view is created. This is useful to execute some code to customize the view when it's not possible to do so using the view! syntax. If you run the application again, you'll see that the buttons now have an image.

Let's now add the cover image widget and the cursor widget. For the image, we'll need to add a new crate to Cargo.toml:

[dependencies]
gdk-pixbuf = "^0.3.0"

Let's also add the corresponding extern crate statement:

extern crate gdk_pixbuf;

We also need new import statements:

use gdk_pixbuf::Pixbuf;
use gtk::{
    Adjustment,
    BoxExt,
    ImageExt,
    LabelExt,
    ScaleExt,
};
use gtk::Orientation::Horizontal;

Let's add a couple of new fields to our Model:

pub struct Model {
    adjustment: Adjustment,
    cover_pixbuf: Option<Pixbuf>,
    cover_visible: bool,
    current_duration: u64,
    current_time: u64,
    play_image: Image,
}

Most of the new fields existed in the application we developed in the two previous chapters. The cover_visible attribute is new, though. We'll use it to know whether we should show the image of the cover. Don't forget to update the initialization of the model:

fn model() -> Model {
    Model {
        adjustment: Adjustment::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
        cover_pixbuf: None,
        cover_visible: false,
        current_duration: 0,
        current_time: 0,
        play_image: new_icon(PLAY_ICON),
    }
}

We can now add the Image after the Toolbar widget:

gtk::Image {
    from_pixbuf: self.model.cover_pixbuf.as_ref(),
    visible: self.model.cover_visible,
},

Here, we call as_ref() on the cover_pixbuf attribute, because, once again, the method (set_from_pixbuf()) requires something that can be converted into a Option<&Pixbuf>. We also specify that the visible property of the image is bound to the cover_visible model attribute. This means that we'll be able to hide the image by setting this attribute to false.

We'll then add the cursor, which will give us the following view:

view! {
    gtk::Window {
        title: "Rusic",
        delete_event(_, _) => (Quit, Inhibit(false)),
        gtk::Box {
            orientation: Vertical,
            #[name="toolbar"]
            gtk::Toolbar {
                // …
            },
            gtk::Image {
                from_pixbuf: self.model.cover_pixbuf.as_ref(),
                visible: self.model.cover_visible,
            },
            gtk::Box {
                orientation: Horizontal,
                spacing: 10,
                gtk::Scale(Horizontal, &self.model.adjustment) {
                    draw_value: false,
                    hexpand: true,
                },
                gtk::Label {
                    text: &millis_to_minutes(self.model.current_time),
                },
                gtk::Label {
                    text: "/",
                },
                gtk::Label {
                    margin_right: 10,
                    text: &millis_to_minutes(self.model.current_duration),
                },
            },
        },
    }
}

This require the following method, which we saw in the previous chapter:

fn millis_to_minutes(millis: u64) -> String {
    let mut seconds = millis / 1_000;
    let minutes = seconds / 60;
    seconds %= 60;
    format!("{}:{:02}", minutes, seconds)
}

We used another way to create a widget:

gtk::Scale(Horizontal, &self.model.adjustment) {
    draw_value: false,
    hexpand: true,
}

This syntax will call the constructor of the widget, like so:

gtk::Scale::new(Horizontal, &self.model.adjustment);

We could also have used the traditional syntax to create a widget:

use gtk::RangeExt;

gtk::Scale {
    adjustment: &self.model.adjustment,
    orientation: Horizontal,
    draw_value: false,
    hexpand: true,
}

These are just two ways to do the same thing.