From 34ca201121a7d7a2f0cd88bc67722a54ab42b835 Mon Sep 17 00:00:00 2001 From: Joshua Strobl Date: Tue, 6 Jul 2021 13:06:20 +0300 Subject: [PATCH] Implemented no albums view and improved indexing of file content directly within an artist. Every KotoArtist now has a dedicated KotoPlaylist that is dynamically generated during KotoArtist initialization and contains references to all KotoTracks. Fixed weird parsing error with some track names that had single quote (ew) and made changes so various parts of the UX would not freak out when we didn't have an album associated with a track. --- src/components/track-table.c | 486 ++++++++++++++++++++++++++++ src/components/track-table.h | 114 +++++++ src/db/loaders.c | 2 + src/indexer/artist-playlist-funcs.h | 25 ++ src/indexer/artist.c | 66 +++- src/indexer/file-indexer.c | 5 +- src/indexer/structs.h | 7 + src/indexer/track.c | 37 ++- src/koto-playerbar.c | 10 +- src/main.c | 8 +- src/meson.build | 1 + src/pages/music/artist-view.c | 147 +++++++-- src/pages/music/artist-view.h | 21 +- src/pages/playlist/list.c | 374 +-------------------- src/pages/playlist/list.h | 60 ---- theme/_main.scss | 2 + theme/components/_track-table.scss | 49 +++ theme/pages/_artist-view.scss | 14 + theme/pages/_playlist-page.scss | 50 --- 19 files changed, 948 insertions(+), 530 deletions(-) create mode 100644 src/components/track-table.c create mode 100644 src/components/track-table.h create mode 100644 src/indexer/artist-playlist-funcs.h create mode 100644 theme/components/_track-table.scss create mode 100644 theme/pages/_artist-view.scss diff --git a/src/components/track-table.c b/src/components/track-table.c new file mode 100644 index 0000000..e1ec15b --- /dev/null +++ b/src/components/track-table.c @@ -0,0 +1,486 @@ +/* track-table.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "../db/cartographer.h" +#include "../playlist/playlist.h" +#include "../koto-button.h" +#include "../koto-utils.h" +#include "../koto-window.h" +#include "koto-action-bar.h" +#include "track-table.h" + +extern KotoActionBar * action_bar; +extern KotoCartographer * koto_maps; +extern KotoWindow * main_window; + +struct _KotoTrackTable { + GObject parent_instance; + gchar * uuid; + KotoPlaylist * playlist; + + GtkWidget * main; + + GtkListItemFactory * item_factory; + GListModel * model; + GtkSelectionModel * selection_model; + + GtkWidget * track_list_content; + GtkWidget * track_list_header; + GtkWidget * track_list_view; + + KotoButton * track_album_button; + KotoButton * track_artist_button; + KotoButton * track_num_button; + KotoButton * track_title_button; + + GtkSizeGroup * track_pos_size_group; + GtkSizeGroup * track_name_size_group; + GtkSizeGroup * track_album_size_group; + GtkSizeGroup * track_artist_size_group; +}; + +struct _KotoTrackTableClass { + GObjectClass parent_class; +}; + +G_DEFINE_TYPE(KotoTrackTable, koto_track_table, G_TYPE_OBJECT); + +static void koto_track_table_class_init(KotoTrackTableClass * c) { + (void) c; +} + +static void koto_track_table_init(KotoTrackTable * self) { + self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + + self->track_name_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + self->track_pos_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + self->track_album_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + self->track_artist_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + self->item_factory = gtk_signal_list_item_factory_new(); // Create a new signal list item factory + g_signal_connect(self->item_factory, "setup", G_CALLBACK(koto_track_table_setup_track_item), self); + g_signal_connect(self->item_factory, "bind", G_CALLBACK(koto_track_table_bind_track_item), self); + + self->track_list_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_add_css_class((self->track_list_content), "track-list-content"); + gtk_widget_set_hexpand(self->track_list_content, TRUE); // Expand horizontally + gtk_widget_set_vexpand(self->track_list_content, TRUE); // Expand vertically + + self->track_list_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class(self->track_list_header, "track-list-header"); + koto_track_table_create_tracks_header(self); // Create our tracks header content + + self->track_list_view = gtk_list_view_new(NULL, self->item_factory); // Create our list view with no model yet + gtk_widget_add_css_class(self->track_list_view, "track-list-columned"); + gtk_widget_set_hexpand(self->track_list_view, TRUE); // Expand horizontally + gtk_widget_set_vexpand(self->track_list_view, TRUE); // Expand vertically + + gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_header); + gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_view); + + gtk_box_append(GTK_BOX(self->main), self->track_list_content); + + g_signal_connect(action_bar, "closed", G_CALLBACK(koto_track_table_handle_action_bar_closed), self); // Handle closed action bar +} + +void koto_track_table_bind_track_item( + GtkListItemFactory * factory, + GtkListItem * item, + KotoTrackTable * self +) { + (void) factory; + + GtkWidget * track_position_label = gtk_widget_get_first_child(gtk_list_item_get_child(item)); + GtkWidget * track_name_label = gtk_widget_get_next_sibling(track_position_label); + GtkWidget * track_album_label = gtk_widget_get_next_sibling(track_name_label); + GtkWidget * track_artist_label = gtk_widget_get_next_sibling(track_album_label); + + KotoTrack * track = gtk_list_item_get_item(item); // Get the track UUID from our model + + if (!KOTO_IS_TRACK(track)) { + return; + } + + gchar * track_name = NULL; + gchar * album_uuid = NULL; + gchar * artist_uuid = NULL; + + g_object_get( + track, + "parsed-name", + &track_name, + "album-uuid", + &album_uuid, + "artist-uuid", + &artist_uuid, + NULL + ); + + guint track_position = koto_playlist_get_position_of_track(self->playlist, track) + 1; + + gtk_label_set_label(GTK_LABEL(track_position_label), g_strdup_printf("%u", track_position)); // Set the track position + gtk_label_set_label(GTK_LABEL(track_name_label), track_name); // Set our track name + + if (koto_utils_is_string_valid(album_uuid)) { // Is associated with an album + KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); + + if (KOTO_IS_ALBUM(album)) { + gtk_label_set_label(GTK_LABEL(track_album_label), koto_album_get_name(album)); // Get the name of the album and set it to the label + } + } + + KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); + + if (KOTO_IS_ARTIST(artist)) { + gtk_label_set_label(GTK_LABEL(track_artist_label), koto_artist_get_name(artist)); // Get the name of the artist and set it to the label + } +} + +void koto_track_table_create_tracks_header(KotoTrackTable * self) { + self->track_num_button = koto_button_new_with_icon("#", "pan-down-symbolic", "pan-up-symbolic", KOTO_BUTTON_PIXBUF_SIZE_SMALL); + koto_button_add_click_handler(self->track_num_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_num_clicked), self); + koto_button_set_image_position(self->track_num_button, KOTO_BUTTON_IMAGE_POS_RIGHT); // Move the image to the right + gtk_size_group_add_widget(self->track_pos_size_group, GTK_WIDGET(self->track_num_button)); + + self->track_title_button = koto_button_new_plain("Title"); + koto_button_add_click_handler(self->track_title_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_name_clicked), self); + gtk_size_group_add_widget(self->track_name_size_group, GTK_WIDGET(self->track_title_button)); + + self->track_album_button = koto_button_new_plain("Album"); + + gtk_widget_set_margin_start(GTK_WIDGET(self->track_album_button), 50); + koto_button_add_click_handler(self->track_album_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_album_clicked), self); + gtk_size_group_add_widget(self->track_album_size_group, GTK_WIDGET(self->track_album_button)); + + self->track_artist_button = koto_button_new_plain("Artist"); + + gtk_widget_set_margin_start(GTK_WIDGET(self->track_artist_button), 50); + koto_button_add_click_handler(self->track_artist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_artist_clicked), self); + gtk_size_group_add_widget(self->track_artist_size_group, GTK_WIDGET(self->track_artist_button)); + + gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_num_button)); + gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_title_button)); + gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_album_button)); + gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_artist_button)); +} + +GtkWidget * koto_track_table_get_main(KotoTrackTable * self) { + return self->main; +} + +void koto_track_table_handle_action_bar_closed ( + KotoActionBar * bar, + gpointer data +) { + (void) bar; + KotoTrackTable * self = data; + + if (!KOTO_IS_TRACK_TABLE(self)) { // Self is not a track table + return; + } + + if (!KOTO_IS_PLAYLIST(self->playlist)) { // No playlist set + return; + } + + gtk_selection_model_unselect_all(self->selection_model); + gtk_widget_grab_focus(GTK_WIDGET(main_window)); // Focus on the window +} + +void koto_track_table_handle_track_album_clicked ( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer user_data +) { + (void) gesture; + (void) n_press; + (void) x; + (void) y; + KotoTrackTable * self = user_data; + + gtk_widget_add_css_class(GTK_WIDGET(self->track_album_button), "active"); + koto_button_hide_image(self->track_num_button); // Go back to hiding the image + koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM); +} + +void koto_track_table_handle_track_artist_clicked( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer user_data +) { + (void) gesture; + (void) n_press; + (void) x; + (void) y; + KotoTrackTable * self = user_data; + + gtk_widget_add_css_class(GTK_WIDGET(self->track_artist_button), "active"); + koto_button_hide_image(self->track_num_button); // Go back to hiding the image + koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST); +} + +void koto_track_table_handle_track_name_clicked( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer user_data +) { + (void) gesture; + (void) n_press; + (void) x; + (void) y; + KotoTrackTable * self = user_data; + + gtk_widget_add_css_class(GTK_WIDGET(self->track_title_button), "active"); + koto_button_hide_image(self->track_num_button); // Go back to hiding the image + koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME); +} + +void koto_track_table_handle_track_num_clicked( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer user_data +) { + (void) gesture; + (void) n_press; + (void) x; + (void) y; + KotoTrackTable * self = user_data; + + KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist); + + if (current_model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) { // Set to newest currently + koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST); // Sort reversed (oldest) + koto_button_show_image(self->track_num_button, TRUE); // Use inverted value (pan-up-symbolic) + } else { + koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // Sort newest + koto_button_show_image(self->track_num_button, FALSE); // Use pan down default + } +} + +void koto_track_table_handle_tracks_selected( + GtkSelectionModel * model, + guint position, + guint n_items, + gpointer user_data +) { + (void) position; + KotoTrackTable * self = user_data; + + if (!KOTO_IS_TRACK_TABLE(self)) { // Not a track table + return; + } + + if (n_items == 0) { // No items selected + koto_action_bar_toggle_reveal(action_bar, FALSE); // Hide the action bar + return; + } + + GtkBitset * selected_items_bitset = gtk_selection_model_get_selection(model); // Get the selected items as a GtkBitSet + GtkBitsetIter iter; + GList * selected_tracks = NULL; + GList * selected_tracks_pos = NULL; + + guint first_track_pos; + + if (!gtk_bitset_iter_init_first(&iter, selected_items_bitset, &first_track_pos)) { // Failed to get the first item + return; + } + + selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(first_track_pos)); + + gboolean have_more_items = TRUE; + + while (have_more_items) { // While we are able to get selected items + guint track_pos; + have_more_items = gtk_bitset_iter_next(&iter, &track_pos); + if (have_more_items) { // Got the next track + selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(track_pos)); + } + } + + GList * cur_pos_list; + + for (cur_pos_list = selected_tracks_pos; cur_pos_list != NULL; cur_pos_list = cur_pos_list->next) { // Iterate over every position that we accumulated + KotoTrack * selected_track = g_list_model_get_item(self->model, GPOINTER_TO_UINT(cur_pos_list->data)); // Get the KotoTrack in the GListModel for this current position + selected_tracks = g_list_append(selected_tracks, selected_track); // Add to selected tracks + } + + koto_action_bar_set_tracks_in_playlist_selection(action_bar, koto_playlist_get_uuid(self->playlist), selected_tracks); // Set the tracks for the playlist selection + koto_action_bar_toggle_reveal(action_bar, TRUE); // Show the items +} + +void koto_track_table_set_model( + KotoTrackTable * self, + KotoPreferredModelType model +) { + if (!KOTO_IS_TRACK_TABLE(self)) { + return; + } + + koto_playlist_apply_model(self->playlist, model); // Apply our new model + self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel + + if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently + gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active"); + } + + if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently + gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active"); + } + + if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name + gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active"); + } + + self->selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(self->model)); + g_signal_connect(self->selection_model, "selection-changed", G_CALLBACK(koto_track_table_handle_tracks_selected), self); // Bind to our selection changed + + gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model +} + +void koto_track_table_set_playlist_model ( + KotoTrackTable * self, + KotoPreferredModelType model +) { + if (!KOTO_IS_TRACK_TABLE(self)) { // Not a track table + return; + } + + if (!KOTO_IS_PLAYLIST(self->playlist)) { // Don't have a playlist yet + return; + } + + koto_playlist_apply_model(self->playlist, model); // Apply our new model + self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel + + if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently + gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active"); + } + + if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently + gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active"); + } + + if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name + gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active"); + } + + self->selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(self->model)); + g_signal_connect(self->selection_model, "selection-changed", G_CALLBACK(koto_track_table_handle_tracks_selected), self); // Bind to our selection changed + + gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model + + KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist); // Get the current model + + if (current_model == KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST) { + koto_button_show_image(self->track_num_button, TRUE); // Immediately use pan-up-symbolic + } +} + +void koto_track_table_set_playlist( + KotoTrackTable * self, + KotoPlaylist * playlist +) { + if (!KOTO_IS_TRACK_TABLE(self)) { + return; + } + + if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist + return; + } + + self->playlist = playlist; + koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // TODO: Enable this to be changed +} + +void koto_track_table_setup_track_item( + GtkListItemFactory * factory, + GtkListItem * item, + gpointer user_data +) { + (void) factory; + KotoTrackTable * self = user_data; + + if (!KOTO_IS_TRACK_TABLE(self)) { + return; + } + + GtkWidget * item_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Have a horizontal box for our content + + gtk_widget_add_css_class(item_content, "track-list-columned-item"); + + GtkWidget * track_number = gtk_label_new(NULL); // Our track number + + gtk_label_set_xalign(GTK_LABEL(track_number), 0); + gtk_widget_add_css_class(track_number, "track-column-number"); + gtk_widget_set_halign(track_number, GTK_ALIGN_END); + gtk_widget_set_hexpand(track_number, FALSE); + gtk_widget_set_size_request(track_number, 150, -1); + gtk_box_append(GTK_BOX(item_content), track_number); + gtk_size_group_add_widget(self->track_pos_size_group, track_number); + + GtkWidget * track_name = gtk_label_new(NULL); // Our track name + + gtk_label_set_xalign(GTK_LABEL(track_name), 0); + gtk_widget_add_css_class(track_name, "track-column-name"); + gtk_widget_set_halign(track_name, GTK_ALIGN_START); + gtk_widget_set_hexpand(track_name, FALSE); + gtk_widget_set_size_request(track_name, 350, -1); + gtk_box_append(GTK_BOX(item_content), track_name); + gtk_size_group_add_widget(self->track_name_size_group, track_name); + + GtkWidget * track_album = gtk_label_new(NULL); // Our track album + + gtk_label_set_xalign(GTK_LABEL(track_album), 0); + gtk_widget_add_css_class(track_album, "track-column-album"); + gtk_widget_set_halign(track_album, GTK_ALIGN_START); + gtk_widget_set_hexpand(track_album, FALSE); + gtk_widget_set_margin_start(track_album, 50); + gtk_widget_set_size_request(track_album, 350, -1); + gtk_box_append(GTK_BOX(item_content), track_album); + gtk_size_group_add_widget(self->track_album_size_group, track_album); + + GtkWidget * track_artist = gtk_label_new(NULL); // Our track artist + + gtk_label_set_xalign(GTK_LABEL(track_artist), 0); + gtk_widget_add_css_class(track_artist, "track-column-artist"); + gtk_widget_set_halign(track_artist, GTK_ALIGN_START); + gtk_widget_set_hexpand(track_artist, TRUE); + gtk_widget_set_margin_start(track_artist, 50); + gtk_widget_set_size_request(track_artist, 350, -1); + gtk_box_append(GTK_BOX(item_content), track_artist); + gtk_size_group_add_widget(self->track_artist_size_group, track_artist); + + gtk_list_item_set_child(item, item_content); +} + +KotoTrackTable * koto_track_table_new() { + return g_object_new( + KOTO_TYPE_TRACK_TABLE, + NULL + ); +} \ No newline at end of file diff --git a/src/components/track-table.h b/src/components/track-table.h new file mode 100644 index 0000000..5b73212 --- /dev/null +++ b/src/components/track-table.h @@ -0,0 +1,114 @@ +/* track-table.h + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include "../playlist/playlist.h" +#include "koto-action-bar.h" + +G_BEGIN_DECLS + +#define KOTO_TYPE_TRACK_TABLE koto_track_table_get_type() +#define KOTO_TRACK_TABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_TRACK_TABLE, KotoTrackTable)) +typedef struct _KotoTrackTable KotoTrackTable; +typedef struct _KotoTrackTableClass KotoTrackTableClass; + +GLIB_AVAILABLE_IN_ALL +GType koto_track_type_get_type(void) G_GNUC_CONST; + +#define KOTO_IS_TRACK_TABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_TRACK_TABLE)) + +void koto_track_table_bind_track_item( + GtkListItemFactory * factory, + GtkListItem * item, + KotoTrackTable * self +); + +void koto_track_table_create_tracks_header(KotoTrackTable * self); + +GtkWidget * koto_track_table_get_main(KotoTrackTable * self); + +void koto_track_table_handle_action_bar_closed( + KotoActionBar * bar, + gpointer data +); + +void koto_track_table_handle_track_album_clicked( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer user_data +); + +void koto_track_table_handle_track_artist_clicked( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer user_data +); + +void koto_track_table_handle_track_name_clicked( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer user_data +); + +void koto_track_table_handle_track_num_clicked( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer user_data +); + +void koto_track_table_handle_tracks_selected( + GtkSelectionModel * model, + guint position, + guint n_items, + gpointer user_data +); + +void koto_track_table_set_model( + KotoTrackTable * self, + KotoPreferredModelType model +); + +void koto_track_table_set_playlist_model( + KotoTrackTable * self, + KotoPreferredModelType model +); + +void koto_track_table_set_playlist( + KotoTrackTable * self, + KotoPlaylist * playlist +); + +void koto_track_table_setup_track_item( + GtkListItemFactory * factory, + GtkListItem * item, + gpointer user_data +); + +KotoTrackTable * koto_track_table_new(); + +G_END_DECLS \ No newline at end of file diff --git a/src/db/loaders.c b/src/db/loaders.c index 41355a0..0bbda88 100644 --- a/src/db/loaders.c +++ b/src/db/loaders.c @@ -61,6 +61,8 @@ int process_artists( return 1; } + koto_artist_set_as_finalized(artist); // Indicate it is finalized + int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE artist_id=\"%s\"", artist_uuid), process_tracks, NULL, NULL); // Process our tracks by artist uuid if (tracks_rc != SQLITE_OK) { // Failed to get our tracks diff --git a/src/indexer/artist-playlist-funcs.h b/src/indexer/artist-playlist-funcs.h new file mode 100644 index 0000000..2c9677f --- /dev/null +++ b/src/indexer/artist-playlist-funcs.h @@ -0,0 +1,25 @@ +/* artist-playlist-funcs.h + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../playlist/playlist.h" +#include "structs.h" + +G_BEGIN_DECLS + +KotoPlaylist * koto_artist_get_playlist(KotoArtist * self); + +G_END_DECLS \ No newline at end of file diff --git a/src/indexer/artist.c b/src/indexer/artist.c index d07cc5b..47def9f 100644 --- a/src/indexer/artist.c +++ b/src/indexer/artist.c @@ -19,7 +19,9 @@ #include #include "../db/db.h" #include "../db/cartographer.h" +#include "../playlist/playlist.h" #include "../koto-utils.h" +#include "artist-playlist-funcs.h" #include "structs.h" #include "track-helpers.h" @@ -39,6 +41,7 @@ static GParamSpec * props[N_PROPERTIES] = { enum { SIGNAL_ALBUM_ADDED, SIGNAL_ALBUM_REMOVED, + SIGNAL_HAS_NO_ALBUMS, SIGNAL_TRACK_ADDED, SIGNAL_TRACK_REMOVED, N_SIGNALS @@ -52,6 +55,9 @@ struct _KotoArtist { GObject parent_instance; gchar * uuid; + KotoPlaylist * content_playlist; + + gboolean finalized; gboolean has_artist_art; gchar * artist_name; GList * albums; @@ -71,6 +77,7 @@ struct _KotoArtistClass { KotoArtist * artist, KotoAlbum * album ); + void (* has_no_albums) (KotoArtist * artist); void (* track_added) ( KotoArtist * artist, KotoTrack * track @@ -130,6 +137,18 @@ static void koto_artist_class_init(KotoArtistClass * c) { KOTO_TYPE_ALBUM ); + artist_signals[SIGNAL_HAS_NO_ALBUMS] = g_signal_new( + "has-no-albums", + G_TYPE_FROM_CLASS(gobject_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(KotoArtistClass, has_no_albums), + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0 + ); + artist_signals[SIGNAL_TRACK_ADDED] = g_signal_new( "track-added", G_TYPE_FROM_CLASS(gobject_class), @@ -175,6 +194,24 @@ static void koto_artist_class_init(KotoArtistClass * c) { g_object_class_install_properties(gobject_class, N_PROPERTIES, props); } +static void koto_artist_init(KotoArtist * self) { + self->albums = NULL; // Create a new GList + + self->content_playlist = koto_playlist_new(); // Create our playlist + g_object_set( + self->content_playlist, + "ephemeral", // Indicate that it is temporary + TRUE, + NULL + ); + + self->finalized = FALSE; // Indicate we not finalized + self->has_artist_art = FALSE; + self->paths = g_hash_table_new(g_str_hash, g_str_equal); + self->tracks = NULL; + self->type = KOTO_LIBRARY_TYPE_UNKNOWN; +} + void koto_artist_commit(KotoArtist * self) { if ((self->uuid == NULL) || strcmp(self->uuid, "")) { // UUID not set self->uuid = g_strdup(g_uuid_string_random()); @@ -211,12 +248,12 @@ void koto_artist_commit(KotoArtist * self) { } } -static void koto_artist_init(KotoArtist * self) { - self->albums = NULL; // Create a new GList - self->has_artist_art = FALSE; - self->paths = g_hash_table_new(g_str_hash, g_str_equal); - self->tracks = NULL; - self->type = KOTO_LIBRARY_TYPE_UNKNOWN; +KotoPlaylist * koto_artist_get_playlist(KotoArtist * self) { + if (!KOTO_IS_ARTIST(self)) { + return NULL; + } + + return self->content_playlist; } static void koto_artist_get_property( @@ -311,6 +348,8 @@ void koto_artist_add_track( koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL); + koto_playlist_add_track(self->content_playlist, track, FALSE, FALSE); // Add this new track for the artist to its playlist + g_signal_emit( self, artist_signals[SIGNAL_TRACK_ADDED], @@ -408,8 +447,11 @@ void koto_artist_remove_track( return; } + gchar * track_uuid = koto_track_get_uuid(track); self->tracks = g_list_remove(self->tracks, koto_track_get_uuid(track)); + koto_playlist_remove_track_by_uuid(self->content_playlist, track_uuid); // Remove the track from our playlist + g_signal_emit( self, artist_signals[SIGNAL_TRACK_ADDED], @@ -438,6 +480,18 @@ void koto_artist_set_artist_name( g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_NAME]); } +void koto_artist_set_as_finalized(KotoArtist * self) { + if (!KOTO_IS_ARTIST(self)) { // Not an artist + return; + } + + self->finalized = TRUE; + + if (g_list_length(self->albums) == 0) { // Have no albums + g_signal_emit_by_name(self, "has-no-albums"); + } +} + void koto_artist_set_path( KotoArtist * self, KotoLibrary * lib, diff --git a/src/indexer/file-indexer.c b/src/indexer/file-indexer.c index 6e66011..e916e97 100644 --- a/src/indexer/file-indexer.c +++ b/src/indexer/file-indexer.c @@ -57,6 +57,7 @@ void index_folder( koto_artist_set_path(artist, self, full_path, TRUE); // Add the path for this library on this Artist and commit immediately koto_cartographer_add_artist(koto_maps, artist); // Add the artist to cartographer index_folder(self, full_path, depth); // Index this directory + koto_artist_set_as_finalized(artist); // Indicate it is finalized } } else if (depth == 2) { // If we are following FOLDER/ARTIST/ALBUM then this would be album gchar * artist_name = g_path_get_basename(path); // Get the last entry from our path which is probably the artist @@ -197,10 +198,6 @@ void index_file( album = KOTO_IS_ALBUM(possible_album) ? possible_album : NULL; } - if (!KOTO_IS_ALBUM(album)) { - return; - } - gchar * album_uuid = KOTO_IS_ALBUM(album) ? koto_album_get_uuid(album) : NULL; track = koto_track_new(koto_artist_get_uuid(artist), album_uuid, file_name, cd); diff --git a/src/indexer/structs.h b/src/indexer/structs.h index 0086c37..d5951ef 100644 --- a/src/indexer/structs.h +++ b/src/indexer/structs.h @@ -180,6 +180,8 @@ void koto_artist_set_artist_name( gchar * artist_name ); +void koto_artist_set_as_finalized(KotoArtist * self); + void koto_artist_set_path( KotoArtist * self, KotoLibrary * lib, @@ -282,6 +284,11 @@ void koto_track_remove_from_playlist( gchar * playlist_uuid ); +void koto_track_set_album_uuid( + KotoTrack * self, + const gchar * album_uuid +); + void koto_track_save_to_playlist( KotoTrack * self, gchar * playlist_uuid, diff --git a/src/indexer/track.c b/src/indexer/track.c index d25166e..8aa2925 100644 --- a/src/indexer/track.c +++ b/src/indexer/track.c @@ -210,8 +210,7 @@ static void koto_track_set_property( g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_UUID]); break; case PROP_ALBUM_UUID: - self->album_uuid = g_strdup(g_value_get_string(val)); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ALBUM_UUID]); + koto_track_set_album_uuid(self, g_value_get_string(val)); break; case PROP_UUID: self->uuid = g_strdup(g_value_get_string(val)); @@ -260,12 +259,14 @@ void koto_track_commit(KotoTrack * self) { self->uuid, self->artist_uuid, self->album_uuid, - self->parsed_name, + g_strescape(self->parsed_name, NULL), (int) self->cd, (int) self->position ); - new_transaction(commit_op, "Failed to write our file to the database", FALSE); + if (new_transaction(commit_op, "Failed to write our file to the database", FALSE) != SQLITE_OK) { + g_message("Failed with song: %s", self->parsed_name); + } GHashTableIter paths_iter; g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths @@ -315,7 +316,7 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) { g_variant_builder_add(builder, "{sv}", "xesam:album", g_variant_new_string(album_name)); } } else { - } // TODO: Implement artist artwork fetching here + } // TODO: Implement artist artwork fetching here g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new_string(self->uuid)); @@ -362,10 +363,10 @@ gchar * koto_track_get_path(KotoTrack * self) { gchar * path = NULL; while (g_hash_table_iter_next(&iter, &uuidptr, &relpathptr)) { // Iterate over all the paths for this file - KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, (gchar *) uuidptr); + KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, (gchar*) uuidptr); if (KOTO_IS_LIBRARY(library)) { - path = g_strdup(g_build_path(G_DIR_SEPARATOR_S, koto_library_get_path(library), koto_library_get_relative_path_to_file(library, (gchar *) relpathptr), NULL)); // Build our full library path using library's path and our file relative path + path = g_strdup(g_build_path(G_DIR_SEPARATOR_S, koto_library_get_path(library), koto_library_get_relative_path_to_file(library, (gchar*) relpathptr), NULL)); // Build our full library path using library's path and our file relative path break; } } @@ -426,6 +427,28 @@ void koto_track_remove_from_playlist( new_transaction(commit_op, "Failed to remove track from playlist", FALSE); } +void koto_track_set_album_uuid( + KotoTrack * self, + const gchar * album_uuid +) { + if (!KOTO_IS_TRACK(self)) { + return; + } + + if (album_uuid == NULL) { + return; + } + + gchar * uuid = g_strdup(album_uuid); + + if (!koto_utils_is_string_valid(uuid)) { // If this is not a valid string + return; + } + + self->album_uuid = uuid; + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ALBUM_UUID]); +} + void koto_track_save_to_playlist( KotoTrack * self, gchar * playlist_uuid, diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c index a2855e5..c0d34f2 100644 --- a/src/koto-playerbar.c +++ b/src/koto-playerbar.c @@ -26,6 +26,7 @@ #include "koto-button.h" #include "config/config.h" #include "koto-playerbar.h" +#include "koto-utils.h" extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup; extern KotoCartographer * koto_maps; @@ -616,10 +617,8 @@ void koto_playerbar_update_track_info( g_object_get(current_track, "parsed-name", &track_name, "artist-uuid", &artist_uuid, "album-uuid", &album_uuid, NULL); KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); g_free(artist_uuid); - g_free(album_uuid); if ((track_name != NULL) && (strcmp(track_name, "") != 0)) { // Have a track name gtk_label_set_text(GTK_LABEL(bar->playback_title), track_name); // Set the label @@ -637,6 +636,13 @@ void koto_playerbar_update_track_info( } } + if (!koto_utils_is_string_valid(album_uuid)) { // Do not have a valid album UUID + return; + } + + KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); + g_free(album_uuid); + if (KOTO_IS_ALBUM(album)) { gchar * album_name = NULL; gchar * art_path = NULL; diff --git a/src/main.c b/src/main.c index 4d27f8d..3c65e12 100644 --- a/src/main.c +++ b/src/main.c @@ -46,6 +46,9 @@ extern GList * supported_mimes; extern gchar * koto_path_to_conf; extern gchar * koto_rev_dns; + +extern gboolean created_new_db; + GVolumeMonitor * volume_monitor = NULL; GtkApplication * app = NULL; GtkWindow * main_window; @@ -59,7 +62,10 @@ static void on_activate (GtkApplication * app) { main_window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL); setup_mpris_interfaces(); // Set up our MPRIS interfaces setup_mediakeys_interface(); // Set up our media key support - read_from_db(); // Read the database, allowing us to propagate the UI with various data such as artists and playlist navigation elements + + if (!created_new_db) { + read_from_db(); // Read the database, allowing us to propagate the UI with various data such as artists and playlist navigation elements + } } gtk_window_present(main_window); diff --git a/src/meson.build b/src/meson.build index 774e3d4..7087ead 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,6 +7,7 @@ add_global_arguments([ koto_sources = [ 'components/koto-action-bar.c', 'components/koto-cover-art-button.c', + 'components/track-table.c', 'config/config.c', 'db/cartographer.c', 'db/db.c', diff --git a/src/pages/music/artist-view.c b/src/pages/music/artist-view.c index 5269b6e..03abfb0 100644 --- a/src/pages/music/artist-view.c +++ b/src/pages/music/artist-view.c @@ -17,13 +17,18 @@ #include #include +#include "../../components/koto-cover-art-button.h" +#include "../../components/track-table.h" #include "../../db/cartographer.h" +#include "../../indexer/artist-playlist-funcs.h" #include "../../indexer/structs.h" +#include "../../playlist/current.h" #include "album-view.h" #include "artist-view.h" #include "config/config.h" #include "koto-utils.h" +extern KotoCurrentPlaylist * current_playlist; extern KotoCartographer * koto_maps; struct _KotoArtistView { @@ -33,6 +38,12 @@ struct _KotoArtistView { GtkWidget * content; GtkWidget * album_list; + GtkWidget * no_albums_view; + GtkWidget * no_albums_artist_header; + KotoCoverArtButton * no_albums_artist_button; + GtkWidget * no_albums_artist_label; + + KotoTrackTable * table; GHashTable * albums_to_component; }; @@ -47,7 +58,6 @@ enum { static GParamSpec * props[N_PROPERTIES] = { NULL, }; -static void koto_artist_view_constructed(GObject * obj); static void koto_artist_view_get_property( GObject * obj, @@ -67,7 +77,6 @@ static void koto_artist_view_class_init(KotoArtistViewClass * c) { GObjectClass * gobject_class; gobject_class = G_OBJECT_CLASS(c); - gobject_class->constructed = koto_artist_view_constructed; gobject_class->set_property = koto_artist_view_set_property; gobject_class->get_property = koto_artist_view_get_property; @@ -82,6 +91,50 @@ static void koto_artist_view_class_init(KotoArtistViewClass * c) { g_object_class_install_properties(gobject_class, N_PROPERTIES, props); } +static void koto_artist_view_init(KotoArtistView * self) { + self->artist = NULL; + self->albums_to_component = g_hash_table_new(g_str_hash, g_str_equal); + + self->scrolled_window = gtk_scrolled_window_new(); // Create our scrolled window + self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Create our content as a GtkBox + + gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), TRUE); + gtk_scrolled_window_set_propagate_natural_width(GTK_SCROLLED_WINDOW(self->scrolled_window), TRUE); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->content); // Add the content as the widget for the scrolled window + gtk_widget_add_css_class(GTK_WIDGET(self->scrolled_window), "artist-view"); + gtk_widget_add_css_class(GTK_WIDGET(self->content), "artist-view-content"); + + self->album_list = gtk_flow_box_new(); // Create our list of our albums + gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->album_list), GTK_SELECTION_NONE); + gtk_widget_add_css_class(self->album_list, "album-list"); + + self->no_albums_view = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_add_css_class(self->no_albums_view, "no-albums-view"); + + self->no_albums_artist_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class(self->no_albums_artist_header, "no-albums-view-header"), + + self->no_albums_artist_button = koto_cover_art_button_new(220, 220, NULL); + KotoButton * cover_art_button = koto_cover_art_button_get_button(self->no_albums_artist_button); // Get the button for the KotoCoverArt + koto_button_add_click_handler(cover_art_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_artist_view_toggle_playback), self); // Handle clicking the button + + self->no_albums_artist_label = gtk_label_new(NULL); // Create a label without any artist name + + gtk_box_append(GTK_BOX(self->no_albums_artist_header), koto_cover_art_button_get_main(self->no_albums_artist_button)); // Add our button to the header + gtk_box_append(GTK_BOX(self->no_albums_artist_header), self->no_albums_artist_label); + + gtk_box_append(GTK_BOX(self->no_albums_view), self->no_albums_artist_header); // Add the header to the no albums view + + self->table = koto_track_table_new(); //Create our track table + gtk_box_append(GTK_BOX(self->no_albums_view), koto_track_table_get_main(self->table)); // Add the table to the no albums view + + gtk_box_append(GTK_BOX(self->content), self->album_list); // Add the album flowbox + gtk_box_append(GTK_BOX(self->content), self->no_albums_view); // Add the no albums view just in case we do not have any albums + + gtk_widget_set_hexpand(GTK_WIDGET(self->album_list), TRUE); + gtk_widget_set_hexpand(GTK_WIDGET(self->no_albums_view), TRUE); +} + static void koto_artist_view_get_property( GObject * obj, guint prop_id, @@ -118,34 +171,6 @@ static void koto_artist_view_set_property( } } -static void koto_artist_view_init(KotoArtistView * self) { - self->artist = NULL; - self->albums_to_component = g_hash_table_new(g_str_hash, g_str_equal); -} - -static void koto_artist_view_constructed(GObject * obj) { - KotoArtistView * self = KOTO_ARTIST_VIEW(obj); - - self->scrolled_window = gtk_scrolled_window_new(); // Create our scrolled window - self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Create our content as a GtkBox - - gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), TRUE); - gtk_scrolled_window_set_propagate_natural_width(GTK_SCROLLED_WINDOW(self->scrolled_window), TRUE); - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->content); // Add the content as the widget for the scrolled window - gtk_widget_add_css_class(GTK_WIDGET(self->scrolled_window), "artist-view"); - gtk_widget_add_css_class(GTK_WIDGET(self->content), "artist-view-content"); - - self->album_list = gtk_flow_box_new(); // Create our list of our albums - gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->album_list), GTK_SELECTION_NONE); - gtk_widget_add_css_class(self->album_list, "album-list"); - - gtk_box_append(GTK_BOX(self->content), self->album_list); // Add the list - - gtk_widget_set_hexpand(GTK_WIDGET(self->album_list), TRUE); - - G_OBJECT_CLASS(koto_artist_view_parent_class)->constructed(obj); -} - void koto_artist_view_add_album( KotoArtistView * self, KotoAlbum * album @@ -169,6 +194,13 @@ void koto_artist_view_add_album( gtk_flow_box_insert(GTK_FLOW_BOX(self->album_list), album_view_main, -1); // Append the album view to the album list g_hash_table_replace(self->albums_to_component, album_uuid, album_view_main); + + gtk_widget_hide(self->no_albums_view); // Hide the no album view + gtk_widget_show(self->album_list); // Show the album list now that it has albums +} + +GtkWidget * koto_artist_view_get_main(KotoArtistView * self) { + return self->scrolled_window; } void koto_artist_view_handle_album_added( @@ -222,6 +254,30 @@ void koto_artist_view_handle_album_removed( g_hash_table_remove(self->albums_to_component, album_uuid); // Remove the album from our hash table } +void koto_artist_view_handle_artist_name_changed( + KotoArtist * artist, + guint prop_id, + KotoArtistView * self +) { + (void) prop_id; + gtk_label_set_text(GTK_LABEL(self->no_albums_artist_label), koto_artist_get_name(artist)); // Update the label for the artist now that it has changed +} + +void koto_artist_view_handle_has_no_albums( + KotoArtist * artist, + gpointer user_data +) { + (void) artist; + KotoArtistView * self = user_data; + + if (!KOTO_IS_ARTIST_VIEW(self)) { + return; + } + + gtk_widget_hide(self->album_list); // Hide our album list flowbox + gtk_widget_show(self->no_albums_view); // Show the no albums view +} + void koto_artist_view_set_artist( KotoArtistView * self, KotoArtist * artist @@ -235,12 +291,39 @@ void koto_artist_view_set_artist( } self->artist = artist; + koto_track_table_set_playlist(self->table, koto_artist_get_playlist(self->artist)); // Set our track table to the artist's playlist + gtk_label_set_text(GTK_LABEL(self->no_albums_artist_label), koto_artist_get_name(self->artist)); // Update our label with the name of the artist + g_signal_connect(artist, "album-added", G_CALLBACK(koto_artist_view_handle_album_added), self); g_signal_connect(artist, "album-removed", G_CALLBACK(koto_artist_view_handle_album_removed), self); + g_signal_connect(artist, "has-no-albums", G_CALLBACK(koto_artist_view_handle_has_no_albums), self); + g_signal_connect(artist, "notify::name", G_CALLBACK(koto_artist_view_handle_artist_name_changed), self); } -GtkWidget * koto_artist_view_get_main(KotoArtistView * self) { - return self->scrolled_window; +void koto_artist_view_toggle_playback( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer data +) { + (void) gesture; + (void) n_press; + (void) x; + (void) y; + KotoArtistView * self = data; + + if (!KOTO_IS_ARTIST_VIEW(self)) { + return; + } + + KotoPlaylist * artist_playlist = koto_artist_get_playlist(self->artist); + + if (!KOTO_IS_PLAYLIST(artist_playlist)) { // Failed our is playlist check for the artist playlist + return; + } + + koto_current_playlist_set_playlist(current_playlist, artist_playlist); // Set our playlist to the one associated with the Artist } KotoArtistView * koto_artist_view_new(KotoArtist * artist) { diff --git a/src/pages/music/artist-view.h b/src/pages/music/artist-view.h index 15442d6..e5c6baa 100644 --- a/src/pages/music/artist-view.h +++ b/src/pages/music/artist-view.h @@ -34,6 +34,8 @@ void koto_artist_view_add_album( KotoAlbum * album ); +GtkWidget * koto_artist_view_get_main(KotoArtistView * self); + void koto_artist_view_handle_album_added( KotoArtist * artist, KotoAlbum * album, @@ -46,11 +48,28 @@ void koto_artist_view_handle_album_removed( gpointer user_data ); +void koto_artist_view_handle_artist_name_changed( + KotoArtist * artist, + guint prop_id, + KotoArtistView * self +); + +void koto_artist_view_handle_has_no_albums( + KotoArtist * artist, + gpointer user_data +); + void koto_artist_view_set_artist( KotoArtistView * self, KotoArtist * artist ); -GtkWidget * koto_artist_view_get_main(KotoArtistView * self); +void koto_artist_view_toggle_playback( + GtkGestureClick * gesture, + int n_press, + double x, + double y, + gpointer data +); G_END_DECLS diff --git a/src/pages/playlist/list.c b/src/pages/playlist/list.c index bc9dc53..c5354d5 100644 --- a/src/pages/playlist/list.c +++ b/src/pages/playlist/list.c @@ -19,7 +19,9 @@ #include #include "../../components/koto-action-bar.h" #include "../../components/koto-cover-art-button.h" +#include "../../components/track-table.h" #include "../../db/cartographer.h" +#include "../../indexer/structs.h" #include "../../playlist/current.h" #include "../../playlist/playlist.h" #include "../../koto-button.h" @@ -59,23 +61,7 @@ struct _KotoPlaylistPage { KotoButton * favorite_button; KotoButton * edit_button; - GtkListItemFactory * item_factory; - GListModel * model; - GtkSelectionModel * selection_model; - - GtkWidget * track_list_content; - GtkWidget * track_list_header; - GtkWidget * track_list_view; - - KotoButton * track_num_button; - KotoButton * track_title_button; - KotoButton * track_album_button; - KotoButton * track_artist_button; - - GtkSizeGroup * track_pos_size_group; - GtkSizeGroup * track_name_size_group; - GtkSizeGroup * track_album_size_group; - GtkSizeGroup * track_artist_size_group; + KotoTrackTable * table; }; struct _KotoPlaylistPageClass { @@ -117,11 +103,6 @@ static void koto_playlist_page_class_init(KotoPlaylistPageClass * c) { } static void koto_playlist_page_init(KotoPlaylistPage * self) { - self->track_name_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - self->track_pos_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - self->track_album_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - self->track_artist_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - self->main = gtk_scrolled_window_new(); // Create our scrolled window self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_widget_add_css_class(self->content, "playlist-page"); @@ -166,30 +147,9 @@ static void koto_playlist_page_init(KotoPlaylistPage * self) { gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->favorite_button)); // Add the favorite button gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->edit_button)); // Add the edit button - self->item_factory = gtk_signal_list_item_factory_new(); // Create a new signal list item factory - g_signal_connect(self->item_factory, "setup", G_CALLBACK(koto_playlist_page_setup_track_item), self); - g_signal_connect(self->item_factory, "bind", G_CALLBACK(koto_playlist_page_bind_track_item), self); - - self->track_list_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_add_css_class((self->track_list_content), "track-list-content"); - gtk_widget_set_hexpand(self->track_list_content, TRUE); // Expand horizontally - gtk_widget_set_vexpand(self->track_list_content, TRUE); // Expand vertically - - self->track_list_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_add_css_class(self->track_list_header, "track-list-header"); - koto_playlist_page_create_tracks_header(self); // Create our tracks header content - - self->track_list_view = gtk_list_view_new(NULL, self->item_factory); // Create our list view with no model yet - gtk_widget_add_css_class(self->track_list_view, "track-list-columned"); - gtk_widget_set_hexpand(self->track_list_view, TRUE); // Expand horizontally - gtk_widget_set_vexpand(self->track_list_view, TRUE); // Expand vertically - - gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_header); - gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_view); - - gtk_box_append(GTK_BOX(self->content), self->track_list_content); - - g_signal_connect(action_bar, "closed", G_CALLBACK(koto_playlist_page_handle_action_bar_closed), self); // Handle closed action bar + self->table = koto_track_table_new(); // Create our new KotoTrackTable + GtkWidget * track_main = koto_track_table_get_main(self->table); + gtk_box_append(GTK_BOX(self->content), track_main); } static void koto_playlist_page_get_property( @@ -228,104 +188,10 @@ static void koto_playlist_page_set_property( } } -void koto_playlist_page_bind_track_item( - GtkListItemFactory * factory, - GtkListItem * item, - KotoPlaylistPage * self -) { - (void) factory; - - GtkWidget * track_position_label = gtk_widget_get_first_child(gtk_list_item_get_child(item)); - GtkWidget * track_name_label = gtk_widget_get_next_sibling(track_position_label); - GtkWidget * track_album_label = gtk_widget_get_next_sibling(track_name_label); - GtkWidget * track_artist_label = gtk_widget_get_next_sibling(track_album_label); - - KotoTrack * track = gtk_list_item_get_item(item); // Get the track UUID from our model - - if (!KOTO_IS_TRACK(track)) { - return; - } - - gchar * track_name = NULL; - gchar * album_uuid = NULL; - gchar * artist_uuid = NULL; - - g_object_get( - track, - "parsed-name", - &track_name, - "album-uuid", - &album_uuid, - "artist-uuid", - &artist_uuid, - NULL - ); - - guint track_position = koto_playlist_get_position_of_track(self->playlist, track) + 1; - - gtk_label_set_label(GTK_LABEL(track_position_label), g_strdup_printf("%u", track_position)); // Set the track position - gtk_label_set_label(GTK_LABEL(track_name_label), track_name); // Set our track name - - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); - - if (KOTO_IS_ALBUM(album)) { - gtk_label_set_label(GTK_LABEL(track_album_label), koto_album_get_name(album)); // Get the name of the album and set it to the label - } - - KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); - - if (KOTO_IS_ARTIST(artist)) { - gtk_label_set_label(GTK_LABEL(track_artist_label), koto_artist_get_name(artist)); // Get the name of the artist and set it to the label - } -} - -void koto_playlist_page_create_tracks_header(KotoPlaylistPage * self) { - self->track_num_button = koto_button_new_with_icon("#", "pan-down-symbolic", "pan-up-symbolic", KOTO_BUTTON_PIXBUF_SIZE_SMALL); - koto_button_add_click_handler(self->track_num_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_track_num_clicked), self); - koto_button_set_image_position(self->track_num_button, KOTO_BUTTON_IMAGE_POS_RIGHT); // Move the image to the right - gtk_size_group_add_widget(self->track_pos_size_group, GTK_WIDGET(self->track_num_button)); - - self->track_title_button = koto_button_new_plain("Title"); - koto_button_add_click_handler(self->track_title_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_track_name_clicked), self); - gtk_size_group_add_widget(self->track_name_size_group, GTK_WIDGET(self->track_title_button)); - - self->track_album_button = koto_button_new_plain("Album"); - - gtk_widget_set_margin_start(GTK_WIDGET(self->track_album_button), 50); - koto_button_add_click_handler(self->track_album_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_track_album_clicked), self); - gtk_size_group_add_widget(self->track_album_size_group, GTK_WIDGET(self->track_album_button)); - - self->track_artist_button = koto_button_new_plain("Artist"); - - gtk_widget_set_margin_start(GTK_WIDGET(self->track_artist_button), 50); - koto_button_add_click_handler(self->track_artist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_track_artist_clicked), self); - gtk_size_group_add_widget(self->track_artist_size_group, GTK_WIDGET(self->track_artist_button)); - - gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_num_button)); - gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_title_button)); - gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_album_button)); - gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_artist_button)); -} - GtkWidget * koto_playlist_page_get_main(KotoPlaylistPage * self) { return self->main; } -void koto_playlist_page_handle_action_bar_closed( - KotoActionBar * bar, - gpointer data -) { - (void) bar; - KotoPlaylistPage * self = data; - - if (!KOTO_IS_PLAYLIST(self->playlist)) { // No playlist set - return; - } - - gtk_selection_model_unselect_all(self->selection_model); - gtk_widget_grab_focus(GTK_WIDGET(main_window)); // Focus on the window -} - void koto_playlist_page_handle_cover_art_clicked( GtkGestureClick * gesture, int n_press, @@ -390,132 +256,6 @@ void koto_playlist_page_handle_playlist_modified( } } -void koto_playlist_page_handle_track_album_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoPlaylistPage * self = user_data; - - gtk_widget_add_css_class(GTK_WIDGET(self->track_album_button), "active"); - koto_button_hide_image(self->track_num_button); // Go back to hiding the image - koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM); -} - -void koto_playlist_page_handle_track_artist_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoPlaylistPage * self = user_data; - - gtk_widget_add_css_class(GTK_WIDGET(self->track_artist_button), "active"); - koto_button_hide_image(self->track_num_button); // Go back to hiding the image - koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST); -} - -void koto_playlist_page_handle_track_name_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoPlaylistPage * self = user_data; - - gtk_widget_add_css_class(GTK_WIDGET(self->track_title_button), "active"); - koto_button_hide_image(self->track_num_button); // Go back to hiding the image - koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME); -} - -void koto_playlist_page_handle_track_num_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoPlaylistPage * self = user_data; - - KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist); - - if (current_model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) { // Set to newest currently - koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST); // Sort reversed (oldest) - koto_button_show_image(self->track_num_button, TRUE); // Use inverted value (pan-up-symbolic) - } else { - koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // Sort newest - koto_button_show_image(self->track_num_button, FALSE); // Use pan down default - } -} - -void koto_playlist_page_handle_tracks_selected( - GtkSelectionModel * model, - guint position, - guint n_items, - gpointer user_data -) { - (void) position; - KotoPlaylistPage * self = user_data; - - if (n_items == 0) { // No items selected - koto_action_bar_toggle_reveal(action_bar, FALSE); // Hide the action bar - return; - } - - GtkBitset * selected_items_bitset = gtk_selection_model_get_selection(model); // Get the selected items as a GtkBitSet - GtkBitsetIter iter; - GList * selected_tracks = NULL; - GList * selected_tracks_pos = NULL; - - guint first_track_pos; - - if (!gtk_bitset_iter_init_first(&iter, selected_items_bitset, &first_track_pos)) { // Failed to get the first item - return; - } - - selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(first_track_pos)); - - gboolean have_more_items = TRUE; - - while (have_more_items) { // While we are able to get selected items - guint track_pos; - have_more_items = gtk_bitset_iter_next(&iter, &track_pos); - if (have_more_items) { // Got the next track - selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(track_pos)); - } - } - - GList * cur_pos_list; - - for (cur_pos_list = selected_tracks_pos; cur_pos_list != NULL; cur_pos_list = cur_pos_list->next) { // Iterate over every position that we accumulated - KotoTrack * selected_track = g_list_model_get_item(self->model, GPOINTER_TO_UINT(cur_pos_list->data)); // Get the KotoTrack in the GListModel for this current position - selected_tracks = g_list_append(selected_tracks, selected_track); // Add to selected tracks - } - - koto_action_bar_set_tracks_in_playlist_selection(action_bar, self->uuid, selected_tracks); // Set the tracks for the playlist selection - koto_action_bar_toggle_reveal(action_bar, TRUE); // Show the items -} - void koto_playlist_page_set_playlist_uuid( KotoPlaylistPage * self, gchar * playlist_uuid @@ -524,10 +264,6 @@ void koto_playlist_page_set_playlist_uuid( return; } - if (!KOTO_IS_PLAYLIST_PAGE(self)) { - return; - } - if (!koto_utils_is_string_valid(playlist_uuid)) { // Provided UUID string is not valid return; } @@ -540,100 +276,10 @@ void koto_playlist_page_set_playlist_uuid( KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->uuid); self->playlist = playlist; - koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // TODO: Enable this to be changed koto_playlist_page_update_header(self); // Update our header g_signal_connect(playlist, "modified", G_CALLBACK(koto_playlist_page_handle_playlist_modified), self); // Handle playlist modification -} - -void koto_playlist_page_set_playlist_model( - KotoPlaylistPage * self, - KotoPreferredModelType model -) { - if (!KOTO_IS_PLAYLIST_PAGE(self)) { - return; - } - - koto_playlist_apply_model(self->playlist, model); // Apply our new model - self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel - - if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently - gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active"); - } - - if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently - gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active"); - } - - if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name - gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active"); - } - - self->selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(self->model)); - g_signal_connect(self->selection_model, "selection-changed", G_CALLBACK(koto_playlist_page_handle_tracks_selected), self); // Bind to our selection changed - - gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model -} - -void koto_playlist_page_setup_track_item( - GtkListItemFactory * factory, - GtkListItem * item, - gpointer user_data -) { - (void) factory; - KotoPlaylistPage * self = user_data; - - if (!KOTO_IS_PLAYLIST_PAGE(self)) { - return; - } - - GtkWidget * item_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Have a horizontal box for our content - - gtk_widget_add_css_class(item_content, "track-list-columned-item"); - - GtkWidget * track_number = gtk_label_new(NULL); // Our track number - - gtk_label_set_xalign(GTK_LABEL(track_number), 0); - gtk_widget_add_css_class(track_number, "track-column-number"); - gtk_widget_set_halign(track_number, GTK_ALIGN_END); - gtk_widget_set_hexpand(track_number, FALSE); - gtk_widget_set_size_request(track_number, 150, -1); - gtk_box_append(GTK_BOX(item_content), track_number); - gtk_size_group_add_widget(self->track_pos_size_group, track_number); - - GtkWidget * track_name = gtk_label_new(NULL); // Our track name - - gtk_label_set_xalign(GTK_LABEL(track_name), 0); - gtk_widget_add_css_class(track_name, "track-column-name"); - gtk_widget_set_halign(track_name, GTK_ALIGN_START); - gtk_widget_set_hexpand(track_name, FALSE); - gtk_widget_set_size_request(track_name, 350, -1); - gtk_box_append(GTK_BOX(item_content), track_name); - gtk_size_group_add_widget(self->track_name_size_group, track_name); - - GtkWidget * track_album = gtk_label_new(NULL); // Our track album - - gtk_label_set_xalign(GTK_LABEL(track_album), 0); - gtk_widget_add_css_class(track_album, "track-column-album"); - gtk_widget_set_halign(track_album, GTK_ALIGN_START); - gtk_widget_set_hexpand(track_album, FALSE); - gtk_widget_set_margin_start(track_album, 50); - gtk_widget_set_size_request(track_album, 350, -1); - gtk_box_append(GTK_BOX(item_content), track_album); - gtk_size_group_add_widget(self->track_album_size_group, track_album); - - GtkWidget * track_artist = gtk_label_new(NULL); // Our track artist - - gtk_label_set_xalign(GTK_LABEL(track_artist), 0); - gtk_widget_add_css_class(track_artist, "track-column-artist"); - gtk_widget_set_halign(track_artist, GTK_ALIGN_START); - gtk_widget_set_hexpand(track_artist, TRUE); - gtk_widget_set_margin_start(track_artist, 50); - gtk_widget_set_size_request(track_artist, 350, -1); - gtk_box_append(GTK_BOX(item_content), track_artist); - gtk_size_group_add_widget(self->track_artist_size_group, track_artist); - - gtk_list_item_set_child(item, item_content); + koto_track_table_set_playlist(self->table, playlist); // Set our track table } void koto_playlist_page_update_header(KotoPlaylistPage * self) { @@ -666,12 +312,6 @@ void koto_playlist_page_update_header(KotoPlaylistPage * self) { if (koto_utils_is_string_valid(artwork)) { // Have artwork koto_cover_art_button_set_art_path(self->playlist_image, artwork); // Update our artwork } - - KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist); // Get the current model - - if (current_model == KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST) { - koto_button_show_image(self->track_num_button, TRUE); // Immediately use pan-up-symbolic - } } KotoPlaylistPage * koto_playlist_page_new(gchar * playlist_uuid) { diff --git a/src/pages/playlist/list.h b/src/pages/playlist/list.h index e3d55b7..eb263c0 100644 --- a/src/pages/playlist/list.h +++ b/src/pages/playlist/list.h @@ -51,10 +51,6 @@ void koto_playlist_page_remove_track( GtkWidget * koto_playlist_page_get_main(KotoPlaylistPage * self); -void koto_playlist_page_handle_action_bar_closed( - KotoActionBar * bar, - gpointer data -); void koto_playlist_page_handle_cover_art_clicked( GtkGestureClick * gesture, @@ -77,71 +73,15 @@ void koto_playlist_page_handle_playlist_modified( gpointer user_data ); -void koto_playlist_page_handle_track_album_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_playlist_page_handle_track_artist_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_playlist_page_handle_track_name_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_playlist_page_handle_track_num_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_playlist_page_handle_tracks_selected( - GtkSelectionModel * model, - guint position, - guint n_items, - gpointer user_data -); - void koto_playlist_page_hide_popover(KotoPlaylistPage * self); -void koto_playlist_page_setup_track_item( - GtkListItemFactory * factory, - GtkListItem * item, - gpointer user_data -); - void koto_playlist_page_set_playlist_uuid( KotoPlaylistPage * self, gchar * playlist_uuid ); -void koto_playlist_page_set_playlist_model( - KotoPlaylistPage * self, - KotoPreferredModelType model -); - void koto_playlist_page_show_popover(KotoPlaylistPage * self); void koto_playlist_page_update_header(KotoPlaylistPage * self); -void koto_playlist_page_handle_listitem_activate( - GtkListView * list, - guint position, - gpointer user_data -); - G_END_DECLS diff --git a/theme/_main.scss b/theme/_main.scss index 7359286..94c071a 100644 --- a/theme/_main.scss +++ b/theme/_main.scss @@ -1,5 +1,7 @@ @import 'components/cover-art-button'; @import 'components/gtk-overrides'; +@import 'components/track-table'; +@import 'pages/artist-view'; @import 'pages/music-local'; @import 'pages/playlist-page'; diff --git a/theme/components/_track-table.scss b/theme/components/_track-table.scss new file mode 100644 index 0000000..59f2d8c --- /dev/null +++ b/theme/components/_track-table.scss @@ -0,0 +1,49 @@ +.track-list-content { // Our Track List + & > .track-list-header, + .track-list-columned-item { + font-size: x-large; + padding: 3ex 2ex; + } + + & > .track-list-header { // Headers + font-weight: bold; + + .koto-button { // All Koto buttons in our header + &.active { // Is active + color: $green; + } + } + } + + & > .track-list-columned { // Column content + & > row { + &:not(:selected) { + color: $text-color-bright; + } + + &:nth-child(odd):not(:selected) { + background-color: $bg-secondary; + } + + & > .track-list-columned-item { // Track rows + font-size: x-large; + } + } + } + + .track-column-number { // Column section within header and track items + + } + + .track-column-name { // Name section within header and track items + + } + + .track-column-album { // Album section within headers and track items + + } + + .track-column-artist { // Artist section within headers and track items + + } +} \ No newline at end of file diff --git a/theme/pages/_artist-view.scss b/theme/pages/_artist-view.scss new file mode 100644 index 0000000..5d07e44 --- /dev/null +++ b/theme/pages/_artist-view.scss @@ -0,0 +1,14 @@ +.artist-view { + .no-albums-view { // No Albums + .no-albums-view-header { // Header for artist when we have no albums + .cover-art-button { // Button to play all artist tracks + margin-right: 40px; + } + + & > label { // Artist Name + font-weight: 800; + font-size: 10ex; + } + } + } +} \ No newline at end of file diff --git a/theme/pages/_playlist-page.scss b/theme/pages/_playlist-page.scss index f8f2924..9527357 100644 --- a/theme/pages/_playlist-page.scss +++ b/theme/pages/_playlist-page.scss @@ -35,54 +35,4 @@ } } - - .track-list-content { // Our Track List - & > .track-list-header, - .track-list-columned-item { - font-size: x-large; - padding: 3ex 2ex; - } - - & > .track-list-header { // Headers - font-weight: bold; - - .koto-button { // All Koto buttons in our header - &.active { // Is active - color: $green; - } - } - } - - & > .track-list-columned { // Column content - & > row { - &:not(:selected) { - color: $text-color-bright; - } - - &:nth-child(odd):not(:selected) { - background-color: $bg-secondary; - } - - & > .track-list-columned-item { // Track rows - font-size: x-large; - } - } - } - - .track-column-number { // Column section within header and track items - - } - - .track-column-name { // Name section within header and track items - - } - - .track-column-album { // Album section within headers and track items - - } - - .track-column-artist { // Artist section within headers and track items - - } - } } \ No newline at end of file