mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Initial implementation of ExtendedMedia.
This commit is contained in:
parent
2523d6e8d8
commit
20b5138e00
14 changed files with 659 additions and 77 deletions
|
@ -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
|
||||
|
|
|
@ -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<HistoryItem*> item,
|
||||
not_null<HistoryMessage*> 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<HistoryItem*> 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<Media> MediaInvoice::clone(not_null<HistoryItem*> parent) {
|
||||
|
@ -1537,6 +1576,16 @@ std::unique_ptr<HistoryView::Media> MediaInvoice::createView(
|
|||
not_null<HistoryView::Element*> message,
|
||||
not_null<HistoryItem*> realParent,
|
||||
HistoryView::Element *replacing) {
|
||||
if (_invoice.extendedMedia) {
|
||||
return _invoice.extendedMedia->createView(
|
||||
message,
|
||||
realParent,
|
||||
replacing);
|
||||
} else if (!_invoice.extendedPreview.dimensions.isEmpty()) {
|
||||
return std::make_unique<HistoryView::ExtendedPreview>(
|
||||
message,
|
||||
&_invoice);
|
||||
}
|
||||
return std::make_unique<HistoryView::Invoice>(message, &_invoice);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
class Image;
|
||||
class History;
|
||||
class HistoryItem;
|
||||
class HistoryMessage;
|
||||
|
||||
namespace base {
|
||||
template <typename Enum>
|
||||
|
@ -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<Media> extendedMedia;
|
||||
PhotoData *photo = nullptr;
|
||||
bool isTest = false;
|
||||
};
|
||||
|
@ -515,7 +526,7 @@ private:
|
|||
TextForMimeData &&caption);
|
||||
|
||||
[[nodiscard]] Invoice ComputeInvoiceData(
|
||||
not_null<HistoryItem*> item,
|
||||
not_null<HistoryMessage*> item,
|
||||
const MTPDmessageMediaInvoice &data);
|
||||
|
||||
[[nodiscard]] Call ComputeCallData(const MTPDmessageActionPhoneCall &call);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -229,10 +229,6 @@ struct Message::CommentsButton {
|
|||
QImage cachedUserpics;
|
||||
ClickHandlerPtr link;
|
||||
QPoint lastPoint;
|
||||
|
||||
QString rightActionCountString;
|
||||
int rightActionCount = 0;
|
||||
int rightActionCountWidth = 0;
|
||||
};
|
||||
|
||||
struct Message::FromNameStatus {
|
||||
|
|
|
@ -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<HistoryItem*> item) {
|
||||
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
const auto controller = my.sessionWindow.get();
|
||||
Payments::CheckoutProcess::Start(
|
||||
item,
|
||||
Payments::Mode::Payment,
|
||||
(controller
|
||||
? crl::guard(
|
||||
controller,
|
||||
[=](auto) { controller->widget()->activate(); })
|
||||
: Fn<void(Payments::CheckoutResult)>()));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ExtendedPreview::ExtendedPreview(
|
||||
not_null<Element*> parent,
|
||||
not_null<Data::Invoice*> 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<int>(radius);
|
||||
const auto intCorners = static_cast<int>(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
|
|
@ -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<Element*> parent,
|
||||
not_null<Data::Invoice*> 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<Data::Invoice*> _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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -116,7 +116,6 @@ private:
|
|||
|
||||
QSize countOptimalSize() override;
|
||||
QSize countCurrentSize(int newWidth) override;
|
||||
[[nodiscard]] QSize pixmapSizeFromData(int newWidth) const;
|
||||
|
||||
bool needInfoDisplay() const;
|
||||
void validateGroupedCache(
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue