diff --git a/include/services/dbus/messages.hpp b/include/services/dbus/messages.hpp index 658bb3c..8220ed6 100644 --- a/include/services/dbus/messages.hpp +++ b/include/services/dbus/messages.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "glibmm/variant.h" @@ -13,6 +14,7 @@ struct MprisPlayer2Message { std::string title; std::string artist; std::string artwork_url; + uint32_t length_ms; std::function play_pause; std::function next; diff --git a/include/services/dbus/mpris.hpp b/include/services/dbus/mpris.hpp index d6b9c79..487e211 100644 --- a/include/services/dbus/mpris.hpp +++ b/include/services/dbus/mpris.hpp @@ -7,8 +7,7 @@ class MprisController { public: - - MprisController(); + static std::shared_ptr getInstance(); void toggle_play(); void next_song(); @@ -17,6 +16,8 @@ class MprisController { sigc::signal &signal_mpris_updated(); private: + MprisController(); + Glib::RefPtr m_connection; Glib::RefPtr m_proxy; sigc::signal mprisUpdatedSignal; diff --git a/include/widgets/controlCenter.hpp b/include/widgets/controlCenter.hpp index 201c748..4d7a3f0 100644 --- a/include/widgets/controlCenter.hpp +++ b/include/widgets/controlCenter.hpp @@ -1,12 +1,46 @@ #pragma once +#include #include "components/popover.hpp" #include "gtkmm/box.h" +#include "gtkmm/label.h" +#include "gtkmm/overlay.h" +#include "gtkmm/picture.h" +#include "gtkmm/scale.h" +#include "gtkmm/scrolledwindow.h" +#include "services/dbus/mpris.hpp" class ControlCenter : public Popover { public: ControlCenter(std::string icon, std::string name); private: + std::shared_ptr mprisController = MprisController::getInstance(); + Gtk::Box container; + Gtk::Box spotifyContainer; + + // image as background, artist, title + Gtk::Overlay topContainer; + Gtk::Picture backgroundImage; + Gtk::Box infoContainer; + Gtk::Label artistLabel; + Gtk::Label titleLabel; + + // + Gtk::Box seekBarContainer; + Gtk::Label currentTimeLabel; + Gtk::Scale seekBar; + Gtk::Label totalTimeLabel; + + // playback controls + Gtk::Box bottomContainer; + Gtk::Button previousButton; + Gtk::Button playPauseButton; + Gtk::Button nextButton; + + Gtk::ScrolledWindow imageWrapper; + + + void onSpotifyMprisUpdated(const MprisPlayer2Message &message); }; \ No newline at end of file diff --git a/resources/bar.css b/resources/bar.css index 2539cf9..a79cb94 100644 --- a/resources/bar.css +++ b/resources/bar.css @@ -16,7 +16,6 @@ window { color: #ffffff; padding-left: 4px; padding-right: 4px; - padding-top: 2px; padding-bottom: 2px; font-size: 14px; font-family: var(--text-font); @@ -34,6 +33,63 @@ popover { font-size: 14px; } +.control-center-popover { + padding: 12px; + background-color: rgba(25, 25, 25, 0.95); +} + +.control-center-spotify-container { + border: 1px solid rgba(80, 80, 80, 0.8); + border-radius: 8px; + background: rgba(30, 30, 30, 0.95); +} + +.control-center-spotify-artist-label { + font-size: 14px; + font-weight: 600; + color: #ffffff; + padding: 2px 6px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.45); + /* bold text shadow */ + text-shadow: 0 0 5px #000000aa; +} + +.control-center-spotify-title-label { + font-size: 12px; + color: #cccccc; + padding: 2px 6px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.35); + /* bold text shadow */ + text-shadow: 0 0 5px #000000aa; +} + +.control-center-seek-bar { + min-height: 6px; + min-width: 120px; + margin: 0 6px; +} + +.control-center-seek-bar trough { + background-color: rgba(255, 255, 255, 0.18); + border-radius: 999px; + min-height: 6px; + min-width: 120px; +} + +.control-center-seek-bar highlight { + background-color: rgba(255, 255, 255, 0.65); + border-radius: 999px; +} + +.control-center-seek-bar slider { + background-color: #ffffff; + border-radius: 999px; + min-width: 10px; + min-height: 10px; +} + tooltip { background-color: #222222; color: #ffffff; diff --git a/src/app.cpp b/src/app.cpp index 7487312..fbb62fb 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -12,7 +12,7 @@ App::App() { this->setupServices(); this->hyprlandService = HyprlandService::getInstance(); this->notificationService = std::make_shared(); - this->mprisController = std::make_shared(); + this->mprisController = MprisController::getInstance(); auto notificationController = NotificationController::getInstance(); this->mprisController->signal_mpris_updated().connect( diff --git a/src/services/dbus/mpris.cpp b/src/services/dbus/mpris.cpp index 7b0f73a..2bbfd7d 100644 --- a/src/services/dbus/mpris.cpp +++ b/src/services/dbus/mpris.cpp @@ -7,6 +7,11 @@ #include "giomm/dbusconnection.h" #include "giomm/dbusproxy.h" +std::shared_ptr MprisController::getInstance() { + static std::shared_ptr instance = std::shared_ptr(new MprisController()); + return instance; +} + MprisController::MprisController() { // 1. Connect to the Session Bus Gio::DBus::Connection::get( diff --git a/src/services/notificationController.cpp b/src/services/notificationController.cpp index 26d93f3..5be26e7 100644 --- a/src/services/notificationController.cpp +++ b/src/services/notificationController.cpp @@ -38,6 +38,8 @@ NotificationController::NotificationController() { } void NotificationController::showSpotifyNotification(MprisPlayer2Message mpris) { + return; + for (const auto &monitor : this->activeMonitors) { auto notification = std::make_shared(monitor, mpris); notification->show(); diff --git a/src/widgets/controlCenter.cpp b/src/widgets/controlCenter.cpp index 1bf9a84..60789b3 100644 --- a/src/widgets/controlCenter.cpp +++ b/src/widgets/controlCenter.cpp @@ -1,8 +1,120 @@ #include "widgets/controlCenter.hpp" +#include "services/textureCache.hpp" ControlCenter::ControlCenter(std::string icon, std::string name) : Popover(icon, name) { - this->popover->set_size_request(200, -1); + this->popover->add_css_class("control-center-popover"); + this->container.set_orientation(Gtk::Orientation::VERTICAL); + this->container.set_spacing(0); + this->container.set_margin_top(0); + this->container.set_margin_bottom(0); + this->container.set_margin_start(0); + this->container.set_margin_end(0); + this->container.append(this->spotifyContainer); + set_popover_child(this->container); + this->spotifyContainer.set_orientation(Gtk::Orientation::VERTICAL); + this->spotifyContainer.set_size_request(200, 240); + this->spotifyContainer.set_hexpand(false); // Important: Don't let the main box expand freely + this->spotifyContainer.set_vexpand(false); + this->spotifyContainer.add_css_class("control-center-spotify-container"); + + this->spotifyContainer.append(this->topContainer); + this->spotifyContainer.append(this->seekBarContainer); + this->spotifyContainer.append(this->bottomContainer); + + this->backgroundImage.set_content_fit(Gtk::ContentFit::COVER); + this->backgroundImage.set_can_shrink(true); + + this->imageWrapper.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::NEVER); + this->imageWrapper.set_child(this->backgroundImage); + + this->topContainer.set_child(this->imageWrapper); + + this->topContainer.set_size_request(200, 100); + this->topContainer.set_vexpand(false); + this->topContainer.set_hexpand(true); + + this->infoContainer.set_orientation(Gtk::Orientation::VERTICAL); + this->infoContainer.set_valign(Gtk::Align::END); + this->infoContainer.set_halign(Gtk::Align::START); + this->infoContainer.append(this->artistLabel); + this->infoContainer.append(this->titleLabel); + this->topContainer.add_overlay(this->infoContainer); + + this->artistLabel.set_halign(Gtk::Align::START); + this->titleLabel.set_halign(Gtk::Align::START); + + + this->seekBarContainer.set_orientation(Gtk::Orientation::HORIZONTAL); + this->seekBarContainer.set_vexpand(false); + this->seekBarContainer.set_hexpand(true); + this->seekBarContainer.set_halign(Gtk::Align::CENTER); + this->seekBarContainer.append(this->currentTimeLabel); + this->seekBarContainer.append(this->seekBar); + this->seekBarContainer.append(this->totalTimeLabel); + + this->currentTimeLabel.set_text("0:00"); + this->currentTimeLabel.set_halign(Gtk::Align::START); + this->totalTimeLabel.set_text("0:00"); + this->totalTimeLabel.set_halign(Gtk::Align::END); + this->seekBar.set_range(0, 100); + this->seekBar.set_value(0); + this->seekBar.set_orientation(Gtk::Orientation::HORIZONTAL); + this->seekBar.set_draw_value(false); + this->seekBar.set_size_request(120, -1); + this->seekBar.set_hexpand(true); + this->seekBar.set_halign(Gtk::Align::CENTER); + this->seekBar.add_css_class("control-center-seek-bar"); + + + this->bottomContainer.set_orientation(Gtk::Orientation::HORIZONTAL); + this->bottomContainer.set_vexpand(false); + this->bottomContainer.set_hexpand(false); + this->bottomContainer.set_valign(Gtk::Align::START); + this->bottomContainer.set_homogeneous(true); + this->topContainer.set_vexpand(false); + this->topContainer.set_hexpand(true); + this->bottomContainer.append(this->previousButton); + this->bottomContainer.append(this->playPauseButton); + this->bottomContainer.append(this->nextButton); + + this->previousButton.set_label("\u23EE"); // Previous track symbol + this->previousButton.add_css_class("notification-button"); + this->previousButton.add_css_class("notification-icon-button"); + this->playPauseButton.set_label("\u23EF"); // Play/Pause symbol + this->playPauseButton.add_css_class("notification-button"); + this->playPauseButton.add_css_class("notification-icon-button"); + this->nextButton.set_label("\u23ED"); // Next track symbol + this->nextButton.add_css_class("notification-button"); + this->nextButton.add_css_class("notification-icon-button"); + + this->previousButton.signal_clicked().connect([this]() { + this->mprisController->previous_song(); + }); + this->playPauseButton.signal_clicked().connect([this]() { + this->mprisController->toggle_play(); + }); + this->nextButton.signal_clicked().connect([this]() { + this->mprisController->next_song(); + }); + + this->mprisController->signal_mpris_updated().connect( + sigc::mem_fun(*this, &ControlCenter::onSpotifyMprisUpdated) + ); + + this->artistLabel.set_text("Artist Name"); + this->artistLabel.add_css_class("control-center-spotify-artist-label"); + this->titleLabel.set_text("Song Title"); + this->titleLabel.add_css_class("control-center-spotify-title-label"); } + +void ControlCenter::onSpotifyMprisUpdated(const MprisPlayer2Message &message) { + this->artistLabel.set_text(message.artist); + this->titleLabel.set_text(message.title); + + if (auto texture = TextureCacheService::getInstance()->getTexture(message.artwork_url)) { + this->backgroundImage.set_paintable(texture); + } +} \ No newline at end of file