mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Add tracks editor UI
This commit is contained in:
parent
0fe143a383
commit
143876c4de
12 changed files with 1159 additions and 26 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -552,6 +552,12 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "formatx"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa6f3b9014e23925937fbf4d05f27a6f4efe42545f98690b94f193bdb3f1959e"
|
||||
|
||||
[[package]]
|
||||
name = "fragile"
|
||||
version = "2.0.0"
|
||||
|
|
@ -1292,6 +1298,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
"formatx",
|
||||
"fragile",
|
||||
"gettext-rs",
|
||||
"glib",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ anyhow = "1"
|
|||
chrono = "0.4"
|
||||
diesel = { version = "2.2", features = ["chrono", "sqlite"] }
|
||||
diesel_migrations = "2.2"
|
||||
formatx = "0.2"
|
||||
fragile = "2"
|
||||
gettext-rs = { version = "0.7", features = ["gettext-system"] }
|
||||
gstreamer-play = "0.23"
|
||||
|
|
|
|||
|
|
@ -258,6 +258,11 @@ template $MusicusHomePage: Adw.NavigationPage {
|
|||
}
|
||||
|
||||
menu primary_menu {
|
||||
item {
|
||||
label: _("_Import music");
|
||||
action: "win.import";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_Library manager");
|
||||
action: "win.library";
|
||||
|
|
|
|||
147
data/ui/recording_selector_popover.blp
Normal file
147
data/ui/recording_selector_popover.blp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusRecordingSelectorPopover: Gtk.Popover {
|
||||
styles [
|
||||
"selector"
|
||||
]
|
||||
|
||||
Gtk.Stack stack {
|
||||
transition-type: slide_left_right;
|
||||
|
||||
Adw.ToolbarView composer_view {
|
||||
[top]
|
||||
Gtk.SearchEntry composer_search_entry {
|
||||
placeholder-text: _("Search composers…");
|
||||
margin-start: 8;
|
||||
margin-end: 8;
|
||||
margin-top: 8;
|
||||
margin-bottom: 6;
|
||||
search-changed => $composer_search_changed() swapped;
|
||||
activate => $composer_activate() swapped;
|
||||
stop-search => $stop_search() swapped;
|
||||
}
|
||||
|
||||
Gtk.ScrolledWindow composer_scrolled_window {
|
||||
height-request: 200;
|
||||
|
||||
Gtk.ListBox composer_list {
|
||||
styles [
|
||||
"selector-list"
|
||||
]
|
||||
|
||||
selection-mode: none;
|
||||
activate-on-single-click: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ToolbarView work_view {
|
||||
[top]
|
||||
Gtk.Box {
|
||||
margin-start: 8;
|
||||
margin-end: 8;
|
||||
margin-top: 8;
|
||||
margin-bottom: 6;
|
||||
orientation: vertical;
|
||||
|
||||
Gtk.CenterBox {
|
||||
[start]
|
||||
Gtk.Button {
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
|
||||
icon-name: "go-previous-symbolic";
|
||||
clicked => $back_to_composer() swapped;
|
||||
}
|
||||
|
||||
[center]
|
||||
Gtk.Label composer_label {
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
|
||||
ellipsize: end;
|
||||
margin-start: 6;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.SearchEntry work_search_entry {
|
||||
placeholder-text: _("Search works…");
|
||||
margin-top: 6;
|
||||
search-changed => $work_search_changed() swapped;
|
||||
activate => $work_activate() swapped;
|
||||
stop-search => $stop_search() swapped;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.ScrolledWindow work_scrolled_window {
|
||||
height-request: 200;
|
||||
|
||||
Gtk.ListBox work_list {
|
||||
styles [
|
||||
"selector-list"
|
||||
]
|
||||
|
||||
selection-mode: none;
|
||||
activate-on-single-click: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ToolbarView recording_view {
|
||||
[top]
|
||||
Gtk.Box {
|
||||
margin-start: 8;
|
||||
margin-end: 8;
|
||||
margin-top: 8;
|
||||
margin-bottom: 6;
|
||||
orientation: vertical;
|
||||
|
||||
Gtk.CenterBox {
|
||||
[start]
|
||||
Gtk.Button {
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
|
||||
icon-name: "go-previous-symbolic";
|
||||
clicked => $back_to_work() swapped;
|
||||
}
|
||||
|
||||
[center]
|
||||
Gtk.Label work_label {
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
|
||||
ellipsize: end;
|
||||
margin-start: 6;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.SearchEntry recording_search_entry {
|
||||
placeholder-text: _("Search recordings…");
|
||||
margin-top: 6;
|
||||
search-changed => $recording_search_changed() swapped;
|
||||
activate => $recording_activate() swapped;
|
||||
stop-search => $stop_search() swapped;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.ScrolledWindow recording_scrolled_window {
|
||||
height-request: 200;
|
||||
|
||||
Gtk.ListBox recording_list {
|
||||
styles [
|
||||
"selector-list"
|
||||
]
|
||||
|
||||
selection-mode: none;
|
||||
activate-on-single-click: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
data/ui/tracks_editor.blp
Normal file
98
data/ui/tracks_editor.blp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusTracksEditor: Adw.NavigationPage {
|
||||
title: _("Tracks");
|
||||
|
||||
Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {}
|
||||
|
||||
Gtk.ScrolledWindow {
|
||||
Adw.Clamp {
|
||||
Gtk.Box {
|
||||
orientation: vertical;
|
||||
margin-bottom: 24;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
|
||||
Gtk.Label {
|
||||
label: _("Recording");
|
||||
xalign: 0;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.ListBox {
|
||||
selection-mode: none;
|
||||
margin-top: 12;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
|
||||
Adw.ActionRow recording_row {
|
||||
title: _("Select recording");
|
||||
activatable: true;
|
||||
activated => $select_recording() swapped;
|
||||
|
||||
[prefix]
|
||||
Gtk.Box select_recording_box {
|
||||
Gtk.Image {
|
||||
icon-name: "document-edit-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Label {
|
||||
label: _("Tracks");
|
||||
xalign: 0;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.ListBox track_list {
|
||||
selection-mode: none;
|
||||
margin-top: 12;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Add files");
|
||||
activatable: true;
|
||||
activated => $add_files() swapped;
|
||||
|
||||
[prefix]
|
||||
Gtk.Image {
|
||||
icon-name: "list-add-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.ListBox {
|
||||
selection-mode: none;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
|
||||
Adw.ButtonRow save_row {
|
||||
title: _("Import tracks");
|
||||
activated => $save() swapped;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
data/ui/tracks_editor_track_row.blp
Normal file
25
data/ui/tracks_editor_track_row.blp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $MusicusTracksEditorTrackRow: Adw.ActionRow {
|
||||
title: _("Select parts");
|
||||
activatable: true;
|
||||
activated => $select_parts() swapped;
|
||||
|
||||
[prefix]
|
||||
Gtk.Box select_parts_box {
|
||||
Gtk.Image {
|
||||
icon-name: "document-edit-symbolic";
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
icon-name: "user-trash-symbolic";
|
||||
valign: center;
|
||||
clicked => $remove() swapped;
|
||||
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,11 @@ pub mod person_selector_popover;
|
|||
pub mod recording_editor;
|
||||
pub mod recording_editor_ensemble_row;
|
||||
pub mod recording_editor_performer_row;
|
||||
pub mod recording_selector_popover;
|
||||
pub mod role_editor;
|
||||
pub mod role_selector_popover;
|
||||
pub mod tracks_editor;
|
||||
pub mod tracks_editor_track_row;
|
||||
pub mod translation_editor;
|
||||
pub mod translation_entry;
|
||||
pub mod work_editor;
|
||||
|
|
|
|||
424
src/editor/recording_selector_popover.rs
Normal file
424
src/editor/recording_selector_popover.rs
Normal file
|
|
@ -0,0 +1,424 @@
|
|||
use crate::{
|
||||
db::models::{Person, Recording, Work},
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
use gettextrs::gettext;
|
||||
use gtk::{
|
||||
glib::{self, subclass::Signal, Properties},
|
||||
pango,
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::cell::{OnceCell, RefCell};
|
||||
|
||||
use super::activatable_row::MusicusActivatableRow;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate, Properties)]
|
||||
#[properties(wrapper_type = super::RecordingSelectorPopover)]
|
||||
#[template(file = "data/ui/recording_selector_popover.blp")]
|
||||
pub struct RecordingSelectorPopover {
|
||||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub composers: RefCell<Vec<Person>>,
|
||||
pub works: RefCell<Vec<Work>>,
|
||||
pub recordings: RefCell<Vec<Recording>>,
|
||||
|
||||
pub composer: RefCell<Option<Person>>,
|
||||
pub work: RefCell<Option<Work>>,
|
||||
|
||||
#[template_child]
|
||||
pub stack: TemplateChild<gtk::Stack>,
|
||||
#[template_child]
|
||||
pub composer_view: TemplateChild<adw::ToolbarView>,
|
||||
#[template_child]
|
||||
pub composer_search_entry: TemplateChild<gtk::SearchEntry>,
|
||||
#[template_child]
|
||||
pub composer_scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||
#[template_child]
|
||||
pub composer_list: TemplateChild<gtk::ListBox>,
|
||||
#[template_child]
|
||||
pub work_view: TemplateChild<adw::ToolbarView>,
|
||||
#[template_child]
|
||||
pub composer_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub work_search_entry: TemplateChild<gtk::SearchEntry>,
|
||||
#[template_child]
|
||||
pub work_scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||
#[template_child]
|
||||
pub work_list: TemplateChild<gtk::ListBox>,
|
||||
#[template_child]
|
||||
pub recording_view: TemplateChild<adw::ToolbarView>,
|
||||
#[template_child]
|
||||
pub work_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub recording_search_entry: TemplateChild<gtk::SearchEntry>,
|
||||
#[template_child]
|
||||
pub recording_scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||
#[template_child]
|
||||
pub recording_list: TemplateChild<gtk::ListBox>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for RecordingSelectorPopover {
|
||||
const NAME: &'static str = "MusicusRecordingSelectorPopover";
|
||||
type Type = super::RecordingSelectorPopover;
|
||||
type ParentType = gtk::Popover;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
klass.bind_template_instance_callbacks();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for RecordingSelectorPopover {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
self.obj()
|
||||
.connect_visible_notify(|obj: &super::RecordingSelectorPopover| {
|
||||
if obj.is_visible() {
|
||||
obj.imp().stack.set_visible_child(&*obj.imp().composer_view);
|
||||
obj.imp().composer_search_entry.set_text("");
|
||||
obj.imp().composer_search_entry.grab_focus();
|
||||
obj.imp()
|
||||
.composer_scrolled_window
|
||||
.vadjustment()
|
||||
.set_value(0.0);
|
||||
}
|
||||
});
|
||||
|
||||
self.obj().search_composers("");
|
||||
}
|
||||
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![
|
||||
Signal::builder("selected")
|
||||
.param_types([Recording::static_type()])
|
||||
.build(),
|
||||
Signal::builder("create").build(),
|
||||
]
|
||||
});
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for RecordingSelectorPopover {
|
||||
// TODO: Fix focus.
|
||||
fn focus(&self, direction_type: gtk::DirectionType) -> bool {
|
||||
if direction_type == gtk::DirectionType::Down {
|
||||
if self.stack.visible_child() == Some(self.composer_list.get().upcast()) {
|
||||
self.composer_list.child_focus(direction_type)
|
||||
} else if self.stack.visible_child() == Some(self.work_list.get().upcast()) {
|
||||
self.work_list.child_focus(direction_type)
|
||||
} else {
|
||||
self.recording_list.child_focus(direction_type)
|
||||
}
|
||||
} else {
|
||||
self.parent_focus(direction_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PopoverImpl for RecordingSelectorPopover {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct RecordingSelectorPopover(ObjectSubclass<imp::RecordingSelectorPopover>)
|
||||
@extends gtk::Widget, gtk::Popover;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl RecordingSelectorPopover {
|
||||
pub fn new(library: &MusicusLibrary) -> Self {
|
||||
glib::Object::builder().property("library", library).build()
|
||||
}
|
||||
|
||||
pub fn connect_selected<F: Fn(&Self, Recording) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> glib::SignalHandlerId {
|
||||
self.connect_local("selected", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
let recording = values[1].get::<Recording>().unwrap();
|
||||
f(&obj, recording);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_create<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("create", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
f(&obj);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn composer_search_changed(&self, entry: >k::SearchEntry) {
|
||||
self.search_composers(&entry.text());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn composer_activate(&self, _: >k::SearchEntry) {
|
||||
if let Some(composer) = self.imp().composers.borrow().first() {
|
||||
self.select_composer(composer.to_owned());
|
||||
} else {
|
||||
self.create();
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn back_to_composer(&self, _: >k::Button) {
|
||||
self.imp()
|
||||
.stack
|
||||
.set_visible_child(&*self.imp().composer_view);
|
||||
self.imp().composer_search_entry.grab_focus();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn work_search_changed(&self, entry: >k::SearchEntry) {
|
||||
self.search_works(&entry.text());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn work_activate(&self, _: >k::SearchEntry) {
|
||||
if let Some(work) = self.imp().works.borrow().first() {
|
||||
self.select_work(work.to_owned());
|
||||
} else {
|
||||
self.create();
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn back_to_work(&self, _: >k::Button) {
|
||||
self.imp().stack.set_visible_child(&*self.imp().work_view);
|
||||
self.imp().work_search_entry.grab_focus();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn recording_search_changed(&self, entry: >k::SearchEntry) {
|
||||
self.search_recordings(&entry.text());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn recording_activate(&self, _: >k::SearchEntry) {
|
||||
if let Some(recording) = self.imp().recordings.borrow().first() {
|
||||
self.select(recording.to_owned());
|
||||
} else {
|
||||
self.create();
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn stop_search(&self, _: >k::SearchEntry) {
|
||||
self.popdown();
|
||||
}
|
||||
|
||||
fn search_composers(&self, search: &str) {
|
||||
let imp = self.imp();
|
||||
|
||||
let persons = imp.library.get().unwrap().search_persons(search).unwrap();
|
||||
|
||||
imp.composer_list.remove_all();
|
||||
|
||||
for person in &persons {
|
||||
let row = MusicusActivatableRow::new(
|
||||
>k::Label::builder()
|
||||
.label(person.to_string())
|
||||
.halign(gtk::Align::Start)
|
||||
.ellipsize(pango::EllipsizeMode::Middle)
|
||||
.build(),
|
||||
);
|
||||
|
||||
row.set_tooltip_text(Some(&person.to_string()));
|
||||
|
||||
let person = person.clone();
|
||||
let obj = self.clone();
|
||||
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.select_composer(person.clone());
|
||||
});
|
||||
|
||||
imp.composer_list.append(&row);
|
||||
}
|
||||
|
||||
let create_box = gtk::Box::builder().spacing(12).build();
|
||||
create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build());
|
||||
create_box.append(
|
||||
>k::Label::builder()
|
||||
.label(gettext("Create new recording"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let create_row = MusicusActivatableRow::new(&create_box);
|
||||
let obj = self.clone();
|
||||
create_row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.create();
|
||||
});
|
||||
|
||||
imp.composer_list.append(&create_row);
|
||||
|
||||
imp.composers.replace(persons);
|
||||
}
|
||||
|
||||
fn search_works(&self, search: &str) {
|
||||
let imp = self.imp();
|
||||
|
||||
let works = imp
|
||||
.library
|
||||
.get()
|
||||
.unwrap()
|
||||
.search_works(imp.composer.borrow().as_ref().unwrap(), search)
|
||||
.unwrap();
|
||||
|
||||
imp.work_list.remove_all();
|
||||
|
||||
for work in &works {
|
||||
let row = MusicusActivatableRow::new(
|
||||
>k::Label::builder()
|
||||
.label(work.name.get())
|
||||
.halign(gtk::Align::Start)
|
||||
.ellipsize(pango::EllipsizeMode::Middle)
|
||||
.build(),
|
||||
);
|
||||
|
||||
row.set_tooltip_text(Some(&work.name.get()));
|
||||
|
||||
let work = work.clone();
|
||||
let obj = self.clone();
|
||||
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.select_work(work.clone());
|
||||
});
|
||||
|
||||
imp.work_list.append(&row);
|
||||
}
|
||||
|
||||
let create_box = gtk::Box::builder().spacing(12).build();
|
||||
create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build());
|
||||
create_box.append(
|
||||
>k::Label::builder()
|
||||
.label(gettext("Create new recording"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let create_row = MusicusActivatableRow::new(&create_box);
|
||||
let obj = self.clone();
|
||||
create_row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.create();
|
||||
});
|
||||
|
||||
imp.work_list.append(&create_row);
|
||||
|
||||
imp.works.replace(works);
|
||||
}
|
||||
|
||||
fn search_recordings(&self, search: &str) {
|
||||
let imp = self.imp();
|
||||
|
||||
let recordings = imp
|
||||
.library
|
||||
.get()
|
||||
.unwrap()
|
||||
.search_recordings(imp.work.borrow().as_ref().unwrap(), search)
|
||||
.unwrap();
|
||||
|
||||
imp.recording_list.remove_all();
|
||||
|
||||
for recording in &recordings {
|
||||
let mut label = recording.performers_string();
|
||||
|
||||
if let Some(year) = recording.year {
|
||||
label.push_str(&format!(" ({year})"));
|
||||
}
|
||||
|
||||
let row = MusicusActivatableRow::new(
|
||||
>k::Label::builder()
|
||||
.label(&label)
|
||||
.halign(gtk::Align::Start)
|
||||
.ellipsize(pango::EllipsizeMode::Middle)
|
||||
.build(),
|
||||
);
|
||||
|
||||
row.set_tooltip_text(Some(&label));
|
||||
|
||||
let recording = recording.clone();
|
||||
let obj = self.clone();
|
||||
row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.select(recording.clone());
|
||||
});
|
||||
|
||||
imp.recording_list.append(&row);
|
||||
}
|
||||
|
||||
let create_box = gtk::Box::builder().spacing(12).build();
|
||||
create_box.append(>k::Image::builder().icon_name("list-add-symbolic").build());
|
||||
create_box.append(
|
||||
>k::Label::builder()
|
||||
.label(gettext("Create new recording"))
|
||||
.halign(gtk::Align::Start)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let create_row = MusicusActivatableRow::new(&create_box);
|
||||
let obj = self.clone();
|
||||
create_row.connect_activated(move |_: &MusicusActivatableRow| {
|
||||
obj.create();
|
||||
});
|
||||
|
||||
imp.recording_list.append(&create_row);
|
||||
|
||||
imp.recordings.replace(recordings);
|
||||
}
|
||||
|
||||
fn select_composer(&self, person: Person) {
|
||||
self.imp().composer_label.set_text(person.name.get());
|
||||
self.imp().work_search_entry.set_text("");
|
||||
self.imp().work_search_entry.grab_focus();
|
||||
self.imp().work_scrolled_window.vadjustment().set_value(0.0);
|
||||
self.imp().stack.set_visible_child(&*self.imp().work_view);
|
||||
|
||||
self.imp().composer.replace(Some(person.clone()));
|
||||
self.search_works("");
|
||||
}
|
||||
|
||||
fn select_work(&self, work: Work) {
|
||||
self.imp().work_label.set_text(work.name.get());
|
||||
self.imp().recording_search_entry.set_text("");
|
||||
self.imp().recording_search_entry.grab_focus();
|
||||
self.imp()
|
||||
.recording_scrolled_window
|
||||
.vadjustment()
|
||||
.set_value(0.0);
|
||||
self.imp()
|
||||
.stack
|
||||
.set_visible_child(&*self.imp().recording_view);
|
||||
|
||||
self.imp().work.replace(Some(work.clone()));
|
||||
self.search_recordings("");
|
||||
}
|
||||
|
||||
fn select(&self, recording: Recording) {
|
||||
self.emit_by_name::<()>("selected", &[&recording]);
|
||||
self.popdown();
|
||||
}
|
||||
|
||||
fn create(&self) {
|
||||
self.emit_by_name::<()>("create", &[]);
|
||||
self.popdown();
|
||||
}
|
||||
}
|
||||
240
src/editor/tracks_editor.rs
Normal file
240
src/editor/tracks_editor.rs
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
use super::tracks_editor_track_row::{PathType, TracksEditorTrackData};
|
||||
use crate::{
|
||||
db::models::Recording,
|
||||
editor::{
|
||||
recording_editor::MusicusRecordingEditor,
|
||||
recording_selector_popover::RecordingSelectorPopover,
|
||||
tracks_editor_track_row::TracksEditorTrackRow,
|
||||
},
|
||||
library::MusicusLibrary,
|
||||
};
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::gettext;
|
||||
use gtk::{
|
||||
gio,
|
||||
glib::{self, clone, subclass::Signal, Properties},
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::{
|
||||
cell::{OnceCell, RefCell},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate, Properties)]
|
||||
#[properties(wrapper_type = super::TracksEditor)]
|
||||
#[template(file = "data/ui/tracks_editor.blp")]
|
||||
pub struct TracksEditor {
|
||||
#[property(get, construct_only)]
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub recording: RefCell<Option<Recording>>,
|
||||
pub recordings_popover: OnceCell<RecordingSelectorPopover>,
|
||||
pub track_rows: RefCell<Vec<TracksEditorTrackRow>>,
|
||||
|
||||
#[template_child]
|
||||
pub recording_row: TemplateChild<adw::ActionRow>,
|
||||
#[template_child]
|
||||
pub select_recording_box: TemplateChild<gtk::Box>,
|
||||
#[template_child]
|
||||
pub track_list: TemplateChild<gtk::ListBox>,
|
||||
#[template_child]
|
||||
pub save_row: TemplateChild<adw::ButtonRow>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for TracksEditor {
|
||||
const NAME: &'static str = "MusicusTracksEditor";
|
||||
type Type = super::TracksEditor;
|
||||
type ParentType = adw::NavigationPage;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
klass.bind_template_instance_callbacks();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for TracksEditor {
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![Signal::builder("created")
|
||||
.param_types([Recording::static_type()])
|
||||
.build()]
|
||||
});
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let recordings_popover = RecordingSelectorPopover::new(self.library.get().unwrap());
|
||||
|
||||
let obj = self.obj().clone();
|
||||
recordings_popover.connect_selected(move |_, recording| {
|
||||
obj.set_recording(recording);
|
||||
});
|
||||
|
||||
let obj = self.obj().clone();
|
||||
recordings_popover.connect_create(move |_| {
|
||||
let editor = MusicusRecordingEditor::new(
|
||||
obj.imp().navigation.get().unwrap(),
|
||||
&obj.library(),
|
||||
None,
|
||||
);
|
||||
|
||||
editor.connect_created(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_, recording| {
|
||||
obj.set_recording(recording);
|
||||
}
|
||||
));
|
||||
|
||||
obj.imp().navigation.get().unwrap().push(&editor);
|
||||
});
|
||||
|
||||
self.select_recording_box.append(&recordings_popover);
|
||||
self.recordings_popover.set(recordings_popover).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for TracksEditor {}
|
||||
impl NavigationPageImpl for TracksEditor {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct TracksEditor(ObjectSubclass<imp::TracksEditor>)
|
||||
@extends gtk::Widget, adw::NavigationPage;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl TracksEditor {
|
||||
pub fn new(
|
||||
navigation: &adw::NavigationView,
|
||||
library: &MusicusLibrary,
|
||||
recording: Option<Recording>,
|
||||
) -> Self {
|
||||
let obj: Self = glib::Object::builder()
|
||||
.property("navigation", navigation)
|
||||
.property("library", library)
|
||||
.build();
|
||||
|
||||
if let Some(recording) = recording {
|
||||
obj.imp().save_row.set_title(&gettext("Save changes"));
|
||||
obj.set_recording(recording);
|
||||
}
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn select_recording(&self, _: &adw::ActionRow) {
|
||||
self.imp().recordings_popover.get().unwrap().popup();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
async fn add_files(&self, _: &adw::ActionRow) {
|
||||
let dialog = gtk::FileDialog::builder()
|
||||
.title(gettext("Select audio files"))
|
||||
.modal(true)
|
||||
.build();
|
||||
|
||||
let root = self.root();
|
||||
let window = root
|
||||
.as_ref()
|
||||
.and_then(|r| r.downcast_ref::<gtk::Window>())
|
||||
.unwrap();
|
||||
|
||||
let obj = self.clone();
|
||||
match dialog.open_multiple_future(Some(window)).await {
|
||||
Err(err) => {
|
||||
if !err.matches(gtk::DialogError::Dismissed) {
|
||||
log::error!("File selection failed: {err}");
|
||||
}
|
||||
}
|
||||
Ok(files) => {
|
||||
for file in &files {
|
||||
obj.add_file(
|
||||
file.unwrap()
|
||||
.downcast::<gio::File>()
|
||||
.unwrap()
|
||||
.path()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_recording(&self, recording: Recording) {
|
||||
self.imp().recording_row.set_title(&format!(
|
||||
"{}: {}",
|
||||
recording.work.composers_string(),
|
||||
recording.work.name.get(),
|
||||
));
|
||||
|
||||
self.imp()
|
||||
.recording_row
|
||||
.set_subtitle(&recording.performers_string());
|
||||
|
||||
for track in self
|
||||
.library()
|
||||
.tracks_for_recording(&recording.recording_id)
|
||||
.unwrap()
|
||||
{
|
||||
self.add_track_row(TracksEditorTrackData {
|
||||
track_id: Some(track.track_id),
|
||||
path: PathType::Library(track.path),
|
||||
works: track.works,
|
||||
});
|
||||
}
|
||||
|
||||
self.imp().recording.replace(Some(recording));
|
||||
}
|
||||
|
||||
fn add_file(&self, path: PathBuf) {
|
||||
self.add_track_row(TracksEditorTrackData {
|
||||
track_id: None,
|
||||
path: PathType::System(path),
|
||||
works: Vec::new(),
|
||||
});
|
||||
}
|
||||
|
||||
fn add_track_row(&self, track_data: TracksEditorTrackData) {
|
||||
let track_row = TracksEditorTrackRow::new(&self.navigation(), &self.library(), track_data);
|
||||
|
||||
track_row.connect_remove(clone!(
|
||||
#[weak(rename_to = this)]
|
||||
self,
|
||||
move |row| {
|
||||
this.imp().track_list.remove(row);
|
||||
this.imp().track_rows.borrow_mut().retain(|p| p != row);
|
||||
}
|
||||
));
|
||||
|
||||
self.imp()
|
||||
.track_list
|
||||
.insert(&track_row, self.imp().track_rows.borrow().len() as i32);
|
||||
|
||||
self.imp().track_rows.borrow_mut().push(track_row);
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn save(&self) {
|
||||
// TODO
|
||||
|
||||
self.navigation().pop();
|
||||
}
|
||||
}
|
||||
149
src/editor/tracks_editor_track_row.rs
Normal file
149
src/editor/tracks_editor_track_row.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
use crate::{db::models::Work, library::MusicusLibrary};
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use formatx::formatx;
|
||||
use gettextrs::gettext;
|
||||
use gtk::glib::{self, clone, subclass::Signal, Properties};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::{
|
||||
cell::{OnceCell, RefCell},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
|
||||
#[properties(wrapper_type = super::TracksEditorTrackRow)]
|
||||
#[template(file = "data/ui/tracks_editor_track_row.blp")]
|
||||
pub struct TracksEditorTrackRow {
|
||||
#[property(get, construct_only)]
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
#[property(get, construct_only)]
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub track_data: RefCell<TracksEditorTrackData>,
|
||||
|
||||
#[template_child]
|
||||
pub select_parts_box: TemplateChild<gtk::Box>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for TracksEditorTrackRow {
|
||||
const NAME: &'static str = "MusicusTracksEditorTrackRow";
|
||||
type Type = super::TracksEditorTrackRow;
|
||||
type ParentType = adw::ActionRow;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
klass.bind_template_instance_callbacks();
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for TracksEditorTrackRow {
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> =
|
||||
Lazy::new(|| vec![Signal::builder("remove").build()]);
|
||||
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for TracksEditorTrackRow {}
|
||||
impl ListBoxRowImpl for TracksEditorTrackRow {}
|
||||
impl PreferencesRowImpl for TracksEditorTrackRow {}
|
||||
impl ActionRowImpl for TracksEditorTrackRow {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct TracksEditorTrackRow(ObjectSubclass<imp::TracksEditorTrackRow>)
|
||||
@extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl TracksEditorTrackRow {
|
||||
pub fn new(
|
||||
navigation: &adw::NavigationView,
|
||||
library: &MusicusLibrary,
|
||||
track_data: TracksEditorTrackData,
|
||||
) -> Self {
|
||||
let obj: Self = glib::Object::builder()
|
||||
.property("navigation", navigation)
|
||||
.property("library", library)
|
||||
.build();
|
||||
|
||||
obj.set_subtitle(&match &track_data.path {
|
||||
PathType::None => String::new(),
|
||||
PathType::Library(path) => path.to_owned(),
|
||||
PathType::System(path) => {
|
||||
let format_string = gettext("Import from {}");
|
||||
let file_name = path.file_name().unwrap().to_str().unwrap();
|
||||
match formatx!(&format_string, file_name) {
|
||||
Ok(title) => title,
|
||||
Err(_) => {
|
||||
log::error!("Error in translated format string: {format_string}");
|
||||
file_name.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
obj.set_works(&track_data.works);
|
||||
obj.imp().track_data.replace(track_data);
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
pub fn connect_remove<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_local("remove", true, move |values| {
|
||||
let obj = values[0].get::<Self>().unwrap();
|
||||
f(&obj);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn track_data(&self) -> TracksEditorTrackData {
|
||||
self.imp().track_data.borrow().to_owned()
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn select_parts(&self) {
|
||||
// self.imp().parts_popover.get().unwrap().popup();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn remove(&self) {
|
||||
self.emit_by_name::<()>("remove", &[]);
|
||||
}
|
||||
|
||||
fn set_works(&self, works: &[Work]) {
|
||||
self.set_title(
|
||||
&works
|
||||
.iter()
|
||||
.map(|w| w.name.get())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(", "),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct TracksEditorTrackData {
|
||||
pub track_id: Option<String>,
|
||||
pub path: PathType,
|
||||
pub works: Vec<Work>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub enum PathType {
|
||||
#[default]
|
||||
None,
|
||||
Library(String),
|
||||
System(PathBuf),
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ impl MusicusLibrary {
|
|||
.collect::<Result<Vec<Ensemble>>>()?;
|
||||
|
||||
let works: Vec<Work> = works::table
|
||||
.inner_join(work_persons::table.inner_join(persons::table))
|
||||
.left_join(work_persons::table.inner_join(persons::table))
|
||||
.filter(works::name.like(&search).or(persons::name.like(&search)))
|
||||
.limit(9)
|
||||
.select(works::all_columns)
|
||||
|
|
@ -225,7 +225,7 @@ impl MusicusLibrary {
|
|||
|
||||
let recordings = recordings::table
|
||||
.inner_join(
|
||||
works::table.inner_join(work_persons::table.inner_join(persons::table)),
|
||||
works::table.left_join(work_persons::table.inner_join(persons::table)),
|
||||
)
|
||||
// .inner_join(recording_persons::table.inner_join(persons::table))
|
||||
.inner_join(recording_ensembles::table)
|
||||
|
|
@ -287,7 +287,7 @@ impl MusicusLibrary {
|
|||
|
||||
let recordings = recordings::table
|
||||
.inner_join(
|
||||
works::table.inner_join(work_persons::table.inner_join(persons::table)),
|
||||
works::table.left_join(work_persons::table.inner_join(persons::table)),
|
||||
)
|
||||
.inner_join(recording_persons::table)
|
||||
.filter(
|
||||
|
|
@ -400,10 +400,10 @@ impl MusicusLibrary {
|
|||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let mut query = recordings::table
|
||||
.inner_join(works::table.inner_join(work_persons::table))
|
||||
.inner_join(recording_persons::table)
|
||||
.inner_join(recording_ensembles::table)
|
||||
.inner_join(album_recordings::table)
|
||||
.inner_join(works::table.left_join(work_persons::table))
|
||||
.left_join(recording_persons::table)
|
||||
.left_join(recording_ensembles::table)
|
||||
.left_join(album_recordings::table)
|
||||
.into_boxed();
|
||||
|
||||
if let Some(composer_id) = program.composer_id() {
|
||||
|
|
@ -556,7 +556,7 @@ impl MusicusLibrary {
|
|||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let works: Vec<Work> = works::table
|
||||
.inner_join(work_persons::table)
|
||||
.left_join(work_persons::table)
|
||||
.filter(
|
||||
works::name
|
||||
.like(&search)
|
||||
|
|
@ -573,6 +573,32 @@ impl MusicusLibrary {
|
|||
Ok(works)
|
||||
}
|
||||
|
||||
pub fn search_recordings(&self, work: &Work, search: &str) -> Result<Vec<Recording>> {
|
||||
let search = format!("%{}%", search);
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let recordings = recordings::table
|
||||
.left_join(recording_persons::table.inner_join(persons::table))
|
||||
.left_join(recording_ensembles::table.inner_join(ensembles::table))
|
||||
.filter(
|
||||
recordings::work_id.eq(&work.work_id).and(
|
||||
persons::name
|
||||
.like(&search)
|
||||
.or(ensembles::name.like(&search)),
|
||||
),
|
||||
)
|
||||
.limit(9)
|
||||
.select(recordings::all_columns)
|
||||
.distinct()
|
||||
.load::<tables::Recording>(connection)?
|
||||
.into_iter()
|
||||
.map(|r| Recording::from_table(r, connection))
|
||||
.collect::<Result<Vec<Recording>>>()?;
|
||||
|
||||
Ok(recordings)
|
||||
}
|
||||
|
||||
pub fn all_works(&self) -> Result<Vec<Work>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
config, home_page::MusicusHomePage, library::MusicusLibrary, library_manager::LibraryManager,
|
||||
player::MusicusPlayer, player_bar::PlayerBar, playlist_page::MusicusPlaylistPage,
|
||||
welcome_page::MusicusWelcomePage,
|
||||
config, editor::tracks_editor::TracksEditor, home_page::MusicusHomePage,
|
||||
library::MusicusLibrary, library_manager::LibraryManager, player::MusicusPlayer,
|
||||
player_bar::PlayerBar, playlist_page::MusicusPlaylistPage, welcome_page::MusicusWelcomePage,
|
||||
};
|
||||
|
||||
use adw::subclass::prelude::*;
|
||||
|
|
@ -15,8 +15,8 @@ mod imp {
|
|||
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||
#[template(file = "data/ui/window.blp")]
|
||||
pub struct MusicusWindow {
|
||||
pub library: RefCell<Option<MusicusLibrary>>,
|
||||
pub player: MusicusPlayer,
|
||||
pub library_manager: RefCell<Option<LibraryManager>>,
|
||||
|
||||
#[template_child]
|
||||
pub stack: TemplateChild<gtk::Stack>,
|
||||
|
|
@ -52,14 +52,29 @@ mod imp {
|
|||
self.obj().add_css_class("devel");
|
||||
}
|
||||
|
||||
let navigation_view = self.navigation_view.get().to_owned();
|
||||
let library_action = gio::ActionEntry::builder("library")
|
||||
.activate(move |_: &super::MusicusWindow, _, _| {
|
||||
navigation_view.push_by_tag("library")
|
||||
let obj = self.obj().to_owned();
|
||||
let import_action = gio::ActionEntry::builder("import")
|
||||
.activate(move |_, _, _| {
|
||||
if let Some(library) = &*obj.imp().library.borrow() {
|
||||
let editor = TracksEditor::new(&obj.imp().navigation_view, library, None);
|
||||
obj.imp().navigation_view.push(&editor);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
self.obj().add_action_entries([library_action]);
|
||||
let obj = self.obj().to_owned();
|
||||
let library_action = gio::ActionEntry::builder("library")
|
||||
.activate(move |_, _, _| {
|
||||
if let Some(library) = &*obj.imp().library.borrow() {
|
||||
let library_manager =
|
||||
LibraryManager::new(&obj.imp().navigation_view, library);
|
||||
obj.imp().navigation_view.push(&library_manager);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
self.obj()
|
||||
.add_action_entries([import_action, library_action]);
|
||||
|
||||
let player_bar = PlayerBar::new(&self.player);
|
||||
self.player_bar_revealer.set_child(Some(&player_bar));
|
||||
|
|
@ -174,16 +189,9 @@ impl MusicusWindow {
|
|||
self.imp().player.set_library(&library);
|
||||
|
||||
let navigation = self.imp().navigation_view.get();
|
||||
if let Some(library_manager) = self.imp().library_manager.take() {
|
||||
navigation.remove(&library_manager);
|
||||
}
|
||||
|
||||
let library_manager = LibraryManager::new(&navigation, &library);
|
||||
|
||||
navigation
|
||||
.replace(&[MusicusHomePage::new(&navigation, &library, &self.imp().player).into()]);
|
||||
navigation.add(&library_manager);
|
||||
|
||||
self.imp().library_manager.replace(Some(library_manager));
|
||||
self.imp().library.replace(Some(library));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue