From 466444e17de1eee8d2b9fa16141d7e7829e492a0 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 5 Nov 2018 15:16:09 +0400 Subject: [PATCH] Extract float player controller. --- Telegram/SourceFiles/mainwidget.cpp | 360 +++--------------- Telegram/SourceFiles/mainwidget.h | 63 +-- .../media/player/media_player_float.cpp | 315 +++++++++++++++ .../media/player/media_player_float.h | 77 ++++ 4 files changed, 460 insertions(+), 355 deletions(-) diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 51c20f76d..dbece8c09 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -214,28 +214,6 @@ StackItemSection::StackItemSection( , _memento(std::move(memento)) { } -template -MainWidget::Float::Float( - QWidget *parent, - not_null controller, - not_null item, - ToggleCallback toggle, - DraggedCallback dragged) -: animationSide(RectPart::Right) -, column(Window::Column::Second) -, corner(RectPart::TopRight) -, widget( - parent, - controller, - item, - [this, toggle = std::move(toggle)](bool visible) { - toggle(this, visible); - }, - [this, dragged = std::move(dragged)](bool closed) { - dragged(this, closed); - }) { -} - MainWidget::MainWidget( QWidget *parent, not_null controller) @@ -250,7 +228,8 @@ MainWidget::MainWidget( this, _controller, Media::Player::Panel::Layout::OnlyPlaylist) -, _playerPanel(this, _controller, Media::Player::Panel::Layout::Full) { +, _playerPanel(this, _controller, Media::Player::Panel::Layout::Full) +, _playerFloats(floatPlayerDelegate()) { Messenger::Instance().mtp()->setUpdatesHandler(rpcDone(&MainWidget::updateReceived)); Messenger::Instance().mtp()->setGlobalFailHandler(rpcFail(&MainWidget::updateFail)); @@ -294,9 +273,6 @@ MainWidget::MainWidget( ) | rpl::start_with_next( [this] { updateControlsGeometry(); }, lifetime()); - subscribe(_controller->floatPlayerAreaUpdated(), [this] { - checkFloatPlayerVisibility(); - }); // MSVC BUG + REGRESSION rpl::mappers::tuple :( using namespace rpl::mappers; @@ -358,11 +334,6 @@ MainWidget::MainWidget( } } }); - subscribe(Media::Player::instance()->trackChangedNotifier(), [this](AudioMsgId::Type type) { - if (type == AudioMsgId::Type::Voice) { - checkCurrentFloatPlayer(); - } - }); subscribe(Adaptive::Changed(), [this]() { handleAdaptiveLayoutUpdate(); }); @@ -388,170 +359,20 @@ void MainWidget::setupConnectingWidget() { Window::AdaptiveIsOneColumn() | rpl::map(!_1)); } -void MainWidget::checkCurrentFloatPlayer() { - const auto state = Media::Player::instance()->current(AudioMsgId::Type::Voice); - const auto fullId = state.contextId(); - const auto last = currentFloatPlayer(); - if (last - && !last->widget->detached() - && last->widget->item()->fullId() == fullId) { - return; - } - if (last) { - last->widget->detach(); - } - if (const auto item = App::histItemById(fullId)) { - if (const auto media = item->media()) { - if (const auto document = media->document()) { - if (document->isVideoMessage()) { - createFloatPlayer(item); - } - } - } - } +not_null MainWidget::floatPlayerDelegate() { + return static_cast(this); } -void MainWidget::createFloatPlayer(not_null item) { - _playerFloats.push_back(std::make_unique( - this, - _controller, - item, - [this](not_null instance, bool visible) { - instance->hiddenByWidget = !visible; - toggleFloatPlayer(instance); - }, - [this](not_null instance, bool closed) { - finishFloatPlayerDrag(instance, closed); - })); - currentFloatPlayer()->column = Auth().settings().floatPlayerColumn(); - currentFloatPlayer()->corner = Auth().settings().floatPlayerCorner(); - checkFloatPlayerVisibility(); +not_null MainWidget::floatPlayerWidget() { + return this; } -void MainWidget::toggleFloatPlayer(not_null instance) { - auto visible = !instance->hiddenByHistory && !instance->hiddenByWidget && instance->widget->isReady(); - if (instance->visible != visible) { - instance->widget->resetMouseState(); - instance->visible = visible; - if (!instance->visibleAnimation.animating() && !instance->hiddenByDrag) { - auto finalRect = QRect(getFloatPlayerPosition(instance), instance->widget->size()); - instance->animationSide = getFloatPlayerSide(finalRect.center()); - } - instance->visibleAnimation.start([this, instance] { - updateFloatPlayerPosition(instance); - }, visible ? 0. : 1., visible ? 1. : 0., st::slideDuration, visible ? anim::easeOutCirc : anim::linear); - updateFloatPlayerPosition(instance); - } +not_null MainWidget::floatPlayerController() { + return _controller; } -void MainWidget::checkFloatPlayerVisibility() { - auto instance = currentFloatPlayer(); - if (!instance) { - return; - } - - auto amVisible = false; - if (auto item = instance->widget->item()) { - Auth().data().queryItemVisibility().notify({ item, &amVisible }, true); - } - instance->hiddenByHistory = amVisible; - toggleFloatPlayer(instance); - updateFloatPlayerPosition(instance); -} - -void MainWidget::updateFloatPlayerPosition(not_null 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; - } - - if (!instance->widget->dragged()) { - if (instance->widget->isHidden()) { - instance->widget->show(); - } - - auto dragged = instance->draggedAnimation.current(1.); - auto position = QPoint(); - if (instance->hiddenByDrag) { - instance->widget->setOpacity(instance->widget->countOpacityByParent()); - position = getFloatPlayerHiddenPosition(instance->dragFrom, instance->widget->size(), instance->animationSide); - } else { - instance->widget->setOpacity(visible * visible); - position = getFloatPlayerPosition(instance); - if (visible < 1.) { - auto hiddenPosition = getFloatPlayerHiddenPosition(position, instance->widget->size(), instance->animationSide); - position.setX(anim::interpolate(hiddenPosition.x(), position.x(), visible)); - position.setY(anim::interpolate(hiddenPosition.y(), position.y(), visible)); - } - } - if (dragged < 1.) { - position.setX(anim::interpolate(instance->dragFrom.x(), position.x(), dragged)); - position.setY(anim::interpolate(instance->dragFrom.y(), position.y(), dragged)); - } - instance->widget->move(position); - } -} - -QPoint MainWidget::getFloatPlayerHiddenPosition(QPoint position, QSize size, RectPart side) const { - switch (side) { - case RectPart::Left: return QPoint(-size.width(), position.y()); - case RectPart::Top: return QPoint(position.x(), -size.height()); - case RectPart::Right: return QPoint(width(), position.y()); - case RectPart::Bottom: return QPoint(position.x(), height()); - } - Unexpected("Bad side in MainWidget::getFloatPlayerHiddenPosition()."); -} - -QPoint MainWidget::getFloatPlayerPosition(not_null instance) const { - auto section = getFloatPlayerSection(instance->column); - auto rect = section->rectForFloatPlayer(); - auto position = rect.topLeft(); - if (IsBottomCorner(instance->corner)) { - position.setY(position.y() + rect.height() - instance->widget->height()); - } - if (IsRightCorner(instance->corner)) { - position.setX(position.x() + rect.width() - instance->widget->width()); - } - return mapFromGlobal(position); -} - -RectPart MainWidget::getFloatPlayerSide(QPoint center) const { - auto left = qAbs(center.x()); - auto right = qAbs(width() - center.x()); - auto top = qAbs(center.y()); - auto bottom = qAbs(height() - center.y()); - if (left < right && left < top && left < bottom) { - return RectPart::Left; - } else if (right < top && right < bottom) { - return RectPart::Right; - } else if (top < bottom) { - return RectPart::Top; - } - return RectPart::Bottom; -} - -void MainWidget::removeFloatPlayer(not_null instance) { - auto widget = std::move(instance->widget); - auto i = std::find_if(_playerFloats.begin(), _playerFloats.end(), [instance](auto &item) { - return (item.get() == instance); - }); - 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(Window::Column column) const { +not_null MainWidget::floatPlayerGetSection( + Window::Column column) { if (Adaptive::ThreeColumn()) { if (column == Window::Column::First) { return _dialogs; @@ -581,100 +402,52 @@ Window::AbstractSectionWidget *MainWidget::getFloatPlayerSection(Window::Column return _dialogs; } -void MainWidget::updateFloatPlayerColumnCorner(QPoint center) { - Expects(!_playerFloats.empty()); - auto size = _playerFloats.back()->widget->size(); - auto min = INT_MAX; - auto column = Auth().settings().floatPlayerColumn(); - auto corner = Auth().settings().floatPlayerCorner(); - auto checkSection = [this, center, size, &min, &column, &corner]( - Window::AbstractSectionWidget *widget, - Window::Column widgetColumn) { - auto rect = mapFromGlobal(widget->rectForFloatPlayer()); - auto left = rect.x() + (size.width() / 2); - auto right = rect.x() + rect.width() - (size.width() / 2); - auto top = rect.y() + (size.height() / 2); - auto bottom = rect.y() + rect.height() - (size.height() / 2); - auto checkCorner = [&](QPoint point, RectPart checked) { - auto distance = (point - center).manhattanLength(); - if (min > distance) { - min = distance; - column = widgetColumn; - corner = checked; - } - }; - checkCorner({ left, top }, RectPart::TopLeft); - checkCorner({ right, top }, RectPart::TopRight); - checkCorner({ left, bottom }, RectPart::BottomLeft); - checkCorner({ right, bottom }, RectPart::BottomRight); - }; - +void MainWidget::floatPlayerEnumerateSections(Fn widget, + Window::Column widgetColumn)> callback) { if (Adaptive::ThreeColumn()) { - checkSection(_dialogs, Window::Column::First); + callback(_dialogs, Window::Column::First); if (_mainSection) { - checkSection(_mainSection, Window::Column::Second); + callback(_mainSection, Window::Column::Second); } else { - checkSection(_history, Window::Column::Second); + callback(_history, Window::Column::Second); } if (_thirdSection) { - checkSection(_thirdSection, Window::Column::Third); + callback(_thirdSection, Window::Column::Third); } } else if (Adaptive::Normal()) { - checkSection(_dialogs, Window::Column::First); + callback(_dialogs, Window::Column::First); if (_mainSection) { - checkSection(_mainSection, Window::Column::Second); + callback(_mainSection, Window::Column::Second); } else { - checkSection(_history, Window::Column::Second); + callback(_history, Window::Column::Second); } } else { if (Adaptive::OneColumn() && selectingPeer()) { - checkSection(_dialogs, Window::Column::First); + callback(_dialogs, Window::Column::First); } else if (_mainSection) { - checkSection(_mainSection, Window::Column::Second); + callback(_mainSection, Window::Column::Second); } else if (!Adaptive::OneColumn() || _history->peer()) { - checkSection(_history, Window::Column::Second); + callback(_history, Window::Column::Second); } else { - checkSection(_dialogs, Window::Column::First); + callback(_dialogs, Window::Column::First); } } - if (Auth().settings().floatPlayerColumn() != column) { - Auth().settings().setFloatPlayerColumn(column); - Auth().saveSettingsDelayed(); - } - if (Auth().settings().floatPlayerCorner() != corner) { - Auth().settings().setFloatPlayerCorner(corner); - Auth().saveSettingsDelayed(); - } } -void MainWidget::finishFloatPlayerDrag(not_null instance, bool closed) { - instance->dragFrom = instance->widget->pos(); - auto center = instance->widget->geometry().center(); - if (closed) { - instance->hiddenByDrag = true; - instance->animationSide = getFloatPlayerSide(center); - } - updateFloatPlayerColumnCorner(center); - instance->column = Auth().settings().floatPlayerColumn(); - instance->corner = Auth().settings().floatPlayerCorner(); - - instance->draggedAnimation.finish(); - instance->draggedAnimation.start([this, instance] { updateFloatPlayerPosition(instance); }, 0., 1., st::slideDuration, anim::sineInOut); - updateFloatPlayerPosition(instance); - - if (closed) { - if (auto item = instance->widget->item()) { - auto voiceData = Media::Player::instance()->current(AudioMsgId::Type::Voice); - if (_player && voiceData.contextId() == item->fullId()) { - _player->entity()->stopAndClose(); - } +void MainWidget::floatPlayerCloseHook(FullMsgId itemId) { + if (_player) { + const auto voiceData = Media::Player::instance()->current( + AudioMsgId::Type::Voice); + if (voiceData.contextId() == itemId) { + _player->entity()->stopAndClose(); } - instance->widget->detach(); } } bool MainWidget::setForwardDraft(PeerId peerId, MessageIdsList &&items) { Expects(peerId != 0); + const auto peer = App::peer(peerId); const auto error = GetErrorTextForForward( peer, @@ -876,7 +649,7 @@ void MainWidget::noHider(HistoryHider *destroyed) { } else { _history->showAnimated(Window::SlideDirection::FromRight, animationParams); } - checkFloatPlayerVisibility(); + _playerFloats.checkVisibility(); } } else { if (_forwardConfirm) { @@ -915,7 +688,7 @@ void MainWidget::hiderLayer(object_ptr h) { updateControlsGeometry(); _dialogs->activate(); } - checkFloatPlayerVisibility(); + _playerFloats.checkVisibility(); } void MainWidget::showForwardLayer(MessageIdsList &&items) { @@ -2047,7 +1820,7 @@ void MainWidget::ui_showPeerHistory( _dialogs->update(); } - checkFloatPlayerVisibility(); + _playerFloats.checkVisibility(); } PeerData *MainWidget::ui_getPeerForMouseAction() { @@ -2132,16 +1905,10 @@ Window::SectionSlideParams MainWidget::prepareThirdSectionAnimation(Window::Sect if (!_thirdSection->hasTopBarShadow()) { result.withTopBarShadow = false; } - for (auto &instance : _playerFloats) { - instance->widget->hide(); - } + _playerFloats.hideAll(); auto sectionTop = getThirdSectionTop(); result.oldContentCache = _thirdSection->grabForShowAnimation(result); - for (auto &instance : _playerFloats) { - if (instance->visible) { - instance->widget->show(); - } - } + _playerFloats.showVisible(); return result; } @@ -2159,9 +1926,7 @@ Window::SectionSlideParams MainWidget::prepareShowAnimation( result.withTopBarShadow = false; } - for (auto &instance : _playerFloats) { - instance->widget->hide(); - } + _playerFloats.hideAll(); if (_player) { _player->hideShadow(); } @@ -2209,11 +1974,7 @@ Window::SectionSlideParams MainWidget::prepareShowAnimation( if (_player) { _player->showShadow(); } - for (auto &instance : _playerFloats) { - if (instance->visible) { - instance->widget->show(); - } - } + _playerFloats.showVisible(); return result; } @@ -2355,7 +2116,7 @@ void MainWidget::showNewSection( } } - checkFloatPlayerVisibility(); + _playerFloats.checkVisibility(); orderWidgets(); } @@ -2462,9 +2223,7 @@ void MainWidget::orderWidgets() { _connecting->raise(); _playerPlaylist->raise(); _playerPanel->raise(); - for (auto &instance : _playerFloats) { - instance->widget->raise(); - } + _playerFloats.raiseAll(); if (_hider) _hider->raise(); } @@ -2477,9 +2236,7 @@ QRect MainWidget::historyRect() const { QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶ms) { QPixmap result; - for (auto &instance : _playerFloats) { - instance->widget->hide(); - } + _playerFloats.hideAll(); if (_player) { _player->hideShadow(); } @@ -2530,11 +2287,7 @@ QPixmap MainWidget::grabForShowAnimation(const Window::SectionSlideParams ¶m if (_player) { _player->showShadow(); } - for (auto &instance : _playerFloats) { - if (instance->visible) { - instance->widget->show(); - } - } + _playerFloats.showVisible(); return result; } @@ -2671,9 +2424,7 @@ void MainWidget::hideAll() { _player->setVisible(false); _playerHeight = 0; } - for (auto &instance : _playerFloats) { - instance->widget->hide(); - } + _playerFloats.hideAll(); } void MainWidget::showAll() { @@ -2746,12 +2497,8 @@ void MainWidget::showAll() { _playerHeight = _player->contentHeight(); } updateControlsGeometry(); - if (auto instance = currentFloatPlayer()) { - checkFloatPlayerVisibility(); - if (instance->visible) { - instance->widget->show(); - } - } + _playerFloats.checkVisibility(); + _playerFloats.showVisible(); App::wnd()->checkHistoryActivation(); } @@ -2861,9 +2608,8 @@ void MainWidget::updateControlsGeometry() { updateMediaPlayerPosition(); updateMediaPlaylistPosition(_playerPlaylist->x()); _contentScrollAddToY = 0; - for (auto &instance : _playerFloats) { - updateFloatPlayerPosition(instance.get()); - } + + _playerFloats.updatePositions(); } void MainWidget::refreshResizeAreas() { @@ -3105,16 +2851,12 @@ bool MainWidget::eventFilter(QObject *o, QEvent *e) { _controller->showBackFromStack(); return true; } - } else if (e->type() == QEvent::Wheel && !_playerFloats.empty()) { - for (auto &instance : _playerFloats) { - if (instance->widget == o) { - auto section = getFloatPlayerSection( - instance->column); - return section->wheelEventFromFloatPlayer(e); - } + } else if (e->type() == QEvent::Wheel) { + if (const auto result = _playerFloats.filterWheelEvent(o, e)) { + return *result; } } - return TWidget::eventFilter(o, e); + return RpWidget::eventFilter(o, e); } void MainWidget::handleAdaptiveLayoutUpdate() { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index aacf3052f..238c649e5 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/single_timer.h" #include "base/weak_ptr.h" #include "ui/rp_widget.h" +#include "media/player/media_player_float.h" struct HistoryMessageMarkupButton; class MainWindow; @@ -36,7 +37,6 @@ namespace Player { class Widget; class VolumeWidget; class Panel; -class Float; } // namespace Player } // namespace Media @@ -80,7 +80,11 @@ class ItemBase; } // namespace Layout } // namespace InlineBots -class MainWidget : public Ui::RpWidget, public RPCSender, private base::Subscriber { +class MainWidget + : public Ui::RpWidget + , public RPCSender + , private base::Subscriber + , private Media::Player::FloatDelegate { Q_OBJECT public: @@ -349,28 +353,6 @@ protected: bool eventFilter(QObject *o, QEvent *e) override; private: - struct Float { - template - Float( - QWidget *parent, - not_null controller, - not_null item, - ToggleCallback toggle, - DraggedCallback dragged); - - bool hiddenByWidget = false; - bool hiddenByHistory = false; - bool visible = false; - RectPart animationSide; - Animation visibleAnimation; - Window::Column column; - RectPart corner; - QPoint dragFrom; - Animation draggedAnimation; - bool hiddenByDrag = false; - object_ptr widget; - }; - using ChannelGetDifferenceTime = QMap; enum class ChannelDifferenceRequest { Unknown, @@ -479,27 +461,16 @@ private: void showAll(); void clearCachedBackground(); - void checkCurrentFloatPlayer(); - void createFloatPlayer(not_null item); - void toggleFloatPlayer(not_null instance); - void checkFloatPlayerVisibility(); - void updateFloatPlayerPosition(not_null instance); - void removeFloatPlayer(not_null instance); - Float *currentFloatPlayer() const { - return _playerFloats.empty() ? nullptr : _playerFloats.back().get(); - } - Window::AbstractSectionWidget *getFloatPlayerSection( - Window::Column column) const; - void finishFloatPlayerDrag( - not_null instance, - bool closed); - void updateFloatPlayerColumnCorner(QPoint center); - QPoint getFloatPlayerPosition(not_null instance) const; - QPoint getFloatPlayerHiddenPosition( - QPoint position, - QSize size, - RectPart side) const; - RectPart getFloatPlayerSide(QPoint center) const; + + not_null floatPlayerDelegate(); + not_null floatPlayerWidget() override; + not_null floatPlayerController() override; + not_null floatPlayerGetSection( + Window::Column column) override; + void floatPlayerEnumerateSections(Fn widget, + Window::Column widgetColumn)> callback) override; + void floatPlayerCloseHook(FullMsgId itemId) override; bool getDifferenceTimeChanged(ChannelData *channel, int32 ms, ChannelGetDifferenceTime &channelCurTime, TimeMs &curTime); @@ -552,7 +523,7 @@ private: object_ptr _playerPlaylist; object_ptr _playerPanel; bool _playerUsingPanel = false; - std::vector> _playerFloats; + Media::Player::FloatController _playerFloats; QPointer _forwardConfirm; // for single column layout object_ptr _hider = { nullptr }; diff --git a/Telegram/SourceFiles/media/player/media_player_float.cpp b/Telegram/SourceFiles/media/player/media_player_float.cpp index 7a1de6227..5e260b583 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.cpp +++ b/Telegram/SourceFiles/media/player/media_player_float.cpp @@ -17,8 +17,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/media_clip_reader.h" #include "media/media_audio.h" #include "media/view/media_clip_playback.h" +#include "media/player/media_player_instance.h" #include "media/player/media_player_round_controller.h" #include "window/window_controller.h" +#include "window/section_widget.h" #include "auth_session.h" #include "styles/style_media_player.h" #include "styles/style_history.h" @@ -266,5 +268,318 @@ void Float::repaintItem() { } } + +template +FloatController::Item::Item( + not_null parent, + not_null controller, + not_null item, + ToggleCallback toggle, + DraggedCallback dragged) +: animationSide(RectPart::Right) +, column(Window::Column::Second) +, corner(RectPart::TopRight) +, widget( + parent, + controller, + item, + [=, toggle = std::move(toggle)](bool visible) { + toggle(this, visible); + }, + [=, dragged = std::move(dragged)](bool closed) { + dragged(this, closed); + }) { +} + +FloatController::FloatController(not_null delegate) +: _delegate(delegate) +, _parent(_delegate->floatPlayerWidget()) +, _controller(_delegate->floatPlayerController()) { + subscribe(_controller->floatPlayerAreaUpdated(), [=] { + checkVisibility(); + }); + + subscribe(Media::Player::instance()->trackChangedNotifier(), [=]( + AudioMsgId::Type type) { + if (type == AudioMsgId::Type::Voice) { + checkCurrent(); + } + }); +} + +void FloatController::checkCurrent() { + const auto state = Media::Player::instance()->current(AudioMsgId::Type::Voice); + const auto fullId = state.contextId(); + const auto last = current(); + if (last + && !last->widget->detached() + && last->widget->item()->fullId() == fullId) { + return; + } + if (last) { + last->widget->detach(); + } + if (const auto item = App::histItemById(fullId)) { + if (const auto media = item->media()) { + if (const auto document = media->document()) { + if (document->isVideoMessage()) { + create(item); + } + } + } + } +} + +void FloatController::create(not_null item) { + _items.push_back(std::make_unique( + _parent, + _controller, + item, + [=](not_null instance, bool visible) { + instance->hiddenByWidget = !visible; + toggle(instance); + }, + [=](not_null instance, bool closed) { + finishDrag(instance, closed); + })); + current()->column = Auth().settings().floatPlayerColumn(); + current()->corner = Auth().settings().floatPlayerCorner(); + checkVisibility(); +} + +void FloatController::toggle(not_null instance) { + auto visible = !instance->hiddenByHistory && !instance->hiddenByWidget && instance->widget->isReady(); + if (instance->visible != visible) { + instance->widget->resetMouseState(); + instance->visible = visible; + if (!instance->visibleAnimation.animating() && !instance->hiddenByDrag) { + auto finalRect = QRect(getPosition(instance), instance->widget->size()); + instance->animationSide = getSide(finalRect.center()); + } + instance->visibleAnimation.start([=] { + updatePosition(instance); + }, visible ? 0. : 1., visible ? 1. : 0., st::slideDuration, visible ? anim::easeOutCirc : anim::linear); + updatePosition(instance); + } +} + +void FloatController::checkVisibility() { + auto instance = current(); + if (!instance) { + return; + } + + auto amVisible = false; + if (auto item = instance->widget->item()) { + Auth().data().queryItemVisibility().notify({ item, &amVisible }, true); + } + instance->hiddenByHistory = amVisible; + toggle(instance); + updatePosition(instance); +} + +void FloatController::hideAll() { + for (const auto &instance : _items) { + instance->widget->hide(); + } +} + +void FloatController::showVisible() { + for (const auto &instance : _items) { + if (instance->visible) { + instance->widget->show(); + } + } +} + +void FloatController::raiseAll() { + for (const auto &instance : _items) { + instance->widget->raise(); + } +} + +void FloatController::updatePositions() { + for (const auto &instance : _items) { + updatePosition(instance.get()); + } +} + +std::optional FloatController::filterWheelEvent( + QObject *object, + QEvent *event) { + for (const auto &instance : _items) { + if (instance->widget == object) { + const auto section = _delegate->floatPlayerGetSection( + instance->column); + return section->wheelEventFromFloatPlayer(event); + } + } + return std::nullopt; +} + +void FloatController::updatePosition(not_null 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, [=] { + remove(instance); + }); + } + return; + } + + if (!instance->widget->dragged()) { + if (instance->widget->isHidden()) { + instance->widget->show(); + } + + auto dragged = instance->draggedAnimation.current(1.); + auto position = QPoint(); + if (instance->hiddenByDrag) { + instance->widget->setOpacity(instance->widget->countOpacityByParent()); + position = getHiddenPosition(instance->dragFrom, instance->widget->size(), instance->animationSide); + } else { + instance->widget->setOpacity(visible * visible); + position = getPosition(instance); + if (visible < 1.) { + auto hiddenPosition = getHiddenPosition(position, instance->widget->size(), instance->animationSide); + position.setX(anim::interpolate(hiddenPosition.x(), position.x(), visible)); + position.setY(anim::interpolate(hiddenPosition.y(), position.y(), visible)); + } + } + if (dragged < 1.) { + position.setX(anim::interpolate(instance->dragFrom.x(), position.x(), dragged)); + position.setY(anim::interpolate(instance->dragFrom.y(), position.y(), dragged)); + } + instance->widget->move(position); + } +} + +QPoint FloatController::getHiddenPosition( + QPoint position, + QSize size, + RectPart side) const { + switch (side) { + case RectPart::Left: return { -size.width(), position.y() }; + case RectPart::Top: return { position.x(), -size.height() }; + case RectPart::Right: return { _parent->width(), position.y() }; + case RectPart::Bottom: return { position.x(), _parent->height() }; + } + Unexpected("Bad side in MainWidget::getFloatPlayerHiddenPosition()."); +} + +QPoint FloatController::getPosition(not_null instance) const { + const auto section = _delegate->floatPlayerGetSection(instance->column); + const auto rect = section->rectForFloatPlayer(); + auto position = rect.topLeft(); + if (IsBottomCorner(instance->corner)) { + position.setY(position.y() + rect.height() - instance->widget->height()); + } + if (IsRightCorner(instance->corner)) { + position.setX(position.x() + rect.width() - instance->widget->width()); + } + return _parent->mapFromGlobal(position); +} + +RectPart FloatController::getSide(QPoint center) const { + const auto left = std::abs(center.x()); + const auto right = std::abs(_parent->width() - center.x()); + const auto top = std::abs(center.y()); + const auto bottom = std::abs(_parent->height() - center.y()); + if (left < right && left < top && left < bottom) { + return RectPart::Left; + } else if (right < top && right < bottom) { + return RectPart::Right; + } else if (top < bottom) { + return RectPart::Top; + } + return RectPart::Bottom; +} + +void FloatController::remove(not_null instance) { + auto widget = std::move(instance->widget); + auto i = ranges::find_if(_items, [&](auto &item) { + return (item.get() == instance); + }); + Assert(i != _items.end()); + _items.erase(i); + + // ~QWidget() can call HistoryInner::enterEvent() which can + // lead to repaintHistoryItem() and we'll have an instance + // in _items with destroyed widget. So we destroy the + // instance first and only after that destroy the widget. + widget.destroy(); +} + +void FloatController::updateColumnCorner(QPoint center) { + Expects(!_items.empty()); + + auto size = _items.back()->widget->size(); + auto min = INT_MAX; + auto column = Auth().settings().floatPlayerColumn(); + auto corner = Auth().settings().floatPlayerCorner(); + auto checkSection = [&]( + not_null widget, + Window::Column widgetColumn) { + auto rect = _parent->mapFromGlobal(widget->rectForFloatPlayer()); + auto left = rect.x() + (size.width() / 2); + auto right = rect.x() + rect.width() - (size.width() / 2); + auto top = rect.y() + (size.height() / 2); + auto bottom = rect.y() + rect.height() - (size.height() / 2); + auto checkCorner = [&](QPoint point, RectPart checked) { + auto distance = (point - center).manhattanLength(); + if (min > distance) { + min = distance; + column = widgetColumn; + corner = checked; + } + }; + checkCorner({ left, top }, RectPart::TopLeft); + checkCorner({ right, top }, RectPart::TopRight); + checkCorner({ left, bottom }, RectPart::BottomLeft); + checkCorner({ right, bottom }, RectPart::BottomRight); + }; + + _delegate->floatPlayerEnumerateSections(checkSection); + + if (Auth().settings().floatPlayerColumn() != column) { + Auth().settings().setFloatPlayerColumn(column); + Auth().saveSettingsDelayed(); + } + if (Auth().settings().floatPlayerCorner() != corner) { + Auth().settings().setFloatPlayerCorner(corner); + Auth().saveSettingsDelayed(); + } +} + +void FloatController::finishDrag(not_null instance, bool closed) { + instance->dragFrom = instance->widget->pos(); + const auto center = instance->widget->geometry().center(); + if (closed) { + instance->hiddenByDrag = true; + instance->animationSide = getSide(center); + } + updateColumnCorner(center); + instance->column = Auth().settings().floatPlayerColumn(); + instance->corner = Auth().settings().floatPlayerCorner(); + + instance->draggedAnimation.finish(); + instance->draggedAnimation.start( + [=] { updatePosition(instance); }, + 0., + 1., + st::slideDuration, + anim::sineInOut); + updatePosition(instance); + + if (closed) { + if (const auto item = instance->widget->item()) { + _delegate->floatPlayerCloseHook(item->fullId()); + } + instance->widget->detach(); + } +} + } // namespace Player } // namespace Media diff --git a/Telegram/SourceFiles/media/player/media_player_float.h b/Telegram/SourceFiles/media/player/media_player_float.h index 79da42998..e3fd94ab3 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.h +++ b/Telegram/SourceFiles/media/player/media_player_float.h @@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Window { class Controller; +class AbstractSectionWidget; +enum class Column; } // namespace Window namespace Media { @@ -93,5 +95,80 @@ private: }; +class FloatDelegate { +public: + virtual not_null floatPlayerWidget() = 0; + virtual not_null floatPlayerController() = 0; + virtual not_null floatPlayerGetSection( + Window::Column column) = 0; + virtual void floatPlayerEnumerateSections(Fn widget, + Window::Column widgetColumn)> callback) = 0; + virtual void floatPlayerCloseHook(FullMsgId itemId) = 0; + +}; + +class FloatController : private base::Subscriber { +public: + explicit FloatController(not_null delegate); + + void checkVisibility(); + void hideAll(); + void showVisible(); + void raiseAll(); + void updatePositions(); + std::optional filterWheelEvent( + QObject *object, + QEvent *event); + +private: + struct Item { + template + Item( + not_null parent, + not_null controller, + not_null item, + ToggleCallback toggle, + DraggedCallback dragged); + + bool hiddenByWidget = false; + bool hiddenByHistory = false; + bool visible = false; + RectPart animationSide; + Animation visibleAnimation; + Window::Column column; + RectPart corner; + QPoint dragFrom; + Animation draggedAnimation; + bool hiddenByDrag = false; + object_ptr widget; + }; + + void checkCurrent(); + void create(not_null item); + void toggle(not_null instance); + void updatePosition(not_null instance); + void remove(not_null instance); + Item *current() const { + return _items.empty() ? nullptr : _items.back().get(); + } + void finishDrag( + not_null instance, + bool closed); + void updateColumnCorner(QPoint center); + QPoint getPosition(not_null instance) const; + QPoint getHiddenPosition( + QPoint position, + QSize size, + RectPart side) const; + RectPart getSide(QPoint center) const; + + not_null _delegate; + not_null _parent; + not_null _controller; + std::vector> _items; + +}; + } // namespace Player } // namespace Media