root/src/queue_page.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// queue_page.rs
//
// Copyright 2023 nee <nee-git@patchouli.garden>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later

use crate::controls::Controls;
use crate::data::Action;
use crate::send;
use adw::prelude::*;
use anyhow::{Context, Result};
use glib::clone;
use gtk::subclass::prelude::*;
use gtk::{gio, glib};
use mpd;
use std::cell::RefCell;

#[derive(Debug, Default)]
pub struct InternalState {
    pub items: Vec<gtk::Button>,
    pub last_active_track: Option<usize>,
}

mod imp {
    use super::*;

    use gtk::CompositeTemplate;

    #[derive(Debug, CompositeTemplate, Default)]
    #[template(resource = "/blue/hidamari/pmpdc/ui/queue_page.ui")]
    pub struct QueuePage {
        #[template_child]
        pub menu_button: TemplateChild<gtk::MenuButton>, // TODO move out of tracklist to keep the menu open while changing servers
        #[template_child]
        pub scroll: TemplateChild<gtk::ScrolledWindow>,
        #[template_child]
        pub controls_bin: TemplateChild<adw::Bin>,
        #[template_child]
        pub open_filters_button: TemplateChild<gtk::Button>,

        pub state: RefCell<Option<InternalState>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for QueuePage {
        const NAME: &'static str = "QueuePage";
        type Type = super::QueuePage;
        type ParentType = gtk::Box;

        fn class_init(klass: &mut Self::Class) {
            Self::bind_template(klass);
        }

        // You must call `Widget`'s `init_template()` within `instance_init()`.
        fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }
    impl ObjectImpl for QueuePage {}
    impl WidgetImpl for QueuePage {}
    impl BoxImpl for QueuePage {}
}

glib::wrapper! {
    pub struct QueuePage(ObjectSubclass<imp::QueuePage>)
        @extends gtk::Widget, gtk::Box,
        @implements gio::ActionMap, gio::ActionGroup;
}

impl QueuePage {
    pub fn new(
        sender: &glib::Sender<Action>,
        track_list: gtk::Box,
        tracks: Vec<gtk::Button>,
        controls: &Controls,
        active_track: Option<usize>,
    ) -> Self {
        let widget: Self = glib::Object::new();

        widget.imp().scroll.set_child(Some(&track_list));
        widget.imp().controls_bin.set_child(Some(controls));
        widget
            .imp()
            .open_filters_button
            .connect_clicked(clone!(@strong sender => move |_| {
                send!(sender, Action::OpenFilterPage);
            }));

        *widget.imp().state.borrow_mut() = Some(InternalState {
            items: tracks,
            last_active_track: active_track,
        });
        widget
    }

    pub fn sync_with_status(&mut self, status: &mpd::Status) -> Result<()> {
        let new_pos = status.song.as_ref().map(|s| s.pos);
        let mut state = self.imp().state.borrow_mut();
        let state = state.as_mut().context("failed to get queue_page state")?;
        if state.last_active_track != new_pos {
            let item = new_pos.and_then(|np| state.items.get(np));
            item.map(|i| i.style_context().add_class("track_active"));
            let last_item = state.last_active_track.and_then(|lp| state.items.get(lp));
            last_item.map(|i| i.style_context().remove_class("track_active"));
            state.last_active_track = new_pos;
        }
        Ok(())
    }
}