mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Inital library manager UI
This commit is contained in:
parent
38638d6fcd
commit
f0135cd415
8 changed files with 1528 additions and 486 deletions
1047
Cargo.lock
generated
1047
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -12,10 +12,11 @@ diesel_migrations = "2.2"
|
|||
fragile = "2"
|
||||
gettext-rs = { version = "0.7", features = ["gettext-system"] }
|
||||
gstreamer-play = "0.23"
|
||||
gtk = { package = "gtk4", version = "0.9", features = ["v4_12", "blueprint"] }
|
||||
gtk = { package = "gtk4", version = "0.9", features = ["v4_18", "blueprint"] }
|
||||
glib = { version = "0.20", features = ["v2_84"] }
|
||||
lazy_static = "1"
|
||||
log = "0.4"
|
||||
mpris-player = "0.6"
|
||||
mpris-server = "0.8"
|
||||
once_cell = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
|
|
|||
|
|
@ -9,48 +9,286 @@ template $MusicusLibraryManager : Adw.NavigationPage {
|
|||
[top]
|
||||
Adw.HeaderBar {}
|
||||
|
||||
Gtk.ScrolledWindow {
|
||||
Adw.Clamp {
|
||||
Gtk.Box {
|
||||
orientation: vertical;
|
||||
spacing: 12;
|
||||
margin-bottom: 24;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add person");
|
||||
clicked => $add_person() swapped;
|
||||
Gtk.Label {
|
||||
label: _("Overview");
|
||||
xalign: 0;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add role");
|
||||
clicked => $add_role() swapped;
|
||||
Gtk.ListBox {
|
||||
selection-mode: none;
|
||||
margin-top: 12;
|
||||
|
||||
styles [
|
||||
"boxed-list-separate"
|
||||
]
|
||||
|
||||
Adw.ActionRow library_path_row {
|
||||
title: _("Library path");
|
||||
activatable: true;
|
||||
activated => $open_library() swapped;
|
||||
|
||||
styles [
|
||||
"property"
|
||||
]
|
||||
|
||||
[suffix]
|
||||
Gtk.Image {
|
||||
icon-name: "document-edit-symbolic";
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add instrument");
|
||||
clicked => $add_instrument() swapped;
|
||||
Adw.ButtonRow {
|
||||
title: _("Import from archive");
|
||||
end-icon-name: "go-next-symbolic";
|
||||
activated => $import_archive() swapped;
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add work");
|
||||
clicked => $add_work() swapped;
|
||||
Adw.ButtonRow {
|
||||
title: _("Export to archive");
|
||||
end-icon-name: "go-next-symbolic";
|
||||
activated => $export_archive() swapped;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add ensemble");
|
||||
clicked => $add_ensemble() swapped;
|
||||
Gtk.Label {
|
||||
label: _("Contents");
|
||||
xalign: 0;
|
||||
margin-top: 24;
|
||||
|
||||
styles [
|
||||
"heading"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add recording");
|
||||
clicked => $add_recording() swapped;
|
||||
Gtk.ListBox {
|
||||
selection-mode: none;
|
||||
margin-top: 12;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Persons");
|
||||
activatable: true;
|
||||
activated => $show_persons() swapped;
|
||||
|
||||
[suffix]
|
||||
Gtk.Box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label n_persons_label {
|
||||
label: "0";
|
||||
|
||||
styles [
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add album");
|
||||
clicked => $add_album() swapped;
|
||||
Gtk.Image {
|
||||
icon-name: "go-next-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.Button {
|
||||
label: _("Add medium");
|
||||
clicked => $add_medium() swapped;
|
||||
Adw.ActionRow {
|
||||
title: _("Roles");
|
||||
activatable: true;
|
||||
activated => $show_roles() swapped;
|
||||
|
||||
[suffix]
|
||||
Gtk.Box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label n_roles_label {
|
||||
label: "0";
|
||||
|
||||
styles [
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "go-next-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Instruments");
|
||||
activatable: true;
|
||||
activated => $show_instruments() swapped;
|
||||
|
||||
[suffix]
|
||||
Gtk.Box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label n_instruments_label {
|
||||
label: "0";
|
||||
|
||||
styles [
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "go-next-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Works");
|
||||
activatable: true;
|
||||
activated => $show_works() swapped;
|
||||
|
||||
[suffix]
|
||||
Gtk.Box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label n_works_label {
|
||||
label: "0";
|
||||
|
||||
styles [
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "go-next-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Ensembles");
|
||||
activatable: true;
|
||||
activated => $show_ensembles() swapped;
|
||||
|
||||
[suffix]
|
||||
Gtk.Box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label n_ensembles_label {
|
||||
label: "0";
|
||||
|
||||
styles [
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "go-next-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Recordings");
|
||||
activatable: true;
|
||||
activated => $show_recordings() swapped;
|
||||
|
||||
[suffix]
|
||||
Gtk.Box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label n_recordings_label {
|
||||
label: "0";
|
||||
|
||||
styles [
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "go-next-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Tracks");
|
||||
activatable: true;
|
||||
activated => $show_tracks() swapped;
|
||||
|
||||
[suffix]
|
||||
Gtk.Box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label n_tracks_label {
|
||||
label: "0";
|
||||
|
||||
styles [
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "go-next-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Mediums");
|
||||
activatable: true;
|
||||
activated => $show_mediums() swapped;
|
||||
|
||||
[suffix]
|
||||
Gtk.Box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label n_mediums_label {
|
||||
label: "0";
|
||||
|
||||
styles [
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "go-next-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow {
|
||||
title: _("Albums");
|
||||
activatable: true;
|
||||
activated => $show_albums() swapped;
|
||||
|
||||
[suffix]
|
||||
Gtk.Box {
|
||||
spacing: 6;
|
||||
|
||||
Gtk.Label n_albums_label {
|
||||
label: "0";
|
||||
|
||||
styles [
|
||||
"numeric"
|
||||
]
|
||||
}
|
||||
|
||||
Gtk.Image {
|
||||
icon-name: "go-next-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
111
src/library.rs
111
src/library.rs
|
|
@ -1,8 +1,9 @@
|
|||
use std::{
|
||||
cell::{OnceCell, RefCell},
|
||||
path::{Path, PathBuf},
|
||||
use crate::{
|
||||
db::{self, models::*, schema::*, tables, TranslatedString},
|
||||
program::Program,
|
||||
};
|
||||
|
||||
use adw::gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
|
||||
use anyhow::Result;
|
||||
use chrono::prelude::*;
|
||||
use diesel::{
|
||||
|
|
@ -12,11 +13,10 @@ use diesel::{
|
|||
sql_types::BigInt,
|
||||
QueryDsl, SqliteConnection,
|
||||
};
|
||||
use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
|
||||
|
||||
use crate::{
|
||||
db::{self, models::*, schema::*, tables, TranslatedString},
|
||||
program::Program,
|
||||
use std::{
|
||||
cell::{OnceCell, RefCell},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
diesel::define_sql_function! {
|
||||
|
|
@ -537,6 +537,15 @@ impl MusicusLibrary {
|
|||
Ok(persons)
|
||||
}
|
||||
|
||||
pub fn all_persons(&self) -> Result<Vec<Person>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let persons = persons::table.order(persons::name).load(connection)?;
|
||||
|
||||
Ok(persons)
|
||||
}
|
||||
|
||||
pub fn search_roles(&self, search: &str) -> Result<Vec<Role>> {
|
||||
let search = format!("%{}%", search);
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
|
|
@ -551,6 +560,15 @@ impl MusicusLibrary {
|
|||
Ok(roles)
|
||||
}
|
||||
|
||||
pub fn all_roles(&self) -> Result<Vec<Role>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let roles = roles::table.order(roles::name).load(connection)?;
|
||||
|
||||
Ok(roles)
|
||||
}
|
||||
|
||||
pub fn search_instruments(&self, search: &str) -> Result<Vec<Instrument>> {
|
||||
let search = format!("%{}%", search);
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
|
|
@ -565,6 +583,17 @@ impl MusicusLibrary {
|
|||
Ok(instruments)
|
||||
}
|
||||
|
||||
pub fn all_instruments(&self) -> Result<Vec<Instrument>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let instruments = instruments::table
|
||||
.order(instruments::name)
|
||||
.load(connection)?;
|
||||
|
||||
Ok(instruments)
|
||||
}
|
||||
|
||||
pub fn search_works(&self, composer: &Person, search: &str) -> Result<Vec<Work>> {
|
||||
let search = format!("%{}%", search);
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
|
|
@ -588,6 +617,20 @@ impl MusicusLibrary {
|
|||
Ok(works)
|
||||
}
|
||||
|
||||
pub fn all_works(&self) -> Result<Vec<Work>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let works = works::table
|
||||
.order(works::name)
|
||||
.load::<tables::Work>(connection)?
|
||||
.into_iter()
|
||||
.map(|w| Work::from_table(w, connection))
|
||||
.collect::<Result<Vec<Work>>>()?;
|
||||
|
||||
Ok(works)
|
||||
}
|
||||
|
||||
pub fn search_ensembles(&self, search: &str) -> Result<Vec<Ensemble>> {
|
||||
let search = format!("%{}%", search);
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
|
|
@ -611,6 +654,60 @@ impl MusicusLibrary {
|
|||
Ok(ensembles)
|
||||
}
|
||||
|
||||
pub fn all_ensembles(&self) -> Result<Vec<Ensemble>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let ensembles = ensembles::table
|
||||
.order(ensembles::name)
|
||||
.load::<tables::Ensemble>(connection)?
|
||||
.into_iter()
|
||||
.map(|e| Ensemble::from_table(e, connection))
|
||||
.collect::<Result<Vec<Ensemble>>>()?;
|
||||
|
||||
Ok(ensembles)
|
||||
}
|
||||
|
||||
pub fn all_recordings(&self) -> Result<Vec<Recording>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let recordings = recordings::table
|
||||
.load::<tables::Recording>(connection)?
|
||||
.into_iter()
|
||||
.map(|e| Recording::from_table(e, connection))
|
||||
.collect::<Result<Vec<Recording>>>()?;
|
||||
|
||||
Ok(recordings)
|
||||
}
|
||||
|
||||
pub fn all_tracks(&self) -> Result<Vec<Track>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let tracks = tracks::table
|
||||
.load::<tables::Track>(connection)?
|
||||
.into_iter()
|
||||
.map(|e| Track::from_table(e, connection))
|
||||
.collect::<Result<Vec<Track>>>()?;
|
||||
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
pub fn all_mediums(&self) -> Result<Vec<tables::Medium>> {
|
||||
// TODO
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
pub fn all_albums(&self) -> Result<Vec<Album>> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
||||
let albums = albums::table.load::<Album>(connection)?;
|
||||
|
||||
Ok(albums)
|
||||
}
|
||||
|
||||
pub fn composer_default_role(&self) -> Result<Role> {
|
||||
let mut binding = self.imp().connection.borrow_mut();
|
||||
let connection = &mut *binding.as_mut().unwrap();
|
||||
|
|
|
|||
|
|
@ -1,14 +1,20 @@
|
|||
use adw::subclass::prelude::*;
|
||||
use gtk::glib;
|
||||
use std::cell::OnceCell;
|
||||
|
||||
use crate::{
|
||||
editor::{
|
||||
ensemble_editor::MusicusEnsembleEditor, instrument_editor::MusicusInstrumentEditor,
|
||||
person_editor::MusicusPersonEditor, recording_editor::MusicusRecordingEditor,
|
||||
role_editor::MusicusRoleEditor, work_editor::MusicusWorkEditor,
|
||||
db::{
|
||||
models::{Album, Ensemble, Instrument, Person, Recording, Role, Track, Work},
|
||||
tables::Medium,
|
||||
},
|
||||
library::MusicusLibrary,
|
||||
window::MusicusWindow,
|
||||
};
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::gettext;
|
||||
use gtk::glib;
|
||||
|
||||
use std::{
|
||||
cell::{OnceCell, RefCell},
|
||||
ffi::OsStr,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
mod imp {
|
||||
|
|
@ -19,6 +25,37 @@ mod imp {
|
|||
pub struct LibraryManager {
|
||||
pub navigation: OnceCell<adw::NavigationView>,
|
||||
pub library: OnceCell<MusicusLibrary>,
|
||||
|
||||
pub persons: RefCell<Vec<Person>>,
|
||||
pub roles: RefCell<Vec<Role>>,
|
||||
pub instruments: RefCell<Vec<Instrument>>,
|
||||
pub works: RefCell<Vec<Work>>,
|
||||
pub ensembles: RefCell<Vec<Ensemble>>,
|
||||
pub recordings: RefCell<Vec<Recording>>,
|
||||
pub tracks: RefCell<Vec<Track>>,
|
||||
pub mediums: RefCell<Vec<Medium>>,
|
||||
pub albums: RefCell<Vec<Album>>,
|
||||
|
||||
#[template_child]
|
||||
pub library_path_row: TemplateChild<adw::ActionRow>,
|
||||
#[template_child]
|
||||
pub n_persons_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub n_roles_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub n_instruments_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub n_works_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub n_ensembles_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub n_recordings_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub n_tracks_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub n_mediums_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub n_albums_label: TemplateChild<gtk::Label>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
|
@ -39,7 +76,13 @@ mod imp {
|
|||
|
||||
impl ObjectImpl for LibraryManager {}
|
||||
impl WidgetImpl for LibraryManager {}
|
||||
impl NavigationPageImpl for LibraryManager {}
|
||||
|
||||
impl NavigationPageImpl for LibraryManager {
|
||||
fn showing(&self) {
|
||||
self.parent_showing();
|
||||
self.obj().update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
|
|
@ -60,99 +103,215 @@ impl LibraryManager {
|
|||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_person(&self, _: >k::Button) {
|
||||
async fn open_library(&self, _: &adw::ActionRow) {
|
||||
let dialog = gtk::FileDialog::builder()
|
||||
.title(gettext("Select music library folder"))
|
||||
.modal(true)
|
||||
.build();
|
||||
|
||||
let root = self.root();
|
||||
let window = root
|
||||
.as_ref()
|
||||
.and_then(|r| r.downcast_ref::<gtk::Window>())
|
||||
.and_then(|w| w.downcast_ref::<MusicusWindow>())
|
||||
.unwrap();
|
||||
|
||||
match dialog.select_folder_future(Some(window)).await {
|
||||
Err(err) => {
|
||||
if !err.matches(gtk::DialogError::Dismissed) {
|
||||
log::error!("Folder selection failed: {err}");
|
||||
}
|
||||
}
|
||||
Ok(folder) => window.set_library_folder(&folder),
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn import_archive(&self, _: &adw::ButtonRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn export_archive(&self, _: &adw::ButtonRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn show_persons(&self, _: &adw::ActionRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn show_roles(&self, _: &adw::ActionRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn show_instruments(&self, _: &adw::ActionRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn show_works(&self, _: &adw::ActionRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn show_ensembles(&self, _: &adw::ActionRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn show_recordings(&self, _: &adw::ActionRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn show_tracks(&self, _: &adw::ActionRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn show_mediums(&self, _: &adw::ActionRow) {}
|
||||
|
||||
#[template_callback]
|
||||
fn show_albums(&self, _: &adw::ActionRow) {}
|
||||
|
||||
// TODO: Make this async.
|
||||
fn update(&self) {
|
||||
let library = self.imp().library.get().unwrap();
|
||||
|
||||
if let Some(Some(filename)) = Path::new(&library.folder()).file_name().map(OsStr::to_str) {
|
||||
self.imp().library_path_row.set_subtitle(filename);
|
||||
}
|
||||
|
||||
let persons = library.all_persons().unwrap();
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusPersonEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
.n_persons_label
|
||||
.set_label(&persons.len().to_string());
|
||||
self.imp().persons.replace(persons);
|
||||
|
||||
#[template_callback]
|
||||
fn add_role(&self, _: >k::Button) {
|
||||
let roles = library.all_roles().unwrap();
|
||||
self.imp().n_roles_label.set_label(&roles.len().to_string());
|
||||
self.imp().roles.replace(roles);
|
||||
|
||||
let instruments = library.all_instruments().unwrap();
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusRoleEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
.n_instruments_label
|
||||
.set_label(&instruments.len().to_string());
|
||||
self.imp().instruments.replace(instruments);
|
||||
|
||||
#[template_callback]
|
||||
fn add_instrument(&self, _: >k::Button) {
|
||||
let works = library.all_works().unwrap();
|
||||
self.imp().n_works_label.set_label(&works.len().to_string());
|
||||
self.imp().works.replace(works);
|
||||
|
||||
let ensembles = library.all_ensembles().unwrap();
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusInstrumentEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
.n_ensembles_label
|
||||
.set_label(&ensembles.len().to_string());
|
||||
self.imp().ensembles.replace(ensembles);
|
||||
|
||||
#[template_callback]
|
||||
fn add_work(&self, _: >k::Button) {
|
||||
let recordings = library.all_recordings().unwrap();
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusWorkEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
.n_recordings_label
|
||||
.set_label(&recordings.len().to_string());
|
||||
self.imp().recordings.replace(recordings);
|
||||
|
||||
#[template_callback]
|
||||
fn add_ensemble(&self, _: >k::Button) {
|
||||
let tracks = library.all_tracks().unwrap();
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusEnsembleEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
}
|
||||
.n_tracks_label
|
||||
.set_label(&tracks.len().to_string());
|
||||
self.imp().tracks.replace(tracks);
|
||||
|
||||
#[template_callback]
|
||||
fn add_recording(&self, _: >k::Button) {
|
||||
let mediums = library.all_mediums().unwrap();
|
||||
self.imp()
|
||||
.navigation
|
||||
.get()
|
||||
.unwrap()
|
||||
.push(&MusicusRecordingEditor::new(
|
||||
&self.imp().navigation.get().unwrap(),
|
||||
&self.imp().library.get().unwrap(),
|
||||
None,
|
||||
));
|
||||
.n_mediums_label
|
||||
.set_label(&mediums.len().to_string());
|
||||
self.imp().mediums.replace(mediums);
|
||||
|
||||
let albums = library.all_albums().unwrap();
|
||||
self.imp()
|
||||
.n_albums_label
|
||||
.set_label(&albums.len().to_string());
|
||||
self.imp().albums.replace(albums);
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_medium(&self, _: >k::Button) {
|
||||
todo!("Medium import");
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn add_album(&self, _: >k::Button) {
|
||||
todo!("Album editor");
|
||||
// #[template_callback]
|
||||
// fn add_person(&self, _: >k::Button) {
|
||||
// self.imp()
|
||||
// .navigation
|
||||
// .get()
|
||||
// .unwrap()
|
||||
// .push(&MusicusAlbumEditor::new(
|
||||
// .push(&MusicusPersonEditor::new(
|
||||
// &self.imp().navigation.get().unwrap(),
|
||||
// &self.imp().library.get().unwrap(),
|
||||
// None,
|
||||
// ));
|
||||
}
|
||||
// }
|
||||
|
||||
// #[template_callback]
|
||||
// fn add_role(&self, _: >k::Button) {
|
||||
// self.imp()
|
||||
// .navigation
|
||||
// .get()
|
||||
// .unwrap()
|
||||
// .push(&MusicusRoleEditor::new(
|
||||
// &self.imp().navigation.get().unwrap(),
|
||||
// &self.imp().library.get().unwrap(),
|
||||
// None,
|
||||
// ));
|
||||
// }
|
||||
|
||||
// #[template_callback]
|
||||
// fn add_instrument(&self, _: >k::Button) {
|
||||
// self.imp()
|
||||
// .navigation
|
||||
// .get()
|
||||
// .unwrap()
|
||||
// .push(&MusicusInstrumentEditor::new(
|
||||
// &self.imp().navigation.get().unwrap(),
|
||||
// &self.imp().library.get().unwrap(),
|
||||
// None,
|
||||
// ));
|
||||
// }
|
||||
|
||||
// #[template_callback]
|
||||
// fn add_work(&self, _: >k::Button) {
|
||||
// self.imp()
|
||||
// .navigation
|
||||
// .get()
|
||||
// .unwrap()
|
||||
// .push(&MusicusWorkEditor::new(
|
||||
// &self.imp().navigation.get().unwrap(),
|
||||
// &self.imp().library.get().unwrap(),
|
||||
// None,
|
||||
// ));
|
||||
// }
|
||||
|
||||
// #[template_callback]
|
||||
// fn add_ensemble(&self, _: >k::Button) {
|
||||
// self.imp()
|
||||
// .navigation
|
||||
// .get()
|
||||
// .unwrap()
|
||||
// .push(&MusicusEnsembleEditor::new(
|
||||
// &self.imp().navigation.get().unwrap(),
|
||||
// &self.imp().library.get().unwrap(),
|
||||
// None,
|
||||
// ));
|
||||
// }
|
||||
|
||||
// #[template_callback]
|
||||
// fn add_recording(&self, _: >k::Button) {
|
||||
// self.imp()
|
||||
// .navigation
|
||||
// .get()
|
||||
// .unwrap()
|
||||
// .push(&MusicusRecordingEditor::new(
|
||||
// &self.imp().navigation.get().unwrap(),
|
||||
// &self.imp().library.get().unwrap(),
|
||||
// None,
|
||||
// ));
|
||||
// }
|
||||
|
||||
// #[template_callback]
|
||||
// fn add_medium(&self, _: >k::Button) {
|
||||
// todo!("Medium import");
|
||||
// }
|
||||
|
||||
// #[template_callback]
|
||||
// fn add_album(&self, _: >k::Button) {
|
||||
// todo!("Album editor");
|
||||
// // self.imp()
|
||||
// // .navigation
|
||||
// // .get()
|
||||
// // .unwrap()
|
||||
// // .push(&MusicusAlbumEditor::new(
|
||||
// // &self.imp().navigation.get().unwrap(),
|
||||
// // &self.imp().library.get().unwrap(),
|
||||
// // None,
|
||||
// // ));
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
161
src/player.rs
161
src/player.rs
|
|
@ -1,7 +1,6 @@
|
|||
use std::{
|
||||
cell::{Cell, OnceCell, RefCell},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use fragile::Fragile;
|
||||
|
|
@ -12,7 +11,6 @@ use gtk::{
|
|||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use mpris_player::{Metadata, MprisPlayer, PlaybackStatus};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -48,7 +46,7 @@ mod imp {
|
|||
|
||||
pub play: OnceCell<gstreamer_play::Play>,
|
||||
pub play_signal_adapter: OnceCell<gstreamer_play::PlaySignalAdapter>,
|
||||
pub mpris: OnceCell<Arc<MprisPlayer>>,
|
||||
pub mpris: OnceCell<mpris_server::Player>,
|
||||
}
|
||||
|
||||
impl MusicusPlayer {
|
||||
|
|
@ -75,10 +73,22 @@ mod imp {
|
|||
}
|
||||
|
||||
let item = item.downcast::<PlaylistItem>().unwrap();
|
||||
self.mpris.get().unwrap().set_metadata(Metadata {
|
||||
artist: Some(vec![item.make_title()]),
|
||||
title: item.make_subtitle(),
|
||||
..Default::default()
|
||||
|
||||
let obj = self.obj().clone();
|
||||
let item_clone = item.clone();
|
||||
glib::spawn_future_local(async move {
|
||||
obj.imp()
|
||||
.mpris
|
||||
.get()
|
||||
.unwrap()
|
||||
.set_metadata(
|
||||
mpris_server::Metadata::builder()
|
||||
.artist(vec![item_clone.make_title()])
|
||||
.title(item_clone.make_subtitle().unwrap_or_else(String::new))
|
||||
.build(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let uri = glib::filename_to_uri(item.path(), None)
|
||||
|
|
@ -121,56 +131,13 @@ mod imp {
|
|||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj().clone();
|
||||
glib::spawn_future_local(async move {
|
||||
obj.init_mpris().await;
|
||||
});
|
||||
|
||||
let play = gstreamer_play::Play::new(None::<gstreamer_play::PlayVideoRenderer>);
|
||||
|
||||
let mpris = MprisPlayer::new(
|
||||
config::APP_ID.to_owned(),
|
||||
config::NAME.to_owned(),
|
||||
config::APP_ID.to_owned(),
|
||||
);
|
||||
|
||||
mpris.set_can_raise(true);
|
||||
mpris.set_can_play(true);
|
||||
mpris.set_can_pause(true);
|
||||
mpris.set_can_go_previous(true);
|
||||
mpris.set_can_go_next(true);
|
||||
mpris.set_can_seek(false);
|
||||
mpris.set_can_set_fullscreen(false);
|
||||
|
||||
let obj = self.obj();
|
||||
mpris.connect_raise(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move || obj.emit_by_name::<()>("raise", &[])
|
||||
));
|
||||
mpris.connect_play(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move || obj.play()
|
||||
));
|
||||
mpris.connect_pause(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move || obj.pause()
|
||||
));
|
||||
mpris.connect_play_pause(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move || obj.play_pause()
|
||||
));
|
||||
mpris.connect_previous(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move || obj.previous()
|
||||
));
|
||||
mpris.connect_next(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move || obj.next()
|
||||
));
|
||||
|
||||
self.mpris.set(mpris).expect("mpris should not be set");
|
||||
|
||||
let mut config = play.config();
|
||||
config.set_position_update_interval(250);
|
||||
play.set_config(config).unwrap();
|
||||
|
|
@ -237,7 +204,11 @@ impl MusicusPlayer {
|
|||
}
|
||||
|
||||
pub fn play_recording(&self, recording: &Recording) {
|
||||
let tracks = &self.library().unwrap().tracks_for_recording(&recording.recording_id).unwrap();
|
||||
let tracks = &self
|
||||
.library()
|
||||
.unwrap()
|
||||
.tracks_for_recording(&recording.recording_id)
|
||||
.unwrap();
|
||||
|
||||
if tracks.is_empty() {
|
||||
log::warn!("Ignoring recording without tracks being added to the playlist.");
|
||||
|
|
@ -330,20 +301,34 @@ impl MusicusPlayer {
|
|||
let imp = self.imp();
|
||||
imp.play.get().unwrap().play();
|
||||
self.set_playing(true);
|
||||
imp.mpris
|
||||
|
||||
let obj = self.clone();
|
||||
glib::spawn_future_local(async move {
|
||||
obj.imp()
|
||||
.mpris
|
||||
.get()
|
||||
.unwrap()
|
||||
.set_playback_status(PlaybackStatus::Playing);
|
||||
.set_playback_status(mpris_server::PlaybackStatus::Playing)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn pause(&self) {
|
||||
let imp = self.imp();
|
||||
imp.play.get().unwrap().pause();
|
||||
self.set_playing(false);
|
||||
imp.mpris
|
||||
|
||||
let obj = self.clone();
|
||||
glib::spawn_future_local(async move {
|
||||
obj.imp()
|
||||
.mpris
|
||||
.get()
|
||||
.unwrap()
|
||||
.set_playback_status(PlaybackStatus::Paused);
|
||||
.set_playback_status(mpris_server::PlaybackStatus::Paused)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
pub fn seek_to(&self, time_ms: u64) {
|
||||
|
|
@ -378,6 +363,62 @@ impl MusicusPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
async fn init_mpris(&self) {
|
||||
let mpris = mpris_server::Player::builder(config::APP_ID)
|
||||
.desktop_entry(config::APP_ID)
|
||||
.can_raise(true)
|
||||
.can_play(true)
|
||||
.can_pause(true)
|
||||
.can_go_previous(true)
|
||||
.can_go_next(true)
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let obj = self.clone();
|
||||
|
||||
mpris.connect_raise(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| obj.emit_by_name::<()>("raise", &[])
|
||||
));
|
||||
|
||||
mpris.connect_play(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| obj.play()
|
||||
));
|
||||
|
||||
mpris.connect_pause(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| obj.pause()
|
||||
));
|
||||
|
||||
mpris.connect_play_pause(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| obj.play_pause()
|
||||
));
|
||||
|
||||
mpris.connect_previous(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| obj.previous()
|
||||
));
|
||||
|
||||
mpris.connect_next(clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| obj.next()
|
||||
));
|
||||
|
||||
self.imp()
|
||||
.mpris
|
||||
.set(mpris)
|
||||
.expect("mpris should not be set");
|
||||
}
|
||||
|
||||
fn generate_items(&self, program: &Program) {
|
||||
if let Some(library) = self.library() {
|
||||
// TODO: if program.play_full_recordings() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use gtk::{
|
|||
subclass::prelude::*,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cell::{Cell, OnceCell};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
|
@ -16,7 +16,7 @@ mod imp {
|
|||
#[template(file = "data/ui/player_bar.blp")]
|
||||
pub struct PlayerBar {
|
||||
#[property(get, construct_only)]
|
||||
pub player: RefCell<MusicusPlayer>,
|
||||
pub player: OnceCell<MusicusPlayer>,
|
||||
|
||||
pub seeking: Cell<bool>,
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ mod imp {
|
|||
|
||||
impl PlayerBar {
|
||||
fn update_item(&self) {
|
||||
if let Some(item) = self.player.borrow().current_item() {
|
||||
if let Some(item) = self.player.get().unwrap().current_item() {
|
||||
self.title_label.set_label(&item.make_title());
|
||||
|
||||
if let Some(subtitle) = item.make_subtitle() {
|
||||
|
|
@ -55,7 +55,7 @@ mod imp {
|
|||
}
|
||||
|
||||
fn update_time(&self) {
|
||||
let player = self.player.borrow();
|
||||
let player = self.player.get().unwrap();
|
||||
|
||||
let current_time_ms = if self.seeking.get() {
|
||||
(self.slider.value() * player.duration_ms() as f64) as u64
|
||||
|
|
@ -106,7 +106,7 @@ mod imp {
|
|||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let player = self.player.borrow();
|
||||
let player = self.player.get().unwrap();
|
||||
|
||||
player
|
||||
.bind_property("playing", &self.play_button.get(), "icon-name")
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use std::path::Path;
|
||||
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::{gio, glib, glib::clone, prelude::*};
|
||||
|
||||
use crate::{
|
||||
config, home_page::MusicusHomePage, library::MusicusLibrary, library_manager::LibraryManager,
|
||||
player::MusicusPlayer, player_bar::PlayerBar, playlist_page::MusicusPlaylistPage,
|
||||
welcome_page::MusicusWelcomePage,
|
||||
};
|
||||
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::{gio, glib, glib::clone, prelude::*};
|
||||
|
||||
use std::{cell::RefCell, path::Path};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ mod imp {
|
|||
#[template(file = "data/ui/window.blp")]
|
||||
pub struct MusicusWindow {
|
||||
pub player: MusicusPlayer,
|
||||
pub library_manager: RefCell<Option<LibraryManager>>,
|
||||
|
||||
#[template_child]
|
||||
pub stack: TemplateChild<gtk::Stack>,
|
||||
|
|
@ -157,7 +158,7 @@ impl MusicusWindow {
|
|||
}
|
||||
|
||||
#[template_callback]
|
||||
fn set_library_folder(&self, folder: &gio::File) {
|
||||
pub fn set_library_folder(&self, folder: &gio::File) {
|
||||
let path = folder.path().unwrap();
|
||||
|
||||
let settings = gio::Settings::new(config::APP_ID);
|
||||
|
|
@ -173,8 +174,16 @@ 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(&LibraryManager::new(&navigation, &library));
|
||||
navigation.add(&library_manager);
|
||||
|
||||
self.imp().library_manager.replace(Some(library_manager));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue