1562 lines
		
	
	
	
		
			43 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1562 lines
		
	
	
	
		
			43 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
This file is part of Telegram Desktop,
 | 
						|
the official desktop application for the Telegram messaging service.
 | 
						|
 | 
						|
For license and copyright information please follow this link:
 | 
						|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
						|
*/
 | 
						|
#include "media/view/media_view_pip.h"
 | 
						|
 | 
						|
#include "media/streaming/media_streaming_player.h"
 | 
						|
#include "media/streaming/media_streaming_document.h"
 | 
						|
#include "media/streaming/media_streaming_utility.h"
 | 
						|
#include "media/view/media_view_playback_progress.h"
 | 
						|
#include "media/audio/media_audio.h"
 | 
						|
#include "data/data_document.h"
 | 
						|
#include "data/data_document_media.h"
 | 
						|
#include "data/data_file_origin.h"
 | 
						|
#include "data/data_session.h"
 | 
						|
#include "data/data_media_rotation.h"
 | 
						|
#include "core/application.h"
 | 
						|
#include "base/platform/base_platform_info.h"
 | 
						|
#include "ui/platform/ui_platform_utility.h"
 | 
						|
#include "ui/widgets/buttons.h"
 | 
						|
#include "ui/wrap/fade_wrap.h"
 | 
						|
#include "ui/widgets/shadow.h"
 | 
						|
#include "window/window_controller.h"
 | 
						|
#include "layout.h" // formatDurationText
 | 
						|
#include "styles/style_window.h"
 | 
						|
#include "styles/style_media_view.h"
 | 
						|
#include "styles/style_calls.h" // st::callShadow
 | 
						|
 | 
						|
#include <QtGui/QWindow>
 | 
						|
#include <QtGui/QScreen>
 | 
						|
#include <QtWidgets/QApplication>
 | 
						|
 | 
						|
namespace Media {
 | 
						|
namespace View {
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr auto kPipLoaderPriority = 2;
 | 
						|
constexpr auto kSaveGeometryTimeout = crl::time(1000);
 | 
						|
constexpr auto kMsInSecond = 1000;
 | 
						|
 | 
						|
[[nodiscard]] bool IsWindowControlsOnLeft() {
 | 
						|
	return Platform::IsMac();
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] QRect ScreenFromPosition(QPoint point) {
 | 
						|
	const auto screen = [&]() -> QScreen* {
 | 
						|
		for (const auto screen : QGuiApplication::screens()) {
 | 
						|
			if (screen->geometry().contains(point)) {
 | 
						|
				return screen;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return nullptr;
 | 
						|
	}();
 | 
						|
	const auto use = screen ? screen : QGuiApplication::primaryScreen();
 | 
						|
	return use
 | 
						|
		? use->availableGeometry()
 | 
						|
		: QRect(0, 0, st::windowDefaultWidth, st::windowDefaultHeight);
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] QPoint ClampToEdges(QRect screen, QRect inner) {
 | 
						|
	const auto skip = st::pipBorderSkip;
 | 
						|
	const auto area = st::pipBorderSnapArea;
 | 
						|
	const auto sleft = screen.x() + skip;
 | 
						|
	const auto stop = screen.y() + skip;
 | 
						|
	const auto sright = screen.x() + screen.width() - skip;
 | 
						|
	const auto sbottom = screen.y() + screen.height() - skip;
 | 
						|
	const auto ileft = inner.x();
 | 
						|
	const auto itop = inner.y();
 | 
						|
	const auto iright = inner.x() + inner.width();
 | 
						|
	const auto ibottom = inner.y() + inner.height();
 | 
						|
	auto shiftx = 0;
 | 
						|
	auto shifty = 0;
 | 
						|
	if (iright + shiftx >= sright - area && iright + shiftx < sright + area) {
 | 
						|
		shiftx += (sright - iright);
 | 
						|
	}
 | 
						|
	if (ileft + shiftx >= sleft - area && ileft + shiftx < sleft + area) {
 | 
						|
		shiftx += (sleft - ileft);
 | 
						|
	}
 | 
						|
	if (ibottom + shifty >= sbottom - area && ibottom + shifty < sbottom + area) {
 | 
						|
		shifty += (sbottom - ibottom);
 | 
						|
	}
 | 
						|
	if (itop + shifty >= stop - area && itop + shifty < stop + area) {
 | 
						|
		shifty += (stop - itop);
 | 
						|
	}
 | 
						|
	return inner.topLeft() + QPoint(shiftx, shifty);
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] QRect Transformed(
 | 
						|
		QRect original,
 | 
						|
		QSize minimalSize,
 | 
						|
		QSize maximalSize,
 | 
						|
		QPoint delta,
 | 
						|
		RectPart by) {
 | 
						|
	const auto width = original.width();
 | 
						|
	const auto height = original.height();
 | 
						|
	const auto x1 = width - minimalSize.width();
 | 
						|
	const auto x2 = maximalSize.width() - width;
 | 
						|
	const auto y1 = height - minimalSize.height();
 | 
						|
	const auto y2 = maximalSize.height() - height;
 | 
						|
	switch (by) {
 | 
						|
	case RectPart::Center: return original.translated(delta);
 | 
						|
	case RectPart::TopLeft:
 | 
						|
		original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
 | 
						|
		original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
 | 
						|
		return original;
 | 
						|
	case RectPart::TopRight:
 | 
						|
		original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
 | 
						|
		original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
 | 
						|
		return original;
 | 
						|
	case RectPart::BottomRight:
 | 
						|
		original.setHeight(
 | 
						|
			original.height() + std::clamp(delta.y(), -y1, y2));
 | 
						|
		original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
 | 
						|
		return original;
 | 
						|
	case RectPart::BottomLeft:
 | 
						|
		original.setHeight(
 | 
						|
			original.height() + std::clamp(delta.y(), -y1, y2));
 | 
						|
		original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
 | 
						|
		return original;
 | 
						|
	case RectPart::Left:
 | 
						|
		original.setLeft(original.x() + std::clamp(delta.x(), -x2, x1));
 | 
						|
		return original;
 | 
						|
	case RectPart::Top:
 | 
						|
		original.setTop(original.y() + std::clamp(delta.y(), -y2, y1));
 | 
						|
		return original;
 | 
						|
	case RectPart::Right:
 | 
						|
		original.setWidth(original.width() + std::clamp(delta.x(), -x1, x2));
 | 
						|
		return original;
 | 
						|
	case RectPart::Bottom:
 | 
						|
		original.setHeight(
 | 
						|
			original.height() + std::clamp(delta.y(), -y1, y2));
 | 
						|
		return original;
 | 
						|
	}
 | 
						|
	return original;
 | 
						|
	Unexpected("RectPart in PiP Transformed.");
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] QRect Constrained(
 | 
						|
		QRect original,
 | 
						|
		QSize minimalSize,
 | 
						|
		QSize maximalSize,
 | 
						|
		QSize ratio,
 | 
						|
		RectPart by,
 | 
						|
		RectParts attached) {
 | 
						|
	if (by == RectPart::Center) {
 | 
						|
		return original;
 | 
						|
	} else if (!original.width() && !original.height()) {
 | 
						|
		return QRect(original.topLeft(), ratio);
 | 
						|
	}
 | 
						|
	const auto widthLarger = (original.width() * ratio.height())
 | 
						|
		> (original.height() * ratio.width());
 | 
						|
	const auto desiredSize = ratio.scaled(
 | 
						|
		original.size(),
 | 
						|
		(((RectParts(by) & RectPart::AllCorners)
 | 
						|
			|| ((by == RectPart::Top || by == RectPart::Bottom)
 | 
						|
				&& widthLarger)
 | 
						|
			|| ((by == RectPart::Left || by == RectPart::Right)
 | 
						|
				&& !widthLarger))
 | 
						|
			? Qt::KeepAspectRatio
 | 
						|
			: Qt::KeepAspectRatioByExpanding));
 | 
						|
	const auto newSize = QSize(
 | 
						|
		std::clamp(
 | 
						|
			desiredSize.width(),
 | 
						|
			minimalSize.width(),
 | 
						|
			maximalSize.width()),
 | 
						|
		std::clamp(
 | 
						|
			desiredSize.height(),
 | 
						|
			minimalSize.height(),
 | 
						|
			maximalSize.height()));
 | 
						|
	switch (by) {
 | 
						|
	case RectPart::TopLeft:
 | 
						|
		return QRect(
 | 
						|
			original.topLeft() + QPoint(
 | 
						|
				original.width() - newSize.width(),
 | 
						|
				original.height() - newSize.height()),
 | 
						|
			newSize);
 | 
						|
	case RectPart::TopRight:
 | 
						|
		return QRect(
 | 
						|
			original.topLeft() + QPoint(
 | 
						|
				0,
 | 
						|
				original.height() - newSize.height()),
 | 
						|
			newSize);
 | 
						|
	case RectPart::BottomRight:
 | 
						|
		return QRect(original.topLeft(), newSize);
 | 
						|
	case RectPart::BottomLeft:
 | 
						|
		return QRect(
 | 
						|
			original.topLeft() + QPoint(
 | 
						|
				original.width() - newSize.width(),
 | 
						|
				0),
 | 
						|
			newSize);
 | 
						|
	case RectPart::Left:
 | 
						|
		return QRect(
 | 
						|
			original.topLeft() + QPoint(
 | 
						|
				(original.width() - newSize.width()),
 | 
						|
				((attached & RectPart::Top)
 | 
						|
					? 0
 | 
						|
					: (attached & RectPart::Bottom)
 | 
						|
					? (original.height() - newSize.height())
 | 
						|
					: (original.height() - newSize.height()) / 2)),
 | 
						|
			newSize);
 | 
						|
	case RectPart::Top:
 | 
						|
		return QRect(
 | 
						|
			original.topLeft() + QPoint(
 | 
						|
				((attached & RectPart::Left)
 | 
						|
					? 0
 | 
						|
					: (attached & RectPart::Right)
 | 
						|
					? (original.width() - newSize.width())
 | 
						|
					: (original.width() - newSize.width()) / 2),
 | 
						|
				0),
 | 
						|
			newSize);
 | 
						|
	case RectPart::Right:
 | 
						|
		return QRect(
 | 
						|
			original.topLeft() + QPoint(
 | 
						|
				0,
 | 
						|
				((attached & RectPart::Top)
 | 
						|
					? 0
 | 
						|
					: (attached & RectPart::Bottom)
 | 
						|
					? (original.height() - newSize.height())
 | 
						|
					: (original.height() - newSize.height()) / 2)),
 | 
						|
			newSize);
 | 
						|
	case RectPart::Bottom:
 | 
						|
		return QRect(
 | 
						|
			original.topLeft() + QPoint(
 | 
						|
				((attached & RectPart::Left)
 | 
						|
					? 0
 | 
						|
					: (attached & RectPart::Right)
 | 
						|
					? (original.width() - newSize.width())
 | 
						|
					: (original.width() - newSize.width()) / 2),
 | 
						|
				(original.height() - newSize.height())),
 | 
						|
			newSize);
 | 
						|
	}
 | 
						|
	Unexpected("RectPart in PiP Constrained.");
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] QByteArray Serialize(const PipPanel::Position &position) {
 | 
						|
	auto result = QByteArray();
 | 
						|
	auto stream = QDataStream(&result, QIODevice::WriteOnly);
 | 
						|
	stream.setVersion(QDataStream::Qt_5_3);
 | 
						|
	stream
 | 
						|
		<< qint32(position.attached.value())
 | 
						|
		<< qint32(position.snapped.value())
 | 
						|
		<< position.screen
 | 
						|
		<< position.geometry;
 | 
						|
	stream.device()->close();
 | 
						|
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
[[nodiscard]] PipPanel::Position Deserialize(const QByteArray &data) {
 | 
						|
	auto stream = QDataStream(data);
 | 
						|
	auto result = PipPanel::Position();
 | 
						|
	auto attached = qint32(0);
 | 
						|
	auto snapped = qint32(0);
 | 
						|
	stream >> attached >> snapped >> result.screen >> result.geometry;
 | 
						|
	if (stream.status() != QDataStream::Ok) {
 | 
						|
		return {};
 | 
						|
	}
 | 
						|
	result.attached = RectParts::from_raw(attached);
 | 
						|
	result.snapped = RectParts::from_raw(snapped);
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
Streaming::FrameRequest UnrotateRequest(
 | 
						|
		const Streaming::FrameRequest &request,
 | 
						|
		int rotation) {
 | 
						|
	if (!rotation) {
 | 
						|
		return request;
 | 
						|
	}
 | 
						|
	const auto unrotatedCorner = [&](RectPart corner) {
 | 
						|
		if (!(request.corners & corner)) {
 | 
						|
			return RectPart(0);
 | 
						|
		}
 | 
						|
		switch (corner) {
 | 
						|
		case RectPart::TopLeft:
 | 
						|
			return (rotation == 90)
 | 
						|
				? RectPart::BottomLeft
 | 
						|
				: (rotation == 180)
 | 
						|
				? RectPart::BottomRight
 | 
						|
				: RectPart::TopRight;
 | 
						|
		case RectPart::TopRight:
 | 
						|
			return (rotation == 90)
 | 
						|
				? RectPart::TopLeft
 | 
						|
				: (rotation == 180)
 | 
						|
				? RectPart::BottomLeft
 | 
						|
				: RectPart::BottomRight;
 | 
						|
		case RectPart::BottomRight:
 | 
						|
			return (rotation == 90)
 | 
						|
				? RectPart::TopRight
 | 
						|
				: (rotation == 180)
 | 
						|
				? RectPart::TopLeft
 | 
						|
				: RectPart::BottomLeft;
 | 
						|
		case RectPart::BottomLeft:
 | 
						|
			return (rotation == 90)
 | 
						|
				? RectPart::BottomRight
 | 
						|
				: (rotation == 180)
 | 
						|
				? RectPart::TopRight
 | 
						|
				: RectPart::TopLeft;
 | 
						|
		}
 | 
						|
		Unexpected("Corner in rotateCorner.");
 | 
						|
	};
 | 
						|
	auto result = request;
 | 
						|
	result.outer = FlipSizeByRotation(request.outer, rotation);
 | 
						|
	result.resize = FlipSizeByRotation(request.resize, rotation);
 | 
						|
	result.corners = unrotatedCorner(RectPart::TopLeft)
 | 
						|
		| unrotatedCorner(RectPart::TopRight)
 | 
						|
		| unrotatedCorner(RectPart::BottomRight)
 | 
						|
		| unrotatedCorner(RectPart::BottomLeft);
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
QRect RotatedRect(QRect rect, int rotation) {
 | 
						|
	switch (rotation) {
 | 
						|
	case 0: return rect;
 | 
						|
	case 90: return QRect(
 | 
						|
		rect.y(),
 | 
						|
		-rect.x() - rect.width(),
 | 
						|
		rect.height(),
 | 
						|
		rect.width());
 | 
						|
	case 180: return QRect(
 | 
						|
		-rect.x() - rect.width(),
 | 
						|
		-rect.y() - rect.height(),
 | 
						|
		rect.width(),
 | 
						|
		rect.height());
 | 
						|
	case 270: return QRect(
 | 
						|
		-rect.y() - rect.height(),
 | 
						|
		rect.x(),
 | 
						|
		rect.height(),
 | 
						|
		rect.width());
 | 
						|
	}
 | 
						|
	Unexpected("Rotation in RotatedRect.");
 | 
						|
}
 | 
						|
 | 
						|
bool UsePainterRotation(int rotation) {
 | 
						|
	return Platform::IsMac() || !(rotation % 180);
 | 
						|
}
 | 
						|
 | 
						|
QSize FlipSizeByRotation(QSize size, int rotation) {
 | 
						|
	return (((rotation / 90) % 2) == 1)
 | 
						|
		? QSize(size.height(), size.width())
 | 
						|
		: size;
 | 
						|
}
 | 
						|
 | 
						|
QImage RotateFrameImage(QImage image, int rotation) {
 | 
						|
	auto transform = QTransform();
 | 
						|
	transform.rotate(rotation);
 | 
						|
	return image.transformed(transform);
 | 
						|
}
 | 
						|
 | 
						|
PipPanel::PipPanel(
 | 
						|
	QWidget *parent,
 | 
						|
	Fn<void(QPainter&, FrameRequest)> paint)
 | 
						|
: _parent(parent)
 | 
						|
, _paint(std::move(paint)) {
 | 
						|
	setWindowFlags(Qt::Tool
 | 
						|
		| Qt::WindowStaysOnTopHint
 | 
						|
		| Qt::FramelessWindowHint
 | 
						|
		| Qt::WindowDoesNotAcceptFocus);
 | 
						|
	setAttribute(Qt::WA_ShowWithoutActivating);
 | 
						|
	setAttribute(Qt::WA_MacAlwaysShowToolWindow);
 | 
						|
	setAttribute(Qt::WA_NoSystemBackground);
 | 
						|
	setAttribute(Qt::WA_TranslucentBackground);
 | 
						|
	Ui::Platform::IgnoreAllActivation(this);
 | 
						|
	Ui::Platform::InitOnTopPanel(this);
 | 
						|
	setMouseTracking(true);
 | 
						|
	resize(0, 0);
 | 
						|
	show();
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::setAspectRatio(QSize ratio) {
 | 
						|
	if (_ratio == ratio) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_ratio = ratio;
 | 
						|
	if (_ratio.isEmpty()) {
 | 
						|
		_ratio = QSize(1, 1);
 | 
						|
	}
 | 
						|
	if (!size().isEmpty()) {
 | 
						|
		setPosition(countPosition());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::setPosition(Position position) {
 | 
						|
	if (!position.screen.isEmpty()) {
 | 
						|
		for (const auto screen : QApplication::screens()) {
 | 
						|
			if (screen->geometry() == position.screen) {
 | 
						|
				setPositionOnScreen(position, screen->availableGeometry());
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	setPositionDefault();
 | 
						|
}
 | 
						|
 | 
						|
QRect PipPanel::inner() const {
 | 
						|
	return rect().marginsRemoved(_padding);
 | 
						|
}
 | 
						|
 | 
						|
RectParts PipPanel::attached() const {
 | 
						|
	return _attached;
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::setDragDisabled(bool disabled) {
 | 
						|
	_dragDisabled = disabled;
 | 
						|
	if (_dragState) {
 | 
						|
		_dragState = std::nullopt;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool PipPanel::dragging() const {
 | 
						|
	return _dragState.has_value();
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<> PipPanel::saveGeometryRequests() const {
 | 
						|
	return _saveGeometryRequests.events();
 | 
						|
}
 | 
						|
 | 
						|
QScreen *PipPanel::myScreen() const {
 | 
						|
	return windowHandle() ? windowHandle()->screen() : nullptr;
 | 
						|
}
 | 
						|
 | 
						|
PipPanel::Position PipPanel::countPosition() const {
 | 
						|
	const auto screen = myScreen();
 | 
						|
	if (!screen) {
 | 
						|
		return Position();
 | 
						|
	}
 | 
						|
	auto result = Position();
 | 
						|
	result.screen = screen->geometry();
 | 
						|
	result.geometry = geometry().marginsRemoved(_padding);
 | 
						|
	const auto available = screen->availableGeometry();
 | 
						|
	const auto skip = st::pipBorderSkip;
 | 
						|
	const auto left = result.geometry.x();
 | 
						|
	const auto right = left + result.geometry.width();
 | 
						|
	const auto top = result.geometry.y();
 | 
						|
	const auto bottom = top + result.geometry.height();
 | 
						|
	if (!_dragState || *_dragState != RectPart::Center) {
 | 
						|
		if (left == available.x()) {
 | 
						|
			result.attached |= RectPart::Left;
 | 
						|
		} else if (right == available.x() + available.width()) {
 | 
						|
			result.attached |= RectPart::Right;
 | 
						|
		} else if (left == available.x() + skip) {
 | 
						|
			result.snapped |= RectPart::Left;
 | 
						|
		} else if (right == available.x() + available.width() - skip) {
 | 
						|
			result.snapped |= RectPart::Right;
 | 
						|
		}
 | 
						|
		if (top == available.y()) {
 | 
						|
			result.attached |= RectPart::Top;
 | 
						|
		} else if (bottom == available.y() + available.height()) {
 | 
						|
			result.attached |= RectPart::Bottom;
 | 
						|
		} else if (top == available.y() + skip) {
 | 
						|
			result.snapped |= RectPart::Top;
 | 
						|
		} else if (bottom == available.y() + available.height() - skip) {
 | 
						|
			result.snapped |= RectPart::Bottom;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::setPositionDefault() {
 | 
						|
	const auto widgetScreen = [&](auto &&widget) -> QScreen* {
 | 
						|
		if (auto handle = widget ? widget->windowHandle() : nullptr) {
 | 
						|
			return handle->screen();
 | 
						|
		}
 | 
						|
		return nullptr;
 | 
						|
	};
 | 
						|
	const auto parentScreen = widgetScreen(_parent);
 | 
						|
	const auto myScreen = widgetScreen(this);
 | 
						|
	if (parentScreen && myScreen && myScreen != parentScreen) {
 | 
						|
		windowHandle()->setScreen(parentScreen);
 | 
						|
	}
 | 
						|
	const auto screen = parentScreen
 | 
						|
		? parentScreen
 | 
						|
		: QGuiApplication::primaryScreen();
 | 
						|
	auto position = Position();
 | 
						|
	position.snapped = RectPart::Top | RectPart::Left;
 | 
						|
	position.screen = screen->geometry();
 | 
						|
	position.geometry = QRect(0, 0, st::pipDefaultSize, st::pipDefaultSize);
 | 
						|
	setPositionOnScreen(position, screen->availableGeometry());
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::setPositionOnScreen(Position position, QRect available) {
 | 
						|
	const auto screen = available;
 | 
						|
	const auto requestedSize = position.geometry.size();
 | 
						|
	const auto max = std::max(requestedSize.width(), requestedSize.height());
 | 
						|
 | 
						|
	// Apply aspect ratio.
 | 
						|
	const auto scaled = (_ratio.width() > _ratio.height())
 | 
						|
		? QSize(max, max * _ratio.height() / _ratio.width())
 | 
						|
		: QSize(max * _ratio.width() / _ratio.height(), max);
 | 
						|
 | 
						|
	// At least one side should not be greater than half of screen size.
 | 
						|
	const auto byWidth = (scaled.width() * screen.height())
 | 
						|
		> (scaled.height() * screen.width());
 | 
						|
	const auto fit = QSize(screen.width() / 2, screen.height() / 2);
 | 
						|
	const auto normalized = (byWidth && scaled.width() > fit.width())
 | 
						|
		? QSize(fit.width(), fit.width() * scaled.height() / scaled.width())
 | 
						|
		: (!byWidth && scaled.height() > fit.height())
 | 
						|
		? QSize(
 | 
						|
			fit.height() * scaled.width() / scaled.height(),
 | 
						|
			fit.height())
 | 
						|
		: scaled;
 | 
						|
 | 
						|
	// Apply minimal size.
 | 
						|
	const auto min = st::pipMinimalSize;
 | 
						|
	const auto minimalSize = (_ratio.width() > _ratio.height())
 | 
						|
		? QSize(min * _ratio.width() / _ratio.height(), min)
 | 
						|
		: QSize(min, min * _ratio.height() / _ratio.width());
 | 
						|
	const auto size = QSize(
 | 
						|
		std::max(normalized.width(), minimalSize.width()),
 | 
						|
		std::max(normalized.height(), minimalSize.height()));
 | 
						|
 | 
						|
	// Apply left-right screen borders.
 | 
						|
	const auto skip = st::pipBorderSkip;
 | 
						|
	const auto inner = screen.marginsRemoved({ skip, skip, skip, skip });
 | 
						|
	auto geometry = QRect(position.geometry.topLeft(), size);
 | 
						|
	if ((position.attached & RectPart::Left)
 | 
						|
		|| (geometry.x() < screen.x())) {
 | 
						|
		geometry.moveLeft(screen.x());
 | 
						|
	} else if ((position.attached & RectPart::Right)
 | 
						|
		|| (geometry.x() + geometry.width() > screen.x() + screen.width())) {
 | 
						|
		geometry.moveLeft(screen.x() + screen.width() - geometry.width());
 | 
						|
	} else if (position.snapped & RectPart::Left) {
 | 
						|
		geometry.moveLeft(inner.x());
 | 
						|
	} else if (position.snapped & RectPart::Right) {
 | 
						|
		geometry.moveLeft(inner.x() + inner.width() - geometry.width());
 | 
						|
	}
 | 
						|
 | 
						|
	// Apply top-bottom screen borders.
 | 
						|
	if ((position.attached & RectPart::Top) || (geometry.y() < screen.y())) {
 | 
						|
		geometry.moveTop(screen.y());
 | 
						|
	} else if ((position.attached & RectPart::Bottom)
 | 
						|
		|| (geometry.y() + geometry.height()
 | 
						|
			> screen.y() + screen.height())) {
 | 
						|
		geometry.moveTop(
 | 
						|
			screen.y() + screen.height() - geometry.height());
 | 
						|
	} else if (position.snapped & RectPart::Top) {
 | 
						|
		geometry.moveTop(inner.y());
 | 
						|
	} else if (position.snapped & RectPart::Bottom) {
 | 
						|
		geometry.moveTop(inner.y() + inner.height() - geometry.height());
 | 
						|
	}
 | 
						|
 | 
						|
	setGeometry(geometry.marginsAdded(_padding));
 | 
						|
	updateDecorations();
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::paintEvent(QPaintEvent *e) {
 | 
						|
	QPainter p(this);
 | 
						|
 | 
						|
	if (_useTransparency) {
 | 
						|
		Ui::Platform::StartTranslucentPaint(p, e->region().rects());
 | 
						|
	}
 | 
						|
 | 
						|
	auto request = FrameRequest();
 | 
						|
	const auto inner = this->inner();
 | 
						|
	request.resize = request.outer = inner.size() * style::DevicePixelRatio();
 | 
						|
	request.corners = RectPart(0)
 | 
						|
		| ((_attached & (RectPart::Left | RectPart::Top))
 | 
						|
			? RectPart(0)
 | 
						|
			: RectPart::TopLeft)
 | 
						|
		| ((_attached & (RectPart::Top | RectPart::Right))
 | 
						|
			? RectPart(0)
 | 
						|
			: RectPart::TopRight)
 | 
						|
		| ((_attached & (RectPart::Right | RectPart::Bottom))
 | 
						|
			? RectPart(0)
 | 
						|
			: RectPart::BottomRight)
 | 
						|
		| ((_attached & (RectPart::Bottom | RectPart::Left))
 | 
						|
			? RectPart(0)
 | 
						|
			: RectPart::BottomLeft);
 | 
						|
	request.radius = ImageRoundRadius::Large;
 | 
						|
	if (_useTransparency) {
 | 
						|
		const auto sides = RectPart::AllSides & ~_attached;
 | 
						|
		Ui::Shadow::paint(p, inner, width(), st::callShadow);
 | 
						|
	}
 | 
						|
	_paint(p, request);
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::mousePressEvent(QMouseEvent *e) {
 | 
						|
	if (e->button() != Qt::LeftButton) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_pressState = _overState;
 | 
						|
	_pressPoint = e->globalPos();
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::mouseReleaseEvent(QMouseEvent *e) {
 | 
						|
	if (e->button() != Qt::LeftButton || !base::take(_pressState)) {
 | 
						|
		return;
 | 
						|
	} else if (!base::take(_dragState)) {
 | 
						|
		//playbackPauseResume();
 | 
						|
	} else {
 | 
						|
		finishDrag(e->globalPos());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::updateOverState(QPoint point) {
 | 
						|
	const auto size = st::pipResizeArea;
 | 
						|
	const auto ignore = _attached | _snapped;
 | 
						|
	const auto count = [&](RectPart side, int padding) {
 | 
						|
		return (ignore & side) ? 0 : padding ? padding : size;
 | 
						|
	};
 | 
						|
	const auto left = count(RectPart::Left, _padding.left());
 | 
						|
	const auto top = count(RectPart::Top, _padding.top());
 | 
						|
	const auto right = count(RectPart::Right, _padding.right());
 | 
						|
	const auto bottom = count(RectPart::Bottom, _padding.bottom());
 | 
						|
	const auto overState = [&] {
 | 
						|
		if (point.x() < left) {
 | 
						|
			if (point.y() < top) {
 | 
						|
				return RectPart::TopLeft;
 | 
						|
			} else if (point.y() >= height() - bottom) {
 | 
						|
				return RectPart::BottomLeft;
 | 
						|
			} else {
 | 
						|
				return RectPart::Left;
 | 
						|
			}
 | 
						|
		} else if (point.x() >= width() - right) {
 | 
						|
			if (point.y() < top) {
 | 
						|
				return RectPart::TopRight;
 | 
						|
			} else if (point.y() >= height() - bottom) {
 | 
						|
				return RectPart::BottomRight;
 | 
						|
			} else {
 | 
						|
				return RectPart::Right;
 | 
						|
			}
 | 
						|
		} else if (point.y() < top) {
 | 
						|
			return RectPart::Top;
 | 
						|
		} else if (point.y() >= height() - bottom) {
 | 
						|
			return RectPart::Bottom;
 | 
						|
		} else {
 | 
						|
			return RectPart::Center;
 | 
						|
		}
 | 
						|
	}();
 | 
						|
	if (_overState != overState) {
 | 
						|
		_overState = overState;
 | 
						|
		setCursor([&] {
 | 
						|
			switch (_overState) {
 | 
						|
			case RectPart::Center:
 | 
						|
				return style::cur_pointer;
 | 
						|
			case RectPart::TopLeft:
 | 
						|
			case RectPart::BottomRight:
 | 
						|
				return style::cur_sizefdiag;
 | 
						|
			case RectPart::TopRight:
 | 
						|
			case RectPart::BottomLeft:
 | 
						|
				return style::cur_sizebdiag;
 | 
						|
			case RectPart::Left:
 | 
						|
			case RectPart::Right:
 | 
						|
				return style::cur_sizehor;
 | 
						|
			case RectPart::Top:
 | 
						|
			case RectPart::Bottom:
 | 
						|
				return style::cur_sizever;
 | 
						|
			}
 | 
						|
			Unexpected("State in PipPanel::updateOverState.");
 | 
						|
		}());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::mouseMoveEvent(QMouseEvent *e) {
 | 
						|
	if (!_pressState) {
 | 
						|
		updateOverState(e->pos());
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto point = e->globalPos();
 | 
						|
	const auto distance = QApplication::startDragDistance();
 | 
						|
	if (!_dragState
 | 
						|
		&& (point - _pressPoint).manhattanLength() > distance
 | 
						|
		&& !_dragDisabled) {
 | 
						|
		_dragState = _pressState;
 | 
						|
		updateDecorations();
 | 
						|
		_dragStartGeometry = geometry().marginsRemoved(_padding);
 | 
						|
	}
 | 
						|
	if (_dragState) {
 | 
						|
		processDrag(point);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::processDrag(QPoint point) {
 | 
						|
	Expects(_dragState.has_value());
 | 
						|
 | 
						|
	const auto dragPart = *_dragState;
 | 
						|
	const auto screen = (dragPart == RectPart::Center)
 | 
						|
		? ScreenFromPosition(point)
 | 
						|
		: myScreen()
 | 
						|
		? myScreen()->availableGeometry()
 | 
						|
		: QRect();
 | 
						|
	if (screen.isEmpty()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto minimalSize = _ratio.scaled(
 | 
						|
		st::pipMinimalSize,
 | 
						|
		st::pipMinimalSize,
 | 
						|
		Qt::KeepAspectRatioByExpanding);
 | 
						|
	const auto maximalSize = _ratio.scaled(
 | 
						|
		screen.width() / 2,
 | 
						|
		screen.height() / 2,
 | 
						|
		Qt::KeepAspectRatio);
 | 
						|
	const auto geometry = Transformed(
 | 
						|
		_dragStartGeometry,
 | 
						|
		minimalSize,
 | 
						|
		maximalSize,
 | 
						|
		point - _pressPoint,
 | 
						|
		dragPart);
 | 
						|
	const auto valid = Constrained(
 | 
						|
		geometry,
 | 
						|
		minimalSize,
 | 
						|
		maximalSize,
 | 
						|
		_ratio,
 | 
						|
		dragPart,
 | 
						|
		_attached);
 | 
						|
	const auto clamped = (dragPart == RectPart::Center)
 | 
						|
		? ClampToEdges(screen, valid)
 | 
						|
		: valid.topLeft();
 | 
						|
	if (clamped != valid.topLeft()) {
 | 
						|
		moveAnimated(clamped);
 | 
						|
	} else {
 | 
						|
		_positionAnimation.stop();
 | 
						|
		setGeometry(valid.marginsAdded(_padding));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::finishDrag(QPoint point) {
 | 
						|
	const auto screen = ScreenFromPosition(point);
 | 
						|
	const auto inner = geometry().marginsRemoved(_padding);
 | 
						|
	const auto position = pos();
 | 
						|
	const auto clamped = [&] {
 | 
						|
		auto result = position;
 | 
						|
		if (result.x() > screen.x() + screen.width() - inner.width()) {
 | 
						|
			result.setX(screen.x() + screen.width() - inner.width());
 | 
						|
		}
 | 
						|
		if (result.x() < screen.x()) {
 | 
						|
			result.setX(screen.x());
 | 
						|
		}
 | 
						|
		if (result.y() > screen.y() + screen.height() - inner.height()) {
 | 
						|
			result.setY(screen.y() + screen.height() - inner.height());
 | 
						|
		}
 | 
						|
		if (result.y() < screen.y()) {
 | 
						|
			result.setY(screen.y());
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	}();
 | 
						|
	if (position != clamped) {
 | 
						|
		moveAnimated(clamped);
 | 
						|
	} else {
 | 
						|
		_positionAnimation.stop();
 | 
						|
		updateDecorations();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::updatePositionAnimated() {
 | 
						|
	const auto progress = _positionAnimation.value(1.);
 | 
						|
	if (!_positionAnimation.animating()) {
 | 
						|
		move(_positionAnimationTo - QPoint(_padding.left(), _padding.top()));
 | 
						|
		if (!_dragState) {
 | 
						|
			updateDecorations();
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto from = QPointF(_positionAnimationFrom);
 | 
						|
	const auto to = QPointF(_positionAnimationTo);
 | 
						|
	move((from + (to - from) * progress).toPoint()
 | 
						|
		- QPoint(_padding.left(), _padding.top()));
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::moveAnimated(QPoint to) {
 | 
						|
	if (_positionAnimation.animating() && _positionAnimationTo == to) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_positionAnimationTo = to;
 | 
						|
	_positionAnimationFrom = pos() + QPoint(_padding.left(), _padding.top());
 | 
						|
	_positionAnimation.stop();
 | 
						|
	_positionAnimation.start(
 | 
						|
		[=] { updatePositionAnimated(); },
 | 
						|
		0.,
 | 
						|
		1.,
 | 
						|
		st::slideWrapDuration,
 | 
						|
		anim::easeOutCirc);
 | 
						|
}
 | 
						|
 | 
						|
void PipPanel::updateDecorations() {
 | 
						|
	const auto guard = gsl::finally([&] {
 | 
						|
		if (!_dragState) {
 | 
						|
			_saveGeometryRequests.fire({});
 | 
						|
		}
 | 
						|
	});
 | 
						|
	const auto position = countPosition();
 | 
						|
	const auto center = position.geometry.center();
 | 
						|
	const auto use = Ui::Platform::TranslucentWindowsSupported(center);
 | 
						|
	const auto full = use ? st::callShadow.extend : style::margins();
 | 
						|
	const auto padding = style::margins(
 | 
						|
		(position.attached & RectPart::Left) ? 0 : full.left(),
 | 
						|
		(position.attached & RectPart::Top) ? 0 : full.top(),
 | 
						|
		(position.attached & RectPart::Right) ? 0 : full.right(),
 | 
						|
		(position.attached & RectPart::Bottom) ? 0 : full.bottom());
 | 
						|
	_snapped = position.snapped;
 | 
						|
	if (_padding == padding && _attached == position.attached) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto newGeometry = position.geometry.marginsAdded(padding);
 | 
						|
	_attached = position.attached;
 | 
						|
	_padding = padding;
 | 
						|
	_useTransparency = use;
 | 
						|
	setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
 | 
						|
	setGeometry(newGeometry);
 | 
						|
	update();
 | 
						|
}
 | 
						|
 | 
						|
Pip::Pip(
 | 
						|
	not_null<Delegate*> delegate,
 | 
						|
	not_null<DocumentData*> data,
 | 
						|
	FullMsgId contextId,
 | 
						|
	std::shared_ptr<Streaming::Document> shared,
 | 
						|
	FnMut<void()> closeAndContinue,
 | 
						|
	FnMut<void()> destroy)
 | 
						|
: _delegate(delegate)
 | 
						|
, _data(data)
 | 
						|
, _contextId(contextId)
 | 
						|
, _instance(std::move(shared), [=] { waitingAnimationCallback(); })
 | 
						|
, _panel(
 | 
						|
	_delegate->pipParentWidget(),
 | 
						|
	[=](QPainter &p, const FrameRequest &request) { paint(p, request); })
 | 
						|
, _playbackProgress(std::make_unique<PlaybackProgress>())
 | 
						|
, _rotation(data->owner().mediaRotation().get(data))
 | 
						|
, _roundRect(ImageRoundRadius::Large, st::radialBg)
 | 
						|
, _closeAndContinue(std::move(closeAndContinue))
 | 
						|
, _destroy(std::move(destroy)) {
 | 
						|
	setupPanel();
 | 
						|
	setupButtons();
 | 
						|
	setupStreaming();
 | 
						|
}
 | 
						|
 | 
						|
Pip::~Pip() = default;
 | 
						|
 | 
						|
void Pip::setupPanel() {
 | 
						|
	const auto size = [&] {
 | 
						|
		if (!_instance.info().video.size.isEmpty()) {
 | 
						|
			return _instance.info().video.size;
 | 
						|
		}
 | 
						|
		const auto media = _data->activeMediaView();
 | 
						|
		const auto good = media ? media->goodThumbnail() : nullptr;
 | 
						|
		const auto useGood = (good && good->loaded());
 | 
						|
		const auto original = useGood ? good->size() : _data->dimensions;
 | 
						|
		return original.isEmpty() ? QSize(1, 1) : original;
 | 
						|
	}();
 | 
						|
	_panel.setAspectRatio(FlipSizeByRotation(size, _rotation));
 | 
						|
	_panel.setPosition(Deserialize(_delegate->pipLoadGeometry()));
 | 
						|
	_panel.show();
 | 
						|
 | 
						|
	_panel.saveGeometryRequests(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		saveGeometry();
 | 
						|
	}, _panel.lifetime());
 | 
						|
 | 
						|
	_panel.events(
 | 
						|
	) | rpl::start_with_next([=](not_null<QEvent*> e) {
 | 
						|
		const auto mousePosition = [&] {
 | 
						|
			return static_cast<QMouseEvent*>(e.get())->pos();
 | 
						|
		};
 | 
						|
		const auto mouseButton = [&] {
 | 
						|
			return static_cast<QMouseEvent*>(e.get())->button();
 | 
						|
		};
 | 
						|
		switch (e->type()) {
 | 
						|
		case QEvent::Close: handleClose(); break;
 | 
						|
		case QEvent::Leave: handleLeave(); break;
 | 
						|
		case QEvent::MouseMove:
 | 
						|
			handleMouseMove(mousePosition());
 | 
						|
			break;
 | 
						|
		case QEvent::MouseButtonPress:
 | 
						|
			handleMousePress(mousePosition(), mouseButton());
 | 
						|
			break;
 | 
						|
		case QEvent::MouseButtonRelease:
 | 
						|
			handleMouseRelease(mousePosition(), mouseButton());
 | 
						|
			break;
 | 
						|
		case QEvent::MouseButtonDblClick:
 | 
						|
			handleDoubleClick(mouseButton());
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}, _panel.lifetime());
 | 
						|
}
 | 
						|
 | 
						|
void Pip::handleClose() {
 | 
						|
	crl::on_main(&_panel, [=] {
 | 
						|
		_destroy();
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void Pip::handleLeave() {
 | 
						|
	setOverState(OverState::None);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::handleMouseMove(QPoint position) {
 | 
						|
	setOverState(computeState(position));
 | 
						|
	seekUpdate(position);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::setOverState(OverState state) {
 | 
						|
	if (_over == state) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto was = _over;
 | 
						|
	_over = state;
 | 
						|
	const auto nowShown = (_over != OverState::None);
 | 
						|
	if ((was != OverState::None) != nowShown) {
 | 
						|
		_controlsShown.start(
 | 
						|
			[=] { _panel.update(); },
 | 
						|
			nowShown ? 0. : 1.,
 | 
						|
			nowShown ? 1. : 0.,
 | 
						|
			st::fadeWrapDuration,
 | 
						|
			anim::linear);
 | 
						|
	}
 | 
						|
	if (!_pressed) {
 | 
						|
		updateActiveState(was);
 | 
						|
	}
 | 
						|
	_panel.update();
 | 
						|
}
 | 
						|
 | 
						|
void Pip::setPressedState(std::optional<OverState> state) {
 | 
						|
	if (_pressed == state) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto was = activeState();
 | 
						|
	_pressed = state;
 | 
						|
	updateActiveState(was);
 | 
						|
}
 | 
						|
 | 
						|
Pip::OverState Pip::activeState() const {
 | 
						|
	return _pressed.value_or(_over);
 | 
						|
}
 | 
						|
 | 
						|
float64 Pip::activeValue(const Button &button) const {
 | 
						|
	return button.active.value((activeState() == button.state) ? 1. : 0.);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::updateActiveState(OverState was) {
 | 
						|
	const auto check = [&](Button &button) {
 | 
						|
		const auto now = (activeState() == button.state);
 | 
						|
		if ((was == button.state) != now) {
 | 
						|
			button.active.start(
 | 
						|
				[=, &button] { _panel.update(button.icon); },
 | 
						|
				now ? 0. : 1.,
 | 
						|
				now ? 1. : 0.,
 | 
						|
				st::fadeWrapDuration,
 | 
						|
				anim::linear);
 | 
						|
		}
 | 
						|
	};
 | 
						|
	check(_close);
 | 
						|
	check(_enlarge);
 | 
						|
	check(_play);
 | 
						|
	check(_playback);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::handleMousePress(QPoint position, Qt::MouseButton button) {
 | 
						|
	if (button != Qt::LeftButton) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_pressed = _over;
 | 
						|
	if (_over == OverState::Playback) {
 | 
						|
		_panel.setDragDisabled(true);
 | 
						|
	}
 | 
						|
	seekUpdate(position);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::handleMouseRelease(QPoint position, Qt::MouseButton button) {
 | 
						|
	if (button != Qt::LeftButton) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	seekUpdate(position);
 | 
						|
	const auto pressed = base::take(_pressed);
 | 
						|
	if (pressed && *pressed == OverState::Playback) {
 | 
						|
		_panel.setDragDisabled(false);
 | 
						|
		seekFinish(_playbackProgress->value());
 | 
						|
		return;
 | 
						|
	} else if (_panel.dragging() || !pressed || *pressed != _over) {
 | 
						|
		_lastHandledPress = std::nullopt;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	_lastHandledPress = _over;
 | 
						|
	switch (_over) {
 | 
						|
	case OverState::Close: _panel.close(); break;
 | 
						|
	case OverState::Enlarge: _closeAndContinue(); break;
 | 
						|
	case OverState::Other: playbackPauseResume(); break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Pip::handleDoubleClick(Qt::MouseButton button) {
 | 
						|
	if (_over != OverState::Other
 | 
						|
		|| !_lastHandledPress
 | 
						|
		|| *_lastHandledPress != _over) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	playbackPauseResume(); // Un-click the first click.
 | 
						|
	_closeAndContinue();
 | 
						|
}
 | 
						|
 | 
						|
void Pip::seekUpdate(QPoint position) {
 | 
						|
	if (!_pressed || *_pressed != OverState::Playback) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto unbound = (position.x() - _playback.icon.x())
 | 
						|
		/ float64(_playback.icon.width());
 | 
						|
	const auto progress = std::clamp(unbound, 0., 1.);
 | 
						|
	seekProgress(progress);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::seekProgress(float64 value) {
 | 
						|
	if (!_lastDurationMs) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	_playbackProgress->setValue(value, false);
 | 
						|
 | 
						|
	const auto positionMs = std::clamp(
 | 
						|
		static_cast<crl::time>(value * _lastDurationMs),
 | 
						|
		crl::time(0),
 | 
						|
		_lastDurationMs);
 | 
						|
	if (_seekPositionMs != positionMs) {
 | 
						|
		_seekPositionMs = positionMs;
 | 
						|
		if (!_instance.player().paused()
 | 
						|
			&& !_instance.player().finished()) {
 | 
						|
			_pausedBySeek = true;
 | 
						|
			playbackPauseResume();
 | 
						|
		}
 | 
						|
		updatePlaybackTexts(_seekPositionMs, _lastDurationMs, kMsInSecond);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Pip::seekFinish(float64 value) {
 | 
						|
	if (!_lastDurationMs) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	const auto positionMs = std::clamp(
 | 
						|
		static_cast<crl::time>(value * _lastDurationMs),
 | 
						|
		crl::time(0),
 | 
						|
		_lastDurationMs);
 | 
						|
	_seekPositionMs = -1;
 | 
						|
	_startPaused = !_pausedBySeek && !_instance.player().finished();
 | 
						|
	restartAtSeekPosition(positionMs);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::setupButtons() {
 | 
						|
	_close.state = OverState::Close;
 | 
						|
	_enlarge.state = OverState::Enlarge;
 | 
						|
	_playback.state = OverState::Playback;
 | 
						|
	_play.state = OverState::Other;
 | 
						|
	_panel.sizeValue(
 | 
						|
	) | rpl::map([=] {
 | 
						|
		return _panel.inner();
 | 
						|
	}) | rpl::start_with_next([=](QRect rect) {
 | 
						|
		const auto skip = st::pipControlSkip;
 | 
						|
		_close.area = QRect(
 | 
						|
			rect.x(),
 | 
						|
			rect.y(),
 | 
						|
			st::pipCloseIcon.width() + 2 * skip,
 | 
						|
			st::pipCloseIcon.height() + 2 * skip);
 | 
						|
		_enlarge.area = QRect(
 | 
						|
			_close.area.x() + _close.area.width(),
 | 
						|
			rect.y(),
 | 
						|
			st::pipEnlargeIcon.width() + 2 * skip,
 | 
						|
			st::pipEnlargeIcon.height() + 2 * skip);
 | 
						|
		if (!IsWindowControlsOnLeft()) {
 | 
						|
			_close.area.moveLeft(rect.x()
 | 
						|
				+ rect.width()
 | 
						|
				- (_close.area.x() - rect.x())
 | 
						|
				- _close.area.width());
 | 
						|
			_enlarge.area.moveLeft(rect.x()
 | 
						|
				+ rect.width()
 | 
						|
				- (_enlarge.area.x() - rect.x())
 | 
						|
				- _enlarge.area.width());
 | 
						|
		}
 | 
						|
		_close.icon = _close.area.marginsRemoved({ skip, skip, skip, skip });
 | 
						|
		_enlarge.icon = _enlarge.area.marginsRemoved(
 | 
						|
			{ skip, skip, skip, skip });
 | 
						|
		_play.icon = QRect(
 | 
						|
			rect.x() + (rect.width() - st::pipPlayIcon.width()) / 2,
 | 
						|
			rect.y() + (rect.height() - st::pipPlayIcon.height()) / 2,
 | 
						|
			st::pipPlayIcon.width(),
 | 
						|
			st::pipPlayIcon.height());
 | 
						|
		const auto playbackSkip = st::pipPlaybackSkip;
 | 
						|
		const auto playbackHeight = 2 * playbackSkip + st::pipPlaybackWide;
 | 
						|
		_playback.area = QRect(
 | 
						|
			rect.x(),
 | 
						|
			rect.y() + rect.height() - playbackHeight,
 | 
						|
			rect.width(),
 | 
						|
			playbackHeight);
 | 
						|
		_playback.icon = _playback.area.marginsRemoved(
 | 
						|
			{ playbackSkip, playbackSkip, playbackSkip, playbackSkip });
 | 
						|
	}, _panel.lifetime());
 | 
						|
 | 
						|
	_playbackProgress->setValueChangedCallback([=](
 | 
						|
			float64 value,
 | 
						|
			float64 receivedTill) {
 | 
						|
		_panel.update(_playback.area);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void Pip::saveGeometry() {
 | 
						|
	_delegate->pipSaveGeometry(Serialize(_panel.countPosition()));
 | 
						|
}
 | 
						|
 | 
						|
void Pip::updatePlayPauseResumeState(const Player::TrackState &state) {
 | 
						|
	auto showPause = Player::ShowPauseIcon(state.state);
 | 
						|
	if (showPause != _showPause) {
 | 
						|
		_showPause = showPause;
 | 
						|
		_panel.update();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Pip::setupStreaming() {
 | 
						|
	_instance.setPriority(kPipLoaderPriority);
 | 
						|
	_instance.lockPlayer();
 | 
						|
 | 
						|
	_instance.player().updates(
 | 
						|
	) | rpl::start_with_next_error([=](Streaming::Update &&update) {
 | 
						|
		handleStreamingUpdate(std::move(update));
 | 
						|
	}, [=](Streaming::Error &&error) {
 | 
						|
		handleStreamingError(std::move(error));
 | 
						|
	}, _instance.lifetime());
 | 
						|
	updatePlaybackState();
 | 
						|
}
 | 
						|
 | 
						|
void Pip::paint(QPainter &p, FrameRequest request) {
 | 
						|
	const auto image = videoFrameForDirectPaint(
 | 
						|
		UnrotateRequest(request, _rotation));
 | 
						|
	const auto inner = _panel.inner();
 | 
						|
	const auto rect = QRect{
 | 
						|
		inner.topLeft(),
 | 
						|
		request.outer / style::DevicePixelRatio()
 | 
						|
	};
 | 
						|
	if (UsePainterRotation(_rotation)) {
 | 
						|
		if (_rotation) {
 | 
						|
			p.save();
 | 
						|
			p.rotate(_rotation);
 | 
						|
		}
 | 
						|
		p.drawImage(RotatedRect(rect, _rotation), image);
 | 
						|
		if (_rotation) {
 | 
						|
			p.restore();
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		p.drawImage(rect, RotateFrameImage(image, _rotation));
 | 
						|
	}
 | 
						|
	if (_instance.player().ready()) {
 | 
						|
		_instance.markFrameShown();
 | 
						|
	}
 | 
						|
	paintRadialLoading(p);
 | 
						|
	paintControls(p);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::paintControls(QPainter &p) const {
 | 
						|
	const auto shown = _controlsShown.value(
 | 
						|
		(_over != OverState::None) ? 1. : 0.);
 | 
						|
	if (!shown) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	p.setOpacity(shown);
 | 
						|
	paintFade(p);
 | 
						|
	paintButtons(p);
 | 
						|
	paintPlayback(p);
 | 
						|
	paintPlaybackTexts(p);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::paintFade(QPainter &p) const {
 | 
						|
	using Part = RectPart;
 | 
						|
	const auto sides = _panel.attached();
 | 
						|
	const auto rounded = RectPart(0)
 | 
						|
		| ((sides & (Part::Top | Part::Left)) ? Part(0) : Part::TopLeft)
 | 
						|
		| ((sides & (Part::Top | Part::Right)) ? Part(0) : Part::TopRight)
 | 
						|
		| ((sides & (Part::Bottom | Part::Right))
 | 
						|
			? Part(0)
 | 
						|
			: Part::BottomRight)
 | 
						|
		| ((sides & (Part::Bottom | Part::Left))
 | 
						|
			? Part(0)
 | 
						|
			: Part::BottomLeft);
 | 
						|
	_roundRect.paintSomeRounded(
 | 
						|
		p,
 | 
						|
		_panel.inner(),
 | 
						|
		rounded | Part::NoTopBottom | Part::Top | Part::Bottom);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::paintButtons(QPainter &p) const {
 | 
						|
	const auto opacity = p.opacity();
 | 
						|
	const auto outer = _panel.width();
 | 
						|
	const auto drawOne = [&](
 | 
						|
			const Button &button,
 | 
						|
			const style::icon &icon,
 | 
						|
			const style::icon &iconOver) {
 | 
						|
		const auto over = activeValue(button);
 | 
						|
		if (over < 1.) {
 | 
						|
			icon.paint(p, button.icon.x(), button.icon.y(), outer);
 | 
						|
		}
 | 
						|
		if (over > 0.) {
 | 
						|
			p.setOpacity(over * opacity);
 | 
						|
			iconOver.paint(p, button.icon.x(), button.icon.y(), outer);
 | 
						|
			p.setOpacity(opacity);
 | 
						|
		}
 | 
						|
	};
 | 
						|
	drawOne(
 | 
						|
		_play,
 | 
						|
		_showPause ? st::pipPauseIcon : st::pipPlayIcon,
 | 
						|
		_showPause ? st::pipPauseIconOver : st::pipPlayIconOver);
 | 
						|
	drawOne(_close, st::pipCloseIcon, st::pipCloseIconOver);
 | 
						|
	drawOne(_enlarge, st::pipEnlargeIcon, st::pipEnlargeIconOver);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::paintPlayback(QPainter &p) const {
 | 
						|
	const auto radius = _playback.icon.height() / 2;
 | 
						|
	const auto shown = activeValue(_playback);
 | 
						|
	const auto progress = _playbackProgress->value();
 | 
						|
	const auto width = _playback.icon.width();
 | 
						|
	const auto height = anim::interpolate(
 | 
						|
		st::pipPlaybackWidth,
 | 
						|
		_playback.icon.height(),
 | 
						|
		activeValue(_playback));
 | 
						|
	const auto left = _playback.icon.x();
 | 
						|
	const auto top = _playback.icon.y() + _playback.icon.height() - height;
 | 
						|
	const auto done = int(std::round(width * progress));
 | 
						|
	PainterHighQualityEnabler hq(p);
 | 
						|
	p.setPen(Qt::NoPen);
 | 
						|
	if (done > 0) {
 | 
						|
		p.setBrush(st::mediaviewPipPlaybackActive);
 | 
						|
		p.setClipRect(left, top, done, height);
 | 
						|
		p.drawRoundedRect(
 | 
						|
			left,
 | 
						|
			top,
 | 
						|
			std::min(done + radius, width),
 | 
						|
			height,
 | 
						|
			radius,
 | 
						|
			radius);
 | 
						|
	}
 | 
						|
	if (done < width) {
 | 
						|
		const auto from = std::max(left + done - radius, left);
 | 
						|
		p.setBrush(st::mediaviewPipPlaybackInactive);
 | 
						|
		p.setClipRect(left + done, top, width - done, height);
 | 
						|
		p.drawRoundedRect(
 | 
						|
			from,
 | 
						|
			top,
 | 
						|
			left + width - from,
 | 
						|
			height,
 | 
						|
			radius,
 | 
						|
			radius);
 | 
						|
	}
 | 
						|
	p.setClipping(false);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::paintPlaybackTexts(QPainter &p) const {
 | 
						|
	const auto left = _playback.area.x() + st::pipPlaybackTextSkip;
 | 
						|
	const auto right = _playback.area.x()
 | 
						|
		+ _playback.area.width()
 | 
						|
		- st::pipPlaybackTextSkip;
 | 
						|
	const auto top = _playback.icon.y()
 | 
						|
		- st::pipPlaybackFont->height
 | 
						|
		+ st::pipPlaybackFont->ascent;
 | 
						|
 | 
						|
	p.setFont(st::pipPlaybackFont);
 | 
						|
	p.setPen(st::mediaviewPipControlsFgOver);
 | 
						|
	p.drawText(left, top, _timeAlready);
 | 
						|
	p.drawText(right - _timeLeftWidth, top, _timeLeft);
 | 
						|
}
 | 
						|
 | 
						|
void Pip::handleStreamingUpdate(Streaming::Update &&update) {
 | 
						|
	using namespace Streaming;
 | 
						|
 | 
						|
	update.data.match([&](Information &update) {
 | 
						|
		_panel.setAspectRatio(
 | 
						|
			FlipSizeByRotation(update.video.size, _rotation));
 | 
						|
	}, [&](const PreloadedVideo &update) {
 | 
						|
		updatePlaybackState();
 | 
						|
	}, [&](const UpdateVideo &update) {
 | 
						|
		_panel.update();
 | 
						|
		Core::App().updateNonIdle();
 | 
						|
		updatePlaybackState();
 | 
						|
	}, [&](const PreloadedAudio &update) {
 | 
						|
		updatePlaybackState();
 | 
						|
	}, [&](const UpdateAudio &update) {
 | 
						|
		updatePlaybackState();
 | 
						|
	}, [&](WaitingForData) {
 | 
						|
	}, [&](MutedByOther) {
 | 
						|
	}, [&](Finished) {
 | 
						|
		updatePlaybackState();
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void Pip::updatePlaybackState() {
 | 
						|
	const auto state = _instance.player().prepareLegacyState();
 | 
						|
	updatePlayPauseResumeState(state);
 | 
						|
	if (state.position == kTimeUnknown
 | 
						|
		|| state.length == kTimeUnknown
 | 
						|
		|| _pausedBySeek) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_playbackProgress->updateState(state);
 | 
						|
 | 
						|
	qint64 position = 0, length = state.length;
 | 
						|
	if (Player::IsStoppedAtEnd(state.state)) {
 | 
						|
		position = state.length;
 | 
						|
	} else if (!Player::IsStoppedOrStopping(state.state)) {
 | 
						|
		position = state.position;
 | 
						|
	} else {
 | 
						|
		position = 0;
 | 
						|
	}
 | 
						|
	const auto playFrequency = state.frequency;
 | 
						|
	_lastDurationMs = (state.length * crl::time(1000)) / playFrequency;
 | 
						|
 | 
						|
	if (_seekPositionMs < 0) {
 | 
						|
		updatePlaybackTexts(position, state.length, playFrequency);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Pip::updatePlaybackTexts(
 | 
						|
		int64 position,
 | 
						|
		int64 length,
 | 
						|
		int64 frequency) {
 | 
						|
	const auto playAlready = position / frequency;
 | 
						|
	const auto playLeft = (length / frequency) - playAlready;
 | 
						|
	const auto already = formatDurationText(playAlready);
 | 
						|
	const auto minus = QChar(8722);
 | 
						|
	const auto left = minus + formatDurationText(playLeft);
 | 
						|
	if (_timeAlready == already && _timeLeft == left) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_timeAlready = already;
 | 
						|
	_timeLeft = left;
 | 
						|
	_timeLeftWidth = st::pipPlaybackFont->width(_timeLeft);
 | 
						|
	_panel.update(QRect(
 | 
						|
		_playback.area.x(),
 | 
						|
		_playback.icon.y() - st::pipPlaybackFont->height,
 | 
						|
		_playback.area.width(),
 | 
						|
		st::pipPlaybackFont->height));
 | 
						|
}
 | 
						|
 | 
						|
void Pip::handleStreamingError(Streaming::Error &&error) {
 | 
						|
	_panel.close();
 | 
						|
}
 | 
						|
 | 
						|
void Pip::playbackPauseResume() {
 | 
						|
	if (_instance.player().failed()) {
 | 
						|
		_panel.close();
 | 
						|
	} else if (_instance.player().finished()
 | 
						|
		|| !_instance.player().active()) {
 | 
						|
		_startPaused = false;
 | 
						|
		restartAtSeekPosition(0);
 | 
						|
	} else if (_instance.player().paused()) {
 | 
						|
		_instance.resume();
 | 
						|
		updatePlaybackState();
 | 
						|
	} else {
 | 
						|
		_instance.pause();
 | 
						|
		updatePlaybackState();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Pip::restartAtSeekPosition(crl::time position) {
 | 
						|
	if (!_instance.info().video.cover.isNull()) {
 | 
						|
		_instance.saveFrameToCover();
 | 
						|
	}
 | 
						|
	auto options = Streaming::PlaybackOptions();
 | 
						|
	options.position = position;
 | 
						|
	options.audioId = _instance.player().prepareLegacyState().id;
 | 
						|
	options.speed = _delegate->pipPlaybackSpeed();
 | 
						|
	_instance.play(options);
 | 
						|
	if (_startPaused) {
 | 
						|
		_instance.pause();
 | 
						|
	}
 | 
						|
	_pausedBySeek = false;
 | 
						|
	updatePlaybackState();
 | 
						|
}
 | 
						|
 | 
						|
QImage Pip::videoFrame(const FrameRequest &request) const {
 | 
						|
	if (_instance.player().ready()) {
 | 
						|
		_preparedCoverStorage = QImage();
 | 
						|
		return _instance.frame(request);
 | 
						|
	}
 | 
						|
	const auto &cover = _instance.info().video.cover;
 | 
						|
 | 
						|
	const auto media = _data->activeMediaView();
 | 
						|
	const auto use = media
 | 
						|
		? media
 | 
						|
		: _data->inlineThumbnailBytes().isEmpty()
 | 
						|
		? nullptr
 | 
						|
		: _data->createMediaView();
 | 
						|
	const auto good = use ? use->goodThumbnail() : nullptr;
 | 
						|
	const auto useGood = (good && good->loaded());
 | 
						|
	const auto thumb = use ? use->thumbnail() : nullptr;
 | 
						|
	const auto useThumb = (thumb && thumb->loaded());
 | 
						|
	const auto blurred = use ? use->thumbnailInline() : nullptr;
 | 
						|
 | 
						|
	const auto state = !cover.isNull()
 | 
						|
		? ThumbState::Cover
 | 
						|
		: useGood
 | 
						|
		? ThumbState::Good
 | 
						|
		: useThumb
 | 
						|
		? ThumbState::Thumb
 | 
						|
		: blurred
 | 
						|
		? ThumbState::Inline
 | 
						|
		: ThumbState::Empty;
 | 
						|
	if (_preparedCoverStorage.isNull()
 | 
						|
		|| _preparedCoverRequest != request
 | 
						|
		|| _preparedCoverState < state) {
 | 
						|
		_preparedCoverRequest = request;
 | 
						|
		_preparedCoverState = state;
 | 
						|
		if (state == ThumbState::Cover) {
 | 
						|
			_preparedCoverStorage = Streaming::PrepareByRequest(
 | 
						|
				_instance.info().video.cover,
 | 
						|
				false,
 | 
						|
				_instance.info().video.rotation,
 | 
						|
				request,
 | 
						|
				std::move(_preparedCoverStorage));
 | 
						|
		} else if (!request.resize.isEmpty()) {
 | 
						|
			if (good && !useGood) {
 | 
						|
				good->load({});
 | 
						|
			} else if (thumb && !useThumb) {
 | 
						|
				thumb->load(_contextId);
 | 
						|
			}
 | 
						|
			using Option = Images::Option;
 | 
						|
			const auto options = Option::Smooth
 | 
						|
				| (useGood ? Option(0) : Option::Blurred)
 | 
						|
				| Option::RoundedLarge
 | 
						|
				| ((request.corners & RectPart::TopLeft)
 | 
						|
					? Option::RoundedTopLeft
 | 
						|
					: Option(0))
 | 
						|
				| ((request.corners & RectPart::TopRight)
 | 
						|
					? Option::RoundedTopRight
 | 
						|
					: Option(0))
 | 
						|
				| ((request.corners & RectPart::BottomRight)
 | 
						|
					? Option::RoundedBottomRight
 | 
						|
					: Option(0))
 | 
						|
				| ((request.corners & RectPart::BottomLeft)
 | 
						|
					? Option::RoundedBottomLeft
 | 
						|
					: Option(0));
 | 
						|
			_preparedCoverStorage = (useGood
 | 
						|
				? good
 | 
						|
				: useThumb
 | 
						|
				? thumb
 | 
						|
				: blurred
 | 
						|
				? blurred
 | 
						|
				: Image::BlankMedia().get())->pixNoCache(
 | 
						|
					_contextId,
 | 
						|
					request.resize.width(),
 | 
						|
					request.resize.height(),
 | 
						|
					options,
 | 
						|
					request.outer.width(),
 | 
						|
					request.outer.height()).toImage();
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return _preparedCoverStorage;
 | 
						|
}
 | 
						|
 | 
						|
QImage Pip::videoFrameForDirectPaint(const FrameRequest &request) const {
 | 
						|
	const auto result = videoFrame(request);
 | 
						|
 | 
						|
#ifdef USE_OPENGL_OVERLAY_WIDGET
 | 
						|
	const auto bytesPerLine = result.bytesPerLine();
 | 
						|
	if (bytesPerLine == result.width() * 4) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	// On macOS 10.8+ we use QOpenGLWidget as OverlayWidget base class.
 | 
						|
	// The OpenGL painter can't paint textures where byte data is with strides.
 | 
						|
	// So in that case we prepare a compact copy of the frame to render.
 | 
						|
	//
 | 
						|
	// See Qt commit ed557c037847e343caa010562952b398f806adcd
 | 
						|
	//
 | 
						|
	auto &cache = _frameForDirectPaint;
 | 
						|
	if (cache.size() != result.size()) {
 | 
						|
		cache = QImage(result.size(), result.format());
 | 
						|
	}
 | 
						|
	const auto height = result.height();
 | 
						|
	const auto line = cache.bytesPerLine();
 | 
						|
	Assert(line == result.width() * 4);
 | 
						|
	Assert(line < bytesPerLine);
 | 
						|
 | 
						|
	auto from = result.bits();
 | 
						|
	auto to = cache.bits();
 | 
						|
	for (auto y = 0; y != height; ++y) {
 | 
						|
		memcpy(to, from, line);
 | 
						|
		to += line;
 | 
						|
		from += bytesPerLine;
 | 
						|
	}
 | 
						|
	return cache;
 | 
						|
#endif // USE_OPENGL_OVERLAY_WIDGET
 | 
						|
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void Pip::paintRadialLoading(QPainter &p) const {
 | 
						|
	const auto inner = countRadialRect();
 | 
						|
#ifdef USE_OPENGL_OVERLAY_WIDGET
 | 
						|
	{
 | 
						|
		if (_radialCache.size() != inner.size() * cIntRetinaFactor()) {
 | 
						|
			_radialCache = QImage(
 | 
						|
				inner.size() * cIntRetinaFactor(),
 | 
						|
				QImage::Format_ARGB32_Premultiplied);
 | 
						|
			_radialCache.setDevicePixelRatio(cRetinaFactor());
 | 
						|
		}
 | 
						|
		_radialCache.fill(Qt::transparent);
 | 
						|
 | 
						|
		Painter q(&_radialCache);
 | 
						|
		paintRadialLoadingContent(q, inner.translated(-inner.topLeft()));
 | 
						|
	}
 | 
						|
	p.drawImage(inner.topLeft(), _radialCache);
 | 
						|
#else // USE_OPENGL_OVERLAY_WIDGET
 | 
						|
	paintRadialLoadingContent(p, inner);
 | 
						|
#endif // USE_OPENGL_OVERLAY_WIDGET
 | 
						|
}
 | 
						|
 | 
						|
void Pip::paintRadialLoadingContent(QPainter &p, const QRect &inner) const {
 | 
						|
	if (!_instance.waitingShown()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto arc = inner.marginsRemoved(QMargins(
 | 
						|
		st::radialLine,
 | 
						|
		st::radialLine,
 | 
						|
		st::radialLine,
 | 
						|
		st::radialLine));
 | 
						|
	p.setOpacity(_instance.waitingOpacity());
 | 
						|
	p.setPen(Qt::NoPen);
 | 
						|
	p.setBrush(st::radialBg);
 | 
						|
	{
 | 
						|
		PainterHighQualityEnabler hq(p);
 | 
						|
		p.drawEllipse(inner);
 | 
						|
	}
 | 
						|
	p.setOpacity(1.);
 | 
						|
	Ui::InfiniteRadialAnimation::Draw(
 | 
						|
		p,
 | 
						|
		_instance.waitingState(),
 | 
						|
		arc.topLeft(),
 | 
						|
		arc.size(),
 | 
						|
		_panel.width(),
 | 
						|
		st::radialFg,
 | 
						|
		st::radialLine);
 | 
						|
}
 | 
						|
 | 
						|
QRect Pip::countRadialRect() const {
 | 
						|
	const auto outer = _panel.inner();
 | 
						|
	return {
 | 
						|
		outer.x() + (outer.width() - st::radialSize.width()) / 2,
 | 
						|
		outer.y() + (outer.height() - st::radialSize.height()) / 2,
 | 
						|
		st::radialSize.width(),
 | 
						|
		st::radialSize.height()
 | 
						|
	};
 | 
						|
}
 | 
						|
 | 
						|
Pip::OverState Pip::computeState(QPoint position) const {
 | 
						|
	if (!_panel.inner().contains(position)) {
 | 
						|
		return OverState::None;
 | 
						|
	} else if (_close.area.contains(position)) {
 | 
						|
		return OverState::Close;
 | 
						|
	} else if (_enlarge.area.contains(position)) {
 | 
						|
		return OverState::Enlarge;
 | 
						|
	} else if (_playback.area.contains(position)) {
 | 
						|
		return OverState::Playback;
 | 
						|
	} else {
 | 
						|
		return OverState::Other;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Pip::waitingAnimationCallback() {
 | 
						|
	_panel.update(countRadialRect());
 | 
						|
}
 | 
						|
 | 
						|
} // namespace View
 | 
						|
} // namespace Media
 |