Syntax sugar to send a message to another relm widget

Sending a message to another widget with emit() is a bit verbose, so relm provides syntactic sugar for this case. Let's send a message to the playlist when the user clicks the remove button:

gtk::ToolButton {
    icon_widget: &new_icon("remove"),
    clicked => playlist@RemoveSong,
}

Here, we used the @ syntax to specify that the message will be sent to another widget. The part before the @ is the receiver widget, while the part after this character is the message. So, this code means that whenever the user clicks the remove button, send the RemoveSong message to the playlist widget.

Let's handle this message in the Paylist::update() method:

#[widget]
impl Widget for Playlist {
    fn update(&mut self, event: Msg) {
        match event {
            AddSong(path) => self.add(&path),
            LoadSong(path) => self.load(&path),
            NextSong => (),
            PauseSong => (),
            PlaySong => self.play(),
            PreviousSong => (),
            RemoveSong => self.remove_selection(),
            SaveSong(path) => (),
            // To be listened by App.
            SongStarted(_) => (),
            StopSong => (),
        }
    }

    // …
}

This calls the remove_selection() method, as shown here:

fn remove_selection(&self) {
    let selection = self.treeview.get_selection();
    if let Some((_, iter)) = selection.get_selected() {
        self.model.model.remove(&iter);
    }
}

This is the same method as the one from  Chapter 5Creating a Music Player. Now, let's send the remaining messages. The PlaySong, PauseSong, SaveSong, and StopSong messages are emitted in the update() method:

#[widget]
impl Widget for App {
    fn update(&mut self, event: Msg) {
        match event {
            PlayPause =>  {
                if self.model.stopped {
                    self.playlist.emit(PlaySong);
                } else {
                    self.playlist.emit(PauseSong);
                    self.set_play_icon(PLAY_ICON);
                }
            },
            Save => {
                let file = show_save_dialog(&self.window);
                if let Some(file) = file {
                    self.playlist.emit(SaveSong(file));
                }
            },
            Stop => {
                self.set_current_time(0);
                self.model.current_duration = 0;
                self.playlist.emit(StopSong);
                self.model.cover_visible = false;
                self.set_play_icon(PLAY_ICON);
            },
            // …
        }
    }
}

The other messages are sent using the @ syntax in the view:

view! {
    #[name="window"]
    gtk::Window {
        title: "Rusic",
        gtk::Box {
            orientation: Vertical,
            #[name="toolbar"]
            gtk::Toolbar {
                // …
                gtk::ToolButton {
                    icon_widget: &new_icon("gtk-media-previous"),
                    clicked => playlist@PreviousSong,
                },
                // …
                gtk::ToolButton {
                    icon_widget: &new_icon("gtk-media-next"),
                    clicked => playlist@NextSong,
                },
            },
            // …
        },
        delete_event(_, _) => (Quit, Inhibit(false)),
    }
}

We'll handle these messages in the Paylist::update() method:

fn update(&mut self, event: Msg) {
    match event {
        AddSong(path) => self.add(&path),
        LoadSong(path) => self.load(&path),
        NextSong => self.next(),
        PauseSong => (),
        PlaySong => self.play(),
        PreviousSong => self.previous(),
        RemoveSong => self.remove_selection(),
        SaveSong(path) => self.save(&path),
        // To be listened by App.
        SongStarted(_) => (),
        StopSong => self.stop(),
    }
}

This requires some new methods:

fn next(&mut self) {
    let selection = self.treeview.get_selection();
    let next_iter =
        if let Some((_, iter)) = selection.get_selected() {
            if !self.model.model.iter_next(&iter) {
                return;
            }
            Some(iter)
        }
        else {
            self.model.model.get_iter_first()
        };
    if let Some(ref iter) = next_iter {
        selection.select_iter(iter);
        self.play();
    }
}
fn previous(&mut self) {
    let selection = self.treeview.get_selection();
    let previous_iter =
        if let Some((_, iter)) = selection.get_selected() {
            if !self.model.model.iter_previous(&iter) {
                return;
            }
            Some(iter)
        }
        else {
            self.model.model.iter_nth_child(None, max(0,  
self.model.model.iter_n_children(None) - 1)) }; if let Some(ref iter) = previous_iter { selection.select_iter(iter); self.play(); } }
use std::fs::File;

fn save(&self, path: &Path) {
    let mut file = File::create(path).unwrap();
    let mut writer = m3u::Writer::new(&mut file);

    let mut write_iter = |iter: &TreeIter| {
        let value = self.model.model.get_value(&iter, PATH_COLUMN as i32);
        let path = value.get::<String>().unwrap();
        writer.write_entry(&m3u::path_entry(path)).unwrap();
    };

    if let Some(iter) = self.model.model.get_iter_first() {
        write_iter(&iter);
        while self.model.model.iter_next(&iter) {
            write_iter(&iter);
        }
    }
}

And function stop:

fn stop(&mut self) {
    self.model.current_song = None;
}

These methods are all similar to the ones we created in the previous chapters. You can run the application to see that we can open and remove songs, but we cannot play them yet. So let's fix this.