| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- /*
- 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 "ui/effects/message_sending_animation_controller.h"
- #include "data/data_document.h"
- #include "data/data_session.h"
- #include "history/history_item.h"
- #include "history/view/history_view_element.h"
- #include "history/view/history_view_list_widget.h" // kItemRevealDuration
- #include "history/view/media/history_view_media.h"
- #include "main/main_session.h"
- #include "mainwidget.h"
- #include "ui/chat/chat_style.h"
- #include "ui/chat/chat_theme.h"
- #include "ui/effects/animation_value.h"
- #include "ui/effects/animation_value_f.h"
- #include "ui/effects/animations.h"
- #include "ui/painter.h"
- #include "ui/rp_widget.h"
- #include "window/window_session_controller.h"
- #include "styles/style_chat.h"
- namespace Ui {
- namespace {
- constexpr auto kSurroundingProgress = 0.5;
- inline float64 OffsetMid(int value, float64 min, float64 max = 1.) {
- return ((value * max) - (value * min)) / 2.;
- }
- class Content final : public RpWidget {
- public:
- Content(
- not_null<RpWidget*> parent,
- not_null<Window::SessionController*> controller,
- MessageSendingAnimationFrom &&fromInfo,
- MessageSendingAnimationController::SendingInfoTo &&to);
- [[nodiscard]] rpl::producer<> destroyRequests() const;
- protected:
- void paintEvent(QPaintEvent *e) override;
- private:
- using Context = Ui::ChatPaintContext;
- void createSurrounding();
- void createBubble();
- HistoryView::Element *maybeView() const;
- bool checkView(HistoryView::Element *currentView) const;
- void drawContent(Painter &p, float64 progress) const;
- const not_null<Window::SessionController*> _controller;
- const bool _crop;
- MessageSendingAnimationController::SendingInfoTo _toInfo;
- QRect _from;
- QPoint _to;
- QRect _innerContentRect;
- Animations::Simple _animation;
- float64 _minScale = 0;
- struct {
- base::unique_qptr<Ui::RpWidget> widget;
- QPoint offsetFromContent;
- } _bubble;
- base::unique_qptr<Ui::RpWidget> _surrounding;
- rpl::event_stream<> _destroyRequests;
- };
- Content::Content(
- not_null<RpWidget*> parent,
- not_null<Window::SessionController*> controller,
- MessageSendingAnimationFrom &&fromInfo,
- MessageSendingAnimationController::SendingInfoTo &&to)
- : RpWidget(parent)
- , _controller(controller)
- , _crop(fromInfo.crop)
- , _toInfo(std::move(to))
- , _from(parent->mapFromGlobal(fromInfo.globalStartGeometry))
- , _innerContentRect(maybeView()->media()->contentRectForReactions())
- , _minScale(float64(_from.height()) / _innerContentRect.height()) {
- Expects(_toInfo.view != nullptr);
- Expects(_toInfo.paintContext != nullptr);
- show();
- setAttribute(Qt::WA_TransparentForMouseEvents);
- raise();
- base::take(
- _toInfo.globalEndTopLeft
- ) | rpl::distinct_until_changed(
- ) | rpl::start_with_next([=](const std::optional<QPoint> &p) {
- if (p) {
- _to = parent->mapFromGlobal(*p);
- } else {
- _destroyRequests.fire({});
- }
- }, lifetime());
- _controller->session().downloaderTaskFinished(
- ) | rpl::start_with_next([=] {
- update();
- }, lifetime());
- resize(_innerContentRect.size());
- const auto innerGeometry = maybeView()->innerGeometry();
- auto animationCallback = [=](float64 value) {
- auto resultFrom = rect();
- resultFrom.moveCenter(_from.center());
- const auto resultTo = _to
- + innerGeometry.topLeft()
- + _innerContentRect.topLeft();
- const auto x = anim::interpolate(resultFrom.x(), resultTo.x(), value);
- const auto y = anim::interpolate(resultFrom.y(), resultTo.y(), value);
- moveToLeft(x, y);
- update();
- if ((value > kSurroundingProgress)
- && !_surrounding
- && !_bubble.widget) {
- const auto currentView = maybeView();
- if (!checkView(currentView)) {
- return;
- }
- if (currentView->hasBubble()) {
- createBubble();
- } else {
- createSurrounding();
- }
- }
- if (_surrounding) {
- _surrounding->moveToLeft(
- x - _innerContentRect.x(),
- y - _innerContentRect.y());
- }
- if (_bubble.widget) {
- _bubble.widget->moveToLeft(
- x - _bubble.offsetFromContent.x(),
- y - _bubble.offsetFromContent.y());
- }
- if (value == 1.) {
- const auto currentView = maybeView();
- if (!checkView(currentView)) {
- return;
- }
- const auto controller = _controller;
- _destroyRequests.fire({});
- controller->session().data().requestViewRepaint(currentView);
- }
- };
- animationCallback(0.);
- _animation.start(
- std::move(animationCallback),
- 0.,
- 1.,
- HistoryView::ListWidget::kItemRevealDuration);
- }
- HistoryView::Element *Content::maybeView() const {
- return _toInfo.view();
- }
- bool Content::checkView(HistoryView::Element *currentView) const {
- if (!currentView) {
- _destroyRequests.fire({});
- return false;
- }
- return true;
- }
- void Content::paintEvent(QPaintEvent *e) {
- const auto progress = _animation.value(_animation.animating() ? 0. : 1.);
- if (!_crop) {
- Painter p(this);
- p.fillRect(e->rect(), Qt::transparent);
- drawContent(p, progress);
- } else {
- // Use QImage to make CompositionMode_Clear work.
- auto image = QImage(
- size() * style::DevicePixelRatio(),
- QImage::Format_ARGB32_Premultiplied);
- image.setDevicePixelRatio(style::DevicePixelRatio());
- image.fill(Qt::transparent);
- const auto scaledFromSize = _from.size().scaled(
- _innerContentRect.size(),
- Qt::KeepAspectRatio);
- const auto cropW = std::ceil(
- (_innerContentRect.width() - scaledFromSize.width())
- / 2.
- * (1. - std::clamp(progress / kSurroundingProgress, 0., 1.)));
- {
- Painter p(&image);
- drawContent(p, progress);
- p.setCompositionMode(QPainter::CompositionMode_Clear);
- p.fillRect(
- QRect(0, 0, cropW, _innerContentRect.height()),
- Qt::black);
- p.fillRect(
- QRect(
- _innerContentRect.width() - cropW,
- 0,
- cropW,
- _innerContentRect.height()),
- Qt::black);
- }
- auto p = QPainter(this);
- p.drawImage(QPoint(), std::move(image));
- }
- }
- void Content::drawContent(Painter &p, float64 progress) const {
- const auto scale = anim::interpolateF(_minScale, 1., progress);
- p.translate(
- (1 - progress) * OffsetMid(width(), _minScale),
- (1 - progress) * OffsetMid(height(), _minScale));
- p.scale(scale, scale);
- const auto currentView = maybeView();
- if (!checkView(currentView)) {
- return;
- }
- auto context = _toInfo.paintContext();
- context.skipDrawingParts = Context::SkipDrawingParts::Surrounding;
- context.outbg = currentView->hasOutLayout();
- p.translate(-_innerContentRect.topLeft());
- currentView->media()->draw(p, context);
- }
- rpl::producer<> Content::destroyRequests() const {
- return _destroyRequests.events();
- }
- void Content::createSurrounding() {
- _surrounding = base::make_unique_q<Ui::RpWidget>(parentWidget());
- _surrounding->setAttribute(Qt::WA_TransparentForMouseEvents);
- const auto currentView = maybeView();
- if (!checkView(currentView)) {
- return;
- }
- const auto surroundingSize = currentView->innerGeometry().size();
- const auto offset = _innerContentRect.topLeft();
- _surrounding->resize(surroundingSize);
- _surrounding->show();
- // Do not raise.
- _surrounding->stackUnder(this);
- stackUnder(_surrounding.get());
- _surrounding->paintRequest(
- ) | rpl::start_with_next([=, size = surroundingSize](const QRect &r) {
- Painter p(_surrounding);
- p.fillRect(r, Qt::transparent);
- const auto progress = _animation.value(0.);
- const auto revProgress = 1. - progress;
- const auto divider = 1. - kSurroundingProgress;
- const auto alpha = (divider - revProgress) / divider;
- p.setOpacity(alpha);
- const auto scale = anim::interpolateF(_minScale, 1., progress);
- p.translate(
- revProgress * OffsetMid(size.width() + offset.x(), _minScale),
- revProgress * OffsetMid(size.height() + offset.y(), _minScale));
- p.scale(scale, scale);
- const auto currentView = maybeView();
- if (!checkView(currentView)) {
- return;
- }
- auto context = _toInfo.paintContext();
- context.skipDrawingParts = Context::SkipDrawingParts::Content;
- context.outbg = currentView->hasOutLayout();
- currentView->media()->draw(p, context);
- }, _surrounding->lifetime());
- }
- void Content::createBubble() {
- _bubble.widget = base::make_unique_q<Ui::RpWidget>(parentWidget());
- _bubble.widget->setAttribute(Qt::WA_TransparentForMouseEvents);
- const auto currentView = maybeView();
- if (!checkView(currentView)) {
- return;
- }
- const auto innerGeometry = currentView->innerGeometry();
- const auto tailWidth = st::historyBubbleTailOutLeft.width();
- _bubble.offsetFromContent = QPoint(
- currentView->hasOutLayout() ? 0 : tailWidth,
- innerGeometry.y());
- const auto scaleOffset = QPoint(0, innerGeometry.y());
- const auto paintOffsetLeft = innerGeometry.x()
- - _bubble.offsetFromContent.x();
- const auto hasCommentsButton = currentView->data()->repliesAreComments()
- || currentView->data()->externalReply();
- _bubble.widget->resize(innerGeometry.size()
- + QSize(
- currentView->hasOutLayout() ? tailWidth : 0,
- hasCommentsButton ? innerGeometry.y() : 0));
- _bubble.widget->show();
- _bubble.widget->stackUnder(this);
- _bubble.widget->paintRequest(
- ) | rpl::start_with_next([=](const QRect &r) {
- Painter p(_bubble.widget);
- p.fillRect(r, Qt::transparent);
- const auto progress = _animation.value(0.);
- const auto revProgress = 1. - progress;
- const auto divider = 1. - kSurroundingProgress;
- const auto alpha = (divider - revProgress) / divider;
- p.setOpacity(alpha);
- const auto scale = anim::interpolateF(_minScale, 1., progress);
- p.translate(
- revProgress * OffsetMid(width() + scaleOffset.x(), _minScale),
- revProgress * OffsetMid(height() + scaleOffset.y(), _minScale));
- p.scale(scale, scale);
- const auto currentView = maybeView();
- if (!checkView(currentView)) {
- return;
- }
- auto context = _toInfo.paintContext();
- context.skipDrawingParts = Context::SkipDrawingParts::Content;
- context.outbg = currentView->hasOutLayout();
- context.translate(paintOffsetLeft, 0);
- p.translate(-paintOffsetLeft, 0);
- currentView->draw(p, context);
- }, _bubble.widget->lifetime());
- }
- } // namespace
- MessageSendingAnimationController::MessageSendingAnimationController(
- not_null<Window::SessionController*> controller)
- : _controller(controller) {
- subscribeToDestructions();
- }
- void MessageSendingAnimationController::subscribeToDestructions() {
- _controller->session().data().itemIdChanged(
- ) | rpl::start_with_next([=](Data::Session::IdChange change) {
- _itemSendPending.remove(change.oldId);
- }, _lifetime);
- _controller->session().data().itemRemoved(
- ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
- _itemSendPending.remove(item->id);
- _processing.remove(item);
- }, _lifetime);
- }
- void MessageSendingAnimationController::appendSending(
- MessageSendingAnimationFrom from) {
- if (anim::Disabled()) {
- return;
- }
- if (from.localId) {
- _itemSendPending[*from.localId] = std::move(from);
- }
- }
- void MessageSendingAnimationController::startAnimation(SendingInfoTo &&to) {
- if (anim::Disabled()) {
- return;
- }
- const auto container = _controller->content();
- const auto item = to.view()->data();
- const auto it = _itemSendPending.find(item->fullId().msg);
- if (it == end(_itemSendPending)) {
- return;
- }
- auto from = std::move(it->second);
- _itemSendPending.erase(it);
- auto content = base::make_unique_q<Content>(
- container,
- _controller,
- std::move(from),
- std::move(to));
- content->destroyRequests(
- ) | rpl::start_with_next([=] {
- _processing.erase(item);
- }, content->lifetime());
- _processing.emplace(item, std::move(content));
- }
- bool MessageSendingAnimationController::hasAnimatedMessage(
- not_null<HistoryItem*> item) const {
- return _processing.contains(item);
- }
- void MessageSendingAnimationController::clear() {
- _itemSendPending.clear();
- _processing.clear();
- }
- bool MessageSendingAnimationController::checkExpectedType(
- not_null<HistoryItem*> item) {
- const auto it = _itemSendPending.find(item->id);
- if (it == end(_itemSendPending)) {
- return false;
- }
- const auto type = it->second.type;
- const auto isSticker = type == MessageSendingAnimationFrom::Type::Sticker;
- const auto isGif = type == MessageSendingAnimationFrom::Type::Gif;
- if (isSticker || isGif) {
- if (item->emptyText()) {
- if (const auto media = item->media()) {
- if (const auto document = media->document()) {
- if ((isGif && document->isGifv())
- || (isSticker && document->sticker())) {
- return true;
- }
- }
- }
- }
- }
- _itemSendPending.erase(it);
- return false;
- }
- } // namespace Ui
|