From 20b5138e006bb9fce0329dcdd70e09570a9e7fed Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 8 Sep 2022 13:17:31 +0400 Subject: [PATCH] Initial implementation of ExtendedMedia. --- Telegram/CMakeLists.txt | 2 + .../SourceFiles/data/data_media_types.cpp | 55 ++- Telegram/SourceFiles/data/data_media_types.h | 13 +- .../SourceFiles/history/history_message.cpp | 5 + .../history/view/history_view_message.cpp | 4 - .../media/history_view_extended_preview.cpp | 403 ++++++++++++++++++ .../media/history_view_extended_preview.h | 102 +++++ .../history/view/media/history_view_file.cpp | 1 + .../history/view/media/history_view_gif.cpp | 19 +- .../view/media/history_view_media_common.cpp | 52 ++- .../view/media/history_view_media_common.h | 12 + .../history/view/media/history_view_photo.cpp | 63 +-- .../history/view/media/history_view_photo.h | 1 - .../media/audio/media_audio_ffmpeg_loader.cpp | 4 +- 14 files changed, 659 insertions(+), 77 deletions(-) create mode 100644 Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp create mode 100644 Telegram/SourceFiles/history/view/media/history_view_extended_preview.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 147042d46d..fb2bf370fb 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -627,6 +627,8 @@ PRIVATE history/view/media/history_view_dice.h history/view/media/history_view_document.cpp history/view/media/history_view_document.h + history/view/media/history_view_extended_preview.cpp + history/view/media/history_view_extended_preview.h history/view/media/history_view_file.cpp history/view/media/history_view_file.h history/view/media/history_view_game.cpp diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 121206723c..c633f11ede 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -9,9 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" +#include "history/history_message.h" // CreateMedia. #include "history/history_location_manager.h" #include "history/view/history_view_element.h" #include "history/view/history_view_item_preview.h" +#include "history/view/media/history_view_extended_preview.h" #include "history/view/media/history_view_photo.h" #include "history/view/media/history_view_sticker.h" #include "history/view/media/history_view_gif.h" @@ -245,10 +247,10 @@ TextForMimeData WithCaptionClipboardText( } Invoice ComputeInvoiceData( - not_null item, + not_null item, const MTPDmessageMediaInvoice &data) { auto description = qs(data.vdescription()); - return { + auto result = Invoice{ .receiptMsgId = data.vreceipt_msg_id().value_or_empty(), .amount = data.vtotal_amount().v, .currency = qs(data.vcurrency()), @@ -263,6 +265,31 @@ Invoice ComputeInvoiceData( : nullptr), .isTest = data.is_test(), }; + if (const auto &media = data.vextended_media()) { + media->match([&](const MTPDmessageExtendedMediaPreview &data) { + auto &preview = result.extendedPreview; + if (const auto &w = data.vw()) { + const auto &h = data.vh(); + Assert(h.has_value()); + preview.dimensions = QSize(w->v, h->v); + } + if (const auto &thumb = data.vthumb()) { + if (thumb->type() == mtpc_photoStrippedSize) { + preview.inlineThumbnailBytes + = thumb->c_photoStrippedSize().vbytes().v; + } + } + if (const auto &duration = data.vvideo_duration()) { + preview.videoDuration = duration->v; + } + }, [&](const MTPDmessageExtendedMedia &data) { + result.extendedMedia = HistoryMessage::CreateMedia( + item, + data.vmedia()); + }); + } + + return result; } Call ComputeCallData(const MTPDmessageActionPhoneCall &call) { @@ -1479,7 +1506,19 @@ MediaInvoice::MediaInvoice( not_null parent, const Invoice &data) : Media(parent) -, _invoice(data) { +, _invoice{ + .receiptMsgId = data.receiptMsgId, + .amount = data.amount, + .currency = data.currency, + .title = data.title, + .description = data.description, + .extendedPreview = data.extendedPreview, + .extendedMedia = (data.extendedMedia + ? data.extendedMedia->clone(parent) + : nullptr), + .photo = data.photo, + .isTest = data.isTest, +} { } std::unique_ptr MediaInvoice::clone(not_null parent) { @@ -1537,6 +1576,16 @@ std::unique_ptr MediaInvoice::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { + if (_invoice.extendedMedia) { + return _invoice.extendedMedia->createView( + message, + realParent, + replacing); + } else if (!_invoice.extendedPreview.dimensions.isEmpty()) { + return std::make_unique( + message, + &_invoice); + } return std::make_unique(message, &_invoice); } diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index c1925bba9c..36385e8c4c 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class Image; class History; class HistoryItem; +class HistoryMessage; namespace base { template @@ -58,12 +59,22 @@ struct Call { bool video = false; }; +struct ExtendedPreview { + QByteArray inlineThumbnailBytes; + QSize dimensions; + TimeId videoDuration = -1; +}; + +class Media; + struct Invoice { MsgId receiptMsgId = 0; uint64 amount = 0; QString currency; QString title; TextWithEntities description; + ExtendedPreview extendedPreview; + std::unique_ptr extendedMedia; PhotoData *photo = nullptr; bool isTest = false; }; @@ -515,7 +526,7 @@ private: TextForMimeData &&caption); [[nodiscard]] Invoice ComputeInvoiceData( - not_null item, + not_null item, const MTPDmessageMediaInvoice &data); [[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 4c49d9b40a..4ff5c321ab 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1063,6 +1063,11 @@ void HistoryMessage::setupForwardedComponent(const CreateConfig &config) { void HistoryMessage::refreshMedia(const MTPMessageMedia *media) { const auto was = (_media != nullptr); + if (const auto invoice = was ? _media->invoice() : nullptr) { + if (invoice->extendedMedia) { + return; + } + } _media = nullptr; if (media) { setMedia(*media); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 5e114e56f7..466cca074a 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -229,10 +229,6 @@ struct Message::CommentsButton { QImage cachedUserpics; ClickHandlerPtr link; QPoint lastPoint; - - QString rightActionCountString; - int rightActionCount = 0; - int rightActionCountWidth = 0; }; struct Message::FromNameStatus { diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp new file mode 100644 index 0000000000..21d3c510d6 --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -0,0 +1,403 @@ +/* +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 "history/view/media/history_view_extended_preview.h" + +//#include "history/history_item_components.h" +#include "history/history_item.h" +#include "history/history.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_cursor_state.h" +#include "history/view/media/history_view_media_common.h" +//#include "main/main_session.h" +//#include "main/main_session_settings.h" +#include "media/streaming/media_streaming_utility.h" +#include "ui/effects/spoiler_mess.h" +#include "ui/image/image.h" +#include "ui/chat/chat_style.h" +//#include "ui/cached_round_corners.h" +#include "data/data_session.h" +//#include "data/data_streaming.h" +//#include "data/data_photo.h" +//#include "data/data_photo_media.h" +//#include "data/data_file_click_handler.h" +//#include "data/data_file_origin.h" +//#include "data/data_auto_download.h" +//#include "core/application.h" +#include "payments/payments_checkout_process.h" +#include "window/window_session_controller.h" +#include "mainwindow.h" +#include "core/click_handler_types.h" +#include "styles/style_chat.h" + +namespace HistoryView { +namespace { + +[[nodiscard]] ClickHandlerPtr MakeInvoiceLink(not_null item) { + return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + const auto controller = my.sessionWindow.get(); + Payments::CheckoutProcess::Start( + item, + Payments::Mode::Payment, + (controller + ? crl::guard( + controller, + [=](auto) { controller->widget()->activate(); }) + : Fn())); + }); +} + +} // namespace + +ExtendedPreview::ExtendedPreview( + not_null parent, + not_null invoice) +: Media(parent) +, _invoice(invoice) +, _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) { + const auto item = parent->data(); + _caption = createCaption(item); + _link = MakeInvoiceLink(item); +} + +ExtendedPreview::~ExtendedPreview() = default; + +void ExtendedPreview::ensureThumbnailRead() const { + if (!_inlineThumbnail.isNull() || _imageCacheInvalid) { + return; + } + const auto &bytes = _invoice->extendedPreview.inlineThumbnailBytes; + if (bytes.isEmpty()) { + return; + } + _inlineThumbnail = Images::FromInlineBytes(bytes); + if (_inlineThumbnail.isNull()) { + _imageCacheInvalid = 1; + } else { + history()->owner().registerHeavyViewPart(_parent); + } +} + +bool ExtendedPreview::hasHeavyPart() const { + return !_inlineThumbnail.isNull(); +} + +void ExtendedPreview::unloadHeavyPart() { + _inlineThumbnail = _imageCache = QImage(); + _caption.unloadCustomEmoji(); +} + +QSize ExtendedPreview::countOptimalSize() { + if (_parent->media() != this) { + _caption = Ui::Text::String(); + } else if (_caption.hasSkipBlock()) { + _caption.updateSkipBlock( + _parent->skipBlockWidth(), + _parent->skipBlockHeight()); + } + const auto &preview = _invoice->extendedPreview; + const auto dimensions = preview.dimensions; + const auto minWidth = std::clamp( + _parent->minWidthForMedia(), + (_parent->hasBubble() + ? st::historyPhotoBubbleMinWidth + : st::minPhotoSize), + st::maxMediaSize); + const auto scaled = CountDesiredMediaSize(dimensions); + auto maxWidth = qMax(scaled.width(), minWidth); + auto minHeight = qMax(scaled.height(), st::minPhotoSize); + if (preview.videoDuration < 0) { + accumulate_max(maxWidth, scaled.height()); + } + if (_parent->hasBubble() && !_caption.isEmpty()) { + maxWidth = qMax(maxWidth, st::msgPadding.left() + + _caption.maxWidth() + + st::msgPadding.right()); + minHeight += st::mediaCaptionSkip + _caption.minHeight(); + if (isBubbleBottom()) { + minHeight += st::msgPadding.bottom(); + } + } + return { maxWidth, minHeight }; +} + +QSize ExtendedPreview::countCurrentSize(int newWidth) { + const auto &preview = _invoice->extendedPreview; + const auto dimensions = preview.dimensions; + const auto minWidth = std::clamp( + _parent->minWidthForMedia(), + (_parent->hasBubble() + ? st::historyPhotoBubbleMinWidth + : st::minPhotoSize), + std::min(newWidth, st::maxMediaSize)); + const auto scaled = (preview.videoDuration >= 0) + ? CountMediaSize( + CountDesiredMediaSize(dimensions), + newWidth) + : CountPhotoMediaSize( + CountDesiredMediaSize(dimensions), + newWidth, + maxWidth()); + newWidth = qMax(scaled.width(), minWidth); + auto newHeight = qMax(scaled.height(), st::minPhotoSize); + if (_parent->hasBubble() && !_caption.isEmpty()) { + const auto maxWithCaption = qMin( + st::msgMaxWidth, + (st::msgPadding.left() + + _caption.maxWidth() + + st::msgPadding.right())); + newWidth = qMax(newWidth, maxWithCaption); + const auto captionw = newWidth + - st::msgPadding.left() + - st::msgPadding.right(); + newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + newHeight += st::msgPadding.bottom(); + } + } + return { newWidth, newHeight }; +} + +void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + + const auto st = context.st; + const auto sti = context.imageStyle(); + const auto stm = context.messageStyle(); + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + auto bubble = _parent->hasBubble(); + auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right(); + auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); + if (bubble) { + if (!_caption.isEmpty()) { + painth -= st::mediaCaptionSkip + _caption.countHeight(captionw); + if (isBubbleBottom()) { + painth -= st::msgPadding.bottom(); + } + rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); + } + } else { + Ui::FillRoundShadow(p, 0, 0, paintw, painth, sti->msgShadow, sti->msgShadowCorners); + } + const auto inWebPage = (_parent->media() != this); + const auto roundRadius = inWebPage + ? ImageRoundRadius::Small + : ImageRoundRadius::Large; + const auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None) + | ((isRoundedInBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None)); + validateImageCache(rthumb.size(), roundRadius, roundCorners); + p.drawImage(rthumb.topLeft(), _imageCache); + fillSpoilerMess(p, rthumb, roundRadius, roundCorners); + if (context.selected()) { + Ui::FillComplexOverlayRect(p, st, rthumb, roundRadius, roundCorners); + } + + const auto innerSize = st::msgFileLayout.thumbSize; + QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize); + p.setPen(Qt::NoPen); + if (context.selected()) { + p.setBrush(st->msgDateImgBgSelected()); + } else { + const auto over = ClickHandler::showAsActive(_link); + p.setBrush(over ? st->msgDateImgBgOver() : st->msgDateImgBg()); + } + { + PainterHighQualityEnabler hq(p); + p.drawEllipse(inner); + } + + // date + if (!_caption.isEmpty()) { + p.setPen(stm->historyTextFg); + _parent->prepareCustomEmojiPaint(p, _caption); + _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, context.selection); + } else if (!inWebPage) { + auto fullRight = paintx + paintw; + auto fullBottom = painty + painth; + if (needInfoDisplay()) { + _parent->drawInfo( + p, + context, + fullRight, + fullBottom, + 2 * paintx + paintw, + InfoDisplayType::Image); + } + if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); + _parent->drawRightAction(p, context, fastShareLeft, fastShareTop, 2 * paintx + paintw); + } + } +} + +void ExtendedPreview::validateImageCache( + QSize outer, + ImageRoundRadius radius, + RectParts corners) const { + const auto intRadius = static_cast(radius); + const auto intCorners = static_cast(corners); + const auto ratio = style::DevicePixelRatio(); + if (_imageCache.size() == (outer * ratio) + && _imageCacheRoundRadius == intRadius + && _imageCacheRoundCorners == intCorners) { + return; + } + _imageCache = prepareImageCache(outer, radius, corners); + _imageCacheRoundRadius = intRadius; + _imageCacheRoundCorners = intCorners; +} + +QImage ExtendedPreview::prepareImageCache( + QSize outer, + ImageRoundRadius radius, + RectParts corners) const { + return Images::Round(prepareImageCache(outer), radius, corners); +} + +QImage ExtendedPreview::prepareImageCache(QSize outer) const { + ensureThumbnailRead(); + return PrepareWithBlurredBackground(outer, {}, {}, _inlineThumbnail); +} + +void ExtendedPreview::fillSpoilerMess( + QPainter &p, + QRect rect, + ImageRoundRadius radius, + RectParts corners) const { + const auto size = style::ConvertScale(100); + static const auto test = [&] { + const auto ratio = style::DevicePixelRatio(); + return Ui::GenerateSpoilerMess({ + .particleFadeInDuration = 200, + .particleFadeOutDuration = 200, + .particleSizeMin = style::ConvertScaleExact(1.5) * ratio, + .particleSizeMax = style::ConvertScaleExact(2.) * ratio, + .particleSpritesCount = 5, + .particlesCount = 2000, + .canvasSize = size * ratio, + .framesCount = 60, + .frameDuration = 33, + }); + }(); + const auto frame = test.frame(); + const auto columns = (rect.width() + size - 1) / size; + const auto rows = (rect.height() + size - 1) / size; + p.setClipRect(rect); + p.translate(rect.topLeft()); + for (auto y = 0; y != rows; ++y) { + for (auto x = 0; x != columns; ++x) { + p.drawImage( + QRect(x * size, y * size, size, size), + *frame.image, + frame.source); + } + } + p.translate(-rect.topLeft()); + p.setClipping(false); +} + +TextState ExtendedPreview::textState(QPoint point, StateRequest request) const { + auto result = TextState(_parent); + + if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) { + return result; + } + auto paintx = 0, painty = 0, paintw = width(), painth = height(); + auto bubble = _parent->hasBubble(); + + if (bubble && !_caption.isEmpty()) { + const auto captionw = paintw + - st::msgPadding.left() + - st::msgPadding.right(); + painth -= _caption.countHeight(captionw); + if (isBubbleBottom()) { + painth -= st::msgPadding.bottom(); + } + if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) { + result = TextState(_parent, _caption.getState( + point - QPoint(st::msgPadding.left(), painth), + captionw, + request.forText())); + return result; + } + painth -= st::mediaCaptionSkip; + } + if (QRect(paintx, painty, paintw, painth).contains(point)) { + result.link = _link; + } + if (_caption.isEmpty() && _parent->media() == this) { + auto fullRight = paintx + paintw; + auto fullBottom = painty + painth; + const auto bottomInfoResult = _parent->bottomInfoTextState( + fullRight, + fullBottom, + point, + InfoDisplayType::Image); + if (bottomInfoResult.link + || bottomInfoResult.cursor != CursorState::None) { + return bottomInfoResult; + } + if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) { + auto fastShareLeft = (fullRight + st::historyFastShareLeft); + auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height()); + if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) { + result.link = _parent->rightActionLink(); + } + } + } + return result; +} + +bool ExtendedPreview::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const { + return p == _link; +} + +bool ExtendedPreview::dragItemByHandler(const ClickHandlerPtr &p) const { + return p == _link; +} + +bool ExtendedPreview::needInfoDisplay() const { + return _parent->data()->isSending() + || _parent->data()->hasFailed() + || _parent->isUnderCursor() + || _parent->isLastAndSelfMessage(); +} + +TextForMimeData ExtendedPreview::selectedText(TextSelection selection) const { + return _caption.toTextForMimeData(selection); +} + +bool ExtendedPreview::needsBubble() const { + if (!_caption.isEmpty()) { + return true; + } + const auto item = _parent->data(); + return !item->isService() + && (item->repliesAreComments() + || item->externalReply() + || item->viaBot() + || _parent->displayedReply() + || _parent->displayForwardedFrom() + || _parent->displayFromName()); +} + +QPoint ExtendedPreview::resolveCustomInfoRightBottom() const { + const auto skipx = (st::msgDateImgDelta + st::msgDateImgPadding.x()); + const auto skipy = (st::msgDateImgDelta + st::msgDateImgPadding.y()); + return QPoint(width() - skipx, height() - skipy); +} + +void ExtendedPreview::parentTextUpdated() { + _caption = (_parent->media() == this) + ? createCaption(_parent->data()) + : Ui::Text::String(); + history()->owner().requestViewResize(_parent); +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h new file mode 100644 index 0000000000..396bc1378c --- /dev/null +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.h @@ -0,0 +1,102 @@ +/* +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 +*/ +#pragma once + +#include "history/view/media/history_view_media.h" + +enum class ImageRoundRadius; + +namespace Data { +struct Invoice; +} // namespace Data + +namespace HistoryView { + +class Element; + +class ExtendedPreview final : public Media { +public: + ExtendedPreview( + not_null parent, + not_null invoice); + ~ExtendedPreview(); + + void draw(Painter &p, const PaintContext &context) const override; + TextState textState(QPoint point, StateRequest request) const override; + + [[nodiscard]] bool toggleSelectionByHandlerClick( + const ClickHandlerPtr &p) const override; + [[nodiscard]] bool dragItemByHandler( + const ClickHandlerPtr &p) const override; + + [[nodiscard]] TextSelection adjustSelection( + TextSelection selection, + TextSelectType type) const override { + return _caption.adjustSelection(selection, type); + } + uint16 fullSelectionLength() const override { + return _caption.length(); + } + bool hasTextForCopy() const override { + return !_caption.isEmpty(); + } + + TextForMimeData selectedText(TextSelection selection) const override; + + TextWithEntities getCaption() const override { + return _caption.toTextWithEntities(); + } + bool needsBubble() const override; + bool customInfoLayout() const override { + return _caption.isEmpty(); + } + QPoint resolveCustomInfoRightBottom() const override; + bool skipBubbleTail() const override { + return isRoundedInBubbleBottom() && _caption.isEmpty(); + } + + void parentTextUpdated() override; + + bool hasHeavyPart() const override; + void unloadHeavyPart() override; + +private: + void ensureThumbnailRead() const; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + + bool needInfoDisplay() const; + void validateImageCache( + QSize outer, + ImageRoundRadius radius, + RectParts corners) const; + [[nodiscard]] QImage prepareImageCache( + QSize outer, + ImageRoundRadius radius, + RectParts corners) const; + [[nodiscard]] QImage prepareImageCache(QSize outer) const; + + void fillSpoilerMess( + QPainter &p, + QRect rect, + ImageRoundRadius radius, + RectParts corners) const; + + const not_null _invoice; + ClickHandlerPtr _link; + Ui::Text::String _caption; + mutable QImage _inlineThumbnail; + mutable QImage _imageCache; + mutable int _imageCacheRoundRadius : 4 = 0; + mutable int _imageCacheRoundCorners : 12 = 0; + mutable int _imageCacheInvalid : 1 = 0; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_file.cpp b/Telegram/SourceFiles/history/view/media/history_view_file.cpp index 149e734ca6..8b5b851c57 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_file.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_file.cpp @@ -22,6 +22,7 @@ namespace HistoryView { bool File::toggleSelectionByHandlerClick(const ClickHandlerPtr &p) const { return p == _openl || p == _savel || p == _cancell; } + bool File::dragItemByHandler(const ClickHandlerPtr &p) const { return p == _openl || p == _savel || p == _cancell; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 7401356b33..e79745a629 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -131,10 +131,9 @@ QSize Gif::countThumbSize(int &inOutWidthMax) const { : _data->isVideoMessage() ? st::maxVideoMessageSize : st::maxGifSize; - const auto useMaxSize = std::max(maxSize, st::minPhotoSize); const auto size = style::ConvertScale(videoSize()); - accumulate_min(inOutWidthMax, useMaxSize); - return DownscaledSize(size, { inOutWidthMax, useMaxSize }); + accumulate_min(inOutWidthMax, maxSize); + return DownscaledSize(size, { inOutWidthMax, maxSize }); } QSize Gif::countOptimalSize() { @@ -146,20 +145,22 @@ QSize Gif::countOptimalSize() { _parent->skipBlockHeight()); } + const auto minWidth = std::clamp( + _parent->minWidthForMedia(), + (_parent->hasBubble() + ? st::historyPhotoBubbleMinWidth + : st::minPhotoSize), + st::maxMediaSize); auto thumbMaxWidth = st::msgMaxWidth; const auto scaled = countThumbSize(thumbMaxWidth); - const auto minWidthByInfo = _parent->infoWidth() - + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x()); - auto maxWidth = std::clamp( - std::max(scaled.width(), minWidthByInfo), - st::minPhotoSize, + auto maxWidth = std::min( + std::max(scaled.width(), minWidth), thumbMaxWidth); auto minHeight = qMax(scaled.height(), st::minPhotoSize); if (!activeCurrentStreamed()) { accumulate_max(maxWidth, gifMaxStatusWidth(_data) + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); } if (_parent->hasBubble()) { - accumulate_max(maxWidth, _parent->minWidthForMedia()); if (!_caption.isEmpty()) { maxWidth = qMax(maxWidth, st::msgPadding.left() + _caption.maxWidth() diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp index e6b6f24483..019f10ebfa 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.cpp @@ -98,9 +98,21 @@ QImage PrepareWithBlurredBackground( ::Media::Streaming::ExpandDecision resize, Image *large, Image *blurred) { + return PrepareWithBlurredBackground( + outer, + resize, + large ? large->original() : QImage(), + blurred ? blurred->original() : QImage()); +} + +QImage PrepareWithBlurredBackground( + QSize outer, + ::Media::Streaming::ExpandDecision resize, + QImage large, + QImage blurred) { const auto ratio = style::DevicePixelRatio(); if (resize.expanding) { - return Images::Prepare(large->original(), resize.result * ratio, { + return Images::Prepare(std::move(large), resize.result * ratio, { .outer = outer, }); } @@ -108,19 +120,19 @@ QImage PrepareWithBlurredBackground( outer * ratio, QImage::Format_ARGB32_Premultiplied); background.setDevicePixelRatio(ratio); - if (!blurred) { + if (blurred.isNull()) { background.fill(Qt::black); - if (!large) { + if (large.isNull()) { return background; } } auto p = QPainter(&background); - if (blurred) { + if (!blurred.isNull()) { using namespace ::Media::Streaming; - FillBlurredBackground(p, outer, blurred->original()); + FillBlurredBackground(p, outer, std::move(blurred)); } - if (large) { - auto image = large->original().scaled( + if (!large.isNull()) { + auto image = large.scaled( resize.result * ratio, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); @@ -134,4 +146,30 @@ QImage PrepareWithBlurredBackground( return background; } +QSize CountDesiredMediaSize(QSize original) { + return DownscaledSize( + style::ConvertScale(original), + { st::maxMediaSize, st::maxMediaSize }); +} + +QSize CountMediaSize(QSize desired, int newWidth) { + Expects(!desired.isEmpty()); + + return (desired.width() <= newWidth) + ? desired + : NonEmptySize( + desired.scaled(newWidth, desired.height(), Qt::KeepAspectRatio)); +} + +QSize CountPhotoMediaSize( + QSize desired, + int newWidth, + int maxWidth) { + const auto media = CountMediaSize(desired, qMin(newWidth, maxWidth)); + return (media.height() <= newWidth) + ? media + : NonEmptySize( + media.scaled(media.width(), newWidth, Qt::KeepAspectRatio)); +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_common.h b/Telegram/SourceFiles/history/view/media/history_view_media_common.h index 5c9a831738..0a880a131f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_common.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_common.h @@ -62,5 +62,17 @@ void PaintInterpolatedIcon( ::Media::Streaming::ExpandDecision resize, Image *large, Image *blurred); +[[nodiscard]] QImage PrepareWithBlurredBackground( + QSize outer, + ::Media::Streaming::ExpandDecision resize, + QImage large, + QImage blurred); + +[[nodiscard]] QSize CountDesiredMediaSize(QSize original); +[[nodiscard]] QSize CountMediaSize(QSize desired, int newWidth); +[[nodiscard]] QSize CountPhotoMediaSize( + QSize desired, + int newWidth, + int maxWidth); } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 3b90607747..093b5be905 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -139,6 +139,10 @@ void Photo::unloadHeavyPart() { } QSize Photo::countOptimalSize() { + if (_serviceWidth > 0) { + return { _serviceWidth, _serviceWidth }; + } + if (_parent->media() != this) { _caption = Ui::Text::String(); } else if (_caption.hasSkipBlock()) { @@ -147,33 +151,15 @@ QSize Photo::countOptimalSize() { _parent->skipBlockHeight()); } - auto maxWidth = 0; - auto minHeight = 0; - - auto tw = style::ConvertScale(_data->width()); - auto th = style::ConvertScale(_data->height()); - if (!tw || !th) { - tw = th = 1; - } - if (tw > st::maxMediaSize) { - th = (st::maxMediaSize * th) / tw; - tw = st::maxMediaSize; - } - if (th > st::maxMediaSize) { - tw = (st::maxMediaSize * tw) / th; - th = st::maxMediaSize; - } - - if (_serviceWidth > 0) { - return { _serviceWidth, _serviceWidth }; - } + const auto scaled = CountDesiredMediaSize( + { _data->width(), _data->height() }); const auto minWidth = std::clamp( _parent->minWidthForMedia(), (_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize), st::maxMediaSize); - const auto maxActualWidth = qMax(tw, minWidth); - maxWidth = qMax(maxActualWidth, th); - minHeight = qMax(th, st::minPhotoSize); + const auto maxActualWidth = qMax(scaled.width(), minWidth); + auto maxWidth = qMax(maxActualWidth, scaled.height()); + auto minHeight = qMax(scaled.height(), st::minPhotoSize); if (_parent->hasBubble() && !_caption.isEmpty()) { maxWidth = qMax(maxWidth, st::msgPadding.left() + _caption.maxWidth() @@ -186,32 +172,6 @@ QSize Photo::countOptimalSize() { return { maxWidth, minHeight }; } -QSize Photo::pixmapSizeFromData(int newWidth) const { - auto tw = style::ConvertScale(_data->width()); - auto th = style::ConvertScale(_data->height()); - if (tw > st::maxMediaSize) { - th = (st::maxMediaSize * th) / tw; - tw = st::maxMediaSize; - } - if (th > st::maxMediaSize) { - tw = (st::maxMediaSize * tw) / th; - th = st::maxMediaSize; - } - - auto pixw = qMin(newWidth, maxWidth()); - auto pixh = th; - if (tw > pixw) { - pixh = (pixw * pixh / tw); - } else { - pixw = tw; - } - if (pixh > newWidth) { - pixw = (pixw * newWidth) / pixh; - pixh = newWidth; - } - return { pixw, pixh }; -} - QSize Photo::countCurrentSize(int newWidth) { if (_serviceWidth) { return { _serviceWidth, _serviceWidth }; @@ -220,7 +180,10 @@ QSize Photo::countCurrentSize(int newWidth) { _parent->minWidthForMedia(), (_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize), std::min(newWidth, st::maxMediaSize)); - auto pix = pixmapSizeFromData(newWidth); + auto pix = CountPhotoMediaSize( + CountDesiredMediaSize({ _data->width(), _data->height() }), + newWidth, + maxWidth()); newWidth = qMax(pix.width(), minWidth); auto newHeight = qMax(pix.height(), st::minPhotoSize); if (_parent->hasBubble() && !_caption.isEmpty()) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index c0cc034611..ec07458a39 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -116,7 +116,6 @@ private: QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; - [[nodiscard]] QSize pixmapSizeFromData(int newWidth) const; bool needInfoDisplay() const; void validateGroupedCache( diff --git a/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp b/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp index f79027ccdb..7788421ab7 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_ffmpeg_loader.cpp @@ -590,8 +590,8 @@ bool FFMpegLoader::seekTo(crl::time positionMs) { } AudioPlayerLoader::ReadResult FFMpegLoader::readMore( - QByteArray & result, - int64 & samplesAdded) { + QByteArray &result, + int64 &samplesAdded) { const auto readResult = readFromReadyContext( _codecContext, result,