-
Notifications
You must be signed in to change notification settings - Fork 111
Media Controls QuickView #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| #pragma once | ||
|
|
||
| #include <QtCore/QVariant> | ||
| #include <QtWidgets/QApplication> | ||
| #include <QtWidgets/QComboBox> | ||
| #include <QtWidgets/QDial> | ||
| #include <QtWidgets/QHBoxLayout> | ||
| #include <QtWidgets/QLabel> | ||
| #include <QtWidgets/QProgressBar> | ||
| #include <QtWidgets/QPushButton> | ||
| #include <QtWidgets/QSpacerItem> | ||
| #include <QtWidgets/QVBoxLayout> | ||
| #include <QtWidgets/QWidget> | ||
| #include <QFrame> | ||
| #include "app/quick_views/quick_view.hpp" | ||
| #include "AAHandler.hpp" | ||
|
|
||
| class Arbiter; | ||
| class MediaWidget; | ||
|
|
||
| class MediaQuickView : public QFrame, public QuickView { | ||
| Q_OBJECT | ||
|
|
||
| public: | ||
| MediaQuickView(Arbiter &arbiter); | ||
| QWidget *centralwidget; | ||
| QHBoxLayout *horizontalLayout; | ||
| QLabel *album_art_label; | ||
| QWidget *widget_2; | ||
| QVBoxLayout *verticalLayout_2; | ||
| QWidget *widget_3; | ||
| QHBoxLayout *horizontalLayout_3; | ||
| QSpacerItem *horizontalSpacer_5; | ||
| QPushButton *prev_button; | ||
| QPushButton *play_button; | ||
| QPushButton *next_button; | ||
| QSpacerItem *horizontalSpacer_6; | ||
| QLabel *track_progress_label; | ||
| QLabel *title_label; | ||
| QProgressBar *progressBar; | ||
| MediaWidget *mediaWidget; | ||
|
|
||
| void init() override; | ||
|
|
||
| }; | ||
|
|
||
| class MediaWidget : public QWidget | ||
| { | ||
| Q_OBJECT | ||
|
|
||
| public: | ||
| MediaWidget(Arbiter &arbiter, QWidget *parent = 0); | ||
| void clear(); | ||
|
|
||
| public slots: | ||
| void updateMetadata(const aasdk::proto::messages::MediaInfoChannelMetadataData& metadata); | ||
| void updatePlayback(const aasdk::proto::messages::MediaInfoChannelPlaybackData& playback); | ||
|
|
||
| protected: | ||
| void paintEvent(QPaintEvent *event) override; | ||
| void resizeEvent(QResizeEvent *event) override; | ||
| void mousePressEvent(QMouseEvent *event) override; | ||
| private: | ||
| Arbiter &arbiter; | ||
| aasdk::proto::messages::MediaInfoChannelMetadataData metadata_; | ||
| aasdk::proto::messages::MediaInfoChannelPlaybackData playback_; | ||
| bool connected = false; | ||
| QPoint title_pos; | ||
| bool scroll_text_left = true; | ||
| QTimer *scroll_timer; | ||
| QRect play_rect; | ||
| QRect prev_rect; | ||
| QRect next_rect; | ||
| }; | ||
|
|
||
|
|
||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| #include <QFrame> | ||
| #include <QtCore/QVariant> | ||
| #include <QtWidgets/QApplication> | ||
| #include <QtWidgets/QComboBox> | ||
| #include <QtWidgets/QDial> | ||
| #include <QtWidgets/QHBoxLayout> | ||
| #include <QtWidgets/QLabel> | ||
| #include <QtWidgets/QPushButton> | ||
| #include <QtWidgets/QSpacerItem> | ||
| #include <QtWidgets/QVBoxLayout> | ||
| #include <QtWidgets/QWidget> | ||
| #include <QDebug> | ||
|
|
||
| #include "app/arbiter.hpp" | ||
| #include "app/widgets/dialog.hpp" | ||
| #include "app/quick_views/media.hpp" | ||
| #include "app/utilities/icon_engine.hpp" | ||
|
|
||
| MediaQuickView::MediaQuickView(Arbiter &arbiter) | ||
| : QFrame() | ||
| , QuickView(arbiter, "Media", this) | ||
| { | ||
|
|
||
| } | ||
|
|
||
| void MediaQuickView::init(){ | ||
|
|
||
| AAHandler *aa_handler = arbiter.android_auto().handler; | ||
| mediaWidget = new MediaWidget(this->arbiter,this); | ||
|
|
||
| horizontalLayout = new QHBoxLayout(this); | ||
| horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout")); | ||
| horizontalLayout->setContentsMargins(0, 0, 0, 0); | ||
| mediaWidget = new MediaWidget(this->arbiter, this); | ||
| horizontalLayout->addWidget(mediaWidget); | ||
|
|
||
| connect(aa_handler, &AAHandler::aa_media_playback_update, [this](const aasdk::proto::messages::MediaInfoChannelPlaybackData& playback){ | ||
| mediaWidget->updatePlayback(playback); | ||
| }); | ||
|
|
||
| connect(aa_handler, &AAHandler::aa_media_metadata_update, [this](const aasdk::proto::messages::MediaInfoChannelMetadataData& metadata){ | ||
| mediaWidget->updateMetadata(metadata); | ||
| }); | ||
|
|
||
| } | ||
|
|
||
| MediaWidget::MediaWidget(Arbiter &arbiter, QWidget *parent) | ||
| : QWidget(parent) | ||
| ,arbiter(arbiter) | ||
| { | ||
|
|
||
| scroll_timer = new QTimer(this); | ||
| scroll_timer->start(33);//30fps | ||
| connect(scroll_timer, &QTimer::timeout, [this](){ | ||
|
|
||
| if(metadata_.has_track_name() && metadata_.has_artist_name()){ | ||
| QString track_info(QString::fromStdString(metadata_.artist_name()) + | ||
| " - " + | ||
| QString::fromStdString(metadata_.track_name())); | ||
| QFont myFont("arial", 10); | ||
| QFontMetrics fm(myFont); | ||
| int text_width=fm.width(track_info); | ||
|
|
||
| int text_rect_width = (width() / 2) - (height() / 2 + (height() * 2)); | ||
|
|
||
| if(text_width > text_rect_width){ | ||
|
|
||
| if(scroll_text_left){ | ||
| title_pos.setX(title_pos.x() - 1); | ||
| if((title_pos.x() + text_width) < height() + text_rect_width){ | ||
| scroll_text_left = false; | ||
| } | ||
|
|
||
| } else{ | ||
| title_pos.setX(title_pos.x() + 1); | ||
| if(title_pos.x() > height()){ | ||
| scroll_text_left = true; | ||
| } | ||
| } | ||
|
|
||
| } | ||
|
|
||
| } | ||
|
|
||
| this->update(); | ||
|
|
||
| }); | ||
|
|
||
| connect(&arbiter, &Arbiter::openauto_connection_changed, [this](bool connected){ | ||
|
|
||
| this->connected = connected; | ||
|
|
||
| }); | ||
|
|
||
| } | ||
|
|
||
| void MediaWidget::paintEvent(QPaintEvent *event) | ||
| { | ||
|
|
||
| // Setup | ||
| QPainter painter(this); | ||
| painter.setRenderHint(QPainter::HighQualityAntialiasing); | ||
| if(arbiter.theme().mode == Session::Theme::Mode::Dark){ | ||
| painter.setBackground(QBrush(QColor(48,48,48))); | ||
| painter.setPen(QPen(Qt::white,1)); | ||
| } else { | ||
| painter.setBackground(QBrush(QColor(250,250,250))); | ||
| painter.setPen(QPen(Qt::black,1)); | ||
| } | ||
|
|
||
| painter.eraseRect(0,0,width(),height()); | ||
|
|
||
| /*** ALBUM ART ***/ | ||
| if(this->metadata_.has_album_art() && connected){ | ||
|
|
||
| QImage art; | ||
| art.loadFromData(QByteArray::fromStdString(metadata_.album_art())); | ||
| QSize size = QSize(height(), height()); | ||
| auto scaled = art.scaled(size, Qt::IgnoreAspectRatio, Qt::FastTransformation); | ||
| painter.drawImage(QPoint(0,0),scaled); | ||
|
|
||
| } | ||
|
|
||
| /*** TRACK TITLE ***/ | ||
| int text_rect_width = (width() / 2) - (height() / 2 + (height() * 2)); | ||
| if(metadata_.has_track_name() && metadata_.has_artist_name() && connected){ | ||
| QString track_info(QString::fromStdString(metadata_.artist_name()) + | ||
| " - " + | ||
| QString::fromStdString(metadata_.track_name())); | ||
| QFont myFont("arial", 10); | ||
| painter.setFont(myFont); | ||
| painter.setClipRect(height(),0, text_rect_width ,height()); | ||
| painter.drawText(title_pos, track_info); | ||
| painter.setClipping(false); | ||
| } | ||
|
|
||
| /*** PREV BUTTON ***/ | ||
| QIcon prev_icon(new IconEngine(this->arbiter, QString("://icons/skip_previous.svg"), true)); | ||
| QPixmap prev_pixmap = prev_icon.pixmap(QSize(128,128)); | ||
| painter.drawPixmap(prev_rect,prev_pixmap); | ||
|
|
||
| /*** PLAY/PAUSE BUTTON ***/ | ||
| if(this->playback_.has_track_progress() && connected && | ||
| metadata_.has_track_length() && metadata_.track_length() != 0){ | ||
|
|
||
| QPen progressBGPen = QPen(arbiter.theme().color(),4); | ||
| painter.setPen(progressBGPen); | ||
| painter.drawArc(play_rect,1440,5760); | ||
| QPen progressPen = QPen(QColor(70,70,70),4); | ||
| painter.setPen(progressPen); | ||
| int step = 5760 / metadata_.track_length(); | ||
| int invVal = metadata_.track_length() - playback_.track_progress(); | ||
| int val = step * invVal; | ||
| painter.drawArc(play_rect,1440,val); | ||
|
|
||
| } else { | ||
| QPen progressPen = QPen(QColor(70,70,70),4); | ||
| painter.setPen(progressPen); | ||
| painter.drawArc(play_rect,1440,5760); | ||
| } | ||
|
|
||
| if(playback_.has_playback_state() && playback_.playback_state() == aasdk::proto::messages::MediaInfoChannelPlaybackData_PlaybackState_PLAY){ | ||
| QIcon icon(new IconEngine(this->arbiter, QString("://icons/pause.svg"), true)); | ||
| QPixmap pause = icon.pixmap(QSize(128,128)); | ||
| painter.drawPixmap(play_rect,pause); | ||
|
|
||
| } else { | ||
| QIcon icon(new IconEngine(this->arbiter, QString("://icons/play.svg"), true)); | ||
| QPixmap play = icon.pixmap(QSize(128,128)); | ||
| painter.drawPixmap(play_rect,play); | ||
| } | ||
|
|
||
| /**** NEXT BUTTON ****/ | ||
| QIcon next_icon(new IconEngine(this->arbiter, QString("://icons/skip_next.svg"), true)); | ||
| QPixmap next_pixmap = next_icon.pixmap(QSize(128,128)); | ||
| painter.drawPixmap(next_rect,next_pixmap); | ||
|
|
||
| } | ||
|
|
||
| void MediaWidget::resizeEvent(QResizeEvent *event) | ||
| { | ||
| prev_rect = QRect((width() / 2) - (height() / 2 + height()), 8,height() - 16,height() - 16); | ||
| play_rect = QRect((width() / 2) - (height() / 2), 8,height() - 16,height() - 16); | ||
| next_rect = QRect((width() / 2) + (height() / 2), 8,height() - 16,height() - 16); | ||
|
|
||
| } | ||
|
|
||
| void MediaWidget::mousePressEvent(QMouseEvent *event) | ||
| { | ||
|
|
||
| if(event->button()==Qt::LeftButton) | ||
| { | ||
|
|
||
| AAHandler *aa_handler = arbiter.android_auto().handler; | ||
| if(prev_rect.contains(event->pos())){ | ||
| aa_handler->injectButtonPress(aasdk::proto::enums::ButtonCode::PREV,openauto::projection::ButtonEventType::RELEASE); | ||
| } | ||
| if(play_rect.contains(event->pos())){ | ||
| aa_handler->injectButtonPress(aasdk::proto::enums::ButtonCode::TOGGLE_PLAY,openauto::projection::ButtonEventType::PRESS); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought I had made a helper method that sent both press and release events for you, but I can't find where that ended up (if it made it in at all), but might be something to double check
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's the AAHandler::injectButtonPressHelper, is that what you're talking about?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmm, no, that's for the event filter stuff. |
||
| aa_handler->injectButtonPress(aasdk::proto::enums::ButtonCode::TOGGLE_PLAY,openauto::projection::ButtonEventType::RELEASE); | ||
| } | ||
| if(next_rect.contains(event->pos())){ | ||
| aa_handler->injectButtonPress(aasdk::proto::enums::ButtonCode::NEXT,openauto::projection::ButtonEventType::RELEASE); | ||
| } | ||
|
|
||
| } | ||
| } | ||
|
|
||
| void MediaWidget::updatePlayback(const aasdk::proto::messages::MediaInfoChannelPlaybackData& playback) | ||
| { | ||
|
|
||
| this->playback_ = playback; | ||
| this->update(); | ||
|
|
||
| } | ||
|
|
||
| void MediaWidget::updateMetadata(const aasdk::proto::messages::MediaInfoChannelMetadataData& metadata) | ||
| { | ||
|
|
||
| title_pos = QPoint(height() + 8, (height() / 2) + 5);//reset | ||
| this->metadata_ = metadata; | ||
| this->update(); | ||
|
|
||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,8 +107,6 @@ QWidget *Dash::control_bar() const | |
| quick_views->setCurrentWidget(quick_view->widget()); | ||
| }); | ||
|
|
||
| layout->addStretch(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so removing this breaks the layout for the other quick views... I'm assuming you were trying to get everything centered on the control bar? The stretch was added here to have everything aligned to the left (to keep it distinct from the close+power buttons) so i think it should stay since it looks like you're dealing with all the painting of the widget yourself, you'll probs need to handle sizing your widget (overriding sizeHint, etc) so the layout essentially knows to give it as much space as possible or you might also be able to play with the layout in
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah partly because I thought it looked better centered, and partly to give more room for the track details before it reverts to scrolling the information. I wasn't really sure why it was in there but I can certainly put it back and use a sizeHint to get the desired look without breaking the layout for other quick views.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rsjudka I've experimented with a couple different methods of changing the size of the media controls to override the default using sizeHints and sizePolicies and different stretch factors but nothing really works properly, it really wants to keep the default size of half the bar. I was thinking I could set it according to the parent size but the parent size changes after initialisation and the sizeHint is constant. The closest I could get was to set a huge width in the sizeHint so it would shrink into the proper width, but even then the height was hard to set right and wouldn't scale properly. None of this felt right either lol. IMO the proper approach would be to add a stretch inside of the quick_views where we want that separation from the power controls. Alternatively I could simply draw the prev, play, and next button at the far right of the area given to the Media Control, but the spacing won't be quite centred. The reason I'd personally prefer the former though is because I'm also working on a separate feature that would allow vehicle plugins to dynamically add quick views, which I think would be useful to plugin developers if they wanted to add, as an example, a HVAC control quick view for their particular vehicle. Having the full area available to developers would be desirable IMO. Let me know which approach you'd prefer to implement though, either one is fairly simple.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh yeah sorry my first recommendation would have been a bit more involved to get things sized for your quick view, but i think adding the ability to add quick views dynamically sounds awesome (and would definitely benefit from having the control bar's layout more adaptable) as to not break the current quick views when this gets merged, could you go ahead and test moving the stretch to the individual quick views?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry to get back to you so late @rsjudka, life's been busy so I haven't had much time to work on this. I did implement and test the changes as stated and it works as intended. I'll try and get to pushing the changes today. I do have the dynamic quick views implemented in another local branch too, which I've been using for my particular application where I inject some HVAC controls from the vehicle's plugin. If you're interested I can merge those into this branch too, or I can create a separate PR, whichever you'd prefer.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no worries :) i think as a separate pr would be great so we can get merged in as soon as youre ready with it! |
||
|
|
||
| auto dialog = new Dialog(this->arbiter, true, this->arbiter.window()); | ||
| dialog->set_title("Power Off"); | ||
| dialog->set_body(this->power_control()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
id prefer if you used
Session::Forge::fontjust to keep everything consistent and could have the size adjust based on a user's scaleThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the topic of scaling - is there going to be an issue with the hard coded constants I see?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do! I'll also be sure to change it so the track information is centered according to the font metrics instead of the 5px offset like I have now. @icecube45 what do you mean by hard coded constants? Besides the font, I'm pretty sure everything is set in relation to the height and width of the widget, except the padding of course. If the user adjusts the scale, the height of the control bar changes because of the power buttons to the right, the MediaWidget draws the icons according to the height of the control bar so it adjusts to the scale automatically. I've attached some screenshots taken with a large and small scale applied. Let me know if I'm missing something else tho!


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was referring to things like the QSize(128,128) or drawArc(play_rect,1440,5760);, but im not too concerned
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i was a bit concerned when i saw that first but it seems like everything is done relative to the widgets size so things should size appropriately (the qsize doesnt matter much here since the painter determines the size and those constants there are just for the arc's angle so all good there)