Play effects in a separate layer over MainWidget.

This commit is contained in:
John Preston 2024-05-14 18:16:25 +04:00
parent f7ab8a2174
commit cde70b9807
4 changed files with 92 additions and 27 deletions

View File

@ -63,6 +63,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/call_delayed.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "mainwidget.h"
#include "menu/menu_item_download_files.h"
#include "core/application.h"
#include "apiwrap.h"
@ -340,6 +341,8 @@ HistoryInner::HistoryInner(
, _history(history)
, _elementDelegate(_history->delegateMixin()->delegate())
, _emojiInteractions(std::make_unique<HistoryView::EmojiInteractions>(
this,
controller->content(),
&controller->session(),
[=](not_null<const Element*> view) { return itemTop(view); }))
, _migrated(history->migrateFrom())
@ -393,10 +396,6 @@ HistoryInner::HistoryInner(
_emojiInteractions->play(std::move(request), view);
}
}, lifetime());
_emojiInteractions->updateRequests(
) | rpl::start_with_next([=](QRect rect) {
update(rect);
}, lifetime());
_emojiInteractions->playStarted(
) | rpl::start_with_next([=](QString &&emoji) {
_controller->emojiInteractions().playStarted(_peer, std::move(emoji));
@ -1238,7 +1237,6 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
p.setOpacity(1.);
_reactionsManager->paint(p, context);
_emojiInteractions->paint(p);
}
bool HistoryInner::eventHook(QEvent *e) {

View File

@ -45,9 +45,13 @@ constexpr auto kDropDelayedAfterDelay = crl::time(2000);
} // namespace
EmojiInteractions::EmojiInteractions(
not_null<QWidget*> parent,
not_null<QWidget*> layerParent,
not_null<Main::Session*> session,
Fn<int(not_null<const Element*>)> itemTop)
: _session(session)
: _parent(parent)
, _layerParent(layerParent)
, _session(session)
, _itemTop(std::move(itemTop)) {
_session->data().viewRemoved(
) | rpl::filter([=] {
@ -282,6 +286,18 @@ void EmojiInteractions::play(
return;
}
if (!_layer) {
_layer = base::make_unique_q<Ui::RpWidget>(_layerParent);
const auto raw = _layer.get();
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
raw->show();
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
paint(raw, clip);
}, raw->lifetime());
}
refreshLayerShift();
_layer->setGeometry(_layerParent->rect());
auto lottie = document->session().emojiStickersPack().effectPlayer(
document,
data,
@ -302,11 +318,12 @@ void EmojiInteractions::play(
const auto i = ranges::find(_plays, raw, [](const Play &p) {
return p.lottie.get();
});
const auto rect = computeRect(*i).translated(shift);
if (rect.y() + rect.height() >= _visibleTop
&& rect.y() <= _visibleBottom) {
_updateRequests.fire_copy(rect);
auto update = computeRect(*i).translated(shift + _layerShift);
if (!i->lastTarget.isEmpty()) {
update = i->lastTarget.united(update);
}
_layer->update(update);
i->lastTarget = QRect();
});
}, lottie->lifetime());
_plays.push_back({
@ -331,6 +348,16 @@ void EmojiInteractions::play(
}
}
void EmojiInteractions::refreshLayerShift() {
_layerShift = Ui::MapFrom(_layerParent, _parent, QPoint(0, 0));
}
void EmojiInteractions::refreshLayerGeometryAndUpdate(QRect rect) {
if (!rect.isEmpty()) {
_layer->update(rect.translated(_layerShift));
}
}
void EmojiInteractions::visibleAreaUpdated(
int visibleTop,
int visibleBottom) {
@ -375,12 +402,34 @@ QRect EmojiInteractions::computeRect(const Play &play) const {
return QRect(QPoint(left, top), size).translated(play.shift);
}
void EmojiInteractions::paint(QPainter &p) {
void EmojiInteractions::paint(not_null<QWidget*> layer, QRect clip) {
refreshLayerShift();
const auto factor = style::DevicePixelRatio();
const auto whole = layer->rect();
auto p = QPainter(layer);
auto updated = QRect();
const auto addRect = [&](QRect rect) {
if (updated.isEmpty()) {
updated = rect;
} else {
updated = rect.united(updated);
}
};
for (auto &play : _plays) {
if (!play.lottie->ready()) {
continue;
}
const auto target = computeRect(play).translated(_layerShift);
if (!target.intersects(whole)) {
play.finished = true;
addRect(target);
continue;
} else if (!target.intersects(clip)) {
continue;
}
auto request = Lottie::FrameRequest();
request.box = play.outer * factor;
const auto rightAligned = play.view->hasRightLayout();
@ -394,18 +443,18 @@ void EmojiInteractions::paint(QPainter &p) {
play.framesCount = information.framesCount;
play.frameRate = information.frameRate;
}
const auto rect = computeRect(play);
if (play.started && !play.frame) {
play.finished = true;
_updateRequests.fire_copy(rect);
addRect(target);
continue;
} else if (play.frame > 0) {
play.started = true;
}
p.drawImage(
QRect(rect.topLeft(), frame.image.size() / factor),
QRect(target.topLeft(), frame.image.size() / factor),
frame.image);
play.lottie->markFrameShown();
play.lastTarget = target.translated(_layerShift);
}
_plays.erase(ranges::remove_if(_plays, [](const Play &play) {
if (!play.finished) {
@ -414,6 +463,18 @@ void EmojiInteractions::paint(QPainter &p) {
return true;
}), end(_plays));
checkDelayed();
if (_plays.empty()) {
layer->hide();
if (_layer.get() == layer) {
crl::on_main([moved = std::move(_layer)] {});
}
} else if (!updated.isEmpty()) {
const auto translated = updated.translated(_layerShift);
if (translated.intersects(whole)) {
_layer->update(translated);
}
}
}
void EmojiInteractions::checkDelayed() {
@ -456,10 +517,6 @@ void EmojiInteractions::checkDelayed() {
good.incoming);
}
rpl::producer<QRect> EmojiInteractions::updateRequests() const {
return _updateRequests.events();
}
rpl::producer<QString> EmojiInteractions::playStarted() const {
return _playStarted.events();
}

View File

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/unique_qptr.h"
namespace Data {
class DocumentMedia;
} // namespace Data
@ -27,6 +29,10 @@ namespace Stickers {
enum class EffectType : uint8;
} // namespace Stickers
namespace Ui {
class RpWidget;
} // namespace Ui
namespace HistoryView {
class Element;
@ -34,6 +40,8 @@ class Element;
class EmojiInteractions final {
public:
EmojiInteractions(
not_null<QWidget*> parent,
not_null<QWidget*> layerParent,
not_null<Main::Session*> session,
Fn<int(not_null<const Element*>)> itemTop);
~EmojiInteractions();
@ -50,14 +58,14 @@ public:
void playEffectOnRead(not_null<const Element*> view);
void playEffect(not_null<const Element*> view);
void paint(QPainter &p);
[[nodiscard]] rpl::producer<QRect> updateRequests() const;
void paint(not_null<QWidget*> layer, QRect clip);
[[nodiscard]] rpl::producer<QString> playStarted() const;
private:
struct Play {
not_null<const Element*> view;
std::unique_ptr<Lottie::SinglePlayer> lottie;
mutable QRect lastTarget;
QPoint shift;
QSize inner;
QSize outer;
@ -111,15 +119,21 @@ private:
const ResolvedEffect &resolved);
void checkPendingEffects();
void refreshLayerShift();
void refreshLayerGeometryAndUpdate(QRect rect);
const not_null<QWidget*> _parent;
const not_null<QWidget*> _layerParent;
const not_null<Main::Session*> _session;
const Fn<int(not_null<const Element*>)> _itemTop;
base::unique_qptr<Ui::RpWidget> _layer;
QPoint _layerShift;
int _visibleTop = 0;
int _visibleBottom = 0;
std::vector<Play> _plays;
std::vector<Delayed> _delayed;
rpl::event_stream<QRect> _updateRequests;
rpl::event_stream<QString> _playStarted;
std::vector<base::weak_ptr<const Element>> _pendingEffects;

View File

@ -375,6 +375,8 @@ ListWidget::ListWidget(
, _delegate(delegate)
, _session(session)
, _emojiInteractions(std::make_unique<EmojiInteractions>(
this,
_delegate->listWindow()->content(),
session,
[=](not_null<const Element*> view) { return itemTop(view); }))
, _context(_delegate->listContext())
@ -501,11 +503,6 @@ ListWidget::ListWidget(
_isChatWide = wide;
}, lifetime());
_emojiInteractions->updateRequests(
) | rpl::start_with_next([=](QRect rect) {
update(rect);
}, lifetime());
_selectScroll.scrolls(
) | rpl::start_with_next([=](int d) {
delegate->listScrollTo(_visibleTop + d, false);
@ -2278,7 +2275,6 @@ void ListWidget::paintEvent(QPaintEvent *e) {
if (_reactionsManager) {
_reactionsManager->paint(p, context);
}
_emojiInteractions->paint(p);
}
void ListWidget::paintUserpics(