diff --git a/Telegram/Resources/basic.style b/Telegram/Resources/basic.style index c638b145c..85ed90231 100644 --- a/Telegram/Resources/basic.style +++ b/Telegram/Resources/basic.style @@ -2035,33 +2035,6 @@ botDescSkip: 8px; suppressAll: 0.2; suppressSong: 0.05; -playerHeight: 44px; -playerBg: #e4e9ef; -playerFg: #54748f; -playerTimeFg: #a4afba; -playerLineHeight: 3px; -playerMoverSize: size(2px, 7px); -playerLineActive: #6389a8; -playerLineInactive: #bac7d4; -playerSkip: 8px; -playerNameStyle: textStyle(defaultTextStyle) { - linkFg: #6389a8; - linkFgDown: #6389a8; - linkFlags: semiboldFont; - linkFlagsOver: semiboldFont; -} -playerPlay: sprite(377px, 109px, 19px, 22px); -playerPause: sprite(379px, 131px, 17px, 20px); -playerNext: sprite(374px, 151px, 22px, 14px); -playerPrev: sprite(374px, 165px, 22px, 14px); -playerClose: sprite(361px, 97px, 12px, 12px); -playerFull: sprite(365px, 109px, 12px, 12px); -playerRepeat: sprite(365px, 121px, 12px, 14px); -playerVolume: sprite(352px, 179px, 44px, 12px); -playerInactiveOpacity: 0.8; -playerUnavailableOpacity: 0.3; -playerDuration: 200; - inlineResultsLeft: 11px; inlineResultsSkip: 3px; inlineMediaHeight: 96px; diff --git a/Telegram/Resources/icons/player_close.png b/Telegram/Resources/icons/player_close.png new file mode 100644 index 000000000..d27dd6b70 Binary files /dev/null and b/Telegram/Resources/icons/player_close.png differ diff --git a/Telegram/Resources/icons/player_close@2x.png b/Telegram/Resources/icons/player_close@2x.png new file mode 100644 index 000000000..cf0e108d8 Binary files /dev/null and b/Telegram/Resources/icons/player_close@2x.png differ diff --git a/Telegram/Resources/icons/player_next.png b/Telegram/Resources/icons/player_next.png index 2649ce5d7..cdcf1f3da 100644 Binary files a/Telegram/Resources/icons/player_next.png and b/Telegram/Resources/icons/player_next.png differ diff --git a/Telegram/Resources/icons/player_next@2x.png b/Telegram/Resources/icons/player_next@2x.png index 2c93ed70e..b646b8472 100644 Binary files a/Telegram/Resources/icons/player_next@2x.png and b/Telegram/Resources/icons/player_next@2x.png differ diff --git a/Telegram/Resources/icons/player_panel_next.png b/Telegram/Resources/icons/player_panel_next.png new file mode 100644 index 000000000..2649ce5d7 Binary files /dev/null and b/Telegram/Resources/icons/player_panel_next.png differ diff --git a/Telegram/Resources/icons/player_panel_next@2x.png b/Telegram/Resources/icons/player_panel_next@2x.png new file mode 100644 index 000000000..2c93ed70e Binary files /dev/null and b/Telegram/Resources/icons/player_panel_next@2x.png differ diff --git a/Telegram/Resources/icons/player_panel_pin.png b/Telegram/Resources/icons/player_panel_pin.png new file mode 100644 index 000000000..809ba4327 Binary files /dev/null and b/Telegram/Resources/icons/player_panel_pin.png differ diff --git a/Telegram/Resources/icons/player_panel_pin@2x.png b/Telegram/Resources/icons/player_panel_pin@2x.png new file mode 100644 index 000000000..07e07315c Binary files /dev/null and b/Telegram/Resources/icons/player_panel_pin@2x.png differ diff --git a/Telegram/Resources/icons/player_panel_previous.png b/Telegram/Resources/icons/player_panel_previous.png new file mode 100644 index 000000000..4460c3daf Binary files /dev/null and b/Telegram/Resources/icons/player_panel_previous.png differ diff --git a/Telegram/Resources/icons/player_panel_previous@2x.png b/Telegram/Resources/icons/player_panel_previous@2x.png new file mode 100644 index 000000000..bb31bf531 Binary files /dev/null and b/Telegram/Resources/icons/player_panel_previous@2x.png differ diff --git a/Telegram/Resources/icons/player_previous.png b/Telegram/Resources/icons/player_previous.png index 4460c3daf..cd71dc0ed 100644 Binary files a/Telegram/Resources/icons/player_previous.png and b/Telegram/Resources/icons/player_previous.png differ diff --git a/Telegram/Resources/icons/player_previous@2x.png b/Telegram/Resources/icons/player_previous@2x.png index bb31bf531..3ddcc5ab5 100644 Binary files a/Telegram/Resources/icons/player_previous@2x.png and b/Telegram/Resources/icons/player_previous@2x.png differ diff --git a/Telegram/Resources/icons/title_button_close@2x.png b/Telegram/Resources/icons/title_button_close@2x.png index d88a98c11..f598165f0 100644 Binary files a/Telegram/Resources/icons/title_button_close@2x.png and b/Telegram/Resources/icons/title_button_close@2x.png differ diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index d6a9208b3..48a385a0b 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -400,9 +400,9 @@ namespace { switch (user.type()) { case mtpc_userEmpty: { - auto &d(user.c_userEmpty()); + auto &d = user.c_userEmpty(); - PeerId peer(peerFromUser(d.vid.v)); + auto peer = peerFromUser(d.vid.v); data = App::user(peer); auto canShareThisContact = data->canShareThisContactFast(); wasContact = data->isContact(); @@ -421,10 +421,10 @@ namespace { if (wasContact != data->isContact()) update.flags |= UpdateFlag::UserIsContact; } break; case mtpc_user: { - auto &d(user.c_user()); + auto &d = user.c_user(); minimal = d.is_min(); - PeerId peer(peerFromUser(d.vid.v)); + auto peer = peerFromUser(d.vid.v); data = App::user(peer); auto canShareThisContact = data->canShareThisContactFast(); wasContact = data->isContact(); @@ -1095,11 +1095,11 @@ namespace { } bool checkEntitiesAndViewsUpdate(const MTPDmessage &m) { - PeerId peerId = peerFromMTP(m.vto_id); + auto peerId = peerFromMTP(m.vto_id); if (m.has_from_id() && peerToUser(peerId) == MTP::authedId()) { peerId = peerFromUser(m.vfrom_id); } - if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) { + if (auto existing = App::histItemById(peerToChannel(peerId), m.vid.v)) { auto text = qs(m.vmessage); auto entities = m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText(); existing->setText({ text, entities }); @@ -1951,7 +1951,7 @@ namespace { HistoryItem *histItemById(ChannelId channelId, MsgId itemId) { if (!itemId) return nullptr; - MsgsData *data = fetchMsgsData(channelId, false); + auto data = fetchMsgsData(channelId, false); if (!data) return nullptr; auto i = data->constFind(itemId); diff --git a/Telegram/SourceFiles/boxes/abstractbox.h b/Telegram/SourceFiles/boxes/abstractbox.h index 26520c20d..c8a7a7f2f 100644 --- a/Telegram/SourceFiles/boxes/abstractbox.h +++ b/Telegram/SourceFiles/boxes/abstractbox.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "layerwidget.h" +#include "ui/widgets/shadow.h" class BlueTitleShadow : public TWidget { public: @@ -93,9 +94,9 @@ private: }; -class ScrollableBoxShadow : public PlainShadow { +class ScrollableBoxShadow : public Ui::PlainShadow { public: - ScrollableBoxShadow(QWidget *parent) : PlainShadow(parent, st::boxScrollShadowBg) { + ScrollableBoxShadow(QWidget *parent) : Ui::PlainShadow(parent, st::boxScrollShadowBg) { } }; diff --git a/Telegram/SourceFiles/boxes/stickersetbox.cpp b/Telegram/SourceFiles/boxes/stickersetbox.cpp index 1058ed031..bbd97414c 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.cpp +++ b/Telegram/SourceFiles/boxes/stickersetbox.cpp @@ -1359,25 +1359,25 @@ void StickersBox::setup() { int bottomSkip = st::boxPadding.bottom(); if (_section == Section::Installed) { _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - _topShadow = new PlainShadow(this, st::contactsAboutShadow); + _topShadow.create(this, st::contactsAboutShadow); - _save = new BoxButton(this, lang(lng_settings_save), st::defaultBoxButton); + _save.create(this, lang(lng_settings_save), st::defaultBoxButton); connect(_save, SIGNAL(clicked()), this, SLOT(onSave())); - _cancel = new BoxButton(this, lang(lng_cancel), st::cancelBoxButton); + _cancel.create(this, lang(lng_cancel), st::cancelBoxButton); connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose())); - _bottomShadow = new ScrollableBoxShadow(this); + _bottomShadow.create(this); bottomSkip = st::boxButtonPadding.top() + _save->height() + st::boxButtonPadding.bottom(); } else if (_section == Section::ArchivedPart) { _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - _topShadow = new PlainShadow(this, st::contactsAboutShadow); + _topShadow.create(this, st::contactsAboutShadow); - _save = new BoxButton(this, lang(lng_box_ok), st::defaultBoxButton); + _save.create(this, lang(lng_box_ok), st::defaultBoxButton); connect(_save, SIGNAL(clicked()), this, SLOT(onClose())); } else if (_section == Section::Archived) { _aboutHeight = st::stickersReorderPadding.top() + _about.countHeight(_aboutWidth) + st::stickersReorderPadding.bottom(); - _topShadow = new PlainShadow(this, st::contactsAboutShadow); + _topShadow.create(this, st::contactsAboutShadow); } ItemListBox::init(_inner, bottomSkip, st::boxTitleHeight + _aboutHeight); setMaxHeight(snap(countHeight(), int32(st::sessionsHeight), int32(st::boxMaxListHeight))); diff --git a/Telegram/SourceFiles/boxes/stickersetbox.h b/Telegram/SourceFiles/boxes/stickersetbox.h index 71001d8f9..1f9cc7561 100644 --- a/Telegram/SourceFiles/boxes/stickersetbox.h +++ b/Telegram/SourceFiles/boxes/stickersetbox.h @@ -24,6 +24,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "core/vector_of_moveable.h" class ConfirmBox; +namespace Ui { +class PlainShadow; +} // namespace Ui class StickerSetInner : public ScrolledWidget, public RPCSender, private base::Subscriber { Q_OBJECT @@ -188,7 +191,7 @@ private: ChildWidget _cancel = { nullptr }; OrderedSet _disenableRequests; mtpRequestId _reorderRequest = 0; - ChildWidget _topShadow = { nullptr }; + ChildWidget _topShadow = { nullptr }; ChildWidget _bottomShadow = { nullptr }; QTimer _scrollTimer; diff --git a/Telegram/SourceFiles/core/click_handler.cpp b/Telegram/SourceFiles/core/click_handler.cpp index 82cc2231c..5ce273ea5 100644 --- a/Telegram/SourceFiles/core/click_handler.cpp +++ b/Telegram/SourceFiles/core/click_handler.cpp @@ -50,7 +50,7 @@ bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) { } } if (p) { - _active.makeIfNull(); + _active.createIfNull(); *_active = p; if ((_activeHost = host)) { bool emitClickHandlerActiveChanged = (!_pressed || !*_pressed || *_pressed == *_active); diff --git a/Telegram/SourceFiles/core/click_handler.h b/Telegram/SourceFiles/core/click_handler.h index 61d9f3a56..7fb8796a4 100644 --- a/Telegram/SourceFiles/core/click_handler.h +++ b/Telegram/SourceFiles/core/click_handler.h @@ -93,7 +93,7 @@ public: if (!_active || !*_active) { return; } - _pressed.makeIfNull(); + _pressed.createIfNull(); *_pressed = *_active; if ((_pressedHost = _activeHost)) { _pressedHost->clickHandlerPressedChanged(*_pressed, true); diff --git a/Telegram/SourceFiles/core/stl_subset.h b/Telegram/SourceFiles/core/stl_subset.h index f969ec927..9c7747616 100644 --- a/Telegram/SourceFiles/core/stl_subset.h +++ b/Telegram/SourceFiles/core/stl_subset.h @@ -189,7 +189,10 @@ public: std::swap(_p, other._p); } ~unique_ptr() noexcept { - delete _p; + if (_p) { + delete _p; + _p = nullptr; + } } T &operator*() const { diff --git a/Telegram/SourceFiles/core/utils.h b/Telegram/SourceFiles/core/utils.h index 117410ab0..f1b049480 100644 --- a/Telegram/SourceFiles/core/utils.h +++ b/Telegram/SourceFiles/core/utils.h @@ -450,15 +450,8 @@ public: NeverFreedPointer(const NeverFreedPointer &other) = delete; NeverFreedPointer &operator=(const NeverFreedPointer &other) = delete; - template - void createIfNull(U creator) { - if (isNull()) { - reset(creator()); - } - } - template - void makeIfNull(Args&&... args) { + void createIfNull(Args&&... args) { if (isNull()) { reset(new T(std_::forward(args)...)); } diff --git a/Telegram/SourceFiles/data/data_abstract_structure.cpp b/Telegram/SourceFiles/data/data_abstract_structure.cpp index 8edc20029..849211aee 100644 --- a/Telegram/SourceFiles/data/data_abstract_structure.cpp +++ b/Telegram/SourceFiles/data/data_abstract_structure.cpp @@ -32,7 +32,7 @@ NeverFreedPointer structures; namespace internal { void registerAbstractStructure(AbstractStructure **p) { - structures.makeIfNull(); + structures.createIfNull(); structures->insert(p); } diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index ca9fed4ec..4df21fb2c 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -584,8 +584,11 @@ struct Data { int32 DebugLoggingFlags = 0; + float64 RememberedSongVolume = kDefaultVolume; float64 SongVolume = kDefaultVolume; + base::Observable SongVolumeChanged; float64 VideoVolume = kDefaultVolume; + base::Observable VideoVolumeChanged; // config int32 ChatSizeMax = 200; @@ -694,8 +697,11 @@ DefineVar(Global, bool, ScreenIsLocked); DefineVar(Global, int32, DebugLoggingFlags); +DefineVar(Global, float64, RememberedSongVolume); DefineVar(Global, float64, SongVolume); +DefineRefVar(Global, base::Observable, SongVolumeChanged); DefineVar(Global, float64, VideoVolume); +DefineRefVar(Global, base::Observable, VideoVolumeChanged); // config DefineVar(Global, int32, ChatSizeMax); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index 3a6a67ba2..89f6b2c36 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -252,8 +252,6 @@ bool started(); void start(); void finish(); -constexpr float64 kDefaultVolume = 0.9; - DeclareReadOnlyVar(uint64, LaunchId); DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate); DeclareRefVar(SingleDelayedCall, HandleUnreadCounterUpdate); @@ -273,8 +271,13 @@ DeclareVar(bool, ScreenIsLocked); DeclareVar(int32, DebugLoggingFlags); +constexpr float64 kDefaultVolume = 0.9; + +DeclareVar(float64, RememberedSongVolume); DeclareVar(float64, SongVolume); +DeclareRefVar(base::Observable, SongVolumeChanged); DeclareVar(float64, VideoVolume); +DeclareRefVar(base::Observable, VideoVolumeChanged); // config DeclareVar(int32, ChatSizeMax); diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index d49f6a1f4..ae6e6894d 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -25,9 +25,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "mainwindow.h" #include "localstorage.h" -#include "playerwidget.h" #include "media/media_audio.h" #include "media/media_clip_reader.h" +#include "media/player/media_player_instance.h" #include "boxes/confirmbox.h" #include "boxes/addcontactbox.h" #include "core/click_handler_types.h" @@ -1463,8 +1463,8 @@ bool HistoryDocument::updateStatusText() const { showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); } else { } - if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) { - showPause = true; + if (!showPause && (playing == AudioMsgId(_data, _parent->fullId()))) { + showPause = (Media::Player::exists() && Media::Player::instance()->isSeeking()); } } } diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index c71496b34..6554cbf5b 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -51,7 +51,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "window/top_bar_widget.h" #include "window/chat_background.h" #include "observer_peer.h" -#include "playerwidget.h" #include "core/qthelp_regex.h" namespace { @@ -3124,7 +3123,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : TWidget(parent) _attachDragDocument->hide(); _attachDragPhoto->hide(); - _topShadow.hide(); + _topShadow->hide(); connect(_attachDragDocument, SIGNAL(dropped(const QMimeData*)), this, SLOT(onDocumentDrop(const QMimeData*))); connect(_attachDragPhoto, SIGNAL(dropped(const QMimeData*)), this, SLOT(onPhotoDrop(const QMimeData*))); @@ -4474,7 +4473,7 @@ bool HistoryWidget::canWriteMessage() const { void HistoryWidget::updateControlsVisibility() { if (!_a_show.animating()) { - _topShadow.setVisible(_peer ? true : false); + _topShadow->setVisible(_peer ? true : false); } updateToEndVisibility(); if (!_history || _a_show.animating()) { @@ -4501,15 +4500,15 @@ void HistoryWidget::updateControlsVisibility() { _attachType->hide(); _emojiPan->hide(); if (_pinnedBar) { - _pinnedBar->cancel.hide(); - _pinnedBar->shadow.hide(); + _pinnedBar->cancel->hide(); + _pinnedBar->shadow->hide(); } return; } if (_pinnedBar) { - _pinnedBar->cancel.show(); - _pinnedBar->shadow.show(); + _pinnedBar->cancel->show(); + _pinnedBar->shadow->show(); } if (_firstLoadRequest && !_scroll.isHidden()) { _scroll.hide(); @@ -5431,11 +5430,11 @@ void HistoryWidget::showAnimated(Window::SlideDirection direction, const Window: _cacheUnder = params.oldContentCache; show(); - _topShadow.setVisible(params.withTopBarShadow ? false : true); + _topShadow->setVisible(params.withTopBarShadow ? false : true); _historyToEnd->finishAnimation(); _cacheOver = App::main()->grabForShowAnimation(params); App::main()->topBar()->startAnim(); - _topShadow.setVisible(params.withTopBarShadow ? true : false); + _topShadow->setVisible(params.withTopBarShadow ? true : false); _scroll.hide(); _kbScroll.hide(); @@ -5458,8 +5457,8 @@ void HistoryWidget::showAnimated(Window::SlideDirection direction, const Window: _joinChannel.hide(); _muteUnmute.hide(); if (_pinnedBar) { - _pinnedBar->shadow.hide(); - _pinnedBar->cancel.hide(); + _pinnedBar->shadow->hide(); + _pinnedBar->cancel->hide(); } int delta = st::slideShift; @@ -5484,7 +5483,7 @@ void HistoryWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; if (dt >= 1) { _a_show.stop(); - _topShadow.setVisible(_peer ? true : false); + _topShadow->setVisible(_peer ? true : false); _historyToEnd->finishAnimation(); a_coordUnder.finish(); @@ -5525,7 +5524,7 @@ void HistoryWidget::doneShow() { void HistoryWidget::animStop() { if (!_a_show.animating()) return; _a_show.stop(); - _topShadow.setVisible(_peer ? true : false); + _topShadow->setVisible(_peer ? true : false); _historyToEnd->finishAnimation(); } @@ -6916,6 +6915,17 @@ void HistoryWidget::peerMessagesUpdated() { if (_list) peerMessagesUpdated(_peer->id); } +void HistoryWidget::grapWithoutTopBarShadow() { + grabStart(); + _topShadow->hide(); +} + +void HistoryWidget::grabFinish() { + _inGrab = false; + resizeEvent(0); + _topShadow->show(); +} + bool HistoryWidget::isItemVisible(HistoryItem *item) { if (isHidden() || _a_show.animating() || !_list) { return false; @@ -6993,8 +7003,8 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) { _reportSpamPanel.move(0, st::replyHeight); _fieldAutocomplete->setBoundings(_scroll.geometry()); } - _pinnedBar->cancel.move(width() - _pinnedBar->cancel.width(), 0); - _pinnedBar->shadow.setGeometry(0, st::replyHeight, width(), st::lineWidth); + _pinnedBar->cancel->move(width() - _pinnedBar->cancel->width(), 0); + _pinnedBar->shadow->setGeometry(0, st::replyHeight, width(), st::lineWidth); } else if (_scroll.y() != 0) { _scroll.move(0, 0); _reportSpamPanel.move(0, 0); @@ -7029,8 +7039,8 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) { break; } - _topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth); - _topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0); + _topShadow->resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth); + _topShadow->moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0); } void HistoryWidget::itemRemoved(HistoryItem *item) { @@ -7532,7 +7542,6 @@ void HistoryWidget::onInlineResultSend(InlineBots::Result *result, UserData *bot HistoryWidget::PinnedBar::PinnedBar(MsgId msgId, HistoryWidget *parent) : msgId(msgId) -, msg(0) , cancel(parent, st::replyCancel) , shadow(parent, st::shadowColor) { } @@ -7582,15 +7591,15 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() { if (!_pinnedBar) { _pinnedBar = new PinnedBar(pinnedMsgId, this); if (_a_show.animating()) { - _pinnedBar->cancel.hide(); - _pinnedBar->shadow.hide(); + _pinnedBar->cancel->hide(); + _pinnedBar->shadow->hide(); } else { - _pinnedBar->cancel.show(); - _pinnedBar->shadow.show(); + _pinnedBar->cancel->show(); + _pinnedBar->shadow->show(); } - connect(&_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide())); + connect(_pinnedBar->cancel, SIGNAL(clicked()), this, SLOT(onPinnedHide())); _reportSpamPanel.raise(); - _topShadow.raise(); + _topShadow->raise(); if (_membersDropdown) { _membersDropdown->raise(); } @@ -8676,11 +8685,11 @@ void HistoryWidget::drawPinnedBar(Painter &p) { p.drawText(left, st::msgReplyPadding.top() + st::msgServiceNameFont->ascent, lang(lng_pinned_message)); p.setPen((((_pinnedBar->msg->toHistoryMessage() && _pinnedBar->msg->toHistoryMessage()->emptyText()) || _pinnedBar->msg->serviceMsg()) ? st::msgInDateFg : st::msgColor)->p); - _pinnedBar->text.drawElided(p, left, st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - left - _pinnedBar->cancel.width() - st::msgReplyPadding.right()); + _pinnedBar->text.drawElided(p, left, st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right()); } else { p.setFont(st::msgDateFont); p.setPen(st::msgInDateFg); - p.drawText(left, st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(lng_profile_loading), width() - left - _pinnedBar->cancel.width() - st::msgReplyPadding.right())); + p.drawText(left, st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->elided(lang(lng_profile_loading), width() - left - _pinnedBar->cancel->width() - st::msgReplyPadding.right())); } } @@ -8715,7 +8724,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { } QRect fill(0, 0, width(), App::main()->height()); - int fromy = (hasTopBar ? (-st::topBarHeight) : 0), x = 0, y = 0; + int fromy = App::main()->backgroundFromY(), x = 0, y = 0; QPixmap cached = App::main()->cachedBackground(fill, x, y); if (cached.isNull()) { auto &pix = Window::chatBackground()->image(); @@ -8751,7 +8760,7 @@ void HistoryWidget::paintEvent(QPaintEvent *e) { if (_recording) drawRecording(p); } } - if (_pinnedBar && !_pinnedBar->cancel.isHidden()) { + if (_pinnedBar && !_pinnedBar->cancel->isHidden()) { drawPinnedBar(p); } if (_scroll.isHidden()) { diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index af7d4e4d5..3e8c98f06 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -38,6 +38,7 @@ class Result; namespace Ui { class HistoryDownButton; class InnerDropdown; +class PlainShadow; } // namespace Ui class Dropdown; @@ -530,7 +531,6 @@ class HistoryWidget : public TWidget, public RPCSender, private base::Subscriber Q_OBJECT public: - HistoryWidget(QWidget *parent); void start(); @@ -701,15 +701,8 @@ public: _inGrab = true; resizeEvent(0); } - void grapWithoutTopBarShadow() { - grabStart(); - _topShadow.hide(); - } - void grabFinish() override { - _inGrab = false; - resizeEvent(0); - _topShadow.show(); - } + void grapWithoutTopBarShadow(); + void grabFinish() override; bool isItemVisible(HistoryItem *item); @@ -902,8 +895,8 @@ private: MsgId msgId = 0; HistoryItem *msg = nullptr; Text text; - IconedButton cancel; - PlainShadow shadow; + ChildWidget cancel; + ChildWidget shadow; }; PinnedBar *_pinnedBar = nullptr; void updatePinnedBar(bool force = false); @@ -1163,7 +1156,7 @@ private: bool _saveDraftText = false; QTimer _saveDraftTimer, _saveCloudDraftTimer; - PlainShadow _topShadow; + ChildWidget _topShadow; bool _inGrab = false; }; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 1cf2b6259..09ffe49e9 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -24,13 +24,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_overview.h" #include "styles/style_history.h" #include "inline_bots/inline_bot_result.h" +#include "media/media_audio.h" #include "media/media_clip_reader.h" #include "media/player/media_player_instance.h" #include "history/history_location_manager.h" #include "localstorage.h" #include "mainwidget.h" #include "lang.h" -#include "playerwidget.h" namespace InlineBots { namespace Layout { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp index bceb885b1..3a530885c 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp @@ -209,7 +209,7 @@ const DocumentItems *documentItems() { namespace internal { void regDocumentItem(DocumentData *document, ItemBase *item) { - documentItemsMap.makeIfNull(); + documentItemsMap.createIfNull(); (*documentItemsMap)[document].insert(item); } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp index 482b20a37..b2825281c 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp @@ -232,7 +232,7 @@ bool Result::onChoose(Layout::ItemBase *layout) { } else if (_document->loading()) { _document->cancel(); } else { - DocumentOpenClickHandler::doOpen(_document, ActionOnLoadNone); + DocumentOpenClickHandler::doOpen(_document, nullptr, ActionOnLoadNone); } return false; } diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index b7b0f6342..87f272096 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -27,7 +27,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "fileuploader.h" #include "mainwindow.h" #include "ui/filedialog.h" -#include "playerwidget.h" #include "boxes/addcontactbox.h" #include "boxes/confirmbox.h" #include "media/media_audio.h" diff --git a/Telegram/SourceFiles/localimageloader.cpp b/Telegram/SourceFiles/localimageloader.cpp index 3e15bf9cd..c3a36bd13 100644 --- a/Telegram/SourceFiles/localimageloader.cpp +++ b/Telegram/SourceFiles/localimageloader.cpp @@ -175,29 +175,22 @@ void TaskQueueWorker::onTaskAdded() { FileLoadTask::FileLoadTask(const QString &filepath, PrepareMediaType type, const FileLoadTo &to, FileLoadForceConfirmType confirm) : _id(rand_value()) , _to(to) , _filepath(filepath) -, _duration(0) , _type(type) -, _confirm(confirm) -, _result(0) { +, _confirm(confirm) { } FileLoadTask::FileLoadTask(const QByteArray &content, PrepareMediaType type, const FileLoadTo &to) : _id(rand_value()) , _to(to) , _content(content) -, _duration(0) -, _type(type) -, _confirm(FileLoadNoForceConfirm) -, _result(0) { +, _type(type) { } FileLoadTask::FileLoadTask(const QImage &image, PrepareMediaType type, const FileLoadTo &to, FileLoadForceConfirmType confirm, const QString &originalText) : _id(rand_value()) , _to(to) , _image(image) -, _duration(0) , _type(type) , _confirm(confirm) -, _originalText(originalText) -, _result(0) { +, _originalText(originalText) { } FileLoadTask::FileLoadTask(const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to) : _id(rand_value()) @@ -205,9 +198,7 @@ FileLoadTask::FileLoadTask(const QByteArray &voice, int32 duration, const VoiceW , _content(voice) , _duration(duration) , _waveform(waveform) -, _type(PrepareAudio) -, _confirm(FileLoadNoForceConfirm) -, _result(0) { +, _type(PrepareAudio) { } void FileLoadTask::process() { diff --git a/Telegram/SourceFiles/localimageloader.h b/Telegram/SourceFiles/localimageloader.h index 44734003a..e531d4021 100644 --- a/Telegram/SourceFiles/localimageloader.h +++ b/Telegram/SourceFiles/localimageloader.h @@ -263,10 +263,10 @@ protected: QString _filepath; QImage _image; QByteArray _content; - int32 _duration; + int32 _duration = 0; VoiceWaveform _waveform; PrepareMediaType _type; - FileLoadForceConfirmType _confirm; + FileLoadForceConfirmType _confirm = FileLoadNoForceConfirm; QString _originalText; FileLoadResultPtr _result; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index b265b3d7b..331e561ce 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "styles/style_dialogs.h" #include "ui/buttons/peer_avatar_button.h" #include "ui/buttons/round_button.h" +#include "ui/widgets/shadow.h" #include "window/section_memento.h" #include "window/section_widget.h" #include "window/top_bar_widget.h" @@ -34,7 +35,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "dialogswidget.h" #include "historywidget.h" #include "overviewwidget.h" -#include "playerwidget.h" #include "lang.h" #include "boxes/addcontactbox.h" #include "fileuploader.h" @@ -50,11 +50,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "localstorage.h" #include "shortcuts.h" #include "media/media_audio.h" +#include "media/player/media_player_panel.h" #include "media/player/media_player_widget.h" +#include "media/player/media_player_volume_controller.h" #include "media/player/media_player_instance.h" #include "core/qthelp_regex.h" #include "core/qthelp_url.h" #include "window/chat_background.h" +#include "window/player_wrap_widget.h" StackItemSection::StackItemSection(std_::unique_ptr &&memento) : StackItem(nullptr) , _memento(std_::move(memento)) { @@ -63,15 +66,12 @@ StackItemSection::StackItemSection(std_::unique_ptr &&me StackItemSection::~StackItemSection() { } -#include "boxes/confirmphonebox.h" - MainWidget::MainWidget(MainWindow *window) : TWidget(window) , _a_show(animation(this, &MainWidget::step_show)) , _dialogsWidth(st::dialogsWidthMin) , _sideShadow(this, st::shadowColor) , _dialogs(this) , _history(this) -, _player(this) , _topBar(this) , _mediaType(this) , _api(new ApiWrap(this)) { @@ -131,8 +131,6 @@ MainWidget::MainWidget(MainWindow *window) : TWidget(window) App::wnd()->getTitle()->updateControlsVisibility(); _topBar->hide(); - _player->hidePlayer(); - orderWidgets(); MTP::setGlobalFailHandler(rpcFail(&MainWidget::updateFail)); @@ -1344,7 +1342,6 @@ void MainWidget::overviewPreloaded(PeerData *peer, const MTPmessages_Messages &r } void MainWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { - if (!_player->isHidden()) _player->mediaOverviewUpdated(peer, type); if (_overview && (_overview->peer() == peer || _overview->peer()->migrateFrom() == peer)) { _overview->mediaOverviewUpdated(peer, type); @@ -1581,24 +1578,8 @@ void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) { } if (playing == audioId && audioId.type() == AudioMsgId::Type::Song) { - if (!_mediaPlayer && Media::Player::exists()) { - _mediaPlayer.create(this); - updateMediaPlayerPosition(); - orderWidgets(); - Media::Player::instance()->createdNotifier().notify(Media::Player::CreatedEvent(_mediaPlayer), true); - } - - _player->updateState(playing, playbackState); - - if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { - if (!_player->isOpened()) { - _player->openPlayer(); - if (_player->isHidden() && !_a_show.animating()) { - _player->showPlayer(); - _playerHeight = _contentScrollAddToY = _player->height(); - resizeEvent(0); - } - } + if (!_playerPanel && !_player && Media::Player::exists()) { + createPlayer(); } } @@ -1612,18 +1593,62 @@ void MainWidget::handleAudioUpdate(const AudioMsgId &audioId) { } } -void MainWidget::closePlayer() { - if (_player->isOpened()) { - _player->closePlayer(); - if (!_player->isHidden() && !_a_show.animating()) { - _player->hidePlayer(); - _contentScrollAddToY = -_player->height(); - _playerHeight = 0; - resizeEvent(0); +void MainWidget::switchToPanelPlayer() { + _player->slideUp(); + _playerVolume.destroyDelayed(); + if (!_playerPanel) { + _playerPanel.create(this, Media::Player::Panel::Layout::Full); + _playerPanel->setPinCallback([this] { switchToFixedPlayer(); }); + updateMediaPlayerPosition(); + orderWidgets(); + Media::Player::instance()->createdNotifier().notify(Media::Player::PanelEvent(_playerPanel), true); + } +} + +void MainWidget::switchToFixedPlayer() { + _playerPanel.destroyDelayed(); + if (!_player) { + createPlayer(); + } else { + _player->slideDown(); + if (!_playerVolume) { + _playerVolume.create(this); + _player->entity()->volumeWidgetCreated(_playerVolume); + updateMediaPlayerPosition(); } } } +void MainWidget::createPlayer() { + _player.create(this, [this] { playerHeightUpdated(); }); + _player->entity()->setCloseCallback([this] { switchToPanelPlayer(); }); + _playerVolume.create(this); + _player->entity()->volumeWidgetCreated(_playerVolume); + orderWidgets(); + if (_a_show.animating()) { + _player->showFast(); + _player->hide(); + } else { + _player->hideFast(); + _player->slideDown(); + _playerHeight = _contentScrollAddToY = _player->contentHeight(); + updateControlsGeometry(); + } +} + +void MainWidget::playerHeightUpdated() { + auto playerHeight = _player->contentHeight(); + if (playerHeight != _playerHeight) { + _contentScrollAddToY += playerHeight - _playerHeight; + _playerHeight = playerHeight; + updateControlsGeometry(); + } + if (_playerPanel && !_playerHeight && _player->isHidden()) { + _playerVolume.destroyDelayed(); + _player.destroyDelayed(); + } +} + void MainWidget::documentLoadProgress(FileLoader *loader) { if (auto mtpLoader = loader ? loader->mtpLoader() : nullptr) { documentLoadProgress(App::document(mtpLoader->objId())); @@ -1645,13 +1670,6 @@ void MainWidget::documentLoadProgress(DocumentData *document) { App::wnd()->documentUpdated(document); if (!document->loaded() && document->song()) { - if (audioPlayer() && document->loading()) { - AudioMsgId playing; - auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); - if (playing.audio() == document && !_player->isHidden()) { - _player->updateState(playing, playbackState); - } - } if (Media::Player::exists()) { Media::Player::instance()->documentLoadProgress(document); } @@ -2314,6 +2332,9 @@ Window::SectionSlideParams MainWidget::prepareShowAnimation(bool willHaveTopBarS result.withTopBarShadow = false; } + if (_player) { + _player->hideShadow(); + } if (selectingPeer() && Adaptive::OneColumn()) { result.oldContentCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); } else if (_wideSection) { @@ -2329,13 +2350,16 @@ Window::SectionSlideParams MainWidget::prepareShowAnimation(bool willHaveTopBarS if (Adaptive::OneColumn()) { result.oldContentCache = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); } else { - _sideShadow.hide(); + _sideShadow->hide(); result.oldContentCache = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); - _sideShadow.show(); + _sideShadow->show(); } if (_overview) _overview->grabFinish(); _history->grabFinish(); } + if (_player) { + _player->showShadow(); + } return result; } @@ -2432,11 +2456,16 @@ void MainWidget::showBackFromStack() { void MainWidget::orderWidgets() { _topBar->raise(); - _player->raise(); _dialogs->raise(); + if (_player) { + _player->raise(); + } + if (_playerVolume) { + _playerVolume->raise(); + } _mediaType->raise(); - _sideShadow.raise(); - if (_mediaPlayer) _mediaPlayer->raise(); + _sideShadow->raise(); + if (_playerPanel) _playerPanel->raise(); if (_hider) _hider->raise(); } @@ -2450,12 +2479,18 @@ QRect MainWidget::historyRect() const { QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { _topBar->stopAnim(); QPixmap result; + if (_player) { + _player->hideShadow(); + } if (Adaptive::OneColumn()) { result = myGrab(this, QRect(0, _playerHeight, _dialogsWidth, height() - _playerHeight)); } else { - _sideShadow.hide(); + _sideShadow->hide(); result = myGrab(this, QRect(_dialogsWidth, _playerHeight, width() - _dialogsWidth, height() - _playerHeight)); - _sideShadow.show(); + _sideShadow->show(); + } + if (_player) { + _player->showShadow(); } return result; } @@ -2602,11 +2637,11 @@ void MainWidget::hideAll() { if (_overview) { _overview->hide(); } - _sideShadow.hide(); + _sideShadow->hide(); _topBar->hide(); _mediaType->hide(); - if (_player->isOpened() && !_player->isHidden()) { - _player->hidePlayer(); + if (_player) { + _player->hide(); _playerHeight = 0; } } @@ -2617,7 +2652,7 @@ void MainWidget::showAll() { Ui::showLayer(new InformBox(lang(lng_signin_password_removed))); } if (Adaptive::OneColumn()) { - _sideShadow.hide(); + _sideShadow->hide(); if (_hider) { _hider->hide(); if (!_forwardConfirm && _hider->wasOffered()) { @@ -2654,7 +2689,7 @@ void MainWidget::showAll() { } } } else { - _sideShadow.show(); + _sideShadow->show(); if (_hider) { _hider->show(); if (_forwardConfirm) { @@ -2677,9 +2712,9 @@ void MainWidget::showAll() { _topBar->show(); } } - if (_player->isOpened() && _player->isHidden()) { - _player->showPlayer(); - _playerHeight = _player->height(); + if (_player) { + _player->show(); + _playerHeight = _player->contentHeight(); } resizeEvent(0); @@ -2695,30 +2730,35 @@ inline int chatsListWidth(int windowWidth) { } // namespace void MainWidget::resizeEvent(QResizeEvent *e) { - int32 tbh = _topBar->isHidden() ? 0 : st::topBarHeight; - updateMediaPlayerPosition(); + updateControlsGeometry(); +} + +void MainWidget::updateControlsGeometry() { + auto tbh = _topBar->isHidden() ? 0 : st::topBarHeight; if (Adaptive::OneColumn()) { _dialogsWidth = width(); - _player->setGeometry(0, 0, _dialogsWidth, _player->height()); + if (_player) { + _player->resizeToWidth(_dialogsWidth); + _player->moveToLeft(0, 0); + } _dialogs->setGeometry(0, _playerHeight, _dialogsWidth, height() - _playerHeight); _topBar->setGeometry(0, _playerHeight, _dialogsWidth, st::topBarHeight); _history->setGeometry(0, _playerHeight + tbh, _dialogsWidth, height() - _playerHeight - tbh); if (_hider) _hider->setGeometry(0, 0, _dialogsWidth, height()); } else { _dialogsWidth = chatsListWidth(width()); - _dialogs->resize(_dialogsWidth, height()); - _dialogs->moveToLeft(0, 0); - _sideShadow.resize(st::lineWidth, height()); - _sideShadow.moveToLeft(_dialogsWidth, 0); - _player->resize(width() - _dialogsWidth, _player->height()); - _player->moveToLeft(_dialogsWidth, 0); - _topBar->resize(width() - _dialogsWidth, st::topBarHeight); - _topBar->moveToLeft(_dialogsWidth, _playerHeight); - _history->resize(width() - _dialogsWidth, height() - _playerHeight - tbh); - _history->moveToLeft(_dialogsWidth, _playerHeight + tbh); + auto sectionWidth = width() - _dialogsWidth; + + _dialogs->setGeometryToLeft(0, 0, _dialogsWidth, height()); + _sideShadow->setGeometryToLeft(_dialogsWidth, 0, st::lineWidth, height()); + if (_player) { + _player->resizeToWidth(sectionWidth); + _player->moveToLeft(_dialogsWidth, 0); + } + _topBar->setGeometryToLeft(_dialogsWidth, _playerHeight, sectionWidth, st::topBarHeight); + _history->setGeometryToLeft(_dialogsWidth, _playerHeight + tbh, sectionWidth, height() - _playerHeight - tbh); if (_hider) { - _hider->resize(width() - _dialogsWidth, height()); - _hider->moveToLeft(_dialogsWidth, 0); + _hider->setGeometryToLeft(_dialogsWidth, 0, sectionWidth, height()); } } _mediaType->moveToLeft(width() - _mediaType->width(), _playerHeight + st::topBarHeight); @@ -2727,12 +2767,18 @@ void MainWidget::resizeEvent(QResizeEvent *e) { _wideSection->setGeometryWithTopMoved(wideSectionGeometry, _contentScrollAddToY); } if (_overview) _overview->setGeometry(_history->geometry()); + updateMediaPlayerPosition(); _contentScrollAddToY = 0; } void MainWidget::updateMediaPlayerPosition() { - if (_mediaPlayer) { - _mediaPlayer->moveToRight(0, 0); + if (_playerPanel) { + _playerPanel->moveToRight(0, 0); + } + if (_playerVolume && _player) { + auto relativePosition = _player->entity()->getPositionForVolumeWidget(); + auto playerMargins = _playerVolume->getMargin(); + _playerVolume->moveToLeft(_player->x() + relativePosition.x() - playerMargins.left(), _player->y() + relativePosition.y() - playerMargins.top()); } } @@ -2745,7 +2791,10 @@ void MainWidget::keyPressEvent(QKeyEvent *e) { void MainWidget::updateAdaptiveLayout() { showAll(); - _sideShadow.setVisible(!Adaptive::OneColumn()); + _sideShadow->setVisible(!Adaptive::OneColumn()); + if (_player) { + _player->updateAdaptiveLayout(); + } } bool MainWidget::needBackButton() { @@ -2807,8 +2856,8 @@ Window::TopBarWidget *MainWidget::topBar() { return _topBar; } -PlayerWidget *MainWidget::player() { - return _player; +int MainWidget::backgroundFromY() const { + return (_topBar->isHidden() ? 0 : (-st::topBarHeight)) - _playerHeight; } void MainWidget::onTopBarClick() { @@ -3486,6 +3535,8 @@ void MainWidget::onSelfParticipantUpdated(ChannelData *channel) { bool MainWidget::contentOverlapped(const QRect &globalRect) { return (_history->contentOverlapped(globalRect) || + (_playerPanel && _playerPanel->overlaps(globalRect)) || + (_playerVolume && _playerVolume->overlaps(globalRect)) || _mediaType->overlaps(globalRect)); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 1a7be86a0..97bd38ebb 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -31,14 +31,18 @@ class Row; namespace Media { namespace Player { class Widget; +class VolumeWidget; +class Panel; } // namespace Player } // namespace Media namespace Ui { class PeerAvatarButton; +class PlainShadow; } // namespace Ui namespace Window { +class PlayerWrapWidget; class TopBarWidget; class SectionMemento; class SectionWidget; @@ -51,7 +55,6 @@ class ConfirmBox; class DialogsWidget; class HistoryWidget; class OverviewWidget; -class PlayerWidget; class HistoryHider; class Dropdown; @@ -128,9 +131,7 @@ enum NotifySettingStatus { namespace InlineBots { namespace Layout { - class ItemBase; - } // namespace Layout } // namespace InlineBots @@ -147,8 +148,8 @@ public: QRect getMembersShowAreaGeometry() const; void setMembersShowAreaActive(bool active); Window::TopBarWidget *topBar(); + int backgroundFromY() const; - PlayerWidget *player(); int contentScrollAddToY() const; void animShow(const QPixmap &bgAnimCache, bool back = false); @@ -379,8 +380,6 @@ public: bool isItemVisible(HistoryItem *item); - void closePlayer(); - void documentLoadProgress(DocumentData *document); void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col); @@ -488,6 +487,12 @@ private: void updateAdaptiveLayout(); void handleAudioUpdate(const AudioMsgId &audioId); void updateMediaPlayerPosition(); + void updateControlsGeometry(); + + void createPlayer(); + void switchToPanelPlayer(); + void switchToFixedPlayer(); + void playerHeightUpdated(); void sendReadRequest(PeerData *peer, MsgId upTo); void channelReadDone(PeerData *peer, const MTPBool &result); @@ -581,15 +586,15 @@ private: int _dialogsWidth; - PlainShadow _sideShadow; - + ChildWidget _sideShadow; ChildWidget _dialogs; ChildWidget _history; ChildWidget _wideSection = { nullptr }; ChildWidget _overview = { nullptr }; - ChildWidget _player; ChildWidget _topBar; - ChildWidget _mediaPlayer = { nullptr }; + ChildWidget _player = { nullptr }; + ChildWidget _playerVolume = { nullptr }; + ChildWidget _playerPanel = { nullptr }; ConfirmBox *_forwardConfirm = nullptr; // for single column layout ChildWidget _hider = { nullptr }; std_::vector_of_moveable> _stack; diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index bed02e902..8a6f08d03 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -286,21 +286,21 @@ void AudioPlayer::AudioMsg::clear() { videoPlayId = 0; } -AudioPlayer::AudioPlayer() : _audioCurrent(0), _songCurrent(0), -_fader(new AudioPlayerFader(&_faderThread)), -_loader(new AudioPlayerLoaders(&_loaderThread)) { +AudioPlayer::AudioPlayer() +: _fader(new AudioPlayerFader(&_faderThread)) +, _loader(new AudioPlayerLoaders(&_loaderThread)) { connect(this, SIGNAL(faderOnTimer()), _fader, SLOT(onTimer())); connect(this, SIGNAL(suppressSong()), _fader, SLOT(onSuppressSong())); connect(this, SIGNAL(unsuppressSong()), _fader, SLOT(onUnsuppressSong())); connect(this, SIGNAL(suppressAll()), _fader, SLOT(onSuppressAll())); - connect(this, SIGNAL(songVolumeChanged()), _fader, SLOT(onSongVolumeChanged())); - connect(this, SIGNAL(videoVolumeChanged()), _fader, SLOT(onVideoVolumeChanged())); + subscribe(Global::RefSongVolumeChanged(), [this] { + QMetaObject::invokeMethod(_fader, "onSongVolumeChanged"); + }); + subscribe(Global::RefVideoVolumeChanged(), [this] { + QMetaObject::invokeMethod(_fader, "onVideoVolumeChanged"); + }); connect(this, SIGNAL(loaderOnStart(const AudioMsgId&,qint64)), _loader, SLOT(onStart(const AudioMsgId&,qint64))); connect(this, SIGNAL(loaderOnCancel(const AudioMsgId&)), _loader, SLOT(onCancel(const AudioMsgId&))); - connect(&_faderThread, SIGNAL(started()), _fader, SLOT(onInit())); - connect(&_loaderThread, SIGNAL(started()), _loader, SLOT(onInit())); - connect(&_faderThread, SIGNAL(finished()), _fader, SLOT(deleteLater())); - connect(&_loaderThread, SIGNAL(finished()), _loader, SLOT(deleteLater())); connect(_loader, SIGNAL(needToCheck()), _fader, SLOT(onTimer())); connect(_loader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); connect(_fader, SIGNAL(needToPreload(const AudioMsgId&)), _loader, SLOT(onLoad(const AudioMsgId&))); @@ -309,6 +309,7 @@ _loader(new AudioPlayerLoaders(&_loaderThread)) { connect(_fader, SIGNAL(error(const AudioMsgId&)), this, SLOT(onError(const AudioMsgId&))); connect(this, SIGNAL(stoppedOnError(const AudioMsgId&)), this, SIGNAL(updated(const AudioMsgId&)), Qt::QueuedConnection); connect(this, SIGNAL(updated(const AudioMsgId&)), this, SLOT(onUpdated(const AudioMsgId&))); + _loaderThread.start(); _faderThread.start(); } @@ -446,6 +447,7 @@ bool AudioPlayer::fadedStop(AudioMsgId::Type type, bool *fadedStart) { void AudioPlayer::play(const AudioMsgId &audio, int64 position) { auto type = audio.type(); AudioMsgId stopped; + auto notLoadedYet = false; { QMutexLocker lock(&playerMutex); @@ -479,14 +481,11 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { current->file = audio.audio()->location(true); current->data = audio.audio()->data(); if (current->file.isEmpty() && current->data.isEmpty()) { + notLoadedYet = true; if (audio.type() == AudioMsgId::Type::Song) { setStoppedState(current); - if (!audio.audio()->loading()) { - DocumentOpenClickHandler::doOpen(audio.audio()); - } } else { setStoppedState(current, AudioPlayerStoppedAtError); - onError(audio); } } else { current->playbackState.position = position; @@ -498,7 +497,16 @@ void AudioPlayer::play(const AudioMsgId &audio, int64 position) { } } } - if (stopped) emit updated(stopped); + if (notLoadedYet) { + if (audio.type() == AudioMsgId::Type::Song) { + DocumentOpenClickHandler::doOpen(audio.audio(), App::histItemById(audio.contextId())); + } else { + onError(audio); + } + } + if (stopped) { + emit updated(stopped); + } } void AudioPlayer::initFromVideo(uint64 videoPlayId, std_::unique_ptr &&data, int64 position) { @@ -949,13 +957,15 @@ AudioCapture *audioCapture() { return capture; } -AudioPlayerFader::AudioPlayerFader(QThread *thread) : _timer(this), _pauseFlag(false), _paused(true), -_suppressAll(false), _suppressAllAnim(false), _suppressSong(false), _suppressSongAnim(false), -_suppressAllGain(1., 1.), _suppressSongGain(1., 1.), -_suppressAllStart(0), _suppressSongStart(0) { +AudioPlayerFader::AudioPlayerFader(QThread *thread) : QObject() +, _timer(this) +, _suppressAllGain(1., 1.) +, _suppressSongGain(1., 1.) { moveToThread(thread); _timer.moveToThread(thread); _pauseTimer.moveToThread(thread); + connect(thread, SIGNAL(started()), this, SLOT(onInit())); + connect(thread, SIGNAL(finished()), this, SLOT(deleteLater())); _timer.setSingleShot(true); connect(&_timer, SIGNAL(timeout()), this, SLOT(onTimer())); diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index a0e767b91..f372feb1f 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -55,7 +55,7 @@ struct AudioPlaybackState { int32 frequency = 0; }; -class AudioPlayer : public QObject, public base::Observable { +class AudioPlayer : public QObject, public base::Observable, private base::Subscriber { Q_OBJECT public: @@ -103,9 +103,6 @@ signals: void unsuppressSong(); void suppressAll(); - void songVolumeChanged(); - void videoVolumeChanged(); - private: bool fadedStop(AudioMsgId::Type type, bool *fadedStart = 0); bool updateCurrentStarted(AudioMsgId::Type type, int32 pos = -1); @@ -150,10 +147,10 @@ private: int *currentIndex(AudioMsgId::Type type); const int *currentIndex(AudioMsgId::Type type) const; - int _audioCurrent; + int _audioCurrent = 0; AudioMsg _audioData[AudioSimultaneousLimit]; - int _songCurrent; + int _songCurrent = 0; AudioMsg _songData[AudioSimultaneousLimit]; AudioMsg _videoData; @@ -252,11 +249,17 @@ private: QTimer _timer, _pauseTimer; QMutex _pauseMutex; - bool _pauseFlag, _paused; + bool _pauseFlag = false; + bool _paused = true; - bool _suppressAll, _suppressAllAnim, _suppressSong, _suppressSongAnim, _songVolumeChanged, _videoVolumeChanged; + bool _suppressAll = false; + bool _suppressAllAnim = false; + bool _suppressSong = false; + bool _suppressSongAnim = false; + bool _songVolumeChanged, _videoVolumeChanged; anim::fvalue _suppressAllGain, _suppressSongGain; - uint64 _suppressAllStart, _suppressSongStart; + uint64 _suppressAllStart = 0; + uint64 _suppressSongStart = 0; }; diff --git a/Telegram/SourceFiles/media/media_audio_loaders.cpp b/Telegram/SourceFiles/media/media_audio_loaders.cpp index fed6ed7c6..f9cf6d41d 100644 --- a/Telegram/SourceFiles/media/media_audio_loaders.cpp +++ b/Telegram/SourceFiles/media/media_audio_loaders.cpp @@ -27,6 +27,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _fromVideoNotify(this, "onVideoSoundAdded") { moveToThread(thread); + connect(thread, SIGNAL(started()), this, SLOT(onInit())); + connect(thread, SIGNAL(finished()), this, SLOT(deleteLater())); } void AudioPlayerLoaders::feedFromVideo(VideoSoundPart &&part) { diff --git a/Telegram/SourceFiles/media/player/media_player.style b/Telegram/SourceFiles/media/player/media_player.style index 6ae882faf..75412374f 100644 --- a/Telegram/SourceFiles/media/player/media_player.style +++ b/Telegram/SourceFiles/media/player/media_player.style @@ -33,6 +33,127 @@ MediaPlayerButton { cancelStroke: pixels; } +mediaPlayerActiveFg: #54b5ed; +mediaPlayerInactiveFg: #dfebf2; + +mediaPlayerButton: MediaPlayerButton { + playPosition: point(2px, 0px); + playOuter: size(17px, 15px); + pausePosition: point(1px, 1px); + pauseOuter: size(15px, 15px); + pauseStroke: 5px; + cancelPosition: point(1px, 1px); + cancelOuter: size(15px, 15px); + cancelStroke: 3px; +} +mediaPlayerButtonSize: size(25px, 30px); + +mediaPlayerButtonPosition: point(5px, 10px); +mediaPlayerSkipIconPosition: point(5px, 12px); + +mediaPlayerHeight: 35px; +mediaPlayerPadding: 8px; +mediaPlayerNameTop: 22px; +mediaPlayerPlayLeft: 7px; +mediaPlayerPlaySkip: 1px; +mediaPlayerPlayTop: 0px; +mediaPlayerCloseRight: 0px; + +mediaPlayerName: flatLabel(labelDefFlat) { + maxHeight: 20px; + textFg: windowTextFg; +} +mediaPlayerTime: LabelSimple(defaultLabelSimple) { + textFg: windowSubTextFg; +} + +mediaPlayerRepeatButton: IconButton { + width: 31px; + height: 30px; + + opacity: 1.; + overOpacity: 1.; + + icon: icon { + { "player_repeat", mediaPlayerActiveFg, point(9px, 11px) } + }; + iconPosition: point(0px, 0px); + downIconPosition: point(0px, 0px); + + duration: 0; +} +mediaPlayerRepeatDisabledIcon: icon { + { "player_repeat", #c8c8c8, point(9px, 11px)} +}; +mediaPlayerRepeatInactiveIcon: icon { + { "player_repeat", mediaPlayerInactiveFg, point(9px, 11px)} +}; + +mediaPlayerVolumeIcon0: icon { + { "player_volume0", mediaPlayerActiveFg }, +}; +mediaPlayerVolumeIcon1: icon { + { "player_volume1", mediaPlayerActiveFg }, +}; +mediaPlayerVolumeIcon2: icon { + { "player_volume2", mediaPlayerActiveFg }, +}; +mediaPlayerVolumeIcon3: icon { + { "player_volume3", mediaPlayerActiveFg }, +}; +mediaPlayerVolumeToggle: IconButton { + width: 31px; + height: 30px; + + opacity: 1.; + overOpacity: 1.; + + icon: mediaPlayerVolumeIcon0; + iconPosition: point(8px, 11px); + downIconPosition: point(8px, 11px); + + duration: 0; +} +mediaPlayerVolumeMargin: 10px; +mediaPlayerVolumeSize: size(27px, 100px); + +mediaPlayerPanelPinButton: IconButton(mediaPlayerRepeatButton) { + icon: icon { + { "player_panel_pin", mediaPlayerActiveFg, point(9px, 11px) } + }; +} + +mediaPlayerNextButton: IconButton(mediaPlayerRepeatButton) { + width: 25px; + icon: icon { + { "player_next", mediaPlayerActiveFg, mediaPlayerSkipIconPosition }, + }; +} +mediaPlayerNextDisabledIcon: icon { + { "player_next", mediaPlayerInactiveFg, mediaPlayerSkipIconPosition }, +}; +mediaPlayerPreviousButton: IconButton(mediaPlayerNextButton) { + icon: icon { + { "player_previous", mediaPlayerActiveFg, mediaPlayerSkipIconPosition }, + }; +} +mediaPlayerPreviousDisabledIcon: icon { + { "player_previous", mediaPlayerInactiveFg, mediaPlayerSkipIconPosition }, +}; +mediaPlayerClose: IconButton(mediaPlayerRepeatButton) { + width: 37px; + icon: icon { + { "player_close", #c8c8c8, point(10px, 12px) }, + }; +} +mediaPlayerPlayback: FilledSlider { + fullWidth: 6px; + lineWidth: 2px; + activeFg: mediaPlayerActiveFg; + inactiveFg: mediaPlayerInactiveFg; + duration: 150; +} + mediaPlayerTitleButtonSize: size(titleHeight, titleHeight); mediaPlayerTitleButtonInner: size(25px, 25px); mediaPlayerTitleButtonInnerBg: #49708f; @@ -48,7 +169,8 @@ mediaPlayerTitleButton: MediaPlayerButton { cancelOuter: size(25px, 25px); cancelStroke: 2px; } -mediaPlayerButton: MediaPlayerButton { + +mediaPlayerPanelButton: MediaPlayerButton { playPosition: point(3px, 0px); playOuter: size(22px, 18px); pausePosition: point(1px, 1px); @@ -58,62 +180,40 @@ mediaPlayerButton: MediaPlayerButton { cancelOuter: size(16px, 18px); cancelStroke: 3px; } +mediaPlayerPanelButtonSize: size(32px, 32px); +mediaPlayerPanelButtonPosition: point(8px, 7px); -mediaPlayerMarginLeft: 10px; -mediaPlayerMarginBottom: 10px; -mediaPlayerWidth: 344px; +mediaPlayerPanelMarginLeft: 10px; +mediaPlayerPanelMarginBottom: 10px; +mediaPlayerPanelWidth: 344px; mediaPlayerCoverHeight: 102px; -mediaPlayerActiveFg: #54b5ed; -mediaPlayerInactiveFg: #dfebf2; - -mediaPlayerButtonSize: size(32px, 32px); -mediaPlayerButtonPosition: point(8px, 7px); - -mediaPlayerRepeatButton: IconButton { - width: 31px; - height: 32px; - - opacity: 1.; - overOpacity: 1.; - - icon: icon { - { "player_repeat", mediaPlayerActiveFg, point(9px, 9px)} - }; - iconPosition: point(0px, 0px); - downIconPosition: point(0px, 0px); - - duration: 0; -} -mediaPlayerRepeatDisabledIcon: icon { - { "player_repeat", mediaPlayerInactiveFg, point(9px, 9px)} -}; -mediaPlayerPreviousButton: IconButton(mediaPlayerRepeatButton) { +mediaPlayerPanelNextButton: IconButton(mediaPlayerRepeatButton) { width: 37px; icon: icon { - { "player_previous", mediaPlayerActiveFg, point(10px, 10px) }, + { "player_panel_next", mediaPlayerActiveFg, point(10px, 10px) }, }; } -mediaPlayerPreviousDisabledIcon: icon { - { "player_previous", mediaPlayerInactiveFg, point(10px, 10px) }, +mediaPlayerPanelNextDisabledIcon: icon { + { "player_panel_next", mediaPlayerInactiveFg, point(10px, 10px) }, }; -mediaPlayerNextButton: IconButton(mediaPlayerPreviousButton) { +mediaPlayerPanelPreviousButton: IconButton(mediaPlayerPanelNextButton) { icon: icon { - { "player_next", mediaPlayerActiveFg, point(10px, 10px) }, + { "player_panel_previous", mediaPlayerActiveFg, point(10px, 10px) }, }; } -mediaPlayerNextDisabledIcon: icon { - { "player_next", mediaPlayerInactiveFg, point(10px, 10px) }, +mediaPlayerPanelPreviousDisabledIcon: icon { + { "player_panel_previous", mediaPlayerInactiveFg, point(10px, 10px) }, }; -mediaPlayerPadding: 18px; -mediaPlayerNameTop: 24px; -mediaPlayerPlayLeft: 9px; -mediaPlayerPlaySkip: 7px; -mediaPlayerPlayTop: 58px; -mediaPlayerPlaybackTop: 32px; -mediaPlayerPlaybackPadding: 8px; -mediaPlayerPlayback: MediaSlider { +mediaPlayerPanelPadding: 18px; +mediaPlayerPanelNameTop: 24px; +mediaPlayerPanelPlayLeft: 9px; +mediaPlayerPanelPlaySkip: 7px; +mediaPlayerPanelPlayTop: 58px; +mediaPlayerPanelPlaybackTop: 32px; +mediaPlayerPanelPlaybackPadding: 8px; +mediaPlayerPanelPlayback: MediaSlider { width: 3px; activeFg: mediaPlayerActiveFg; inactiveFg: mediaPlayerInactiveFg; @@ -123,41 +223,8 @@ mediaPlayerPlayback: MediaSlider { duration: 150; } -mediaPlayerName: flatLabel(labelDefFlat) { - maxHeight: 20px; - textFg: windowTextFg; -} -mediaPlayerTime: LabelSimple(defaultLabelSimple) { - textFg: windowSubTextFg; -} - -mediaPlayerVolumeTop: 65px; -mediaPlayerVolumeRight: 51px; -mediaPlayerVolumeWidth: 86px; -mediaPlayerVolumeLength: 64px; - -mediaPlayerVolumeIcon0: icon { - { "player_volume0", mediaPlayerActiveFg }, -}; -mediaPlayerVolumeIcon1: icon { - { "player_volume1", mediaPlayerActiveFg }, -}; -mediaPlayerVolumeIcon2: icon { - { "player_volume2", mediaPlayerActiveFg }, -}; -mediaPlayerVolumeIcon3: icon { - { "player_volume3", mediaPlayerActiveFg }, -}; -mediaPlayerVolumeToggle: IconButton { - width: 18px; - height: 17px; - - opacity: 1.; - overOpacity: 1.; - - icon: mediaPlayerVolumeIcon0; - iconPosition: point(0px, 2px); - downIconPosition: point(0px, 2px); - - duration: 0; -} +mediaPlayerPanelVolumeTop: 65px; +mediaPlayerPanelVolumeSkip: 3px; +mediaPlayerPanelVolumeWidth: 64px; +mediaPlayerPanelVolumeToggleSkip: 0px; +mediaPlayerPanelVolumeToggleTop: 57px; diff --git a/Telegram/SourceFiles/media/player/media_player_cover.cpp b/Telegram/SourceFiles/media/player/media_player_cover.cpp index dd8cd4df4..ba1bbfe3f 100644 --- a/Telegram/SourceFiles/media/player/media_player_cover.cpp +++ b/Telegram/SourceFiles/media/player/media_player_cover.cpp @@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/flatlabel.h" #include "ui/widgets/label_simple.h" +#include "ui/widgets/media_slider.h" #include "ui/buttons/icon_button.h" #include "media/media_audio.h" #include "media/view/media_clip_playback.h" @@ -31,7 +32,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "media/player/media_player_volume_controller.h" #include "styles/style_media_player.h" #include "styles/style_mediaview.h" -#include "shortcuts.h" namespace Media { namespace Player { @@ -42,7 +42,7 @@ class CoverWidget::PlayButton : public Button { public: PlayButton(QWidget *parent); - void setState(State state) { + void setState(PlayButtonLayout::State state) { _layout.setState(state); } void finishTransform() { @@ -58,24 +58,26 @@ private: }; CoverWidget::PlayButton::PlayButton(QWidget *parent) : Button(parent) -, _layout(st::mediaPlayerButton, [this] { update(); }) { - resize(st::mediaPlayerButtonSize); +, _layout(st::mediaPlayerPanelButton, [this] { update(); }) { + resize(st::mediaPlayerPanelButtonSize); setCursor(style::cur_pointer); } void CoverWidget::PlayButton::paintEvent(QPaintEvent *e) { Painter p(this); - p.translate(st::mediaPlayerButtonPosition.x(), st::mediaPlayerButtonPosition.y()); + p.translate(st::mediaPlayerPanelButtonPosition.x(), st::mediaPlayerPanelButtonPosition.y()); _layout.paint(p, st::mediaPlayerActiveFg); } CoverWidget::CoverWidget(QWidget *parent) : TWidget(parent) , _nameLabel(this, st::mediaPlayerName) , _timeLabel(this, st::mediaPlayerTime) -, _playback(this, st::mediaPlayerPlayback) +, _playback(new Ui::MediaSlider(this, st::mediaPlayerPanelPlayback)) , _playPause(this) +, _volumeToggle(this, st::mediaPlayerVolumeToggle) , _volumeController(this) +, _pinPlayer(this, st::mediaPlayerPanelPinButton) , _repeatTrack(this, st::mediaPlayerRepeatButton) { setAttribute(Qt::WA_OpaquePaintEvent); @@ -85,26 +87,34 @@ CoverWidget::CoverWidget(QWidget *parent) : TWidget(parent) _playback->setChangeFinishedCallback([this](float64 value) { handleSeekFinished(value); }); - _playPause->setClickedCallback([this]() { + _playPause->setClickedCallback([this] { if (exists()) { instance()->playPauseCancelClicked(); } }); updateRepeatTrackIcon(); - _repeatTrack->setClickedCallback([this]() { + _repeatTrack->setClickedCallback([this] { instance()->toggleRepeat(); - updateRepeatTrackIcon(); }); + updateVolumeToggleIcon(); + _volumeToggle->setClickedCallback([this]() { + Global::SetSongVolume((Global::SongVolume() > 0) ? 0. : Global::RememberedSongVolume()); + Global::RefSongVolumeChanged().notify(); + }); + subscribe(Global::RefSongVolumeChanged(), [this] { updateVolumeToggleIcon(); }); if (exists()) { - subscribe(instance()->playlistChangedNotifier(), [this]() { + subscribe(instance()->repeatChangedNotifier(), [this] { + updateRepeatTrackIcon(); + }); + subscribe(instance()->playlistChangedNotifier(), [this] { handlePlaylistUpdate(); }); subscribe(instance()->updatedNotifier(), [this](const UpdatedEvent &e) { handleSongUpdate(e); }); - subscribe(instance()->songChangedNotifier(), [this]() { + subscribe(instance()->songChangedNotifier(), [this] { handleSongChange(); }); handleSongChange(); @@ -117,6 +127,10 @@ CoverWidget::CoverWidget(QWidget *parent) : TWidget(parent) } } +void CoverWidget::setPinCallback(PinCallback &&callback) { + _pinPlayer->setClickedCallback(std_::move(callback)); +} + void CoverWidget::handleSeekProgress(float64 progress) { if (!_lastDurationMs) return; @@ -148,15 +162,22 @@ void CoverWidget::handleSeekFinished(float64 progress) { } void CoverWidget::resizeEvent(QResizeEvent *e) { - _nameLabel->resizeToWidth(width() - 2 * (st::mediaPlayerPadding) - _timeLabel->width() - st::normalFont->spacew); + auto widthForName = width() - 2 * (st::mediaPlayerPanelPadding); + widthForName -= _timeLabel->width() + 2 * st::normalFont->spacew; + _nameLabel->resizeToWidth(widthForName); updateLabelPositions(); - int skip = (st::mediaPlayerPlayback.seekSize.width() / 2); - int length = (width() - 2 * st::mediaPlayerPadding + st::mediaPlayerPlayback.seekSize.width()); - _playback->setGeometry(st::mediaPlayerPadding - skip, st::mediaPlayerPlaybackTop, length, 2 * st::mediaPlayerPlaybackPadding + st::mediaPlayerPlayback.width); + int skip = (st::mediaPlayerPanelPlayback.seekSize.width() / 2); + int length = (width() - 2 * st::mediaPlayerPanelPadding + st::mediaPlayerPanelPlayback.seekSize.width()); + _playback->setGeometry(st::mediaPlayerPanelPadding - skip, st::mediaPlayerPanelPlaybackTop, length, 2 * st::mediaPlayerPanelPlaybackPadding + st::mediaPlayerPanelPlayback.width); + + auto top = st::mediaPlayerPanelVolumeToggleTop; + auto right = st::mediaPlayerPanelPlayLeft; + _repeatTrack->moveToRight(right, top); right += _repeatTrack->width(); + _pinPlayer->moveToRight(right, top); right += _pinPlayer->width() + st::mediaPlayerPanelVolumeSkip; + _volumeController->moveToRight(right, st::mediaPlayerPanelVolumeTop); right += _volumeController->width() + st::mediaPlayerPanelVolumeToggleSkip; + _volumeToggle->moveToRight(right, top); - _repeatTrack->moveToRight(st::mediaPlayerPlayLeft, st::mediaPlayerPlayTop); - _volumeController->moveToRight(st::mediaPlayerVolumeRight, st::mediaPlayerVolumeTop); updatePlayPrevNextPositions(); } @@ -166,23 +187,24 @@ void CoverWidget::paintEvent(QPaintEvent *e) { } void CoverWidget::updatePlayPrevNextPositions() { + auto left = st::mediaPlayerPanelPlayLeft; + auto top = st::mediaPlayerPanelPlayTop; if (_previousTrack) { - auto left = st::mediaPlayerPlayLeft; - _previousTrack->moveToLeft(left, st::mediaPlayerPlayTop); left += _previousTrack->width() + st::mediaPlayerPlaySkip; - _playPause->moveToLeft(left, st::mediaPlayerPlayTop); left += _playPause->width() + st::mediaPlayerPlaySkip; - _nextTrack->moveToLeft(left, st::mediaPlayerPlayTop); + _previousTrack->moveToLeft(left, top); left += _previousTrack->width() + st::mediaPlayerPanelPlaySkip; + _playPause->moveToLeft(left, top); left += _playPause->width() + st::mediaPlayerPanelPlaySkip; + _nextTrack->moveToLeft(left, top); } else { - _playPause->moveToLeft(st::mediaPlayerPlayLeft, st::mediaPlayerPlayTop); + _playPause->moveToLeft(left, top); } } void CoverWidget::updateLabelPositions() { - _nameLabel->moveToLeft(st::mediaPlayerPadding, st::mediaPlayerNameTop - st::mediaPlayerName.font->ascent); - _timeLabel->moveToRight(st::mediaPlayerPadding, st::mediaPlayerNameTop - st::mediaPlayerTime.font->ascent); + _nameLabel->moveToLeft(st::mediaPlayerPanelPadding, st::mediaPlayerPanelNameTop - st::mediaPlayerName.font->ascent); + _timeLabel->moveToRight(st::mediaPlayerPanelPadding, st::mediaPlayerPanelNameTop - st::mediaPlayerTime.font->ascent); } void CoverWidget::updateRepeatTrackIcon() { - _repeatTrack->setIcon(instance()->repeatEnabled() ? nullptr : &st::mediaPlayerRepeatDisabledIcon); + _repeatTrack->setIcon(instance()->repeatEnabled() ? nullptr : &st::mediaPlayerRepeatInactiveIcon); } void CoverWidget::handleSongUpdate(const UpdatedEvent &e) { @@ -249,7 +271,7 @@ void CoverWidget::updateTimeLabel() { _timeLabel->setText(_time); } if (timeLabelWidth != _timeLabel->width()) { - _nameLabel->resizeToWidth(width() - 2 * (st::mediaPlayerPadding) - _timeLabel->width() - st::normalFont->spacew); + _nameLabel->resizeToWidth(width() - 2 * (st::mediaPlayerPanelPadding) - _timeLabel->width() - st::normalFont->spacew); updateLabelPositions(); } } @@ -281,17 +303,17 @@ void CoverWidget::handlePlaylistUpdate() { createPrevNextButtons(); auto previousEnabled = (index > 0); auto nextEnabled = (index + 1 < playlist.size()); - _previousTrack->setIcon(previousEnabled ? nullptr : &st::mediaPlayerPreviousDisabledIcon); + _previousTrack->setIcon(previousEnabled ? nullptr : &st::mediaPlayerPanelPreviousDisabledIcon); _previousTrack->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); - _nextTrack->setIcon(nextEnabled ? nullptr : &st::mediaPlayerNextDisabledIcon); + _nextTrack->setIcon(nextEnabled ? nullptr : &st::mediaPlayerPanelNextDisabledIcon); _nextTrack->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); } } void CoverWidget::createPrevNextButtons() { if (!_previousTrack) { - _previousTrack.create(this, st::mediaPlayerPreviousButton); - _nextTrack.create(this, st::mediaPlayerNextButton); + _previousTrack.create(this, st::mediaPlayerPanelPreviousButton); + _nextTrack.create(this, st::mediaPlayerPanelNextButton); _previousTrack->setClickedCallback([this]() { if (exists()) { instance()->previous(); @@ -314,5 +336,21 @@ void CoverWidget::destroyPrevNextButtons() { } } +void CoverWidget::updateVolumeToggleIcon() { + auto icon = []() -> const style::icon * { + auto volume = Global::SongVolume(); + if (volume > 0) { + if (volume < 1 / 3.) { + return &st::mediaPlayerVolumeIcon1; + } else if (volume < 2 / 3.) { + return &st::mediaPlayerVolumeIcon2; + } + return &st::mediaPlayerVolumeIcon3; + } + return nullptr; + }; + _volumeToggle->setIcon(icon()); +} + } // namespace Player } // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_cover.h b/Telegram/SourceFiles/media/player/media_player_cover.h index 333c106da..9275b33f7 100644 --- a/Telegram/SourceFiles/media/player/media_player_cover.h +++ b/Telegram/SourceFiles/media/player/media_player_cover.h @@ -35,7 +35,6 @@ class Playback; namespace Player { -class PlaybackWidget; class VolumeController; struct UpdatedEvent; @@ -43,6 +42,9 @@ class CoverWidget : public TWidget, private base::Subscriber { public: CoverWidget(QWidget *parent); + using PinCallback = base::lambda_unique; + void setPinCallback(PinCallback &&callback); + protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; @@ -57,6 +59,8 @@ private: void createPrevNextButtons(); void destroyPrevNextButtons(); + void updateVolumeToggleIcon(); + void handleSongUpdate(const UpdatedEvent &e); void handleSongChange(); void handlePlaylistUpdate(); @@ -75,7 +79,9 @@ private: ChildWidget _previousTrack = { nullptr }; ChildWidget _playPause; ChildWidget _nextTrack = { nullptr }; + ChildWidget _volumeToggle; ChildWidget _volumeController; + ChildWidget _pinPlayer; ChildWidget _repeatTrack; }; diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 8bed47180..ae5692799 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -84,6 +84,8 @@ void Instance::handleSongUpdate(const AudioMsgId &audioId) { void Instance::setCurrent(const AudioMsgId &audioId) { if (_current != audioId) { _current = audioId; + _isPlaying = false; + auto history = _history, migrated = _migrated; auto item = _current ? App::histItemById(_current.contextId()) : nullptr; if (item) { @@ -263,7 +265,36 @@ void Instance::emitUpdate(CheckCallback check) { next(); } } - _isPlaying = !(playbackState.state & AudioPlayerStoppedMask); + auto isPlaying = !(playbackState.state & AudioPlayerStoppedMask); + if (_isPlaying != isPlaying) { + _isPlaying = isPlaying; + if (_isPlaying) { + preloadNext(); + } + } +} + +void Instance::preloadNext() { + if (!_current) { + return; + } + auto index = _playlist.indexOf(_current.contextId()); + if (index < 0) { + return; + } + auto nextIndex = index + 1; + if (nextIndex >= _playlist.size()) { + return; + } + if (auto item = App::histItemById(_playlist[nextIndex])) { + if (auto media = item->getMedia()) { + if (auto document = media->getDocument()) { + if (!document->loaded(DocumentData::FilePathResolveSaveFromDataSilent)) { + DocumentOpenClickHandler::doOpen(document, nullptr, ActionOnLoadNone); + } + } + } + } } } // namespace Player diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index 4ea505d88..ccf892a4c 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -40,11 +40,11 @@ bool exists(); class Instance; Instance *instance(); -class Widget; -struct CreatedEvent { - explicit CreatedEvent(Widget *widget) : widget(widget) { +class Panel; +struct PanelEvent { + explicit PanelEvent(Panel *panel) : panel(panel) { } - Widget *widget; + Panel *panel; }; struct UpdatedEvent { UpdatedEvent(const AudioMsgId *audioId, const AudioPlaybackState *playbackState) : audioId(audioId), playbackState(playbackState) { @@ -74,6 +74,7 @@ public: } void toggleRepeat() { _repeatEnabled = !_repeatEnabled; + _repeatChangedNotifier.notify(); } bool isSeeking() const { @@ -86,9 +87,12 @@ public: return _playlist; } - base::Observable &createdNotifier() { + base::Observable &createdNotifier() { return _createdNotifier; } + base::Observable &destroyedNotifier() { + return _destroyedNotifier; + } base::Observable &updatedNotifier() { return _updatedNotifier; } @@ -98,6 +102,9 @@ public: base::Observable &songChangedNotifier() { return _songChangedNotifier; } + base::Observable &repeatChangedNotifier() { + return _repeatChangedNotifier; + } void documentLoadProgress(DocumentData *document); @@ -112,6 +119,7 @@ private: void setCurrent(const AudioMsgId &audioId); void rebuildPlaylist(); void moveInPlaylist(int delta); + void preloadNext(); template void emitUpdate(CheckCallback check); @@ -125,10 +133,12 @@ private: QList _playlist; bool _isPlaying = false; - base::Observable _createdNotifier; + base::Observable _createdNotifier; + base::Observable _destroyedNotifier; base::Observable _updatedNotifier; base::Observable _playlistChangedNotifier; base::Observable _songChangedNotifier; + base::Observable _repeatChangedNotifier; }; diff --git a/Telegram/SourceFiles/media/player/media_player_panel.cpp b/Telegram/SourceFiles/media/player/media_player_panel.cpp new file mode 100644 index 000000000..0bbb247e1 --- /dev/null +++ b/Telegram/SourceFiles/media/player/media_player_panel.cpp @@ -0,0 +1,225 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "media/player/media_player_panel.h" + +#include "media/player/media_player_cover.h" +#include "media/player/media_player_list.h" +#include "media/player/media_player_instance.h" +#include "styles/style_media_player.h" +#include "mainwindow.h" + +namespace Media { +namespace Player { + +Panel::Panel(QWidget *parent, Layout layout) : TWidget(parent) +, _shadow(st::defaultInnerDropdown.shadow) { + if (layout == Layout::Full) { + _cover.create(this); + } + _hideTimer.setSingleShot(true); + connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideStart())); + + _showTimer.setSingleShot(true); + connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShowStart())); + + if (_scroll) { + connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + } + + if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { + connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWindowActiveChanged())); + } + + hide(); + resize(contentLeft() + st::mediaPlayerPanelWidth, st::mediaPlayerCoverHeight + st::mediaPlayerPanelMarginBottom); +} + +bool Panel::overlaps(const QRect &globalRect) { + if (isHidden() || _a_appearance.animating()) return false; + + auto marginLeft = rtl() ? 0 : contentLeft(); + auto marginRight = rtl() ? contentLeft() : 0; + return rect().marginsRemoved(QMargins(marginLeft, 0, marginRight, st::mediaPlayerPanelMarginBottom)).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); +} + +void Panel::onWindowActiveChanged() { + if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { + leaveEvent(nullptr); + } +} + +void Panel::resizeEvent(QResizeEvent *e) { + _cover->resize(width() - contentLeft(), st::mediaPlayerCoverHeight); + _cover->moveToRight(0, 0); + if (_scroll) { + _scroll->resize(width(), height() - _cover->height()); + _scroll->moveToRight(0, _cover->height()); + _list->resizeToWidth(width()); + } + //_scroll->setGeometry(rect().marginsRemoved(_st.padding).marginsRemoved(_st.scrollMargin)); + //if (auto widget = static_cast(_scroll->widget())) { + // widget->resizeToWidth(_scroll->width()); + // onScroll(); + //} +} + +void Panel::onScroll() { + //if (auto widget = static_cast(_scroll->widget())) { + // int visibleTop = _scroll->scrollTop(); + // int visibleBottom = visibleTop + _scroll->height(); + // widget->setVisibleTopBottom(visibleTop, visibleBottom); + //} +} + +void Panel::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (!_cache.isNull()) { + bool animating = _a_appearance.animating(getms()); + if (animating) { + p.setOpacity(_a_appearance.current(_hiding)); + } else if (_hiding) { + hidingFinished(); + return; + } + p.drawPixmap(0, 0, _cache); + if (!animating) { + showChildren(); + _cache = QPixmap(); + } + return; + } + + // draw shadow + auto shadowedRect = myrtlrect(contentLeft(), 0, contentWidth(), height() - st::mediaPlayerPanelMarginBottom); + auto shadowedSides = (rtl() ? Ui::RectShadow::Side::Right : Ui::RectShadow::Side::Left) | Ui::RectShadow::Side::Bottom; + _shadow.paint(p, shadowedRect, st::defaultInnerDropdown.shadowShift, shadowedSides); + p.fillRect(shadowedRect, st::windowBg); +} + +void Panel::enterEvent(QEvent *e) { + _hideTimer.stop(); + if (_a_appearance.animating(getms())) { + onShowStart(); + } else { + _showTimer.start(0); + } + return TWidget::enterEvent(e); +} + +void Panel::leaveEvent(QEvent *e) { + _showTimer.stop(); + if (_a_appearance.animating(getms())) { + onHideStart(); + } else { + _hideTimer.start(300); + } + return TWidget::leaveEvent(e); +} + +void Panel::otherEnter() { + _hideTimer.stop(); + if (_a_appearance.animating(getms())) { + onShowStart(); + } else { + _showTimer.start(300); + } +} + +void Panel::otherLeave() { + _showTimer.stop(); + if (_a_appearance.animating(getms())) { + onHideStart(); + } else { + _hideTimer.start(0); + } +} + +void Panel::setPinCallback(PinCallback &&callback) { + if (_cover) { + _cover->setPinCallback(std_::move(callback)); + } +} + +Panel::~Panel() { + if (exists()) { + instance()->destroyedNotifier().notify(Media::Player::PanelEvent(this), true); + } +} + +void Panel::onShowStart() { + if (isHidden()) { + show(); + } else if (!_hiding) { + return; + } + _hiding = false; + startAnimation(); +} + +void Panel::onHideStart() { + if (_hiding) return; + + _hiding = true; + startAnimation(); +} + +void Panel::startAnimation() { + auto from = _hiding ? 1. : 0.; + auto to = _hiding ? 0. : 1.; + if (!_a_appearance.animating()) { + showChildren(); + _cache = myGrab(this); + } + hideChildren(); + _a_appearance.start([this] { appearanceCallback(); }, from, to, st::defaultInnerDropdown.duration); +} + +void Panel::appearanceCallback() { + if (!_a_appearance.animating() && _hiding) { + _hiding = false; + hidingFinished(); + } else { + update(); + } +} + +void Panel::hidingFinished() { + hide(); + showChildren(); +} + +int Panel::contentLeft() const { + return st::mediaPlayerPanelMarginLeft; +} + +bool Panel::eventFilter(QObject *obj, QEvent *e) { + if (e->type() == QEvent::Enter) { + otherEnter(); + } else if (e->type() == QEvent::Leave) { + otherLeave(); + } + return false; +} + +} // namespace Player +} // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_panel.h b/Telegram/SourceFiles/media/player/media_player_panel.h new file mode 100644 index 000000000..179a4a3ac --- /dev/null +++ b/Telegram/SourceFiles/media/player/media_player_panel.h @@ -0,0 +1,94 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "ui/effects/rect_shadow.h" + +class ScrollArea; + +namespace Media { +namespace Player { + +class CoverWidget; +class ListWidget; + +class Panel : public TWidget, private base::Subscriber { + Q_OBJECT + +public: + enum class Layout { + Full, + OnlyPlaylist, + }; + Panel(QWidget *parent, Layout layout); + + void setLayout(Layout layout); + bool overlaps(const QRect &globalRect); + + void otherEnter(); + void otherLeave(); + + using PinCallback = base::lambda_unique; + void setPinCallback(PinCallback &&callback); + + ~Panel(); + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + + bool eventFilter(QObject *obj, QEvent *e) override; + +private slots: + void onShowStart(); + void onHideStart(); + void onScroll(); + + void onWindowActiveChanged(); + +private: + void appearanceCallback(); + void hidingFinished(); + int contentLeft() const; + int contentWidth() const { + return width() - contentLeft(); + } + + void startAnimation(); + + bool _hiding = false; + + QPixmap _cache; + FloatAnimation _a_appearance; + + QTimer _hideTimer, _showTimer; + + Ui::RectShadow _shadow; + ChildWidget _cover = { nullptr }; + ChildWidget _list = { nullptr }; + ChildWidget _scroll = { nullptr }; + +}; + +} // namespace Clip +} // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp index 4217bb06d..e1aecf9e0 100644 --- a/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp +++ b/Telegram/SourceFiles/media/player/media_player_volume_controller.cpp @@ -26,65 +26,217 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "ui/widgets/media_slider.h" #include "styles/style_media_player.h" #include "styles/style_widgets.h" +#include "mainwindow.h" namespace Media { namespace Player { VolumeController::VolumeController(QWidget *parent) : TWidget(parent) -, _toggle(this, st::mediaPlayerVolumeToggle) -, _slider(this, st::mediaPlayerPlayback) { - _toggle->setClickedCallback([this]() { - setVolume(_slider->value() ? 0. : _rememberedVolume); - }); +, _slider(this, st::mediaPlayerPanelPlayback) { _slider->setChangeProgressCallback([this](float64 volume) { applyVolumeChange(volume); }); _slider->setChangeFinishedCallback([this](float64 volume) { if (volume > 0) { - _rememberedVolume = volume; + Global::SetRememberedSongVolume(volume); } applyVolumeChange(volume); }); + subscribe(Global::RefSongVolumeChanged(), [this] { + if (!_slider->isChanging()) { + _slider->setValue(Global::SongVolume(), true); + } + }); auto animated = false; setVolume(Global::SongVolume(), animated); - resize(st::mediaPlayerVolumeWidth, 2 * st::mediaPlayerPlaybackPadding + st::mediaPlayerPlayback.width); + resize(st::mediaPlayerPanelVolumeWidth, 2 * st::mediaPlayerPanelPlaybackPadding + st::mediaPlayerPanelPlayback.width); +} + +void VolumeController::setIsVertical(bool vertical) { + using Direction = Ui::MediaSlider::Direction; + _slider->setDirection(vertical ? Direction::Vertical : Direction::Horizontal); + _slider->setAlwaysDisplayMarker(vertical); } void VolumeController::resizeEvent(QResizeEvent *e) { - _slider->resize(st::mediaPlayerVolumeLength, height()); - _slider->moveToRight(0, 0); - _toggle->moveToLeft(0, (height() - _toggle->height()) / 2); + _slider->setGeometry(rect()); } void VolumeController::setVolume(float64 volume, bool animated) { _slider->setValue(volume, animated); if (volume > 0) { - _rememberedVolume = volume; + Global::SetRememberedSongVolume(volume); } applyVolumeChange(volume); } void VolumeController::applyVolumeChange(float64 volume) { - if (volume > 0) { - if (volume < 1 / 3.) { - _toggle->setIcon(&st::mediaPlayerVolumeIcon1); - } else if (volume < 2 / 3.) { - _toggle->setIcon(&st::mediaPlayerVolumeIcon2); - } else { - _toggle->setIcon(&st::mediaPlayerVolumeIcon3); - } - } else { - _toggle->setIcon(nullptr); - } if (volume != Global::SongVolume()) { Global::SetSongVolume(volume); - if (auto player = audioPlayer()) { - emit player->songVolumeChanged(); - } + Global::RefSongVolumeChanged().notify(); } } +VolumeWidget::VolumeWidget(QWidget *parent) : TWidget(parent) +, _shadow(st::defaultInnerDropdown.shadow) +, _controller(this) { + hide(); + _controller->setIsVertical(true); + + _hideTimer.setSingleShot(true); + connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideStart())); + + _showTimer.setSingleShot(true); + connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShowStart())); + + if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { + connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWindowActiveChanged())); + } + + hide(); + auto margin = getMargin(); + resize(margin.left() + st::mediaPlayerVolumeSize.width() + margin.right(), margin.top() + st::mediaPlayerVolumeSize.height() + margin.bottom()); +} + +QMargins VolumeWidget::getMargin() const { + return QMargins(st::mediaPlayerVolumeMargin, st::mediaPlayerPlayback.fullWidth, st::mediaPlayerVolumeMargin, st::mediaPlayerVolumeMargin); +} + +bool VolumeWidget::overlaps(const QRect &globalRect) { + if (isHidden() || _a_appearance.animating()) return false; + + return rect().marginsRemoved(getMargin()).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); +} + +void VolumeWidget::onWindowActiveChanged() { + if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { + leaveEvent(nullptr); + } +} + +void VolumeWidget::resizeEvent(QResizeEvent *e) { + auto inner = rect().marginsRemoved(getMargin()); + _controller->setGeometry(inner.x(), inner.y() - st::lineWidth, inner.width(), inner.height() + st::lineWidth - ((st::mediaPlayerVolumeSize.width() - st::mediaPlayerPanelPlayback.width) / 2)); +} + +void VolumeWidget::paintEvent(QPaintEvent *e) { + Painter p(this); + + if (!_cache.isNull()) { + bool animating = _a_appearance.animating(getms()); + if (animating) { + p.setOpacity(_a_appearance.current(_hiding)); + } else if (_hiding) { + hidingFinished(); + return; + } + p.drawPixmap(0, 0, _cache); + if (!animating) { + showChildren(); + _cache = QPixmap(); + } + return; + } + + // draw shadow + auto shadowedRect = rect().marginsRemoved(getMargin()); + using ShadowSide = Ui::RectShadow::Side; + auto shadowedSides = ShadowSide::Left | ShadowSide::Right | ShadowSide::Bottom; + _shadow.paint(p, shadowedRect, st::defaultInnerDropdown.shadowShift, shadowedSides); + p.fillRect(shadowedRect.x(), 0, shadowedRect.width(), shadowedRect.y() + shadowedRect.height(), st::windowBg); +} + +void VolumeWidget::enterEvent(QEvent *e) { + _hideTimer.stop(); + if (_a_appearance.animating(getms())) { + onShowStart(); + } else { + _showTimer.start(0); + } + return TWidget::enterEvent(e); +} + +void VolumeWidget::leaveEvent(QEvent *e) { + _showTimer.stop(); + if (_a_appearance.animating(getms())) { + onHideStart(); + } else { + _hideTimer.start(300); + } + return TWidget::leaveEvent(e); +} + +void VolumeWidget::otherEnter() { + _hideTimer.stop(); + if (_a_appearance.animating(getms())) { + onShowStart(); + } else { + _showTimer.start(0); + } +} + +void VolumeWidget::otherLeave() { + _showTimer.stop(); + if (_a_appearance.animating(getms())) { + onHideStart(); + } else { + _hideTimer.start(0); + } +} + +void VolumeWidget::onShowStart() { + if (isHidden()) { + show(); + } else if (!_hiding) { + return; + } + _hiding = false; + startAnimation(); +} + +void VolumeWidget::onHideStart() { + if (_hiding) return; + + _hiding = true; + startAnimation(); +} + +void VolumeWidget::startAnimation() { + auto from = _hiding ? 1. : 0.; + auto to = _hiding ? 0. : 1.; + if (_cache.isNull()) { + showChildren(); + _cache = myGrab(this); + } + hideChildren(); + _a_appearance.start([this] { appearanceCallback(); }, from, to, st::defaultInnerDropdown.duration); +} + +void VolumeWidget::appearanceCallback() { + if (!_a_appearance.animating() && _hiding) { + _hiding = false; + hidingFinished(); + } else { + update(); + } +} + +void VolumeWidget::hidingFinished() { + hide(); + showChildren(); + _cache = QPixmap(); +} + +bool VolumeWidget::eventFilter(QObject *obj, QEvent *e) { + if (e->type() == QEvent::Enter) { + otherEnter(); + } else if (e->type() == QEvent::Leave) { + otherLeave(); + } + return false; +} + } // namespace Player } // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_volume_controller.h b/Telegram/SourceFiles/media/player/media_player_volume_controller.h index e26de2cae..980028ca2 100644 --- a/Telegram/SourceFiles/media/player/media_player_volume_controller.h +++ b/Telegram/SourceFiles/media/player/media_player_volume_controller.h @@ -20,18 +20,23 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "ui/effects/rect_shadow.h" + namespace Ui { class IconButton; class MediaSlider; +class RectShadow; } // namespace Ui namespace Media { namespace Player { -class VolumeController : public TWidget { +class VolumeController : public TWidget, private base::Subscriber { public: VolumeController(QWidget *parent); + void setIsVertical(bool vertical); + protected: void resizeEvent(QResizeEvent *e) override; @@ -39,9 +44,50 @@ private: void setVolume(float64 volume, bool animated = true); void applyVolumeChange(float64 volume); - ChildWidget _toggle; ChildWidget _slider; - float64 _rememberedVolume = Global::kDefaultVolume; + +}; + +class VolumeWidget : public TWidget { + Q_OBJECT + +public: + VolumeWidget(QWidget *parent); + + bool overlaps(const QRect &globalRect); + + void otherEnter(); + void otherLeave(); + + QMargins getMargin() const; + +protected: + void resizeEvent(QResizeEvent *e) override; + void paintEvent(QPaintEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + + bool eventFilter(QObject *obj, QEvent *e) override; + +private slots: + void onShowStart(); + void onHideStart(); + void onWindowActiveChanged(); + +private: + void appearanceCallback(); + void hidingFinished(); + void startAnimation(); + + bool _hiding = false; + + QPixmap _cache; + FloatAnimation _a_appearance; + + QTimer _hideTimer, _showTimer; + + Ui::RectShadow _shadow; + ChildWidget _controller; }; diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index d3563eb81..348ad4b79 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -21,189 +21,371 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "media/player/media_player_widget.h" -#include "media/player/media_player_cover.h" -#include "media/player/media_player_list.h" +#include "ui/flatlabel.h" +#include "ui/widgets/label_simple.h" +#include "ui/widgets/filled_slider.h" +#include "ui/widgets/shadow.h" +#include "ui/buttons/icon_button.h" +#include "media/media_audio.h" +#include "media/view/media_clip_playback.h" +#include "media/player/media_player_button.h" +#include "media/player/media_player_instance.h" +#include "media/player/media_player_volume_controller.h" #include "styles/style_media_player.h" -#include "mainwindow.h" +#include "styles/style_mediaview.h" namespace Media { namespace Player { +using State = PlayButtonLayout::State; + +class Widget::PlayButton : public Button { +public: + PlayButton(QWidget *parent); + + void setState(PlayButtonLayout::State state) { + _layout.setState(state); + } + void finishTransform() { + _layout.finishTransform(); + } + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + PlayButtonLayout _layout; + +}; + +Widget::PlayButton::PlayButton(QWidget *parent) : Button(parent) +, _layout(st::mediaPlayerButton, [this] { update(); }) { + resize(st::mediaPlayerButtonSize); + setCursor(style::cur_pointer); +} + +void Widget::PlayButton::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.translate(st::mediaPlayerButtonPosition.x(), st::mediaPlayerButtonPosition.y()); + _layout.paint(p, st::mediaPlayerActiveFg); +} + Widget::Widget(QWidget *parent) : TWidget(parent) -, _shadow(st::defaultInnerDropdown.shadow) -, _cover(this) { - _hideTimer.setSingleShot(true); - connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(onHideStart())); +, _nameLabel(this, st::mediaPlayerName) +, _timeLabel(this, st::mediaPlayerTime) +, _playPause(this) +, _volumeToggle(this, st::mediaPlayerVolumeToggle) +, _repeatTrack(this, st::mediaPlayerRepeatButton) +, _close(this, st::mediaPlayerClose) +, _shadow(this, st::shadowColor) +, _playback(new Ui::FilledSlider(this, st::mediaPlayerPlayback)) { + setAttribute(Qt::WA_OpaquePaintEvent); + resize(st::wndMinWidth, st::mediaPlayerHeight + st::lineWidth); - _showTimer.setSingleShot(true); - connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShowStart())); + _playback->setChangeProgressCallback([this](float64 value) { + handleSeekProgress(value); + }); + _playback->setChangeFinishedCallback([this](float64 value) { + handleSeekFinished(value); + }); + _playPause->setClickedCallback([this] { + if (exists()) { + instance()->playPauseCancelClicked(); + } + }); - if (_scroll) { - connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); + updateVolumeToggleIcon(); + _volumeToggle->setClickedCallback([this] { + Global::SetSongVolume((Global::SongVolume() > 0) ? 0. : Global::RememberedSongVolume()); + Global::RefSongVolumeChanged().notify(); + }); + subscribe(Global::RefSongVolumeChanged(), [this] { updateVolumeToggleIcon(); }); + + updateRepeatTrackIcon(); + _repeatTrack->setClickedCallback([this] { + instance()->toggleRepeat(); + }); + + if (exists()) { + subscribe(instance()->repeatChangedNotifier(), [this] { + updateRepeatTrackIcon(); + }); + subscribe(instance()->playlistChangedNotifier(), [this] { + handlePlaylistUpdate(); + }); + subscribe(instance()->updatedNotifier(), [this](const UpdatedEvent &e) { + handleSongUpdate(e); + }); + subscribe(instance()->songChangedNotifier(), [this] { + handleSongChange(); + }); + handleSongChange(); + if (auto player = audioPlayer()) { + AudioMsgId playing; + auto playbackState = player->currentState(&playing, AudioMsgId::Type::Song); + handleSongUpdate(UpdatedEvent(&playing, &playbackState)); + _playPause->finishTransform(); + } } - - if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) { - connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWindowActiveChanged())); - } - - hide(); - resize(contentLeft() + st::mediaPlayerWidth, st::mediaPlayerCoverHeight + st::mediaPlayerMarginBottom); } -bool Widget::overlaps(const QRect &globalRect) { - if (isHidden() || _a_appearance.animating()) return false; - - auto marginLeft = rtl() ? 0 : contentLeft(); - auto marginRight = rtl() ? contentLeft() : 0; - return rect().marginsRemoved(QMargins(marginLeft, 0, marginRight, st::mediaPlayerMarginBottom)).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size())); +void Widget::updateVolumeToggleIcon() { + auto icon = []() -> const style::icon * { + auto volume = Global::SongVolume(); + if (volume > 0) { + if (volume < 1 / 3.) { + return &st::mediaPlayerVolumeIcon1; + } else if (volume < 2 / 3.) { + return &st::mediaPlayerVolumeIcon2; + } + return &st::mediaPlayerVolumeIcon3; + } + return nullptr; + }; + _volumeToggle->setIcon(icon()); } -void Widget::onWindowActiveChanged() { - if (!App::wnd()->windowHandle()->isActive() && !isHidden()) { - leaveEvent(nullptr); +void Widget::setCloseCallback(CloseCallback &&callback) { + _close->setClickedCallback(std_::move(callback)); +} + +void Widget::setShadowGeometryToLeft(int x, int y, int w, int h) { + _shadow->setGeometryToLeft(x, y, w, h); +} + +void Widget::showShadow() { + _shadow->show(); + _playback->show(); +} + +void Widget::hideShadow() { + _shadow->hide(); + _playback->hide(); +} + +QPoint Widget::getPositionForVolumeWidget() const { + auto x = _volumeToggle->x(); + x += (_volumeToggle->width() - st::mediaPlayerVolumeSize.width()) / 2; + if (rtl()) x = width() - x - st::mediaPlayerVolumeSize.width(); + return QPoint(x, height()); +} + +void Widget::volumeWidgetCreated(VolumeWidget *widget) { + _volumeToggle->installEventFilter(widget); +} + +void Widget::handleSeekProgress(float64 progress) { + if (!_lastDurationMs) return; + + auto positionMs = snap(static_cast(progress * _lastDurationMs), 0LL, _lastDurationMs); + if (_seekPositionMs != positionMs) { + _seekPositionMs = positionMs; + updateTimeLabel(); + if (exists()) { + instance()->startSeeking(); + } + } +} + +void Widget::handleSeekFinished(float64 progress) { + if (!_lastDurationMs) return; + + auto positionMs = snap(static_cast(progress * _lastDurationMs), 0LL, _lastDurationMs); + _seekPositionMs = -1; + + AudioMsgId playing; + auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); + if (playing && playbackState.duration) { + audioPlayer()->seek(qRound(progress * playbackState.duration)); + } + + if (exists()) { + instance()->stopSeeking(); } } void Widget::resizeEvent(QResizeEvent *e) { - _cover->resize(width() - contentLeft(), st::mediaPlayerCoverHeight); - _cover->moveToRight(0, 0); - if (_scroll) { - _scroll->resize(width(), height() - _cover->height()); - _scroll->moveToRight(0, _cover->height()); - _list->resizeToWidth(width()); - } - //_scroll->setGeometry(rect().marginsRemoved(_st.padding).marginsRemoved(_st.scrollMargin)); - //if (auto widget = static_cast(_scroll->widget())) { - // widget->resizeToWidth(_scroll->width()); - // onScroll(); - //} -} + updatePlayPrevNextPositions(); -void Widget::onScroll() { - //if (auto widget = static_cast(_scroll->widget())) { - // int visibleTop = _scroll->scrollTop(); - // int visibleBottom = visibleTop + _scroll->height(); - // widget->setVisibleTopBottom(visibleTop, visibleBottom); - //} + auto right = st::mediaPlayerCloseRight; + _close->moveToRight(right, st::mediaPlayerPlayTop); right += _close->width(); + _repeatTrack->moveToRight(right, st::mediaPlayerPlayTop); right += _repeatTrack->width(); + _volumeToggle->moveToRight(right, st::mediaPlayerPlayTop); right += _volumeToggle->width(); + + updateLabelsGeometry(); + + _playback->setGeometry(0, height() - st::mediaPlayerPlayback.fullWidth, width(), st::mediaPlayerPlayback.fullWidth); } void Widget::paintEvent(QPaintEvent *e) { Painter p(this); + auto fill = e->rect().intersected(QRect(0, 0, width(), st::mediaPlayerHeight)); + if (!fill.isEmpty()) { + p.fillRect(fill, st::windowBg); + } +} - if (!_cache.isNull()) { - bool animating = _a_appearance.animating(getms()); - if (animating) { - p.setOpacity(_a_appearance.current(_hiding)); - } else if (_hiding) { - hidingFinished(); - return; - } - p.drawPixmap(0, 0, _cache); - if (!animating) { - showChildren(); - _cache = QPixmap(); - } +void Widget::updatePlayPrevNextPositions() { + auto left = st::mediaPlayerPlayLeft; + auto top = st::mediaPlayerPlayTop; + if (_previousTrack) { + _previousTrack->moveToLeft(left, top); left += _previousTrack->width() + st::mediaPlayerPlaySkip; + _playPause->moveToLeft(left, top); left += _playPause->width() + st::mediaPlayerPlaySkip; + _nextTrack->moveToLeft(left, top); + } else { + _playPause->moveToLeft(left, top); + } +} + +void Widget::updateLabelsGeometry() { + auto left = st::mediaPlayerPlayLeft + _playPause->width(); + if (_previousTrack) { + left += _previousTrack->width() + st::mediaPlayerPlaySkip + _nextTrack->width() + st::mediaPlayerPlaySkip; + } + left += st::mediaPlayerPadding; + + auto right = st::mediaPlayerCloseRight + _close->width() + _repeatTrack->width() + _volumeToggle->width(); + right += st::mediaPlayerPadding; + + auto widthForName = width() - left - right; + widthForName -= _timeLabel->width() + 2 * st::normalFont->spacew; + _nameLabel->resizeToWidth(widthForName); + + _nameLabel->moveToLeft(left, st::mediaPlayerNameTop - st::mediaPlayerName.font->ascent); + _timeLabel->moveToRight(right, st::mediaPlayerNameTop - st::mediaPlayerTime.font->ascent); +} + +void Widget::updateRepeatTrackIcon() { + _repeatTrack->setIcon(instance()->repeatEnabled() ? nullptr : &st::mediaPlayerRepeatDisabledIcon); +} + +void Widget::handleSongUpdate(const UpdatedEvent &e) { + auto &audioId = *e.audioId; + auto &playbackState = *e.playbackState; + if (!audioId || !audioId.audio()->song()) { return; } - // draw shadow - auto shadowedRect = myrtlrect(contentLeft(), 0, contentWidth(), height() - st::mediaPlayerMarginBottom); - auto shadowedSides = (rtl() ? Ui::RectShadow::Side::Right : Ui::RectShadow::Side::Left) | Ui::RectShadow::Side::Bottom; - _shadow.paint(p, shadowedRect, st::defaultInnerDropdown.shadowShift, shadowedSides); - p.fillRect(shadowedRect, st::windowBg); -} + _playback->updateState(*e.playbackState); -void Widget::enterEvent(QEvent *e) { - _hideTimer.stop(); - if (_a_appearance.animating(getms())) { - onShowStart(); - } else { - _showTimer.start(0); + auto stopped = ((playbackState.state & AudioPlayerStoppedMask) || playbackState.state == AudioPlayerFinishing); + auto showPause = !stopped && (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); + if (exists() && instance()->isSeeking()) { + showPause = true; } - return TWidget::enterEvent(e); -} - -void Widget::leaveEvent(QEvent *e) { - _showTimer.stop(); - if (_a_appearance.animating(getms())) { - onHideStart(); - } else { - _hideTimer.start(300); - } - return TWidget::leaveEvent(e); -} - -void Widget::otherEnter() { - _hideTimer.stop(); - if (_a_appearance.animating(getms())) { - onShowStart(); - } else { - _showTimer.start(300); - } -} - -void Widget::otherLeave() { - _showTimer.stop(); - if (_a_appearance.animating(getms())) { - onHideStart(); - } else { - _hideTimer.start(0); - } -} - -void Widget::onShowStart() { - if (isHidden()) { - show(); - } else if (!_hiding) { - return; - } - _hiding = false; - startAnimation(); -} - -void Widget::onHideStart() { - if (_hiding) return; - - _hiding = true; - startAnimation(); -} - -void Widget::startAnimation() { - auto from = _hiding ? 1. : 0.; - auto to = _hiding ? 0. : 1.; - if (!_a_appearance.animating()) { - showChildren(); - _cache = myGrab(this); - } - hideChildren(); - _a_appearance.start([this] { - update(); - - // hack, animating() call destroys lambda :( - auto that = this; - if (!_a_appearance.animating() && that->_hiding) { - that->_hiding = false; - that->hidingFinished(); + auto state = [audio = audioId.audio(), showPause] { + if (audio->loading()) { + return State::Cancel; + } else if (showPause) { + return State::Pause; } - }, from, to, st::defaultInnerDropdown.duration); + return State::Play; + }; + _playPause->setState(state()); + + updateTimeText(audioId, playbackState); } -void Widget::hidingFinished() { - hide(); - showChildren(); -} - -int Widget::contentLeft() const { - return st::mediaPlayerMarginLeft; -} - -bool Widget::eventFilter(QObject *obj, QEvent *e) { - if (e->type() == QEvent::Enter) { - otherEnter(); - } else if (e->type() == QEvent::Leave) { - otherLeave(); +void Widget::updateTimeText(const AudioMsgId &audioId, const AudioPlaybackState &playbackState) { + QString time; + qint64 position = 0, duration = 0, display = 0; + auto frequency = (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); + if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { + display = position = playbackState.position; + duration = playbackState.duration; + } else { + display = playbackState.duration ? playbackState.duration : (audioId.audio()->song()->duration * frequency); + } + + _lastDurationMs = (playbackState.duration * 1000LL) / frequency; + + if (audioId.audio()->loading()) { + auto loaded = audioId.audio()->loadOffset(); + auto loadProgress = snap(float64(loaded) / qMax(audioId.audio()->size, 1), 0., 1.); + _time = QString::number(qRound(loadProgress * 100)) + '%'; + _playback->setDisabled(true); + } else { + display = display / frequency; + _time = formatDurationText(display); + _playback->setDisabled(false); + } + if (_seekPositionMs < 0) { + updateTimeLabel(); + } +} + +void Widget::updateTimeLabel() { + auto timeLabelWidth = _timeLabel->width(); + if (_seekPositionMs >= 0) { + auto playAlready = _seekPositionMs / 1000LL; + _timeLabel->setText(formatDurationText(playAlready)); + } else { + _timeLabel->setText(_time); + } + if (timeLabelWidth != _timeLabel->width()) { + updateLabelsGeometry(); + } +} + +void Widget::handleSongChange() { + auto ¤t = instance()->current(); + auto song = current.audio()->song(); + + TextWithEntities textWithEntities; + if (song->performer.isEmpty()) { + textWithEntities.text = song->title.isEmpty() ? (current.audio()->name.isEmpty() ? qsl("Unknown Track") : current.audio()->name) : song->title; + } else { + auto title = song->title.isEmpty() ? qsl("Unknown Track") : textClean(song->title); + textWithEntities.text = song->performer + QString::fromUtf8(" \xe2\x80\x93 ") + title; + textWithEntities.entities.append({ EntityInTextBold, 0, song->performer.size(), QString() }); + } + _nameLabel->setMarkedText(textWithEntities); + + handlePlaylistUpdate(); +} + +void Widget::handlePlaylistUpdate() { + auto ¤t = instance()->current(); + auto &playlist = instance()->playlist(); + auto index = playlist.indexOf(current.contextId()); + if (!current || index < 0) { + destroyPrevNextButtons(); + } else { + createPrevNextButtons(); + auto previousEnabled = (index > 0); + auto nextEnabled = (index + 1 < playlist.size()); + _previousTrack->setIcon(previousEnabled ? nullptr : &st::mediaPlayerPreviousDisabledIcon); + _previousTrack->setCursor(previousEnabled ? style::cur_pointer : style::cur_default); + _nextTrack->setIcon(nextEnabled ? nullptr : &st::mediaPlayerNextDisabledIcon); + _nextTrack->setCursor(nextEnabled ? style::cur_pointer : style::cur_default); + } +} + +void Widget::createPrevNextButtons() { + if (!_previousTrack) { + _previousTrack.create(this, st::mediaPlayerPreviousButton); + _nextTrack.create(this, st::mediaPlayerNextButton); + _previousTrack->setClickedCallback([this]() { + if (exists()) { + instance()->previous(); + } + }); + _nextTrack->setClickedCallback([this]() { + if (exists()) { + instance()->next(); + } + }); + updatePlayPrevNextPositions(); + } +} + +void Widget::destroyPrevNextButtons() { + if (_previousTrack) { + _previousTrack.destroy(); + _nextTrack.destroy(); + updatePlayPrevNextPositions(); } - return false; } } // namespace Player diff --git a/Telegram/SourceFiles/media/player/media_player_widget.h b/Telegram/SourceFiles/media/player/media_player_widget.h index 28d09e617..fb3380f34 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.h +++ b/Telegram/SourceFiles/media/player/media_player_widget.h @@ -20,63 +20,79 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "ui/effects/rect_shadow.h" +class AudioMsgId; +struct AudioPlaybackState; +class FlatLabel; -class ScrollArea; +namespace Ui { +class LabelSimple; +class IconButton; +class PlainShadow; +} // namespace Ui namespace Media { +namespace Clip { +class Playback; +} // namespace Clip + namespace Player { -class CoverWidget; -class ListWidget; +class PlayButton; +class VolumeWidget; +struct UpdatedEvent; class Widget : public TWidget, private base::Subscriber { - Q_OBJECT - public: Widget(QWidget *parent); - bool overlaps(const QRect &globalRect); + using CloseCallback = base::lambda_unique; + void setCloseCallback(CloseCallback &&callback); - void otherEnter(); - void otherLeave(); + void setShadowGeometryToLeft(int x, int y, int w, int h); + void showShadow(); + void hideShadow(); + + QPoint getPositionForVolumeWidget() const; + void volumeWidgetCreated(VolumeWidget *widget); protected: void resizeEvent(QResizeEvent *e) override; void paintEvent(QPaintEvent *e) override; - void enterEvent(QEvent *e) override; - void leaveEvent(QEvent *e) override; - - bool eventFilter(QObject *obj, QEvent *e) override; - -private slots: - void onShowStart(); - void onHideStart(); - void onScroll(); - - void onWindowActiveChanged(); private: - void hidingFinished(); - int contentLeft() const; - int contentWidth() const { - return width() - contentLeft(); - } + void handleSeekProgress(float64 progress); + void handleSeekFinished(float64 progress); - void startAnimation(); + void updatePlayPrevNextPositions(); + void updateLabelsGeometry(); + void updateRepeatTrackIcon(); + void createPrevNextButtons(); + void destroyPrevNextButtons(); - bool _hiding = false; + void updateVolumeToggleIcon(); - QPixmap _cache; - FloatAnimation _a_appearance; + void handleSongUpdate(const UpdatedEvent &e); + void handleSongChange(); + void handlePlaylistUpdate(); - QTimer _hideTimer, _showTimer; + void updateTimeText(const AudioMsgId &audioId, const AudioPlaybackState &playbackState); + void updateTimeLabel(); - Ui::RectShadow _shadow; - ChildWidget _cover; - ChildWidget _list = { nullptr }; - ChildWidget _scroll = { nullptr }; + int64 _seekPositionMs = -1; + int64 _lastDurationMs = 0; + QString _time; + class PlayButton; + ChildWidget _nameLabel; + ChildWidget _timeLabel; + ChildWidget _previousTrack = { nullptr }; + ChildWidget _playPause; + ChildWidget _nextTrack = { nullptr }; + ChildWidget _volumeToggle; + ChildWidget _repeatTrack; + ChildWidget _close; + ChildWidget _shadow = { nullptr }; + ChildWidget _playback; }; diff --git a/Telegram/SourceFiles/media/view/media_clip_controller.cpp b/Telegram/SourceFiles/media/view/media_clip_controller.cpp index c4a7af66c..0ea8e4a51 100644 --- a/Telegram/SourceFiles/media/view/media_clip_controller.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_controller.cpp @@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "media/view/media_clip_volume_controller.h" #include "styles/style_mediaview.h" #include "ui/widgets/label_simple.h" +#include "ui/widgets/media_slider.h" #include "ui/effects/fade_animation.h" #include "ui/buttons/icon_button.h" #include "media/media_audio.h" @@ -34,7 +35,7 @@ namespace Clip { Controller::Controller(QWidget *parent) : TWidget(parent) , _playPauseResume(this, st::mediaviewPlayButton) -, _playback(this, st::mediaviewPlayback) +, _playback(new Ui::MediaSlider(this, st::mediaviewPlayback)) , _volumeController(this) , _fullScreenToggle(this, st::mediaviewFullScreenButton) , _playedAlready(this, st::mediaviewPlayProgressLabel) diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.cpp b/Telegram/SourceFiles/media/view/media_clip_playback.cpp index 3117250d5..480ff21db 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.cpp +++ b/Telegram/SourceFiles/media/view/media_clip_playback.cpp @@ -22,13 +22,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "media/view/media_clip_playback.h" #include "styles/style_mediaview.h" -#include "ui/widgets/media_slider.h" #include "media/media_audio.h" namespace Media { namespace Clip { -Playback::Playback(QWidget *parent, const style::MediaSlider &st) : _slider(new Ui::MediaSlider(parent, st)) { +Playback::Playback(Ui::ContinuousSlider *slider) : _slider(slider) { } void Playback::updateState(const AudioPlaybackState &playbackState) { diff --git a/Telegram/SourceFiles/media/view/media_clip_playback.h b/Telegram/SourceFiles/media/view/media_clip_playback.h index 32cb2ffe5..2b15cf42f 100644 --- a/Telegram/SourceFiles/media/view/media_clip_playback.h +++ b/Telegram/SourceFiles/media/view/media_clip_playback.h @@ -20,32 +20,26 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once -#include "ui/widgets/media_slider.h" +#include "ui/widgets/continuous_slider.h" struct AudioPlaybackState; -namespace style { -struct MediaSlider; -} // namespace style -namespace Ui { -class MediaSlider; -} // namespace Ui namespace Media { namespace Clip { class Playback { public: - Playback(QWidget *parent, const style::MediaSlider &st); + Playback(Ui::ContinuousSlider *slider); void updateState(const AudioPlaybackState &playbackState); void setFadeOpacity(float64 opacity) { _slider->setFadeOpacity(opacity); } - void setChangeProgressCallback(Ui::MediaSlider::Callback &&callback) { + void setChangeProgressCallback(Ui::ContinuousSlider::Callback &&callback) { _slider->setChangeProgressCallback(std_::move(callback)); } - void setChangeFinishedCallback(Ui::MediaSlider::Callback &&callback) { + void setChangeFinishedCallback(Ui::ContinuousSlider::Callback &&callback) { _slider->setChangeFinishedCallback(std_::move(callback)); } void setGeometry(int x, int y, int w, int h) { @@ -68,7 +62,7 @@ public: } private: - Ui::MediaSlider *_slider; + Ui::ContinuousSlider *_slider; int64 _position = 0; int64 _duration = 0; diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index fb0b9b98c..f7320ab9c 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -792,7 +792,7 @@ void MediaView::onDocClick() { if (_doc->loading()) { onSaveCancel(); } else { - DocumentOpenClickHandler::doOpen(_doc, ActionOnLoadNone); + DocumentOpenClickHandler::doOpen(_doc, nullptr, ActionOnLoadNone); if (_doc->loading() && !_radial.animating()) { _radial.start(_doc->progress()); } @@ -1477,7 +1477,7 @@ void MediaView::onVideoSeekFinished(int64 positionMs) { void MediaView::onVideoVolumeChanged(float64 volume) { Global::SetVideoVolume(volume); - emit audioPlayer()->videoVolumeChanged(); + Global::RefVideoVolumeChanged().notify(); } void MediaView::onVideoToggleFullScreen() { diff --git a/Telegram/SourceFiles/mtproto/file_download.cpp b/Telegram/SourceFiles/mtproto/file_download.cpp index 349380c9c..44531984a 100644 --- a/Telegram/SourceFiles/mtproto/file_download.cpp +++ b/Telegram/SourceFiles/mtproto/file_download.cpp @@ -61,23 +61,14 @@ namespace { } FileLoader::FileLoader(const QString &toFile, int32 size, LocationType locationType, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading) -: _prev(0) -, _next(0) -, _priority(0) -, _paused(false) -, _autoLoading(autoLoading) -, _inQueue(false) -, _complete(false) -, _localStatus(LocalNotTried) +: _autoLoading(autoLoading) , _file(toFile) , _fname(toFile) -, _fileIsOpen(false) , _toCache(toCache) , _fromCloud(fromCloud) , _size(size) , _type(mtpc_storage_fileUnknown) -, _locationType(locationType) -, _localTaskId(0) { +, _locationType(locationType) { } QByteArray FileLoader::imageFormat(const QSize &shrinkBox) const { @@ -124,7 +115,9 @@ int32 FileLoader::fullSize() const { } bool FileLoader::setFileName(const QString &fileName) { - if (_toCache != LoadToCacheAsWell || !_fname.isEmpty()) return fileName.isEmpty(); + if (_toCache != LoadToCacheAsWell || !_fname.isEmpty()) { + return fileName.isEmpty() || (fileName == _fname); + } _fname = fileName; _file.setFileName(_fname); return true; @@ -591,6 +584,7 @@ bool mtpFileLoader::tryLoadLocal() { } } } + emit progress(this); if (_localStatus != LocalNotTried) { return _complete; diff --git a/Telegram/SourceFiles/mtproto/file_download.h b/Telegram/SourceFiles/mtproto/file_download.h index f4453ee7f..ed9d15b8e 100644 --- a/Telegram/SourceFiles/mtproto/file_download.h +++ b/Telegram/SourceFiles/mtproto/file_download.h @@ -182,12 +182,16 @@ signals: protected: void readImage(const QSize &shrinkBox) const; - FileLoader *_prev, *_next; - int32 _priority; + FileLoader *_prev = nullptr; + FileLoader *_next = nullptr; + int _priority = 0; FileLoaderQueue *_queue; - bool _paused, _autoLoading, _inQueue, _complete; - mutable LocalLoadStatus _localStatus; + bool _paused = false; + bool _autoLoading = false; + bool _inQueue = false; + bool _complete = false; + mutable LocalLoadStatus _localStatus = LocalNotTried; virtual bool tryLoadLocal() = 0; virtual void cancelRequests() = 0; @@ -201,7 +205,7 @@ protected: QFile _file; QString _fname; - bool _fileIsOpen; + bool _fileIsOpen = false; LoadToCacheSetting _toCache; LoadFromCloudSetting _fromCloud; @@ -212,7 +216,7 @@ protected: mtpTypeId _type; LocationType _locationType; - TaskId _localTaskId; + TaskId _localTaskId = 0; mutable QByteArray _imageFormat; mutable QPixmap _imagePixmap; diff --git a/Telegram/SourceFiles/observer_peer.cpp b/Telegram/SourceFiles/observer_peer.cpp index 66dfb3602..01ca66e6f 100644 --- a/Telegram/SourceFiles/observer_peer.cpp +++ b/Telegram/SourceFiles/observer_peer.cpp @@ -37,8 +37,8 @@ using AllUpdatesList = QMap; NeverFreedPointer AllUpdates; void StartCallback() { - SmallUpdates.makeIfNull(); - AllUpdates.makeIfNull(); + SmallUpdates.createIfNull(); + AllUpdates.createIfNull(); } void FinishCallback() { SmallUpdates.clear(); @@ -63,8 +63,8 @@ void mergePeerUpdate(PeerUpdate &mergeTo, const PeerUpdate &mergeFrom) { } void peerUpdatedDelayed(const PeerUpdate &update) { - SmallUpdates.makeIfNull(); - AllUpdates.makeIfNull(); + SmallUpdates.createIfNull(); + AllUpdates.createIfNull(); Global::RefHandleDelayedPeerUpdates().call(); diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index fe87679cd..8b66c1f92 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -31,7 +31,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "application.h" #include "fileuploader.h" #include "mainwindow.h" -#include "playerwidget.h" #include "media/media_audio.h" #include "media/player/media_player_instance.h" #include "localstorage.h" diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 2f8bed179..cf24529fe 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -32,9 +32,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include "overviewwidget.h" #include "application.h" -#include "playerwidget.h" #include "overview/overview_layout.h" #include "history/history_media_types.h" +#include "media/media_audio.h" // flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html @@ -1903,13 +1903,8 @@ OverviewInner::~OverviewInner() { OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewType type) : TWidget(parent) , _scroll(this, st::historyScroll, false) , _inner(this, &_scroll, peer, type) -, _noDropResizeIndex(false) , _a_show(animation(this, &OverviewWidget::step_show)) -, _scrollSetAfterShow(0) -, _scrollDelta(0) -, _selCount(0) -, _topShadow(this, st::shadowColor) -, _inGrab(false) { +, _topShadow(this, st::shadowColor) { _scroll.setFocusPolicy(Qt::NoFocus); _scroll.setWidget(&_inner); _scroll.move(0, 0); @@ -1923,8 +1918,6 @@ OverviewWidget::OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewTyp connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); _scrollTimer.setSingleShot(false); -// connect(App::main()->player(), SIGNAL(playerSongChanged(const FullMsgId&)), this, SLOT(onPlayerSongChanged(const FullMsgId&))); - switchType(type); } @@ -1963,8 +1956,8 @@ void OverviewWidget::resizeEvent(QResizeEvent *e) { } _noDropResizeIndex = false; - _topShadow.resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth); - _topShadow.moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0); + _topShadow->resize(width() - ((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0), st::lineWidth); + _topShadow->moveToLeft((!Adaptive::OneColumn() && !_inGrab) ? st::lineWidth : 0, 0); } void OverviewWidget::paintEvent(QPaintEvent *e) { @@ -2129,9 +2122,9 @@ void OverviewWidget::showAnimated(Window::SlideDirection direction, const Window _cacheUnder = params.oldContentCache; show(); - _topShadow.setVisible(params.withTopBarShadow ? false : true); + _topShadow->setVisible(params.withTopBarShadow ? false : true); _cacheOver = App::main()->grabForShowAnimation(params); - _topShadow.setVisible(params.withTopBarShadow ? true : false); + _topShadow->setVisible(params.withTopBarShadow ? true : false); App::main()->topBar()->startAnim(); _scrollSetAfterShow = _scroll.scrollTop(); @@ -2159,7 +2152,7 @@ void OverviewWidget::step_show(float64 ms, bool timer) { float64 dt = ms / st::slideDuration; if (dt >= 1) { _a_show.stop(); - _topShadow.show(); + _topShadow->show(); a_coordUnder.finish(); a_coordOver.finish(); @@ -2202,6 +2195,17 @@ void OverviewWidget::changingMsgId(HistoryItem *row, MsgId newId) { } } +void OverviewWidget::grapWithoutTopBarShadow() { + grabStart(); + _topShadow->hide(); +} + +void OverviewWidget::grabFinish() { + _inGrab = false; + resizeEvent(0); + _topShadow->show(); +} + void OverviewWidget::ui_repaintHistoryItem(const HistoryItem *item) { if (peer() == item->history()->peer || migratePeer() == item->history()->peer) { _inner.repaintItem(item); @@ -2263,15 +2267,6 @@ void OverviewWidget::onScrollTimer() { _scroll.scrollToY(_scroll.scrollTop() + d); } -//void OverviewWidget::onPlayerSongChanged(const FullMsgId &msgId) { -// if (type() == OverviewMusicFiles) { -// int32 top = _inner.itemTop(msgId); -// if (top > 0) { -// _scroll.scrollToY(snap(top - int(_scroll.height() - (st::msgPadding.top() + st::mediaThumbSize + st::msgPadding.bottom())) / 2, 0, _scroll.scrollTopMax())); -// } -// } -//} - void OverviewWidget::checkSelectingScroll(QPoint point) { if (point.y() < _scroll.scrollTop()) { _scrollDelta = point.y() - _scroll.scrollTop(); diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index 631a51ae3..d23146497 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -25,20 +25,21 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Overview { namespace Layout { - class AbstractItem; class ItemBase; class Date; - } // namespace Layout } // namespace Overview +namespace Ui { +class PlainShadow; +} // namespace Ui + class OverviewWidget; class OverviewInner : public QWidget, public AbstractTooltipShower, public RPCSender, private base::Subscriber { Q_OBJECT public: - OverviewInner(OverviewWidget *overview, ScrollArea *scroll, PeerData *peer, MediaOverviewType type); void activate(); @@ -96,7 +97,6 @@ public: ~OverviewInner(); public slots: - void onUpdateSelected(); void copyContextUrl(); @@ -123,7 +123,6 @@ public slots: void onNeedSearchMessages(); private: - MsgId complexMsgId(const HistoryItem *item) const; bool itemMigrated(MsgId msgId) const; @@ -253,7 +252,6 @@ class OverviewWidget : public TWidget, public RPCSender { Q_OBJECT public: - OverviewWidget(QWidget *parent, PeerData *peer, MediaOverviewType type); void clear(); @@ -309,15 +307,8 @@ public: _inGrab = true; resizeEvent(0); } - void grapWithoutTopBarShadow() { - grabStart(); - _topShadow.hide(); - } - void grabFinish() override { - _inGrab = false; - resizeEvent(0); - _topShadow.show(); - } + void grapWithoutTopBarShadow(); + void grabFinish() override; void rpcClear() override { _inner.rpcClear(); RPCSender::rpcClear(); @@ -330,12 +321,10 @@ public: ~OverviewWidget(); public slots: - void activate(); void onScroll(); void onScrollTimer(); -// void onPlayerSongChanged(const FullMsgId &msgId); void onForwardSelected(); void onDeleteSelected(); @@ -344,10 +333,9 @@ public slots: void onClearSelected(); private: - ScrollArea _scroll; OverviewInner _inner; - bool _noDropResizeIndex; + bool _noDropResizeIndex = false; QString _header; @@ -356,15 +344,15 @@ private: anim::ivalue a_coordUnder, a_coordOver; anim::fvalue a_progress; - int32 _scrollSetAfterShow; + int32 _scrollSetAfterShow = 0; QTimer _scrollTimer; - int32 _scrollDelta; + int32 _scrollDelta = 0; - int32 _selCount; + int32 _selCount = 0; - PlainShadow _topShadow; - bool _inGrab; + ChildWidget _topShadow; + bool _inGrab = false; }; diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index a15a76c36..b90368628 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -214,7 +214,7 @@ using Notification = QSharedPointer; void start() { if (LibNotifyLoaded()) { if (Libs::notify_is_initted() || Libs::notify_init("Telegram Desktop")) { - ManagerInstance.makeIfNull(); + ManagerInstance.createIfNull(); if (!ManagerInstance->init()) { ManagerInstance.clear(); LOG(("LibNotify Error: manager failed to init!")); diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index 6dd1d6edb..e22b0b609 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -21,7 +21,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "mainwidget.h" #include "application.h" -#include "playerwidget.h" #include "historywidget.h" #include "localstorage.h" #include "window/notifications_manager_default.h" diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm index a1ddf0896..df3ade08d 100644 --- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm +++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm @@ -96,7 +96,7 @@ namespace Notifications { void start() { if (cPlatform() != dbipMacOld) { - ManagerInstance.makeIfNull(); + ManagerInstance.createIfNull(); } } diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp index 337af08cd..aded7f1b4 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp @@ -316,7 +316,7 @@ private: void start() { if (init()) { - ManagerInstance.makeIfNull(); + ManagerInstance.createIfNull(); } } diff --git a/Telegram/SourceFiles/playerwidget.cpp b/Telegram/SourceFiles/playerwidget.cpp deleted file mode 100644 index a434bd82a..000000000 --- a/Telegram/SourceFiles/playerwidget.cpp +++ /dev/null @@ -1,695 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#include "stdafx.h" -#include "playerwidget.h" - -#include "shortcuts.h" -#include "lang.h" -#include "boxes/addcontactbox.h" -#include "application.h" -#include "mainwindow.h" -#include "playerwidget.h" -#include "mainwidget.h" -#include "localstorage.h" -#include "media/media_audio.h" -#include "history/history_media_types.h" - -PlayerWidget::PlayerWidget(QWidget *parent) : TWidget(parent) -, _a_state(animation(this, &PlayerWidget::step_state)) -, _a_progress(animation(this, &PlayerWidget::step_progress)) { - resize(st::wndMinWidth, st::playerHeight); - setMouseTracking(true); - memset(_stateHovers, 0, sizeof(_stateHovers)); -} - -void PlayerWidget::paintEvent(QPaintEvent *e) { - Painter p(this); - - QRect r(e->rect()), checkr(myrtlrect(r)); - p.fillRect(r, st::playerBg->b); - - if (!_playbackRect.contains(checkr)) { - if (_fullAvailable && checkr.intersects(_prevRect)) { - if (_prevAvailable) { - float64 o = _stateHovers[OverPrev]; - p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); - } else { - p.setOpacity(st::playerUnavailableOpacity); - } - p.drawSpriteCenterLeft(_prevRect, width(), st::playerPrev); - } - if (checkr.intersects(_playRect)) { - float64 o = _stateHovers[OverPlay]; - p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); - p.drawSpriteCenterLeft(_playRect, width(), (_showPause || _down == OverPlayback) ? st::playerPause : st::playerPlay); - } - if (_fullAvailable && checkr.intersects(_nextRect)) { - if (_nextAvailable) { - float64 o = _stateHovers[OverNext]; - p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); - } else { - p.setOpacity(st::playerUnavailableOpacity); - } - p.drawSpriteCenterLeft(_nextRect, width(), st::playerNext); - } - if (checkr.intersects(_closeRect)) { - float64 o = _stateHovers[OverClose]; - p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); - p.drawSpriteCenterLeft(_closeRect, width(), st::playerClose); - } - if (checkr.intersects(_volumeRect)) { - float64 o = _stateHovers[OverVolume]; - p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); - int32 top = _volumeRect.y() + (_volumeRect.height() - st::playerVolume.pxHeight()) / 2; - int32 left = _volumeRect.x() + (_volumeRect.width() - st::playerVolume.pxWidth()) / 2; - int32 mid = left + qRound(st::playerVolume.pxWidth() * Global::SongVolume()); - int32 right = left + st::playerVolume.pxWidth(); - if (rtl()) { - left = width() - left; - mid = width() - mid; - right = width() - right; - if (mid < left) { - p.drawPixmap(QRect(mid, top, left - mid, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.rect().x() + (mid - right) * cIntRetinaFactor(), st::playerVolume.rect().y(), (left - mid) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor())); - } - if (right < mid) { - p.setOpacity(st::playerUnavailableOpacity); - p.drawPixmap(QRect(right, top, mid - right, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.rect().x(), st::playerVolume.rect().y(), (mid - right) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor())); - } - } else { - if (mid > left) { - p.drawPixmap(QRect(left, top, mid - left, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.rect().x(), st::playerVolume.rect().y(), (mid - left) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor())); - } - if (right > mid) { - p.setOpacity(st::playerUnavailableOpacity); - p.drawPixmap(QRect(mid, top, right - mid, st::playerVolume.pxHeight()), App::sprite(), QRect(st::playerVolume.rect().x() + (mid - left) * cIntRetinaFactor(), st::playerVolume.rect().y(), (right - mid) * cIntRetinaFactor(), st::playerVolume.pxHeight() * cIntRetinaFactor())); - } - } - } - if (_fullAvailable && checkr.intersects(_fullRect)) { - float64 o = _stateHovers[OverFull]; - p.setOpacity(o * 1. + (1. - o) * st::playerInactiveOpacity); - p.drawSpriteCenterLeft(_fullRect, width(), st::playerFull); - } - if (checkr.intersects(_repeatRect)) { - float64 o = _stateHovers[OverRepeat]; - p.setOpacity(_repeat ? 1. : (o * st::playerInactiveOpacity + (1. - o) * st::playerUnavailableOpacity)); - p.drawSpriteCenterLeft(_repeatRect, width(), st::playerRepeat); - } - p.setOpacity(1.); - - p.setPen(st::playerTimeFg->p); - p.setFont(st::linkFont->f); - p.drawTextLeft(_infoRect.x() + _infoRect.width() - _timeWidth, _infoRect.y() + (_infoRect.height() - st::linkFont->height) / 2, width(), _time, _timeWidth); - - textstyleSet(&st::playerNameStyle); - p.setPen(st::playerFg->p); - _name.drawElided(p, _infoRect.x() + (rtl() ? (_timeWidth + st::playerSkip) : 0), _infoRect.y() + (_infoRect.height() - st::linkFont->height) / 2, _infoRect.width() - _timeWidth - st::playerSkip); - textstyleRestore(); - } - - if (_duration) { - float64 prg = (_down == OverPlayback) ? _downProgress : a_progress.current(); - int32 from = _playbackRect.x(), mid = qRound(_playbackRect.x() + prg * _playbackRect.width()), end = _playbackRect.x() + _playbackRect.width(); - if (mid > from) { - p.fillRect(rtl() ? (width() - mid) : from, height() - st::playerLineHeight, mid - from, _playbackRect.height(), st::playerLineActive->b); - } - if (end > mid) { - p.fillRect(rtl() ? (width() - end) : mid, height() - st::playerLineHeight, end - mid, st::playerLineHeight, st::playerLineInactive->b); - } - if (_stateHovers[OverPlayback] > 0) { - p.setOpacity(_stateHovers[OverPlayback]); - - int32 x = mid - (st::playerMoverSize.width() / 2); - p.fillRect(rtl() ? (width() - x - st::playerMoverSize.width()) : x, height() - st::playerMoverSize.height(), st::playerMoverSize.width(), st::playerMoverSize.height(), st::playerLineActive->b); - } - } else if (a_loadProgress.current() > 0) { - int32 from = _playbackRect.x(), mid = qRound(_playbackRect.x() + a_loadProgress.current() * _playbackRect.width()); - if (mid > from) { - p.fillRect(rtl() ? (width() - mid) : from, height() - st::playerLineHeight, mid - from, _playbackRect.height(), st::playerLineInactive->b); - } - } -} - -void PlayerWidget::mousePressEvent(QMouseEvent *e) { - QPoint pos(myrtlpoint(e->pos())); - - if (e->button() == Qt::LeftButton) { - _down = OverNone; - if (_song && _over == OverPlay) { - playPausePressed(); - return; - } else if (_over == OverPrev) { - prevPressed(); - } else if (_over == OverNext) { - nextPressed(); - } else if (_over == OverClose) { - _down = OverClose; - } else if (_over == OverVolume) { - _down = OverVolume; - _downCoord = pos.x() - _volumeRect.x(); - Global::SetSongVolume(snap((_downCoord - ((_volumeRect.width() - st::playerVolume.pxWidth()) / 2)) / float64(st::playerVolume.pxWidth()), 0., 1.)); - emit audioPlayer()->songVolumeChanged(); - rtlupdate(_volumeRect); - } else if (_over == OverPlayback) { - AudioMsgId playing; - auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); - if (playing == _song && playbackState.duration) { - if (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerStarting || playbackState.state == AudioPlayerResuming) { - audioPlayer()->pauseresume(AudioMsgId::Type::Song); - } - _down = OverPlayback; - _downProgress = snap((pos.x() - _playbackRect.x()) / float64(_playbackRect.width()), 0., 1.); - _downDuration = playbackState.duration; - _downFrequency = (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); - - rtlupdate(_playbackRect); - updateDownTime(); - } - } else if (_over == OverFull && _song) { - if (HistoryItem *item = App::histItemById(_song.contextId())) { - App::main()->showMediaOverview(item->history()->peer, OverviewMusicFiles); - } - } else if (_over == OverRepeat) { - _repeat = !_repeat; - updateOverRect(OverRepeat); - } - } -} - -void PlayerWidget::updateDownTime() { - QString time = formatDurationText(qRound(_downDuration * _downProgress) / _downFrequency); - if (time != _time) { - _time = time; - _timeWidth = st::linkFont->width(_time); - rtlupdate(_infoRect); - } -} - -void PlayerWidget::updateOverState(OverState newState) { - bool result = true; - if (_over != newState) { - updateOverRect(_over); - updateOverRect(newState); - if (_over != OverNone) { - _stateAnimations.remove(_over); - _stateAnimations[-_over] = getms() - ((1. - _stateHovers[_over]) * st::playerDuration); - if (!_a_state.animating()) _a_state.start(); - } else { - result = false; - } - _over = newState; - if (newState != OverNone) { - _stateAnimations.remove(-_over); - _stateAnimations[_over] = getms() - (_stateHovers[_over] * st::playerDuration); - if (!_a_state.animating()) _a_state.start(); - setCursor(style::cur_pointer); - } else { - setCursor(style::cur_default); - } - } -} - -void PlayerWidget::updateOverRect(OverState state) { - switch (state) { - case OverPrev: rtlupdate(_prevRect); break; - case OverPlay: rtlupdate(_playRect); break; - case OverNext: rtlupdate(_nextRect); break; - case OverClose: rtlupdate(_closeRect); break; - case OverVolume: rtlupdate(_volumeRect); break; - case OverFull: rtlupdate(_fullRect); break; - case OverRepeat: rtlupdate(_repeatRect); break; - case OverPlayback: rtlupdate(_playbackRect); break; - } -} - -void PlayerWidget::updateControls() { - _fullAvailable = (_index >= 0); - - History *history = _msgmigrated ? _migrated : _history; - _prevAvailable = _fullAvailable && ((_index > 0) || (_index == 0 && _migrated && !_msgmigrated && !_migrated->overview[OverviewMusicFiles].isEmpty())); - _nextAvailable = _fullAvailable && ((_index < history->overview[OverviewMusicFiles].size() - 1) || (_msgmigrated && _index == _migrated->overview[OverviewMusicFiles].size() - 1 && _history->overviewLoaded(OverviewMusicFiles) && _history->overviewCount(OverviewMusicFiles) > 0)); - resizeEvent(0); - update(); - if (_index >= 0 && _index < MediaOverviewStartPerPage) { - if (!_history->overviewLoaded(OverviewMusicFiles) || (_migrated && !_migrated->overviewLoaded(OverviewMusicFiles))) { - if (App::main()) { - if (_msgmigrated || (_migrated && _index == 0 && _history->overviewLoaded(OverviewMusicFiles))) { - App::main()->loadMediaBack(_migrated->peer, OverviewMusicFiles); - } else { - App::main()->loadMediaBack(_history->peer, OverviewMusicFiles); - if (_migrated && _index == 0 && _migrated->overview[OverviewMusicFiles].isEmpty() && !_migrated->overviewLoaded(OverviewMusicFiles)) { - App::main()->loadMediaBack(_migrated->peer, OverviewMusicFiles); - } - } - if (_msgmigrated && !_history->overviewCountLoaded(OverviewMusicFiles)) { - App::main()->preloadOverview(_history->peer, OverviewMusicFiles); - } - } - } - } -} - -void PlayerWidget::findCurrent() { - _index = -1; - if (!_history || !_song.contextId().msg) return; - - const History::MediaOverview *o = &(_msgmigrated ? _migrated : _history)->overview[OverviewMusicFiles]; - if ((_msgmigrated ? _migrated : _history)->channelId() == _song.contextId().channel) { - for (int i = 0, l = o->size(); i < l; ++i) { - if (o->at(i) == _song.contextId().msg) { - _index = i; - break; - } - } - } - preloadNext(); -} - -void PlayerWidget::preloadNext() { - if (_index < 0) return; - - History *history = _msgmigrated ? _migrated : _history; - const History::MediaOverview *o = &history->overview[OverviewMusicFiles]; - HistoryItem *next = 0; - if (_index < o->size() - 1) { - next = App::histItemById(history->channelId(), o->at(_index + 1)); - } else if (_msgmigrated && _index == o->size() - 1 && _history->overviewLoaded(OverviewMusicFiles) && _history->overviewCount(OverviewMusicFiles) > 0) { - next = App::histItemById(_history->channelId(), _history->overview[OverviewMusicFiles].at(0)); - } else if (_msgmigrated && _index == o->size() - 1 && !_history->overviewCountLoaded(OverviewMusicFiles)) { - if (App::main()) App::main()->preloadOverview(_history->peer, OverviewMusicFiles); - } - if (next) { - if (HistoryDocument *document = static_cast(next->getMedia())) { - DocumentData *d = document->getDocument(); - if (!d->loaded(DocumentData::FilePathResolveSaveFromDataSilent)) { - DocumentOpenClickHandler::doOpen(d, ActionOnLoadNone); - } - } - } -} - -void PlayerWidget::startPlay(const FullMsgId &msgId) { - if (HistoryItem *item = App::histItemById(msgId)) { - if (HistoryDocument *doc = static_cast(item->getMedia())) { - audioPlayer()->play(AudioMsgId(doc->getDocument(), item->fullId())); - updateState(); - } - } -} - -void PlayerWidget::clearSelection() { - for (StateAnimations::const_iterator i = _stateAnimations.cbegin(); i != _stateAnimations.cend(); ++i) { - _stateHovers[qAbs(i.key())] = 0; - } - _stateAnimations.clear(); -} - -void PlayerWidget::mediaOverviewUpdated(PeerData *peer, MediaOverviewType type) { - if (_history && (_history->peer == peer || (_migrated && _migrated->peer == peer)) && type == OverviewMusicFiles) { - _index = -1; - History *history = _msgmigrated ? _migrated : _history; - if (history->channelId() == _song.contextId().channel && _song.contextId().msg) { - for (int i = 0, l = history->overview[OverviewMusicFiles].size(); i < l; ++i) { - if (history->overview[OverviewMusicFiles].at(i) == _song.contextId().msg) { - _index = i; - preloadNext(); - break; - } - } - } - updateControls(); - } -} - -bool PlayerWidget::seekingSong(const AudioMsgId &song) const { - return (_down == OverPlayback) && (song == _song); -} - -void PlayerWidget::openPlayer() { - _playerOpened = true; - Shortcuts::enableMediaShortcuts(); -} - -bool PlayerWidget::isOpened() const { - return _playerOpened; -} - -void PlayerWidget::closePlayer() { - _playerOpened = false; - Shortcuts::disableMediaShortcuts(); -} - -void PlayerWidget::showPlayer() { - TWidget::show(); -} - -void PlayerWidget::hidePlayer() { - clearSelection(); - TWidget::hide(); -} - -void PlayerWidget::step_state(uint64 ms, bool timer) { - for (StateAnimations::iterator i = _stateAnimations.begin(); i != _stateAnimations.cend();) { - int32 over = qAbs(i.key()); - updateOverRect(OverState(over)); - - float64 dt = float64(ms - i.value()) / st::playerDuration; - if (dt >= 1) { - _stateHovers[over] = (i.key() > 0) ? 1 : 0; - i = _stateAnimations.erase(i); - } else { - _stateHovers[over] = (i.key() > 0) ? dt : (1 - dt); - ++i; - } - } - if (_stateAnimations.isEmpty()) { - _a_state.stop(); - } -} - -void PlayerWidget::mouseMoveEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); -} - -void PlayerWidget::leaveEvent(QEvent *e) { - _lastMousePos = QCursor::pos(); - updateSelected(); -} - -void PlayerWidget::updateSelected() { - QPoint pos(myrtlpoint(mapFromGlobal(_lastMousePos))); - - if (_down == OverVolume) { - int32 delta = (pos.x() - _volumeRect.x()) - _downCoord; - float64 startFrom = snap((_downCoord - ((_volumeRect.width() - st::playerVolume.pxWidth()) / 2)) / float64(st::playerVolume.pxWidth()), 0., 1.); - float64 add = delta / float64(4 * st::playerVolume.pxWidth()), result = snap(startFrom + add, 0., 1.); - if (result != Global::SongVolume()) { - Global::SetSongVolume(result); - emit audioPlayer()->songVolumeChanged(); - rtlupdate(_volumeRect); - } - } else if (_down == OverPlayback) { - _downProgress = snap((pos.x() - _playbackRect.x()) / float64(_playbackRect.width()), 0., 1.); - rtlupdate(_playbackRect); - updateDownTime(); - } else if (_down == OverNone) { - bool inInfo = ((pos.x() >= _infoRect.x()) && (pos.x() < _fullRect.x() + _fullRect.width()) && (pos.y() >= _playRect.y()) && (pos.y() <= _playRect.y() + _playRect.height())); - if (_prevAvailable && _prevRect.contains(pos)) { - updateOverState(OverPrev); - } else if (_nextAvailable && _nextRect.contains(pos)) { - updateOverState(OverNext); - } else if (_playRect.contains(pos)) { - updateOverState(OverPlay); - } else if (_closeRect.contains(pos)) { - updateOverState(OverClose); - } else if (_volumeRect.contains(pos)) { - updateOverState(OverVolume); - } else if (_repeatRect.contains(pos)) { - updateOverState(OverRepeat); - } else if (_duration && _playbackRect.contains(pos)) { - updateOverState(OverPlayback); - } else if (_fullAvailable && inInfo) { - updateOverState(OverFull); - } else if (_over != OverNone) { - updateOverState(OverNone); - } - } -} - -void PlayerWidget::mouseReleaseEvent(QMouseEvent *e) { - if (_down == OverVolume) { - mouseMoveEvent(e); - Local::writeUserSettings(); - } else if (_down == OverPlayback) { - mouseMoveEvent(e); - AudioMsgId playing; - auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); - if (playing == _song && playbackState.duration) { - _downDuration = playbackState.duration; - audioPlayer()->seek(qRound(_downProgress * _downDuration)); - - _showPause = true; - - a_progress = anim::fvalue(_downProgress, _downProgress); - _a_progress.stop(); - } - update(); - } else if (_down == OverClose && _over == OverClose) { - closePressed(); - } - _down = OverNone; -} - -void PlayerWidget::playPressed() { - if (!_song || isHidden()) return; - - AudioMsgId playing; - auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); - if (playing == _song && !(playbackState.state & AudioPlayerStoppedMask)) { - if (playbackState.state == AudioPlayerPausing || playbackState.state == AudioPlayerPaused || playbackState.state == AudioPlayerPausedAtEnd) { - audioPlayer()->pauseresume(AudioMsgId::Type::Song); - } - } else { - audioPlayer()->play(_song); - audioPlayer()->notify(_song); - } -} - -void PlayerWidget::pausePressed() { - if (!_song || isHidden()) return; - - AudioMsgId playing; - auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); - if (playing == _song && !(playbackState.state & AudioPlayerStoppedMask)) { - if (playbackState.state == AudioPlayerStarting || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerFinishing) { - audioPlayer()->pauseresume(AudioMsgId::Type::Song); - } - } -} - -void PlayerWidget::playPausePressed() { - if (!_song || isHidden()) return; - - AudioMsgId playing; - auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); - if (playing == _song && !(playbackState.state & AudioPlayerStoppedMask)) { - audioPlayer()->pauseresume(AudioMsgId::Type::Song); - } else { - audioPlayer()->play(_song); - audioPlayer()->notify(_song); - } -} - -void PlayerWidget::prevPressed() { - if (isHidden()) return; - - auto history = _msgmigrated ? _migrated : _history; - auto o = history ? &history->overview[OverviewMusicFiles] : nullptr; - if (audioPlayer() && o && _index > 0 && _index <= o->size() && !o->isEmpty()) { - startPlay(FullMsgId(history->channelId(), o->at(_index - 1))); - } else if (!_index && _history && _migrated && !_msgmigrated) { - o = &_migrated->overview[OverviewMusicFiles]; - if (!o->isEmpty()) { - startPlay(FullMsgId(_migrated->channelId(), o->at(o->size() - 1))); - } - } -} - -void PlayerWidget::nextPressed() { - if (isHidden()) return; - - auto history = _msgmigrated ? _migrated : _history; - auto o = history ? &history->overview[OverviewMusicFiles] : nullptr; - if (audioPlayer() && o && _index >= 0 && _index < o->size() - 1) { - startPlay(FullMsgId(history->channelId(), o->at(_index + 1))); - } else if (o && (_index == o->size() - 1) && _msgmigrated && _history->overviewLoaded(OverviewMusicFiles)) { - o = &_history->overview[OverviewMusicFiles]; - if (!o->isEmpty()) { - startPlay(FullMsgId(_history->channelId(), o->at(0))); - } - } -} - -void PlayerWidget::stopPressed() { - if (!_song || isHidden()) return; - - audioPlayer()->stop(AudioMsgId::Type::Song); -} - -void PlayerWidget::closePressed() { - stopPressed(); - if (App::main()) App::main()->closePlayer(); -} - -void PlayerWidget::resizeEvent(QResizeEvent *e) { - int32 availh = (height() - st::playerLineHeight); - int32 ch = st::playerPlay.pxHeight() + st::playerSkip, ct = (availh - ch) / 2; - _playbackRect = QRect(Adaptive::OneColumn() ? 0 : st::lineWidth, height() - st::playerMoverSize.height(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::playerMoverSize.height()); - _prevRect = _fullAvailable ? QRect(st::playerSkip / 2, ct, st::playerPrev.pxWidth() + st::playerSkip, ch) : QRect(); - _playRect = QRect(_fullAvailable ? (_prevRect.x() + _prevRect.width()) : (st::playerSkip / 2), ct, st::playerPlay.pxWidth() + st::playerSkip, ch); - _nextRect = _fullAvailable ? QRect(_playRect.x() + _playRect.width(), ct, st::playerNext.pxWidth() + st::playerSkip, ch) : QRect(); - - _closeRect = QRect(width() - st::playerSkip / 2 - st::playerClose.pxWidth() - st::playerSkip, ct, st::playerClose.pxWidth() + st::playerSkip, ch); - _volumeRect = QRect(_closeRect.x() - st::playerVolume.pxWidth() - st::playerSkip, ct, st::playerVolume.pxWidth() + st::playerSkip, ch); - _repeatRect = QRect(_volumeRect.x() - st::playerRepeat.pxWidth() - st::playerSkip, ct, st::playerRepeat.pxWidth() + st::playerSkip, ch); - _fullRect = _fullAvailable ? QRect(_repeatRect.x() - st::playerFull.pxWidth() - st::playerSkip, ct, st::playerFull.pxWidth() + st::playerSkip, ch) : QRect(); - - int32 infoLeft = (_fullAvailable ? (_nextRect.x() + _nextRect.width()) : (_playRect.x() + _playRect.width())); - _infoRect = QRect(infoLeft + st::playerSkip / 2, 0, (_fullAvailable ? _fullRect.x() : _repeatRect.x()) - infoLeft - st::playerSkip, availh); - - update(); -} - -void PlayerWidget::step_progress(float64 ms, bool timer) { - float64 dt = ms / (2 * AudioVoiceMsgUpdateView); - if (_duration && dt >= 1) { - _a_progress.stop(); - a_progress.finish(); - a_loadProgress.finish(); - } else { - a_progress.update(qMin(dt, 1.), anim::linear); - a_loadProgress.update(1. - (st::radialDuration / (st::radialDuration + ms)), anim::linear); - } - if (timer) rtlupdate(_playbackRect); -} - -void PlayerWidget::updateState() { - updateState(AudioMsgId(), AudioPlaybackState()); -} - -void PlayerWidget::updateState(AudioMsgId playing, AudioPlaybackState playbackState) { - if (!playing) { - playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song); - } - - bool songChanged = false; - if (playing && _song != playing) { - songChanged = true; - _song = playing; - if (HistoryItem *item = App::histItemById(_song.contextId())) { - _history = item->history(); - if (_history->peer->migrateFrom()) { - _migrated = App::history(_history->peer->migrateFrom()->id); - _msgmigrated = false; - } else if (_history->peer->migrateTo()) { - _migrated = _history; - _history = App::history(_migrated->peer->migrateTo()->id); - _msgmigrated = true; - } - findCurrent(); - } else { - _history = nullptr; - _msgmigrated = false; - _index = -1; - } - auto song = _song.audio()->song(); - if (song->performer.isEmpty()) { - _name.setText(st::linkFont, song->title.isEmpty() ? (_song.audio()->name.isEmpty() ? qsl("Unknown Track") : _song.audio()->name) : song->title, _textNameOptions); - } else { - TextCustomTagsMap custom; - custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink())); - _name.setRichText(st::linkFont, QString::fromUtf8("[c]%1[/c] \xe2\x80\x93 %2").arg(textRichPrepare(song->performer)).arg(song->title.isEmpty() ? qsl("Unknown Track") : textRichPrepare(song->title)), _textNameOptions, custom); - } - updateControls(); - } - - qint64 position = 0, duration = 0, display = 0; - if (playing == _song) { - if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) { - display = position = playbackState.position; - duration = playbackState.duration; - } else { - display = playbackState.duration; - } - display = display / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency); - } else if (_song) { - display = _song.audio()->song()->duration; - } - bool showPause = false, stopped = ((playbackState.state & AudioPlayerStoppedMask) || playbackState.state == AudioPlayerFinishing); - bool wasPlaying = (_duration != 0); - if (!stopped) { - showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting); - } - QString time; - float64 progress = 0.; - int32 loaded; - float64 loadProgress = 1.; - if (duration || !_song || !_song.audio() || !_song.audio()->loading()) { - time = (_down == OverPlayback) ? _time : formatDurationText(display); - progress = duration ? snap(float64(position) / duration, 0., 1.) : 0.; - loaded = duration ? _song.audio()->size : 0; - } else { - loaded = _song.audio()->loading() ? _song.audio()->loadOffset() : 0; - time = formatDownloadText(loaded, _song.audio()->size); - loadProgress = snap(float64(loaded) / qMax(_song.audio()->size, 1), 0., 1.); - } - if (time != _time || showPause != _showPause) { - if (_time != time) { - _time = time; - _timeWidth = st::linkFont->width(_time); - } - _showPause = showPause; - if (duration != _duration || position != _position || loaded != _loaded) { - if (!songChanged && ((!stopped && duration && _duration) || (!duration && _loaded != loaded))) { - a_progress.start(progress); - a_loadProgress.start(loadProgress); - _a_progress.start(); - } else { - a_progress = anim::fvalue(progress, progress); - a_loadProgress = anim::fvalue(loadProgress, loadProgress); - _a_progress.stop(); - } - _position = position; - _duration = duration; - _loaded = loaded; - } - update(); - } else if (duration != _duration || position != _position || loaded != _loaded) { - if (!songChanged && ((!stopped && duration && _duration) || (!duration && _loaded != loaded))) { - a_progress.start(progress); - a_loadProgress.start(loadProgress); - _a_progress.start(); - } else { - a_progress = anim::fvalue(progress, progress); - a_loadProgress = anim::fvalue(loadProgress, loadProgress); - _a_progress.stop(); - } - _position = position; - _duration = duration; - _loaded = loaded; - } - - if (wasPlaying && playbackState.state == AudioPlayerStoppedAtEnd) { - if (_repeat) { - if (_song.audio()) { -// audioPlayer()->play(_song); -// updateState(); - } - } else { -// nextPressed(); - } - } - - if (songChanged) { - emit playerSongChanged(_song.contextId()); - } -} diff --git a/Telegram/SourceFiles/playerwidget.h b/Telegram/SourceFiles/playerwidget.h deleted file mode 100644 index f93a91dcf..000000000 --- a/Telegram/SourceFiles/playerwidget.h +++ /dev/null @@ -1,138 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -It is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#pragma once - -#include "media/media_audio.h" - -class PlayerWidget : public TWidget { - Q_OBJECT - -public: - - PlayerWidget(QWidget *parent); - - void paintEvent(QPaintEvent *e); - void mousePressEvent(QMouseEvent *e); - void mouseMoveEvent(QMouseEvent *e); - void leaveEvent(QEvent *e); - void mouseReleaseEvent(QMouseEvent *e); - void resizeEvent(QResizeEvent *e); - - void playPressed(); - void pausePressed(); - void playPausePressed(); - void prevPressed(); - void nextPressed(); - void stopPressed(); - void closePressed(); - - void step_progress(float64 ms, bool timer); - void step_state(uint64 ms, bool timer); - - void updateState(AudioMsgId playing, AudioPlaybackState playbackState); - void updateState(); - void clearSelection(); - - void mediaOverviewUpdated(PeerData *peer, MediaOverviewType type); - - bool seekingSong(const AudioMsgId &song) const; - - void openPlayer(); - bool isOpened() const; - void closePlayer(); - - void showPlayer(); - void hidePlayer(); - -signals: - - void playerSongChanged(const FullMsgId &msgId); - -private: - - // Use startPlayer()/stopPlayer() or showPlayer()/hidePlayer() instead. - void show(); - void hide(); - - enum OverState { - OverNone = 0, - OverPrev, - OverPlay, - OverNext, - OverClose, - OverVolume, - OverFull, - OverRepeat, - OverPlayback, - - OverStateCount - }; - void updateDownTime(); - void updateOverState(OverState newState); - void updateOverRect(OverState state); - - void updateControls(); - void findCurrent(); - void preloadNext(); - - void startPlay(const FullMsgId &msgId); - - QPoint _lastMousePos; - void updateSelected(); - - bool _playerOpened = false; - - bool _prevAvailable = false; - bool _nextAvailable = false; - bool _fullAvailable = false; - OverState _over = OverNone; - OverState _down = OverNone; - int32 _downCoord = 0; - int64 _downDuration; - int32 _downFrequency = AudioVoiceMsgFrequency; - float64 _downProgress = 0.; - - float64 _stateHovers[OverStateCount]; - typedef QMap StateAnimations; - StateAnimations _stateAnimations; - Animation _a_state; - - AudioMsgId _song; - bool _msgmigrated = false; - int32 _index = -1; - History *_migrated = nullptr; - History *_history = nullptr; - QRect _playRect, _prevRect, _nextRect, _playbackRect; - QRect _closeRect, _volumeRect, _fullRect, _repeatRect, _infoRect; - int32 _timeWidth = 0; - bool _repeat = false; - QString _time; - Text _name; - bool _showPause = false; - int64 _position = 0; - int64 _duration = 0; - int32 _loaded = 0; - - anim::fvalue a_progress = { 0., 0. }; - anim::fvalue a_loadProgress = { 0., 0. }; - Animation _a_progress; - -}; diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index ea63da7ed..9a54a2a3e 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -38,7 +38,7 @@ Widget::Widget(QWidget *parent, PeerData *peer) : Window::SectionWidget(parent) _fixedBar->resizeToWidth(width()); _fixedBar->show(); - _fixedBarShadow->setMode(ToggleableShadow::Mode::HiddenFast); + _fixedBarShadow->setMode(Ui::ToggleableShadow::Mode::HiddenFast); _fixedBarShadow->raise(); updateAdaptiveLayout(); subscribe(Adaptive::Changed(), [this]() { updateAdaptiveLayout(); }); @@ -85,7 +85,7 @@ bool Widget::showInternal(const Window::SectionMemento *memento) { void Widget::setInternalState(const SectionMemento *memento) { myEnsureResized(this); _scroll->scrollToY(memento->_scrollTop); - _fixedBarShadow->setMode(memento->_scrollTop > 0 ? ToggleableShadow::Mode::ShownFast : ToggleableShadow::Mode::HiddenFast); + _fixedBarShadow->setMode(memento->_scrollTop > 0 ? Ui::ToggleableShadow::Mode::ShownFast : Ui::ToggleableShadow::Mode::HiddenFast); } std_::unique_ptr Widget::createMemento() const { @@ -116,14 +116,14 @@ void Widget::resizeEvent(QResizeEvent *e) { } int scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); - _fixedBarShadow->setMode((scrollTop > 0) ? ToggleableShadow::Mode::Shown : ToggleableShadow::Mode::Hidden); + _fixedBarShadow->setMode((scrollTop > 0) ? Ui::ToggleableShadow::Mode::Shown : Ui::ToggleableShadow::Mode::Hidden); } } void Widget::onScroll() { int scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); - _fixedBarShadow->setMode((scrollTop > 0) ? ToggleableShadow::Mode::Shown : ToggleableShadow::Mode::Hidden); + _fixedBarShadow->setMode((scrollTop > 0) ? Ui::ToggleableShadow::Mode::Shown : Ui::ToggleableShadow::Mode::Hidden); } void Widget::showAnimatedHook() { diff --git a/Telegram/SourceFiles/profile/profile_widget.h b/Telegram/SourceFiles/profile/profile_widget.h index 2bc9b91e7..fa48eca67 100644 --- a/Telegram/SourceFiles/profile/profile_widget.h +++ b/Telegram/SourceFiles/profile/profile_widget.h @@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #pragma once #include "window/section_widget.h" +#include "ui/widgets/shadow.h" class ScrollArea; @@ -70,7 +71,7 @@ private: ChildWidget _scroll; ChildWidget _inner; ChildWidget _fixedBar; - ChildWidget _fixedBarShadow; + ChildWidget _fixedBarShadow; }; diff --git a/Telegram/SourceFiles/pspecific_mac_p.mm b/Telegram/SourceFiles/pspecific_mac_p.mm index 3db075577..3136cdc1e 100644 --- a/Telegram/SourceFiles/pspecific_mac_p.mm +++ b/Telegram/SourceFiles/pspecific_mac_p.mm @@ -21,7 +21,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "mainwindow.h" #include "mainwidget.h" #include "application.h" -#include "playerwidget.h" #include "localstorage.h" #include "media/player/media_player_instance.h" #include "platform/mac/mac_utilities.h" diff --git a/Telegram/SourceFiles/settings/settings_widget.cpp b/Telegram/SourceFiles/settings/settings_widget.cpp index 114883840..f6506e02f 100644 --- a/Telegram/SourceFiles/settings/settings_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_widget.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "settings/settings_inner_widget.h" #include "settings/settings_fixed_bar.h" #include "styles/style_settings.h" +#include "ui/widgets/shadow.h" #include "ui/scrollarea.h" #include "mainwindow.h" #include "mainwidget.h" diff --git a/Telegram/SourceFiles/settings/settings_widget.h b/Telegram/SourceFiles/settings/settings_widget.h index b7912e47c..43207c997 100644 --- a/Telegram/SourceFiles/settings/settings_widget.h +++ b/Telegram/SourceFiles/settings/settings_widget.h @@ -22,6 +22,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "layerwidget.h" +namespace Ui { +class PlainShadow; +} // namespace Ui + namespace Settings { class InnerWidget; @@ -50,7 +54,7 @@ private: ChildWidget _scroll; ChildWidget _inner; ChildWidget _fixedBar; - ChildWidget _fixedBarShadow1, _fixedBarShadow2; + ChildWidget _fixedBarShadow1, _fixedBarShadow2; int _contentLeft = 0; diff --git a/Telegram/SourceFiles/stickers/emoji_pan.cpp b/Telegram/SourceFiles/stickers/emoji_pan.cpp index 63850ba28..a7f0e5563 100644 --- a/Telegram/SourceFiles/stickers/emoji_pan.cpp +++ b/Telegram/SourceFiles/stickers/emoji_pan.cpp @@ -1249,7 +1249,7 @@ void StickerPanInner::selectInlineResult(int row, int column) { } else if (document->loading()) { document->cancel(); } else { - DocumentOpenClickHandler::doOpen(document, ActionOnLoadNone); + DocumentOpenClickHandler::doOpen(document, nullptr, ActionOnLoadNone); } } else if (auto inlineResult = item->getResult()) { if (inlineResult->onChoose(item)) { diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index 1d646dacc..a41c50b8f 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -920,6 +920,11 @@ bool StickerData::setInstalled() const { } QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = false, const QString already = QString(), const QDir &dir = QDir()) { + auto alreadySavingFilename = data->loadingFilePath(); + if (!alreadySavingFilename.isEmpty()) { + return alreadySavingFilename; + } + QString name, filter, caption, prefix; MimeType mimeType = mimeTypeForName(data->mime); QStringList p = mimeType.globPatterns(); @@ -953,19 +958,15 @@ QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = fals return saveFileName(caption, filter, prefix, name, forceSavingAs, dir); } -void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { +void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, ActionOnLoad action) { if (!data->date) return; - HistoryItem *item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr); - FullMsgId msgId; - if (item) { - msgId = item->fullId(); - } + auto msgId = context ? context->fullId() : FullMsgId(); bool playVoice = data->voice() && audioPlayer(); bool playMusic = data->song() && audioPlayer(); bool playVideo = data->isVideo() && audioPlayer(); - bool playAnimation = data->isAnimation() && item && item->getMedia(); - const FileLocation &location(data->location(true)); + bool playAnimation = data->isAnimation(); + auto &location = data->location(true); if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) { if (playVoice) { AudioMsgId playing; @@ -992,9 +993,9 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { } } else if (playVideo) { if (!data->data().isEmpty()) { - App::wnd()->showDocument(data, item); + App::wnd()->showDocument(data, context); } else if (location.accessEnable()) { - App::wnd()->showDocument(data, item); + App::wnd()->showDocument(data, context); location.accessDisable(); } else { auto filepath = location.name(); @@ -1013,17 +1014,17 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { if (App::main()) App::main()->mediaMarkRead(data); } else if (data->size < MediaViewImageSizeLimit) { if (!data->data().isEmpty() && playAnimation) { - if (action == ActionOnLoadPlayInline && item->getMedia()) { - item->getMedia()->playInline(item); + if (action == ActionOnLoadPlayInline && context && context->getMedia()) { + context->getMedia()->playInline(context); } else { - App::wnd()->showDocument(data, item); + App::wnd()->showDocument(data, context); } } else if (location.accessEnable()) { if (data->isAnimation() || QImageReader(location.name()).canRead()) { - if (action == ActionOnLoadPlayInline && item && item->getMedia()) { - item->getMedia()->playInline(item); + if (action == ActionOnLoadPlayInline && context && context->getMedia()) { + context->getMedia()->playInline(context); } else { - App::wnd()->showDocument(data, item); + App::wnd()->showDocument(data, context); } } else { psOpenFile(location.name()); @@ -1050,11 +1051,13 @@ void DocumentOpenClickHandler::doOpen(DocumentData *data, ActionOnLoad action) { } void DocumentOpenClickHandler::onClickImpl() const { - doOpen(document(), document()->voice() ? ActionOnLoadNone : ActionOnLoadOpen); + auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr); + doOpen(document(), item, document()->voice() ? ActionOnLoadNone : ActionOnLoadOpen); } void GifOpenClickHandler::onClickImpl() const { - doOpen(document(), ActionOnLoadPlayInline); + auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr); + doOpen(document(), item, ActionOnLoadPlayInline); } void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) { @@ -1355,6 +1358,10 @@ bool DocumentData::loading() const { return _loader && _loader != CancelledMtpFileLoader; } +QString DocumentData::loadingFilePath() const { + return loading() ? _loader->fileName() : QString(); +} + bool DocumentData::displayLoading() const { return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading(); } @@ -1378,11 +1385,8 @@ bool DocumentData::uploading() const { } void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) { - _actionOnLoad = action; - _actionOnLoadMsgId = actionMsgId; - if (loaded(FilePathResolveChecked)) { - const FileLocation &l(location(true)); + auto &l = location(true); if (!toFile.isEmpty()) { if (!_data.isEmpty()) { QFile f(toFile); @@ -1393,22 +1397,29 @@ void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMs setLocation(FileLocation(StorageFilePartial, toFile)); Local::writeFileLocation(mediaKey(), FileLocation(mtpToStorageType(mtpc_storage_filePartial), toFile)); } else if (l.accessEnable()) { - QFile(l.name()).copy(toFile); + auto alreadyName = l.name(); + if (alreadyName != toFile) { + QFile(alreadyName).copy(toFile); + } l.accessDisable(); } } + _actionOnLoad = action; + _actionOnLoadMsgId = actionMsgId; performActionOnLoad(); return; } - if (_loader == CancelledMtpFileLoader) _loader = 0; + if (_loader == CancelledMtpFileLoader) _loader = nullptr; if (_loader) { if (!_loader->setFileName(toFile)) { - cancel(); - _loader = 0; + cancel(); // changes _actionOnLoad + _loader = nullptr; } } + _actionOnLoad = action; + _actionOnLoadMsgId = actionMsgId; if (_loader) { if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); } else { diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 7ca225b33..a8c5d5005 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1083,6 +1083,7 @@ public: }; bool loaded(FilePathResolveType type = FilePathResolveCached) const; bool loading() const; + QString loadingFilePath() const; bool displayLoading() const; void save(const QString &toFile, ActionOnLoad action = ActionOnLoadNone, const FullMsgId &actionMsgId = FullMsgId(), LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal, bool autoLoading = false); void cancel(); @@ -1320,7 +1321,7 @@ protected: class DocumentOpenClickHandler : public DocumentClickHandler { public: using DocumentClickHandler::DocumentClickHandler; - static void doOpen(DocumentData *document, ActionOnLoad action = ActionOnLoadOpen); + static void doOpen(DocumentData *document, HistoryItem *context, ActionOnLoad action = ActionOnLoadOpen); protected: void onClickImpl() const override; }; diff --git a/Telegram/SourceFiles/title.cpp b/Telegram/SourceFiles/title.cpp index 61d83f233..ab7a0f8a2 100644 --- a/Telegram/SourceFiles/title.cpp +++ b/Telegram/SourceFiles/title.cpp @@ -29,7 +29,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "boxes/aboutbox.h" #include "media/media_audio.h" #include "media/player/media_player_title_button.h" -#include "media/player/media_player_widget.h" +#include "media/player/media_player_panel.h" #include "media/player/media_player_instance.h" class TitleWidget::Hider : public TWidget { @@ -103,10 +103,16 @@ TitleWidget::TitleWidget(QWidget *parent) : TWidget(parent) subscribe(Adaptive::Changed(), [this]() { updateAdaptiveLayout(); }); if (Media::Player::exists()) { - subscribe(Media::Player::instance()->createdNotifier(), [this](const Media::Player::CreatedEvent &e) { + subscribe(Media::Player::instance()->createdNotifier(), [this](const Media::Player::PanelEvent &e) { if (!_player) { _player.create(this); - _player->installEventFilter(e.widget); + updateControlsVisibility(); + } + _player->installEventFilter(e.panel); + }); + subscribe(Media::Player::instance()->destroyedNotifier(), [this](const Media::Player::PanelEvent &e) { + if (_player) { + _player.destroyDelayed(); updateControlsVisibility(); } }); diff --git a/Telegram/SourceFiles/title.h b/Telegram/SourceFiles/title.h index 0643bac68..43e7923e7 100644 --- a/Telegram/SourceFiles/title.h +++ b/Telegram/SourceFiles/title.h @@ -27,7 +27,7 @@ class MainWindow; namespace Media { namespace Player { class TitleButton; -class CreatedEvent; +class PanelEvent; } // namespace Player } // namespace Media class AudioMsgId; @@ -71,8 +71,6 @@ private: void updateSystemButtonsVisibility(); void updateControlsPosition(); - void handleMediaPlayerCreated(const Media::Player::CreatedEvent &e); - style::color statusColor; class Hider; diff --git a/Telegram/SourceFiles/ui/effects/rect_shadow.cpp b/Telegram/SourceFiles/ui/effects/rect_shadow.cpp index e50c0bc22..cf0aec82e 100644 --- a/Telegram/SourceFiles/ui/effects/rect_shadow.cpp +++ b/Telegram/SourceFiles/ui/effects/rect_shadow.cpp @@ -84,9 +84,9 @@ void RectShadow::paint(Painter &p, const QRect &box, int shifty, Sides sides) { bool wasSmooth = p.renderHints().testFlag(QPainter::SmoothPixmapTransform); if (wasSmooth) p.setRenderHint(QPainter::SmoothPixmapTransform, false); - if (left) p.drawPixmap(box.left() - countsize + shifty, box.top() + (top ? minus : 0) + shifty, countsize - shifty, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _left, 0, 0, count - rshifty, 1); + if (left) p.drawPixmap(box.left() - countsize + shifty, box.top() + (top ? (minus + shifty) : 0), countsize - shifty, box.height() - (bottom ? (minus - shifty) : 0) - (top ? (minus + shifty) : 0), _left, 0, 0, count - rshifty, 1); if (top) p.drawPixmap(box.left() + (left ? minus : 0), box.top() - countsize + 2 * shifty, box.width() - (right ? minus : 0) - (left ? minus : 0), countsize - 2 * shifty, _top, 0, 0, 1, count - 2 * rshifty); - if (right) p.drawPixmap(box.left() + box.width(), box.top() + (top ? minus : 0) + shifty, countsize - shifty, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _right, rshifty, 0, count - rshifty, 1); + if (right) p.drawPixmap(box.left() + box.width(), box.top() + (top ? (minus + shifty) : 0), countsize - shifty, box.height() - (bottom ? (minus - shifty) : 0) - (top ? (minus + shifty) : 0), _right, rshifty, 0, count - rshifty, 1); if (bottom) p.drawPixmap(box.left() + (left ? minus : 0), box.top() + box.height(), box.width() - (right ? minus : 0) - (left ? minus : 0), countsize, _bottom, 0, 0, 1, count); if (wasSmooth) p.setRenderHint(QPainter::SmoothPixmapTransform); } diff --git a/Telegram/SourceFiles/ui/filedialog.cpp b/Telegram/SourceFiles/ui/filedialog.cpp index 4601b616e..d7b2d9174 100644 --- a/Telegram/SourceFiles/ui/filedialog.cpp +++ b/Telegram/SourceFiles/ui/filedialog.cpp @@ -282,13 +282,13 @@ using QueryList = QList; NeverFreedPointer Queries; void StartCallback() { - Queries.makeIfNull(); + Queries.createIfNull(); } } // namespace QueryId queryReadFile(const QString &caption, const QString &filter) { - Queries.makeIfNull(); + Queries.createIfNull(); Queries->push_back(Query(Query::Type::ReadFile, caption, filter)); Global::RefHandleFileDialogQueue().call(); @@ -296,7 +296,7 @@ QueryId queryReadFile(const QString &caption, const QString &filter) { } QueryId queryReadFiles(const QString &caption, const QString &filter) { - Queries.makeIfNull(); + Queries.createIfNull(); Queries->push_back(Query(Query::Type::ReadFiles, caption, filter)); Global::RefHandleFileDialogQueue().call(); @@ -304,7 +304,7 @@ QueryId queryReadFiles(const QString &caption, const QString &filter) { } QueryId queryWriteFile(const QString &caption, const QString &filter, const QString &filePath) { - Queries.makeIfNull(); + Queries.createIfNull(); Queries->push_back(Query(Query::Type::WriteFile, caption, filter, filePath)); Global::RefHandleFileDialogQueue().call(); @@ -312,7 +312,7 @@ QueryId queryWriteFile(const QString &caption, const QString &filter, const QStr } QueryId queryReadFolder(const QString &caption) { - Queries.makeIfNull(); + Queries.createIfNull(); Queries->push_back(Query(Query::Type::ReadFolder, caption)); Global::RefHandleFileDialogQueue().call(); diff --git a/Telegram/SourceFiles/ui/style/style_core.cpp b/Telegram/SourceFiles/ui/style/style_core.cpp index d56b9714c..846394ef8 100644 --- a/Telegram/SourceFiles/ui/style/style_core.cpp +++ b/Telegram/SourceFiles/ui/style/style_core.cpp @@ -46,7 +46,7 @@ void stopModules() { } // namespace void registerModule(ModuleBase *module) { - styleModules.makeIfNull(); + styleModules.createIfNull(); styleModules->push_back(module); } diff --git a/Telegram/SourceFiles/ui/style/style_core_icon.cpp b/Telegram/SourceFiles/ui/style/style_core_icon.cpp index 3eb35a2b9..d373b8b58 100644 --- a/Telegram/SourceFiles/ui/style/style_core_icon.cpp +++ b/Telegram/SourceFiles/ui/style/style_core_icon.cpp @@ -159,7 +159,7 @@ void MonoIcon::ensureLoaded() const { if (_owningPixmap) { _pixmap = createIconPixmap(_mask, _color); } else { - iconPixmaps.makeIfNull(); + iconPixmaps.createIfNull(); auto key = qMakePair(_mask, colorKey(_color->c)); auto i = iconPixmaps->constFind(key); if (i == iconPixmaps->cend()) { diff --git a/Telegram/SourceFiles/ui/toast/toast_manager.cpp b/Telegram/SourceFiles/ui/toast/toast_manager.cpp index 39c63f1c3..a65dcc7e5 100644 --- a/Telegram/SourceFiles/ui/toast/toast_manager.cpp +++ b/Telegram/SourceFiles/ui/toast/toast_manager.cpp @@ -40,7 +40,7 @@ Manager::Manager(QWidget *parent) : QObject(parent) { } Manager *Manager::instance(QWidget *parent) { - _managers.makeIfNull(); + _managers.createIfNull(); auto i = _managers->constFind(parent); if (i == _managers->cend()) { i = _managers->insert(parent, new Manager(parent)); diff --git a/Telegram/SourceFiles/ui/twidget.cpp b/Telegram/SourceFiles/ui/twidget.cpp index 20053a7b5..90c13f3dd 100644 --- a/Telegram/SourceFiles/ui/twidget.cpp +++ b/Telegram/SourceFiles/ui/twidget.cpp @@ -78,42 +78,6 @@ QPixmap myGrab(TWidget *target, QRect rect) { return result; } -enum class Mode { - Shown, - ShownFast, - Hidden, - HiddenFast -}; -void ToggleableShadow::setMode(Mode mode) { - if (mode == Mode::ShownFast || mode == Mode::HiddenFast) { - if (!_a_opacity.animating()) { - _a_opacity.finish(); - update(); - } - } - if (_shown && (mode == Mode::Hidden || mode == Mode::HiddenFast)) { - _shown = false; - if (mode == Mode::Hidden) { - _a_opacity.start([this] { update(); }, 1., 0., st::shadowToggleDuration); - } - } else if (!_shown && (mode == Mode::Shown || mode == Mode::ShownFast)) { - _shown = true; - if (mode == Mode::Shown) { - _a_opacity.start([this] { update(); }, 0., 1., st::shadowToggleDuration); - } - } -} - -void ToggleableShadow::paintEvent(QPaintEvent *e) { - Painter p(this); - if (_a_opacity.animating(getms())) { - p.setOpacity(_a_opacity.current()); - } else if (!_shown) { - return; - } - p.fillRect(e->rect(), _color); -} - void sendSynteticMouseEvent(QWidget *widget, QEvent::Type type, Qt::MouseButton button, const QPoint &globalPoint) { if (auto windowHandle = widget->window()->windowHandle()) { auto localPoint = windowHandle->mapFromGlobal(globalPoint); diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index 9cf8be01f..07ca80262 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -120,52 +120,59 @@ public: } }; -#define T_WIDGET public: \ -TWidget *tparent() { \ -return qobject_cast(parentWidget()); \ -} \ -const TWidget *tparent() const { \ - return qobject_cast(parentWidget()); \ -} \ -virtual void leaveToChildEvent(QEvent *e, QWidget *child) { /* e -- from enterEvent() of child TWidget */ \ -} \ -virtual void enterFromChildEvent(QEvent *e, QWidget *child) { /* e -- from leaveEvent() of child TWidget */ \ -} \ -void moveToLeft(int x, int y, int outerw = 0) { \ - move(rtl() ? ((outerw > 0 ? outerw : parentWidget()->width()) - x - width()) : x, y); \ -} \ -void moveToRight(int x, int y, int outerw = 0) { \ - move(rtl() ? x : ((outerw > 0 ? outerw : parentWidget()->width()) - x - width()), y); \ -} \ -QPoint myrtlpoint(int x, int y) const { \ - return rtlpoint(x, y, width()); \ -} \ -QPoint myrtlpoint(const QPoint p) const { \ - return rtlpoint(p, width()); \ -} \ -QRect myrtlrect(int x, int y, int w, int h) const { \ - return rtlrect(x, y, w, h, width()); \ -} \ -QRect myrtlrect(const QRect &r) { \ - return rtlrect(r, width()); \ -} \ -void rtlupdate(const QRect &r) { \ - update(myrtlrect(r)); \ -} \ -void rtlupdate(int x, int y, int w, int h) { \ - update(myrtlrect(x, y, w, h)); \ -} \ +#define T_WIDGET \ +public: \ + TWidget *tparent() { \ + return qobject_cast(parentWidget()); \ + } \ + const TWidget *tparent() const { \ + return qobject_cast(parentWidget()); \ + } \ + virtual void leaveToChildEvent(QEvent *e, QWidget *child) { /* e -- from enterEvent() of child TWidget */ \ + } \ + virtual void enterFromChildEvent(QEvent *e, QWidget *child) { /* e -- from leaveEvent() of child TWidget */ \ + } \ + void moveToLeft(int x, int y, int outerw = 0) { \ + move(rtl() ? ((outerw > 0 ? outerw : parentWidget()->width()) - x - width()) : x, y); \ + } \ + void moveToRight(int x, int y, int outerw = 0) { \ + move(rtl() ? x : ((outerw > 0 ? outerw : parentWidget()->width()) - x - width()), y); \ + } \ + void setGeometryToLeft(int x, int y, int w, int h, int outerw = 0) { \ + setGeometry(rtl() ? ((outerw > 0 ? outerw : parentWidget()->width()) - x - w) : x, y, w, h); \ + } \ + void setGeometryToRight(int x, int y, int w, int h, int outerw = 0) { \ + setGeometry(rtl() ? x : ((outerw > 0 ? outerw : parentWidget()->width()) - x - w), y, w, h); \ + } \ + QPoint myrtlpoint(int x, int y) const { \ + return rtlpoint(x, y, width()); \ + } \ + QPoint myrtlpoint(const QPoint p) const { \ + return rtlpoint(p, width()); \ + } \ + QRect myrtlrect(int x, int y, int w, int h) const { \ + return rtlrect(x, y, w, h, width()); \ + } \ + QRect myrtlrect(const QRect &r) const { \ + return rtlrect(r, width()); \ + } \ + void rtlupdate(const QRect &r) { \ + update(myrtlrect(r)); \ + } \ + void rtlupdate(int x, int y, int w, int h) { \ + update(myrtlrect(x, y, w, h)); \ + } \ protected: \ -void enterEvent(QEvent *e) override { \ - TWidget *p(tparent()); \ - if (p) p->leaveToChildEvent(e, this); \ - return enterEventHook(e); \ -} \ -void leaveEvent(QEvent *e) override { \ - TWidget *p(tparent()); \ - if (p) p->enterFromChildEvent(e, this); \ - return leaveEventHook(e); \ -} + void enterEvent(QEvent *e) override { \ + TWidget *p(tparent()); \ + if (p) p->leaveToChildEvent(e, this); \ + return enterEventHook(e); \ + } \ + void leaveEvent(QEvent *e) override { \ + TWidget *p(tparent()); \ + if (p) p->enterFromChildEvent(e, this); \ + return leaveEventHook(e); \ + } class TWidget : public QWidget { Q_OBJECT @@ -239,48 +246,6 @@ protected: void myEnsureResized(QWidget *target); QPixmap myGrab(TWidget *target, QRect rect = QRect()); -class PlainShadow : public TWidget { -public: - PlainShadow(QWidget *parent, const style::color &color) : TWidget(parent), _color(color) { - } - -protected: - void paintEvent(QPaintEvent *e) override { - Painter(this).fillRect(e->rect(), _color->b); - } - -private: - const style::color &_color; - -}; - -class ToggleableShadow : public TWidget { -public: - ToggleableShadow(QWidget *parent, const style::color &color) : TWidget(parent), _color(color) { - } - - enum class Mode { - Shown, - ShownFast, - Hidden, - HiddenFast - }; - void setMode(Mode mode); - - bool isFullyShown() const { - return _shown && !_a_opacity.animating(); - } - -protected: - void paintEvent(QPaintEvent *e) override; - -private: - const style::color &_color; - FloatAnimation _a_opacity; - bool _shown = true; - -}; - class SingleDelayedCall : public QObject { Q_OBJECT diff --git a/Telegram/SourceFiles/ui/widgets/continuous_slider.cpp b/Telegram/SourceFiles/ui/widgets/continuous_slider.cpp new file mode 100644 index 000000000..2863855b2 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/continuous_slider.cpp @@ -0,0 +1,129 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/widgets/continuous_slider.h" + +namespace Ui { + +ContinuousSlider::ContinuousSlider(QWidget *parent) : TWidget(parent) +, _a_value(animation(this, &ContinuousSlider::step_value)) { + setCursor(style::cur_pointer); +} + +float64 ContinuousSlider::value() const { + return a_value.current(); +} + +void ContinuousSlider::setDisabled(bool disabled) { + if (_disabled != disabled) { + _disabled = disabled; + setCursor(_disabled ? style::cur_default : style::cur_pointer); + update(); + } +} + +void ContinuousSlider::setValue(float64 value, bool animated) { + if (animated) { + a_value.start(value); + _a_value.start(); + } else { + a_value = anim::fvalue(value, value); + _a_value.stop(); + } + update(); +} + +void ContinuousSlider::setFadeOpacity(float64 opacity) { + _fadeOpacity = opacity; + update(); +} + +void ContinuousSlider::step_value(float64 ms, bool timer) { + float64 dt = ms / (2 * AudioVoiceMsgUpdateView); + if (dt >= 1) { + _a_value.stop(); + a_value.finish(); + } else { + a_value.update(qMin(dt, 1.), anim::linear); + } + if (timer) update(); +} + +void ContinuousSlider::mouseMoveEvent(QMouseEvent *e) { + if (_mouseDown) { + updateDownValueFromPos(e->pos()); + } +} + +float64 ContinuousSlider::computeValue(const QPoint &pos) const { + auto seekRect = myrtlrect(getSeekRect()); + auto result = isHorizontal() ? + (pos.x() - seekRect.x()) / float64(seekRect.width()) : + (1. - (pos.y() - seekRect.y()) / float64(seekRect.height())); + return snap(result, 0., 1.); +} + +void ContinuousSlider::mousePressEvent(QMouseEvent *e) { + _mouseDown = true; + _downValue = computeValue(e->pos()); + update(); + if (_changeProgressCallback) { + _changeProgressCallback(_downValue); + } +} + +void ContinuousSlider::mouseReleaseEvent(QMouseEvent *e) { + if (_mouseDown) { + _mouseDown = false; + if (_changeFinishedCallback) { + _changeFinishedCallback(_downValue); + } + a_value = anim::fvalue(_downValue, _downValue); + _a_value.stop(); + update(); + } +} + +void ContinuousSlider::updateDownValueFromPos(const QPoint &pos) { + _downValue = computeValue(pos); + update(); + if (_changeProgressCallback) { + _changeProgressCallback(_downValue); + } +} + +void ContinuousSlider::enterEvent(QEvent *e) { + setOver(true); +} + +void ContinuousSlider::leaveEvent(QEvent *e) { + setOver(false); +} + +void ContinuousSlider::setOver(bool over) { + if (_over == over) return; + + _over = over; + auto from = _over ? 0. : 1., to = _over ? 1. : 0.; + _a_over.start([this] { update(); }, from, to, getOverDuration()); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/continuous_slider.h b/Telegram/SourceFiles/ui/widgets/continuous_slider.h new file mode 100644 index 000000000..1d063fa65 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/continuous_slider.h @@ -0,0 +1,109 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace Ui { + +class ContinuousSlider : public TWidget { +public: + ContinuousSlider(QWidget *parent); + + enum class Direction { + Horizontal, + Vertical, + }; + void setDirection(Direction direction) { + _direction = direction; + update(); + } + + float64 value() const; + void setValue(float64 value, bool animated); + void setFadeOpacity(float64 opacity); + void setDisabled(bool disabled); + + using Callback = base::lambda_unique; + void setChangeProgressCallback(Callback &&callback) { + _changeProgressCallback = std_::move(callback); + } + void setChangeFinishedCallback(Callback &&callback) { + _changeFinishedCallback = std_::move(callback); + } + bool isChanging() const { + return _mouseDown; + } + +protected: + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void enterEvent(QEvent *e) override; + void leaveEvent(QEvent *e) override; + + float64 fadeOpacity() const { + return _fadeOpacity; + } + float64 getCurrentValue(uint64 ms) { + _a_value.step(ms); + return _mouseDown ? _downValue : a_value.current(); + } + float64 getCurrentOverFactor(uint64 ms) { + return _a_over.current(ms, _over ? 1. : 0.); + } + bool isDisabled() const { + return _disabled; + } + Direction getDirection() const { + return _direction; + } + bool isHorizontal() const { + return (_direction == Direction::Horizontal); + } + +private: + virtual QRect getSeekRect() const = 0; + virtual float64 getOverDuration() const = 0; + + void step_value(float64 ms, bool timer); + void setOver(bool over); + float64 computeValue(const QPoint &pos) const; + void updateDownValueFromPos(const QPoint &pos); + + Direction _direction = Direction::Horizontal; + bool _disabled = false; + + Callback _changeProgressCallback; + Callback _changeFinishedCallback; + + bool _over = false; + FloatAnimation _a_over; + + anim::fvalue a_value = { 0., 0. }; + Animation _a_value; + + bool _mouseDown = false; + float64 _downValue = 0.; + + float64 _fadeOpacity = 1.; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/filled_slider.cpp b/Telegram/SourceFiles/ui/widgets/filled_slider.cpp new file mode 100644 index 000000000..83dedadec --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/filled_slider.cpp @@ -0,0 +1,73 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/widgets/filled_slider.h" + +#include "styles/style_widgets.h" + +namespace Ui { + +FilledSlider::FilledSlider(QWidget *parent, const style::FilledSlider &st) : ContinuousSlider(parent) +, _st(st) { +} + +QRect FilledSlider::getSeekRect() const { + return QRect(0, 0, width(), height()); +} + +float64 FilledSlider::getOverDuration() const { + return _st.duration; +} + +void FilledSlider::paintEvent(QPaintEvent *e) { + Painter p(this); + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::HighQualityAntialiasing); + + auto masterOpacity = fadeOpacity(); + auto ms = getms(); + auto disabled = isDisabled(); + auto over = getCurrentOverFactor(ms); + auto lineWidth = _st.lineWidth + ((_st.fullWidth - _st.lineWidth) * over); + auto lineWidthRounded = qFloor(lineWidth); + auto lineWidthPartial = lineWidth - lineWidthRounded; + auto seekRect = getSeekRect(); + auto value = getCurrentValue(ms); + auto from = seekRect.x(), mid = disabled ? from : qRound(from + value * seekRect.width()), end = from + seekRect.width(); + if (mid > from) { + p.setOpacity(masterOpacity); + p.fillRect(from, height() - lineWidthRounded, (mid - from), lineWidthRounded, _st.activeFg); + if (lineWidthPartial > 0.01) { + p.setOpacity(masterOpacity * lineWidthPartial); + p.fillRect(from, height() - lineWidthRounded - 1, (mid - from), 1, _st.activeFg); + } + } + if (end > mid && over > 0) { + p.setOpacity(masterOpacity * over); + p.fillRect(mid, height() - lineWidthRounded, (end - mid), lineWidthRounded, _st.inactiveFg); + if (lineWidthPartial > 0.01) { + p.setOpacity(masterOpacity * over * lineWidthPartial); + p.fillRect(mid, height() - lineWidthRounded - 1, (end - mid), 1, _st.inactiveFg); + } + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/filled_slider.h b/Telegram/SourceFiles/ui/widgets/filled_slider.h new file mode 100644 index 000000000..7037b9544 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/filled_slider.h @@ -0,0 +1,46 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "ui/widgets/continuous_slider.h" + +namespace style { +struct FilledSlider; +} // namespace style + +namespace Ui { + +class FilledSlider : public ContinuousSlider { +public: + FilledSlider(QWidget *parent, const style::FilledSlider &st); + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + QRect getSeekRect() const override; + float64 getOverDuration() const override; + + const style::FilledSlider &_st; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/media_slider.cpp b/Telegram/SourceFiles/ui/widgets/media_slider.cpp index a4a3db825..dacfd2651 100644 --- a/Telegram/SourceFiles/ui/widgets/media_slider.cpp +++ b/Telegram/SourceFiles/ui/widgets/media_slider.cpp @@ -25,148 +25,78 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Ui { -MediaSlider::MediaSlider(QWidget *parent, const style::MediaSlider &st) : TWidget(parent) -, _st(st) -, _a_value(animation(this, &MediaSlider::step_value)) { - setCursor(style::cur_pointer); +MediaSlider::MediaSlider(QWidget *parent, const style::MediaSlider &st) : ContinuousSlider(parent) +, _st(st) { } -float64 MediaSlider::value() const { - return a_value.current(); +QRect MediaSlider::getSeekRect() const { + return isHorizontal() + ? QRect(_st.seekSize.width() / 2, 0, width() - _st.seekSize.width(), height()) + : QRect(0, _st.seekSize.height() / 2, width(), height() - _st.seekSize.width()); } -void MediaSlider::setDisabled(bool disabled) { - if (_disabled != disabled) { - _disabled = disabled; - setCursor(_disabled ? style::cur_default : style::cur_pointer); - update(); - } -} - -void MediaSlider::setValue(float64 value, bool animated) { - if (animated) { - a_value.start(value); - _a_value.start(); - } else { - a_value = anim::fvalue(value, value); - _a_value.stop(); - } - update(); -} - -void MediaSlider::setFadeOpacity(float64 opacity) { - _fadeOpacity = opacity; - update(); -} - -void MediaSlider::step_value(float64 ms, bool timer) { - float64 dt = ms / (2 * AudioVoiceMsgUpdateView); - if (dt >= 1) { - _a_value.stop(); - a_value.finish(); - } else { - a_value.update(qMin(dt, 1.), anim::linear); - } - if (timer) update(); -} - -int MediaSlider::lineLeft() const { - return (_st.seekSize.width() / 2); -} - -int MediaSlider::lineWidth() const { - return (width() - _st.seekSize.width()); +float64 MediaSlider::getOverDuration() const { + return _st.duration; } void MediaSlider::paintEvent(QPaintEvent *e) { Painter p(this); - - int radius = _st.width / 2; - p.setOpacity(_fadeOpacity); p.setPen(Qt::NoPen); p.setRenderHint(QPainter::HighQualityAntialiasing); + auto horizontal = isHorizontal(); auto ms = getms(); - _a_value.step(ms); - auto over = _a_over.current(ms, _over ? 1. : 0.); - int skip = lineLeft(); - int length = lineWidth(); - float64 prg = _mouseDown ? _downValue : a_value.current(); - int32 from = skip, mid = _disabled ? 0 : qRound(from + prg * length), end = from + length; + auto masterOpacity = fadeOpacity(); + auto radius = _st.width / 2; + auto disabled = isDisabled(); + auto over = getCurrentOverFactor(ms); + auto seekRect = getSeekRect(); + auto value = getCurrentValue(ms); + + // invert colors and value for vertical + if (!horizontal) value = 1. - value; + + auto markerFrom = (horizontal ? seekRect.x() : seekRect.y()); + auto markerLength = (horizontal ? seekRect.width() : seekRect.height()); + auto from = _alwaysDisplayMarker ? 0 : markerFrom; + auto length = _alwaysDisplayMarker ? (horizontal ? width() : height()) : markerLength; + auto mid = disabled ? from : qRound(from + value * length); + auto end = from + length; if (mid > from) { - p.setClipRect(0, 0, mid, height()); - p.setOpacity(_fadeOpacity * (over * _st.activeOpacity + (1. - over) * _st.inactiveOpacity)); - p.setBrush(_st.activeFg); - p.drawRoundedRect(from, (height() - _st.width) / 2, mid + radius - from, _st.width, radius, radius); + auto fromClipRect = horizontal ? QRect(0, 0, mid, height()) : QRect(0, 0, width(), mid); + auto fromRect = horizontal + ? QRect(from, (height() - _st.width) / 2, mid + radius - from, _st.width) + : QRect((width() - _st.width) / 2, from, _st.width, mid + radius - from); + p.setClipRect(fromClipRect); + p.setOpacity(masterOpacity * (over * _st.activeOpacity + (1. - over) * _st.inactiveOpacity)); + p.setBrush(horizontal ? _st.activeFg : _st.inactiveFg); + p.drawRoundedRect(fromRect, radius, radius); } if (end > mid) { - p.setClipRect(mid, 0, width() - mid, height()); - p.setOpacity(_fadeOpacity); - p.setBrush(_st.inactiveFg); - p.drawRoundedRect(mid - radius, (height() - _st.width) / 2, end - (mid - radius), _st.width, radius, radius); + auto endClipRect = horizontal ? QRect(mid, 0, width() - mid, height()) : QRect(0, mid, width(), height() - mid); + auto endRect = horizontal + ? QRect(mid - radius, (height() - _st.width) / 2, end - (mid - radius), _st.width) + : QRect((width() - _st.width) / 2, mid - radius, _st.width, end - (mid - radius)); + p.setClipRect(endClipRect); + p.setOpacity(masterOpacity); + p.setBrush(horizontal ? _st.inactiveFg : _st.activeFg); + p.drawRoundedRect(endRect, radius, radius); } - if (!_disabled && over > 0) { - int x = mid - skip; - p.setClipRect(rect()); - p.setOpacity(_fadeOpacity * _st.activeOpacity); - auto seekButton = QRect(x, (height() - _st.seekSize.height()) / 2, _st.seekSize.width(), _st.seekSize.height()); - int remove = ((1. - over) * _st.seekSize.width()) / 2.; - if (remove * 2 < _st.seekSize.width()) { + auto markerSizeRatio = disabled ? 0. : (_alwaysDisplayMarker ? 1. : over); + if (markerSizeRatio > 0) { + auto position = qRound(markerFrom + value * markerLength) - (horizontal ? seekRect.x() : seekRect.y()); + auto seekButton = horizontal + ? QRect(position, (height() - _st.seekSize.height()) / 2, _st.seekSize.width(), _st.seekSize.height()) + : QRect((width() - _st.seekSize.width()) / 2, position, _st.seekSize.width(), _st.seekSize.height()); + auto size = horizontal ? _st.seekSize.width() : _st.seekSize.height(); + auto remove = static_cast(((1. - markerSizeRatio) * size) / 2.); + if (remove * 2 < size) { + p.setClipRect(rect()); + p.setOpacity(masterOpacity * _st.activeOpacity); p.setBrush(_st.activeFg); p.drawEllipse(seekButton.marginsRemoved(QMargins(remove, remove, remove, remove))); } } } -void MediaSlider::mouseMoveEvent(QMouseEvent *e) { - if (_mouseDown) { - updateDownValueFromPos(e->pos().x()); - } -} - -void MediaSlider::mousePressEvent(QMouseEvent *e) { - _mouseDown = true; - _downValue = snap((e->pos().x() - lineLeft()) / float64(lineWidth()), 0., 1.); - update(); - if (_changeProgressCallback) { - _changeProgressCallback(_downValue); - } -} - -void MediaSlider::mouseReleaseEvent(QMouseEvent *e) { - if (_mouseDown) { - _mouseDown = false; - if (_changeFinishedCallback) { - _changeFinishedCallback(_downValue); - } - a_value = anim::fvalue(_downValue, _downValue); - _a_value.stop(); - update(); - } -} - -void MediaSlider::updateDownValueFromPos(int pos) { - _downValue = snap((pos - lineLeft()) / float64(lineWidth()), 0., 1.); - update(); - if (_changeProgressCallback) { - _changeProgressCallback(_downValue); - } -} - -void MediaSlider::enterEvent(QEvent *e) { - setOver(true); -} - -void MediaSlider::leaveEvent(QEvent *e) { - setOver(false); -} - -void MediaSlider::setOver(bool over) { - if (_over == over) return; - - _over = over; - auto from = _over ? 0. : 1., to = _over ? 1. : 0.; - _a_over.start([this] { update(); }, from, to, _st.duration); -} - } // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/media_slider.h b/Telegram/SourceFiles/ui/widgets/media_slider.h index 3b4433c02..4bf1967d3 100644 --- a/Telegram/SourceFiles/ui/widgets/media_slider.h +++ b/Telegram/SourceFiles/ui/widgets/media_slider.h @@ -20,62 +20,32 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #pragma once +#include "ui/widgets/continuous_slider.h" + namespace style { struct MediaSlider; } // namespace style namespace Ui { -class MediaSlider : public TWidget { +class MediaSlider : public ContinuousSlider { public: MediaSlider(QWidget *parent, const style::MediaSlider &st); - float64 value() const; - void setValue(float64 value, bool animated); - void setFadeOpacity(float64 opacity); - void setDisabled(bool disabled); - - using Callback = base::lambda_unique; - void setChangeProgressCallback(Callback &&callback) { - _changeProgressCallback = std_::move(callback); - } - void setChangeFinishedCallback(Callback &&callback) { - _changeFinishedCallback = std_::move(callback); + void setAlwaysDisplayMarker(bool alwaysDisplayMarker) { + _alwaysDisplayMarker = alwaysDisplayMarker; + update(); } protected: void paintEvent(QPaintEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - void enterEvent(QEvent *e) override; - void leaveEvent(QEvent *e) override; private: - void step_value(float64 ms, bool timer); - void setOver(bool over); - void updateDownValueFromPos(int pos); - - int lineLeft() const; - int lineWidth() const; + QRect getSeekRect() const override; + float64 getOverDuration() const override; const style::MediaSlider &_st; - - bool _disabled = false; - - Callback _changeProgressCallback; - Callback _changeFinishedCallback; - - bool _over = false; - FloatAnimation _a_over; - - anim::fvalue a_value = { 0., 0. }; - Animation _a_value; - - bool _mouseDown = false; - float64 _downValue = 0.; - - float64 _fadeOpacity = 1.; + bool _alwaysDisplayMarker = false; }; diff --git a/Telegram/SourceFiles/ui/widgets/shadow.cpp b/Telegram/SourceFiles/ui/widgets/shadow.cpp new file mode 100644 index 000000000..c6bbf0df9 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/shadow.cpp @@ -0,0 +1,56 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "ui/widgets/shadow.h" + +namespace Ui { + +void ToggleableShadow::setMode(Mode mode) { + if (mode == Mode::ShownFast || mode == Mode::HiddenFast) { + if (!_a_opacity.animating()) { + _a_opacity.finish(); + update(); + } + } + if (_shown && (mode == Mode::Hidden || mode == Mode::HiddenFast)) { + _shown = false; + if (mode == Mode::Hidden) { + _a_opacity.start([this] { update(); }, 1., 0., st::shadowToggleDuration); + } + } else if (!_shown && (mode == Mode::Shown || mode == Mode::ShownFast)) { + _shown = true; + if (mode == Mode::Shown) { + _a_opacity.start([this] { update(); }, 0., 1., st::shadowToggleDuration); + } + } +} + +void ToggleableShadow::paintEvent(QPaintEvent *e) { + Painter p(this); + if (_a_opacity.animating(getms())) { + p.setOpacity(_a_opacity.current()); + } else if (!_shown) { + return; + } + p.fillRect(e->rect(), _color); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/shadow.h b/Telegram/SourceFiles/ui/widgets/shadow.h new file mode 100644 index 000000000..92e1f3443 --- /dev/null +++ b/Telegram/SourceFiles/ui/widgets/shadow.h @@ -0,0 +1,47 @@ +#pragma once + +namespace Ui { + +class PlainShadow : public TWidget { +public: + PlainShadow(QWidget *parent, const style::color &color) : TWidget(parent), _color(color) { + } + +protected: + void paintEvent(QPaintEvent *e) override { + Painter(this).fillRect(e->rect(), _color->b); + } + +private: + const style::color &_color; + +}; + +class ToggleableShadow : public TWidget { +public: + ToggleableShadow(QWidget *parent, const style::color &color) : TWidget(parent), _color(color) { + } + + enum class Mode { + Shown, + ShownFast, + Hidden, + HiddenFast + }; + void setMode(Mode mode); + + bool isFullyShown() const { + return _shown && !_a_opacity.animating(); + } + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + const style::color &_color; + FloatAnimation _a_opacity; + bool _shown = true; + +}; + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h b/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h index 4a2645e70..380e65209 100644 --- a/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h +++ b/Telegram/SourceFiles/ui/widgets/widget_slide_wrap.h @@ -27,19 +27,20 @@ namespace Ui { template class WidgetSlideWrap : public TWidget { public: + using UpdateCallback = base::lambda_unique; WidgetSlideWrap(QWidget *parent, Widget *entity , style::margins entityPadding - , base::lambda_unique &&updateCallback + , UpdateCallback &&updateCallback , int duration = st::widgetSlideDuration) : TWidget(parent) - , _entity(entity) - , _padding(entityPadding) - , _duration(duration) - , _updateCallback(std_::move(updateCallback)) - , _a_height(animation(this, &WidgetSlideWrap::step_height)) { - entity->setParent(this); - entity->moveToLeft(_padding.left(), _padding.top()); - _realSize = entity->rect().marginsAdded(_padding).size(); - entity->installEventFilter(this); + , _entity(entity) + , _padding(entityPadding) + , _duration(duration) + , _updateCallback(std_::move(updateCallback)) + , _a_height(animation(this, &WidgetSlideWrap::step_height)) { + _entity->setParent(this); + _entity->moveToLeft(_padding.left(), _padding.top()); + _realSize = _entity->rect().marginsAdded(_padding).size(); + _entity->installEventFilter(this); resize(_realSize); } @@ -91,6 +92,7 @@ public: } void showFast() { + show(); _a_height.stop(); resize(_realSize); if (_updateCallback) { @@ -152,7 +154,7 @@ private: bool _inResizeToWidth = false; style::margins _padding; int _duration; - base::lambda_unique _updateCallback; + UpdateCallback _updateCallback; style::size _realSize; int _forceHeight = -1; diff --git a/Telegram/SourceFiles/ui/widgets/widgets.style b/Telegram/SourceFiles/ui/widgets/widgets.style index 8b6973705..636c4e055 100644 --- a/Telegram/SourceFiles/ui/widgets/widgets.style +++ b/Telegram/SourceFiles/ui/widgets/widgets.style @@ -43,6 +43,14 @@ MediaSlider { duration: int; } +FilledSlider { + fullWidth: pixels; + lineWidth: pixels; + activeFg: color; + inactiveFg: color; + duration: int; +} + widgetSlideDuration: 200; discreteSliderHeight: 39px; diff --git a/Telegram/SourceFiles/window/chat_background.cpp b/Telegram/SourceFiles/window/chat_background.cpp index 442c3c1f2..294e53471 100644 --- a/Telegram/SourceFiles/window/chat_background.cpp +++ b/Telegram/SourceFiles/window/chat_background.cpp @@ -83,7 +83,7 @@ void ChatBackground::setTile(bool tile) { } ChatBackground *chatBackground() { - instance.makeIfNull(); + instance.createIfNull(); return instance.data(); } diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index e9c2e6557..6fa37344b 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -57,7 +57,7 @@ internal::Widget::Direction notificationShiftDirection() { } // namespace void start() { - ManagerInstance.makeIfNull(); + ManagerInstance.createIfNull(); } Manager *manager() { diff --git a/Telegram/SourceFiles/window/player_wrap_widget.cpp b/Telegram/SourceFiles/window/player_wrap_widget.cpp new file mode 100644 index 000000000..7bd1f8ab6 --- /dev/null +++ b/Telegram/SourceFiles/window/player_wrap_widget.cpp @@ -0,0 +1,44 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org +*/ +#include "stdafx.h" +#include "window/player_wrap_widget.h" + +#include "ui/widgets/shadow.h" + +namespace Window { + +PlayerWrapWidget::PlayerWrapWidget(QWidget *parent, UpdateCallback &&updateCallback) : Parent(parent + , new Media::Player::Widget(parent) + , style::margins(0, 0, 0, 0) + , std_::move(updateCallback)) { +} + +void PlayerWrapWidget::resizeEvent(QResizeEvent *e) { + updateShadowGeometry(); + Parent::resizeEvent(e); +} + +void PlayerWrapWidget::updateShadowGeometry() { + auto skip = Adaptive::OneColumn() ? 0 : st::lineWidth; + entity()->setShadowGeometryToLeft(skip, height() - st::lineWidth, width() - skip, st::lineWidth); +} + +} // namespace Window diff --git a/Telegram/SourceFiles/window/player_wrap_widget.h b/Telegram/SourceFiles/window/player_wrap_widget.h new file mode 100644 index 000000000..742795003 --- /dev/null +++ b/Telegram/SourceFiles/window/player_wrap_widget.h @@ -0,0 +1,40 @@ +#pragma once + +#include "ui/widgets/widget_slide_wrap.h" +#include "media/player/media_player_widget.h" + +namespace Ui { +class PlainShadow; +} // namespace Ui + +namespace Window { + +class PlayerWrapWidget : public Ui::WidgetSlideWrap { + using Parent = Ui::WidgetSlideWrap; + +public: + using UpdateCallback = Parent::UpdateCallback; + PlayerWrapWidget(QWidget *parent, UpdateCallback &&updateCallback); + + void updateAdaptiveLayout() { + updateShadowGeometry(); + } + void showShadow() { + entity()->showShadow(); + } + void hideShadow() { + entity()->hideShadow(); + } + int contentHeight() const { + return height() - st::lineWidth; + } + +protected: + void resizeEvent(QResizeEvent *e) override; + +private: + void updateShadowGeometry(); + +}; + +} // namespace Window diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index da6c92f3e..5a39b4656 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -129,8 +129,6 @@ '<(src_loc)/overviewwidget.h', '<(src_loc)/passcodewidget.cpp', '<(src_loc)/passcodewidget.h', - '<(src_loc)/playerwidget.cpp', - '<(src_loc)/playerwidget.h', '<(src_loc)/localimageloader.cpp', '<(src_loc)/localimageloader.h', '<(src_loc)/localstorage.cpp', @@ -268,6 +266,8 @@ '<(src_loc)/media/player/media_player_instance.h', '<(src_loc)/media/player/media_player_list.cpp', '<(src_loc)/media/player/media_player_list.h', + '<(src_loc)/media/player/media_player_panel.cpp', + '<(src_loc)/media/player/media_player_panel.h', '<(src_loc)/media/player/media_player_title_button.cpp', '<(src_loc)/media/player/media_player_title_button.h', '<(src_loc)/media/player/media_player_volume_controller.cpp', @@ -467,13 +467,19 @@ '<(src_loc)/ui/toast/toast_manager.h', '<(src_loc)/ui/toast/toast_widget.cpp', '<(src_loc)/ui/toast/toast_widget.h', + '<(src_loc)/ui/widgets/continuous_slider.cpp', + '<(src_loc)/ui/widgets/continuous_slider.h', + '<(src_loc)/ui/widgets/discrete_slider.cpp', + '<(src_loc)/ui/widgets/discrete_slider.h', + '<(src_loc)/ui/widgets/filled_slider.cpp', + '<(src_loc)/ui/widgets/filled_slider.h', '<(src_loc)/ui/widgets/label_simple.cpp', '<(src_loc)/ui/widgets/label_simple.h', '<(src_loc)/ui/widgets/media_slider.cpp', '<(src_loc)/ui/widgets/media_slider.h', + '<(src_loc)/ui/widgets/shadow.cpp', + '<(src_loc)/ui/widgets/shadow.h', '<(src_loc)/ui/widgets/widget_slide_wrap.h', - '<(src_loc)/ui/widgets/discrete_slider.cpp', - '<(src_loc)/ui/widgets/discrete_slider.h', '<(src_loc)/ui/animation.cpp', '<(src_loc)/ui/animation.h', '<(src_loc)/ui/button.cpp', @@ -514,6 +520,8 @@ '<(src_loc)/window/notifications_manager_default.h', '<(src_loc)/window/notifications_utilities.cpp', '<(src_loc)/window/notifications_utilities.h', + '<(src_loc)/window/player_wrap_widget.cpp', + '<(src_loc)/window/player_wrap_widget.h', '<(src_loc)/window/section_widget.cpp', '<(src_loc)/window/section_widget.h', '<(src_loc)/window/slide_animation.cpp',