From c91e29d15d487f7c4c369485d0f01290e84cb99a Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
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 90c1f158c..80eaafed6 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<qint32>(Window::Column::Second);
-	qint32 floatPlayerCorner = static_cast<qint32>(Window::Corner::TopRight);
+	qint32 floatPlayerCorner = static_cast<qint32>(RectPart::TopRight);
 	QMap<QString, QString> 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<Window::Corner>(floatPlayerCorner);
+	auto uncheckedCorner = static_cast<RectPart>(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 03734625b..bd45b01e8 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<QString, QString> soundOverrides;
 		Window::Column floatPlayerColumn;
-		Window::Corner floatPlayerCorner;
+		RectPart floatPlayerCorner;
 	};
 
 	base::Variable<bool> _contactsLoaded = { false };
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index d4dbf79ef..416d95ffc 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -102,8 +102,9 @@ StackItemSection::~StackItemSection() {
 
 template <typename ToggleCallback, typename DraggedCallback>
 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<Float>(this, item, [this](Float *instance, bool visible) {
+						_playerFloats.push_back(std::make_unique<Float>(this, item, [this](gsl::not_null<Float*> instance, bool visible) {
 							instance->hiddenByWidget = !visible;
 							toggleFloatPlayer(instance);
-						}, [this](Float *instance, bool closed) {
+						}, [this](gsl::not_null<Float*> 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<Float*> 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<Float*> 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<Float*> 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<Float*> 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<Window::Column*> column) {
+Window::AbstractSectionWidget *MainWidget::getFloatPlayerSection(gsl::not_null<Window::Column*> 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<Float*> 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<HistoryHider>(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 24710d7b2..aa3f8e85f 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<Media::Player::Float> widget;
 	};
 
@@ -579,16 +580,19 @@ private:
 
 	void clearCachedBackground();
 	void checkCurrentFloatPlayer();
-	void toggleFloatPlayer(Float *instance);
+	void toggleFloatPlayer(gsl::not_null<Float*> instance);
 	void checkFloatPlayerVisibility();
-	void updateFloatPlayerPosition(Float *instance);
-	void removeFloatPlayer(Float *instance);
+	void updateFloatPlayerPosition(gsl::not_null<Float*> instance);
+	void removeFloatPlayer(gsl::not_null<Float*> instance);
 	Float *currentFloatPlayer() const {
 		return _playerFloats.empty() ? nullptr : _playerFloats.back().get();
 	}
-	Window::AbstractSectionWidget *getFloatPlayerSection(gsl::not_null<Window::Column*> column);
-	void finishFloatPlayerDrag(Float *instance, bool closed);
+	Window::AbstractSectionWidget *getFloatPlayerSection(gsl::not_null<Window::Column*> column) const;
+	void finishFloatPlayerDrag(gsl::not_null<Float*> instance, bool closed);
 	void updateFloatPlayerColumnCorner(QPoint center);
+	QPoint getFloatPlayerPosition(gsl::not_null<Float*> 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 c7ae731dd..c69a5e6f8 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 af6b2ecfd..7de0ea545 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 1438f2e60..6bf05676f 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<Window::Controller*> controller) : TWidget(parent), _controller(controller) {