| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667 |
- /*
- 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/player/media_player_float.h"
- #include "data/data_document.h"
- #include "data/data_session.h"
- #include "history/view/media/history_view_media.h"
- #include "history/history_item.h"
- #include "history/history.h"
- #include "history/view/history_view_element.h"
- #include "media/audio/media_audio.h"
- #include "media/streaming/media_streaming_instance.h"
- #include "media/view/media_view_playback_progress.h"
- #include "media/player/media_player_instance.h"
- #include "window/window_session_controller.h"
- #include "window/section_widget.h"
- #include "core/application.h"
- #include "core/core_settings.h"
- #include "main/main_session.h"
- #include "main/main_account.h"
- #include "ui/painter.h"
- #include "ui/ui_utility.h"
- #include "styles/style_media_player.h"
- #include "styles/style_chat.h"
- #include <QtWidgets/QApplication>
- namespace Media {
- namespace Player {
- using DoubleClickedCallback = Fn<void(not_null<const HistoryItem*>)>;
- RoundPainter::RoundPainter(not_null<HistoryItem*> item)
- : _item(item) {
- }
- bool RoundPainter::fillFrame(const QSize &size) {
- auto creating = _frame.isNull();
- const auto ratio = style::DevicePixelRatio();
- if (creating) {
- _frame = QImage(
- size * ratio,
- QImage::Format_ARGB32_Premultiplied);
- _frame.setDevicePixelRatio(ratio);
- }
- auto frameInner = [&] {
- return QRect(QPoint(), _frame.size() / ratio);
- };
- if (const auto streamed = instance()->roundVideoStreamed(_item)) {
- auto request = Streaming::FrameRequest::NonStrict();
- request.outer = request.resize = _frame.size();
- if (_roundingMask.size() != request.outer) {
- _roundingMask = Images::EllipseMask(frameInner().size());
- }
- request.mask = _roundingMask;
- auto frame = streamed->frame(request);
- if (!frame.isNull()) {
- _frame.fill(Qt::transparent);
- auto p = QPainter(&_frame);
- PainterHighQualityEnabler hq(p);
- p.drawImage(frameInner(), frame);
- return true;
- }
- }
- if (creating) {
- _frame.fill(Qt::transparent);
- auto p = QPainter(&_frame);
- PainterHighQualityEnabler hq(p);
- p.setPen(Qt::NoPen);
- p.setBrush(st::imageBg);
- p.drawEllipse(frameInner());
- }
- return false;
- }
- const QImage &RoundPainter::frame() const {
- return _frame;
- }
- Float::Float(
- QWidget *parent,
- not_null<HistoryItem*> item,
- Fn<void(bool visible)> toggleCallback,
- Fn<void(bool closed)> draggedCallback,
- DoubleClickedCallback doubleClickedCallback)
- : RpWidget(parent)
- , _item(item)
- , _toggleCallback(std::move(toggleCallback))
- , _draggedCallback(std::move(draggedCallback))
- , _doubleClickedCallback(std::move(doubleClickedCallback)) {
- auto media = _item->media();
- Assert(media != nullptr);
- auto document = media->document();
- Assert(document != nullptr);
- Assert(document->isVideoMessage());
- auto margin = st::mediaPlayerFloatMargin;
- auto size = 2 * margin + st::mediaPlayerFloatSize;
- resize(size, size);
- _roundPainter = std::make_unique<RoundPainter>(item);
- prepareShadow();
- document->session().data().itemRepaintRequest(
- ) | rpl::start_with_next([this](auto item) {
- if (_item == item) {
- repaintItem();
- }
- }, lifetime());
- document->session().data().itemRemoved(
- ) | rpl::start_with_next([this](auto item) {
- if (_item == item) {
- detach();
- }
- }, lifetime());
- document->session().account().sessionChanges(
- ) | rpl::start_with_next([=] {
- detach();
- }, lifetime());
- setCursor(style::cur_pointer);
- }
- void Float::mousePressEvent(QMouseEvent *e) {
- _down = true;
- _downPoint = e->pos();
- }
- void Float::mouseMoveEvent(QMouseEvent *e) {
- if (_down && (e->pos() - _downPoint).manhattanLength() > QApplication::startDragDistance()) {
- _down = false;
- _drag = true;
- _dragLocalPoint = e->pos();
- } else if (_drag) {
- auto delta = (e->pos() - _dragLocalPoint);
- move(pos() + delta);
- setOpacity(outRatio());
- }
- }
- float64 Float::outRatio() const {
- auto parent = parentWidget()->rect();
- auto min = 1.;
- if (x() < parent.x()) {
- accumulate_min(min, 1. - (parent.x() - x()) / float64(width()));
- }
- if (y() < parent.y()) {
- accumulate_min(min, 1. - (parent.y() - y()) / float64(height()));
- }
- if (x() + width() > parent.x() + parent.width()) {
- accumulate_min(min, 1. - (x() + width() - parent.x() - parent.width()) / float64(width()));
- }
- if (y() + height() > parent.y() + parent.height()) {
- accumulate_min(min, 1. - (y() + height() - parent.y() - parent.height()) / float64(height()));
- }
- return std::clamp(min, 0., 1.);
- }
- void Float::mouseReleaseEvent(QMouseEvent *e) {
- if (base::take(_down) && _item) {
- pauseResume();
- }
- if (_drag) {
- finishDrag(outRatio() < 0.5);
- }
- }
- void Float::finishDrag(bool closed) {
- _drag = false;
- if (_draggedCallback) {
- _draggedCallback(closed);
- }
- }
- void Float::mouseDoubleClickEvent(QMouseEvent *e) {
- if (_item && _doubleClickedCallback) {
- // Handle second click.
- pauseResume();
- _doubleClickedCallback(_item);
- }
- }
- void Float::pauseResume() {
- if (const auto streamed = getStreamed()) {
- if (streamed->paused()) {
- streamed->resume();
- } else {
- streamed->pause();
- }
- }
- }
- void Float::detach() {
- if (_item) {
- _item = nullptr;
- _roundPainter = nullptr;
- if (_toggleCallback) {
- _toggleCallback(false);
- }
- }
- }
- void Float::prepareShadow() {
- const auto ratio = style::DevicePixelRatio();
- auto shadow = QImage(
- size() * ratio,
- QImage::Format_ARGB32_Premultiplied);
- shadow.fill(Qt::transparent);
- shadow.setDevicePixelRatio(ratio);
- {
- auto p = QPainter(&shadow);
- PainterHighQualityEnabler hq(p);
- p.setPen(Qt::NoPen);
- p.setBrush(st::shadowFg);
- auto extend = 2 * st::lineWidth;
- p.drawEllipse(getInnerRect().marginsAdded(QMargins(extend, extend, extend, extend)));
- }
- _shadow = Ui::PixmapFromImage(Images::Blur(std::move(shadow)));
- }
- QRect Float::getInnerRect() const {
- auto margin = st::mediaPlayerFloatMargin;
- return rect().marginsRemoved(QMargins(margin, margin, margin, margin));
- }
- void Float::paintEvent(QPaintEvent *e) {
- auto p = QPainter(this);
- p.setOpacity(_opacity);
- p.drawPixmap(0, 0, _shadow);
- const auto inner = getInnerRect();
- if (!(_roundPainter && _roundPainter->fillFrame(inner.size()))
- && _toggleCallback) {
- _toggleCallback(false);
- }
- if (_roundPainter) {
- p.drawImage(inner.topLeft(), _roundPainter->frame());
- }
- const auto playback = getPlayback();
- const auto progress = playback ? playback->value() : 1.;
- if (progress > 0.) {
- auto pen = st::historyVideoMessageProgressFg->p;
- //auto was = p.pen();
- pen.setWidth(st::radialLine);
- pen.setCapStyle(Qt::RoundCap);
- p.setPen(pen);
- p.setOpacity(_opacity * st::historyVideoMessageProgressOpacity);
- auto from = arc::kQuarterLength;
- auto len = -qRound(arc::kFullLength * progress);
- auto stepInside = st::radialLine / 2;
- {
- PainterHighQualityEnabler hq(p);
- p.drawArc(inner.marginsRemoved(QMargins(stepInside, stepInside, stepInside, stepInside)), from, len);
- }
- //p.setPen(was);
- //p.setOpacity(_opacity);
- }
- }
- Streaming::Instance *Float::getStreamed() const {
- return instance()->roundVideoStreamed(_item);
- }
- View::PlaybackProgress *Float::getPlayback() const {
- return instance()->roundVideoPlayback(_item);
- }
- bool Float::hasFrame() const {
- return (getStreamed() != nullptr);
- }
- void Float::repaintItem() {
- update();
- if (hasFrame() && _toggleCallback) {
- _toggleCallback(true);
- }
- }
- template <typename ToggleCallback, typename DraggedCallback>
- FloatController::Item::Item(
- not_null<QWidget*> parent,
- not_null<HistoryItem*> item,
- ToggleCallback toggle,
- DraggedCallback dragged,
- DoubleClickedCallback doubleClicked)
- : animationSide(RectPart::Right)
- , column(Window::Column::Second)
- , corner(RectPart::TopRight)
- , widget(
- parent,
- item,
- [=, toggle = std::move(toggle)](bool visible) {
- toggle(this, visible);
- },
- [=, dragged = std::move(dragged)](bool closed) {
- dragged(this, closed);
- },
- std::move(doubleClicked)) {
- }
- FloatController::FloatController(not_null<FloatDelegate*> delegate)
- : _delegate(delegate)
- , _parent(_delegate->floatPlayerWidget()) {
- Media::Player::instance()->trackChanged(
- ) | rpl::filter([=](AudioMsgId::Type type) {
- return (type == AudioMsgId::Type::Voice);
- }) | rpl::start_with_next([=] {
- checkCurrent();
- }, _lifetime);
- startDelegateHandling();
- }
- void FloatController::replaceDelegate(not_null<FloatDelegate*> delegate) {
- _delegateLifetime.destroy();
- _delegate = delegate;
- _parent = _delegate->floatPlayerWidget();
- startDelegateHandling();
- for (const auto &item : _items) {
- item->widget->setParent(_parent);
- }
- checkVisibility();
- }
- void FloatController::startDelegateHandling() {
- _delegate->floatPlayerCheckVisibilityRequests(
- ) | rpl::start_with_next([=] {
- checkVisibility();
- }, _delegateLifetime);
- _delegate->floatPlayerHideAllRequests(
- ) | rpl::start_with_next([=] {
- hideAll();
- }, _delegateLifetime);
- _delegate->floatPlayerShowVisibleRequests(
- ) | rpl::start_with_next([=] {
- showVisible();
- }, _delegateLifetime);
- _delegate->floatPlayerRaiseAllRequests(
- ) | rpl::start_with_next([=] {
- raiseAll();
- }, _delegateLifetime);
- _delegate->floatPlayerUpdatePositionsRequests(
- ) | rpl::start_with_next([=] {
- updatePositions();
- }, _delegateLifetime);
- _delegate->floatPlayerFilterWheelEventRequests(
- ) | rpl::start_with_next([=](
- const FloatDelegate::FloatPlayerFilterWheelEventRequest &request) {
- *request.result = filterWheelEvent(request.object, request.event);
- }, _delegateLifetime);
- _delegate->floatPlayerAreaUpdates(
- ) | rpl::start_with_next([=] {
- checkVisibility();
- }, _delegateLifetime);
- }
- void FloatController::checkCurrent() {
- const auto state = Media::Player::instance()->current(AudioMsgId::Type::Voice);
- const auto audio = state.audio();
- const auto fullId = state.contextId();
- const auto last = current();
- if (last
- && audio
- && !last->widget->detached()
- && (&last->widget->item()->history()->session() == &audio->session())
- && (last->widget->item()->fullId() == fullId)) {
- return;
- }
- if (last) {
- last->widget->detach();
- }
- if (!audio) {
- return;
- }
- if (const auto item = audio->session().data().message(fullId)) {
- if (const auto media = item->media()) {
- if (const auto document = media->document()) {
- if (document->isVideoMessage()) {
- create(item);
- }
- }
- }
- }
- }
- void FloatController::create(not_null<HistoryItem*> item) {
- _items.push_back(std::make_unique<Item>(
- _parent,
- item,
- [=](not_null<Item*> instance, bool visible) {
- instance->hiddenByWidget = !visible;
- toggle(instance);
- },
- [=](not_null<Item*> instance, bool closed) {
- finishDrag(instance, closed);
- },
- [=](not_null<const HistoryItem*> item) {
- _delegate->floatPlayerDoubleClickEvent(item);
- }));
- current()->column = Core::App().settings().floatPlayerColumn();
- current()->corner = Core::App().settings().floatPlayerCorner();
- checkVisibility();
- }
- void FloatController::toggle(not_null<Item*> instance) {
- auto visible = !instance->hiddenByHistory && !instance->hiddenByWidget && instance->widget->isReady();
- if (instance->visible != visible) {
- instance->widget->resetMouseState();
- instance->visible = visible;
- if (!instance->visibleAnimation.animating() && !instance->hiddenByDrag) {
- auto finalRect = QRect(getPosition(instance), instance->widget->size());
- instance->animationSide = getSide(finalRect.center());
- }
- instance->visibleAnimation.start([=] {
- updatePosition(instance);
- }, visible ? 0. : 1., visible ? 1. : 0., st::slideDuration, visible ? anim::easeOutCirc : anim::linear);
- updatePosition(instance);
- }
- }
- void FloatController::checkVisibility() {
- const auto instance = current();
- if (!instance) {
- return;
- }
- const auto item = instance->widget->item();
- instance->hiddenByHistory = item
- ? _delegate->floatPlayerIsVisible(item)
- : false;
- toggle(instance);
- updatePosition(instance);
- }
- void FloatController::hideAll() {
- for (const auto &instance : _items) {
- instance->widget->hide();
- }
- }
- void FloatController::showVisible() {
- for (const auto &instance : _items) {
- if (instance->visible) {
- instance->widget->show();
- }
- }
- }
- void FloatController::raiseAll() {
- for (const auto &instance : _items) {
- instance->widget->raise();
- }
- }
- void FloatController::updatePositions() {
- for (const auto &instance : _items) {
- updatePosition(instance.get());
- }
- }
- std::optional<bool> FloatController::filterWheelEvent(
- not_null<QObject*> object,
- not_null<QEvent*> event) {
- for (const auto &instance : _items) {
- if (instance->widget == object) {
- const auto section = _delegate->floatPlayerGetSection(
- instance->column);
- return section->floatPlayerHandleWheelEvent(event);
- }
- }
- return std::nullopt;
- }
- void FloatController::updatePosition(not_null<Item*> instance) {
- auto visible = instance->visibleAnimation.value(instance->visible ? 1. : 0.);
- if (visible == 0. && !instance->visible) {
- instance->widget->hide();
- if (instance->widget->detached()) {
- InvokeQueued(instance->widget, [=] {
- remove(instance);
- });
- }
- return;
- }
- if (!instance->widget->dragged()) {
- if (instance->widget->isHidden()) {
- instance->widget->show();
- }
- auto dragged = instance->draggedAnimation.value(1.);
- auto position = QPoint();
- if (instance->hiddenByDrag) {
- instance->widget->setOpacity(instance->widget->countOpacityByParent());
- position = getHiddenPosition(instance->dragFrom, instance->widget->size(), instance->animationSide);
- } else {
- instance->widget->setOpacity(visible * visible);
- position = getPosition(instance);
- if (visible < 1.) {
- auto hiddenPosition = getHiddenPosition(position, instance->widget->size(), instance->animationSide);
- position.setX(anim::interpolate(hiddenPosition.x(), position.x(), visible));
- position.setY(anim::interpolate(hiddenPosition.y(), position.y(), visible));
- }
- }
- if (dragged < 1.) {
- position.setX(anim::interpolate(instance->dragFrom.x(), position.x(), dragged));
- position.setY(anim::interpolate(instance->dragFrom.y(), position.y(), dragged));
- }
- instance->widget->move(position);
- }
- }
- QPoint FloatController::getHiddenPosition(
- QPoint position,
- QSize size,
- RectPart side) const {
- switch (side) {
- case RectPart::Left: return { -size.width(), position.y() };
- case RectPart::Top: return { position.x(), -size.height() };
- case RectPart::Right: return { _parent->width(), position.y() };
- case RectPart::Bottom: return { position.x(), _parent->height() };
- }
- Unexpected("Bad side in MainWidget::getFloatPlayerHiddenPosition().");
- }
- QPoint FloatController::getPosition(not_null<Item*> instance) const {
- const auto section = _delegate->floatPlayerGetSection(instance->column);
- const auto rect = section->floatPlayerAvailableRect();
- auto position = rect.topLeft();
- if (IsBottomCorner(instance->corner)) {
- position.setY(position.y() + rect.height() - instance->widget->height());
- }
- if (IsRightCorner(instance->corner)) {
- position.setX(position.x() + rect.width() - instance->widget->width());
- }
- return _parent->mapFromGlobal(position);
- }
- RectPart FloatController::getSide(QPoint center) const {
- const auto left = std::abs(center.x());
- const auto right = std::abs(_parent->width() - center.x());
- const auto top = std::abs(center.y());
- const auto bottom = std::abs(_parent->height() - center.y());
- if (left < right && left < top && left < bottom) {
- return RectPart::Left;
- } else if (right < top && right < bottom) {
- return RectPart::Right;
- } else if (top < bottom) {
- return RectPart::Top;
- }
- return RectPart::Bottom;
- }
- void FloatController::remove(not_null<Item*> instance) {
- auto widget = std::move(instance->widget);
- auto i = ranges::find_if(_items, [&](auto &item) {
- return (item.get() == instance);
- });
- Assert(i != _items.end());
- _items.erase(i);
- // ~QWidget() can call HistoryInner::enterEvent() which can
- // lead to repaintHistoryItem() and we'll have an instance
- // in _items with destroyed widget. So we destroy the
- // instance first and only after that destroy the widget.
- widget.destroy();
- }
- void FloatController::updateColumnCorner(QPoint center) {
- Expects(!_items.empty());
- auto size = _items.back()->widget->size();
- auto min = INT_MAX;
- auto column = Core::App().settings().floatPlayerColumn();
- auto corner = Core::App().settings().floatPlayerCorner();
- auto checkSection = [&](
- not_null<FloatSectionDelegate*> widget,
- Window::Column widgetColumn) {
- auto rect = _parent->mapFromGlobal(
- widget->floatPlayerAvailableRect());
- auto left = rect.x() + (size.width() / 2);
- auto right = rect.x() + rect.width() - (size.width() / 2);
- auto top = rect.y() + (size.height() / 2);
- auto bottom = rect.y() + rect.height() - (size.height() / 2);
- auto checkCorner = [&](QPoint point, RectPart checked) {
- auto distance = (point - center).manhattanLength();
- if (min > distance) {
- min = distance;
- column = widgetColumn;
- corner = checked;
- }
- };
- checkCorner({ left, top }, RectPart::TopLeft);
- checkCorner({ right, top }, RectPart::TopRight);
- checkCorner({ left, bottom }, RectPart::BottomLeft);
- checkCorner({ right, bottom }, RectPart::BottomRight);
- };
- _delegate->floatPlayerEnumerateSections(checkSection);
- auto &settings = Core::App().settings();
- if (settings.floatPlayerColumn() != column) {
- settings.setFloatPlayerColumn(column);
- Core::App().saveSettingsDelayed();
- }
- if (settings.floatPlayerCorner() != corner) {
- settings.setFloatPlayerCorner(corner);
- Core::App().saveSettingsDelayed();
- }
- }
- void FloatController::finishDrag(not_null<Item*> instance, bool closed) {
- instance->dragFrom = instance->widget->pos();
- const auto center = instance->widget->geometry().center();
- if (closed) {
- instance->hiddenByDrag = true;
- instance->animationSide = getSide(center);
- }
- updateColumnCorner(center);
- instance->column = Core::App().settings().floatPlayerColumn();
- instance->corner = Core::App().settings().floatPlayerCorner();
- instance->draggedAnimation.stop();
- instance->draggedAnimation.start(
- [=] { updatePosition(instance); },
- 0.,
- 1.,
- st::slideDuration,
- anim::sineInOut);
- updatePosition(instance);
- if (closed) {
- if (const auto item = instance->widget->item()) {
- _closeEvents.fire(item->fullId());
- }
- instance->widget->detach();
- Media::Player::instance()->stop(AudioMsgId::Type::Voice);
- }
- }
- } // namespace Player
- } // namespace Media
|