Support spoilers in reply previews / pinned bar.

This commit is contained in:
John Preston 2022-12-30 13:37:34 +04:00
parent 46bae9ed74
commit d02819db13
17 changed files with 280 additions and 101 deletions

View file

@ -1198,26 +1198,29 @@ bool DocumentData::isStickerSetInstalled() const {
Image *DocumentData::getReplyPreview( Image *DocumentData::getReplyPreview(
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<PeerData*> context) { not_null<PeerData*> context,
bool spoiler) {
if (!hasThumbnail()) { if (!hasThumbnail()) {
return nullptr; return nullptr;
} else if (!_replyPreview) { } else if (!_replyPreview) {
_replyPreview = std::make_unique<Data::ReplyPreview>(this); _replyPreview = std::make_unique<Data::ReplyPreview>(this);
} }
return _replyPreview->image(origin, context); return _replyPreview->image(origin, context, spoiler);
} }
Image *DocumentData::getReplyPreview(not_null<HistoryItem*> item) { Image *DocumentData::getReplyPreview(not_null<HistoryItem*> item) {
return getReplyPreview(item->fullId(), item->history()->peer); const auto media = item->media();
const auto spoiler = media && media->hasSpoiler();
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
} }
bool DocumentData::replyPreviewLoaded() const { bool DocumentData::replyPreviewLoaded(bool spoiler) const {
if (!hasThumbnail()) { if (!hasThumbnail()) {
return true; return true;
} else if (!_replyPreview) { } else if (!_replyPreview) {
return false; return false;
} }
return _replyPreview->loaded(); return _replyPreview->loaded(spoiler);
} }
StickerData *DocumentData::sticker() const { StickerData *DocumentData::sticker() const {

View file

@ -142,9 +142,10 @@ public:
[[nodiscard]] Image *getReplyPreview( [[nodiscard]] Image *getReplyPreview(
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<PeerData*> context); not_null<PeerData*> context,
bool spoiler);
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item); [[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
[[nodiscard]] bool replyPreviewLoaded() const; [[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
[[nodiscard]] StickerData *sticker() const; [[nodiscard]] StickerData *sticker() const;
[[nodiscard]] Data::FileOrigin stickerSetOrigin() const; [[nodiscard]] Data::FileOrigin stickerSetOrigin() const;

View file

@ -618,7 +618,7 @@ Image *MediaPhoto::replyPreview() const {
} }
bool MediaPhoto::replyPreviewLoaded() const { bool MediaPhoto::replyPreviewLoaded() const {
return _photo->replyPreviewLoaded(); return _photo->replyPreviewLoaded(_spoiler);
} }
TextWithEntities MediaPhoto::notificationText() const { TextWithEntities MediaPhoto::notificationText() const {
@ -854,7 +854,7 @@ Image *MediaFile::replyPreview() const {
} }
bool MediaFile::replyPreviewLoaded() const { bool MediaFile::replyPreviewLoaded() const {
return _document->replyPreviewLoaded(); return _document->replyPreviewLoaded(_spoiler);
} }
ItemPreview MediaFile::toPreview(ToPreviewOptions options) const { ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
@ -1479,10 +1479,11 @@ Image *MediaWebPage::replyPreview() const {
} }
bool MediaWebPage::replyPreviewLoaded() const { bool MediaWebPage::replyPreviewLoaded() const {
const auto spoiler = false;
if (const auto document = MediaWebPage::document()) { if (const auto document = MediaWebPage::document()) {
return document->replyPreviewLoaded(); return document->replyPreviewLoaded(spoiler);
} else if (const auto photo = MediaWebPage::photo()) { } else if (const auto photo = MediaWebPage::photo()) {
return photo->replyPreviewLoaded(); return photo->replyPreviewLoaded(spoiler);
} }
return true; return true;
} }
@ -1552,10 +1553,11 @@ Image *MediaGame::replyPreview() const {
} }
bool MediaGame::replyPreviewLoaded() const { bool MediaGame::replyPreviewLoaded() const {
const auto spoiler = false;
if (const auto document = _game->document) { if (const auto document = _game->document) {
return document->replyPreviewLoaded(); return document->replyPreviewLoaded(spoiler);
} else if (const auto photo = _game->photo) { } else if (const auto photo = _game->photo) {
return photo->replyPreviewLoaded(); return photo->replyPreviewLoaded(spoiler);
} }
return true; return true;
} }
@ -1675,8 +1677,9 @@ Image *MediaInvoice::replyPreview() const {
} }
bool MediaInvoice::replyPreviewLoaded() const { bool MediaInvoice::replyPreviewLoaded() const {
const auto spoiler = false;
if (const auto photo = _invoice.photo) { if (const auto photo = _invoice.photo) {
return photo->replyPreviewLoaded(); return photo->replyPreviewLoaded(spoiler);
} }
return true; return true;
} }

View file

@ -209,22 +209,25 @@ bool PhotoData::uploading() const {
Image *PhotoData::getReplyPreview( Image *PhotoData::getReplyPreview(
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<PeerData*> context) { not_null<PeerData*> context,
bool spoiler) {
if (!_replyPreview) { if (!_replyPreview) {
_replyPreview = std::make_unique<Data::ReplyPreview>(this); _replyPreview = std::make_unique<Data::ReplyPreview>(this);
} }
return _replyPreview->image(origin, context); return _replyPreview->image(origin, context, spoiler);
} }
Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) { Image *PhotoData::getReplyPreview(not_null<HistoryItem*> item) {
return getReplyPreview(item->fullId(), item->history()->peer); const auto media = item->media();
const auto spoiler = media && media->hasSpoiler();
return getReplyPreview(item->fullId(), item->history()->peer, spoiler);
} }
bool PhotoData::replyPreviewLoaded() const { bool PhotoData::replyPreviewLoaded(bool spoiler) const {
if (!_replyPreview) { if (!_replyPreview) {
return false; return false;
} }
return _replyPreview->loaded(); return _replyPreview->loaded(spoiler);
} }
void PhotoData::setRemoteLocation( void PhotoData::setRemoteLocation(

View file

@ -66,9 +66,10 @@ public:
[[nodiscard]] Image *getReplyPreview( [[nodiscard]] Image *getReplyPreview(
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<PeerData*> context); not_null<PeerData*> context,
bool spoiler);
[[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item); [[nodiscard]] Image *getReplyPreview(not_null<HistoryItem*> item);
[[nodiscard]] bool replyPreviewLoaded() const; [[nodiscard]] bool replyPreviewLoaded(bool spoiler) const;
void setRemoteLocation( void setRemoteLocation(
int32 dc, int32 dc,

View file

@ -27,7 +27,11 @@ ReplyPreview::ReplyPreview(not_null<PhotoData*> photo)
ReplyPreview::~ReplyPreview() = default; ReplyPreview::~ReplyPreview() = default;
void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) { void ReplyPreview::prepare(
not_null<Image*> image,
Images::Options options,
bool spoiler) {
using namespace Images;
if (image->isNull()) { if (image->isNull()) {
return; return;
} }
@ -41,24 +45,34 @@ void ReplyPreview::prepare(not_null<Image*> image, Images::Options options) {
: QSize( : QSize(
st::msgReplyBarSize.height(), st::msgReplyBarSize.height(),
h * st::msgReplyBarSize.height() / w); h * st::msgReplyBarSize.height() / w);
thumbSize *= cIntRetinaFactor(); thumbSize *= style::DevicePixelRatio();
options |= Images::Option::TransparentBackground; options |= Option::TransparentBackground;
auto outerSize = st::msgReplyBarSize.height(); auto outerSize = st::msgReplyBarSize.height();
auto bitmap = image->pixNoCache( auto original = spoiler
thumbSize, ? image->original().scaled(
{ .options = options, .outer = { outerSize, outerSize } }); { 40, 40 },
_image = std::make_unique<Image>(bitmap.toImage()); Qt::KeepAspectRatio,
_good = ((options & Images::Option::Blur) == 0); Qt::SmoothTransformation)
: image->original();
auto prepared = Prepare(std::move(original), thumbSize, {
.options = options | (spoiler ? Option::Blur : Option()),
.outer = { outerSize, outerSize },
});
(spoiler ? _spoilered : _regular) = std::make_unique<Image>(
std::move(prepared));
_good = spoiler || ((options & Option::Blur) == 0);
} }
Image *ReplyPreview::image( Image *ReplyPreview::image(
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<PeerData*> context) { not_null<PeerData*> context,
if (_checked) { bool spoiler) {
return _image.get(); auto &image = spoiler ? _spoilered : _regular;
} auto &checked = spoiler ? _checkedSpoilered : _checkedRegular;
if (_document) { if (checked) {
if (!_image || (!_good && _document->hasThumbnail())) { return image.get();
} else if (_document) {
if (!image || (!_good && _document->hasThumbnail())) {
if (!_documentMedia) { if (!_documentMedia) {
_documentMedia = _document->createMediaView(); _documentMedia = _document->createMediaView();
_documentMedia->thumbnailWanted(origin); _documentMedia->thumbnailWanted(origin);
@ -67,51 +81,67 @@ Image *ReplyPreview::image(
const auto option = _document->isVideoMessage() const auto option = _document->isVideoMessage()
? Images::Option::RoundCircle ? Images::Option::RoundCircle
: Images::Option::None; : Images::Option::None;
if (thumbnail) { if (spoiler) {
if (const auto image = _documentMedia->thumbnailInline()) {
prepare(image, option, true);
} else if (thumbnail) {
prepare(thumbnail, option, true);
}
} else if (thumbnail) {
prepare(thumbnail, option); prepare(thumbnail, option);
} else if (!_image) { } else if (!image) {
if (const auto image = _documentMedia->thumbnailInline()) { if (const auto image = _documentMedia->thumbnailInline()) {
prepare(image, option | Images::Option::Blur); prepare(image, option | Images::Option::Blur);
} }
} }
if (_good || !_document->hasThumbnail()) { if (_good || !_document->hasThumbnail()) {
_checked = true; checked = true;
_documentMedia = nullptr; _documentMedia = nullptr;
} }
} }
} else { } else {
Assert(_photo != nullptr); Assert(_photo != nullptr);
if (!_image || !_good) { if (!image || !_good) {
const auto inlineThumbnailBytes = _photo->inlineThumbnailBytes(); const auto inlineThumbnailBytes = _photo->inlineThumbnailBytes();
if (!_photoMedia) { if (!_photoMedia) {
_photoMedia = _photo->createMediaView(); _photoMedia = _photo->createMediaView();
} }
using Size = PhotoSize;
const auto loadThumbnail = inlineThumbnailBytes.isEmpty() const auto loadThumbnail = inlineThumbnailBytes.isEmpty()
|| _photoMedia->autoLoadThumbnailAllowed(context); || (!spoiler
&& _photoMedia->autoLoadThumbnailAllowed(context));
if (loadThumbnail) { if (loadThumbnail) {
_photoMedia->wanted(PhotoSize::Small, origin); _photoMedia->wanted(Size::Small, origin);
} }
if (const auto small = _photoMedia->image(PhotoSize::Small)) { if (spoiler) {
prepare(small, Images::Option(0)); const auto option = Images::Option::Blur;
} else if (const auto large = _photoMedia->image( if (const auto blurred = _photoMedia->thumbnailInline()) {
PhotoSize::Large)) { prepare(blurred, {}, true);
prepare(large, Images::Option(0)); } else if (const auto small = _photoMedia->image(Size::Small)) {
} else if (!_image) { prepare(small, {}, true);
} else if (const auto large = _photoMedia->image(Size::Large)) {
prepare(large, {}, true);
}
} else if (const auto small = _photoMedia->image(Size::Small)) {
prepare(small, {});
} else if (const auto large = _photoMedia->image(Size::Large)) {
prepare(large, {});
} else if (!image) {
if (const auto blurred = _photoMedia->thumbnailInline()) { if (const auto blurred = _photoMedia->thumbnailInline()) {
prepare(blurred, Images::Option::Blur); prepare(blurred, Images::Option::Blur);
} }
} }
if (_good) { if (_good) {
_checked = true; checked = true;
_photoMedia = nullptr; _photoMedia = nullptr;
} }
} }
} }
return _image.get(); return image.get();
} }
bool ReplyPreview::loaded() const { bool ReplyPreview::loaded(bool spoiler) const {
return _checked; return spoiler ? _checkedSpoilered : _checkedRegular;
} }
} // namespace Data } // namespace Data

View file

@ -25,19 +25,25 @@ public:
[[nodiscard]] Image *image( [[nodiscard]] Image *image(
Data::FileOrigin origin, Data::FileOrigin origin,
not_null<PeerData*> context); not_null<PeerData*> context,
[[nodiscard]] bool loaded() const; bool spoiler);
[[nodiscard]] bool loaded(bool spoiler) const;
private: private:
void prepare(not_null<Image*> image, Images::Options options); void prepare(
not_null<Image*> image,
Images::Options options,
bool spoiler = false);
std::unique_ptr<Image> _image; std::unique_ptr<Image> _regular;
std::unique_ptr<Image> _spoilered;
PhotoData *_photo = nullptr; PhotoData *_photo = nullptr;
DocumentData *_document = nullptr; DocumentData *_document = nullptr;
std::shared_ptr<PhotoMedia> _photoMedia; std::shared_ptr<PhotoMedia> _photoMedia;
std::shared_ptr<DocumentMedia> _documentMedia; std::shared_ptr<DocumentMedia> _documentMedia;
bool _good = false; bool _good = false;
bool _checked = false; bool _checkedRegular = false;
bool _checkedSpoilered = false;
}; };

View file

@ -283,9 +283,10 @@ bool HistoryMessageReply::updateData(
} }
if (replyToMsg) { if (replyToMsg) {
const auto repaint = [=] { holder->customEmojiRepaint(); };
const auto context = Core::MarkedTextContext{ const auto context = Core::MarkedTextContext{
.session = &holder->history()->session(), .session = &holder->history()->session(),
.customEmojiRepaint = [=] { holder->customEmojiRepaint(); }, .customEmojiRepaint = repaint,
}; };
replyToText.setMarkedText( replyToText.setMarkedText(
st::messageTextStyle, st::messageTextStyle,
@ -312,9 +313,17 @@ bool HistoryMessageReply::updateData(
? replyToMsg->from()->id ? replyToMsg->from()->id
: PeerId(0); : PeerId(0);
} }
const auto media = replyToMsg->media();
if (!media || !media->hasReplyPreview() || !media->hasSpoiler()) {
spoiler = nullptr;
} else if (!spoiler) {
spoiler = std::make_unique<Ui::SpoilerAnimation>(repaint);
}
} else if (force) { } else if (force) {
replyToMsgId = 0; replyToMsgId = 0;
replyToColorKey = PeerId(0); replyToColorKey = PeerId(0);
spoiler = nullptr;
} }
if (force) { if (force) {
holder->history()->owner().requestItemResize(holder); holder->history()->owner().requestItemResize(holder);
@ -463,14 +472,15 @@ void HistoryMessageReply::paint(
if (w > st::msgReplyBarSkip) { if (w > st::msgReplyBarSkip) {
if (replyToMsg) { if (replyToMsg) {
auto hasPreview = replyToMsg->media() ? replyToMsg->media()->hasReplyPreview() : false; const auto media = replyToMsg->media();
auto hasPreview = media && media->hasReplyPreview();
if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) { if (hasPreview && w < st::msgReplyBarSkip + st::msgReplyBarSize.height()) {
hasPreview = false; hasPreview = false;
} }
auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0; auto previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
if (hasPreview) { if (hasPreview) {
if (const auto image = replyToMsg->media()->replyPreview()) { if (const auto image = media->replyPreview()) {
auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x); auto to = style::rtlrect(x + st::msgReplyBarSkip, y + st::msgReplyPadding.top() + st::msgReplyBarPos.y(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height(), w + 2 * x);
const auto preview = image->pixSingle( const auto preview = image->pixSingle(
image->size() / style::DevicePixelRatio(), image->size() / style::DevicePixelRatio(),
@ -482,6 +492,16 @@ void HistoryMessageReply::paint(
.outer = to.size(), .outer = to.size(),
}); });
p.drawPixmap(to.x(), to.y(), preview); p.drawPixmap(to.x(), to.y(), preview);
if (spoiler) {
holder->clearCustomEmojiRepaint();
Ui::FillSpoilerRect(
p,
to,
Ui::DefaultImageSpoiler().frame(
spoiler->index(
context.now,
context.paused)));
}
} }
} }
if (w > st::msgReplyBarSkip + previewSkip) { if (w > st::msgReplyBarSkip + previewSkip) {

View file

@ -246,6 +246,7 @@ struct HistoryMessageReply
WebPageId replyToWebPageId = 0; WebPageId replyToWebPageId = 0;
ReplyToMessagePointer replyToMsg; ReplyToMessagePointer replyToMsg;
std::unique_ptr<HistoryMessageVia> replyToVia; std::unique_ptr<HistoryMessageVia> replyToVia;
std::unique_ptr<Ui::SpoilerAnimation> spoiler;
ClickHandlerPtr replyToLnk; ClickHandlerPtr replyToLnk;
mutable Ui::Text::String replyToName, replyToText; mutable Ui::Text::String replyToName, replyToText;
mutable int replyToVersion = 0; mutable int replyToVersion = 0;

View file

@ -7490,20 +7490,44 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
p.setInactive( p.setInactive(
controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any)); controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any));
p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg); p.fillRect(myrtlrect(0, backy, width(), backh), st::historyReplyBg);
const auto media = (!drawWebPagePreview && drawMsgText)
? drawMsgText->media()
: nullptr;
const auto hasPreview = media && media->hasReplyPreview();
const auto preview = hasPreview ? media->replyPreview() : nullptr;
const auto spoilered = preview && media->hasSpoiler();
if (!spoilered) {
_replySpoiler = nullptr;
} else if (!_replySpoiler) {
_replySpoiler = std::make_unique<Ui::SpoilerAnimation>([=] {
updateField();
});
}
if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) { if (_editMsgId || _replyToId || (!hasForward && _kbReplyTo)) {
const auto now = crl::now();
const auto paused = p.inactive();
auto replyLeft = st::historyReplySkip; auto replyLeft = st::historyReplySkip;
(_editMsgId ? st::historyEditIcon : st::historyReplyIcon).paint(p, st::historyReplyIconPosition + QPoint(0, backy), width()); (_editMsgId ? st::historyEditIcon : st::historyReplyIcon).paint(p, st::historyReplyIconPosition + QPoint(0, backy), width());
if (!drawWebPagePreview) { if (!drawWebPagePreview) {
if (drawMsgText) { if (drawMsgText) {
if (drawMsgText->media() && drawMsgText->media()->hasReplyPreview()) { if (hasPreview) {
if (const auto image = drawMsgText->media()->replyPreview()) { if (preview) {
auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); auto to = QRect(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height());
p.drawPixmap(to.x(), to.y(), image->pixSingle( p.drawPixmap(to.x(), to.y(), preview->pixSingle(
image->size() / style::DevicePixelRatio(), preview->size() / style::DevicePixelRatio(),
{ {
.options = Images::Option::RoundSmall, .options = Images::Option::RoundSmall,
.outer = to.size(), .outer = to.size(),
})); }));
if (_replySpoiler) {
Ui::FillSpoilerRect(
p,
to,
Ui::DefaultImageSpoiler().frame(
_replySpoiler->index(now, paused)));
}
} }
replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x();
} }
@ -7521,8 +7545,8 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
.availableWidth = width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right(), .availableWidth = width() - replyLeft - _fieldBarCancel->width() - st::msgReplyPadding.right(),
.palette = &st::historyComposeAreaPalette, .palette = &st::historyComposeAreaPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
.now = crl::now(), .now = now,
.paused = p.inactive(), .paused = paused,
.elisionLines = 1, .elisionLines = 1,
}); });
} else { } else {

View file

@ -70,6 +70,7 @@ class RequestsBar;
struct PreparedList; struct PreparedList;
class SendFilesWay; class SendFilesWay;
class SendAsButton; class SendAsButton;
class SpoilerAnimation;
enum class ReportReason; enum class ReportReason;
class ChooseThemeController; class ChooseThemeController;
class ContinuousScroll; class ContinuousScroll;
@ -626,6 +627,7 @@ private:
HistoryItem *_replyEditMsg = nullptr; HistoryItem *_replyEditMsg = nullptr;
Ui::Text::String _replyEditMsgText; Ui::Text::String _replyEditMsgText;
std::unique_ptr<Ui::SpoilerAnimation> _replySpoiler;
mutable base::Timer _updateEditTimeLeftDisplay; mutable base::Timer _updateEditTimeLeftDisplay;
object_ptr<Ui::IconButton> _fieldBarCancel; object_ptr<Ui::IconButton> _fieldBarCancel;

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "ui/chat/forward_options_box.h" #include "ui/chat/forward_options_box.h"
#include "ui/effects/spoiler_mess.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/painter.h" #include "ui/painter.h"
@ -306,33 +307,35 @@ void ForwardPanel::paint(
return; return;
} }
const_cast<ForwardPanel*>(this)->checkTexts(); const_cast<ForwardPanel*>(this)->checkTexts();
const auto now = crl::now();
const auto paused = p.inactive();
const auto firstItem = _data.items.front(); const auto firstItem = _data.items.front();
const auto firstMedia = firstItem->media(); const auto firstMedia = firstItem->media();
const auto hasPreview = (_data.items.size() < 2) const auto hasPreview = (_data.items.size() < 2)
&& firstMedia && firstMedia
&& firstMedia->hasReplyPreview(); && firstMedia->hasReplyPreview();
const auto preview = hasPreview ? firstMedia->replyPreview() : nullptr; const auto preview = hasPreview ? firstMedia->replyPreview() : nullptr;
const auto spoiler = preview && firstMedia->hasSpoiler();
if (!spoiler) {
_spoiler = nullptr;
} else if (!_spoiler) {
_spoiler = std::make_unique<Ui::SpoilerAnimation>(_repaint);
}
if (preview) { if (preview) {
auto to = QRect( auto to = QRect(
x, x,
y + st::msgReplyPadding.top(), y + st::msgReplyPadding.top(),
st::msgReplyBarSize.height(), st::msgReplyBarSize.height(),
st::msgReplyBarSize.height()); st::msgReplyBarSize.height());
if (preview->width() == preview->height()) { p.drawPixmap(to.x(), to.y(), preview->pixSingle(
p.drawPixmap(to.x(), to.y(), preview->pix()); preview->size() / style::DevicePixelRatio(),
} else { {
auto from = (preview->width() > preview->height()) .options = Images::Option::RoundSmall,
? QRect( .outer = to.size(),
(preview->width() - preview->height()) / 2, }));
0, if (_spoiler) {
preview->height(), Ui::FillSpoilerRect(p, to, Ui::DefaultImageSpoiler().frame(
preview->height()) _spoiler->index(now, paused)));
: QRect(
0,
(preview->height() - preview->width()) / 2,
preview->width(),
preview->width());
p.drawPixmap(to, preview->pix(), from);
} }
const auto skip = st::msgReplyBarSize.height() const auto skip = st::msgReplyBarSize.height()
+ st::msgReplyBarSkip + st::msgReplyBarSkip
@ -355,8 +358,8 @@ void ForwardPanel::paint(
.availableWidth = available, .availableWidth = available,
.palette = &st::historyComposeAreaPalette, .palette = &st::historyComposeAreaPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
.now = crl::now(), .now = now,
.paused = p.inactive(), .paused = paused,
.elisionLines = 1, .elisionLines = 1,
}); });
} }

View file

@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class Painter; class Painter;
class HistoryItem; class HistoryItem;
namespace Ui {
class SpoilerAnimation;
} // namespace Ui
namespace Data { namespace Data {
class Thread; class Thread;
} // namespace Data } // namespace Data
@ -57,6 +61,7 @@ private:
rpl::event_stream<> _itemsUpdated; rpl::event_stream<> _itemsUpdated;
Ui::Text::String _from, _text; Ui::Text::String _from, _text;
mutable std::unique_ptr<Ui::SpoilerAnimation> _spoiler;
int _nameVersion = 0; int _nameVersion = 0;
}; };

View file

@ -38,8 +38,9 @@ namespace {
[[nodiscard]] Ui::MessageBarContent ContentWithPreview( [[nodiscard]] Ui::MessageBarContent ContentWithPreview(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
Image *preview, Image *preview,
bool spoiler,
Fn<void()> repaint) { Fn<void()> repaint) {
auto result = ContentWithoutPreview(item, std::move(repaint)); auto result = ContentWithoutPreview(item, repaint);
if (!preview) { if (!preview) {
static const auto kEmpty = [&] { static const auto kEmpty = [&] {
const auto size = st::historyReplyHeight * cIntRetinaFactor(); const auto size = st::historyReplyHeight * cIntRetinaFactor();
@ -51,8 +52,10 @@ namespace {
return result; return result;
}(); }();
result.preview = kEmpty; result.preview = kEmpty;
result.spoilerRepaint = nullptr;
} else { } else {
result.preview = preview->original(); result.preview = preview->original();
result.spoilerRepaint = spoiler ? repaint : nullptr;
} }
return result; return result;
} }
@ -90,7 +93,11 @@ namespace {
}) | rpl::then( }) | rpl::then(
rpl::single(kFullLoaded) rpl::single(kFullLoaded)
) | rpl::map([=] { ) | rpl::map([=] {
return ContentWithPreview(item, media->replyPreview(), repaint); return ContentWithPreview(
item,
media->replyPreview(),
media->hasSpoiler(),
repaint);
}); });
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();
} }

View file

@ -71,8 +71,8 @@ bool DrawWebPageDataPreview(
} }
const auto preview = photo const auto preview = photo
? photo->getReplyPreview(Data::FileOrigin(), context) ? photo->getReplyPreview(Data::FileOrigin(), context, false)
: document->getReplyPreview(Data::FileOrigin(), context); : document->getReplyPreview(Data::FileOrigin(), context, false);
if (preview) { if (preview) {
const auto w = preview->width(); const auto w = preview->width();
const auto h = preview->height(); const auto h = preview->height();

View file

@ -140,6 +140,7 @@ void MessageBar::tweenTo(MessageBarContent &&content) {
? RectPart::Bottom ? RectPart::Bottom
: RectPart::None; : RectPart::None;
animation.imageFrom = grabImagePart(); animation.imageFrom = grabImagePart();
animation.spoilerFrom = std::move(_spoiler);
animation.bodyOrTextFrom = grabBodyOrTextPart(animation.bodyAnimation); animation.bodyOrTextFrom = grabBodyOrTextPart(animation.bodyAnimation);
const auto sameLength = SameFirstPartLength( const auto sameLength = SameFirstPartLength(
_content.title, _content.title,
@ -208,6 +209,12 @@ void MessageBar::updateFromContent(MessageBarContent &&content) {
Ui::DialogTextOptions(), Ui::DialogTextOptions(),
_content.context); _content.context);
_image = prepareImage(_content.preview); _image = prepareImage(_content.preview);
if (!_content.spoilerRepaint) {
_spoiler = nullptr;
} else if (!_spoiler) {
_spoiler = std::make_unique<SpoilerAnimation>(
_content.spoilerRepaint);
}
} }
QRect MessageBar::imageRect() const { QRect MessageBar::imageRect() const {
@ -258,10 +265,21 @@ auto MessageBar::makeGrabGuard() {
auto imageShown = _animation auto imageShown = _animation
? std::move(_animation->imageShown) ? std::move(_animation->imageShown)
: Ui::Animations::Simple(); : Ui::Animations::Simple();
return gsl::finally([&, shown = std::move(imageShown)]() mutable { auto spoiler = std::move(_spoiler);
auto fromSpoiler = _animation
? std::move(_animation->spoilerFrom)
: nullptr;
return gsl::finally([
&,
shown = std::move(imageShown),
spoiler = std::move(spoiler),
fromSpoiler = std::move(fromSpoiler)
]() mutable {
if (_animation) { if (_animation) {
_animation->imageShown = std::move(shown); _animation->imageShown = std::move(shown);
_animation->spoilerFrom = std::move(fromSpoiler);
} }
_spoiler = std::move(spoiler);
}); });
} }
@ -358,12 +376,20 @@ void MessageBar::paint(Painter &p) {
: (_animation->movingTo == RectPart::Top) : (_animation->movingTo == RectPart::Top)
? (shiftTo - shiftFull) ? (shiftTo - shiftFull)
: (shiftTo + shiftFull); : (shiftTo + shiftFull);
const auto now = crl::now();
const auto paused = p.inactive();
paintLeftBar(p); paintLeftBar(p);
if (!_animation) { if (!_animation) {
if (!_image.isNull()) { if (!_image.isNull()) {
p.drawPixmap(image, _image); paintImageWithSpoiler(
p,
image,
_image,
_spoiler.get(),
now,
paused);
} }
} else if (!_animation->imageTo.isNull() } else if (!_animation->imageTo.isNull()
|| (!_animation->imageFrom.isNull() || (!_animation->imageFrom.isNull()
@ -381,14 +407,30 @@ void MessageBar::paint(Painter &p) {
}(); }();
if (_animation->bodyMoved.animating()) { if (_animation->bodyMoved.animating()) {
p.setOpacity(1. - progress); p.setOpacity(1. - progress);
p.drawPixmap( paintImageWithSpoiler(
p,
rect.translated(0, shiftFrom), rect.translated(0, shiftFrom),
_animation->imageFrom); _animation->imageFrom,
_animation->spoilerFrom.get(),
now,
paused);
p.setOpacity(progress); p.setOpacity(progress);
p.drawPixmap(rect.translated(0, shiftTo), _animation->imageTo); paintImageWithSpoiler(
p,
rect.translated(0, shiftTo),
_animation->imageTo,
_spoiler.get(),
now,
paused);
p.setOpacity(1.); p.setOpacity(1.);
} else { } else {
p.drawPixmap(rect, _image); paintImageWithSpoiler(
p,
rect,
_image,
_spoiler.get(),
now,
paused);
} }
} }
if (!_animation || _animation->bodyAnimation == BodyAnimation::None) { if (!_animation || _animation->bodyAnimation == BodyAnimation::None) {
@ -409,8 +451,8 @@ void MessageBar::paint(Painter &p) {
.availableWidth = body.width(), .availableWidth = body.width(),
.palette = &_st.textPalette, .palette = &_st.textPalette,
.spoiler = Ui::Text::DefaultSpoilerCache(), .spoiler = Ui::Text::DefaultSpoilerCache(),
.now = crl::now(), .now = now,
.paused = p.inactive(), .paused = paused,
.elisionLines = 1, .elisionLines = 1,
}); });
} }
@ -510,6 +552,21 @@ void MessageBar::ensureGradientsCreated(int size) {
_topBarGradient = Images::PixmapFast(std::move(top)); _topBarGradient = Images::PixmapFast(std::move(top));
} }
void MessageBar::paintImageWithSpoiler(
QPainter &p,
QRect rect,
const QPixmap &image,
SpoilerAnimation *spoiler,
crl::time now,
bool paused) const {
p.drawPixmap(rect, image);
if (spoiler) {
const auto frame = DefaultImageSpoiler().frame(
spoiler->index(now, paused));
FillSpoilerRect(p, rect, frame);
}
}
void MessageBar::paintLeftBar(Painter &p) { void MessageBar::paintLeftBar(Painter &p) {
const auto state = countBarState(); const auto state = countBarState();
const auto gradientSize = int(std::ceil(state.size * 2.5)); const auto gradientSize = int(std::ceil(state.size * 2.5));

View file

@ -18,6 +18,8 @@ struct MessageBar;
namespace Ui { namespace Ui {
class SpoilerAnimation;
struct MessageBarContent { struct MessageBarContent {
int index = 0; int index = 0;
int count = 1; int count = 1;
@ -25,6 +27,7 @@ struct MessageBarContent {
TextWithEntities text; TextWithEntities text;
std::any context; std::any context;
QImage preview; QImage preview;
Fn<void()> spoilerRepaint;
style::margins margins; style::margins margins;
}; };
@ -38,7 +41,7 @@ public:
void set(MessageBarContent &&content); void set(MessageBarContent &&content);
void set(rpl::producer<MessageBarContent> content); void set(rpl::producer<MessageBarContent> content);
[[nodiscard]] not_null<Ui::RpWidget*> widget() { [[nodiscard]] not_null<RpWidget*> widget() {
return &_widget; return &_widget;
} }
@ -52,10 +55,10 @@ private:
None, None,
}; };
struct Animation { struct Animation {
Ui::Animations::Simple bodyMoved; Animations::Simple bodyMoved;
Ui::Animations::Simple imageShown; Animations::Simple imageShown;
Ui::Animations::Simple barScroll; Animations::Simple barScroll;
Ui::Animations::Simple barTop; Animations::Simple barTop;
QPixmap bodyOrTextFrom; QPixmap bodyOrTextFrom;
QPixmap bodyOrTextTo; QPixmap bodyOrTextTo;
QPixmap titleSame; QPixmap titleSame;
@ -63,6 +66,7 @@ private:
QPixmap titleTo; QPixmap titleTo;
QPixmap imageFrom; QPixmap imageFrom;
QPixmap imageTo; QPixmap imageTo;
std::unique_ptr<SpoilerAnimation> spoilerFrom;
BodyAnimation bodyAnimation = BodyAnimation::None; BodyAnimation bodyAnimation = BodyAnimation::None;
RectPart movingTo = RectPart::None; RectPart movingTo = RectPart::None;
}; };
@ -98,19 +102,28 @@ private:
[[nodiscard]] BarState countBarState() const; [[nodiscard]] BarState countBarState() const;
void ensureGradientsCreated(int size); void ensureGradientsCreated(int size);
void paintImageWithSpoiler(
QPainter &p,
QRect rect,
const QPixmap &image,
SpoilerAnimation *spoiler,
crl::time now,
bool paused) const;
[[nodiscard]] static BodyAnimation DetectBodyAnimationType( [[nodiscard]] static BodyAnimation DetectBodyAnimationType(
Animation *currentAnimation, Animation *currentAnimation,
const MessageBarContent &currentContent, const MessageBarContent &currentContent,
const MessageBarContent &nextContent); const MessageBarContent &nextContent);
const style::MessageBar &_st; const style::MessageBar &_st;
Ui::RpWidget _widget; RpWidget _widget;
Fn<bool()> _customEmojiPaused; Fn<bool()> _customEmojiPaused;
MessageBarContent _content; MessageBarContent _content;
rpl::lifetime _contentLifetime; rpl::lifetime _contentLifetime;
Ui::Text::String _title, _text; Text::String _title, _text;
QPixmap _image, _topBarGradient, _bottomBarGradient; QPixmap _image, _topBarGradient, _bottomBarGradient;
std::unique_ptr<Animation> _animation; std::unique_ptr<Animation> _animation;
std::unique_ptr<SpoilerAnimation> _spoiler;
bool _customEmojiRepaintScheduled = false; bool _customEmojiRepaintScheduled = false;
}; };