diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 5933f1e85..99545cc50 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -441,6 +441,19 @@ void GifsListWidget::selectInlineResult( return; } + const auto messageSendingFrom = [&] { + if (options.scheduled) { + return Ui::MessageSendingAnimationFrom(); + } + const auto rect = item->innerContentRect().translated( + _mosaic.findRect(index).topLeft()); + return Ui::MessageSendingAnimationFrom{ + .localId = controller()->session().data().nextLocalMessageId(), + .globalStartGeometry = mapToGlobal(rect), + .crop = true, + }; + }; + forceSend |= base::IsCtrlPressed(); if (const auto photo = item->getPhoto()) { using Data::PhotoSize; @@ -448,7 +461,7 @@ void GifsListWidget::selectInlineResult( if (forceSend || (media && media->image(PhotoSize::Thumbnail)) || (media && media->image(PhotoSize::Large))) { - _photoChosen.fire_copy({ + _photoChosen.fire({ .photo = photo, .options = options }); } else if (!photo->loading(PhotoSize::Thumbnail)) { @@ -458,9 +471,11 @@ void GifsListWidget::selectInlineResult( const auto media = document->activeMediaView(); const auto preview = Data::VideoPreviewState(media.get()); if (forceSend || (media && preview.loaded())) { - _fileChosen.fire_copy({ + _fileChosen.fire({ .document = document, - .options = options }); + .options = options, + .messageSendingFrom = messageSendingFrom(), + }); } else if (!preview.usingThumbnail()) { if (preview.loading()) { document->cancel(); @@ -472,7 +487,12 @@ void GifsListWidget::selectInlineResult( } } else if (const auto inlineResult = item->getResult()) { if (inlineResult->onChoose(item)) { - _inlineResultChosen.fire({ inlineResult, _searchBot, options }); + _inlineResultChosen.fire({ + .result = inlineResult, + .bot = _searchBot, + .options = options, + .messageSendingFrom = messageSendingFrom(), + }); } } } diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 523807798..a703edcd8 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -2758,6 +2758,29 @@ QRect Message::innerGeometry() const { w + rightActionSize().value_or(QSize(0, 0)).width() * 2, width())); } + if (hasBubble()) { + result.translate(0, st::msgPadding.top() + st::mediaInBubbleSkip); + + if (displayFromName()) { + // See paintFromName(). + result.translate(0, st::msgNameFont->height); + } + // Skip displayForwardedFrom() until there are no animations for it. + if (displayedReply()) { + // See paintReplyInfo(). + result.translate( + 0, + st::msgReplyPadding.top() + + st::msgReplyBarSize.height() + + st::msgReplyPadding.bottom()); + } + if (!displayFromName() && !displayForwardedFrom()) { + // See paintViaBotIdInfo(). + if (message()->Has()) { + result.translate(0, st::msgServiceNameFont->height); + } + } + } return result; } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 21258af61..122e1899e 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -139,6 +139,15 @@ int Gif::resizeGetHeight(int width) { return _height; } +QRect Gif::innerContentRect() const { + ensureDataMediaCreated(getShownDocument()); + + const auto size = (!_thumb.isNull()) + ? (_thumb.size() / style::DevicePixelRatio()) + : countFrameSize(); + return QRect(QPoint(), size); +} + void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) const { const auto document = getShownDocument(); ensureDataMediaCreated(document); diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index 49a2c34a2..0b6f02a9b 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -86,6 +86,8 @@ public: void unloadHeavyPart() override; + QRect innerContentRect() const override; + private: enum class StateFlag { Over = (1 << 0), diff --git a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp index 9b15a6be8..b0858895b 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_inner.cpp @@ -270,7 +270,10 @@ void Inner::selectInlineResult( const auto document = item->getDocument() ? item->getDocument() : item->getPreviewDocument(); - if (options.scheduled || !document || !document->sticker()) { + if (options.scheduled + || item->isFullLine() + || !document + || (!document->sticker() && !document->isGifv())) { return {}; } const auto rect = item->innerContentRect().translated( @@ -278,6 +281,7 @@ void Inner::selectInlineResult( return { .localId = _controller->session().data().nextLocalMessageId(), .globalStartGeometry = mapToGlobal(rect), + .crop = document->isGifv(), }; }; diff --git a/Telegram/SourceFiles/ui/effects/message_sending_animation_common.h b/Telegram/SourceFiles/ui/effects/message_sending_animation_common.h index 7b0b4e53d..7f9e26429 100644 --- a/Telegram/SourceFiles/ui/effects/message_sending_animation_common.h +++ b/Telegram/SourceFiles/ui/effects/message_sending_animation_common.h @@ -12,6 +12,7 @@ namespace Ui { struct MessageSendingAnimationFrom { std::optional localId; QRect globalStartGeometry; + bool crop = false; }; } // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.cpp b/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.cpp index 398fdcfb0..7d1015626 100644 --- a/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.cpp +++ b/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.cpp @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/rp_widget.h" #include "window/window_session_controller.h" +#include "styles/style_chat.h" namespace Ui { namespace { @@ -36,7 +37,7 @@ public: Content( not_null parent, not_null controller, - QRect globalGeometryFrom, + const MessageSendingAnimationFrom &fromInfo, MessageSendingAnimationController::SendingInfoTo &&to); [[nodiscard]] rpl::producer<> destroyRequests() const; @@ -48,9 +49,12 @@ private: using Context = Ui::ChatPaintContext; void createSurrounding(); + void createBubble(); not_null view() const; + void drawContent(Painter &p, float64 progress) const; const not_null _controller; + const bool _crop; MessageSendingAnimationController::SendingInfoTo _toInfo; QRect _from; QPoint _to; @@ -59,6 +63,10 @@ private: Animations::Simple _animation; float64 _minScale = 0; + struct { + base::unique_qptr widget; + QPoint offsetFromContent; + } _bubble; base::unique_qptr _surrounding; rpl::event_stream<> _destroyRequests; @@ -68,12 +76,13 @@ private: Content::Content( not_null parent, not_null controller, - QRect globalGeometryFrom, + const MessageSendingAnimationFrom &fromInfo, MessageSendingAnimationController::SendingInfoTo &&to) : RpWidget(parent) , _controller(controller) +, _crop(fromInfo.crop) , _toInfo(std::move(to)) -, _from(parent->mapFromGlobal(globalGeometryFrom)) +, _from(parent->mapFromGlobal(fromInfo.globalStartGeometry)) , _innerContentRect(view()->media()->contentRectForReactions()) , _minScale(float64(_from.height()) / _innerContentRect.height()) { Expects(_toInfo.view != nullptr); @@ -111,14 +120,25 @@ Content::Content( moveToLeft(x, y); update(); - if ((value > kSurroundingProgress) && !_surrounding) { - createSurrounding(); + if ((value > kSurroundingProgress) + && !_surrounding + && !_bubble.widget) { + if (view()->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 = view(); @@ -140,12 +160,50 @@ not_null Content::view() const { } void Content::paintEvent(QPaintEvent *e) { - Painter p(this); - - p.fillRect(e->rect(), Qt::transparent); - 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); + } + + Painter p(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( @@ -211,6 +269,65 @@ void Content::createSurrounding() { }, _surrounding->lifetime()); } +void Content::createBubble() { + _bubble.widget = base::make_unique_q(parentWidget()); + _bubble.widget->setAttribute(Qt::WA_TransparentForMouseEvents); + + const auto currentView = view(); + 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 = view(); + + 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( @@ -224,7 +341,7 @@ void MessageSendingAnimationController::appendSending( return; } if (from.localId) { - _itemSendPending[*from.localId] = from.globalStartGeometry; + _itemSendPending[*from.localId] = std::move(from); } } diff --git a/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.h b/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.h index 541d3f6b2..8279b040a 100644 --- a/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.h +++ b/Telegram/SourceFiles/ui/effects/message_sending_animation_controller.h @@ -45,7 +45,7 @@ public: private: const not_null _controller; - base::flat_map _itemSendPending; + base::flat_map _itemSendPending; base::flat_map< not_null,