From 78897dd143ee6e6ece3f7d320e1193914a5d63f3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 24 Nov 2023 13:02:03 +0400 Subject: [PATCH] Show repost info on story view. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/data/data_story.cpp | 44 ++++ Telegram/SourceFiles/data/data_story.h | 8 + .../history/view/history_view_reply.cpp | 50 +++- .../history/view/history_view_reply.h | 39 +++ .../media_stories_caption_full_view.cpp | 2 +- .../stories/media_stories_controller.cpp | 44 +++- .../media/stories/media_stories_controller.h | 10 + .../stories/media_stories_repost_view.cpp | 238 ++++++++++++++++++ .../media/stories/media_stories_repost_view.h | 56 +++++ .../media/stories/media_stories_view.cpp | 16 ++ .../media/stories/media_stories_view.h | 4 + .../media/view/media_view_overlay_widget.cpp | 27 +- Telegram/SourceFiles/ui/chat/chat_style.cpp | 15 ++ Telegram/SourceFiles/ui/chat/chat_style.h | 9 + 15 files changed, 540 insertions(+), 24 deletions(-) create mode 100644 Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp create mode 100644 Telegram/SourceFiles/media/stories/media_stories_repost_view.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 8745365c1..f32c071f0 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1034,6 +1034,8 @@ PRIVATE media/stories/media_stories_recent_views.h media/stories/media_stories_reply.cpp media/stories/media_stories_reply.h + media/stories/media_stories_repost_view.cpp + media/stories/media_stories_repost_view.h media/stories/media_stories_share.cpp media/stories/media_stories_share.h media/stories/media_stories_sibling.cpp diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp index 565e0c904..79b8f7fba 100644 --- a/Telegram/SourceFiles/data/data_story.cpp +++ b/Telegram/SourceFiles/data/data_story.cpp @@ -105,6 +105,31 @@ using UpdateFlag = StoryUpdate::Flag; return result; } +[[nodiscard]] PeerData *RepostSourcePeer( + not_null owner, + const MTPDstoryItem &data) { + if (const auto forwarded = data.vfwd_from()) { + if (const auto from = forwarded->data().vfrom()) { + return owner->peer(peerFromMTP(*from)); + } + } + return nullptr; +} + +[[nodiscard]] QString RepostSourceName(const MTPDstoryItem &data) { + if (const auto forwarded = data.vfwd_from()) { + return qs(forwarded->data().vfrom_name().value_or_empty()); + } + return {}; +} + +[[nodiscard]] StoryId RepostSourceId(const MTPDstoryItem &data) { + if (const auto forwarded = data.vfwd_from()) { + return forwarded->data().vstory_id().value_or_empty(); + } + return {}; +} + } // namespace class StoryPreload::LoadTask final : private Storage::DownloadMtprotoTask { @@ -216,6 +241,9 @@ Story::Story( TimeId now) : _id(id) , _peer(peer) +, _repostSourcePeer(RepostSourcePeer(&peer->owner(), data)) +, _repostSourceName(RepostSourceName(data)) +, _repostSourceId(RepostSourceId(data)) , _date(data.vdate().v) , _expires(data.vexpire_date().v) { applyFields(std::move(media), data, now, true); @@ -730,6 +758,22 @@ TimeId Story::lastUpdateTime() const { return _lastUpdateTime; } +bool Story::repost() const { + return _repostSourcePeer || !_repostSourceName.isEmpty(); +} + +PeerData *Story::repostSourcePeer() const { + return _repostSourcePeer; +} + +QString Story::repostSourceName() const { + return _repostSourceName; +} + +StoryId Story::repostSourceId() const { + return _repostSourceId; +} + StoryPreload::StoryPreload(not_null story, Fn done) : _story(story) , _done(std::move(done)) { diff --git a/Telegram/SourceFiles/data/data_story.h b/Telegram/SourceFiles/data/data_story.h index 7ebe6bbde..b97e12379 100644 --- a/Telegram/SourceFiles/data/data_story.h +++ b/Telegram/SourceFiles/data/data_story.h @@ -181,6 +181,11 @@ public: void applyViewsCounts(const MTPDstoryViews &data); [[nodiscard]] TimeId lastUpdateTime() const; + [[nodiscard]] bool repost() const; + [[nodiscard]] PeerData *repostSourcePeer() const; + [[nodiscard]] QString repostSourceName() const; + [[nodiscard]] StoryId repostSourceId() const; + private: struct ViewsCounts { int views = 0; @@ -203,6 +208,9 @@ private: const StoryId _id = 0; const not_null _peer; + PeerData * const _repostSourcePeer = nullptr; + const QString _repostSourceName; + const StoryId _repostSourceId = 0; Data::ReactionId _sentReactionId; StoryMedia _media; TextWithEntities _caption; diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index 98cefe4d8..b0975a387 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -38,30 +38,40 @@ namespace { constexpr auto kNonExpandedLinesLimit = 5; +} // namespace + void ValidateBackgroundEmoji( DocumentId backgroundEmojiId, not_null data, not_null cache, not_null quote, not_null view) { + if (data->firstFrameMask.isNull() && !data->emoji) { + data->emoji = CreateBackgroundEmojiInstance( + &view->history()->owner(), + backgroundEmojiId, + crl::guard(view, [=] { view->repaint(); })); + } + ValidateBackgroundEmoji(backgroundEmojiId, data, cache, quote); +} + +void ValidateBackgroundEmoji( + DocumentId backgroundEmojiId, + not_null data, + not_null cache, + not_null quote) { + Expects(!data->firstFrameMask.isNull() || data->emoji != nullptr); + if (data->firstFrameMask.isNull()) { if (!cache->frames[0].isNull()) { for (auto &frame : cache->frames) { frame = QImage(); } } - const auto tag = Data::CustomEmojiSizeTag::Isolated; - if (!data->emoji) { - const auto repaint = crl::guard(view, [=] { view->repaint(); }); - const auto owner = &view->history()->owner(); - data->emoji = owner->customEmojiManager().create( - backgroundEmojiId, - repaint, - tag); - } if (!data->emoji->ready()) { return; } + const auto tag = Data::CustomEmojiSizeTag::Isolated; const auto size = Data::FrameSizeFromTag(tag); data->firstFrameMask = QImage( QSize(size, size), @@ -115,8 +125,19 @@ void ValidateBackgroundEmoji( cache->frames[2] = make(kSize3); } +auto CreateBackgroundEmojiInstance( + not_null owner, + DocumentId backgroundEmojiId, + Fn repaint) +-> std::unique_ptr { + return owner->customEmojiManager().create( + backgroundEmojiId, + repaint, + Data::CustomEmojiSizeTag::Isolated); +} + void FillBackgroundEmoji( - Painter &p, + QPainter &p, const QRect &rect, bool quote, const Ui::BackgroundEmojiCache &cache) { @@ -157,8 +178,6 @@ void FillBackgroundEmoji( p.setOpacity(1.); } -} // namespace - Reply::Reply() : _name(st::maxSignatureSize / 2) , _text(st::maxSignatureSize / 2) { @@ -799,6 +818,12 @@ void Reply::stopLastRipple() { TextWithEntities Reply::PeerEmoji( not_null history, PeerData *peer) { + return PeerEmoji(&history->owner(), peer); +} + +TextWithEntities Reply::PeerEmoji( + not_null owner, + PeerData *peer) { using namespace std; const auto icon = !peer ? pair(&st::historyReplyUser, st::historyReplyUserPadding) @@ -807,7 +832,6 @@ TextWithEntities Reply::PeerEmoji( : (peer->isChannel() || peer->isChat()) ? pair(&st::historyReplyGroup, st::historyReplyGroupPadding) : pair(&st::historyReplyUser, st::historyReplyUserPadding); - const auto owner = &history->owner(); return Ui::Text::SingleCustomEmoji( owner->customEmojiManager().registerInternalEmoji( *icon.first, diff --git a/Telegram/SourceFiles/history/view/history_view_reply.h b/Telegram/SourceFiles/history/view/history_view_reply.h index 4b30a842d..9a70356ea 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.h +++ b/Telegram/SourceFiles/history/view/history_view_reply.h @@ -9,12 +9,48 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" +namespace Data { +class Session; +} // namespace Data + namespace Ui { class SpoilerAnimation; +struct BackgroundEmojiData; +struct BackgroundEmojiCache; } // namespace Ui +namespace Ui::Text { +class CustomEmoji; +struct QuotePaintCache; +} // namespace Ui::Text + namespace HistoryView { +void ValidateBackgroundEmoji( + DocumentId backgroundEmojiId, + not_null data, + not_null cache, + not_null quote, + not_null view); + +// For this one data->firstFrameMask or data->emoji must be already set. +void ValidateBackgroundEmoji( + DocumentId backgroundEmojiId, + not_null data, + not_null cache, + not_null quote); +[[nodiscard]] auto CreateBackgroundEmojiInstance( + not_null owner, + DocumentId backgroundEmojiId, + Fn repaint) +-> std::unique_ptr; + +void FillBackgroundEmoji( + QPainter &p, + const QRect &rect, + bool quote, + const Ui::BackgroundEmojiCache &cache); + class Reply final : public RuntimeComponent { public: Reply(); @@ -66,6 +102,9 @@ public: [[nodiscard]] static TextWithEntities PeerEmoji( not_null history, PeerData *peer); + [[nodiscard]] static TextWithEntities PeerEmoji( + not_null owner, + PeerData *peer); [[nodiscard]] static TextWithEntities ComposePreviewName( not_null history, not_null to, diff --git a/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp index 25687468f..4cc32c23d 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_caption_full_view.cpp @@ -26,7 +26,7 @@ CaptionFullView::CaptionFullView(not_null controller) object_ptr>( _scroll.get(), object_ptr(_scroll.get(), st::storiesCaptionFull), - st::mediaviewCaptionPadding))) + st::mediaviewCaptionPadding + _controller->repostCaptionPadding()))) , _text(_wrap->entity()) { _text->setMarkedText(controller->captionText(), Core::MarkedTextContext{ .session = &controller->uiShow()->session(), diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp index 2cda1b111..80beb06a8 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp @@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/stories/media_stories_reactions.h" #include "media/stories/media_stories_recent_views.h" #include "media/stories/media_stories_reply.h" +#include "media/stories/media_stories_repost_view.h" #include "media/stories/media_stories_share.h" #include "media/stories/media_stories_stealth.h" #include "media/stories/media_stories_view.h" @@ -332,6 +333,8 @@ Controller::Controller(not_null delegate) } Controller::~Controller() { + _captionFullView = nullptr; + _repostView = nullptr; changeShown(nullptr); } @@ -586,7 +589,34 @@ TextWithEntities Controller::captionText() const { } bool Controller::skipCaption() const { - return _captionFullView != nullptr; + return (_captionFullView != nullptr) + || (_captionText.empty() && !repost()); +} + +bool Controller::repost() const { + return _repostView != nullptr; +} + +int Controller::repostSkipTop() const { + return _repostView + ? (_repostView->height() + + (_captionText.empty() ? 0 : st::mediaviewTextSkip)) + : 0; +} + +QRect Controller::captionWithRepostGeometry(QRect caption) const { + return caption.marginsAdded(st::mediaviewCaptionPadding).marginsAdded( + { 0, repostSkipTop(), 0, 0 }); +} + +void Controller::drawRepostInfo( + Painter &p, + int x, + int y, + int availableWidth) const { + Expects(_repostView != nullptr); + + _repostView->draw(p, x, y - repostSkipTop(), availableWidth); } void Controller::toggleLiked() { @@ -837,6 +867,7 @@ void Controller::show( } captionClosed(); + _repostView = validateRepostView(story); _captionText = story->caption(); _contentFaded = false; _contentFadeAnimation.stop(); @@ -1341,6 +1372,10 @@ void Controller::repaintSibling(not_null sibling) { } } +void Controller::repaint() { + _delegate->storiesRepaint(); +} + SiblingView Controller::sibling(SiblingType type) const { const auto &pointer = (type == SiblingType::Left) ? _siblingLeft @@ -1428,6 +1463,13 @@ StoryId Controller::shownId(int index) const { : StoryId(); } +std::unique_ptr Controller::validateRepostView( + not_null story) { + return story->repost() + ? std::make_unique(this, story) + : nullptr; +} + void Controller::loadMoreToList() { Expects(shown()); diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h index 88ceea064..073428a05 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_controller.h +++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h @@ -67,6 +67,7 @@ struct SiblingView; enum class SiblingType; struct ContentLayout; class CaptionFullView; +class RepostView; enum class ReactionsMode; class SuggestedReactionView; @@ -121,11 +122,15 @@ public: [[nodiscard]] Data::FileOrigin fileOrigin() const; [[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] bool skipCaption() const; + [[nodiscard]] bool repost() const; void toggleLiked(); void showFullCaption(); void captionClosing(); void captionClosed(); + [[nodiscard]] QRect captionWithRepostGeometry(QRect caption) const; + void drawRepostInfo(Painter &p, int x, int y, int availableWidth) const; + [[nodiscard]] std::shared_ptr uiShow() const; [[nodiscard]] auto stickerOrEmojiChosen() const -> rpl::producer; @@ -152,6 +157,7 @@ public: void changeVolume(float64 volume); void volumeChangeFinished(); + void repaint(); void repaintSibling(not_null sibling); [[nodiscard]] SiblingView sibling(SiblingType type) const; @@ -239,6 +245,8 @@ private: [[nodiscard]] PeerData *shownPeer() const; [[nodiscard]] int shownCount() const; [[nodiscard]] StoryId shownId(int index) const; + [[nodiscard]] std::unique_ptr validateRepostView( + not_null story); void rebuildFromContext(not_null peer, FullStoryId storyId); void checkMoveByDelta(); void loadMoreToList(); @@ -247,6 +255,7 @@ private: const std::vector &lists, int index); + [[nodiscard]] int repostSkipTop() const; void updateAreas(Data::Story *story); void reactionChosen(ReactionsMode mode, ChosenReaction chosen); @@ -263,6 +272,7 @@ private: std::unique_ptr _unsupported; std::unique_ptr _photoPlayback; std::unique_ptr _captionFullView; + std::unique_ptr _repostView; Ui::Animations::Simple _contentFadeAnimation; bool _contentFaded = false; diff --git a/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp new file mode 100644 index 000000000..cf44bcce8 --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp @@ -0,0 +1,238 @@ +/* +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 "media/stories/media_stories_repost_view.h" + +#include "core/ui_integration.h" +#include "data/data_peer.h" +#include "data/data_session.h" +#include "data/data_stories.h" +#include "history/view/history_view_reply.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "media/stories/media_stories_controller.h" +#include "ui/effects/ripple_animation.h" +#include "ui/text/text_custom_emoji.h" +#include "ui/text/text_options.h" +#include "ui/painter.h" +#include "ui/power_saving.h" +#include "styles/style_chat.h" +#include "styles/style_media_view.h" + +namespace Media::Stories { + +RepostView::RepostView( + not_null controller, + not_null story) +: _controller(controller) +, _story(story) { + Expects(_story->repost()); + + _story->session().colorIndicesValue( + ) | rpl::start_with_next([=](Ui::ColorIndicesCompressed &&indices) { + _colorIndices = std::move(indices); + if (_maxWidth) { + _controller->repaint(); + } + }, _lifetime); +} + +RepostView::~RepostView() = default; + +int RepostView::height() const { + return st::historyReplyPadding.top() + + st::semiboldFont->height + + st::normalFont->height + + st::historyReplyPadding.bottom(); +} + +void RepostView::draw(Painter &p, int x, int y, int availableWidth) { + if (!_maxWidth) { + recountDimensions(); + } + const auto w = std::min(_maxWidth, availableWidth); + const auto rect = QRect(x, y, w, height()); + const auto colorPeer = _story->repostSourcePeer(); + const auto backgroundEmojiId = colorPeer + ? colorPeer->backgroundEmojiId() + : DocumentId(); + const auto cache = &_quoteCache; + const auto "eSt = st::messageQuoteStyle; + const auto backgroundEmoji = backgroundEmojiId + ? &_backgroundEmojiData + : nullptr; + const auto backgroundEmojiCache = backgroundEmoji + ? &backgroundEmoji->caches[0] + : nullptr; + + auto rippleColor = cache->bg; + cache->bg = QColor(0, 0, 0, 64); + Ui::Text::ValidateQuotePaintCache(*cache, quoteSt); + Ui::Text::FillQuotePaint(p, rect, *cache, quoteSt); + if (backgroundEmoji) { + using namespace HistoryView; + if (backgroundEmoji->firstFrameMask.isNull() + && !backgroundEmoji->emoji) { + backgroundEmoji->emoji = CreateBackgroundEmojiInstance( + &_story->owner(), + backgroundEmojiId, + crl::guard(this, [=] { _controller->repaint(); })); + } + ValidateBackgroundEmoji( + backgroundEmojiId, + backgroundEmoji, + backgroundEmojiCache, + cache); + if (!backgroundEmojiCache->frames[0].isNull()) { + const auto hasQuoteIcon = false; + FillBackgroundEmoji( + p, + rect, + hasQuoteIcon, + *backgroundEmojiCache); + } + } + cache->bg = rippleColor; + + if (_ripple) { + _ripple->paint(p, x, y, w, &rippleColor); + if (_ripple->empty()) { + _ripple.reset(); + } + } + + const auto pausedSpoiler = On(PowerSaving::kChatSpoiler); + auto textLeft = x + st::historyReplyPadding.left(); + auto textTop = y + + st::historyReplyPadding.top() + + st::semiboldFont->height; + if (w > st::historyReplyPadding.left()) { + if (_stateText.isEmpty()) { + const auto textw = w + - st::historyReplyPadding.left() + - st::historyReplyPadding.right(); + const auto namew = textw; + if (namew > 0) { + p.setPen(cache->icon); + _name.drawLeftElided( + p, + x + st::historyReplyPadding.left(), + y + st::historyReplyPadding.top(), + namew, + w + 2 * x); + _text.draw(p, { + .position = { textLeft, textTop }, + .availableWidth = w, + .palette = &st::mediaviewTextPalette, + .spoiler = Ui::Text::DefaultSpoilerCache(), + .pausedEmoji = On(PowerSaving::kEmojiChat), + .pausedSpoiler = On(PowerSaving::kChatSpoiler), + .elisionLines = 1, + }); + } + } else { + p.setFont(st::msgDateFont); + p.setPen(cache->icon); + p.drawTextLeft( + textLeft, + (y + + st::historyReplyPadding.top() + + (st::msgDateFont->height / 2)), + w + 2 * x, + st::msgDateFont->elided( + _stateText, + x + w - textLeft - st::historyReplyPadding.right())); + } + } +} + +void RepostView::recountDimensions() { + const auto sender = _story->repostSourcePeer(); + const auto name = sender ? sender->name() : _story->repostSourceName(); + const auto owner = &_story->owner(); + const auto repostId = _story->repostSourceId(); + + const auto colorIndexPlusOne = sender + ? (sender->colorIndex() + 1) + : 1; + const auto dark = true; + const auto colorPattern = colorIndexPlusOne + ? Ui::ColorPatternIndex(_colorIndices, colorIndexPlusOne - 1, dark) + : 0; + Assert(colorPattern < Ui::Text::kMaxQuoteOutlines); + const auto values = Ui::SimpleColorIndexValues( + QColor(255, 255, 255), + colorPattern); + _quoteCache.bg = values.bg; + _quoteCache.outlines = values.outlines; + _quoteCache.icon = values.name; + + auto text = TextWithEntities(); + auto displaying = true; + auto unavailable = false; + if (sender && repostId) { + const auto of = owner->stories().lookup({ sender->id, repostId }); + displaying = of.has_value(); + unavailable = !displaying && (of.error() == Data::NoStory::Deleted); + if (displaying) { + text = (*of)->caption(); + } else if (!unavailable) { + const auto done = crl::guard(this, [=] { + _maxWidth = 0; + _controller->repaint(); + }); + owner->stories().resolve({ sender->id, repostId }, done); + } + } + if (displaying && !unavailable && text.empty()) { + text = { tr::lng_in_dlg_story(tr::now) }; + } + + auto nameFull = TextWithEntities(); + nameFull.append(HistoryView::Reply::PeerEmoji(owner, sender)); + nameFull.append(name); + auto context = Core::MarkedTextContext{ + .session = &_story->session(), + .customEmojiRepaint = [] {}, + .customEmojiLoopLimit = 1, + }; + _name.setMarkedText( + st::semiboldTextStyle, + nameFull, + Ui::NameTextOptions(), + context); + context.customEmojiRepaint = crl::guard(this, [=] { + _controller->repaint(); + }), + _text.setMarkedText( + st::defaultTextStyle, + text, + Ui::DialogTextOptions(), + context); + + const auto nameMaxWidth = _name.maxWidth(); + const auto optimalTextWidth = std::min( + _text.maxWidth(), + st::maxSignatureSize); + _maxWidth = std::max(nameMaxWidth, optimalTextWidth); + if (!displaying) { + _stateText = !unavailable + ? tr::lng_profile_loading(tr::now) + : tr::lng_deleted_story(tr::now); + const auto phraseWidth = st::msgDateFont->width(_stateText); + _maxWidth = unavailable + ? phraseWidth + : std::max(_maxWidth, phraseWidth); + } else { + _stateText = QString(); + } + _maxWidth = st::historyReplyPadding.left() + + _maxWidth + + st::historyReplyPadding.right(); +} + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_repost_view.h b/Telegram/SourceFiles/media/stories/media_stories_repost_view.h new file mode 100644 index 000000000..5812cd77f --- /dev/null +++ b/Telegram/SourceFiles/media/stories/media_stories_repost_view.h @@ -0,0 +1,56 @@ +/* +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 "base/weak_ptr.h" +#include "ui/chat/chat_style.h" + +class Painter; + +namespace Data { +class Story; +} // namespace Data + +namespace Ui { +class RippleAnimation; +} // namespace Ui + +namespace Media::Stories { + +class Controller; + +class RepostView final : public base::has_weak_ptr { +public: + RepostView( + not_null controller, + not_null story); + ~RepostView(); + + [[nodiscard]] int height() const; + void draw(Painter &p, int x, int y, int availableWidth); + +private: + void recountDimensions(); + + const not_null _controller; + const not_null _story; + std::unique_ptr _ripple; + + Ui::Text::String _name; + Ui::Text::String _text; + Ui::Text::QuotePaintCache _quoteCache; + Ui::BackgroundEmojiData _backgroundEmojiData; + QString _stateText; + Ui::ColorIndicesCompressed _colorIndices; + int _maxWidth = 0; + + rpl::lifetime _lifetime; + +}; + +} // namespace Media::Stories diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_view.cpp index 0347a5e6e..795304f3c 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_view.cpp @@ -146,6 +146,22 @@ bool View::skipCaption() const { return _controller->skipCaption(); } +bool View::repost() const { + return _controller->repost(); +} + +QRect View::captionWithRepostGeometry(QRect caption) const { + return _controller->captionWithRepostGeometry(caption); +} + +void View::drawRepostInfo( + Painter &p, + int x, + int y, + int availableWidth) const { + _controller->drawRepostInfo(p, x, y, availableWidth); +} + void View::showFullCaption() { _controller->showFullCaption(); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_view.h b/Telegram/SourceFiles/media/stories/media_stories_view.h index 081536299..b37742341 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_view.h +++ b/Telegram/SourceFiles/media/stories/media_stories_view.h @@ -78,8 +78,12 @@ public: [[nodiscard]] Data::FileOrigin fileOrigin() const; [[nodiscard]] TextWithEntities captionText() const; [[nodiscard]] bool skipCaption() const; + [[nodiscard]] bool repost() const; void showFullCaption(); + [[nodiscard]] QRect captionWithRepostGeometry(QRect caption) const; + void drawRepostInfo(Painter &p, int x, int y, int availableWidth) const; + void updatePlayback(const Player::TrackState &state); [[nodiscard]] ClickHandlerPtr lookupAreaHandler(QPoint point) const; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index f6f77ce2e..ea94341b4 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -1434,7 +1434,10 @@ void OverlayWidget::refreshCaptionGeometry() { _captionShowMoreWidth = 0; _captionSkipBlockWidth = 0; - if (_caption.isEmpty()) { + const auto storiesCaptionWidth = _w + - st::mediaviewCaptionPadding.left() + - st::mediaviewCaptionPadding.right(); + if (_caption.isEmpty() && (!_stories || !_stories->repost())) { _captionRect = QRect(); return; } @@ -1451,9 +1454,7 @@ void OverlayWidget::refreshCaptionGeometry() { ? _groupThumbsTop : height() - st::mediaviewCaptionMargin.height(); const auto captionWidth = _stories - ? (_w - - st::mediaviewCaptionPadding.left() - - st::mediaviewCaptionPadding.right()) + ? storiesCaptionWidth : std::min( (_groupThumbsAvailableWidth - st::mediaviewCaptionPadding.left() @@ -4612,8 +4613,7 @@ void OverlayWidget::paint(not_null renderer) { if (!_stories) { renderer->paintFooter(footerGeometry(), opacity); } - if (!_caption.isEmpty() - && (!_stories || !_stories->skipCaption())) { + if (!(_stories ? _stories->skipCaption() : _caption.isEmpty())) { renderer->paintCaption(captionGeometry(), opacity); } if (_groupThumbs) { @@ -5020,8 +5020,13 @@ void OverlayWidget::paintCaptionContent( QRect outer, QRect clip, float64 opacity) { - const auto inner = outer.marginsRemoved(st::mediaviewCaptionPadding); - if (!_stories) { + auto inner = outer.marginsRemoved(st::mediaviewCaptionPadding); + inner.setTop(inner.top() + inner.height() - _captionRect.height()); + if (_stories) { + if (_stories->repost()) { + _stories->drawRepostInfo(p, inner.x(), inner.y(), inner.width()); + } + } else { p.setOpacity(opacity); p.setBrush(st::mediaviewCaptionBg); p.setPen(Qt::NoPen); @@ -5068,7 +5073,9 @@ void OverlayWidget::paintCaptionContent( } QRect OverlayWidget::captionGeometry() const { - return _captionRect.marginsAdded(st::mediaviewCaptionPadding); + return (_stories && _stories->repost()) + ? _stories->captionWithRepostGeometry(_captionRect) + : _captionRect.marginsAdded(st::mediaviewCaptionPadding); } void OverlayWidget::paintGroupThumbsContent( @@ -5710,6 +5717,8 @@ void OverlayWidget::updateOver(QPoint pos) { lnk = ensureCaptionExpandLink(); } lnkhost = this; + } else if (_stories && captionGeometry().contains(pos)) { + //_stories->repostState(); } else if (_groupThumbs && _groupThumbsRect.contains(pos)) { const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop); lnk = _groupThumbs->getState(point); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.cpp b/Telegram/SourceFiles/ui/chat/chat_style.cpp index d85da4ec4..206eb523d 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.cpp +++ b/Telegram/SourceFiles/ui/chat/chat_style.cpp @@ -143,6 +143,21 @@ int BackgroundEmojiData::CacheIndex( return (base * 2) + (selected ? 1 : 0); }; +int ColorPatternIndex( + const ColorIndicesCompressed &indices, + uint8 colorIndex, + bool dark) { + Expects(colorIndex >= 0 && colorIndex < kColorIndexCount); + + if (!indices.colors + || colorIndex < kSimpleColorIndexCount) { + return 0; + } + auto &data = (*indices.colors)[colorIndex]; + auto &colors = dark ? data.dark : data.light; + return colors[2] ? 2 : colors[1] ? 1 : 0; +} + ChatStyle::ChatStyle(rpl::producer colorIndices) { if (colorIndices) { _colorIndicesLifetime = std::move( diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index afa63a7ae..85102db6a 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -236,12 +236,21 @@ struct ColorIndicesCompressed { std::shared_ptr> colors; }; +[[nodiscard]] int ColorPatternIndex( + const ColorIndicesCompressed &indices, + uint8 colorIndex, + bool dark); + struct ColorIndexValues { std::array outlines; QColor name; QColor bg; }; +[[nodiscard]] ColorIndexValues SimpleColorIndexValues( + QColor color, + int patternIndex); + class ChatStyle final : public style::palette { public: explicit ChatStyle(rpl::producer colorIndices);