From b7550f63c904ae3f1020cbad1b4bae8665199317 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 22 May 2017 18:25:49 +0300 Subject: [PATCH] Add a floating player for round video messages. --- .../chat_helpers/tabbed_section.cpp | 10 +- .../SourceFiles/chat_helpers/tabbed_section.h | 12 +- .../chat_helpers/tabbed_selector.cpp | 8 + .../chat_helpers/tabbed_selector.h | 4 + Telegram/SourceFiles/dialogswidget.cpp | 15 +- Telegram/SourceFiles/dialogswidget.h | 8 +- Telegram/SourceFiles/facades.cpp | 23 ++- Telegram/SourceFiles/history/history_item.cpp | 1 + Telegram/SourceFiles/historywidget.cpp | 39 ++-- Telegram/SourceFiles/historywidget.h | 8 +- Telegram/SourceFiles/mainwidget.cpp | 166 ++++++++++++++- Telegram/SourceFiles/mainwidget.h | 150 ++++++++------ .../SourceFiles/media/media_clip_reader.cpp | 11 + .../SourceFiles/media/media_clip_reader.h | 5 +- .../media/player/media_player.style | 3 + .../media/player/media_player_float.cpp | 193 ++++++++++++++++++ .../media/player/media_player_float.h | 76 +++++++ .../media/player/media_player_instance.cpp | 10 +- .../media/player/media_player_widget.cpp | 6 +- Telegram/SourceFiles/overviewwidget.cpp | 13 +- Telegram/SourceFiles/overviewwidget.h | 8 +- .../profile/profile_common_groups_section.cpp | 14 +- .../profile/profile_common_groups_section.h | 8 +- .../profile/profile_section_memento.cpp | 4 +- .../profile/profile_section_memento.h | 2 +- .../SourceFiles/profile/profile_widget.cpp | 10 +- Telegram/SourceFiles/profile/profile_widget.h | 6 +- Telegram/SourceFiles/structs.h | 3 + Telegram/SourceFiles/ui/twidget.h | 25 ++- Telegram/SourceFiles/window/section_memento.h | 3 +- .../SourceFiles/window/section_widget.cpp | 2 +- Telegram/SourceFiles/window/section_widget.h | 61 +++++- Telegram/gyp/telegram_sources.txt | 2 + 33 files changed, 771 insertions(+), 138 deletions(-) create mode 100644 Telegram/SourceFiles/media/player/media_player_float.cpp create mode 100644 Telegram/SourceFiles/media/player/media_player_float.h diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp index 5e7ac4e303..aeea08dfb1 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_section.cpp @@ -28,7 +28,7 @@ namespace ChatHelpers { TabbedSection::TabbedSection(QWidget *parent, gsl::not_null controller) : TabbedSection(parent, controller, object_ptr(this, controller)) { } -TabbedSection::TabbedSection(QWidget *parent, gsl::not_null controller, object_ptr selector) : TWidget(parent) +TabbedSection::TabbedSection(QWidget *parent, gsl::not_null controller, object_ptr selector) : Window::AbstractSectionWidget(parent, controller) , _selector(std::move(selector)) { resize(st::emojiPanWidth, st::emojiPanMaxHeight); @@ -73,4 +73,12 @@ void TabbedSection::stickersInstalled(uint64 setId) { _selector->stickersInstalled(setId); } +bool TabbedSection::wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) { + return _selector->wheelEventFromFloatPlayer(e); +} + +QRect TabbedSection::rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) { + return _selector->rectForFloatPlayer(); +} + } // namespace ChatHelpers diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_section.h b/Telegram/SourceFiles/chat_helpers/tabbed_section.h index b3bc0b59e4..2b78cbda2f 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_section.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_section.h @@ -20,17 +20,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "ui/twidget.h" - -namespace Window { -class Controller; -} // namespace Window +#include "window/section_widget.h" namespace ChatHelpers { class TabbedSelector; -class TabbedSection : public TWidget { +class TabbedSection : public Window::AbstractSectionWidget { public: TabbedSection(QWidget *parent, gsl::not_null controller); TabbedSection(QWidget *parent, gsl::not_null controller, object_ptr selector); @@ -46,6 +42,10 @@ public: void stickersInstalled(uint64 setId); + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) override; + QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) override; + protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp index bfdf79dc89..8ef926079e 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.cpp @@ -453,6 +453,14 @@ QImage TabbedSelector::grabForAnimation() { return result; } +bool TabbedSelector::wheelEventFromFloatPlayer(QEvent *e) { + return _scroll->viewportEvent(e); +} + +QRect TabbedSelector::rectForFloatPlayer() { + return mapToGlobal(_scroll->geometry()); +} + TabbedSelector::~TabbedSelector() = default; void TabbedSelector::hideFinished() { diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h index 27975bf0f7..0b45532457 100644 --- a/Telegram/SourceFiles/chat_helpers/tabbed_selector.h +++ b/Telegram/SourceFiles/chat_helpers/tabbed_selector.h @@ -77,6 +77,10 @@ public: _beforeHidingCallback = std::move(callback); } + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e); + QRect rectForFloatPlayer(); + ~TabbedSelector(); class Inner; diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index 99ad052f7a..ee4fbd9f79 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -2265,15 +2265,14 @@ void DialogsWidget::UpdateButton::paintEvent(QPaintEvent *e) { } } -DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null controller) : TWidget(parent) -, _controller(controller) +DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null controller) : Window::AbstractSectionWidget(parent, controller) , _mainMenuToggle(this, st::dialogsMenuToggle) , _filter(this, st::dialogsFilter, lang(lng_dlg_filter)) , _jumpToDate(this, object_ptr(this, st::dialogsCalendar)) , _cancelSearch(this, st::dialogsCancelSearch) , _lockUnlock(this, st::dialogsLock) , _scroll(this, st::dialogsScroll) { - _inner = _scroll->setOwnedWidget(object_ptr(this, _controller, parent)); + _inner = _scroll->setOwnedWidget(object_ptr(this, controller, parent)); connect(_inner, SIGNAL(draggingScrollDelta(int)), this, SLOT(onDraggingScrollDelta(int))); connect(_inner, SIGNAL(mustScrollTo(int,int)), _scroll, SLOT(scrollToY(int,int))); connect(_inner, SIGNAL(dialogMoved(int,int)), this, SLOT(onDialogMoved(int,int))); @@ -2434,6 +2433,14 @@ void DialogsWidget::showAnimated(Window::SlideDirection direction, const Window: _a_show.start([this] { animationCallback(); }, 0., 1., st::slideDuration, Window::SlideAnimation::transition()); } +bool DialogsWidget::wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) { + return _scroll->viewportEvent(e); +} + +QRect DialogsWidget::rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) { + return mapToGlobal(_scroll->geometry()); +} + void DialogsWidget::animationCallback() { update(); if (!_a_show.animating()) { @@ -2986,7 +2993,7 @@ void DialogsWidget::setSearchInPeer(PeerData *peer) { _searchInMigrated = newSearchInPeer ? newSearchInPeer->migrateFrom() : nullptr; if (newSearchInPeer != _searchInPeer) { _searchInPeer = newSearchInPeer; - _controller->searchInPeerChanged().notify(_searchInPeer, true); + controller()->searchInPeerChanged().notify(_searchInPeer, true); updateJumpToDateVisibility(); } _inner->searchInPeer(_searchInPeer); diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index ab6d7a386e..5bda639d3a 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -305,7 +305,7 @@ private: Q_DECLARE_OPERATORS_FOR_FLAGS(DialogsInner::UpdateRowSections); -class DialogsWidget : public TWidget, public RPCSender, private base::Subscriber { +class DialogsWidget : public Window::AbstractSectionWidget, public RPCSender { Q_OBJECT public: @@ -347,6 +347,10 @@ public: void searchMessages(const QString &query, PeerData *inPeer = 0); void onSearchMore(); + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) override; + QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) override; + void notify_userIsContactChanged(UserData *user, bool fromThisApp); void notify_historyMuteUpdated(History *history); @@ -409,8 +413,6 @@ private: bool searchFailed(DialogsSearchRequestType type, const RPCError &error, mtpRequestId req); bool peopleFailed(const RPCError &error, mtpRequestId req); - gsl::not_null _controller; - bool _dragInScroll = false; bool _dragForward = false; QTimer _chooseByDragTimer; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 3aae8222c2..2ffd033f96 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "profile/profile_section_memento.h" #include "core/click_handler_types.h" +#include "media/media_clip_reader.h" #include "observer_peer.h" #include "mainwindow.h" #include "mainwidget.h" @@ -348,8 +349,8 @@ void inlineKeyboardMoved(const HistoryItem *item, int oldKeyboardTop, int newKey } bool switchInlineBotButtonReceived(const QString &query, UserData *samePeerBot, MsgId samePeerReplyTo) { - if (auto m = App::main()) { - return m->notify_switchInlineBotButtonReceived(query, samePeerBot, samePeerReplyTo); + if (auto main = App::main()) { + return main->notify_switchInlineBotButtonReceived(query, samePeerBot, samePeerReplyTo); } return false; } @@ -367,13 +368,23 @@ void historyMuteUpdated(History *history) { } void handlePendingHistoryUpdate() { - if (MainWidget *m = App::main()) { - m->notify_handlePendingHistoryUpdate(); + if (auto main = App::main()) { + main->notify_handlePendingHistoryUpdate(); } - for_const (HistoryItem *item, Global::PendingRepaintItems()) { + for (auto item : base::take(Global::RefPendingRepaintItems())) { Ui::repaintHistoryItem(item); + + // Start the video if it is waiting for that. + if (item->pendingInitDimensions()) { + if (auto media = item->getMedia()) { + if (auto reader = media->getClipReader()) { + if (!reader->started() && reader->mode() == Media::Clip::Reader::Mode::Video) { + item->history()->resizeGetHeight(item->history()->width); + } + } + } + } } - Global::RefPendingRepaintItems().clear(); } void unreadCounterUpdated() { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ad178facad..0eba7c2085 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -973,6 +973,7 @@ void HistoryItem::clipCallback(Media::Clip::Notification notification) { if (!stopped) { setPendingInitDimensions(); Notify::historyItemLayoutChanged(this); + Global::RefPendingRepaintItems().insert(this); } } break; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index fafdf1259e..994614ad65 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -478,10 +478,9 @@ QPoint SilentToggle::tooltipPos() const { return QCursor::pos(); } -HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null controller) : TWidget(parent) -, _controller(controller) +HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null controller) : Window::AbstractSectionWidget(parent, controller) , _fieldBarCancel(this, st::historyReplyCancel) -, _topBar(this, _controller) +, _topBar(this, controller) , _scroll(this, st::historyScroll, false) , _historyDown(_scroll, st::historyToDown) , _fieldAutocomplete(this) @@ -496,11 +495,11 @@ HistoryWidget::HistoryWidget(QWidget *parent, gsl::not_null , _botKeyboardHide(this, st::historyBotKeyboardHide) , _botCommandStart(this, st::historyBotCommandStart) , _silent(this) -, _field(this, _controller, st::historyComposeField, lang(lng_message_ph)) +, _field(this, controller, st::historyComposeField, lang(lng_message_ph)) , _recordCancelWidth(st::historyRecordFont->width(lang(lng_record_cancel))) , _a_recording(animation(this, &HistoryWidget::step_recording)) , _kbScroll(this, st::botKbScroll) -, _tabbedPanel(this, _controller) +, _tabbedPanel(this, controller) , _tabbedSelector(_tabbedPanel->getSelector()) , _attachDragDocument(this) , _attachDragPhoto(this) @@ -770,7 +769,7 @@ void HistoryWidget::applyInlineBotQuery(UserData *bot, const QString &query) { inlineBotChanged(); } if (!_inlineResults) { - _inlineResults.create(this, _controller); + _inlineResults.create(this, controller()); _inlineResults->setResultSelectedCallback([this](InlineBots::Result *result, UserData *bot) { onInlineResultSend(result, bot); }); @@ -1856,7 +1855,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re } _scroll->hide(); - _list = _scroll->setOwnedWidget(object_ptr(this, _controller, _scroll, _history)); + _list = _scroll->setOwnedWidget(object_ptr(this, controller(), _scroll, _history)); _list->show(); _updateHistoryItems.stop(); @@ -1904,7 +1903,7 @@ void HistoryWidget::showHistory(const PeerId &peerId, MsgId showAtMsgId, bool re App::main()->dlgUpdated(wasHistory ? wasHistory->peer : nullptr, wasMsgId); emit historyShown(_history, _showAtMsgId); - _controller->historyPeerChanged().notify(_peer, true); + controller()->historyPeerChanged().notify(_peer, true); update(); } @@ -3411,6 +3410,22 @@ bool HistoryWidget::eventFilter(QObject *obj, QEvent *e) { return TWidget::eventFilter(obj, e); } +bool HistoryWidget::wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) { + if (playerColumn == Window::Column::Third && _tabbedSection) { + auto tabbedColumn = (myColumn == Window::Column::First) ? Window::Column::Second : Window::Column::Third; + return _tabbedSection->wheelEventFromFloatPlayer(e, tabbedColumn, playerColumn); + } + return _scroll->viewportEvent(e); +} + +QRect HistoryWidget::rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) { + if (playerColumn == Window::Column::Third && _tabbedSection) { + auto tabbedColumn = (myColumn == Window::Column::First) ? Window::Column::Second : Window::Column::Third; + return mapToGlobal(_tabbedSection->rectForFloatPlayer(tabbedColumn, playerColumn)); + } + return mapToGlobal(_scroll->geometry()); +} + DragState HistoryWidget::getDragState(const QMimeData *d) { if (!d || d->hasFormat(qsl("application/x-td-forward-selected")) @@ -3792,14 +3807,14 @@ void HistoryWidget::updateTabbedSelectorSectionShown() { // sendPendingMoveAndResizeEvents() for all widgets in the window, which can lead // to a new HistoryWidget::resizeEvent() call and an infinite recursion here. if (_tabbedSectionUsed) { - _tabbedSection.create(this, _controller, _tabbedPanel->takeSelector()); + _tabbedSection.create(this, controller(), _tabbedPanel->takeSelector()); _tabbedSection->setCancelledCallback([this] { setInnerFocus(); }); _tabbedSelectorToggle->setColorOverrides(&st::historyAttachEmojiActive, &st::historyRecordVoiceFgActive, &st::historyRecordVoiceRippleBgActive); _rightShadow.create(this, st::shadowFg); auto destroyingPanel = std::move(_tabbedPanel); updateControlsVisibility(); } else { - _tabbedPanel.create(this, _controller, _tabbedSection->takeSelector()); + _tabbedPanel.create(this, controller(), _tabbedSection->takeSelector()); _tabbedSelectorToggle->installEventFilter(_tabbedPanel); _tabbedSection.destroy(); _tabbedSelectorToggle->setColorOverrides(nullptr, nullptr, nullptr); @@ -3859,12 +3874,12 @@ void HistoryWidget::toggleTabbedSelectorMode() { updateTabbedSelectorSectionShown(); recountChatWidth(); updateControlsGeometry(); - } else if (_controller->canProvideChatWidth(minimalWidthForTabbedSelectorSection())) { + } else if (controller()->canProvideChatWidth(minimalWidthForTabbedSelectorSection())) { if (!AuthSession::Current().data().tabbedSelectorSectionEnabled()) { AuthSession::Current().data().setTabbedSelectorSectionEnabled(true); AuthSession::Current().saveDataDelayed(kSaveTabbedSelectorSectionTimeoutMs); } - _controller->provideChatWidth(minimalWidthForTabbedSelectorSection()); + controller()->provideChatWidth(minimalWidthForTabbedSelectorSection()); updateTabbedSelectorSectionShown(); recountChatWidth(); updateControlsGeometry(); diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 06360ac8aa..ccbf41243c 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -161,7 +161,7 @@ private: }; -class HistoryWidget : public TWidget, public RPCSender, private base::Subscriber { +class HistoryWidget final : public Window::AbstractSectionWidget, public RPCSender { Q_OBJECT public: @@ -337,6 +337,10 @@ public: void deleteContextItem(bool forEveryone); void deleteSelectedItems(bool forEveryone); + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) override; + QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) override; + void app_sendBotCallback(const HistoryMessageReplyMarkup::Button *button, const HistoryItem *msg, int row, int col); void ui_repaintHistoryItem(const HistoryItem *item); @@ -532,8 +536,6 @@ private: void hideSelectorControlsAnimated(); int countMembersDropdownHeightMax() const; - gsl::not_null _controller; - MsgId _replyToId = 0; Text _replyToName; int _replyToNameVersion = 0; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 8f45252c27..9b32422b7f 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -57,6 +57,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "media/player/media_player_widget.h" #include "media/player/media_player_volume_controller.h" #include "media/player/media_player_instance.h" +#include "media/player/media_player_float.h" #include "base/qthelp_regex.h" #include "base/qthelp_url.h" #include "window/themes/window_theme.h" @@ -97,6 +98,12 @@ StackItemSection::StackItemSection(std::unique_ptr &&mem StackItemSection::~StackItemSection() { } +template +MainWidget::Float::Float(QWidget *parent, HistoryItem *item, ToggleCallback callback) : widget(parent, item, [this, toggle = std::move(callback)](bool visible) { + toggle(this, visible); +}) { +} + MainWidget::MainWidget(QWidget *parent, gsl::not_null controller) : TWidget(parent) , _controller(controller) , _dialogsWidth(st::dialogsWidthMin) @@ -194,6 +201,11 @@ MainWidget::MainWidget(QWidget *parent, gsl::not_null contr } } }); + subscribe(Media::Player::instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) { + if (type == AudioMsgId::Type::Voice) { + checkCurrentFloatPlayer(); + } + }); subscribe(Adaptive::Changed(), [this]() { handleAdaptiveLayoutUpdate(); }); @@ -218,6 +230,114 @@ MainWidget::MainWidget(QWidget *parent, gsl::not_null contr #endif // !TDESKTOP_DISABLE_AUTOUPDATE } +void MainWidget::checkCurrentFloatPlayer() { + auto state = Media::Player::instance()->current(AudioMsgId::Type::Voice); + auto fullId = state.contextId(); + auto last = currentFloatPlayer(); + if (!last || last->widget->itemId() != fullId) { + if (last) { + last->widget->detach(); + } + if (auto item = App::histItemById(fullId)) { + if (auto media = item->getMedia()) { + if (auto document = media->getDocument()) { + if (document->isRoundVideo()) { + _playerFloats.push_back(std::make_unique(this, item, [this](Float *instance, bool visible) { + toggleFloatPlayer(instance, visible); + })); + toggleFloatPlayer(currentFloatPlayer(), true); + } + } + } + } + } +} + +void MainWidget::toggleFloatPlayer(Float *instance, bool visible) { + if (instance->visible != visible) { + instance->visible = visible; + instance->visibleAnimation.start([this, instance] { + updateFloatPlayerPosition(instance); + }, visible ? 0. : 1., visible ? 1. : 0., st::slideDuration, visible ? anim::easeOutCirc : anim::linear); + updateFloatPlayerPosition(instance); + } +} + +void MainWidget::updateFloatPlayerPosition(Float *instance) { + auto visible = instance->visibleAnimation.current(instance->visible ? 1. : 0.); + if (visible == 0. && !instance->visible) { + instance->widget->hide(); + if (instance->widget->detached()) { + InvokeQueued(instance->widget, [this, instance] { + removeFloatPlayer(instance); + }); + } + return; + } + + instance->widget->setOpacity(visible * visible); + if (instance->widget->isHidden()) { + instance->widget->show(); + } + + auto column = instance->column; + auto section = getFloatPlayerSection(&column); + auto rect = section->rectForFloatPlayer(column, instance->column); + auto position = rect.topLeft(); + if (Window::IsBottomCorner(instance->corner)) { + position.setY(position.y() + rect.height() - instance->widget->height()); + } + if (Window::IsRightCorner(instance->corner)) { + position.setX(position.x() + rect.width() - instance->widget->width()); + } + position = mapFromGlobal(position); + + auto hiddenTop = Window::IsTopCorner(instance->corner) ? -instance->widget->height() : height(); + auto visibleTop = position.y(); + instance->widget->move(position.x(), anim::interpolate(hiddenTop, visibleTop, visible)); +} + +void MainWidget::removeFloatPlayer(Float *instance) { + auto widget = std::move(instance->widget); + auto i = std::find_if(_playerFloats.begin(), _playerFloats.end(), [instance](auto &item) { + return (item.get() == instance); + }); + t_assert(i != _playerFloats.end()); + _playerFloats.erase(i); + + // ~QWidget() can call HistoryInner::enterEvent() which can + // lead to repaintHistoryItem() and we'll have an instance + // in _playerFloats with destroyed widget. So we destroy the + // instance first and only after that destroy the widget. + widget.destroy(); +} + +Window::AbstractSectionWidget *MainWidget::getFloatPlayerSection(gsl::not_null column) { + if (!Adaptive::Normal()) { + *column = Adaptive::OneColumn() ? Window::Column::First : Window::Column::Second; + if (Adaptive::OneColumn() && selectingPeer()) { + return _dialogs; + } else if (_overview) { + return _overview; + } else if (_wideSection) { + return _wideSection; + } else if (!Adaptive::OneColumn() || _history->peer()) { + return _history; + } + return _dialogs; + } + if (*column == Window::Column::First) { + return _dialogs; + } + *column = Window::Column::Second; + if (_overview) { + return _overview; + } else if (_wideSection) { + return _wideSection; + } + return _history; +} + bool MainWidget::onForward(const PeerId &peer, ForwardWhatMessages what) { PeerData *p = App::peer(peer); if (!peer || (p->isChannel() && !p->asChannel()->canPublish() && p->asChannel()->isBroadcast()) || (p->isChat() && !p->asChat()->canWrite()) || (p->isUser() && p->asUser()->isInaccessible())) { @@ -573,11 +693,17 @@ void MainWidget::ui_repaintHistoryItem(const HistoryItem *item) { _playerPlaylist->ui_repaintHistoryItem(item); _playerPanel->ui_repaintHistoryItem(item); if (_overview) _overview->ui_repaintHistoryItem(item); + if (auto last = currentFloatPlayer()) { + last->widget->ui_repaintHistoryItem(item); + } } void MainWidget::notify_historyItemLayoutChanged(const HistoryItem *item) { _history->notify_historyItemLayoutChanged(item); if (_overview) _overview->notify_historyItemLayoutChanged(item); + if (auto last = currentFloatPlayer()) { + last->widget->ui_repaintHistoryItem(item); + } } void MainWidget::notify_historyMuteUpdated(History *history) { @@ -2331,6 +2457,8 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::Show _overview = nullptr; } } + + updateControlsGeometry(); if (onlyDialogs) { _history->hide(); if (!_a_show.animating()) { @@ -2342,9 +2470,7 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::Show } } } else { - if (noPeer) { - updateControlsGeometry(); - } else if (wasActivePeer != activePeer()) { + if (!noPeer && wasActivePeer != activePeer()) { if (activePeer()->isChannel()) { activePeer()->asChannel()->ptsWaitingForShortPoll(WaitForChannelGetDifference); } @@ -2527,6 +2653,9 @@ Window::SectionSlideParams MainWidget::prepareShowAnimation(bool willHaveTopBarS result.withTabbedSection = false; } + for (auto &instance : _playerFloats) { + instance->widget->hide(); + } if (_player) { _player->hideShadow(); } @@ -2579,6 +2708,11 @@ Window::SectionSlideParams MainWidget::prepareShowAnimation(bool willHaveTopBarS if (_player) { _player->showShadow(); } + for (auto &instance : _playerFloats) { + if (instance->visible) { + instance->widget->show(); + } + } return result; } @@ -2607,7 +2741,7 @@ void MainWidget::showNewWideSection(const Window::SectionMemento *memento, bool auto sectionTop = getSectionTop(); auto newWideGeometry = QRect(_history->x(), sectionTop, _history->width(), height() - sectionTop); - auto newWideSection = memento->createWidget(this, newWideGeometry); + auto newWideSection = memento->createWidget(this, _controller, newWideGeometry); auto animatedShow = [this] { if (_a_show.animating() || App::passcoded()) { return false; @@ -2713,6 +2847,9 @@ void MainWidget::orderWidgets() { _sideResizeArea->raise(); _playerPlaylist->raise(); _playerPanel->raise(); + for (auto &instance : _playerFloats) { + instance->widget->raise(); + } if (_hider) _hider->raise(); } @@ -2725,6 +2862,9 @@ QRect MainWidget::historyRect() const { QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { QPixmap result; + for (auto &instance : _playerFloats) { + instance->widget->hide(); + } if (_player) { _player->hideShadow(); } @@ -2761,6 +2901,11 @@ QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶m if (_player) { _player->showShadow(); } + for (auto &instance : _playerFloats) { + if (instance->visible) { + instance->widget->show(); + } + } return result; } @@ -2786,7 +2931,7 @@ void MainWidget::dlgUpdated(PeerData *peer, MsgId msgId) { } void MainWidget::showJumpToDate(PeerData *peer, QDate requestedDate) { - t_assert(peer != nullptr); + Expects(peer != nullptr); auto currentPeerDate = [peer] { if (auto history = App::historyLoaded(peer)) { if (history->scrollTopItem) { @@ -3122,6 +3267,9 @@ void MainWidget::updateControlsGeometry() { updateMediaPlayerPosition(); updateMediaPlaylistPosition(_playerPlaylist->x()); _contentScrollAddToY = 0; + for (auto &instance : _playerFloats) { + updateFloatPlayerPosition(instance.get()); + } } void MainWidget::updateDialogsWidthAnimated() { @@ -3202,6 +3350,14 @@ bool MainWidget::eventFilter(QObject *o, QEvent *e) { showBackFromStack(); return true; } + } else if (e->type() == QEvent::Wheel && !_playerFloats.empty()) { + for (auto &instance : _playerFloats) { + if (instance->widget == o) { + auto column = instance->column; + auto section = getFloatPlayerSection(&column); + return section->wheelEventFromFloatPlayer(e, column, instance->column); + } + } } return TWidget::eventFilter(o, e); } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 7e51287911..2a5f12bf5c 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -24,6 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "history/history_common.h" #include "core/single_timer.h" #include "base/weak_unique_ptr.h" +#include "window/section_widget.h" namespace Notify { struct PeerUpdate; @@ -38,6 +39,7 @@ namespace Player { class Widget; class VolumeWidget; class Panel; +class Float; } // namespace Player } // namespace Media @@ -460,6 +462,35 @@ protected: bool eventFilter(QObject *o, QEvent *e) override; private: + struct Float { + template + Float(QWidget *parent, HistoryItem *item, ToggleCallback callback); + + bool visible = false; + Animation visibleAnimation; + Window::Corner corner = Window::Corner::TopRight; + Window::Column column = Window::Column::Second; + QPoint position; + object_ptr widget; + }; + + using ChannelGetDifferenceTime = QMap; + enum class ChannelDifferenceRequest { + Unknown, + PtsGapOrShortPoll, + AfterFail, + }; + + struct DeleteHistoryRequest { + PeerData *peer; + bool justClearHistory; + }; + + struct DeleteAllFromUserParams { + ChannelData *channel; + UserData *from; + }; + void animationCallback(); void handleAdaptiveLayoutUpdate(); void updateWindowAdaptiveLayout(); @@ -506,6 +537,63 @@ private: void saveSectionInStack(); + void getChannelDifference(ChannelData *channel, ChannelDifferenceRequest from = ChannelDifferenceRequest::Unknown); + void gotDifference(const MTPupdates_Difference &diff); + bool failDifference(const RPCError &e); + void feedDifference(const MTPVector &users, const MTPVector &chats, const MTPVector &msgs, const MTPVector &other); + void gotState(const MTPupdates_State &state); + void updSetState(int32 pts, int32 date, int32 qts, int32 seq); + void gotChannelDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff); + bool failChannelDifference(ChannelData *channel, const RPCError &err); + void failDifferenceStartTimerFor(ChannelData *channel); + + void feedUpdateVector(const MTPVector &updates, bool skipMessageIds = false); + void feedMessageIds(const MTPVector &updates); + + void deleteHistoryPart(DeleteHistoryRequest request, const MTPmessages_AffectedHistory &result); + void deleteAllFromUserPart(DeleteAllFromUserParams params, const MTPmessages_AffectedHistory &result); + + void updateReceived(const mtpPrime *from, const mtpPrime *end); + bool updateFail(const RPCError &e); + + void usernameResolveDone(QPair msgIdAndStartToken, const MTPcontacts_ResolvedPeer &result); + bool usernameResolveFail(QString name, const RPCError &error); + + void inviteCheckDone(QString hash, const MTPChatInvite &invite); + bool inviteCheckFail(const RPCError &error); + void inviteImportDone(const MTPUpdates &result); + bool inviteImportFail(const RPCError &error); + + int getSectionTop() const; + + void hideAll(); + void showAll(); + + void overviewPreloaded(PeerData *data, const MTPmessages_Messages &result, mtpRequestId req); + bool overviewFailed(PeerData *data, const RPCError &error, mtpRequestId req); + + void clearCachedBackground(); + void checkCurrentFloatPlayer(); + void toggleFloatPlayer(Float *instance, bool visible); + void updateFloatPlayerPosition(Float *instance); + void removeFloatPlayer(Float *instance); + Float *currentFloatPlayer() const { + return _playerFloats.empty() ? nullptr : _playerFloats.back().get(); + } + Window::AbstractSectionWidget *getFloatPlayerSection(gsl::not_null column); + + bool ptsUpdated(int32 pts, int32 ptsCount); + bool ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdates &updates); + bool ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdate &update); + void ptsApplySkippedUpdates(); + bool requestingDifference() const { + return _ptsWaiter.requesting(); + } + bool getDifferenceTimeChanged(ChannelData *channel, int32 ms, ChannelGetDifferenceTime &channelCurTime, TimeMs &curTime); + + void viewsIncrementDone(QVector ids, const MTPVector &result, mtpRequestId req); + bool viewsIncrementFail(const RPCError &error, mtpRequestId req); + gsl::not_null _controller; bool _started = false; @@ -520,56 +608,7 @@ private: SingleTimer _updateMutedTimer; - enum class ChannelDifferenceRequest { - Unknown, - PtsGapOrShortPoll, - AfterFail, - }; - void getChannelDifference(ChannelData *channel, ChannelDifferenceRequest from = ChannelDifferenceRequest::Unknown); - void gotDifference(const MTPupdates_Difference &diff); - bool failDifference(const RPCError &e); - void feedDifference(const MTPVector &users, const MTPVector &chats, const MTPVector &msgs, const MTPVector &other); - void gotState(const MTPupdates_State &state); - void updSetState(int32 pts, int32 date, int32 qts, int32 seq); - void gotChannelDifference(ChannelData *channel, const MTPupdates_ChannelDifference &diff); - bool failChannelDifference(ChannelData *channel, const RPCError &err); - void failDifferenceStartTimerFor(ChannelData *channel); - - void feedUpdateVector(const MTPVector &updates, bool skipMessageIds = false); - void feedMessageIds(const MTPVector &updates); - - struct DeleteHistoryRequest { - PeerData *peer; - bool justClearHistory; - }; - void deleteHistoryPart(DeleteHistoryRequest request, const MTPmessages_AffectedHistory &result); - struct DeleteAllFromUserParams { - ChannelData *channel; - UserData *from; - }; - void deleteAllFromUserPart(DeleteAllFromUserParams params, const MTPmessages_AffectedHistory &result); - - void updateReceived(const mtpPrime *from, const mtpPrime *end); - bool updateFail(const RPCError &e); - - void usernameResolveDone(QPair msgIdAndStartToken, const MTPcontacts_ResolvedPeer &result); - bool usernameResolveFail(QString name, const RPCError &error); - - void inviteCheckDone(QString hash, const MTPChatInvite &invite); - bool inviteCheckFail(const RPCError &error); QString _inviteHash; - void inviteImportDone(const MTPUpdates &result); - bool inviteImportFail(const RPCError &error); - - int getSectionTop() const; - - void hideAll(); - void showAll(); - - void overviewPreloaded(PeerData *data, const MTPmessages_Messages &result, mtpRequestId req); - bool overviewFailed(PeerData *data, const RPCError &error, mtpRequestId req); - - void clearCachedBackground(); Animation _a_show; bool _showBack = false; @@ -594,6 +633,7 @@ private: object_ptr _playerPlaylist; object_ptr _playerPanel; bool _playerUsingPanel = false; + std::vector> _playerFloats; QPointer _forwardConfirm; // for single column layout object_ptr _hider = { nullptr }; @@ -610,22 +650,12 @@ private: int32 updSeq = 0; SingleTimer noUpdatesTimer; - bool ptsUpdated(int32 pts, int32 ptsCount); - bool ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdates &updates); - bool ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdate &update); - void ptsApplySkippedUpdates(); PtsWaiter _ptsWaiter; - bool requestingDifference() const { - return _ptsWaiter.requesting(); - } - typedef QMap ChannelGetDifferenceTime; ChannelGetDifferenceTime _channelGetDifferenceTimeByPts, _channelGetDifferenceTimeAfterFail; TimeMs _getDifferenceTimeByPts = 0; TimeMs _getDifferenceTimeAfterFail = 0; - bool getDifferenceTimeChanged(ChannelData *channel, int32 ms, ChannelGetDifferenceTime &channelCurTime, TimeMs &curTime); - SingleTimer _byPtsTimer; QMap _bySeqUpdates; @@ -677,8 +707,6 @@ private: typedef QMap ViewsIncrementByRequest; ViewsIncrementByRequest _viewsIncrementByRequest; SingleTimer _viewsIncrementTimer; - void viewsIncrementDone(QVector ids, const MTPVector &result, mtpRequestId req); - bool viewsIncrementFail(const RPCError &error, mtpRequestId req); std::unique_ptr _background; diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index 0d1beb75be..33ffe74e31 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -272,6 +272,17 @@ QPixmap Reader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh, return frame->pix; } +QPixmap Reader::current() { + Expects(_mode == Mode::Video); + + auto frame = frameToShow(); + t_assert(frame != nullptr); + + frame->displayed.storeRelease(1); + moveToNextShow(); + return frame->pix; +} + bool Reader::ready() const { if (_width && _height) return true; diff --git a/Telegram/SourceFiles/media/media_clip_reader.h b/Telegram/SourceFiles/media/media_clip_reader.h index 4d0916fa84..260d36532e 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.h +++ b/Telegram/SourceFiles/media/media_clip_reader.h @@ -82,6 +82,7 @@ public: void start(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, ImageRoundCorners corners); QPixmap current(int framew, int frameh, int outerw, int outerh, ImageRoundRadius radius, ImageRoundCorners corners, TimeMs ms); + QPixmap current(); QPixmap frameOriginal() const { if (auto frame = frameToShow()) { auto result = QPixmap::fromImage(frame->original); @@ -91,7 +92,7 @@ public: return QPixmap(); } bool currentDisplayed() const { - Frame *frame = frameToShow(); + auto frame = frameToShow(); return frame ? (frame->displayed.loadAcquire() != 0) : true; } bool autoPausedGif() const { @@ -107,7 +108,7 @@ public: State state() const; bool started() const { - int step = _step.loadAcquire(); + auto step = _step.loadAcquire(); return (step == WaitingForFirstFrameStep) || (step >= 0); } bool ready() const; diff --git a/Telegram/SourceFiles/media/player/media_player.style b/Telegram/SourceFiles/media/player/media_player.style index 57db2f4cd3..5c8ddab0fb 100644 --- a/Telegram/SourceFiles/media/player/media_player.style +++ b/Telegram/SourceFiles/media/player/media_player.style @@ -241,3 +241,6 @@ mediaPlayerFileLayout: OverviewFileLayout(overviewFileLayout) { songIconBg: mediaPlayerActiveFg; songOverBg: mediaPlayerActiveFg; } + +mediaPlayerFloatSize: 128px; +mediaPlayerFloatMargin: 12px; diff --git a/Telegram/SourceFiles/media/player/media_player_float.cpp b/Telegram/SourceFiles/media/player/media_player_float.cpp new file mode 100644 index 0000000000..d544c6f6eb --- /dev/null +++ b/Telegram/SourceFiles/media/player/media_player_float.cpp @@ -0,0 +1,193 @@ +/* +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-2017 John Preston, https://desktop.telegram.org +*/ +#include "media/player/media_player_float.h" + +#include "styles/style_media_player.h" +#include "media/media_clip_reader.h" +#include "media/view/media_clip_playback.h" +#include "media/media_audio.h" +#include "styles/style_history.h" + +namespace Media { +namespace Player { + +Float::Float(QWidget *parent, HistoryItem *item, base::lambda toggleCallback) : TWidget(parent) +, _item(item) +, _toggleCallback(std::move(toggleCallback)) { + auto media = _item->getMedia(); + t_assert(media != nullptr); + + auto document = media->getDocument(); + t_assert(document != nullptr); + t_assert(document->isRoundVideo()); + + auto margin = st::mediaPlayerFloatMargin; + auto size = 2 * margin + st::mediaPlayerFloatSize; + resize(size, size); + + prepareShadow(); + + subscribe(Global::RefItemRemoved(), [this](HistoryItem *item) { + if (_item == item) { + detach(); + } + }); +} + +void Float::detach() { + if (_item) { + _item = nullptr; + if (_toggleCallback) { + _toggleCallback(false); + } + } +} + +void Float::prepareShadow() { + auto shadow = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + shadow.fill(Qt::transparent); + shadow.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&shadow); + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(st::shadowFg); + auto extend = 2 * st::lineWidth; + p.drawEllipse(getInnerRect().marginsAdded(QMargins(extend, extend, extend, extend))); + } + _shadow = App::pixmapFromImageInPlace(Images::prepareBlur(shadow)); +} + +QRect Float::getInnerRect() const { + auto margin = st::mediaPlayerFloatMargin; + return rect().marginsRemoved(QMargins(margin, margin, margin, margin)); +} + +void Float::paintEvent(QPaintEvent *e) { + Painter p(this); + + p.setOpacity(_opacity); + p.drawPixmap(0, 0, _shadow); + + if (!fillFrame() && _toggleCallback) { + _toggleCallback(false); + } + + auto inner = getInnerRect(); + p.drawImage(inner.topLeft(), _frame); + + auto progress = _roundPlayback ? _roundPlayback->value() : 1.; + if (progress > 0.) { + auto pen = st::historyVideoMessageProgressFg->p; + auto was = p.pen(); + pen.setWidth(st::radialLine); + pen.setCapStyle(Qt::RoundCap); + p.setPen(pen); + p.setOpacity(_opacity * st::historyVideoMessageProgressOpacity); + + auto from = QuarterArcLength; + auto len = -qRound(FullArcLength * progress); + auto stepInside = st::radialLine / 2; + { + PainterHighQualityEnabler hq(p); + p.drawArc(inner.marginsRemoved(QMargins(stepInside, stepInside, stepInside, stepInside)), from, len); + } + + //p.setPen(was); + //p.setOpacity(_opacity); + } +} + +Clip::Reader *Float::getReader() const { + if (auto media = _item ? _item->getMedia() : nullptr) { + if (auto reader = media->getClipReader()) { + if (reader->started() && reader->mode() == Clip::Reader::Mode::Video) { + return reader; + } + } + } + return nullptr; +} + +bool Float::hasFrame() const { + if (auto reader = getReader()) { + return !reader->current().isNull(); + } + return false; +} + +bool Float::fillFrame() { + auto creating = _frame.isNull(); + if (creating) { + _frame = QImage(getInnerRect().size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + _frame.setDevicePixelRatio(cRetinaFactor()); + } + auto frameInner = [this] { + return QRect(0, 0, _frame.width() / cIntRetinaFactor(), _frame.height() / cIntRetinaFactor()); + }; + if (auto reader = getReader()) { + updatePlayback(); + auto frame = reader->current(); + if (!frame.isNull()) { + _frame.fill(Qt::transparent); + + Painter p(&_frame); + PainterHighQualityEnabler hq(p); + p.drawPixmap(frameInner(), frame); + return true; + } + } + if (creating) { + _frame.fill(Qt::transparent); + + Painter p(&_frame); + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setBrush(st::imageBg); + p.drawEllipse(frameInner()); + } + return false; +} + +void Float::updatePlayback() { + if (_item) { + if (!_roundPlayback) { + _roundPlayback = std::make_unique(); + _roundPlayback->setValueChangedCallback([this](float64 value) { + update(); + }); + } + auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); + if (state.id.contextId() == _item->fullId()) { + _roundPlayback->updateState(state); + } + } +} + +void Float::repaintItem() { + update(); + if (hasFrame() && _toggleCallback) { + _toggleCallback(true); + } +} + +} // namespace Player +} // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_float.h b/Telegram/SourceFiles/media/player/media_player_float.h new file mode 100644 index 0000000000..b67d39234a --- /dev/null +++ b/Telegram/SourceFiles/media/player/media_player_float.h @@ -0,0 +1,76 @@ +/* +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-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +namespace Media { +namespace Clip { +class Playback; +} // namespace Clip + +namespace Player { + +class Float : public TWidget, private base::Subscriber { +public: + Float(QWidget *parent, HistoryItem *item, base::lambda toggleCallback); + + FullMsgId itemId() const { + return _item ? _item->fullId() : FullMsgId(); + } + void setOpacity(float64 opacity) { + _opacity = opacity; + update(); + } + void detach(); + bool detached() const { + return !_item; + } + void ui_repaintHistoryItem(const HistoryItem *item) { + if (item == _item) { + repaintItem(); + } + } + +protected: + void paintEvent(QPaintEvent *e); + +private: + Clip::Reader *getReader() const; + void repaintItem(); + void prepareShadow(); + bool hasFrame() const; + bool fillFrame(); + QRect getInnerRect() const; + void updatePlayback(); + + HistoryItem *_item = nullptr; + base::lambda _toggleCallback; + + float64 _opacity = 1.; + + QPixmap _shadow; + QImage _frame; + + std::unique_ptr _roundPlayback; + +}; + +} // namespace Player +} // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index e3b9e92d06..2de23f8032 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -179,11 +179,13 @@ bool Instance::moveInPlaylist(Data *data, int delta, bool autonext) { auto msgId = data->playlist[newIndex]; if (auto item = App::histItemById(msgId)) { if (auto media = item->getMedia()) { - if (autonext) { - _switchToNextNotifier.notify({ data->current, msgId }); + if (auto document = media->getDocument()) { + if (autonext) { + _switchToNextNotifier.notify({ data->current, msgId }); + } + DocumentOpenClickHandler::doOpen(media->getDocument(), item, ActionOnLoadPlayInline); + return true; } - media->playInline(); - return true; } } return false; diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index cc95ee291f..a45a924ea5 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -207,7 +207,7 @@ void Widget::setShadowGeometryToLeft(int x, int y, int w, int h) { void Widget::showShadow() { _shadow->show(); - _playbackSlider->show(); + _playbackSlider->setVisible(_type == AudioMsgId::Type::Song); } void Widget::hideShadow() { @@ -382,7 +382,9 @@ void Widget::setType(AudioMsgId::Type type) { _type = type; _repeatTrack->setVisible(_type == AudioMsgId::Type::Song); _volumeToggle->setVisible(_type == AudioMsgId::Type::Song); - _playbackSlider->setVisible(_type == AudioMsgId::Type::Song); + if (!_shadow->isHidden()) { + _playbackSlider->setVisible(_type == AudioMsgId::Type::Song); + } updateLabelsGeometry(); handleSongChange(); handleSongUpdate(mixer()->currentState(_type)); diff --git a/Telegram/SourceFiles/overviewwidget.cpp b/Telegram/SourceFiles/overviewwidget.cpp index 70a13e7dc7..cdf170fbed 100644 --- a/Telegram/SourceFiles/overviewwidget.cpp +++ b/Telegram/SourceFiles/overviewwidget.cpp @@ -1917,9 +1917,8 @@ OverviewInner::~OverviewInner() { clear(); } -OverviewWidget::OverviewWidget(QWidget *parent, gsl::not_null controller, PeerData *peer, MediaOverviewType type) : TWidget(parent) -, _controller(controller) -, _topBar(this, _controller) +OverviewWidget::OverviewWidget(QWidget *parent, gsl::not_null controller, PeerData *peer, MediaOverviewType type) : Window::AbstractSectionWidget(parent, controller) +, _topBar(this, controller) , _scroll(this, st::settingsScroll, false) , _mediaType(this, st::defaultDropdownMenu) , _topShadow(this, st::shadowFg) { @@ -2113,6 +2112,14 @@ int32 OverviewWidget::lastScrollTop() const { return _scroll->scrollTop(); } +bool OverviewWidget::wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) { + return _scroll->viewportEvent(e); +} + +QRect OverviewWidget::rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) { + return mapToGlobal(_scroll->geometry()); +} + int32 OverviewWidget::countBestScroll() const { if (type() == OverviewMusicFiles) { auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); diff --git a/Telegram/SourceFiles/overviewwidget.h b/Telegram/SourceFiles/overviewwidget.h index fd3344d41e..1438fab7f3 100644 --- a/Telegram/SourceFiles/overviewwidget.h +++ b/Telegram/SourceFiles/overviewwidget.h @@ -286,7 +286,7 @@ private: Ui::PopupMenu *_menu = nullptr; }; -class OverviewWidget : public TWidget, public RPCSender { +class OverviewWidget : public Window::AbstractSectionWidget, public RPCSender { Q_OBJECT public: @@ -351,6 +351,10 @@ public: void deleteContextItem(bool forEveryone); void deleteSelectedItems(bool forEveryone); + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) override; + QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) override; + void ui_repaintHistoryItem(const HistoryItem *item); void notify_historyItemLayoutChanged(const HistoryItem *item); @@ -375,8 +379,6 @@ private: void topBarClick(); void animationCallback(); - gsl::not_null _controller; - object_ptr _backAnimationButton = { nullptr }; object_ptr _topBar; object_ptr _scroll; diff --git a/Telegram/SourceFiles/profile/profile_common_groups_section.cpp b/Telegram/SourceFiles/profile/profile_common_groups_section.cpp index 705be58722..3576618421 100644 --- a/Telegram/SourceFiles/profile/profile_common_groups_section.cpp +++ b/Telegram/SourceFiles/profile/profile_common_groups_section.cpp @@ -42,8 +42,8 @@ constexpr int kCommonGroupsPerPage = 40; } // namespace -object_ptr SectionMemento::createWidget(QWidget *parent, const QRect &geometry) const { - auto result = object_ptr(parent, _peer); +object_ptr SectionMemento::createWidget(QWidget *parent, gsl::not_null controller, const QRect &geometry) const { + auto result = object_ptr(parent, controller, _peer); result->setInternalState(geometry, this); return std::move(result); } @@ -337,7 +337,7 @@ InnerWidget::~InnerWidget() { } } -Widget::Widget(QWidget *parent, PeerData *peer) : Window::SectionWidget(parent) +Widget::Widget(QWidget *parent, gsl::not_null controller, PeerData *peer) : Window::SectionWidget(parent, controller) , _scroll(this, st::settingsScroll) , _fixedBar(this) , _fixedBarShadow(this, st::shadowFg) { @@ -447,5 +447,13 @@ void Widget::showFinishedHook() { _fixedBar->setAnimatingMode(false); } +bool Widget::wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) { + return _scroll->viewportEvent(e); +} + +QRect Widget::rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) { + return mapToGlobal(_scroll->geometry()); +} + } // namespace CommonGroups } // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_common_groups_section.h b/Telegram/SourceFiles/profile/profile_common_groups_section.h index 88b9a2fc32..a46c0ec2e6 100644 --- a/Telegram/SourceFiles/profile/profile_common_groups_section.h +++ b/Telegram/SourceFiles/profile/profile_common_groups_section.h @@ -43,7 +43,7 @@ public: SectionMemento(PeerData *peer) : _peer(peer) { } - object_ptr createWidget(QWidget *parent, const QRect &geometry) const override; + object_ptr createWidget(QWidget *parent, gsl::not_null controller, const QRect &geometry) const override; PeerData *getPeer() const { return _peer; @@ -171,7 +171,7 @@ class Widget final : public Window::SectionWidget { Q_OBJECT public: - Widget(QWidget *parent, PeerData *peer); + Widget(QWidget *parent, gsl::not_null controller, PeerData *peer); PeerData *peer() const; PeerData *peerForDialogs() const override { @@ -189,6 +189,10 @@ public: void setInternalState(const QRect &geometry, const SectionMemento *memento); + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) override; + QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) override; + protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/profile/profile_section_memento.cpp b/Telegram/SourceFiles/profile/profile_section_memento.cpp index 3563cc8361..5998c86e98 100644 --- a/Telegram/SourceFiles/profile/profile_section_memento.cpp +++ b/Telegram/SourceFiles/profile/profile_section_memento.cpp @@ -24,8 +24,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Profile { -object_ptr SectionMemento::createWidget(QWidget *parent, const QRect &geometry) const { - auto result = object_ptr(parent, _peer); +object_ptr SectionMemento::createWidget(QWidget *parent, gsl::not_null controller, const QRect &geometry) const { + auto result = object_ptr(parent, controller, _peer); result->setInternalState(geometry, this); return std::move(result); } diff --git a/Telegram/SourceFiles/profile/profile_section_memento.h b/Telegram/SourceFiles/profile/profile_section_memento.h index c7bcc2a6af..979a32fb3c 100644 --- a/Telegram/SourceFiles/profile/profile_section_memento.h +++ b/Telegram/SourceFiles/profile/profile_section_memento.h @@ -31,7 +31,7 @@ public: SectionMemento(PeerData *peer) : _peer(peer) { } - object_ptr createWidget(QWidget *parent, const QRect &geometry) const override; + object_ptr createWidget(QWidget *parent, gsl::not_null controller, const QRect &geometry) const override; PeerData *getPeer() const { return _peer; diff --git a/Telegram/SourceFiles/profile/profile_widget.cpp b/Telegram/SourceFiles/profile/profile_widget.cpp index d3ae6dea0d..1264e6e699 100644 --- a/Telegram/SourceFiles/profile/profile_widget.cpp +++ b/Telegram/SourceFiles/profile/profile_widget.cpp @@ -32,7 +32,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Profile { -Widget::Widget(QWidget *parent, PeerData *peer) : Window::SectionWidget(parent) +Widget::Widget(QWidget *parent, gsl::not_null controller, PeerData *peer) : Window::SectionWidget(parent, controller) , _scroll(this, st::settingsScroll) , _fixedBar(this, peer) , _fixedBarShadow(this, object_ptr(this, st::shadowFg)) { @@ -161,4 +161,12 @@ void Widget::showFinishedHook() { _inner->showFinished(); } +bool Widget::wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) { + return _scroll->viewportEvent(e); +} + +QRect Widget::rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) { + return mapToGlobal(_scroll->geometry()); +} + } // namespace Profile diff --git a/Telegram/SourceFiles/profile/profile_widget.h b/Telegram/SourceFiles/profile/profile_widget.h index 0da7a8a4a7..bd57297787 100644 --- a/Telegram/SourceFiles/profile/profile_widget.h +++ b/Telegram/SourceFiles/profile/profile_widget.h @@ -38,7 +38,7 @@ class Widget final : public Window::SectionWidget { Q_OBJECT public: - Widget(QWidget *parent, PeerData *peer); + Widget(QWidget *parent, gsl::not_null controller, PeerData *peer); PeerData *peer() const; PeerData *peerForDialogs() const override { @@ -54,6 +54,10 @@ public: void setInternalState(const QRect &geometry, const SectionMemento *memento); + // Float player interface. + bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) override; + QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) override; + protected: void resizeEvent(QResizeEvent *e) override; diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index bb42b0683d..aaed49f20d 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -175,6 +175,9 @@ static const WebPageId CancelledWebPageId = 0xFFFFFFFFFFFFFFFFULL; inline bool operator==(const FullMsgId &a, const FullMsgId &b) { return (a.channel == b.channel) && (a.msg == b.msg); } +inline bool operator!=(const FullMsgId &a, const FullMsgId &b) { + return !(a == b); +} inline bool operator<(const FullMsgId &a, const FullMsgId &b) { if (a.msg < b.msg) return true; if (a.msg > b.msg) return false; diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index c0a88a7a4c..af6b2ecfd0 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -170,22 +170,35 @@ public: QPoint myrtlpoint(int x, int y) const { return rtlpoint(x, y, Base::width()); } - QPoint myrtlpoint(const QPoint p) const { - return rtlpoint(p, Base::width()); + QPoint myrtlpoint(const QPoint point) const { + return rtlpoint(point, Base::width()); } QRect myrtlrect(int x, int y, int w, int h) const { return rtlrect(x, y, w, h, Base::width()); } - QRect myrtlrect(const QRect &r) const { - return rtlrect(r, Base::width()); + QRect myrtlrect(const QRect &rect) const { + return rtlrect(rect, Base::width()); } - void rtlupdate(const QRect &r) { - Base::update(myrtlrect(r)); + void rtlupdate(const QRect &rect) { + Base::update(myrtlrect(rect)); } void rtlupdate(int x, int y, int w, int h) { Base::update(myrtlrect(x, y, w, h)); } + QPoint mapFromGlobal(const QPoint &point) const { + return Base::mapFromGlobal(point); + } + QPoint mapToGlobal(const QPoint &point) const { + return Base::mapToGlobal(point); + } + QRect mapFromGlobal(const QRect &rect) const { + return QRect(mapFromGlobal(rect.topLeft()), rect.size()); + } + QRect mapToGlobal(const QRect &rect) { + return QRect(mapToGlobal(rect.topLeft()), rect.size()); + } + protected: void enterEvent(QEvent *e) final override { if (auto parent = tparent()) { diff --git a/Telegram/SourceFiles/window/section_memento.h b/Telegram/SourceFiles/window/section_memento.h index 91c4ea9b6b..46c22f3d91 100644 --- a/Telegram/SourceFiles/window/section_memento.h +++ b/Telegram/SourceFiles/window/section_memento.h @@ -22,11 +22,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Window { +class Controller; class SectionWidget; class SectionMemento { public: - virtual object_ptr createWidget(QWidget *parent, const QRect &geometry) const = 0; + virtual object_ptr createWidget(QWidget *parent, gsl::not_null controller, const QRect &geometry) const = 0; virtual ~SectionMemento() { } diff --git a/Telegram/SourceFiles/window/section_widget.cpp b/Telegram/SourceFiles/window/section_widget.cpp index 87b14cb8b4..459eca71db 100644 --- a/Telegram/SourceFiles/window/section_widget.cpp +++ b/Telegram/SourceFiles/window/section_widget.cpp @@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Window { -SectionWidget::SectionWidget(QWidget *parent) : TWidget(parent) { +SectionWidget::SectionWidget(QWidget *parent, gsl::not_null controller) : AbstractSectionWidget(parent, controller) { } void SectionWidget::setGeometryWithTopMoved(const QRect &newGeometry, int topDelta) { diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index e3f1ba5d2c..1438f2e601 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -25,6 +25,60 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org namespace Window { +class Controller; + +enum class Column { + First, + Second, + Third, +}; + +enum class Corner { + TopLeft, + TopRight, + BottomLeft, + BottomRight, +}; + +inline bool IsTopCorner(Corner corner) { + return (corner == Corner::TopLeft) || (corner == Corner::TopRight); +} + +inline bool IsBottomCorner(Corner corner) { + return !IsTopCorner(corner); +} + +inline bool IsLeftCorner(Corner corner) { + return (corner == Corner::TopLeft) || (corner == Corner::BottomLeft); +} + +inline bool IsRightCorner(Corner corner) { + return !IsLeftCorner(corner); +} + +class AbstractSectionWidget : public TWidget, protected base::Subscriber { +public: + AbstractSectionWidget(QWidget *parent, gsl::not_null controller) : TWidget(parent), _controller(controller) { + } + + // Float player interface. + virtual bool wheelEventFromFloatPlayer(QEvent *e, Window::Column myColumn, Window::Column playerColumn) { + return false; + } + virtual QRect rectForFloatPlayer(Window::Column myColumn, Window::Column playerColumn) { + return mapToGlobal(rect()); + } + +protected: + gsl::not_null controller() const { + return _controller; + } + +private: + gsl::not_null _controller; + +}; + class SectionMemento; struct SectionSlideParams { @@ -37,12 +91,9 @@ struct SectionSlideParams { } }; -class SectionWidget : public TWidget, protected base::Subscriber { - Q_OBJECT - +class SectionWidget : public AbstractSectionWidget { public: - - SectionWidget(QWidget *parent); + SectionWidget(QWidget *parent, gsl::not_null controller); virtual PeerData *peerForDialogs() const { return nullptr; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index c970f98473..069aa3015a 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -181,6 +181,8 @@ <(src_loc)/media/player/media_player_button.h <(src_loc)/media/player/media_player_cover.cpp <(src_loc)/media/player/media_player_cover.h +<(src_loc)/media/player/media_player_float.cpp +<(src_loc)/media/player/media_player_float.h <(src_loc)/media/player/media_player_instance.cpp <(src_loc)/media/player/media_player_instance.h <(src_loc)/media/player/media_player_list.cpp