diff --git a/meson.build b/meson.build index c24b07f..c02b43c 100644 --- a/meson.build +++ b/meson.build @@ -11,6 +11,7 @@ alsa_dep = dependency('alsa') executable( 'hyperpop', + './src/player/SongLibrary.cpp', './src/player/OpusFile.cpp', './src/player/ALSAOutput.cpp', './src/player/Player.cpp', diff --git a/src/main.cpp b/src/main.cpp index a71b582..19fd427 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,12 +5,16 @@ #include "raven/Widget.hpp" #include "raven/Button.hpp" #include "src/player/Player.hpp" +#include "src/player/SongLibrary.hpp" #include "ui/AppWidget.hpp" #include "player/ALSAOutput.hpp" #include "player/OpusFile.hpp" int main() { + SongLibrary *library = new SongLibrary(); + library->populate_from("/home/hippoz/music"); + Player player; Raven::Application application(true); @@ -18,7 +22,11 @@ int main() window->spawn_window(); - window->set_main_widget(&player); + window->set_main_widget(&player, library); - return application.run(); + application.run(); + + delete library; + + return 0; } diff --git a/src/player/SongLibrary.cpp b/src/player/SongLibrary.cpp new file mode 100644 index 0000000..ced06bc --- /dev/null +++ b/src/player/SongLibrary.cpp @@ -0,0 +1,41 @@ +#include "SongLibrary.hpp" +#include +#include +#include + + +void SongLibrary::populate_from(std::filesystem::path directory) { + m_all_songs.clear(); + m_songs_by_artist.clear(); + + for (const auto &entry : std::filesystem::directory_iterator(directory)) { + OggOpusFile *file = op_open_file(entry.path().c_str(), NULL); + if (file == NULL) { + continue; + } + const OpusTags *tags = op_tags(file, -1); + if (tags == NULL) { + continue; + } + + const char *artist_value = opus_tags_query(tags, "artist", 0); + const char *title_value = opus_tags_query(tags, "title", 0); + const char *album_value = opus_tags_query(tags, "album", 0); + if (artist_value == NULL || title_value == NULL || album_value == NULL) { + continue; + } + + std::shared_ptr song_entry = std::make_shared(); + song_entry->artist = artist_value; + song_entry->title = title_value; + song_entry->album = album_value; + song_entry->absolute_path = entry.path().c_str(); + song_entry->length_ms = op_pcm_total(file, -1); + + m_all_songs.push_back(song_entry); + m_songs_by_artist[song_entry->artist].push_back(song_entry); + + op_free(file); + } +} + diff --git a/src/player/SongLibrary.hpp b/src/player/SongLibrary.hpp new file mode 100644 index 0000000..f0131ac --- /dev/null +++ b/src/player/SongLibrary.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include +#include + + +class SongLibrary { +public: + struct SongEntry { + std::string artist; + std::string album; + std::string title; + std::string absolute_path; + size_t length_ms; + }; + + SongLibrary() {} + + void populate_from(std::filesystem::path directory); + + const std::vector> &all_songs() { return m_all_songs; } + const std::map>> &songs_by_artist() { return m_songs_by_artist; } +private: + std::vector> m_all_songs; + std::map>> m_songs_by_artist; +}; + diff --git a/src/ui/AppWidget.cpp b/src/ui/AppWidget.cpp index 2617214..9e4403d 100644 --- a/src/ui/AppWidget.cpp +++ b/src/ui/AppWidget.cpp @@ -1,4 +1,5 @@ #include "AppWidget.hpp" +#include "GenericStyle.hpp" #include "raven/Styles.hpp" #include "raven/Box.hpp" #include "raven/TextInput.hpp" @@ -11,17 +12,65 @@ #include "raven/DocumentLayout.hpp" #include "src/player/OpusFile.hpp" #include "src/player/Player.hpp" +#include "src/player/SongLibrary.hpp" #include -static void populate_song_list(std::filesystem::path songs_directory, std::shared_ptr song_list_widget, Player *player) { +Raven::RGB accent_color = { 0xF7A99E }; +Raven::RGB background_color = { 0xfff9f4 }; + +Raven::GenericStyle background_color_style { + NULL, + Raven::unused, + accent_color, + Raven::unused, + Raven::unused, + 0.0, + true, + false +}; + +Raven::GenericStyle main_view_style { + NULL, + Raven::unused, + background_color, + Raven::unused, + Raven::unused, + 12.0, + true, + false +}; + +Raven::GenericStyle song_button_style { + pango_font_description_from_string("sans-serif"), + Raven::black1, + background_color, + accent_color.factor(1.1), + accent_color.factor(1.2), + 6.0, + true, + true +}; + +Raven::GenericStyle artist_button_style { + pango_font_description_from_string("sans-serif"), + Raven::black1, + accent_color, + accent_color.factor(1.1), + accent_color.factor(1.2), + 6.0, + true, + true +}; + +static void populate_song_list(std::shared_ptr song_list_widget, Player *player, SongLibrary *song_library, std::string target_artist) { song_list_widget->clear_children(); - for (const auto &entry : std::filesystem::directory_iterator(songs_directory)) { - std::string filename = entry.path().filename(); - std::string path_string = entry.path().string(); - auto button = song_list_widget->add(filename); - button->on_click = [player, path_string]() { + + for (const auto &entry : song_library->songs_by_artist().at(target_artist)) { + auto button = song_list_widget->add(entry->title); + button->set_style(&song_button_style); + button->on_click = [player, entry]() { auto file = std::make_shared(); - file->open(path_string); + file->open(entry->absolute_path); player->set_file(file); }; } @@ -30,23 +79,47 @@ static void populate_song_list(std::filesystem::path songs_directory, std::share void AppWidget::on_init() { /* init */ { - auto layout = set_layout(Raven::Direction::Vertical); + auto layout = set_layout(Raven::Direction::Horizontal); layout->set_spacing(8); - layout->set_margin(12); + layout->set_margin(16); + + set_style(&background_color_style); + } + + /* sidebar */ + { + m_sidebar = add(); + m_sidebar->set_style(&background_color_style); + m_sidebar->rect().set_min_width(250.0); + auto target = m_sidebar->make_target(); + target->set_style(&background_color_style); + auto layout = target->set_layout(Raven::Direction::Vertical); + layout->set_spacing(6.0); + layout->set_inherit_secondary_dimension(true); } /* song list */ { m_song_list = add(); m_song_list->set_grows(true); + m_song_list->set_style(&main_view_style); auto target = m_song_list->make_target(); auto layout = target->set_layout(Raven::Direction::Vertical); - layout->set_spacing(4.0); + layout->set_spacing(6.0); + layout->set_margin(10.0); layout->set_inherit_secondary_dimension(true); } - populate_song_list("/home/hippoz/music", m_song_list->target(), m_player); + populate_song_list(m_song_list->target(), m_player, m_song_library, "EDEN"); + for (const auto &it : m_song_library->songs_by_artist()) { + auto button = m_sidebar->target()->add(it.first); + button->set_style(&artist_button_style); + button->on_click = [this, it]() { + populate_song_list(m_song_list->target(), m_player, m_song_library, it.first); + }; + } reflow(); set_did_init(true); -} \ No newline at end of file +} + diff --git a/src/ui/AppWidget.hpp b/src/ui/AppWidget.hpp index eeeb689..3174402 100644 --- a/src/ui/AppWidget.hpp +++ b/src/ui/AppWidget.hpp @@ -4,16 +4,20 @@ #include "raven/Widget.hpp" #include "raven/TextInput.hpp" #include "src/player/Player.hpp" +#include "src/player/SongLibrary.hpp" class AppWidget : public Raven::Widget { public: - AppWidget(Player *player) + AppWidget(Player *player, SongLibrary *library) : Raven::Widget() - , m_player(player) {} + , m_player(player) + , m_song_library(library) {} protected: void on_init() override; private: std::shared_ptr m_song_list; + std::shared_ptr m_sidebar; Player *m_player; + SongLibrary *m_song_library; };