mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
New search page
This commit is contained in:
parent
a6e0935df8
commit
cff489f43e
12 changed files with 830 additions and 793 deletions
90
data/ui/album_page.blp
Normal file
90
data/ui/album_page.blp
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusAlbumPage: Adw.NavigationPage {
|
||||||
|
title: _("Album");
|
||||||
|
|
||||||
|
Adw.ToolbarView {
|
||||||
|
[top]
|
||||||
|
Adw.HeaderBar {
|
||||||
|
show-title: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ScrolledWindow {
|
||||||
|
Adw.Clamp {
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
margin-bottom: 24;
|
||||||
|
margin-start: 12;
|
||||||
|
margin-end: 12;
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
spacing: 12;
|
||||||
|
margin-top: 24;
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
hexpand: true;
|
||||||
|
|
||||||
|
Gtk.Label title_label {
|
||||||
|
wrap: true;
|
||||||
|
xalign: 0.0;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"title-1",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label subtitle_label {
|
||||||
|
wrap: true;
|
||||||
|
xalign: 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Button {
|
||||||
|
icon-name: "document-edit-symbolic";
|
||||||
|
valign: center;
|
||||||
|
clicked => $edit_button_clicked() swapped;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"flat",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Button {
|
||||||
|
icon-name: "media-playback-start-symbolic";
|
||||||
|
label: _("_Play album");
|
||||||
|
use-underline: true;
|
||||||
|
valign: center;
|
||||||
|
clicked => $play_button_clicked() swapped;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"pill",
|
||||||
|
"suggested-action",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: _("Recordings");
|
||||||
|
xalign: 0;
|
||||||
|
margin-top: 24;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"heading",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.FlowBox recordings_flow_box {
|
||||||
|
margin-top: 12;
|
||||||
|
column-spacing: 12;
|
||||||
|
row-spacing: 12;
|
||||||
|
homogeneous: true;
|
||||||
|
selection-mode: none;
|
||||||
|
child-activated => $recording_selected() swapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
using Gtk 4.0;
|
|
||||||
|
|
||||||
template $MusicusSearchEntry : Gtk.Box {
|
|
||||||
styles ["searchbar"]
|
|
||||||
|
|
||||||
margin-start: 12;
|
|
||||||
margin-end: 12;
|
|
||||||
margin-top: 6;
|
|
||||||
margin-bottom: 6;
|
|
||||||
|
|
||||||
Gtk.Image {
|
|
||||||
icon-name: "system-search-symbolic";
|
|
||||||
}
|
|
||||||
|
|
||||||
Gtk.Box tags_box {
|
|
||||||
valign: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gtk.Text text {
|
|
||||||
placeholder-text: _("Enter composers, performers, works…");
|
|
||||||
hexpand: true;
|
|
||||||
activate => $activate() swapped;
|
|
||||||
backspace => $backspace() swapped;
|
|
||||||
changed => $text_changed() swapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gtk.Image clear_icon {
|
|
||||||
visible: false;
|
|
||||||
icon-name: "edit-clear-symbolic";
|
|
||||||
tooltip-text: _("Clear entry");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
using Adw 1;
|
using Adw 1;
|
||||||
|
|
||||||
template $MusicusHomePage: Adw.NavigationPage {
|
template $MusicusSearchPage: Adw.NavigationPage {
|
||||||
title: _("Musicus");
|
title: _("Musicus");
|
||||||
tag: "home";
|
|
||||||
|
|
||||||
Gtk.Overlay {
|
|
||||||
Adw.ToolbarView {
|
Adw.ToolbarView {
|
||||||
[top]
|
[top]
|
||||||
Adw.HeaderBar header_bar {
|
Adw.HeaderBar header_bar {
|
||||||
|
|
@ -16,83 +14,78 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[top]
|
Gtk.ScrolledWindow scrolled_window {
|
||||||
Adw.Clamp {
|
Adw.Clamp {
|
||||||
maximum-size: 1000;
|
maximum-size: 1000;
|
||||||
tightening-threshold: 600;
|
tightening-threshold: 600;
|
||||||
|
|
||||||
Gtk.Box {
|
Gtk.Box {
|
||||||
orientation: vertical;
|
orientation: vertical;
|
||||||
|
margin-bottom: 24;
|
||||||
$MusicusSearchEntry search_entry {
|
margin-start: 12;
|
||||||
activate => $select() swapped;
|
margin-end: 12;
|
||||||
}
|
|
||||||
|
|
||||||
Gtk.Box header_box {
|
Gtk.Box header_box {
|
||||||
visible: false;
|
visible: false;
|
||||||
spacing: 12;
|
spacing: 12;
|
||||||
margin-start: 12;
|
|
||||||
margin-end: 12;
|
|
||||||
margin-top: 24;
|
margin-top: 24;
|
||||||
margin-bottom: 12;
|
|
||||||
|
|
||||||
Gtk.Button {
|
|
||||||
styles [
|
|
||||||
"flat"
|
|
||||||
]
|
|
||||||
|
|
||||||
valign: center;
|
|
||||||
icon-name: "go-previous-symbolic";
|
|
||||||
clicked => $back_button_clicked() swapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gtk.Box {
|
Gtk.Box {
|
||||||
orientation: vertical;
|
orientation: vertical;
|
||||||
hexpand: true;
|
hexpand: true;
|
||||||
|
|
||||||
Gtk.Label title_label {
|
Gtk.Label title_label {
|
||||||
styles [
|
wrap: true;
|
||||||
"title-1"
|
|
||||||
]
|
|
||||||
|
|
||||||
xalign: 0.0;
|
xalign: 0.0;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"title-1",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Label subtitle_label {
|
Gtk.Label subtitle_label {
|
||||||
|
wrap: true;
|
||||||
xalign: 0.0;
|
xalign: 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Button {
|
Gtk.Button {
|
||||||
styles [
|
|
||||||
"flat"
|
|
||||||
]
|
|
||||||
|
|
||||||
valign: center;
|
|
||||||
icon-name: "document-edit-symbolic";
|
icon-name: "document-edit-symbolic";
|
||||||
|
valign: center;
|
||||||
clicked => $edit_button_clicked() swapped;
|
clicked => $edit_button_clicked() swapped;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"flat",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Button {
|
||||||
|
icon-name: "media-playback-start-symbolic";
|
||||||
|
label: _("_Play");
|
||||||
|
use-underline: true;
|
||||||
|
valign: center;
|
||||||
|
clicked => $play_button_clicked() swapped;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"pill",
|
||||||
|
"suggested-action",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Gtk.SearchEntry search_entry {
|
||||||
|
placeholder-text: _("Enter composers, performers, works…");
|
||||||
|
margin-top: 24;
|
||||||
|
activate => $select() swapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Stack stack {
|
Gtk.Stack stack {
|
||||||
Gtk.StackPage {
|
Gtk.StackPage {
|
||||||
name: "results";
|
name: "results";
|
||||||
|
|
||||||
child: Gtk.ScrolledWindow {
|
child: Gtk.Box {
|
||||||
hscrollbar-policy: never;
|
|
||||||
|
|
||||||
Adw.Clamp {
|
|
||||||
maximum-size: 1000;
|
|
||||||
tightening-threshold: 600;
|
|
||||||
|
|
||||||
Gtk.Box {
|
|
||||||
orientation: vertical;
|
orientation: vertical;
|
||||||
margin-start: 12;
|
|
||||||
margin-end: 12;
|
|
||||||
margin-top: 24;
|
margin-top: 24;
|
||||||
margin-bottom: 68;
|
|
||||||
|
|
||||||
Gtk.FlowBox programs_flow_box {
|
Gtk.FlowBox programs_flow_box {
|
||||||
margin-top: 12;
|
margin-top: 12;
|
||||||
|
|
@ -106,7 +99,7 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
|
|
||||||
Gtk.Label {
|
Gtk.Label {
|
||||||
styles [
|
styles [
|
||||||
"heading"
|
"heading",
|
||||||
]
|
]
|
||||||
|
|
||||||
visible: bind composers_flow_box.visible;
|
visible: bind composers_flow_box.visible;
|
||||||
|
|
@ -126,7 +119,7 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
|
|
||||||
Gtk.Label {
|
Gtk.Label {
|
||||||
styles [
|
styles [
|
||||||
"heading"
|
"heading",
|
||||||
]
|
]
|
||||||
|
|
||||||
visible: bind performers_flow_box.visible;
|
visible: bind performers_flow_box.visible;
|
||||||
|
|
@ -146,7 +139,7 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
|
|
||||||
Gtk.Label {
|
Gtk.Label {
|
||||||
styles [
|
styles [
|
||||||
"heading"
|
"heading",
|
||||||
]
|
]
|
||||||
|
|
||||||
visible: bind ensembles_flow_box.visible;
|
visible: bind ensembles_flow_box.visible;
|
||||||
|
|
@ -166,7 +159,7 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
|
|
||||||
Gtk.Label {
|
Gtk.Label {
|
||||||
styles [
|
styles [
|
||||||
"heading"
|
"heading",
|
||||||
]
|
]
|
||||||
|
|
||||||
visible: bind instruments_flow_box.visible;
|
visible: bind instruments_flow_box.visible;
|
||||||
|
|
@ -186,7 +179,7 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
|
|
||||||
Gtk.Label {
|
Gtk.Label {
|
||||||
styles [
|
styles [
|
||||||
"heading"
|
"heading",
|
||||||
]
|
]
|
||||||
|
|
||||||
visible: bind works_flow_box.visible;
|
visible: bind works_flow_box.visible;
|
||||||
|
|
@ -206,7 +199,7 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
|
|
||||||
Gtk.Label {
|
Gtk.Label {
|
||||||
styles [
|
styles [
|
||||||
"heading"
|
"heading",
|
||||||
]
|
]
|
||||||
|
|
||||||
visible: bind recordings_flow_box.visible;
|
visible: bind recordings_flow_box.visible;
|
||||||
|
|
@ -226,7 +219,7 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
|
|
||||||
Gtk.Label {
|
Gtk.Label {
|
||||||
styles [
|
styles [
|
||||||
"heading"
|
"heading",
|
||||||
]
|
]
|
||||||
|
|
||||||
visible: bind albums_flow_box.visible;
|
visible: bind albums_flow_box.visible;
|
||||||
|
|
@ -243,8 +236,6 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
selection-mode: none;
|
selection-mode: none;
|
||||||
child-activated => $album_selected() swapped;
|
child-activated => $album_selected() swapped;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,20 +250,7 @@ template $MusicusHomePage: Adw.NavigationPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
[overlay]
|
|
||||||
Gtk.Button play_button {
|
|
||||||
styles [
|
|
||||||
"pill",
|
|
||||||
"suggested-action"
|
|
||||||
]
|
|
||||||
|
|
||||||
halign: end;
|
|
||||||
valign: end;
|
|
||||||
margin-end: 24;
|
|
||||||
margin-bottom: 24;
|
|
||||||
label: _("Play music");
|
|
||||||
clicked => $play() swapped;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
132
src/album_page.rs
Normal file
132
src/album_page.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
use std::cell::OnceCell;
|
||||||
|
|
||||||
|
use adw::subclass::prelude::*;
|
||||||
|
use gtk::{
|
||||||
|
glib::{self, Properties},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
db::models::*, editor::album::AlbumEditor, library::Library, player::Player,
|
||||||
|
playlist_item::PlaylistItem, recording_tile::RecordingTile,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
|
||||||
|
#[properties(wrapper_type = super::AlbumPage)]
|
||||||
|
#[template(file = "data/ui/album_page.blp")]
|
||||||
|
pub struct AlbumPage {
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub navigation: OnceCell<adw::NavigationView>,
|
||||||
|
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub library: OnceCell<Library>,
|
||||||
|
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub player: OnceCell<Player>,
|
||||||
|
|
||||||
|
pub album: OnceCell<Album>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
pub title_label: TemplateChild<gtk::Label>,
|
||||||
|
#[template_child]
|
||||||
|
pub subtitle_label: TemplateChild<gtk::Label>,
|
||||||
|
#[template_child]
|
||||||
|
pub recordings_flow_box: TemplateChild<gtk::FlowBox>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for AlbumPage {
|
||||||
|
const NAME: &'static str = "MusicusAlbumPage";
|
||||||
|
type Type = super::AlbumPage;
|
||||||
|
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 AlbumPage {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for AlbumPage {}
|
||||||
|
impl NavigationPageImpl for AlbumPage {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct AlbumPage(ObjectSubclass<imp::AlbumPage>)
|
||||||
|
@extends gtk::Widget, adw::NavigationPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl AlbumPage {
|
||||||
|
pub fn new(
|
||||||
|
navigation: &adw::NavigationView,
|
||||||
|
library: &Library,
|
||||||
|
player: &Player,
|
||||||
|
album: Album,
|
||||||
|
) -> Self {
|
||||||
|
let obj: Self = glib::Object::builder()
|
||||||
|
.property("navigation", navigation)
|
||||||
|
.property("library", library)
|
||||||
|
.property("player", player)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
obj.imp().title_label.set_label(&album.to_string());
|
||||||
|
obj.imp().subtitle_label.set_label(&album.performers_string());
|
||||||
|
|
||||||
|
for recording in &album.recordings {
|
||||||
|
obj.imp()
|
||||||
|
.recordings_flow_box
|
||||||
|
.append(&RecordingTile::new(navigation, library, recording));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.imp().album.set(album).unwrap();
|
||||||
|
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn edit_button_clicked(&self) {
|
||||||
|
self.navigation().push(&AlbumEditor::new(
|
||||||
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
Some(&self.imp().album.get().unwrap().clone()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn play_button_clicked(&self) {
|
||||||
|
let playlist = self
|
||||||
|
.imp()
|
||||||
|
.album
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.recordings
|
||||||
|
.iter()
|
||||||
|
.map(|r| self.player().recording_to_playlist(r))
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<PlaylistItem>>();
|
||||||
|
|
||||||
|
self.player().append_and_play(playlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn recording_selected(&self, tile: >k::FlowBoxChild) {
|
||||||
|
let playlist = self
|
||||||
|
.player()
|
||||||
|
.recording_to_playlist(tile.downcast_ref::<RecordingTile>().unwrap().recording());
|
||||||
|
self.player().append_and_play(playlist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! This module contains higher-level models combining information from
|
//! This module contains higher-level models combining information from
|
||||||
//! multiple database tables.
|
//! multiple database tables.
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::{collections::HashSet, fmt::Display};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
@ -392,6 +392,27 @@ impl Album {
|
||||||
recordings,
|
recordings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn performers_string(&self) -> String {
|
||||||
|
let mut performers = HashSet::new();
|
||||||
|
let mut ensembles = HashSet::new();
|
||||||
|
|
||||||
|
for recording in &self.recordings {
|
||||||
|
for performer in &recording.persons {
|
||||||
|
performers.insert(performer.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
for ensemble in &recording.ensembles {
|
||||||
|
ensembles.insert(ensemble.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
performers
|
||||||
|
.into_iter()
|
||||||
|
.chain(ensembles)
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Album {}
|
impl Eq for Album {}
|
||||||
|
|
|
||||||
389
src/home_page.rs
389
src/home_page.rs
|
|
@ -1,389 +0,0 @@
|
||||||
use std::cell::{OnceCell, RefCell};
|
|
||||||
|
|
||||||
use adw::subclass::{navigation_page::NavigationPageImpl, prelude::*};
|
|
||||||
use gtk::{
|
|
||||||
gio,
|
|
||||||
glib::{self, Properties},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
album_tile::AlbumTile,
|
|
||||||
config,
|
|
||||||
db::models::*,
|
|
||||||
editor::{
|
|
||||||
ensemble::EnsembleEditor, instrument::InstrumentEditor, person::PersonEditor,
|
|
||||||
work::WorkEditor,
|
|
||||||
},
|
|
||||||
library::{Library, LibraryQuery},
|
|
||||||
player::Player,
|
|
||||||
program::Program,
|
|
||||||
program_tile::ProgramTile,
|
|
||||||
recording_tile::RecordingTile,
|
|
||||||
search_entry::SearchEntry,
|
|
||||||
search_tag::Tag,
|
|
||||||
tag_tile::TagTile,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod imp {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
|
|
||||||
#[properties(wrapper_type = super::HomePage)]
|
|
||||||
#[template(file = "data/ui/home_page.blp")]
|
|
||||||
pub struct HomePage {
|
|
||||||
#[property(get, construct_only)]
|
|
||||||
pub navigation: OnceCell<adw::NavigationView>,
|
|
||||||
|
|
||||||
#[property(get, construct_only)]
|
|
||||||
pub library: OnceCell<Library>,
|
|
||||||
|
|
||||||
#[property(get, construct_only)]
|
|
||||||
pub player: OnceCell<Player>,
|
|
||||||
|
|
||||||
pub programs: RefCell<Vec<Program>>,
|
|
||||||
pub composers: RefCell<Vec<Person>>,
|
|
||||||
pub performers: RefCell<Vec<Person>>,
|
|
||||||
pub ensembles: RefCell<Vec<Ensemble>>,
|
|
||||||
pub instruments: RefCell<Vec<Instrument>>,
|
|
||||||
pub works: RefCell<Vec<Work>>,
|
|
||||||
pub recordings: RefCell<Vec<Recording>>,
|
|
||||||
pub albums: RefCell<Vec<Album>>,
|
|
||||||
|
|
||||||
#[template_child]
|
|
||||||
pub search_entry: TemplateChild<SearchEntry>,
|
|
||||||
#[template_child]
|
|
||||||
pub stack: TemplateChild<gtk::Stack>,
|
|
||||||
#[template_child]
|
|
||||||
pub header_box: TemplateChild<gtk::Box>,
|
|
||||||
#[template_child]
|
|
||||||
pub title_label: TemplateChild<gtk::Label>,
|
|
||||||
#[template_child]
|
|
||||||
pub subtitle_label: TemplateChild<gtk::Label>,
|
|
||||||
#[template_child]
|
|
||||||
pub programs_flow_box: TemplateChild<gtk::FlowBox>,
|
|
||||||
#[template_child]
|
|
||||||
pub composers_flow_box: TemplateChild<gtk::FlowBox>,
|
|
||||||
#[template_child]
|
|
||||||
pub performers_flow_box: TemplateChild<gtk::FlowBox>,
|
|
||||||
#[template_child]
|
|
||||||
pub ensembles_flow_box: TemplateChild<gtk::FlowBox>,
|
|
||||||
#[template_child]
|
|
||||||
pub instruments_flow_box: TemplateChild<gtk::FlowBox>,
|
|
||||||
#[template_child]
|
|
||||||
pub works_flow_box: TemplateChild<gtk::FlowBox>,
|
|
||||||
#[template_child]
|
|
||||||
pub recordings_flow_box: TemplateChild<gtk::FlowBox>,
|
|
||||||
#[template_child]
|
|
||||||
pub albums_flow_box: TemplateChild<gtk::FlowBox>,
|
|
||||||
#[template_child]
|
|
||||||
pub play_button: TemplateChild<gtk::Button>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for HomePage {
|
|
||||||
const NAME: &'static str = "MusicusHomePage";
|
|
||||||
type Type = super::HomePage;
|
|
||||||
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 HomePage {
|
|
||||||
fn constructed(&self) {
|
|
||||||
self.parent_constructed();
|
|
||||||
|
|
||||||
self.search_entry.set_key_capture_widget(&*self.obj());
|
|
||||||
|
|
||||||
let obj = self.obj().to_owned();
|
|
||||||
self.search_entry.connect_query_changed(move |entry| {
|
|
||||||
obj.query(&entry.query());
|
|
||||||
});
|
|
||||||
|
|
||||||
let obj = self.obj().to_owned();
|
|
||||||
self.library.get().unwrap().connect_changed(move |_| {
|
|
||||||
obj.imp().search_entry.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
self.player
|
|
||||||
.get()
|
|
||||||
.unwrap()
|
|
||||||
.bind_property("active", &self.play_button.get(), "visible")
|
|
||||||
.invert_boolean()
|
|
||||||
.sync_create()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let settings = gio::Settings::new(&config::APP_ID);
|
|
||||||
|
|
||||||
let programs = vec![
|
|
||||||
Program::deserialize(&settings.string("program1")).unwrap(),
|
|
||||||
Program::deserialize(&settings.string("program2")).unwrap(),
|
|
||||||
Program::deserialize(&settings.string("program3")).unwrap(),
|
|
||||||
];
|
|
||||||
|
|
||||||
for program in &programs {
|
|
||||||
self.programs_flow_box
|
|
||||||
.append(&ProgramTile::new(program.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.programs.replace(programs);
|
|
||||||
|
|
||||||
self.obj().query(&LibraryQuery::default());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetImpl for HomePage {}
|
|
||||||
impl NavigationPageImpl for HomePage {}
|
|
||||||
}
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct HomePage(ObjectSubclass<imp::HomePage>)
|
|
||||||
@extends gtk::Widget, adw::NavigationPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gtk::template_callbacks]
|
|
||||||
impl HomePage {
|
|
||||||
pub fn new(navigation: &adw::NavigationView, library: &Library, player: &Player) -> Self {
|
|
||||||
glib::Object::builder()
|
|
||||||
.property("navigation", navigation)
|
|
||||||
.property("library", library)
|
|
||||||
.property("player", player)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn back_button_clicked(&self) {
|
|
||||||
self.imp().search_entry.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn edit_button_clicked(&self) {
|
|
||||||
if let Some(tag) = self.imp().search_entry.tags().first() {
|
|
||||||
match tag {
|
|
||||||
Tag::Composer(person) | Tag::Performer(person) => {
|
|
||||||
self.navigation().push(&PersonEditor::new(
|
|
||||||
&self.navigation(),
|
|
||||||
&self.library(),
|
|
||||||
Some(person),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Tag::Ensemble(ensemble) => {
|
|
||||||
self.navigation().push(&EnsembleEditor::new(
|
|
||||||
&self.navigation(),
|
|
||||||
&self.library(),
|
|
||||||
Some(ensemble),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Tag::Instrument(instrument) => self.navigation().push(&InstrumentEditor::new(
|
|
||||||
&self.navigation(),
|
|
||||||
&self.library(),
|
|
||||||
Some(instrument),
|
|
||||||
)),
|
|
||||||
Tag::Work(work) => self.navigation().push(&WorkEditor::new(
|
|
||||||
&self.navigation(),
|
|
||||||
&self.library(),
|
|
||||||
Some(work),
|
|
||||||
false,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn play(&self) {
|
|
||||||
let program = Program::from_query(self.imp().search_entry.query());
|
|
||||||
self.player().set_program(program);
|
|
||||||
|
|
||||||
self.player().play();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn select(&self, search_entry: &SearchEntry) {
|
|
||||||
let imp = self.imp();
|
|
||||||
|
|
||||||
if imp.programs_flow_box.is_visible() {
|
|
||||||
if let Some(program) = imp.programs.borrow().first().cloned() {
|
|
||||||
self.player().set_program(program);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let (composer, performer, ensemble, instrument, work, recording, album) = {
|
|
||||||
(
|
|
||||||
imp.composers.borrow().first().cloned(),
|
|
||||||
imp.performers.borrow().first().cloned(),
|
|
||||||
imp.ensembles.borrow().first().cloned(),
|
|
||||||
imp.instruments.borrow().first().cloned(),
|
|
||||||
imp.works.borrow().first().cloned(),
|
|
||||||
imp.recordings.borrow().first().cloned(),
|
|
||||||
imp.albums.borrow().first().cloned(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(person) = composer {
|
|
||||||
search_entry.add_tag(Tag::Composer(person));
|
|
||||||
} else if let Some(person) = performer {
|
|
||||||
search_entry.add_tag(Tag::Performer(person));
|
|
||||||
} else if let Some(ensemble) = ensemble {
|
|
||||||
search_entry.add_tag(Tag::Ensemble(ensemble));
|
|
||||||
} else if let Some(instrument) = instrument {
|
|
||||||
search_entry.add_tag(Tag::Instrument(instrument));
|
|
||||||
} else if let Some(work) = work {
|
|
||||||
search_entry.add_tag(Tag::Work(work));
|
|
||||||
} else if let Some(recording) = recording {
|
|
||||||
self.player().play_recording(&recording);
|
|
||||||
} else if let Some(album) = album {
|
|
||||||
self.show_album(&album);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn program_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) {
|
|
||||||
self.player()
|
|
||||||
.set_program(tile.downcast_ref::<ProgramTile>().unwrap().program());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn tile_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) {
|
|
||||||
self.imp()
|
|
||||||
.search_entry
|
|
||||||
.add_tag(tile.downcast_ref::<TagTile>().unwrap().tag().clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn recording_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) {
|
|
||||||
self.player()
|
|
||||||
.play_recording(tile.downcast_ref::<RecordingTile>().unwrap().recording());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn album_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) {
|
|
||||||
self.show_album(tile.downcast_ref::<AlbumTile>().unwrap().album());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_album(&self, _album: &Album) {
|
|
||||||
todo!("Show album");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query(&self, query: &LibraryQuery) {
|
|
||||||
let imp = self.imp();
|
|
||||||
let results = self.library().query(query).unwrap();
|
|
||||||
|
|
||||||
for flowbox in [
|
|
||||||
&imp.composers_flow_box,
|
|
||||||
&imp.performers_flow_box,
|
|
||||||
&imp.ensembles_flow_box,
|
|
||||||
&imp.instruments_flow_box,
|
|
||||||
&imp.works_flow_box,
|
|
||||||
&imp.recordings_flow_box,
|
|
||||||
&imp.albums_flow_box,
|
|
||||||
] {
|
|
||||||
while let Some(widget) = flowbox.first_child() {
|
|
||||||
flowbox.remove(&widget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imp.programs_flow_box.set_visible(query.is_empty());
|
|
||||||
|
|
||||||
if let Some(tag) = imp.search_entry.tags().first() {
|
|
||||||
match tag {
|
|
||||||
Tag::Composer(person) | Tag::Performer(person) => {
|
|
||||||
imp.title_label.set_text(&person.name.get());
|
|
||||||
imp.subtitle_label.set_visible(false);
|
|
||||||
}
|
|
||||||
Tag::Ensemble(ensemble) => {
|
|
||||||
imp.title_label.set_text(&ensemble.name.get());
|
|
||||||
imp.subtitle_label.set_visible(false);
|
|
||||||
}
|
|
||||||
Tag::Instrument(instrument) => {
|
|
||||||
imp.title_label.set_text(&instrument.name.get());
|
|
||||||
imp.subtitle_label.set_visible(false);
|
|
||||||
}
|
|
||||||
Tag::Work(work) => {
|
|
||||||
imp.title_label.set_text(&work.name.get());
|
|
||||||
if let Some(composers) = work.composers_string() {
|
|
||||||
imp.subtitle_label.set_text(&composers);
|
|
||||||
imp.subtitle_label.set_visible(true);
|
|
||||||
} else {
|
|
||||||
imp.subtitle_label.set_visible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imp.header_box.set_visible(true);
|
|
||||||
} else {
|
|
||||||
imp.header_box.set_visible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if results.is_empty() {
|
|
||||||
imp.stack.set_visible_child_name("empty");
|
|
||||||
} else {
|
|
||||||
imp.stack.set_visible_child_name("results");
|
|
||||||
|
|
||||||
imp.composers_flow_box
|
|
||||||
.set_visible(!results.composers.is_empty());
|
|
||||||
imp.performers_flow_box
|
|
||||||
.set_visible(!results.performers.is_empty());
|
|
||||||
imp.ensembles_flow_box
|
|
||||||
.set_visible(!results.ensembles.is_empty());
|
|
||||||
imp.instruments_flow_box
|
|
||||||
.set_visible(!results.instruments.is_empty());
|
|
||||||
imp.works_flow_box.set_visible(!results.works.is_empty());
|
|
||||||
imp.recordings_flow_box
|
|
||||||
.set_visible(!results.recordings.is_empty());
|
|
||||||
imp.albums_flow_box.set_visible(!results.albums.is_empty());
|
|
||||||
|
|
||||||
for composer in &results.composers {
|
|
||||||
imp.composers_flow_box
|
|
||||||
.append(&TagTile::new(Tag::Composer(composer.clone())));
|
|
||||||
}
|
|
||||||
|
|
||||||
for performer in &results.performers {
|
|
||||||
imp.performers_flow_box
|
|
||||||
.append(&TagTile::new(Tag::Performer(performer.clone())));
|
|
||||||
}
|
|
||||||
|
|
||||||
for ensemble in &results.ensembles {
|
|
||||||
imp.ensembles_flow_box
|
|
||||||
.append(&TagTile::new(Tag::Ensemble(ensemble.clone())));
|
|
||||||
}
|
|
||||||
|
|
||||||
for instrument in &results.instruments {
|
|
||||||
imp.instruments_flow_box
|
|
||||||
.append(&TagTile::new(Tag::Instrument(instrument.clone())));
|
|
||||||
}
|
|
||||||
|
|
||||||
for work in &results.works {
|
|
||||||
imp.works_flow_box
|
|
||||||
.append(&TagTile::new(Tag::Work(work.clone())));
|
|
||||||
}
|
|
||||||
|
|
||||||
for recording in &results.recordings {
|
|
||||||
imp.recordings_flow_box.append(&RecordingTile::new(
|
|
||||||
&self.navigation(),
|
|
||||||
&self.library(),
|
|
||||||
recording,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for album in &results.albums {
|
|
||||||
imp.albums_flow_box.append(&AlbumTile::new(album));
|
|
||||||
}
|
|
||||||
|
|
||||||
imp.composers.replace(results.composers);
|
|
||||||
imp.performers.replace(results.performers);
|
|
||||||
imp.ensembles.replace(results.ensembles);
|
|
||||||
imp.instruments.replace(results.instruments);
|
|
||||||
imp.works.replace(results.works);
|
|
||||||
imp.recordings.replace(results.recordings);
|
|
||||||
imp.albums.replace(results.albums);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -72,8 +72,8 @@ impl Library {
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query(&self, query: &LibraryQuery) -> Result<LibraryResults> {
|
pub fn search(&self, query: &LibraryQuery, search: &str) -> Result<LibraryResults> {
|
||||||
let search = format!("%{}%", query.search);
|
let search = format!("%{}%", search);
|
||||||
let mut binding = self.imp().connection.borrow_mut();
|
let mut binding = self.imp().connection.borrow_mut();
|
||||||
let connection = &mut *binding.as_mut().unwrap();
|
let connection = &mut *binding.as_mut().unwrap();
|
||||||
|
|
||||||
|
|
@ -1541,14 +1541,13 @@ impl Library {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Clone, Default, Debug)]
|
||||||
pub struct LibraryQuery {
|
pub struct LibraryQuery {
|
||||||
pub composer: Option<Person>,
|
pub composer: Option<Person>,
|
||||||
pub performer: Option<Person>,
|
pub performer: Option<Person>,
|
||||||
pub ensemble: Option<Ensemble>,
|
pub ensemble: Option<Ensemble>,
|
||||||
pub instrument: Option<Instrument>,
|
pub instrument: Option<Instrument>,
|
||||||
pub work: Option<Work>,
|
pub work: Option<Work>,
|
||||||
pub search: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LibraryQuery {
|
impl LibraryQuery {
|
||||||
|
|
@ -1558,7 +1557,6 @@ impl LibraryQuery {
|
||||||
&& self.ensemble.is_none()
|
&& self.ensemble.is_none()
|
||||||
&& self.instrument.is_none()
|
&& self.instrument.is_none()
|
||||||
&& self.work.is_none()
|
&& self.work.is_none()
|
||||||
&& self.search.is_empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
|
mod album_page;
|
||||||
mod album_tile;
|
mod album_tile;
|
||||||
mod application;
|
mod application;
|
||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
mod editor;
|
mod editor;
|
||||||
mod home_page;
|
mod search_page;
|
||||||
mod library;
|
mod library;
|
||||||
mod library_manager;
|
mod library_manager;
|
||||||
mod player;
|
mod player;
|
||||||
|
|
@ -14,7 +15,6 @@ mod playlist_tile;
|
||||||
mod program;
|
mod program;
|
||||||
mod program_tile;
|
mod program_tile;
|
||||||
mod recording_tile;
|
mod recording_tile;
|
||||||
mod search_entry;
|
|
||||||
mod search_tag;
|
mod search_tag;
|
||||||
mod selector;
|
mod selector;
|
||||||
mod tag_tile;
|
mod tag_tile;
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ impl Player {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play_recording(&self, recording: &Recording) {
|
pub fn recording_to_playlist(&self, recording: &Recording) -> Vec<PlaylistItem> {
|
||||||
let tracks = &self
|
let tracks = &self
|
||||||
.library()
|
.library()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -211,8 +211,8 @@ impl Player {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if tracks.is_empty() {
|
if tracks.is_empty() {
|
||||||
log::warn!("Ignoring recording without tracks being added to the playlist.");
|
log::warn!("Recording without tracks: {}.", &recording.recording_id);
|
||||||
return;
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let performances = recording.performers_string();
|
let performances = recording.performers_string();
|
||||||
|
|
@ -272,14 +272,14 @@ impl Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.append(items);
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append(&self, tracks: Vec<PlaylistItem>) {
|
pub fn append(&self, items: Vec<PlaylistItem>) {
|
||||||
let playlist = self.playlist();
|
let playlist = self.playlist();
|
||||||
|
|
||||||
for track in tracks {
|
for item in items {
|
||||||
playlist.append(&track);
|
playlist.append(&item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.active() && playlist.n_items() > 0 {
|
if !self.active() && playlist.n_items() > 0 {
|
||||||
|
|
@ -289,6 +289,21 @@ impl Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn append_and_play(&self, items: Vec<PlaylistItem>) {
|
||||||
|
let playlist = self.playlist();
|
||||||
|
let first_index = playlist.n_items();
|
||||||
|
|
||||||
|
for item in items {
|
||||||
|
playlist.append(&item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if playlist.n_items() > first_index {
|
||||||
|
self.set_active(true);
|
||||||
|
self.set_current_index(first_index);
|
||||||
|
self.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn play_pause(&self) {
|
pub fn play_pause(&self) {
|
||||||
if self.playing() {
|
if self.playing() {
|
||||||
self.pause();
|
self.pause();
|
||||||
|
|
@ -423,7 +438,8 @@ impl Player {
|
||||||
if let Some(library) = self.library() {
|
if let Some(library) = self.library() {
|
||||||
// TODO: if program.play_full_recordings() {
|
// TODO: if program.play_full_recordings() {
|
||||||
let recording = library.generate_recording(program).unwrap();
|
let recording = library.generate_recording(program).unwrap();
|
||||||
self.play_recording(&recording);
|
let playlist = self.recording_to_playlist(&recording);
|
||||||
|
self.append(playlist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
use std::{cell::RefCell, time::Duration};
|
|
||||||
|
|
||||||
use adw::{prelude::*, subclass::prelude::*};
|
|
||||||
use gtk::{
|
|
||||||
gdk, gio,
|
|
||||||
glib::{self, clone, subclass::Signal, Propagation},
|
|
||||||
};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
library::LibraryQuery,
|
|
||||||
search_tag::{SearchTag, Tag},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod imp {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
|
||||||
#[template(file = "data/ui/search_entry.blp")]
|
|
||||||
pub struct SearchEntry {
|
|
||||||
#[template_child]
|
|
||||||
pub tags_box: TemplateChild<gtk::Box>,
|
|
||||||
#[template_child]
|
|
||||||
pub text: TemplateChild<gtk::Text>,
|
|
||||||
#[template_child]
|
|
||||||
pub clear_icon: TemplateChild<gtk::Image>,
|
|
||||||
|
|
||||||
pub tags: RefCell<Vec<SearchTag>>,
|
|
||||||
pub query_changed: RefCell<Option<gio::Cancellable>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[glib::object_subclass]
|
|
||||||
impl ObjectSubclass for SearchEntry {
|
|
||||||
const NAME: &'static str = "MusicusSearchEntry";
|
|
||||||
type Type = super::SearchEntry;
|
|
||||||
type ParentType = gtk::Box;
|
|
||||||
|
|
||||||
fn class_init(klass: &mut Self::Class) {
|
|
||||||
klass.bind_template();
|
|
||||||
klass.bind_template_instance_callbacks();
|
|
||||||
klass.set_css_name("entry");
|
|
||||||
|
|
||||||
klass.add_shortcut(
|
|
||||||
>k::Shortcut::builder()
|
|
||||||
.trigger(>k::KeyvalTrigger::new(
|
|
||||||
gdk::Key::Escape,
|
|
||||||
gdk::ModifierType::empty(),
|
|
||||||
))
|
|
||||||
.action(>k::CallbackAction::new(|widget, _| match widget
|
|
||||||
.downcast_ref::<super::SearchEntry>()
|
|
||||||
{
|
|
||||||
Some(obj) => {
|
|
||||||
obj.reset();
|
|
||||||
Propagation::Stop
|
|
||||||
}
|
|
||||||
None => Propagation::Proceed,
|
|
||||||
}))
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
|
||||||
obj.init_template();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectImpl for SearchEntry {
|
|
||||||
fn constructed(&self) {
|
|
||||||
let controller = gtk::GestureClick::new();
|
|
||||||
|
|
||||||
controller.connect_pressed(|gesture, _, _, _| {
|
|
||||||
gesture.set_state(gtk::EventSequenceState::Claimed);
|
|
||||||
});
|
|
||||||
|
|
||||||
let obj = self.obj().to_owned();
|
|
||||||
controller.connect_released(move |_, _, _, _| {
|
|
||||||
obj.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
self.clear_icon.add_controller(controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signals() -> &'static [Signal] {
|
|
||||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
|
||||||
vec![
|
|
||||||
Signal::builder("activate").build(),
|
|
||||||
Signal::builder("query-changed").build(),
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
SIGNALS.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WidgetImpl for SearchEntry {
|
|
||||||
fn grab_focus(&self) -> bool {
|
|
||||||
self.text.grab_focus_without_selecting()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BoxImpl for SearchEntry {}
|
|
||||||
}
|
|
||||||
|
|
||||||
glib::wrapper! {
|
|
||||||
pub struct SearchEntry(ObjectSubclass<imp::SearchEntry>)
|
|
||||||
@extends gtk::Widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gtk::template_callbacks]
|
|
||||||
impl SearchEntry {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
glib::Object::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connect_query_changed<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
|
||||||
self.connect_local("query-changed", true, move |values| {
|
|
||||||
let obj = values[0].get::<Self>().unwrap();
|
|
||||||
f(&obj);
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_key_capture_widget(&self, widget: &impl IsA<gtk::Widget>) {
|
|
||||||
let controller = gtk::EventControllerKey::new();
|
|
||||||
|
|
||||||
controller.connect_key_pressed(clone!(
|
|
||||||
#[weak(rename_to = this)]
|
|
||||||
self,
|
|
||||||
#[upgrade_or]
|
|
||||||
glib::Propagation::Proceed,
|
|
||||||
move |controller, _, _, _| {
|
|
||||||
match controller.forward(&this.imp().text.get()) {
|
|
||||||
true => {
|
|
||||||
this.grab_focus();
|
|
||||||
glib::Propagation::Stop
|
|
||||||
}
|
|
||||||
false => glib::Propagation::Proceed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
controller.connect_key_released(clone!(
|
|
||||||
#[weak(rename_to = this)]
|
|
||||||
self,
|
|
||||||
move |controller, _, _, _| {
|
|
||||||
controller.forward(&this.imp().text.get());
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
widget.add_controller(controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&self) {
|
|
||||||
{
|
|
||||||
let mut tags = self.imp().tags.borrow_mut();
|
|
||||||
while let Some(tag) = tags.pop() {
|
|
||||||
self.imp().tags_box.remove(&tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imp().text.set_text("");
|
|
||||||
self.emit_by_name::<()>("query-changed", &[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_tag(&self, tag: Tag) {
|
|
||||||
let imp = self.imp();
|
|
||||||
|
|
||||||
imp.clear_icon.set_visible(true);
|
|
||||||
imp.text.set_text("");
|
|
||||||
|
|
||||||
let tag = SearchTag::new(tag);
|
|
||||||
|
|
||||||
tag.connect_remove(clone!(
|
|
||||||
#[weak(rename_to = this)]
|
|
||||||
self,
|
|
||||||
move |tag| {
|
|
||||||
let imp = this.imp();
|
|
||||||
|
|
||||||
imp.tags_box.remove(tag);
|
|
||||||
|
|
||||||
{
|
|
||||||
imp.tags.borrow_mut().retain(|t| t.tag() != tag.tag());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit_by_name::<()>("query-changed", &[]);
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
imp.tags_box.append(&tag);
|
|
||||||
imp.tags.borrow_mut().push(tag);
|
|
||||||
self.emit_by_name::<()>("query-changed", &[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tags(&self) -> Vec<Tag> {
|
|
||||||
self.imp()
|
|
||||||
.tags
|
|
||||||
.borrow()
|
|
||||||
.iter()
|
|
||||||
.map(|t| t.tag().to_owned())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query(&self) -> LibraryQuery {
|
|
||||||
let mut query = LibraryQuery {
|
|
||||||
search: self.imp().text.text().to_string(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
for tag in &*self.imp().tags.borrow() {
|
|
||||||
match tag.tag().clone() {
|
|
||||||
Tag::Composer(person) => query.composer = Some(person),
|
|
||||||
Tag::Performer(person) => query.performer = Some(person),
|
|
||||||
Tag::Ensemble(ensemble) => query.ensemble = Some(ensemble),
|
|
||||||
Tag::Instrument(instrument) => query.instrument = Some(instrument),
|
|
||||||
Tag::Work(work) => query.work = Some(work),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn activate(&self, _: >k::Text) {
|
|
||||||
self.emit_by_name::<()>("activate", &[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn backspace(&self, text: >k::Text) {
|
|
||||||
if text.position() == 0 {
|
|
||||||
let changed = if let Some(tag) = self.imp().tags.borrow_mut().pop() {
|
|
||||||
self.imp().tags_box.remove(&tag);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
self.emit_by_name::<()>("query-changed", &[]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
async fn text_changed(&self, text: >k::Text) {
|
|
||||||
let imp = self.imp();
|
|
||||||
|
|
||||||
if imp.tags.borrow().is_empty() {
|
|
||||||
imp.clear_icon.set_visible(!text.text().is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cancellable) = imp.query_changed.borrow_mut().take() {
|
|
||||||
cancellable.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
let cancellable = gio::Cancellable::new();
|
|
||||||
imp.query_changed.replace(Some(cancellable.clone()));
|
|
||||||
|
|
||||||
let _ = gio::CancellableFuture::new(
|
|
||||||
async {
|
|
||||||
glib::timeout_future(Duration::from_millis(150)).await;
|
|
||||||
self.emit_by_name::<()>("query-changed", &[]);
|
|
||||||
},
|
|
||||||
cancellable,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
478
src/search_page.rs
Normal file
478
src/search_page.rs
Normal file
|
|
@ -0,0 +1,478 @@
|
||||||
|
use std::cell::{OnceCell, RefCell};
|
||||||
|
|
||||||
|
use adw::subclass::{navigation_page::NavigationPageImpl, prelude::*};
|
||||||
|
use formatx::formatx;
|
||||||
|
use gettextrs::gettext;
|
||||||
|
use gtk::{
|
||||||
|
gio,
|
||||||
|
glib::{self, Properties},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
album_page::AlbumPage,
|
||||||
|
album_tile::AlbumTile,
|
||||||
|
config,
|
||||||
|
db::models::*,
|
||||||
|
editor::{
|
||||||
|
ensemble::EnsembleEditor, instrument::InstrumentEditor, person::PersonEditor,
|
||||||
|
work::WorkEditor,
|
||||||
|
},
|
||||||
|
library::{Library, LibraryQuery},
|
||||||
|
player::Player,
|
||||||
|
program::Program,
|
||||||
|
program_tile::ProgramTile,
|
||||||
|
recording_tile::RecordingTile,
|
||||||
|
search_tag::Tag,
|
||||||
|
tag_tile::TagTile,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
|
||||||
|
#[properties(wrapper_type = super::SearchPage)]
|
||||||
|
#[template(file = "data/ui/search_page.blp")]
|
||||||
|
pub struct SearchPage {
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub navigation: OnceCell<adw::NavigationView>,
|
||||||
|
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub library: OnceCell<Library>,
|
||||||
|
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub player: OnceCell<Player>,
|
||||||
|
|
||||||
|
pub query: OnceCell<LibraryQuery>,
|
||||||
|
pub highlight: RefCell<Option<Tag>>,
|
||||||
|
|
||||||
|
pub programs: RefCell<Vec<Program>>,
|
||||||
|
pub composers: RefCell<Vec<Person>>,
|
||||||
|
pub performers: RefCell<Vec<Person>>,
|
||||||
|
pub ensembles: RefCell<Vec<Ensemble>>,
|
||||||
|
pub instruments: RefCell<Vec<Instrument>>,
|
||||||
|
pub works: RefCell<Vec<Work>>,
|
||||||
|
pub recordings: RefCell<Vec<Recording>>,
|
||||||
|
pub albums: RefCell<Vec<Album>>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
pub scrolled_window: TemplateChild<gtk::ScrolledWindow>,
|
||||||
|
#[template_child]
|
||||||
|
pub header_bar: TemplateChild<adw::HeaderBar>,
|
||||||
|
#[template_child]
|
||||||
|
pub search_entry: TemplateChild<gtk::SearchEntry>,
|
||||||
|
#[template_child]
|
||||||
|
pub stack: TemplateChild<gtk::Stack>,
|
||||||
|
#[template_child]
|
||||||
|
pub header_box: TemplateChild<gtk::Box>,
|
||||||
|
#[template_child]
|
||||||
|
pub title_label: TemplateChild<gtk::Label>,
|
||||||
|
#[template_child]
|
||||||
|
pub subtitle_label: TemplateChild<gtk::Label>,
|
||||||
|
#[template_child]
|
||||||
|
pub programs_flow_box: TemplateChild<gtk::FlowBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub composers_flow_box: TemplateChild<gtk::FlowBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub performers_flow_box: TemplateChild<gtk::FlowBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub ensembles_flow_box: TemplateChild<gtk::FlowBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub instruments_flow_box: TemplateChild<gtk::FlowBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub works_flow_box: TemplateChild<gtk::FlowBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub recordings_flow_box: TemplateChild<gtk::FlowBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub albums_flow_box: TemplateChild<gtk::FlowBox>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for SearchPage {
|
||||||
|
const NAME: &'static str = "MusicusSearchPage";
|
||||||
|
type Type = super::SearchPage;
|
||||||
|
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 SearchPage {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
self.search_entry.set_key_capture_widget(Some(&*self.obj()));
|
||||||
|
|
||||||
|
let obj = self.obj().to_owned();
|
||||||
|
self.search_entry.connect_search_changed(move |entry| {
|
||||||
|
obj.imp().scrolled_window.vadjustment().set_value(0.0);
|
||||||
|
obj.search(&entry.text());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for SearchPage {
|
||||||
|
fn map(&self) {
|
||||||
|
self.parent_map();
|
||||||
|
self.search_entry.grab_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NavigationPageImpl for SearchPage {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct SearchPage(ObjectSubclass<imp::SearchPage>)
|
||||||
|
@extends gtk::Widget, adw::NavigationPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl SearchPage {
|
||||||
|
pub fn new(
|
||||||
|
navigation: &adw::NavigationView,
|
||||||
|
library: &Library,
|
||||||
|
player: &Player,
|
||||||
|
query: LibraryQuery,
|
||||||
|
) -> Self {
|
||||||
|
let obj: Self = glib::Object::builder()
|
||||||
|
.property("navigation", navigation)
|
||||||
|
.property("library", library)
|
||||||
|
.property("player", player)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if query.is_empty() {
|
||||||
|
let settings = gio::Settings::new(&config::APP_ID);
|
||||||
|
|
||||||
|
let programs = vec![
|
||||||
|
Program::deserialize(&settings.string("program1")).unwrap(),
|
||||||
|
Program::deserialize(&settings.string("program2")).unwrap(),
|
||||||
|
Program::deserialize(&settings.string("program3")).unwrap(),
|
||||||
|
];
|
||||||
|
|
||||||
|
for program in &programs {
|
||||||
|
obj.imp()
|
||||||
|
.programs_flow_box
|
||||||
|
.append(&ProgramTile::new(program.to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.imp().programs.replace(programs);
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.imp().query.set(query).unwrap();
|
||||||
|
obj.search("");
|
||||||
|
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn edit_button_clicked(&self) {
|
||||||
|
if let Some(highlight) = &*self.imp().highlight.borrow() {
|
||||||
|
match highlight {
|
||||||
|
Tag::Composer(person) | Tag::Performer(person) => {
|
||||||
|
self.navigation().push(&PersonEditor::new(
|
||||||
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
Some(person),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Tag::Ensemble(ensemble) => {
|
||||||
|
self.navigation().push(&EnsembleEditor::new(
|
||||||
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
Some(ensemble),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Tag::Instrument(instrument) => self.navigation().push(&InstrumentEditor::new(
|
||||||
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
Some(instrument),
|
||||||
|
)),
|
||||||
|
Tag::Work(work) => self.navigation().push(&WorkEditor::new(
|
||||||
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
Some(work),
|
||||||
|
false,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn play_button_clicked(&self) {
|
||||||
|
let program = Program::from_query(self.imp().query.get().unwrap().clone());
|
||||||
|
self.player().set_program(program);
|
||||||
|
self.player().play();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn select(&self) {
|
||||||
|
let imp = self.imp();
|
||||||
|
|
||||||
|
if imp.programs_flow_box.is_visible() {
|
||||||
|
if let Some(program) = imp.programs.borrow().first().cloned() {
|
||||||
|
self.player().set_program(program);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut new_query = self.imp().query.get().unwrap().clone();
|
||||||
|
|
||||||
|
let query_changed = if let Some(person) = imp.composers.borrow().first().cloned() {
|
||||||
|
new_query.composer = Some(person);
|
||||||
|
true
|
||||||
|
} else if let Some(person) = imp.performers.borrow().first().cloned() {
|
||||||
|
new_query.performer = Some(person);
|
||||||
|
true
|
||||||
|
} else if let Some(ensemble) = imp.ensembles.borrow().first().cloned() {
|
||||||
|
new_query.ensemble = Some(ensemble);
|
||||||
|
true
|
||||||
|
} else if let Some(instrument) = imp.instruments.borrow().first().cloned() {
|
||||||
|
new_query.instrument = Some(instrument);
|
||||||
|
true
|
||||||
|
} else if let Some(work) = imp.works.borrow().first().cloned() {
|
||||||
|
new_query.work = Some(work);
|
||||||
|
true
|
||||||
|
} else if let Some(recording) = imp.recordings.borrow().first().cloned() {
|
||||||
|
let playlist = self.player().recording_to_playlist(&recording);
|
||||||
|
self.player().append_and_play(playlist);
|
||||||
|
false
|
||||||
|
} else if let Some(album) = imp.albums.borrow().first().cloned() {
|
||||||
|
self.show_album(&album);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if query_changed {
|
||||||
|
self.navigation().push(&SearchPage::new(
|
||||||
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
&self.player(),
|
||||||
|
new_query,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn program_selected(&self, tile: >k::FlowBoxChild) {
|
||||||
|
self.player()
|
||||||
|
.set_program(tile.downcast_ref::<ProgramTile>().unwrap().program());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn tile_selected(&self, tile: >k::FlowBoxChild) {
|
||||||
|
let mut new_query = self.imp().query.get().unwrap().clone();
|
||||||
|
match tile.downcast_ref::<TagTile>().unwrap().tag().clone() {
|
||||||
|
Tag::Composer(person) => new_query.composer = Some(person),
|
||||||
|
Tag::Performer(person) => new_query.performer = Some(person),
|
||||||
|
Tag::Ensemble(ensemble) => new_query.ensemble = Some(ensemble),
|
||||||
|
Tag::Instrument(instrument) => new_query.instrument = Some(instrument),
|
||||||
|
Tag::Work(work) => new_query.work = Some(work),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.navigation().push(&SearchPage::new(
|
||||||
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
&self.player(),
|
||||||
|
new_query,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn recording_selected(&self, tile: >k::FlowBoxChild) {
|
||||||
|
let playlist = self
|
||||||
|
.player()
|
||||||
|
.recording_to_playlist(tile.downcast_ref::<RecordingTile>().unwrap().recording());
|
||||||
|
self.player().append_and_play(playlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn album_selected(&self, tile: >k::FlowBoxChild) {
|
||||||
|
self.show_album(tile.downcast_ref::<AlbumTile>().unwrap().album());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_album(&self, album: &Album) {
|
||||||
|
self.navigation().push(&AlbumPage::new(
|
||||||
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
&self.player(),
|
||||||
|
album.to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, search: &str) {
|
||||||
|
let query = self.imp().query.get().unwrap();
|
||||||
|
|
||||||
|
let imp = self.imp();
|
||||||
|
let results = self.library().search(query, search).unwrap();
|
||||||
|
|
||||||
|
for flowbox in [
|
||||||
|
&imp.composers_flow_box,
|
||||||
|
&imp.performers_flow_box,
|
||||||
|
&imp.ensembles_flow_box,
|
||||||
|
&imp.instruments_flow_box,
|
||||||
|
&imp.works_flow_box,
|
||||||
|
&imp.recordings_flow_box,
|
||||||
|
&imp.albums_flow_box,
|
||||||
|
] {
|
||||||
|
while let Some(widget) = flowbox.first_child() {
|
||||||
|
flowbox.remove(&widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only show programs initially.
|
||||||
|
imp.programs_flow_box
|
||||||
|
.set_visible(query.is_empty() && search.is_empty());
|
||||||
|
|
||||||
|
imp.header_bar.set_show_title(query.is_empty());
|
||||||
|
imp.header_box.set_visible(!query.is_empty());
|
||||||
|
|
||||||
|
let highlight = if let Some(work) = &query.work {
|
||||||
|
imp.title_label.set_text(&work.name.get());
|
||||||
|
if let Some(composers) = work.composers_string() {
|
||||||
|
imp.subtitle_label.set_text(&composers);
|
||||||
|
imp.subtitle_label.set_visible(true);
|
||||||
|
} else {
|
||||||
|
imp.subtitle_label.set_visible(false);
|
||||||
|
}
|
||||||
|
Some(Tag::Work(work.to_owned()))
|
||||||
|
} else if let Some(person) = &query.composer {
|
||||||
|
imp.title_label.set_text(&person.name.get());
|
||||||
|
imp.subtitle_label.set_visible(false);
|
||||||
|
Some(Tag::Composer(person.to_owned()))
|
||||||
|
} else if let Some(person) = &query.performer {
|
||||||
|
imp.title_label.set_text(&person.name.get());
|
||||||
|
imp.subtitle_label.set_visible(false);
|
||||||
|
Some(Tag::Performer(person.to_owned()))
|
||||||
|
} else if let Some(ensemble) = &query.ensemble {
|
||||||
|
imp.title_label.set_text(&ensemble.name.get());
|
||||||
|
imp.subtitle_label.set_visible(false);
|
||||||
|
Some(Tag::Ensemble(ensemble.to_owned()))
|
||||||
|
} else if let Some(instrument) = &query.instrument {
|
||||||
|
imp.title_label
|
||||||
|
.set_text(&formatx!(gettext("Music for {}"), &instrument.name.get()).unwrap());
|
||||||
|
imp.subtitle_label.set_visible(false);
|
||||||
|
Some(Tag::Instrument(instrument.to_owned()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(highlight) = &highlight {
|
||||||
|
if !matches!(highlight, Tag::Work(_)) {
|
||||||
|
let mut details = Vec::new();
|
||||||
|
|
||||||
|
match highlight {
|
||||||
|
Tag::Composer(_) => {
|
||||||
|
if let Some(instrument) = &query.instrument {
|
||||||
|
details.push(formatx!(gettext("Works with {}"), instrument).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (Some(person), Some(ensemble)) = (&query.performer, &query.ensemble)
|
||||||
|
{
|
||||||
|
details.push(
|
||||||
|
formatx!(gettext("Performed by {} and {}"), person, ensemble)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
} else if let Some(person) = &query.performer {
|
||||||
|
details.push(formatx!(gettext("Performed by {}"), person).unwrap());
|
||||||
|
} else if let Some(ensemble) = &query.ensemble {
|
||||||
|
details.push(formatx!(gettext("Performed by {}"), ensemble).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tag::Performer(_) => {
|
||||||
|
if let Some(instrument) = &query.instrument {
|
||||||
|
details.push(formatx!(gettext("Works with {}"), instrument).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ensemble) = &query.ensemble {
|
||||||
|
details.push(formatx!(gettext("Performed with {}"), ensemble).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tag::Ensemble(_) => {
|
||||||
|
if let Some(instrument) = &query.instrument {
|
||||||
|
details.push(formatx!(gettext("Works with {}"), instrument).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Tag::Instrument(_) => (),
|
||||||
|
// Already covered.
|
||||||
|
Tag::Work(_) => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
imp.subtitle_label.set_visible(!details.is_empty());
|
||||||
|
imp.subtitle_label.set_text(&details.join(", "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imp.highlight.replace(highlight);
|
||||||
|
|
||||||
|
if results.is_empty() {
|
||||||
|
imp.stack.set_visible_child_name("empty");
|
||||||
|
} else {
|
||||||
|
imp.stack.set_visible_child_name("results");
|
||||||
|
|
||||||
|
imp.composers_flow_box
|
||||||
|
.set_visible(!results.composers.is_empty());
|
||||||
|
imp.performers_flow_box
|
||||||
|
.set_visible(!results.performers.is_empty());
|
||||||
|
imp.ensembles_flow_box
|
||||||
|
.set_visible(!results.ensembles.is_empty());
|
||||||
|
imp.instruments_flow_box
|
||||||
|
.set_visible(!results.instruments.is_empty());
|
||||||
|
imp.works_flow_box.set_visible(!results.works.is_empty());
|
||||||
|
imp.recordings_flow_box
|
||||||
|
.set_visible(!results.recordings.is_empty());
|
||||||
|
imp.albums_flow_box.set_visible(!results.albums.is_empty());
|
||||||
|
|
||||||
|
for composer in &results.composers {
|
||||||
|
imp.composers_flow_box
|
||||||
|
.append(&TagTile::new(Tag::Composer(composer.clone())));
|
||||||
|
}
|
||||||
|
|
||||||
|
for performer in &results.performers {
|
||||||
|
imp.performers_flow_box
|
||||||
|
.append(&TagTile::new(Tag::Performer(performer.clone())));
|
||||||
|
}
|
||||||
|
|
||||||
|
for ensemble in &results.ensembles {
|
||||||
|
imp.ensembles_flow_box
|
||||||
|
.append(&TagTile::new(Tag::Ensemble(ensemble.clone())));
|
||||||
|
}
|
||||||
|
|
||||||
|
for instrument in &results.instruments {
|
||||||
|
imp.instruments_flow_box
|
||||||
|
.append(&TagTile::new(Tag::Instrument(instrument.clone())));
|
||||||
|
}
|
||||||
|
|
||||||
|
for work in &results.works {
|
||||||
|
imp.works_flow_box
|
||||||
|
.append(&TagTile::new(Tag::Work(work.clone())));
|
||||||
|
}
|
||||||
|
|
||||||
|
for recording in &results.recordings {
|
||||||
|
imp.recordings_flow_box.append(&RecordingTile::new(
|
||||||
|
&self.navigation(),
|
||||||
|
&self.library(),
|
||||||
|
recording,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for album in &results.albums {
|
||||||
|
imp.albums_flow_box.append(&AlbumTile::new(album));
|
||||||
|
}
|
||||||
|
|
||||||
|
imp.composers.replace(results.composers);
|
||||||
|
imp.performers.replace(results.performers);
|
||||||
|
imp.ensembles.replace(results.ensembles);
|
||||||
|
imp.instruments.replace(results.instruments);
|
||||||
|
imp.works.replace(results.works);
|
||||||
|
imp.recordings.replace(results.recordings);
|
||||||
|
imp.albums.replace(results.albums);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,9 +4,15 @@ use adw::subclass::prelude::*;
|
||||||
use gtk::{gio, glib, glib::clone, prelude::*};
|
use gtk::{gio, glib, glib::clone, prelude::*};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config, editor::tracks::TracksEditor, home_page::HomePage, library::Library,
|
config,
|
||||||
library_manager::LibraryManager, player::Player, player_bar::PlayerBar,
|
editor::tracks::TracksEditor,
|
||||||
playlist_page::PlaylistPage, welcome_page::WelcomePage,
|
library::{Library, LibraryQuery},
|
||||||
|
library_manager::LibraryManager,
|
||||||
|
player::Player,
|
||||||
|
player_bar::PlayerBar,
|
||||||
|
playlist_page::PlaylistPage,
|
||||||
|
search_page::SearchPage,
|
||||||
|
welcome_page::WelcomePage,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
|
|
@ -189,7 +195,13 @@ impl Window {
|
||||||
self.imp().player.set_library(&library);
|
self.imp().player.set_library(&library);
|
||||||
|
|
||||||
let navigation = self.imp().navigation_view.get();
|
let navigation = self.imp().navigation_view.get();
|
||||||
navigation.replace(&[HomePage::new(&navigation, &library, &self.imp().player).into()]);
|
navigation.replace(&[SearchPage::new(
|
||||||
|
&navigation,
|
||||||
|
&library,
|
||||||
|
&self.imp().player,
|
||||||
|
LibraryQuery::default(),
|
||||||
|
)
|
||||||
|
.into()]);
|
||||||
|
|
||||||
self.imp().library.replace(Some(library));
|
self.imp().library.replace(Some(library));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue