diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp index d588d3aa9..827786c85 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/stickers_emoji_pack.h" #include "chat_helpers/stickers_emoji_image_loader.h" +#include "history/view/history_view_element.h" #include "history/history_item.h" #include "history/history.h" #include "lottie/lottie_common.h" @@ -101,10 +102,10 @@ EmojiPack::EmojiPack(not_null session) : _session(session) { refresh(); - session->data().itemRemoved( - ) | rpl::filter([](not_null item) { - return item->isIsolatedEmoji(); - }) | rpl::start_with_next([=](not_null item) { + session->data().viewRemoved( + ) | rpl::filter([](not_null view) { + return view->isIsolatedEmoji() || view->isOnlyCustomEmoji(); + }) | rpl::start_with_next([=](not_null item) { remove(item); }, _lifetime); @@ -122,26 +123,26 @@ EmojiPack::EmojiPack(not_null session) EmojiPack::~EmojiPack() = default; -bool EmojiPack::add(not_null item) { - if (const auto custom = item->onlyCustomEmoji()) { - _onlyCustomItems.emplace(item); +bool EmojiPack::add(not_null view) { + if (const auto custom = view->onlyCustomEmoji()) { + _onlyCustomItems.emplace(view); return true; - } else if (const auto emoji = item->isolatedEmoji()) { - _items[emoji].emplace(item); + } else if (const auto emoji = view->isolatedEmoji()) { + _items[emoji].emplace(view); return true; } return false; } -void EmojiPack::remove(not_null item) { - Expects(item->isIsolatedEmoji() || item->isOnlyCustomEmoji()); +void EmojiPack::remove(not_null view) { + Expects(view->isIsolatedEmoji() || view->isOnlyCustomEmoji()); - if (item->isOnlyCustomEmoji()) { - _onlyCustomItems.remove(item); - } else if (const auto emoji = item->isolatedEmoji()) { + if (view->isOnlyCustomEmoji()) { + _onlyCustomItems.remove(view); + } else if (const auto emoji = view->isolatedEmoji()) { const auto i = _items.find(emoji); Assert(i != end(_items)); - const auto j = i->second.find(item); + const auto j = i->second.find(view); Assert(j != end(i->second)); i->second.erase(j); if (i->second.empty()) { @@ -436,9 +437,20 @@ auto EmojiPack::collectAnimationsIndices( } void EmojiPack::refreshAll() { + auto items = base::flat_set>(); + auto count = 0; for (const auto &[emoji, list] : _items) { - refreshItems(list); + // refreshItems(list); // This call changes _items! + count += int(list.size()); } + items.reserve(count); + for (const auto &[emoji, list] : _items) { + // refreshItems(list); // This call changes _items! + for (const auto &view : list) { + items.emplace(view->data()); + } + } + refreshItems(items); refreshItems(_onlyCustomItems); } @@ -458,8 +470,18 @@ void EmojiPack::refreshItems(EmojiPtr emoji) { } void EmojiPack::refreshItems( - const base::flat_set> &list) { - for (const auto &item : list) { + const base::flat_set> &list) { + auto items = base::flat_set>(); + items.reserve(list.size()); + for (const auto &view : list) { + items.emplace(view->data()); + } + refreshItems(items); +} + +void EmojiPack::refreshItems( + const base::flat_set> &items) { + for (const auto &item : items) { _session->data().requestItemViewRefresh(item); } } diff --git a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h index 12df35ff7..8a4f7e956 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_emoji_pack.h @@ -35,6 +35,10 @@ class UniversalImages; } // namespace Emoji } // namespace Ui +namespace HistoryView { +class Element; +} // namespace HistoryView + namespace Stickers { using IsolatedEmoji = Ui::Text::IsolatedEmoji; @@ -48,6 +52,8 @@ struct LargeEmojiImage { class EmojiPack final { public: + using ViewElement = HistoryView::Element; + struct Sticker { DocumentData *document = nullptr; const Lottie::ColorReplacements *replacements = nullptr; @@ -63,8 +69,8 @@ public: explicit EmojiPack(not_null session); ~EmojiPack(); - bool add(not_null item); - void remove(not_null item); + bool add(not_null view); + void remove(not_null view); [[nodiscard]] Sticker stickerForEmoji(EmojiPtr emoji); [[nodiscard]] Sticker stickerForEmoji(const IsolatedEmoji &emoji); @@ -106,17 +112,18 @@ private: -> base::flat_map>; void refreshAll(); void refreshItems(EmojiPtr emoji); - void refreshItems(const base::flat_set> &list); + void refreshItems(const base::flat_set> &list); + void refreshItems(const base::flat_set> &items); - not_null _session; + const not_null _session; base::flat_map> _map; base::flat_map< IsolatedEmoji, - base::flat_set>> _items; + base::flat_set>> _items; base::flat_map> _images; mtpRequestId _requestId = 0; - base::flat_set> _onlyCustomItems; + base::flat_set> _onlyCustomItems; int _animationsVersion = 0; base::flat_map< diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 37a371576..98f4621f3 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1529,11 +1529,10 @@ rpl::producer> Session::itemDataChanges() const { void Session::requestItemTextRefresh(not_null item) { if (const auto i = _views.find(item); i != _views.end()) { - for (const auto view : i->second) { - if (const auto media = view->media()) { - media->parentTextUpdated(); - } + for (const auto &view : i->second) { + view->itemTextUpdated(); } + requestItemResize(item); } } @@ -1573,6 +1572,7 @@ rpl::producer> Session::itemRemoved( } void Session::notifyViewRemoved(not_null view) { + _shownSpoilers.remove(view); _viewRemoved.fire_copy(view); } @@ -1672,24 +1672,15 @@ void Session::unloadHeavyViewParts( } } -void Session::registerShownSpoiler(FullMsgId id) { - if (const auto item = message(id)) { - _shownSpoilers.emplace(item); - } -} - -void Session::unregisterShownSpoiler(FullMsgId id) { - if (const auto item = message(id)) { - _shownSpoilers.remove(item); - } +void Session::registerShownSpoiler(not_null view) { + _shownSpoilers.emplace(view); } void Session::hideShownSpoilers() { - for (const auto &item : _shownSpoilers) { - item->hideSpoilers(); - requestItemTextRefresh(item); + for (const auto &view : base::take(_shownSpoilers)) { + view->hideSpoilers(); + requestViewRepaint(view); } - _shownSpoilers = base::flat_set>(); } void Session::removeMegagroupParticipant( @@ -2156,7 +2147,6 @@ void Session::removeDependencyMessage(not_null item) { void Session::unregisterMessage(not_null item) { const auto peerId = item->history()->peer->id; const auto itemId = item->id; - _shownSpoilers.remove(item); _itemRemoved.fire_copy(item); session().changes().messageUpdated( item, diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index c08ce4a7a..c06807682 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -296,8 +296,7 @@ public: int from, int till); - void registerShownSpoiler(FullMsgId id); - void unregisterShownSpoiler(FullMsgId id); + void registerShownSpoiler(not_null view); void hideShownSpoilers(); using MegagroupParticipant = std::tuple< @@ -956,7 +955,7 @@ private: rpl::event_stream _invitesToCalls; base::flat_map>> _invitedToCallUsers; - base::flat_set> _shownSpoilers; + base::flat_set> _shownSpoilers; History *_topPromoted = nullptr; diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 67d98a72c..3896ad2c6 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -220,68 +220,71 @@ struct StickerSetIdentifier { } }; -enum class MessageFlag : uint32 { - HideEdited = (1U << 0), - Legacy = (1U << 1), - HasReplyMarkup = (1U << 2), - HasFromId = (1U << 3), - HasPostAuthor = (1U << 4), - HasViews = (1U << 5), - HasReplyInfo = (1U << 6), - CanViewReactions = (1U << 7), - AdminLogEntry = (1U << 8), - Post = (1U << 9), - Silent = (1U << 10), - Outgoing = (1U << 11), - Pinned = (1U << 12), - MediaIsUnread = (1U << 13), - HasUnreadReaction = (1U << 14), - MentionsMe = (1U << 15), - IsOrWasScheduled = (1U << 16), - NoForwards = (1U << 17), +enum class MessageFlag : uint64 { + HideEdited = (1ULL << 0), + Legacy = (1ULL << 1), + HasReplyMarkup = (1ULL << 2), + HasFromId = (1ULL << 3), + HasPostAuthor = (1ULL << 4), + HasViews = (1ULL << 5), + HasReplyInfo = (1ULL << 6), + CanViewReactions = (1ULL << 7), + AdminLogEntry = (1ULL << 8), + Post = (1ULL << 9), + Silent = (1ULL << 10), + Outgoing = (1ULL << 11), + Pinned = (1ULL << 12), + MediaIsUnread = (1ULL << 13), + HasUnreadReaction = (1ULL << 14), + MentionsMe = (1ULL << 15), + IsOrWasScheduled = (1ULL << 16), + NoForwards = (1ULL << 17), // Needs to return back to inline mode. - HasSwitchInlineButton = (1U << 18), + HasSwitchInlineButton = (1ULL << 18), // For "shared links" indexing. - HasTextLinks = (1U << 19), + HasTextLinks = (1ULL << 19), // Group / channel create or migrate service message. - IsGroupEssential = (1U << 20), + IsGroupEssential = (1ULL << 20), // Edited media is generated on the client // and should not update media from server. - IsLocalUpdateMedia = (1U << 21), + IsLocalUpdateMedia = (1ULL << 21), // Sent from inline bot, need to re-set media when sent. - FromInlineBot = (1U << 22), + FromInlineBot = (1ULL << 22), // Generated on the client side and should be unread. - ClientSideUnread = (1U << 23), + ClientSideUnread = (1ULL << 23), // In a supergroup. - HasAdminBadge = (1U << 24), + HasAdminBadge = (1ULL << 24), // Outgoing message that is being sent. - BeingSent = (1U << 25), + BeingSent = (1ULL << 25), // Outgoing message and failed to be sent. - SendingFailed = (1U << 26), + SendingFailed = (1ULL << 26), // No media and only a several emoji or an only custom emoji text. - SpecialOnlyEmoji = (1U << 27), + SpecialOnlyEmoji = (1ULL << 27), // Message existing in the message history. - HistoryEntry = (1U << 28), + HistoryEntry = (1ULL << 28), // Local message, not existing on the server. - Local = (1U << 29), + Local = (1ULL << 29), // Fake message for some UI element. - FakeHistoryItem = (1U << 30), + FakeHistoryItem = (1ULL << 30), // Contact sign-up message, notification should be skipped for Silent. - IsContactSignUp = (1U << 31), + IsContactSignUp = (1ULL << 31), + + // Optimization for item text custom emoji repainting. + CustomEmojiRepainting = (1ULL << 32), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index 599f6936f..9a9cf3e92 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -724,7 +724,7 @@ void GenerateItems( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, - message, + std::move(message), peerToUser(from->id), photo)); }; @@ -1060,7 +1060,7 @@ void GenerateItems( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, - message, + std::move(message), peerToUser(from->id))); } }; @@ -1137,7 +1137,7 @@ void GenerateItems( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, - message, + std::move(message), peerToUser(from->id))); } }; @@ -1233,7 +1233,7 @@ void GenerateItems( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, - message, + std::move(message), peerToUser(from->id))); }; @@ -1308,7 +1308,7 @@ void GenerateItems( history->nextNonHistoryEntryId(), MessageFlag::AdminLogEntry, date, - message, + std::move(message), peerToUser(from->id), nullptr)); }; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 56c2d1b09..12f09363a 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2312,7 +2312,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto media = (view ? view->media() : nullptr); const auto mediaHasTextForCopy = media && media->hasTextForCopy(); if (const auto document = media ? media->getDocument() : nullptr) { - if (!item->isIsolatedEmoji() && document->sticker()) { + if (!view->isIsolatedEmoji() && document->sticker()) { if (document->sticker()->set) { _menu->addAction(document->isStickerSetInstalled() ? tr::lng_context_pack_info(tr::now) : tr::lng_context_pack_add(tr::now), [=] { showStickerPackInfo(document); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 297186e50..ce3f4fe92 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -376,8 +376,8 @@ void HistoryItem::invalidateChatListEntry() { } void HistoryItem::customEmojiRepaint() { - if (!_customEmojiRepaintScheduled) { - _customEmojiRepaintScheduled = true; + if (!(_flags & MessageFlag::CustomEmojiRepainting)) { + _flags |= MessageFlag::CustomEmojiRepainting; history()->owner().requestItemRepaint(this); } } @@ -1250,7 +1250,7 @@ MessageGroupId HistoryItem::groupId() const { } bool HistoryItem::isEmpty() const { - return _text.isEmpty() + return _text.empty() && !_media && !Has(); } @@ -1260,7 +1260,7 @@ TextWithEntities HistoryItem::notificationText() const { if (_media && !isService()) { return _media->notificationText(); } else if (!emptyText()) { - return _text.toTextWithEntities(); + return _text; } return TextWithEntities(); }(); @@ -1271,14 +1271,17 @@ TextWithEntities HistoryItem::notificationText() const { Ui::kQEllipsis); } +const std::vector &HistoryItem::customTextLinks() const { + static const auto result = std::vector(); + return result; +} + ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { auto result = [&]() -> ItemPreview { if (_media) { return _media->toPreview(options); } else if (!emptyText()) { - return { - .text = _text.toTextWithEntities() - }; + return { .text = _text }; } return {}; }(); @@ -1325,14 +1328,6 @@ TextWithEntities HistoryItem::inReplyText() const { }).text; } -Ui::Text::IsolatedEmoji HistoryItem::isolatedEmoji() const { - return {}; -} - -Ui::Text::OnlyCustomEmoji HistoryItem::onlyCustomEmoji() const { - return {}; -} - HistoryItem::~HistoryItem() { applyTTL(0); } @@ -1470,14 +1465,14 @@ not_null HistoryItem::Create( data.vdate().v, data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)); } else if (checked == MediaCheckResult::Empty) { - const auto text = HistoryService::PreparedText{ + auto text = HistoryService::PreparedText{ tr::lng_message_empty(tr::now, Ui::Text::WithEntities) }; return history->makeServiceMessage( id, FlagsFromMTP(id, data.vflags().v, localFlags), data.vdate().v, - text, + std::move(text), data.vfrom_id() ? peerFromMTP(*data.vfrom_id()) : PeerId(0)); } else if (checked == MediaCheckResult::HasTimeToLive) { return history->makeServiceMessage(id, data, localFlags); @@ -1489,9 +1484,12 @@ not_null HistoryItem::Create( } return history->makeServiceMessage(id, data, localFlags); }, [&](const MTPDmessageEmpty &data) -> HistoryItem* { - const auto text = HistoryService::PreparedText{ - tr::lng_message_empty(tr::now, Ui::Text::WithEntities) - }; - return history->makeServiceMessage(id, localFlags, TimeId(0), text); + return history->makeServiceMessage( + id, + localFlags, + TimeId(0), + HistoryService::PreparedText{ tr::lng_message_empty( + tr::now, + Ui::Text::WithEntities) }); }); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 57d76bb10..c402150b4 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -195,14 +195,6 @@ public: [[nodiscard]] bool isGroupMigrate() const { return isGroupEssential() && isEmpty(); } - [[nodiscard]] bool isIsolatedEmoji() const { - return (_flags & MessageFlag::SpecialOnlyEmoji) - && _text.isIsolatedEmoji(); - } - [[nodiscard]] bool isOnlyCustomEmoji() const { - return (_flags & MessageFlag::SpecialOnlyEmoji) - && _text.isOnlyCustomEmoji(); - } [[nodiscard]] bool hasViews() const { return _flags & MessageFlag::HasViews; } @@ -322,8 +314,6 @@ public: [[nodiscard]] virtual ItemPreview toPreview( ToPreviewOptions options) const; [[nodiscard]] virtual TextWithEntities inReplyText() const; - [[nodiscard]] virtual Ui::Text::IsolatedEmoji isolatedEmoji() const; - [[nodiscard]] virtual Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const; [[nodiscard]] virtual TextWithEntities originalText() const { return TextWithEntities(); } @@ -331,6 +321,8 @@ public: -> TextWithEntities { return TextWithEntities(); } + [[nodiscard]] virtual auto customTextLinks() const + -> const std::vector &; [[nodiscard]] virtual TextForMimeData clipboardText() const { return TextForMimeData(); } @@ -356,11 +348,9 @@ public: virtual void setRealId(MsgId newId); virtual void incrementReplyToTopCounter() { } - virtual void hideSpoilers() { - } [[nodiscard]] bool emptyText() const { - return _text.isEmpty(); + return _text.empty(); } [[nodiscard]] bool canPin() const; @@ -414,9 +404,6 @@ public: [[nodiscard]] bool computeDropForwardedInfo() const; virtual void setText(const TextWithEntities &textWithEntities) { } - [[nodiscard]] virtual bool textHasLinks() const { - return false; - } [[nodiscard]] MsgId replyToId() const; [[nodiscard]] MsgId replyToTop() const; @@ -494,10 +481,7 @@ protected: void applyTTL(const MTPDmessageService &data); void applyTTL(TimeId destroyAt); - Ui::Text::String _text = { st::msgMinWidth }; - int _textWidth = -1; - int _textHeight = 0; - bool _customEmojiRepaintScheduled = false; + TextWithEntities _text; struct SavedMediaData { TextWithEntities text; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index a1533f1a8..e41e93702 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -15,7 +15,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_unread_things.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_context_menu.h" // CopyPostLink. -#include "history/view/history_view_spoiler_click_handler.h" #include "history/view/media/history_view_media.h" // AddTimestampLinks. #include "chat_helpers/stickers_emoji_pack.h" #include "main/main_session.h" @@ -24,8 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/share_box.h" #include "ui/text/text_isolated_emoji.h" #include "ui/text/format_values.h" -#include "ui/item_text_options.h" -#include "core/ui_integration.h" #include "storage/storage_shared_media.h" #include "mtproto/mtproto_config.h" #include "data/notify/data_notify_settings.h" @@ -100,10 +97,7 @@ namespace { [[nodiscard]] TextWithEntities EnsureNonEmpty( const TextWithEntities &text = TextWithEntities()) { - if (!text.text.isEmpty()) { - return text; - } - return { QString::fromUtf8(":-("), EntitiesInText() }; + return !text.text.isEmpty() ? text : TextWithEntities{ u":-("_q }; } } // namespace @@ -384,7 +378,7 @@ HistoryMessage::HistoryMessage( _media = std::make_unique( this, Data::ComputeCallData(data)); - setEmptyText(); + setTextValue({}); }, [](const auto &) { Unexpected("Service message action type in HistoryMessage."); }); @@ -607,7 +601,7 @@ HistoryMessage::HistoryMessage( std::move(markup)); _media = std::make_unique(this, game); - setEmptyText(); + setTextValue({}); } HistoryMessage::HistoryMessage( @@ -863,10 +857,6 @@ void HistoryMessage::setCommentsItemId(FullMsgId id) { } } -void HistoryMessage::hideSpoilers() { - HistoryView::HideSpoilers(_text); -} - bool HistoryMessage::updateDependencyItem() { if (const auto reply = Get()) { const auto documentId = reply->replyToDocumentId; @@ -875,7 +865,7 @@ bool HistoryMessage::updateDependencyItem() { const auto mediaIdChanged = (documentId != reply->replyToDocumentId) || (webpageId != reply->replyToWebPageId); if (mediaIdChanged && generateLocalEntitiesByReply()) { - reapplyText(); + history()->owner().requestItemTextRefresh(this); } return result; } @@ -1315,7 +1305,7 @@ void HistoryMessage::applyEdition(const MTPDmessageService &message) { const auto wasGrouped = history()->owner().groups().isGrouped(this); setReplyMarkup({}); refreshMedia(nullptr); - setEmptyText(); + setTextValue({}); changeViewsCount(-1); setForwardsCount(-1); if (wasGrouped) { @@ -1337,14 +1327,13 @@ void HistoryMessage::applyEdition(const MTPMessageExtendedMedia &media) { void HistoryMessage::updateSentContent( const TextWithEntities &textWithEntities, const MTPMessageMedia *media) { - const auto isolated = isolatedEmoji(); setText(textWithEntities); if (_flags & MessageFlag::FromInlineBot) { if (!media || !_media || !_media->updateInlineResultMedia(*media)) { refreshSentMedia(media); } _flags &= ~MessageFlag::FromInlineBot; - } else if (media || _media || !isolated || isolated != isolatedEmoji()) { + } else if (media || _media) { if (!media || !_media || !_media->updateSentMedia(*media)) { refreshSentMedia(media); } @@ -1509,65 +1498,16 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) { break; } } - - if (_media && _media->consumeMessageText(textWithEntities)) { - setEmptyText(); - return; - } - - clearSpecialOnlyEmoji(); - const auto context = Core::MarkedTextContext{ - .session = &history()->session(), - .customEmojiRepaint = [=] { customEmojiRepaint(); }, - }; - _text.setMarkedText( - st::messageTextStyle, - withLocalEntities(textWithEntities), - Ui::ItemTextOptions(this), - context); - HistoryView::FillTextWithAnimatedSpoilers(_text); - if (!textWithEntities.text.isEmpty() && _text.isEmpty()) { - // If server has allowed some text that we've trim-ed entirely, - // just replace it with something so that UI won't look buggy. - _text.setMarkedText( - st::messageTextStyle, - EnsureNonEmpty(), - Ui::ItemTextOptions(this)); - } else if (!_media) { - checkSpecialOnlyEmoji(); - } - - _textWidth = -1; - _textHeight = 0; + setTextValue((_media && _media->consumeMessageText(textWithEntities)) + ? TextWithEntities() + : std::move(textWithEntities)); } -void HistoryMessage::reapplyText() { - setText(originalText()); - history()->owner().requestItemResize(this); -} - -void HistoryMessage::setEmptyText() { - clearSpecialOnlyEmoji(); - _text.setMarkedText( - st::messageTextStyle, - { QString(), EntitiesInText() }, - Ui::ItemTextOptions(this)); - - _textWidth = -1; - _textHeight = 0; -} - -void HistoryMessage::clearSpecialOnlyEmoji() { - if (!(_flags & MessageFlag::SpecialOnlyEmoji)) { - return; - } - history()->session().emojiStickersPack().remove(this); - _flags &= ~MessageFlag::SpecialOnlyEmoji; -} - -void HistoryMessage::checkSpecialOnlyEmoji() { - if (history()->session().emojiStickersPack().add(this)) { - _flags |= MessageFlag::SpecialOnlyEmoji; +void HistoryMessage::setTextValue(TextWithEntities text) { + const auto had = !_text.empty(); + _text = std::move(text); + if (had) { + history()->owner().requestItemTextRefresh(this); } } @@ -1616,19 +1556,8 @@ void HistoryMessage::setReplyMarkup(HistoryMessageMarkupData &&markup) { } } -Ui::Text::IsolatedEmoji HistoryMessage::isolatedEmoji() const { - return _text.toIsolatedEmoji(); -} - -Ui::Text::OnlyCustomEmoji HistoryMessage::onlyCustomEmoji() const { - return _text.toOnlyCustomEmoji(); -} - TextWithEntities HistoryMessage::originalText() const { - if (emptyText()) { - return { QString(), EntitiesInText() }; - } - return _text.toTextWithEntities(); + return _text; } TextWithEntities HistoryMessage::originalTextWithLocalEntities() const { @@ -1636,14 +1565,7 @@ TextWithEntities HistoryMessage::originalTextWithLocalEntities() const { } TextForMimeData HistoryMessage::clipboardText() const { - if (emptyText()) { - return TextForMimeData(); - } - return _text.toTextForMimeData(); -} - -bool HistoryMessage::textHasLinks() const { - return emptyText() ? false : _text.hasLinks(); + return TextForMimeData::WithExpandedLinks(_text); } bool HistoryMessage::changeViewsCount(int count) { @@ -1923,7 +1845,7 @@ void HistoryMessage::dependencyItemRemoved(HistoryItem *dependency) { reply->itemRemoved(this, dependency); if (documentId != reply->replyToDocumentId && generateLocalEntitiesByReply()) { - reapplyText(); + history()->owner().requestItemTextRefresh(this); } } } diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 17348551b..ccc25e8be 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -181,13 +181,10 @@ public: [[nodiscard]] Storage::SharedMediaTypesMask sharedMediaTypes() const override; void setText(const TextWithEntities &textWithEntities) override; - [[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const override; - [[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const override; [[nodiscard]] TextWithEntities originalText() const override; [[nodiscard]] auto originalTextWithLocalEntities() const -> TextWithEntities override; [[nodiscard]] TextForMimeData clipboardText() const override; - [[nodiscard]] bool textHasLinks() const override; [[nodiscard]] int viewsCount() const override; [[nodiscard]] int repliesCount() const override; @@ -212,7 +209,6 @@ public: [[nodiscard]] MsgId dependencyMsgId() const override { return replyToId(); } - void hideSpoilers() override; void applySentMessage(const MTPDmessage &data) override; void applySentMessage( @@ -227,7 +223,7 @@ public: ~HistoryMessage(); private: - void setEmptyText(); + void setTextValue(TextWithEntities text); [[nodiscard]] bool isTooOldForEdit(TimeId now) const; [[nodiscard]] bool isLegacyMessage() const { return _flags & MessageFlag::Legacy; @@ -235,9 +231,6 @@ private: [[nodiscard]] bool checkCommentsLinkedChat(ChannelId id) const; - void clearSpecialOnlyEmoji(); - void checkSpecialOnlyEmoji(); - // For an invoice button we replace the button text with a "Receipt" key. // It should show the receipt for the payed invoice. Still let mobile apps do that. void replaceBuyWithReceiptInMarkup(); @@ -271,7 +264,6 @@ private: [[nodiscard]] bool generateLocalEntitiesByReply() const; [[nodiscard]] TextWithEntities withLocalEntities( const TextWithEntities &textWithEntities) const; - void reapplyText(); [[nodiscard]] bool checkRepliesPts( const HistoryMessageRepliesData &data) const; diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 02135e316..b33edca12 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -20,7 +20,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history_item_components.h" #include "history/view/history_view_service_message.h" #include "history/view/history_view_item_preview.h" -#include "history/view/history_view_spoiler_click_handler.h" #include "data/data_folder.h" #include "data/data_session.h" #include "data/data_media_types.h" @@ -32,7 +31,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_group_call.h" // Data::GroupCall::id(). #include "core/application.h" #include "core/click_handler_types.h" -#include "core/ui_integration.h" #include "base/unixtime.h" #include "base/timer_rpl.h" #include "calls/calls_instance.h" // Core::App().calls().joinGroupCall. @@ -42,7 +40,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "storage/storage_shared_media.h" #include "payments/payments_checkout_process.h" // CheckoutProcess::Start. #include "ui/text/format_values.h" -#include "ui/text/text_options.h" #include "ui/text/text_utilities.h" namespace { @@ -636,8 +633,8 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { return result; }; - const auto messageText = action.match([&]( - const MTPDmessageActionChatAddUser &data) { + setServiceText(action.match([&]( + const MTPDmessageActionChatAddUser &data) { return prepareChatAddUserText(data); }, [&](const MTPDmessageActionChatJoinedByLink &data) { return prepareChatJoinedByLink(data); @@ -714,9 +711,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { return PreparedText{ tr::lng_message_empty(tr::now, Ui::Text::WithEntities) }; - }); - - setServiceText(messageText); + })); // Additional information. applyAction(action); @@ -1219,11 +1214,11 @@ HistoryService::HistoryService( MsgId id, MessageFlags flags, TimeId date, - const PreparedText &message, + PreparedText &&message, PeerId from, PhotoData *photo) : HistoryItem(history, id, flags, date, from) { - setServiceText(message); + setServiceText(std::move(message)); if (photo) { _media = std::make_unique( this, @@ -1279,28 +1274,13 @@ ClickHandlerPtr HistoryService::fromLink() const { return _from->createOpenLink(); } -void HistoryService::setServiceText(const PreparedText &prepared) { - const auto context = Core::MarkedTextContext{ - .session = &history()->session(), - .customEmojiRepaint = [=] { customEmojiRepaint(); }, - }; - _text.setMarkedText( - st::serviceTextStyle, - prepared.text, - Ui::ItemTextServiceOptions(), - context); - HistoryView::FillTextWithAnimatedSpoilers(_text); - auto linkIndex = 0; - for (const auto &link : prepared.links) { - // Link indices start with 1. - _text.setLink(++linkIndex, link); +void HistoryService::setServiceText(PreparedText &&prepared) { + const auto had = !_text.empty(); + _text = std::move(prepared.text); + _textLinks = std::move(prepared.links); + if (had) { + history()->owner().requestItemTextRefresh(this); } - _textWidth = -1; - _textHeight = 0; -} - -void HistoryService::hideSpoilers() { - HistoryView::HideSpoilers(_text); } void HistoryService::markMediaAsReadHook() { @@ -1506,6 +1486,10 @@ void HistoryService::createFromMtp(const MTPDmessageService &message) { setMessageByAction(message.vaction()); } +const std::vector &HistoryService::customTextLinks() const { + return _textLinks; +} + void HistoryService::applyEdition(const MTPDmessageService &message) { clearDependency(); UpdateComponents(0); @@ -1525,8 +1509,6 @@ void HistoryService::removeMedia() { if (!_media) return; _media.reset(); - _textWidth = -1; - _textHeight = 0; history()->owner().requestItemResize(this); } @@ -1552,7 +1534,7 @@ void HistoryService::updateDependentText() { } void HistoryService::updateText(PreparedText &&text) { - setServiceText(text); + setServiceText(std::move(text)); history()->owner().requestItemResize(this); invalidateChatListEntry(); history()->owner().updateDependentMessages(this); diff --git a/Telegram/SourceFiles/history/history_service.h b/Telegram/SourceFiles/history/history_service.h index 2c788b911..daac1eacf 100644 --- a/Telegram/SourceFiles/history/history_service.h +++ b/Telegram/SourceFiles/history/history_service.h @@ -77,7 +77,7 @@ class HistoryService : public HistoryItem { public: struct PreparedText { TextWithEntities text; - QList links; + std::vector links; }; HistoryService( @@ -95,7 +95,7 @@ public: MsgId id, MessageFlags flags, TimeId date, - const PreparedText &message, + PreparedText &&message, PeerId from = 0, PhotoData *photo = nullptr); @@ -113,6 +113,8 @@ public: return true; } + const std::vector &customTextLinks() const override; + void applyEdition(const MTPDmessageService &message) override; crl::time getSelfDestructIn(crl::time now) override; @@ -131,9 +133,7 @@ public: not_null delegate, HistoryView::Element *replacing = nullptr) override; - void setServiceText(const PreparedText &prepared); - - void hideSpoilers() override; + void setServiceText(PreparedText &&prepared); ~HistoryService(); @@ -187,6 +187,8 @@ private: friend class HistoryView::Service; + std::vector _textLinks; + }; [[nodiscard]] not_null GenerateJoinedMessage( diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index a2ae6490f..9c8a4acbd 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -21,11 +21,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/reactions/history_view_reactions_button.h" #include "history/view/reactions/history_view_reactions.h" #include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_spoiler_click_handler.h" #include "history/history.h" #include "base/unixtime.h" #include "core/application.h" #include "core/core_settings.h" #include "core/click_handler_types.h" +#include "core/ui_integration.h" #include "main/main_session.h" #include "main/main_domain.h" #include "chat_helpers/stickers_emoji_pack.h" @@ -34,6 +36,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_style.h" #include "ui/toast/toast.h" #include "ui/toasts/common_toasts.h" +#include "ui/text/text_options.h" +#include "ui/item_text_options.h" #include "ui/painter.h" #include "data/data_session.h" #include "data/data_groups.h" @@ -355,11 +359,16 @@ void DateBadge::paint( Element::Element( not_null delegate, not_null data, - Element *replacing) + Element *replacing, + Flag serviceFlag) : _delegate(delegate) , _data(data) +, _dateTime(IsItemScheduledUntilOnline(data) + ? QDateTime() + : ItemDateTime(data)) +, _text(st::msgMinWidth) , _isScheduledUntilOnline(IsItemScheduledUntilOnline(data)) -, _dateTime(_isScheduledUntilOnline ? QDateTime() : ItemDateTime(data)) +, _flags(serviceFlag | Flag::NeedsResize) , _context(delegate->elementContext()) { history()->owner().registerItemView(this); refreshMedia(replacing); @@ -403,16 +412,36 @@ void Element::setY(int y) { void Element::refreshDataIdHook() { } +void Element::clearSpecialOnlyEmoji() { + if (!(_flags & Flag::SpecialOnlyEmoji)) { + return; + } + history()->session().emojiStickersPack().remove(this); + _flags &= ~Flag::SpecialOnlyEmoji; +} + +void Element::checkSpecialOnlyEmoji() { + if (history()->session().emojiStickersPack().add(this)) { + _flags |= Flag::SpecialOnlyEmoji; + } +} + +void Element::hideSpoilers() { + if (_text.hasSpoilers()) { + _text.setSpoilerRevealed(false, anim::type::instant); + } +} + void Element::customEmojiRepaint() { - if (!_customEmojiRepaintScheduled) { - _customEmojiRepaintScheduled = true; + if (!(_flags & Flag::CustomEmojiRepainting)) { + _flags |= Flag::CustomEmojiRepainting; history()->owner().requestViewRepaint(this); } } void Element::clearCustomEmojiRepaint() const { - _customEmojiRepaintScheduled = false; - data()->_customEmojiRepaintScheduled = false; + _flags &= ~Flag::CustomEmojiRepainting; + data()->_flags &= ~MessageFlag::CustomEmojiRepainting; } void Element::prepareCustomEmojiPaint( @@ -548,35 +577,33 @@ void Element::refreshMedia(Element *replacing) { _flags &= ~Flag::HiddenByGroup; const auto item = data(); - const auto media = item->media(); - if (media && media->canBeGrouped()) { - if (const auto group = history()->owner().groups().find(item)) { - if (group->items.front() != item) { - _media = nullptr; - _flags |= Flag::HiddenByGroup; - } else { - _media = std::make_unique( - this, - group->items); - if (!pendingResize()) { - history()->owner().requestViewResize(this); + if (const auto media = item->media()) { + if (media->canBeGrouped()) { + if (const auto group = history()->owner().groups().find(item)) { + if (group->items.front() != item) { + _media = nullptr; + _flags |= Flag::HiddenByGroup; + } else { + _media = std::make_unique( + this, + group->items); + if (!pendingResize()) { + history()->owner().requestViewResize(this); + } } + return; } - return; } - } - const auto session = &history()->session(); - if (const auto media = _data->media()) { _media = media->createView(this, replacing); - } else if (_data->isOnlyCustomEmoji() + } else if (isOnlyCustomEmoji() && Core::App().settings().largeEmoji()) { _media = std::make_unique( this, - std::make_unique(this, _data->onlyCustomEmoji())); - } else if (_data->isIsolatedEmoji() + std::make_unique(this, onlyCustomEmoji())); + } else if (isIsolatedEmoji() && Core::App().settings().largeEmoji()) { - const auto emoji = _data->isolatedEmoji(); - const auto emojiStickers = &session->emojiStickersPack(); + const auto emoji = isolatedEmoji(); + const auto emojiStickers = &history()->session().emojiStickersPack(); const auto skipPremiumEffect = false; if (const auto sticker = emojiStickers->stickerForEmoji(emoji)) { _media = std::make_unique( @@ -597,6 +624,90 @@ void Element::refreshMedia(Element *replacing) { } } +Ui::Text::IsolatedEmoji Element::isolatedEmoji() const { + return _text.toIsolatedEmoji(); +} + +Ui::Text::OnlyCustomEmoji Element::onlyCustomEmoji() const { + return _text.toOnlyCustomEmoji(); +} + +const Ui::Text::String &Element::text() const { + return _text; +} + +int Element::textHeightFor(int textWidth) { + validateText(); + if (_textWidth != textWidth) { + _textWidth = textWidth; + _textHeight = _text.countHeight(textWidth); + } + return _textHeight; +} + +void Element::validateText() { + const auto item = data(); + const auto &text = item->_text; + if (_text.isEmpty() == text.empty()) { + return; + } + const auto context = Core::MarkedTextContext{ + .session = &history()->session(), + .customEmojiRepaint = [=] { customEmojiRepaint(); }, + }; + if (_flags & Flag::ServiceMessage) { + _text.setMarkedText( + st::serviceTextStyle, + text, + Ui::ItemTextServiceOptions(), + context); + auto linkIndex = 0; + for (const auto &link : item->customTextLinks()) { + // Link indices start with 1. + _text.setLink(++linkIndex, link); + } + } else { + clearSpecialOnlyEmoji(); + const auto context = Core::MarkedTextContext{ + .session = &history()->session(), + .customEmojiRepaint = [=] { customEmojiRepaint(); }, + }; + _text.setMarkedText( + st::messageTextStyle, + item->originalTextWithLocalEntities(), + Ui::ItemTextOptions(item), + context); + if (!text.empty() && _text.isEmpty()) { + // If server has allowed some text that we've trim-ed entirely, + // just replace it with something so that UI won't look buggy. + _text.setMarkedText( + st::messageTextStyle, + { u":-("_q }, + Ui::ItemTextOptions(item)); + } + if (!item->media()) { + checkSpecialOnlyEmoji(); + refreshMedia(nullptr); + } + } + FillTextWithAnimatedSpoilers(this, _text); + _textWidth = -1; + _textHeight = 0; +} + +void Element::validateTextSkipBlock(bool has, int width, int height) { + validateText(); + if (!has) { + if (_text.removeSkipBlock()) { + _textWidth = -1; + _textHeight = 0; + } + } else if (_text.updateSkipBlock(width, height)) { + _textWidth = -1; + _textHeight = 0; + } +} + void Element::previousInBlocksChanged() { recountDisplayDateInBlocks(); recountAttachToPreviousInBlocks(); @@ -938,6 +1049,19 @@ bool Element::isSignedAuthorElided() const { void Element::itemDataChanged() { } +void Element::itemTextUpdated() { + if (const auto media = _media.get()) { + media->parentTextUpdated(); + } + clearSpecialOnlyEmoji(); + _text = Ui::Text::String(st::msgMinWidth); + _textWidth = -1; + _textHeight = 0; + if (_media && !data()->media()) { + refreshMedia(nullptr); + } +} + void Element::unloadHeavyPart() { history()->owner().unregisterHeavyViewPart(this); if (_media) { @@ -945,7 +1069,7 @@ void Element::unloadHeavyPart() { } if (_heavyCustomEmoji) { _heavyCustomEmoji = false; - data()->_text.unloadPersistentAnimation(); + _text.unloadPersistentAnimation(); if (const auto reply = data()->Get()) { reply->replyToText.unloadPersistentAnimation(); } @@ -1125,7 +1249,7 @@ Element::~Element() { base::take(_media); if (_heavyCustomEmoji) { _heavyCustomEmoji = false; - data()->_text.unloadPersistentAnimation(); + _text.unloadPersistentAnimation(); checkHeavyPart(); } if (_data->mainView() == this) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 987206996..68142325e 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_object.h" #include "base/runtime_composer.h" #include "base/flags.h" +#include "base/weak_ptr.h" class History; class HistoryBlock; @@ -236,54 +237,71 @@ struct DateBadge : public RuntimeComponent { class Element : public Object , public RuntimeComposer - , public ClickHandlerHost { + , public ClickHandlerHost + , public base::has_weak_ptr { public: - Element( - not_null delegate, - not_null data, - Element *replacing); - enum class Flag : uchar { - NeedsResize = 0x01, - AttachedToPrevious = 0x02, - AttachedToNext = 0x04, - HiddenByGroup = 0x08, + ServiceMessage = 0x01, + NeedsResize = 0x02, + AttachedToPrevious = 0x04, + AttachedToNext = 0x08, + HiddenByGroup = 0x10, + SpecialOnlyEmoji = 0x20, + CustomEmojiRepainting = 0x40, }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } - not_null delegate() const; - not_null data() const; - not_null history() const; - Media *media() const; - Context context() const; + Element( + not_null delegate, + not_null data, + Element *replacing, + Flag serviceFlag); + + [[nodiscard]] not_null delegate() const; + [[nodiscard]] not_null data() const; + [[nodiscard]] not_null history() const; + [[nodiscard]] Media *media() const; + [[nodiscard]] Context context() const; void refreshDataId(); - QDateTime dateTime() const; + [[nodiscard]] QDateTime dateTime() const; - int y() const; + [[nodiscard]] int y() const; void setY(int y); - virtual int marginTop() const = 0; - virtual int marginBottom() const = 0; + [[nodiscard]] virtual int marginTop() const = 0; + [[nodiscard]] virtual int marginBottom() const = 0; void setPendingResize(); - bool pendingResize() const; - bool isUnderCursor() const; + [[nodiscard]] bool pendingResize() const; + [[nodiscard]] bool isUnderCursor() const; - bool isLastAndSelfMessage() const; + [[nodiscard]] bool isLastAndSelfMessage() const; - bool isAttachedToPrevious() const; - bool isAttachedToNext() const; + [[nodiscard]] bool isAttachedToPrevious() const; + [[nodiscard]] bool isAttachedToNext() const; - int skipBlockWidth() const; - int skipBlockHeight() const; - virtual int infoWidth() const; - virtual int bottomInfoFirstLineWidth() const; - virtual bool bottomInfoIsWide() const; + [[nodiscard]] int skipBlockWidth() const; + [[nodiscard]] int skipBlockHeight() const; + [[nodiscard]] virtual int infoWidth() const; + [[nodiscard]] virtual int bottomInfoFirstLineWidth() const; + [[nodiscard]] virtual bool bottomInfoIsWide() const; - bool isHiddenByGroup() const; - virtual bool isHidden() const; + [[nodiscard]] bool isHiddenByGroup() const; + [[nodiscard]] virtual bool isHidden() const; + + [[nodiscard]] bool isIsolatedEmoji() const { + return (_flags & Flag::SpecialOnlyEmoji) + && _text.isIsolatedEmoji(); + } + [[nodiscard]] bool isOnlyCustomEmoji() const { + return (_flags & Flag::SpecialOnlyEmoji) + && _text.isOnlyCustomEmoji(); + } + + [[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const; + [[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const; // For blocks context this should be called only from recountAttachToPreviousInBlocks(). void setAttachToPrevious(bool attachToNext); @@ -300,9 +318,9 @@ public: void createUnreadBar(rpl::producer text); void destroyUnreadBar(); - int displayedDateHeight() const; - bool displayDate() const; - bool isInOneDayWithPrevious() const; + [[nodiscard]] int displayedDateHeight() const; + [[nodiscard]] bool displayDate() const; + [[nodiscard]] bool isInOneDayWithPrevious() const; virtual void draw(Painter &p, const PaintContext &context) const = 0; [[nodiscard]] virtual PointState pointState(QPoint point) const = 0; @@ -382,8 +400,9 @@ public: [[nodiscard]] virtual bool isSignedAuthorElided() const; virtual void itemDataChanged(); + void itemTextUpdated(); - virtual bool hasHeavyPart() const; + [[nodiscard]] virtual bool hasHeavyPart() const; virtual void unloadHeavyPart(); void checkHeavyPart(); @@ -395,21 +414,21 @@ public: not_null item) const; // Legacy blocks structure. - HistoryBlock *block(); - const HistoryBlock *block() const; + [[nodiscard]] HistoryBlock *block(); + [[nodiscard]] const HistoryBlock *block() const; void attachToBlock(not_null block, int index); void removeFromBlock(); void refreshInBlock(); void setIndexInBlock(int index); - int indexInBlock() const; - Element *previousInBlocks() const; - Element *previousDisplayedInBlocks() const; - Element *nextInBlocks() const; - Element *nextDisplayedInBlocks() const; + [[nodiscard]] int indexInBlock() const; + [[nodiscard]] Element *previousInBlocks() const; + [[nodiscard]] Element *previousDisplayedInBlocks() const; + [[nodiscard]] Element *nextInBlocks() const; + [[nodiscard]] Element *nextDisplayedInBlocks() const; void previousInBlocksChanged(); void nextInBlocksRemoved(); - virtual QRect innerGeometry() const = 0; + [[nodiscard]] virtual QRect innerGeometry() const = 0; void customEmojiRepaint(); void prepareCustomEmojiPaint( @@ -421,6 +440,7 @@ public: const PaintContext &context, const Reactions::InlineList &reactions) const; void clearCustomEmojiRepaint() const; + void hideSpoilers(); [[nodiscard]] ClickHandlerPtr fromPhotoLink() const { return fromLink(); @@ -461,6 +481,14 @@ protected: virtual void refreshDataIdHook(); + [[nodiscard]] const Ui::Text::String &text() const; + [[nodiscard]] int textHeightFor(int textWidth); + void validateText(); + void validateTextSkipBlock(bool has, int width, int height); + + void clearSpecialOnlyEmoji(); + void checkSpecialOnlyEmoji(); + private: // This should be called only from previousInBlocksChanged() // to add required bits to the Composer mask @@ -483,22 +511,23 @@ private: const not_null _delegate; const not_null _data; + HistoryBlock *_block = nullptr; std::unique_ptr _media; mutable ClickHandlerPtr _fromLink; - bool _isScheduledUntilOnline = false; - mutable bool _heavyCustomEmoji = false; - mutable bool _customEmojiRepaintScheduled = false; - const QDateTime _dateTime; + mutable Ui::Text::String _text = { st::msgMinWidth }; + mutable int _textWidth = -1; + mutable int _textHeight = 0; + int _y = 0; - Context _context = Context(); - - Flags _flags = Flag::NeedsResize; - - HistoryBlock *_block = nullptr; int _indexInBlock = -1; + bool _isScheduledUntilOnline = false; + mutable bool _heavyCustomEmoji = false; + mutable Flags _flags = Flag(0); + Context _context = Context(); + }; } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 7960e44ab..12f85df4e 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -255,7 +255,7 @@ Message::Message( not_null delegate, not_null data, Element *replacing) -: Element(delegate, data, replacing) +: Element(delegate, data, replacing, Flag(0)) , _bottomInfo( &data->history()->owner().reactions(), BottomInfoDataFromMessage(this)) { @@ -447,17 +447,18 @@ auto Message::takeReactionAnimations() } QSize Message::performCountOptimalSize() { + validateText(); + updateViewButtonExistence(); + updateMediaInBubbleState(); + refreshRightBadge(); + refreshInfoSkipBlock(); + const auto item = message(); const auto media = this->media(); auto maxWidth = 0; auto minHeight = 0; - updateViewButtonExistence(); - updateMediaInBubbleState(); - refreshRightBadge(); - refreshInfoSkipBlock(); - const auto reactionsInBubble = _reactions && embedReactionsInBubble(); if (_reactions) { _reactions->initDimensions(); @@ -490,7 +491,7 @@ QSize Message::performCountOptimalSize() { if (context() == Context::Replies && item->isDiscussionPost()) { maxWidth = std::max(maxWidth, st::msgMaxWidth); } - minHeight = hasVisibleText() ? item->_text.minHeight() : 0; + minHeight = hasVisibleText() ? text().minHeight() : 0; if (reactionsInBubble) { const auto reactionsMaxWidth = st::msgPadding.left() + _reactions->maxWidth() @@ -529,8 +530,8 @@ QSize Message::performCountOptimalSize() { - st::msgPadding.left() - st::msgPadding.right(); if (hasVisibleText() && maxWidth < plainMaxWidth()) { - minHeight -= item->_text.minHeight(); - minHeight += item->_text.countHeight(innerWidth); + minHeight -= text().minHeight(); + minHeight += text().countHeight(innerWidth); } if (reactionsInBubble) { minHeight -= _reactions->minHeight(); @@ -1298,8 +1299,8 @@ void Message::paintText( const auto stm = context.messageStyle(); p.setPen(stm->historyTextFg); p.setFont(st::msgFont); - prepareCustomEmojiPaint(p, context, item->_text); - item->_text.draw(p, { + prepareCustomEmojiPaint(p, context, text()); + text().draw(p, { .position = trect.topLeft(), .availableWidth = trect.width(), .palette = &stm->textPalette, @@ -1606,7 +1607,7 @@ TextState Message::textState( result = entry->textState( point - QPoint(entryLeft, entryTop), request); - result.symbol += item->_text.length() + (mediaDisplayed ? media->fullSelectionLength() : 0); + result.symbol += text().length() + (mediaDisplayed ? media->fullSelectionLength() : 0); } } @@ -1632,18 +1633,18 @@ TextState Message::textState( if (point.y() >= mediaTop && point.y() < mediaTop + mediaHeight) { result = media->textState(point - QPoint(mediaLeft, mediaTop), request); - result.symbol += item->_text.length(); + result.symbol += text().length(); } else if (getStateText(point, trect, &result, request)) { checkBottomInfoState(); return result; } else if (point.y() >= trect.y() + trect.height()) { - result.symbol = item->_text.length(); + result.symbol = text().length(); } } else if (getStateText(point, trect, &result, request)) { checkBottomInfoState(); return result; } else if (point.y() >= trect.y() + trect.height()) { - result.symbol = item->_text.length(); + result.symbol = text().length(); } } checkBottomInfoState(); @@ -1665,7 +1666,7 @@ TextState Message::textState( } } else if (media && media->isDisplayed()) { result = media->textState(point - g.topLeft(), request); - result.symbol += item->_text.length(); + result.symbol += text().length(); } if (keyboard && item->isHistoryEntry()) { @@ -1938,7 +1939,7 @@ bool Message::getStateText( } const auto item = message(); if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) { - *outResult = TextState(item, item->_text.getState( + *outResult = TextState(item, text().getState( point - trect.topLeft(), trect.width(), request.forText())); @@ -2004,7 +2005,7 @@ TextForMimeData Message::selectedText(TextSelection selection) const { const auto media = this->media(); auto logEntryOriginalResult = TextForMimeData(); - auto textResult = item->_text.toTextForMimeData(selection); + auto textResult = text().toTextForMimeData(selection); auto skipped = skipTextSelection(selection); auto mediaDisplayed = (media && media->isDisplayed()); auto mediaResult = (mediaDisplayed || isHiddenByGroup()) @@ -2036,8 +2037,8 @@ TextSelection Message::adjustSelection( const auto item = message(); const auto media = this->media(); - auto result = item->_text.adjustSelection(selection, type); - auto beforeMediaLength = item->_text.length(); + auto result = text().adjustSelection(selection, type); + auto beforeMediaLength = text().length(); if (selection.to <= beforeMediaLength) { return result; } @@ -2370,13 +2371,13 @@ void Message::refreshDataIdHook() { int Message::plainMaxWidth() const { return st::msgPadding.left() - + (hasVisibleText() ? message()->_text.maxWidth() : 0) + + (hasVisibleText() ? text().maxWidth() : 0) + st::msgPadding.right(); } int Message::monospaceMaxWidth() const { return st::msgPadding.left() - + (hasVisibleText() ? message()->_text.countMaxMonospaceWidth() : 0) + + (hasVisibleText() ? text().countMaxMonospaceWidth() : 0) + st::msgPadding.right(); } @@ -2893,11 +2894,11 @@ TextSelection Message::skipTextSelection(TextSelection selection) const { if (selection.from == 0xFFFF) { return selection; } - return HistoryView::UnshiftItemSelection(selection, message()->_text); + return HistoryView::UnshiftItemSelection(selection, text()); } TextSelection Message::unskipTextSelection(TextSelection selection) const { - return HistoryView::ShiftItemSelection(selection, message()->_text); + return HistoryView::ShiftItemSelection(selection, text()); } QRect Message::innerGeometry() const { @@ -3058,15 +3059,7 @@ int Message::resizeContentGetHeight(int newWidth) { entry->resizeGetHeight(contentWidth); } } else { - if (hasVisibleText()) { - if (textWidth != item->_textWidth) { - item->_textWidth = textWidth; - item->_textHeight = item->_text.countHeight(textWidth); - } - newHeight = item->_textHeight; - } else { - newHeight = 0; - } + newHeight = hasVisibleText() ? textHeightFor(textWidth) : 0; if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) { newHeight += st::msgPadding.bottom(); if (mediaDisplayed) { @@ -3181,7 +3174,7 @@ void Message::refreshInfoSkipBlock() { const auto item = message(); const auto media = this->media(); const auto hasTextSkipBlock = [&] { - if (item->_text.isEmpty()) { + if (item->_text.empty()) { return false; } else if (item->Has()) { return false; @@ -3201,15 +3194,7 @@ void Message::refreshInfoSkipBlock() { _reactions->removeSkipBlock(); } } - if (!hasTextSkipBlock) { - if (item->_text.removeSkipBlock()) { - item->_textWidth = -1; - item->_textHeight = 0; - } - } else if (item->_text.updateSkipBlock(skipWidth, skipHeight)) { - item->_textWidth = -1; - item->_textHeight = 0; - } + validateTextSkipBlock(hasTextSkipBlock, skipWidth, skipHeight); } TimeId Message::displayedEditDate() const { diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 119a549ec..099e8a476 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "history/view/history_view_bottom_info.h" #include "ui/effects/animations.h" -#include "base/weak_ptr.h" class HistoryMessage; struct HistoryMessageEdited; @@ -47,7 +46,7 @@ struct PsaTooltipState : public RuntimeComponent { mutable bool buttonVisible = true; }; -class Message : public Element, public base::has_weak_ptr { +class Message final : public Element { public: Message( not_null delegate, diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.cpp b/Telegram/SourceFiles/history/view/history_view_service_message.cpp index e957ef736..ae0dd0d0a 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_service_message.cpp @@ -388,7 +388,7 @@ Service::Service( not_null delegate, not_null data, Element *replacing) -: Element(delegate, data, replacing) { +: Element(delegate, data, replacing, Flag::ServiceMessage) { } not_null Service::message() const { @@ -420,9 +420,7 @@ QSize Service::performCountCurrentSize(int newWidth) { const auto item = message(); const auto media = this->media(); - if (item->_text.isEmpty()) { - item->_textHeight = 0; - } else { + if (!text().isEmpty()) { auto contentWidth = newWidth; if (delegate()->elementIsChatWide()) { accumulate_min(contentWidth, st::msgMaxWidth + 2 * st::msgPhotoSkip + 2 * st::msgMargin.left()); @@ -433,15 +431,9 @@ QSize Service::performCountCurrentSize(int newWidth) { } auto nwidth = qMax(contentWidth - st::msgServicePadding.left() - st::msgServicePadding.right(), 0); - if (nwidth != item->_textWidth) { - item->_textWidth = nwidth; - item->_textHeight = item->_text.countHeight(nwidth); - } - if (contentWidth >= maxWidth()) { - newHeight += minHeight(); - } else { - newHeight += item->_textHeight; - } + newHeight += (contentWidth >= maxWidth()) + ? minHeight() + : textHeightFor(nwidth); newHeight += st::msgServicePadding.top() + st::msgServicePadding.bottom() + st::msgServiceMargin.top() + st::msgServiceMargin.bottom(); if (media) { newHeight += st::msgServiceMargin.top() + media->resizeGetHeight(media->maxWidth()); @@ -454,9 +446,10 @@ QSize Service::performCountCurrentSize(int newWidth) { QSize Service::performCountOptimalSize() { const auto item = message(); const auto media = this->media(); + validateText(); - auto maxWidth = item->_text.maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right(); - auto minHeight = item->_text.minHeight(); + auto maxWidth = text().maxWidth() + st::msgServicePadding.left() + st::msgServicePadding.right(); + auto minHeight = text().minHeight(); if (media) { media->initDimensions(); } @@ -533,14 +526,14 @@ void Service::draw(Painter &p, const PaintContext &context) const { context.st, g.left(), g.width(), - item->_text, + text(), trect); p.setBrush(Qt::NoBrush); p.setPen(st->msgServiceFg()); p.setFont(st::msgServiceFont); - prepareCustomEmojiPaint(p, context, item->_text); - item->_text.draw(p, { + prepareCustomEmojiPaint(p, context, text()); + text().draw(p, { .position = trect.topLeft(), .availableWidth = trect.width(), .align = style::al_top, @@ -618,7 +611,7 @@ TextState Service::textState(QPoint point, StateRequest request) const { if (trect.contains(point)) { auto textRequest = request.forText(); textRequest.align = style::al_center; - result = TextState(item, item->_text.getState( + result = TextState(item, text().getState( point - trect.topLeft(), trect.width(), textRequest)); @@ -652,13 +645,13 @@ void Service::updatePressed(QPoint point) { } TextForMimeData Service::selectedText(TextSelection selection) const { - return message()->_text.toTextForMimeData(selection); + return text().toTextForMimeData(selection); } TextSelection Service::adjustSelection( TextSelection selection, TextSelectType type) const { - return message()->_text.adjustSelection(selection, type); + return text().adjustSelection(selection, type); } EmptyPainter::EmptyPainter(not_null history) : _history(history) { diff --git a/Telegram/SourceFiles/history/view/history_view_service_message.h b/Telegram/SourceFiles/history/view/history_view_service_message.h index df34de79d..863b4ac80 100644 --- a/Telegram/SourceFiles/history/view/history_view_service_message.h +++ b/Telegram/SourceFiles/history/view/history_view_service_message.h @@ -18,7 +18,7 @@ struct CornersPixmaps; namespace HistoryView { -class Service : public Element { +class Service final : public Element { public: Service( not_null delegate, diff --git a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp b/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp index 82f13c8a4..e151a4ca9 100644 --- a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp +++ b/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.cpp @@ -12,49 +12,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_element.h" #include "main/main_session.h" #include "window/window_session_controller.h" +#include "base/weak_ptr.h" namespace HistoryView { namespace { class AnimatedSpoilerClickHandler final : public ClickHandler { public: - explicit AnimatedSpoilerClickHandler(Ui::Text::String &text) - : _text(text) { - } + AnimatedSpoilerClickHandler( + not_null view, + Ui::Text::String &text); void onClick(ClickContext context) const override; private: + base::weak_ptr _weak; Ui::Text::String &_text; }; +AnimatedSpoilerClickHandler::AnimatedSpoilerClickHandler( + not_null view, + Ui::Text::String &text) +: _weak(view) +, _text(text) { +} + void AnimatedSpoilerClickHandler::onClick(ClickContext context) const { const auto button = context.button; - if (button != Qt::LeftButton) { + const auto view = _weak.get(); + if (button != Qt::LeftButton || !view) { return; } const auto my = context.other.value(); if (const auto d = my.elementDelegate ? my.elementDelegate() : nullptr) { _text.setSpoilerRevealed(true, anim::type::normal); if (const auto controller = my.sessionWindow.get()) { - controller->session().data().registerShownSpoiler(my.itemId); + controller->session().data().registerShownSpoiler(view); } } } } // namespace -void FillTextWithAnimatedSpoilers(Ui::Text::String &text) { +void FillTextWithAnimatedSpoilers( + not_null view, + Ui::Text::String &text) { if (text.hasSpoilers()) { text.setSpoilerLink( - std::make_shared(text)); - } -} - -void HideSpoilers(Ui::Text::String &text) { - if (text.hasSpoilers()) { - text.setSpoilerRevealed(false, anim::type::instant); + std::make_shared(view, text)); } } diff --git a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.h b/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.h index e5a8f3936..3bef398b6 100644 --- a/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.h +++ b/Telegram/SourceFiles/history/view/history_view_spoiler_click_handler.h @@ -9,7 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { -void FillTextWithAnimatedSpoilers(Ui::Text::String &text); -void HideSpoilers(Ui::Text::String &text); +class Element; + +void FillTextWithAnimatedSpoilers( + not_null view, + Ui::Text::String &text); } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.cpp b/Telegram/SourceFiles/history/view/media/history_view_media.cpp index beb587202..b7c5960ea 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media.cpp @@ -208,7 +208,7 @@ Ui::Text::String Media::createCaption(not_null item) const { item->originalTextWithLocalEntities(), Ui::ItemTextOptions(item), context); - FillTextWithAnimatedSpoilers(result); + FillTextWithAnimatedSpoilers(_parent, result); if (const auto width = _parent->skipBlockWidth()) { result.updateSkipBlock(width, _parent->skipBlockHeight()); } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 18580e46a..06f3c837f 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 18580e46a1206f4ba875ed6d4da553d60407288f +Subproject commit 06f3c837f6b65868ec1ed048ba8843fbed247677