mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-27 04:07:25 +01:00
Initial port to GTK4
This commit is contained in:
parent
1a9e58d627
commit
801a130ef8
76 changed files with 3098 additions and 6625 deletions
|
|
@ -3,12 +3,11 @@ use crate::backend::*;
|
|||
use crate::database::*;
|
||||
use crate::editors::EnsembleEditor;
|
||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
|
||||
use gettextrs::gettext;
|
||||
use gio::prelude::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -16,8 +15,10 @@ pub struct EnsembleScreen {
|
|||
backend: Rc<Backend>,
|
||||
ensemble: Ensemble,
|
||||
widget: gtk::Box,
|
||||
search_entry: gtk::SearchEntry,
|
||||
stack: gtk::Stack,
|
||||
recording_list: Rc<List<Recording>>,
|
||||
recording_list: Rc<List>,
|
||||
recordings: RefCell<Vec<Recording>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
|
|
@ -26,13 +27,13 @@ impl EnsembleScreen {
|
|||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/ensemble_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Label, title_label);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Frame, recording_frame);
|
||||
|
||||
header.set_title(Some(&ensemble.name));
|
||||
title_label.set_label(&ensemble.name);
|
||||
|
||||
let edit_action = gio::SimpleAction::new("edit", None);
|
||||
let delete_action = gio::SimpleAction::new("delete", None);
|
||||
|
|
@ -43,75 +44,66 @@ impl EnsembleScreen {
|
|||
|
||||
widget.insert_action_group("widget", Some(&actions));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
let recording_list = List::new();
|
||||
recording_frame.set_child(Some(&recording_list.widget));
|
||||
|
||||
recording_list.set_make_widget(|recording: &Recording| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&work_label);
|
||||
vbox.add(&performers_label);
|
||||
|
||||
vbox.upcast()
|
||||
});
|
||||
|
||||
recording_list.set_filter(
|
||||
clone!(@strong search_entry => move |recording: &Recording| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let text = recording.work.get_title() + &recording.get_performers();
|
||||
search.is_empty() || text.contains(&search)
|
||||
}),
|
||||
);
|
||||
|
||||
recording_frame.add(&recording_list.widget.clone());
|
||||
|
||||
let result = Rc::new(Self {
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
ensemble,
|
||||
widget,
|
||||
search_entry,
|
||||
stack,
|
||||
recording_list,
|
||||
recordings: RefCell::new(Vec::new()),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.recording_list.invalidate_filter();
|
||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
||||
this.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&recording.work.get_title()));
|
||||
row.set_subtitle(Some(&recording.get_performers()));
|
||||
|
||||
let recording = recording.to_owned();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone()));
|
||||
navigator.push(RecordingScreen::new(this.backend.clone(), recording.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
edit_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
let editor = EnsembleEditor::new(result.backend.clone(), Some(result.ensemble.clone()));
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.recording_list.set_filter_cb(clone!(@strong this => move |index| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
let search = this.search_entry.get_text().unwrap().to_string().to_lowercase();
|
||||
let text = recording.work.get_title() + &recording.get_performers();
|
||||
search.is_empty() || text.to_lowercase().contains(&search)
|
||||
}));
|
||||
|
||||
edit_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
let editor = EnsembleEditor::new(this.backend.clone(), Some(this.ensemble.clone()));
|
||||
let window = NavigatorWindow::new(editor);
|
||||
window.show();
|
||||
}));
|
||||
|
||||
delete_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
delete_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
clone.backend.db().delete_ensemble(&clone.ensemble.id).await.unwrap();
|
||||
clone.backend.library_changed();
|
||||
|
|
@ -119,7 +111,7 @@ impl EnsembleScreen {
|
|||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
|
|
@ -131,12 +123,14 @@ impl EnsembleScreen {
|
|||
if recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
clone.recording_list.show_items(recordings);
|
||||
let length = recordings.len();
|
||||
clone.recordings.replace(recordings);
|
||||
clone.recording_list.update(length);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ use crate::backend::*;
|
|||
use crate::database::*;
|
||||
use crate::editors::PersonEditor;
|
||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
|
||||
use gettextrs::gettext;
|
||||
use gio::prelude::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -17,8 +16,13 @@ pub struct PersonScreen {
|
|||
person: Person,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
work_list: Rc<List<Work>>,
|
||||
recording_list: Rc<List<Recording>>,
|
||||
search_entry: gtk::SearchEntry,
|
||||
work_box: gtk::Box,
|
||||
work_list: Rc<List>,
|
||||
recording_box: gtk::Box,
|
||||
recording_list: Rc<List>,
|
||||
works: RefCell<Vec<Work>>,
|
||||
recordings: RefCell<Vec<Recording>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +31,7 @@ impl PersonScreen {
|
|||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/person_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Label, title_label);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
|
|
@ -36,7 +40,7 @@ impl PersonScreen {
|
|||
get_widget!(builder, gtk::Box, recording_box);
|
||||
get_widget!(builder, gtk::Frame, recording_frame);
|
||||
|
||||
header.set_title(Some(&person.name_fl()));
|
||||
title_label.set_label(&person.name_fl());
|
||||
|
||||
let edit_action = gio::SimpleAction::new("edit", None);
|
||||
let delete_action = gio::SimpleAction::new("delete", None);
|
||||
|
|
@ -47,107 +51,98 @@ impl PersonScreen {
|
|||
|
||||
widget.insert_action_group("widget", Some(&actions));
|
||||
|
||||
let work_list = List::new(&gettext("No works found."));
|
||||
let work_list = List::new();
|
||||
let recording_list = List::new();
|
||||
work_frame.set_child(Some(&work_list.widget));
|
||||
recording_frame.set_child(Some(&recording_list.widget));
|
||||
|
||||
work_list.set_make_widget(|work: &Work| {
|
||||
let label = gtk::Label::new(Some(&work.title));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
|
||||
work_list.set_filter(clone!(@strong search_entry => move |work: &Work| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let title = work.title.to_lowercase();
|
||||
search.is_empty() || title.contains(&search)
|
||||
}));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
|
||||
recording_list.set_make_widget(|recording: &Recording| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&work_label);
|
||||
vbox.add(&performers_label);
|
||||
|
||||
vbox.upcast()
|
||||
});
|
||||
|
||||
recording_list.set_filter(
|
||||
clone!(@strong search_entry => move |recording: &Recording| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let text = recording.work.get_title() + &recording.get_performers();
|
||||
search.is_empty() || text.contains(&search)
|
||||
}),
|
||||
);
|
||||
|
||||
work_frame.add(&work_list.widget);
|
||||
recording_frame.add(&recording_list.widget);
|
||||
|
||||
let result = Rc::new(Self {
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
person,
|
||||
widget,
|
||||
stack,
|
||||
search_entry,
|
||||
work_box,
|
||||
work_list,
|
||||
recording_box,
|
||||
recording_list,
|
||||
works: RefCell::new(Vec::new()),
|
||||
recordings: RefCell::new(Vec::new()),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.work_list.invalidate_filter();
|
||||
result.recording_list.invalidate_filter();
|
||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
||||
this.work_list.invalidate_filter();
|
||||
this.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.work_list
|
||||
.set_selected(clone!(@strong result => move |work| {
|
||||
result.recording_list.clear_selection();
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
this.work_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||
let work = &this.works.borrow()[index];
|
||||
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&work.title));
|
||||
|
||||
let work = work.to_owned();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(WorkScreen::new(result.backend.clone(), work.clone()));
|
||||
navigator.push(WorkScreen::new(this.backend.clone(), work.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
result.work_list.clear_selection();
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.work_list.set_filter_cb(clone!(@strong this => move |index| {
|
||||
let work = &this.works.borrow()[index];
|
||||
let search = this.search_entry.get_text().unwrap().to_string().to_lowercase();
|
||||
let title = work.title.to_lowercase();
|
||||
search.is_empty() || title.contains(&search)
|
||||
}));
|
||||
|
||||
this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&recording.work.get_title()));
|
||||
row.set_subtitle(Some(&recording.get_performers()));
|
||||
|
||||
let recording = recording.to_owned();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone()));
|
||||
navigator.push(RecordingScreen::new(this.backend.clone(), recording.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
edit_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
let editor = PersonEditor::new(result.backend.clone(), Some(result.person.clone()));
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.recording_list.set_filter_cb(clone!(@strong this => move |index| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
let search = this.search_entry.get_text().unwrap().to_string().to_lowercase();
|
||||
let text = recording.work.get_title() + &recording.get_performers();
|
||||
search.is_empty() || text.contains(&search)
|
||||
}));
|
||||
|
||||
edit_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
let editor = PersonEditor::new(this.backend.clone(), Some(this.person.clone()));
|
||||
let window = NavigatorWindow::new(editor);
|
||||
window.show();
|
||||
}));
|
||||
|
||||
delete_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
delete_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
clone.backend.db().delete_person(&clone.person.id).await.unwrap();
|
||||
clone.backend.library_changed();
|
||||
|
|
@ -155,7 +150,7 @@ impl PersonScreen {
|
|||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
let works = clone
|
||||
.backend
|
||||
|
|
@ -163,6 +158,7 @@ impl PersonScreen {
|
|||
.get_works(&clone.person.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let recordings = clone
|
||||
.backend
|
||||
.db()
|
||||
|
|
@ -174,22 +170,26 @@ impl PersonScreen {
|
|||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
if works.is_empty() {
|
||||
work_box.hide();
|
||||
clone.work_box.hide();
|
||||
} else {
|
||||
clone.work_list.show_items(works);
|
||||
let length = works.len();
|
||||
clone.works.replace(works);
|
||||
clone.work_list.update(length);
|
||||
}
|
||||
|
||||
if recordings.is_empty() {
|
||||
recording_box.hide();
|
||||
clone.recording_box.hide();
|
||||
} else {
|
||||
clone.recording_list.show_items(recordings);
|
||||
let length = recordings.len();
|
||||
clone.recordings.replace(recordings);
|
||||
clone.recording_list.update(length);
|
||||
}
|
||||
|
||||
clone.stack.set_visible_child_name("content");
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,15 +4,22 @@ use gettextrs::gettext;
|
|||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
struct PlaylistElement {
|
||||
pub item: usize,
|
||||
pub track: usize,
|
||||
pub title: String,
|
||||
pub subtitle: Option<String>,
|
||||
pub playable: bool,
|
||||
/// Elements for visually representing the playlist.
|
||||
enum ListItem {
|
||||
/// A header shown on top of a track set. This contains an index
|
||||
/// referencing the playlist item containing this track set.
|
||||
Header(usize),
|
||||
|
||||
/// A playable track. This contains an index to the playlist item, an
|
||||
/// index to the track and whether it is the currently played one.
|
||||
Track(usize, usize, bool),
|
||||
|
||||
/// A separator shown between track sets.
|
||||
Separator,
|
||||
}
|
||||
|
||||
pub struct PlayerScreen {
|
||||
|
|
@ -27,16 +34,18 @@ pub struct PlayerScreen {
|
|||
duration_label: gtk::Label,
|
||||
play_image: gtk::Image,
|
||||
pause_image: gtk::Image,
|
||||
list: Rc<List<PlaylistElement>>,
|
||||
player: Rc<RefCell<Option<Rc<Player>>>>,
|
||||
seeking: Rc<Cell<bool>>,
|
||||
current_item: Rc<Cell<usize>>,
|
||||
current_track: Rc<Cell<usize>>,
|
||||
back_cb: Rc<RefCell<Option<Box<dyn Fn() -> ()>>>>,
|
||||
list: Rc<List>,
|
||||
playlist: RefCell<Vec<PlaylistItem>>,
|
||||
items: RefCell<Vec<ListItem>>,
|
||||
player: RefCell<Option<Rc<Player>>>,
|
||||
seeking: Cell<bool>,
|
||||
current_item: Cell<usize>,
|
||||
current_track: Cell<usize>,
|
||||
back_cb: RefCell<Option<Box<dyn Fn()>>>,
|
||||
}
|
||||
|
||||
impl PlayerScreen {
|
||||
pub fn new() -> Self {
|
||||
pub fn new() -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/player_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
|
|
@ -55,124 +64,10 @@ impl PlayerScreen {
|
|||
get_widget!(builder, gtk::Image, pause_image);
|
||||
get_widget!(builder, gtk::Frame, frame);
|
||||
|
||||
let back_cb = Rc::new(RefCell::new(None::<Box<dyn Fn() -> ()>>));
|
||||
let list = List::new();
|
||||
frame.set_child(Some(&list.widget));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong back_cb => move |_| {
|
||||
if let Some(cb) = &*back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
}));
|
||||
|
||||
let player = Rc::new(RefCell::new(None::<Rc<Player>>));
|
||||
let seeking = Rc::new(Cell::new(false));
|
||||
|
||||
previous_button.connect_clicked(clone!(@strong player => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.previous().unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
play_button.connect_clicked(clone!(@strong player => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.play_pause();
|
||||
}
|
||||
}));
|
||||
|
||||
next_button.connect_clicked(clone!(@strong player => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.next().unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
stop_button.connect_clicked(clone!(@strong player, @strong back_cb => move |_| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
if let Some(cb) = &*back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
|
||||
player.clear();
|
||||
}
|
||||
}));
|
||||
|
||||
position_scale.connect_button_press_event(clone!(@strong seeking => move |_, _| {
|
||||
seeking.replace(true);
|
||||
Inhibit(false)
|
||||
}));
|
||||
|
||||
position_scale.connect_button_release_event(
|
||||
clone!(@strong seeking, @strong position, @strong player => move |_, _| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.seek(position.get_value() as u64);
|
||||
}
|
||||
|
||||
seeking.replace(false);
|
||||
Inhibit(false)
|
||||
}),
|
||||
);
|
||||
|
||||
position_scale.connect_value_changed(
|
||||
clone!(@strong seeking, @strong position, @strong position_label => move |_| {
|
||||
if seeking.get() {
|
||||
let ms = position.get_value() as u64;
|
||||
let min = ms / 60000;
|
||||
let sec = (ms % 60000) / 1000;
|
||||
position_label.set_text(&format!("{}:{:02}", min, sec));
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let current_item = Rc::new(Cell::<usize>::new(0));
|
||||
let current_track = Rc::new(Cell::<usize>::new(0));
|
||||
let list = List::new("");
|
||||
|
||||
list.set_make_widget(clone!(
|
||||
@strong current_item,
|
||||
@strong current_track
|
||||
=> move |element: &PlaylistElement| {
|
||||
let title_label = gtk::Label::new(Some(&element.title));
|
||||
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
title_label.set_halign(gtk::Align::Start);
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.add(&title_label);
|
||||
if let Some(subtitle) = &element.subtitle {
|
||||
let subtitle_label = gtk::Label::new(Some(&subtitle));
|
||||
subtitle_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
subtitle_label.set_halign(gtk::Align::Start);
|
||||
subtitle_label.set_opacity(0.5);
|
||||
vbox.add(&subtitle_label);
|
||||
}
|
||||
|
||||
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
hbox.set_border_width(6);
|
||||
|
||||
if element.playable {
|
||||
let image = gtk::Image::new();
|
||||
|
||||
if element.item == current_item.get() && element.track == current_track.get() {
|
||||
image.set_from_icon_name(
|
||||
Some("media-playback-start-symbolic"),
|
||||
gtk::IconSize::Button,
|
||||
);
|
||||
}
|
||||
|
||||
hbox.add(&image);
|
||||
} else if element.item > 0 {
|
||||
hbox.set_margin_top(18);
|
||||
}
|
||||
hbox.add(&vbox);
|
||||
hbox.upcast()
|
||||
}
|
||||
));
|
||||
|
||||
list.set_selected(clone!(@strong player => move |element| {
|
||||
if let Some(player) = &*player.borrow() {
|
||||
player.set_track(element.item, element.track).unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
frame.add(&list.widget);
|
||||
|
||||
Self {
|
||||
let this = Rc::new(Self {
|
||||
widget,
|
||||
title_label,
|
||||
subtitle_label,
|
||||
|
|
@ -185,106 +80,222 @@ impl PlayerScreen {
|
|||
play_image,
|
||||
pause_image,
|
||||
list,
|
||||
player,
|
||||
seeking,
|
||||
current_item,
|
||||
current_track,
|
||||
back_cb,
|
||||
}
|
||||
}
|
||||
items: RefCell::new(Vec::new()),
|
||||
playlist: RefCell::new(Vec::new()),
|
||||
player: RefCell::new(None),
|
||||
seeking: Cell::new(false),
|
||||
current_item: Cell::new(0),
|
||||
current_track: Cell::new(0),
|
||||
back_cb: RefCell::new(None),
|
||||
});
|
||||
|
||||
pub fn set_player(&self, player: Option<Rc<Player>>) {
|
||||
self.player.replace(player.clone());
|
||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(cb) = &*this.back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
}));
|
||||
|
||||
if let Some(player) = player {
|
||||
let playlist = Rc::new(RefCell::new(Vec::<PlaylistItem>::new()));
|
||||
this.previous_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(player) = &*this.player.borrow() {
|
||||
player.previous().unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
player.add_playlist_cb(clone!(
|
||||
@strong player,
|
||||
@strong self.previous_button as previous_button,
|
||||
@strong self.next_button as next_button,
|
||||
@strong self.list as list,
|
||||
@strong playlist
|
||||
=> move |new_playlist| {
|
||||
playlist.replace(new_playlist);
|
||||
previous_button.set_sensitive(player.has_previous());
|
||||
next_button.set_sensitive(player.has_next());
|
||||
this.play_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(player) = &*this.player.borrow() {
|
||||
player.play_pause();
|
||||
}
|
||||
}));
|
||||
|
||||
let mut elements = Vec::new();
|
||||
for (item_index, item) in playlist.borrow().iter().enumerate() {
|
||||
elements.push(PlaylistElement {
|
||||
item: item_index,
|
||||
track: 0,
|
||||
title: item.track_set.recording.work.get_title(),
|
||||
subtitle: Some(item.track_set.recording.get_performers()),
|
||||
playable: false,
|
||||
});
|
||||
this.next_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(player) = &*this.player.borrow() {
|
||||
player.next().unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
for track_index in &item.indices {
|
||||
let track = &item.track_set.tracks[*track_index];
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.track_set.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let title = if parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
parts.join(", ")
|
||||
};
|
||||
|
||||
elements.push(PlaylistElement {
|
||||
item: item_index,
|
||||
track: *track_index,
|
||||
title: title,
|
||||
subtitle: None,
|
||||
playable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
list.show_items(elements);
|
||||
stop_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(player) = &*this.player.borrow() {
|
||||
if let Some(cb) = &*this.back_cb.borrow() {
|
||||
cb();
|
||||
}
|
||||
));
|
||||
|
||||
player.add_track_cb(clone!(
|
||||
@strong player,
|
||||
@strong playlist,
|
||||
@strong self.previous_button as previous_button,
|
||||
@strong self.next_button as next_button,
|
||||
@strong self.title_label as title_label,
|
||||
@strong self.subtitle_label as subtitle_label,
|
||||
@strong self.position_label as position_label,
|
||||
@strong self.current_item as self_item,
|
||||
@strong self.current_track as self_track,
|
||||
@strong self.list as list
|
||||
=> move |current_item, current_track| {
|
||||
previous_button.set_sensitive(player.has_previous());
|
||||
next_button.set_sensitive(player.has_next());
|
||||
player.clear();
|
||||
}
|
||||
}));
|
||||
|
||||
let item = &playlist.borrow()[current_item];
|
||||
let track = &item.track_set.tracks[current_track];
|
||||
// position_scale.connect_button_press_event(clone!(@strong seeking => move |_, _| {
|
||||
// seeking.replace(true);
|
||||
// Inhibit(false)
|
||||
// }));
|
||||
|
||||
// position_scale.connect_button_release_event(
|
||||
// clone!(@strong seeking, @strong position, @strong player => move |_, _| {
|
||||
// if let Some(player) = &*player.borrow() {
|
||||
// player.seek(position.get_value() as u64);
|
||||
// }
|
||||
|
||||
// seeking.replace(false);
|
||||
// Inhibit(false)
|
||||
// }),
|
||||
// );
|
||||
|
||||
position_scale.connect_value_changed(clone!(@strong this => move |_| {
|
||||
if this.seeking.get() {
|
||||
let ms = this.position.get_value() as u64;
|
||||
let min = ms / 60000;
|
||||
let sec = (ms % 60000) / 1000;
|
||||
|
||||
this.position_label.set_text(&format!("{}:{:02}", min, sec));
|
||||
}
|
||||
}));
|
||||
|
||||
this.list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||
match this.items.borrow()[index] {
|
||||
ListItem::Header(item_index) => {
|
||||
let playlist_item = &this.playlist.borrow()[item_index];
|
||||
let recording = &playlist_item.track_set.recording;
|
||||
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(false);
|
||||
row.set_selectable(false);
|
||||
row.set_title(Some(&recording.work.get_title()));
|
||||
row.set_subtitle(Some(&recording.get_performers()));
|
||||
|
||||
row.upcast()
|
||||
}
|
||||
ListItem::Track(item_index, track_index, playing) => {
|
||||
let playlist_item = &this.playlist.borrow()[item_index];
|
||||
let index = playlist_item.indices[track_index];
|
||||
let track = &playlist_item.track_set.tracks[index];
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.track_set.recording.work.parts[*part].title.clone());
|
||||
parts.push(playlist_item.track_set.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let mut title = item.track_set.recording.work.get_title();
|
||||
if !parts.is_empty() {
|
||||
title = format!("{}: {}", title, parts.join(", "));
|
||||
}
|
||||
let title = if parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
parts.join(", ")
|
||||
};
|
||||
|
||||
title_label.set_text(&title);
|
||||
subtitle_label.set_text(&item.track_set.recording.get_performers());
|
||||
position_label.set_text("0:00");
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_selectable(false);
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&title));
|
||||
|
||||
self_item.replace(current_item);
|
||||
self_track.replace(current_track);
|
||||
list.update();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
if let Some(player) = &*this.player.borrow() {
|
||||
player.set_track(item_index, track_index).unwrap();
|
||||
}
|
||||
}));
|
||||
|
||||
let icon = if playing {
|
||||
Some("media-playback-start-symbolic")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let image = gtk::Image::from_icon_name(icon);
|
||||
row.add_prefix(&image);
|
||||
|
||||
row.upcast()
|
||||
}
|
||||
));
|
||||
ListItem::Separator => {
|
||||
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
|
||||
separator.upcast()
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// list.set_make_widget(clone!(
|
||||
// @strong current_item,
|
||||
// @strong current_track
|
||||
// => move |element: &PlaylistElement| {
|
||||
// let title_label = gtk::Label::new(Some(&element.title));
|
||||
// title_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
// title_label.set_halign(gtk::Align::Start);
|
||||
|
||||
// let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
// vbox.append(&title_label);
|
||||
|
||||
// if let Some(subtitle) = &element.subtitle {
|
||||
// let subtitle_label = gtk::Label::new(Some(&subtitle));
|
||||
// subtitle_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
// subtitle_label.set_halign(gtk::Align::Start);
|
||||
// subtitle_label.set_opacity(0.5);
|
||||
// vbox.append(&subtitle_label);
|
||||
// }
|
||||
|
||||
// let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
// hbox.set_margin_top(6);
|
||||
// hbox.set_margin_bottom(6);
|
||||
// hbox.set_margin_start(6);
|
||||
// hbox.set_margin_end(6);
|
||||
|
||||
// if element.playable {
|
||||
// let image = gtk::Image::new();
|
||||
|
||||
// if element.item == current_item.get() && element.track == current_track.get() {
|
||||
// image.set_from_icon_name(
|
||||
// Some("media-playback-start-symbolic"),
|
||||
// gtk::IconSize::Button,
|
||||
// );
|
||||
// }
|
||||
|
||||
// hbox.append(&image);
|
||||
// } else if element.item > 0 {
|
||||
// hbox.set_margin_top(18);
|
||||
// }
|
||||
// hbox.append(&vbox);
|
||||
// hbox.upcast()
|
||||
// }
|
||||
// ));
|
||||
|
||||
// list.set_selected(clone!(@strong player => move |element| {
|
||||
// if let Some(player) = &*player.borrow() {
|
||||
// player.set_track(element.item, element.track).unwrap();
|
||||
// }
|
||||
// }));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub fn set_player(self: Rc<Self>, player: Option<Rc<Player>>) {
|
||||
self.player.replace(player.clone());
|
||||
|
||||
if let Some(player) = player {
|
||||
player.add_playlist_cb(clone!(@strong self as this => move |playlist| {
|
||||
this.playlist.replace(playlist);
|
||||
this.show_playlist();
|
||||
}));
|
||||
|
||||
player.add_track_cb(clone!(@strong self as this, @strong player => move |current_item, current_track| {
|
||||
this.previous_button.set_sensitive(player.has_previous());
|
||||
this.next_button.set_sensitive(player.has_next());
|
||||
|
||||
let item = &this.playlist.borrow()[current_item];
|
||||
let track = &item.track_set.tracks[current_track];
|
||||
|
||||
let mut parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
parts.push(item.track_set.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let mut title = item.track_set.recording.work.get_title();
|
||||
if !parts.is_empty() {
|
||||
title = format!("{}: {}", title, parts.join(", "));
|
||||
}
|
||||
|
||||
this.title_label.set_text(&title);
|
||||
this.subtitle_label.set_text(&item.track_set.recording.get_performers());
|
||||
this.position_label.set_text("0:00");
|
||||
|
||||
this.current_item.set(current_item);
|
||||
this.current_track.set(current_track);
|
||||
|
||||
this.show_playlist();
|
||||
}));
|
||||
|
||||
player.add_duration_cb(clone!(
|
||||
@strong self.duration_label as duration_label,
|
||||
|
|
@ -302,15 +313,11 @@ impl PlayerScreen {
|
|||
@strong self.play_image as play_image,
|
||||
@strong self.pause_image as pause_image
|
||||
=> move |playing| {
|
||||
if let Some(child) = play_button.get_child() {
|
||||
play_button.remove( &child);
|
||||
}
|
||||
|
||||
play_button.add(if playing {
|
||||
play_button.set_child(Some(if playing {
|
||||
&pause_image
|
||||
} else {
|
||||
&play_image
|
||||
});
|
||||
}));
|
||||
}
|
||||
));
|
||||
|
||||
|
|
@ -333,4 +340,33 @@ impl PlayerScreen {
|
|||
pub fn set_back_cb<F: Fn() -> () + 'static>(&self, cb: F) {
|
||||
self.back_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Update the user interface according to the playlist.
|
||||
fn show_playlist(&self) {
|
||||
let playlist = self.playlist.borrow();
|
||||
let current_item = self.current_item.get();
|
||||
let current_track = self.current_track.get();
|
||||
|
||||
let mut first = true;
|
||||
let mut items = Vec::new();
|
||||
|
||||
for (item_index, playlist_item) in playlist.iter().enumerate() {
|
||||
if !first {
|
||||
items.push(ListItem::Separator);
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
items.push(ListItem::Header(item_index));
|
||||
|
||||
for (index, _) in playlist_item.indices.iter().enumerate() {
|
||||
let playing = current_item == item_index && current_track == index;
|
||||
items.push(ListItem::Track(item_index, index, playing));
|
||||
}
|
||||
}
|
||||
|
||||
let length = items.len();
|
||||
self.items.replace(items);
|
||||
self.list.update(length);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,16 +8,28 @@ use gio::prelude::*;
|
|||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Representation of one entry within the track list.
|
||||
enum ListItem {
|
||||
/// A track row. This hold an index to the track set and an index to the
|
||||
/// track within the track set.
|
||||
Track(usize, usize),
|
||||
|
||||
/// A separator intended for use between track sets.
|
||||
Separator,
|
||||
}
|
||||
|
||||
pub struct RecordingScreen {
|
||||
backend: Rc<Backend>,
|
||||
recording: Recording,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
list: Rc<List>,
|
||||
track_sets: RefCell<Vec<TrackSet>>,
|
||||
items: RefCell<Vec<ListItem>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
|
|
@ -26,14 +38,15 @@ impl RecordingScreen {
|
|||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Label, title_label);
|
||||
get_widget!(builder, gtk::Label, subtitle_label);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Frame, frame);
|
||||
get_widget!(builder, gtk::Button, add_to_playlist_button);
|
||||
|
||||
header.set_title(Some(&recording.work.get_title()));
|
||||
header.set_subtitle(Some(&recording.get_performers()));
|
||||
title_label.set_label(&recording.work.get_title());
|
||||
subtitle_label.set_label(&recording.get_performers());
|
||||
|
||||
let edit_action = gio::SimpleAction::new("edit", None);
|
||||
let delete_action = gio::SimpleAction::new("delete", None);
|
||||
|
|
@ -48,95 +61,96 @@ impl RecordingScreen {
|
|||
|
||||
widget.insert_action_group("widget", Some(&actions));
|
||||
|
||||
let list = List::new(&gettext("No tracks found."));
|
||||
frame.add(&list.widget);
|
||||
let list = List::new();
|
||||
frame.set_child(Some(&list.widget));
|
||||
|
||||
let result = Rc::new(Self {
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
recording,
|
||||
widget,
|
||||
stack,
|
||||
list,
|
||||
track_sets: RefCell::new(Vec::new()),
|
||||
items: RefCell::new(Vec::new()),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
list.set_make_widget(clone!(@strong result => move |track_set: &TrackSet| {
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.set_spacing(6);
|
||||
this.list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||
match this.items.borrow()[index] {
|
||||
ListItem::Track(track_set_index, track_index) => {
|
||||
let track_set = &this.track_sets.borrow()[track_set_index];
|
||||
let track = &track_set.tracks[track_index];
|
||||
|
||||
for track in &track_set.tracks {
|
||||
let track_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
let mut title_parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
title_parts.push(this.recording.work.parts[*part].title.clone());
|
||||
}
|
||||
|
||||
let mut title_parts = Vec::<String>::new();
|
||||
for part in &track.work_parts {
|
||||
title_parts.push(result.recording.work.parts[*part].title.clone());
|
||||
let title = if title_parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
title_parts.join(", ")
|
||||
};
|
||||
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_title(Some(&title));
|
||||
|
||||
row.upcast()
|
||||
}
|
||||
ListItem::Separator => {
|
||||
let separator = gtk::Separator::new(gtk::Orientation::Horizontal);
|
||||
separator.upcast()
|
||||
}
|
||||
|
||||
let title = if title_parts.is_empty() {
|
||||
gettext("Unknown")
|
||||
} else {
|
||||
title_parts.join(", ")
|
||||
};
|
||||
|
||||
let title_label = gtk::Label::new(Some(&title));
|
||||
title_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
title_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let file_name_label = gtk::Label::new(Some(&track.path));
|
||||
file_name_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
file_name_label.set_opacity(0.5);
|
||||
file_name_label.set_halign(gtk::Align::Start);
|
||||
|
||||
track_box.add(&title_label);
|
||||
track_box.add(&file_name_label);
|
||||
|
||||
vbox.add(&track_box);
|
||||
}
|
||||
|
||||
vbox.upcast()
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
add_to_playlist_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
// if let Some(player) = result.backend.get_player() {
|
||||
// player.add_item(PlaylistItem {
|
||||
// track_set: result.track_sets.get(0).unwrap().clone(),
|
||||
// indices: result.tracks.borrow().clone(),
|
||||
// }).unwrap();
|
||||
// }
|
||||
// TODO: Decide whether to handle multiple track sets.
|
||||
add_to_playlist_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
if let Some(player) = this.backend.get_player() {
|
||||
if let Some(track_set) = this.track_sets.borrow().get(0).cloned() {
|
||||
let indices = (0..track_set.tracks.len()).collect();
|
||||
|
||||
let playlist_item = PlaylistItem {
|
||||
track_set,
|
||||
indices,
|
||||
};
|
||||
|
||||
player.add_item(playlist_item).unwrap();
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
edit_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
let editor = RecordingEditor::new(result.backend.clone(), Some(result.recording.clone()));
|
||||
edit_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
let editor = RecordingEditor::new(this.backend.clone(), Some(this.recording.clone()));
|
||||
let window = NavigatorWindow::new(editor);
|
||||
window.show();
|
||||
}));
|
||||
|
||||
delete_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
delete_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
clone.backend.db().delete_recording(&clone.recording.id).await.unwrap();
|
||||
clone.backend.library_changed();
|
||||
});
|
||||
}));
|
||||
|
||||
edit_tracks_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
// let editor = TracksEditor::new(result.backend.clone(), Some(result.recording.clone()), result.tracks.borrow().clone());
|
||||
edit_tracks_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
// let editor = TracksEditor::new(this.backend.clone(), Some(this.recording.clone()), this.tracks.borrow().clone());
|
||||
// let window = NavigatorWindow::new(editor);
|
||||
// window.show();
|
||||
}));
|
||||
|
||||
delete_tracks_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
delete_tracks_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
// clone.backend.db().delete_tracks(&clone.recording.id).await.unwrap();
|
||||
// clone.backend.library_changed();
|
||||
|
|
@ -144,7 +158,7 @@ impl RecordingScreen {
|
|||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
let track_sets = clone
|
||||
.backend
|
||||
|
|
@ -153,12 +167,34 @@ impl RecordingScreen {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
list.show_items(track_sets.clone());
|
||||
clone.show_track_sets(track_sets);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
clone.track_sets.replace(track_sets);
|
||||
});
|
||||
|
||||
result
|
||||
this
|
||||
}
|
||||
|
||||
/// Update the track sets variable as well as the user interface.
|
||||
fn show_track_sets(&self, track_sets: Vec<TrackSet>) {
|
||||
let mut first = true;
|
||||
let mut items = Vec::new();
|
||||
|
||||
for (track_set_index, track_set) in track_sets.iter().enumerate() {
|
||||
if !first {
|
||||
items.push(ListItem::Separator);
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
for (track_index, _) in track_set.tracks.iter().enumerate() {
|
||||
items.push(ListItem::Track(track_set_index, track_index));
|
||||
}
|
||||
}
|
||||
|
||||
let length = items.len();
|
||||
self.items.replace(items);
|
||||
self.track_sets.replace(track_sets);
|
||||
self.list.update(length);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ use crate::backend::*;
|
|||
use crate::database::*;
|
||||
use crate::editors::WorkEditor;
|
||||
use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow};
|
||||
use gettextrs::gettext;
|
||||
use gio::prelude::*;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -17,7 +16,9 @@ pub struct WorkScreen {
|
|||
work: Work,
|
||||
widget: gtk::Box,
|
||||
stack: gtk::Stack,
|
||||
recording_list: Rc<List<Recording>>,
|
||||
search_entry: gtk::SearchEntry,
|
||||
recording_list: Rc<List>,
|
||||
recordings: RefCell<Vec<Recording>>,
|
||||
navigator: RefCell<Option<Rc<Navigator>>>,
|
||||
}
|
||||
|
||||
|
|
@ -26,14 +27,15 @@ impl WorkScreen {
|
|||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_screen.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Label, title_label);
|
||||
get_widget!(builder, gtk::Label, subtitle_label);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
get_widget!(builder, gtk::Stack, stack);
|
||||
get_widget!(builder, gtk::Frame, recording_frame);
|
||||
|
||||
header.set_title(Some(&work.title));
|
||||
header.set_subtitle(Some(&work.composer.name_fl()));
|
||||
title_label.set_label(&work.composer.name_fl());
|
||||
subtitle_label.set_label(&work.title);
|
||||
|
||||
let edit_action = gio::SimpleAction::new("edit", None);
|
||||
let delete_action = gio::SimpleAction::new("delete", None);
|
||||
|
|
@ -44,73 +46,66 @@ impl WorkScreen {
|
|||
|
||||
widget.insert_action_group("widget", Some(&actions));
|
||||
|
||||
let recording_list = List::new(&gettext("No recordings found."));
|
||||
let recording_list = List::new();
|
||||
recording_frame.set_child(Some(&recording_list.widget));
|
||||
|
||||
recording_list.set_make_widget(|recording: &Recording| {
|
||||
let work_label = gtk::Label::new(Some(&recording.work.get_title()));
|
||||
|
||||
work_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
work_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let performers_label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
performers_label.set_ellipsize(pango::EllipsizeMode::End);
|
||||
performers_label.set_opacity(0.5);
|
||||
performers_label.set_halign(gtk::Align::Start);
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
vbox.set_border_width(6);
|
||||
vbox.add(&work_label);
|
||||
vbox.add(&performers_label);
|
||||
|
||||
vbox.upcast()
|
||||
});
|
||||
|
||||
recording_list.set_filter(clone!(@strong search_entry => move |recording: &Recording| {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
let text = recording.work.get_title().to_lowercase() + &recording.get_performers().to_lowercase();
|
||||
search.is_empty() || text.contains(&search)
|
||||
}),);
|
||||
|
||||
recording_frame.add(&recording_list.widget);
|
||||
|
||||
let result = Rc::new(Self {
|
||||
let this = Rc::new(Self {
|
||||
backend,
|
||||
work,
|
||||
widget,
|
||||
stack,
|
||||
search_entry,
|
||||
recording_list,
|
||||
recordings: RefCell::new(Vec::new()),
|
||||
navigator: RefCell::new(None),
|
||||
});
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong result => move |_| {
|
||||
result.recording_list.invalidate_filter();
|
||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
||||
this.recording_list.invalidate_filter();
|
||||
}));
|
||||
|
||||
back_button.connect_clicked(clone!(@strong result => move |_| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
back_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.clone().pop();
|
||||
}
|
||||
}));
|
||||
|
||||
result
|
||||
.recording_list
|
||||
.set_selected(clone!(@strong result => move |recording| {
|
||||
let navigator = result.navigator.borrow().clone();
|
||||
this.recording_list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&recording.work.get_title()));
|
||||
row.set_subtitle(Some(&recording.get_performers()));
|
||||
|
||||
let recording = recording.to_owned();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
let navigator = this.navigator.borrow().clone();
|
||||
if let Some(navigator) = navigator {
|
||||
navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone()));
|
||||
navigator.push(RecordingScreen::new(this.backend.clone(), recording.clone()));
|
||||
}
|
||||
}));
|
||||
|
||||
edit_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
let editor = WorkEditor::new(result.backend.clone(), Some(result.work.clone()));
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.recording_list.set_filter_cb(clone!(@strong this => move |index| {
|
||||
let recording = &this.recordings.borrow()[index];
|
||||
let search = this.search_entry.get_text().unwrap().to_string().to_lowercase();
|
||||
let text = recording.work.get_title() + &recording.get_performers();
|
||||
search.is_empty() || text.to_lowercase().contains(&search)
|
||||
}));
|
||||
|
||||
edit_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
let editor = WorkEditor::new(this.backend.clone(), Some(this.work.clone()));
|
||||
let window = NavigatorWindow::new(editor);
|
||||
window.show();
|
||||
}));
|
||||
|
||||
delete_action.connect_activate(clone!(@strong result => move |_, _| {
|
||||
delete_action.connect_activate(clone!(@strong this => move |_, _| {
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
clone.backend.db().delete_work(&clone.work.id).await.unwrap();
|
||||
clone.backend.library_changed();
|
||||
|
|
@ -118,7 +113,7 @@ impl WorkScreen {
|
|||
}));
|
||||
|
||||
let context = glib::MainContext::default();
|
||||
let clone = result.clone();
|
||||
let clone = this.clone();
|
||||
context.spawn_local(async move {
|
||||
let recordings = clone
|
||||
.backend
|
||||
|
|
@ -130,12 +125,14 @@ impl WorkScreen {
|
|||
if recordings.is_empty() {
|
||||
clone.stack.set_visible_child_name("nothing");
|
||||
} else {
|
||||
clone.recording_list.show_items(recordings);
|
||||
let length = recordings.len();
|
||||
clone.recordings.replace(recordings);
|
||||
clone.recording_list.update(length);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
}
|
||||
});
|
||||
|
||||
result
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue