From c91e29d15d487f7c4c369485d0f01290e84cb99a Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 24 May 2017 15:07:58 +0300 Subject: [PATCH] Improve floating player show / hide animations. Also replace Window::Corner with a generic RectPart enumeration. --- Telegram/SourceFiles/auth_session.cpp | 14 +-- Telegram/SourceFiles/auth_session.h | 7 +- Telegram/SourceFiles/mainwidget.cpp | 118 ++++++++++++------ Telegram/SourceFiles/mainwidget.h | 20 +-- .../media/player/media_player_float.h | 3 + Telegram/SourceFiles/ui/twidget.h | 48 +++++++ Telegram/SourceFiles/window/section_widget.h | 23 ---- 7 files changed, 155 insertions(+), 78 deletions(-) diff --git a/Telegram/SourceFiles/auth_session.cpp b/Telegram/SourceFiles/auth_session.cpp index 90c1f158c0..80eaafed6e 100644 --- a/Telegram/SourceFiles/auth_session.cpp +++ b/Telegram/SourceFiles/auth_session.cpp @@ -40,7 +40,7 @@ constexpr auto kAutoLockTimeoutLateMs = TimeMs(3000); AuthSessionData::Variables::Variables() : selectorTab(ChatHelpers::SelectorTab::Emoji) , floatPlayerColumn(Window::Column::Second) -, floatPlayerCorner(Window::Corner::TopRight) { +, floatPlayerCorner(RectPart::TopRight) { } QByteArray AuthSessionData::serialize() const { @@ -90,7 +90,7 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) { qint32 tabbedSelectorSectionEnabled = 1; qint32 tabbedSelectorSectionTooltipShown = 0; qint32 floatPlayerColumn = static_cast(Window::Column::Second); - qint32 floatPlayerCorner = static_cast(Window::Corner::TopRight); + qint32 floatPlayerCorner = static_cast(RectPart::TopRight); QMap soundOverrides; stream >> selectorTab; stream >> lastSeenWarningSeen; @@ -135,12 +135,12 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) { case Window::Column::Second: case Window::Column::Third: _variables.floatPlayerColumn = uncheckedColumn; break; } - auto uncheckedCorner = static_cast(floatPlayerCorner); + auto uncheckedCorner = static_cast(floatPlayerCorner); switch (uncheckedCorner) { - case Window::Corner::TopLeft: - case Window::Corner::TopRight: - case Window::Corner::BottomLeft: - case Window::Corner::BottomRight: _variables.floatPlayerCorner = uncheckedCorner; break; + case RectPart::TopLeft: + case RectPart::TopRight: + case RectPart::BottomLeft: + case RectPart::BottomRight: _variables.floatPlayerCorner = uncheckedCorner; break; } } diff --git a/Telegram/SourceFiles/auth_session.h b/Telegram/SourceFiles/auth_session.h index 03734625b0..bd45b01e88 100644 --- a/Telegram/SourceFiles/auth_session.h +++ b/Telegram/SourceFiles/auth_session.h @@ -31,7 +31,6 @@ namespace Notifications { class System; } // namespace Notifications enum class Column; -enum class Corner; } // namespace Window namespace Calls { @@ -108,10 +107,10 @@ public: Window::Column floatPlayerColumn() const { return _variables.floatPlayerColumn; } - void setFloatPlayerCorner(Window::Corner corner) { + void setFloatPlayerCorner(RectPart corner) { _variables.floatPlayerCorner = corner; } - Window::Corner floatPlayerCorner() const { + RectPart floatPlayerCorner() const { return _variables.floatPlayerCorner; } @@ -125,7 +124,7 @@ private: int tabbedSelectorSectionTooltipShown = 0; QMap soundOverrides; Window::Column floatPlayerColumn; - Window::Corner floatPlayerCorner; + RectPart floatPlayerCorner; }; base::Variable _contactsLoaded = { false }; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index d4dbf79efe..416d95ffc1 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -102,8 +102,9 @@ StackItemSection::~StackItemSection() { template MainWidget::Float::Float(QWidget *parent, HistoryItem *item, ToggleCallback toggle, DraggedCallback dragged) -: column(Window::Column::Second) -, corner(Window::Corner::TopRight) +: animationSide(RectPart::Right) +, column(Window::Column::Second) +, corner(RectPart::TopRight) , widget(parent, item, [this, toggle = std::move(toggle)](bool visible) { toggle(this, visible); }, [this, dragged = std::move(dragged)](bool closed) { @@ -252,10 +253,10 @@ void MainWidget::checkCurrentFloatPlayer() { 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) { + _playerFloats.push_back(std::make_unique(this, item, [this](gsl::not_null instance, bool visible) { instance->hiddenByWidget = !visible; toggleFloatPlayer(instance); - }, [this](Float *instance, bool closed) { + }, [this](gsl::not_null instance, bool closed) { finishFloatPlayerDrag(instance, closed); })); currentFloatPlayer()->column = AuthSession::Current().data().floatPlayerColumn(); @@ -268,11 +269,15 @@ void MainWidget::checkCurrentFloatPlayer() { } } -void MainWidget::toggleFloatPlayer(Float *instance) { +void MainWidget::toggleFloatPlayer(gsl::not_null instance) { auto visible = !instance->hiddenByHistory && !instance->hiddenByWidget && !instance->widget->detached(); 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); @@ -295,7 +300,7 @@ void MainWidget::checkFloatPlayerVisibility() { updateFloatPlayerPosition(instance); } -void MainWidget::updateFloatPlayerPosition(Float *instance) { +void MainWidget::updateFloatPlayerPosition(gsl::not_null instance) { auto visible = instance->visibleAnimation.current(instance->visible ? 1. : 0.); if (visible == 0. && !instance->visible) { instance->widget->hide(); @@ -307,27 +312,25 @@ void MainWidget::updateFloatPlayerPosition(Float *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(); - position.setY(anim::interpolate(hiddenTop, position.y(), visible)); 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)); @@ -336,7 +339,46 @@ void MainWidget::updateFloatPlayerPosition(Float *instance) { } } -void MainWidget::removeFloatPlayer(Float *instance) { +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(gsl::not_null instance) const { + auto column = instance->column; + auto section = getFloatPlayerSection(&column); + auto rect = section->rectForFloatPlayer(column, instance->column); + 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(gsl::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); @@ -351,7 +393,7 @@ void MainWidget::removeFloatPlayer(Float *instance) { widget.destroy(); } -Window::AbstractSectionWidget *MainWidget::getFloatPlayerSection(gsl::not_null column) { +Window::AbstractSectionWidget *MainWidget::getFloatPlayerSection(gsl::not_null column) const { if (!Adaptive::Normal()) { *column = Adaptive::OneColumn() ? Window::Column::First : Window::Column::Second; if (Adaptive::OneColumn() && selectingPeer()) { @@ -389,17 +431,17 @@ void MainWidget::updateFloatPlayerColumnCorner(QPoint center) { 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 = [this, playerColumn, &min, &column, &corner](int distance, Window::Corner checked) { + auto checkCorner = [this, playerColumn, &min, &column, &corner](int distance, RectPart checked) { if (min > distance) { min = distance; column = playerColumn; corner = checked; } }; - checkCorner((QPoint(left, top) - center).manhattanLength(), Window::Corner::TopLeft); - checkCorner((QPoint(right, top) - center).manhattanLength(), Window::Corner::TopRight); - checkCorner((QPoint(left, bottom) - center).manhattanLength(), Window::Corner::BottomLeft); - checkCorner((QPoint(right, bottom) - center).manhattanLength(), Window::Corner::BottomRight); + checkCorner((QPoint(left, top) - center).manhattanLength(), RectPart::TopLeft); + checkCorner((QPoint(right, top) - center).manhattanLength(), RectPart::TopRight); + checkCorner((QPoint(left, bottom) - center).manhattanLength(), RectPart::BottomLeft); + checkCorner((QPoint(right, bottom) - center).manhattanLength(), RectPart::BottomRight); }; if (!Adaptive::Normal()) { @@ -436,10 +478,14 @@ void MainWidget::updateFloatPlayerColumnCorner(QPoint center) { } } -void MainWidget::finishFloatPlayerDrag(Float *instance, bool closed) { +void MainWidget::finishFloatPlayerDrag(gsl::not_null instance, bool closed) { instance->dragFrom = instance->widget->pos(); - - updateFloatPlayerColumnCorner(instance->widget->geometry().center()); + auto center = instance->widget->geometry().center(); + if (closed) { + instance->hiddenByDrag = true; + instance->animationSide = getFloatPlayerSide(center); + } + updateFloatPlayerColumnCorner(center); instance->column = AuthSession::Current().data().floatPlayerColumn(); instance->corner = AuthSession::Current().data().floatPlayerCorner(); @@ -1001,7 +1047,7 @@ void MainWidget::inlineSwitchLayer(const QString &botAndQuery) { hiderLayer(object_ptr(this, botAndQuery)); } -bool MainWidget::selectingPeer(bool withConfirm) { +bool MainWidget::selectingPeer(bool withConfirm) const { return _hider ? (withConfirm ? _hider->withConfirm() : true) : false; } diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 24710d7b2a..aa3f8e85fb 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -58,7 +58,6 @@ class SectionWidget; class AbstractSectionWidget; struct SectionSlideParams; enum class Column; -enum class Corner; } // namespace Window namespace Calls { @@ -256,7 +255,7 @@ public: void onShareContact(const PeerId &peer, UserData *contact); bool onSendPaths(const PeerId &peer); void onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data); - bool selectingPeer(bool withConfirm = false); + bool selectingPeer(bool withConfirm = false) const; bool selectingPeerForInlineSwitch(); void offerPeer(PeerId peer); void dialogsActivate(); @@ -471,11 +470,13 @@ private: bool hiddenByWidget = false; bool hiddenByHistory = false; bool visible = false; + RectPart animationSide; Animation visibleAnimation; Window::Column column; - Window::Corner corner; + RectPart corner; QPoint dragFrom; Animation draggedAnimation; + bool hiddenByDrag = false; object_ptr widget; }; @@ -579,16 +580,19 @@ private: void clearCachedBackground(); void checkCurrentFloatPlayer(); - void toggleFloatPlayer(Float *instance); + void toggleFloatPlayer(gsl::not_null instance); void checkFloatPlayerVisibility(); - void updateFloatPlayerPosition(Float *instance); - void removeFloatPlayer(Float *instance); + void updateFloatPlayerPosition(gsl::not_null instance); + void removeFloatPlayer(gsl::not_null instance); Float *currentFloatPlayer() const { return _playerFloats.empty() ? nullptr : _playerFloats.back().get(); } - Window::AbstractSectionWidget *getFloatPlayerSection(gsl::not_null column); - void finishFloatPlayerDrag(Float *instance, bool closed); + Window::AbstractSectionWidget *getFloatPlayerSection(gsl::not_null column) const; + void finishFloatPlayerDrag(gsl::not_null instance, bool closed); void updateFloatPlayerColumnCorner(QPoint center); + QPoint getFloatPlayerPosition(gsl::not_null instance) const; + QPoint getFloatPlayerHiddenPosition(QPoint position, QSize size, RectPart side) const; + RectPart getFloatPlayerSide(QPoint center) const; bool ptsUpdated(int32 pts, int32 ptsCount); bool ptsUpdated(int32 pts, int32 ptsCount, const MTPUpdates &updates); diff --git a/Telegram/SourceFiles/media/player/media_player_float.h b/Telegram/SourceFiles/media/player/media_player_float.h index c7ae731dde..c69a5e6f8a 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.h +++ b/Telegram/SourceFiles/media/player/media_player_float.h @@ -40,6 +40,9 @@ public: update(); } } + float64 countOpacityByParent() const { + return outRatio(); + } void detach(); bool detached() const { return !_item; diff --git a/Telegram/SourceFiles/ui/twidget.h b/Telegram/SourceFiles/ui/twidget.h index af6b2ecfd0..7de0ea545e 100644 --- a/Telegram/SourceFiles/ui/twidget.h +++ b/Telegram/SourceFiles/ui/twidget.h @@ -27,6 +27,54 @@ QString GetOverride(const QString &familyName); } // namespace +enum class RectPart { + None = 0, + + TopLeft = (1 << 0), + Top = (1 << 1), + TopRight = (1 << 2), + Left = (1 << 3), + Center = (1 << 4), + Right = (1 << 5), + BottomLeft = (1 << 6), + Bottom = (1 << 7), + BottomRight = (1 << 8), + + FullTop = TopLeft | Top | TopRight, + NoTopBottom = Left | Center | Right, + FullBottom = BottomLeft | Bottom | BottomRight, + NoTop = NoTopBottom | FullBottom, + NoBottom = FullTop | NoTopBottom, + + FullLeft = TopLeft | Left | BottomLeft, + NoLeftRight = Top | Center | Bottom, + FullRight = TopRight | Right | BottomRight, + NoLeft = NoLeftRight | FullRight, + NoRight = FullLeft | NoLeftRight, + + CornersMask = TopLeft | TopRight | BottomLeft | BottomRight, + SidesMask = Top | Bottom | Left | Right, + + All = FullTop | NoTop, +}; +Q_DECLARE_FLAGS(RectParts, RectPart); + +inline bool IsTopCorner(RectPart corner) { + return (corner == RectPart::TopLeft) || (corner == RectPart::TopRight); +} + +inline bool IsBottomCorner(RectPart corner) { + return (corner == RectPart::BottomLeft) || (corner == RectPart::BottomRight); +} + +inline bool IsLeftCorner(RectPart corner) { + return (corner == RectPart::TopLeft) || (corner == RectPart::BottomLeft); +} + +inline bool IsRightCorner(RectPart corner) { + return (corner == RectPart::TopRight) || (corner == RectPart::BottomRight); +} + class Painter : public QPainter { public: explicit Painter(QPaintDevice *device) : QPainter(device) { diff --git a/Telegram/SourceFiles/window/section_widget.h b/Telegram/SourceFiles/window/section_widget.h index 1438f2e601..6bf05676f4 100644 --- a/Telegram/SourceFiles/window/section_widget.h +++ b/Telegram/SourceFiles/window/section_widget.h @@ -33,29 +33,6 @@ enum class Column { 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) {