From a4e4681835717e44ef398b527eef1e9a751f9060 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Dec 2022 18:19:56 +0400 Subject: [PATCH] Add jump-to-topic panel in View as Messages. --- Telegram/SourceFiles/api/api_updates.cpp | 2 +- Telegram/SourceFiles/dialogs/dialogs.style | 3 +- .../dialogs/dialogs_inner_widget.cpp | 3 +- Telegram/SourceFiles/dialogs/dialogs_row.cpp | 2 +- .../SourceFiles/dialogs/dialogs_widget.cpp | 4 +- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 2 +- .../dialogs/ui/dialogs_message_view.cpp | 9 +- Telegram/SourceFiles/history/history.cpp | 54 ++- Telegram/SourceFiles/history/history.h | 20 +- .../history/history_inner_widget.cpp | 4 +- Telegram/SourceFiles/history/history_item.cpp | 2 +- .../SourceFiles/history/history_widget.cpp | 6 +- .../view/history_view_context_menu.cpp | 2 +- .../history/view/history_view_element.cpp | 31 +- .../history/view/history_view_element.h | 7 + .../history/view/history_view_message.cpp | 425 ++++++++++++++---- .../history/view/history_view_message.h | 17 +- .../view/history_view_top_bar_widget.cpp | 2 +- .../media/history_view_extended_preview.cpp | 3 +- .../history/view/media/history_view_gif.cpp | 3 +- .../view/media/history_view_location.cpp | 3 +- .../view/media/history_view_media_grouped.cpp | 1 + .../history/view/media/history_view_photo.cpp | 7 +- Telegram/SourceFiles/storage/file_upload.cpp | 2 +- Telegram/SourceFiles/ui/chat/chat.style | 6 + 25 files changed, 470 insertions(+), 150 deletions(-) diff --git a/Telegram/SourceFiles/api/api_updates.cpp b/Telegram/SourceFiles/api/api_updates.cpp index 9518430b3..c5fcf92c3 100644 --- a/Telegram/SourceFiles/api/api_updates.cpp +++ b/Telegram/SourceFiles/api/api_updates.cpp @@ -2213,7 +2213,7 @@ void Updates::feedUpdate(const MTPUpdate &update) { history->requestChatListMessage(); if (!history->folderKnown() || (!history->unreadCountKnown() - && !history->peer->isForum())) { + && !history->isForum())) { history->owner().histories().requestDialogEntry(history); } if (!channel->amCreator()) { diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 451cefcf3..0dc080092 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -86,8 +86,7 @@ forumDialogRow: DialogRow(defaultDialogRow) { forumDialogJumpArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFg }}; forumDialogJumpArrowOver: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgOver }}; forumDialogJumpArrowSkip: 8px; -forumDialogJumpArrowLeft: 3px; -forumDialogJumpArrowTop: 3px; +forumDialogJumpArrowPosition: point(3px, 3px); forumDialogJumpPadding: margins(8px, 3px, 8px, 3px); forumDialogJumpRadius: 11px; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 92d48847a..a58b00026 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -571,8 +571,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { bool mayBeActive) { const auto key = row->key(); const auto active = mayBeActive && (activeEntry.key == key); - const auto forum = key.history() - && key.history()->peer->isForum(); + const auto forum = key.history() && key.history()->isForum(); if (forum && !_topicJumpCache) { _topicJumpCache = std::make_unique(); } diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.cpp b/Telegram/SourceFiles/dialogs/dialogs_row.cpp index 85c47d986..3deb2232f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_row.cpp @@ -264,7 +264,7 @@ Row::Row(Key key, int index, int top) : _id(key), _top(top), _index(index) { void Row::recountHeight(float64 narrowRatio) { if (const auto history = _id.history()) { - _height = history->peer->isForum() + _height = history->isForum() ? anim::interpolate( st::forumDialogRow.height, st::defaultDialogRow.height, diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index a43d81f8a..fd278ff77 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -455,7 +455,7 @@ void Widget::chosenRow(const ChosenRow &row) { topic, row.message.fullId.msg, Window::SectionShow::Way::ClearStack); - } else if (history && history->peer->isForum() && !row.message.fullId) { + } else if (history && history->isForum() && !row.message.fullId) { const auto forum = history->peer->forum(); if (controller()->shownForum().current() == forum) { controller()->closeForum(); @@ -1916,7 +1916,7 @@ void Widget::dropEvent(QDropEvent *e) { controller()->content()->filesOrForwardDrop( thread, e->mimeData()); - if (!thread->owningHistory()->peer->isForum()) { + if (!thread->owningHistory()->isForum()) { hideChildList(); } controller()->widget()->raise(); diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 105ea4958..2e601b997 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -56,7 +56,7 @@ const auto kPsaBadgePrefix = "cloud_lng_badge_psa_"; } else if (const auto user = history->peer->asUser()) { return (user->onlineTill > 0); } - return !history->peer->isForum(); + return !history->isForum(); } void PaintRowTopRight( diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index eff37e2da..7159aa2d8 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -326,14 +326,11 @@ void MessageView::paint( rect.setLeft(rect.x() + _textCache.maxWidth()); } if (jump1) { - const auto x = (rect.width() > 0) - ? rect.x() - : finalRight; - const auto add = st::forumDialogJumpArrowLeft; - const auto y = rect.y() + st::forumDialogJumpArrowTop; + const auto position = st::forumDialogJumpArrowPosition + + QPoint((rect.width() > 0) ? rect.x() : finalRight, rect.y()); (context.selected ? st::forumDialogJumpArrowOver - : st::forumDialogJumpArrow).paint(p, x + add, y, context.width); + : st::forumDialogJumpArrow).paint(p, position, context.width); } } diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 63547c143..0db425b6d 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1271,7 +1271,7 @@ void History::newItemAdded(not_null item) { if (item->unread(this)) { if (unreadCountKnown()) { setUnreadCount(unreadCount() + 1); - } else if (!peer->isForum()) { + } else if (!isForum()) { owner().histories().requestDialogEntry(this); } } else { @@ -1800,7 +1800,7 @@ void History::setUnreadCount(int newUnreadCount) { if (_unreadCount == newUnreadCount) { return; } - const auto notifier = unreadStateChangeNotifier(!peer->isForum()); + const auto notifier = unreadStateChangeNotifier(!isForum()); _unreadCount = newUnreadCount; const auto lastOutgoing = [&] { @@ -1839,7 +1839,7 @@ void History::setUnreadMark(bool unread) { return; } const auto notifier = unreadStateChangeNotifier( - !unreadCount() && !peer->isForum()); + !unreadCount() && !isForum()); Thread::setUnreadMarkFlag(unread); } @@ -1871,7 +1871,7 @@ void History::setMuted(bool muted) { if (this->muted() == muted) { return; } else { - const auto state = peer->isForum() + const auto state = isForum() ? Dialogs::BadgesState() : computeBadgesState(); const auto notify = (state.unread || state.reaction); @@ -1984,7 +1984,7 @@ int History::chatListNameVersion() const { } void History::hasUnreadMentionChanged(bool has) { - if (peer->isForum()) { + if (isForum()) { return; } auto was = chatListUnreadState(); @@ -1997,7 +1997,7 @@ void History::hasUnreadMentionChanged(bool has) { } void History::hasUnreadReactionChanged(bool has) { - if (peer->isForum()) { + if (isForum()) { return; } auto was = chatListUnreadState(); @@ -2966,18 +2966,22 @@ HistoryItem *History::lastEditableMessage() const { } void History::resizeToWidth(int newWidth) { - const auto resizeAllItems = (_width != newWidth); - - if (!resizeAllItems && !hasPendingResizedItems()) { + using Request = HistoryBlock::ResizeRequest; + const auto request = (_flags & Flag::PendingAllItemsResize) + ? Request::ReinitAll + : (_width != newWidth) + ? Request::ResizeAll + : Request::ResizePending; + if (request == Request::ResizePending && !hasPendingResizedItems()) { return; } - _flags &= ~(Flag::HasPendingResizedItems); + _flags &= ~(Flag::HasPendingResizedItems | Flag::PendingAllItemsResize); _width = newWidth; int y = 0; for (const auto &block : blocks) { block->setY(y); - y += block->resizeGetHeight(newWidth, resizeAllItems); + y += block->resizeGetHeight(newWidth, request); } _height = y; } @@ -3024,6 +3028,11 @@ void History::forumChanged(Data::Forum *old) { if (cloudDraft(MsgId(0))) { updateChatListSortPosition(); } + _flags |= Flag::PendingAllItemsResize; +} + +bool History::isForum() const { + return (_flags & Flag::IsForum); } not_null History::migrateToOrMe() const { @@ -3441,14 +3450,25 @@ HistoryBlock::HistoryBlock(not_null history) : _history(history) { } -int HistoryBlock::resizeGetHeight(int newWidth, bool resizeAllItems) { +int HistoryBlock::resizeGetHeight(int newWidth, ResizeRequest request) { auto y = 0; - for (const auto &message : messages) { - message->setY(y); - if (resizeAllItems || message->pendingResize()) { + if (request == ResizeRequest::ReinitAll) { + for (const auto &message : messages) { + message->setY(y); + message->initDimensions(); y += message->resizeGetHeight(newWidth); - } else { - y += message->height(); + } + } else if (request == ResizeRequest::ResizeAll) { + for (const auto &message : messages) { + message->setY(y); + y += message->resizeGetHeight(newWidth); + } + } else { + for (const auto &message : messages) { + message->setY(y); + y += message->pendingResize() + ? message->resizeGetHeight(newWidth) + : message->height(); } } _height = y; diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 6285dcbb3..9f189af24 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -94,6 +94,7 @@ public: } void forumChanged(Data::Forum *old); + [[nodiscard]] bool isForum() const; not_null migrateToOrMe() const; History *migrateFrom() const; @@ -462,10 +463,11 @@ private: enum class Flag : uchar { HasPendingResizedItems = (1 << 0), - IsTopPromoted = (1 << 1), - IsForum = (1 << 2), - FakeUnreadWhileOpened = (1 << 3), - HasPinnedMessages = (1 << 4), + PendingAllItemsResize = (1 << 1), + IsTopPromoted = (1 << 2), + IsForum = (1 << 3), + FakeUnreadWhileOpened = (1 << 4), + HasPinnedMessages = (1 << 5), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { @@ -636,12 +638,18 @@ private: HistoryView::SendActionPainter _sendActionPainter; - }; +}; class HistoryBlock { public: using Element = HistoryView::Element; + enum class ResizeRequest { + ReinitAll = 0, + ResizeAll = 1, + ResizePending = 2, + }; + HistoryBlock(not_null history); HistoryBlock(const HistoryBlock &) = delete; HistoryBlock &operator=(const HistoryBlock &) = delete; @@ -652,7 +660,7 @@ public: void remove(not_null view); void refreshView(not_null view); - int resizeGetHeight(int newWidth, bool resizeAllItems); + int resizeGetHeight(int newWidth, ResizeRequest request); int y() const { return _y; } diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index af62f91e7..3cdcf3091 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -227,7 +227,7 @@ public: return _widget ? _widget->elementAnimationsPaused() : false; } bool elementHideReply(not_null view) override { - return false; + return view->isTopicRootReply(); } bool elementShownUnread(not_null view) override { return view->data()->unread(view->data()->history()); @@ -2096,7 +2096,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { } const auto repliesCount = item->repliesCount(); const auto withReplies = (repliesCount > 0); - const auto topicRootId = item->history()->peer->isForum() + const auto topicRootId = item->history()->isForum() ? item->topicRootId() : 0; if (topicRootId diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 80c99fc1c..cb694ccca 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -775,7 +775,7 @@ void HistoryItem::setRealId(MsgId newId) { } _history->owner().notifyItemDataChange(this); - _history->owner().requestItemRepaint(this); + _history->owner().requestItemResize(this); } bool HistoryItem::canPin() const { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 5136e58fe..10490f42f 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -2971,7 +2971,7 @@ void HistoryWidget::unreadCountUpdated() { } }); } else { - _cornerButtons.updateJumpDownVisibility(_history->peer->isForum() + _cornerButtons.updateJumpDownVisibility(_history->isForum() ? 0 : _history->chatListBadgesState().unreadCounter); } @@ -5991,7 +5991,7 @@ void HistoryWidget::handlePeerMigration() { } bool HistoryWidget::replyToPreviousMessage() { - if (!_history || _editMsgId || _history->peer->isForum()) { + if (!_history || _editMsgId || _history->isForum()) { return false; } const auto fullId = FullMsgId(_history->peer->id, _replyToId); @@ -6014,7 +6014,7 @@ bool HistoryWidget::replyToPreviousMessage() { } bool HistoryWidget::replyToNextMessage() { - if (!_history || _editMsgId || _history->peer->isForum()) { + if (!_history || _editMsgId || _history->isForum()) { return false; } const auto fullId = FullMsgId(_history->peer->id, _replyToId); diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index e91f4cbc5..8bedf035b 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -617,7 +617,7 @@ bool AddViewRepliesAction( || (context != Context::History && context != Context::Pinned)) { return false; } - const auto topicRootId = item->history()->peer->isForum() + const auto topicRootId = item->history()->isForum() ? item->topicRootId() : 0; const auto repliesCount = item->repliesCount(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index ff5a25197..0a805653e 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -566,6 +566,10 @@ bool Element::isBubbleAttachedToNext() const { return _flags & Flag::BubbleAttachedToNext; } +bool Element::isTopicRootReply() const { + return _flags & Flag::TopicRootReply; +} + int Element::skipBlockWidth() const { return st::msgDateSpace + infoWidth() - st::msgDateDelta.x(); } @@ -905,7 +909,8 @@ bool Element::computeIsAttachToPrevious(not_null previous) { < kAttachMessageToPreviousSecondsDelta) && mayBeAttached(this) && mayBeAttached(previous) - && (!previousMarkup || previousMarkup->hiddenBy(prev->media())); + && (!previousMarkup || previousMarkup->hiddenBy(prev->media())) + && (item->topicRootId() == prev->topicRootId()); if (possible) { const auto forwarded = item->Get(); const auto prevForwarded = prev->Get(); @@ -1068,17 +1073,35 @@ void Element::recountDisplayDateInBlocks() { } QSize Element::countOptimalSize() { + _flags &= ~Flag::NeedsResize; return performCountOptimalSize(); } QSize Element::countCurrentSize(int newWidth) { if (_flags & Flag::NeedsResize) { - _flags &= ~Flag::NeedsResize; initDimensions(); } return performCountCurrentSize(newWidth); } +void Element::refreshIsTopicRootReply() { + const auto topicRootReply = countIsTopicRootReply(); + if (topicRootReply) { + _flags |= Flag::TopicRootReply; + } else { + _flags &= ~Flag::TopicRootReply; + } +} + +bool Element::countIsTopicRootReply() const { + const auto item = data(); + if (!item->history()->isForum()) { + return false; + } + const auto replyTo = item->replyToId(); + return !replyTo || (item->topicRootId() == replyTo); +} + void Element::setDisplayDate(bool displayDate) { const auto item = data(); if (displayDate && !Has()) { @@ -1156,6 +1179,10 @@ bool Element::displayFromName() const { return false; } +bool Element::displayTopicButton() const { + return false; +} + bool Element::displayForwardedFrom() const { return false; } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 744156ce6..e32cc752d 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -254,6 +254,7 @@ public: SpecialOnlyEmoji = 0x0080, CustomEmojiRepainting = 0x0100, ScheduledUntilOnline = 0x0200, + TopicRootReply = 0x0400, }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { return true; } @@ -290,6 +291,8 @@ public: [[nodiscard]] bool isBubbleAttachedToPrevious() const; [[nodiscard]] bool isBubbleAttachedToNext() const; + [[nodiscard]] bool isTopicRootReply() const; + [[nodiscard]] int skipBlockWidth() const; [[nodiscard]] int skipBlockHeight() const; [[nodiscard]] virtual int infoWidth() const; @@ -373,6 +376,7 @@ public: [[nodiscard]] virtual bool displayFromPhoto() const; [[nodiscard]] virtual bool hasFromName() const; [[nodiscard]] virtual bool displayFromName() const; + [[nodiscard]] virtual bool displayTopicButton() const; [[nodiscard]] virtual bool displayForwardedFrom() const; [[nodiscard]] virtual bool hasOutLayout() const; [[nodiscard]] virtual bool drawBubble() const; @@ -497,6 +501,7 @@ protected: void clearSpecialOnlyEmoji(); void checkSpecialOnlyEmoji(); + void refreshIsTopicRootReply(); private: // This should be called only from previousInBlocksChanged() @@ -510,6 +515,8 @@ private: // HistoryView::Element::Flag::AttachedToPrevious. void recountAttachToPreviousInBlocks(); + [[nodiscard]] bool countIsTopicRootReply() const; + QSize countOptimalSize() final override; QSize countCurrentSize(int newWidth) final override; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index dcc5d41a5..46796cb06 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_message.h" #include "core/click_handler_types.h" // ClickHandlerContext +#include "core/ui_integration.h" #include "history/view/history_view_cursor_state.h" #include "history/history_item_components.h" #include "history/history_message.h" @@ -225,6 +226,23 @@ QString FastReplyText() { return tr::lng_fast_reply(tr::now); } +[[nodiscard]] ClickHandlerPtr MakeTopicButtonLink( + not_null topic, + MsgId messageId) { + const auto weak = base::make_weak(topic); + return std::make_shared([=](ClickContext context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + if (const auto strong = weak.get()) { + controller->showTopic( + strong, + messageId, + Window::SectionShow::Way::Forward); + } + } + }); +} + } // namespace style::color FromNameFg( @@ -260,11 +278,19 @@ style::color FromNameFg( struct Message::CommentsButton { std::unique_ptr ripple; - int rippleShift = 0; std::vector userpics; QImage cachedUserpics; ClickHandlerPtr link; QPoint lastPoint; + int rippleShift = 0; +}; + +struct Message::TopicButton { + std::unique_ptr ripple; + ClickHandlerPtr link; + Ui::Text::String name; + QPoint lastPoint; + int nameVersion = 0; }; struct Message::FromNameStatus { @@ -488,9 +514,11 @@ auto Message::takeReactionAnimations() QSize Message::performCountOptimalSize() { const auto item = message(); const auto markup = item->inlineReplyMarkup(); + refreshIsTopicRootReply(); validateText(); validateInlineKeyboard(markup); updateViewButtonExistence(); + refreshTopicButton(); updateMediaInBubbleState(); refreshRightBadge(); refreshInfoSkipBlock(); @@ -614,6 +642,15 @@ QSize Message::performCountOptimalSize() { } else if (via && !displayForwardedFrom()) { accumulate_max(maxWidth, st::msgPadding.left() + via->maxWidth + st::msgPadding.right()); } + if (displayTopicButton()) { + const auto padding = st::msgPadding + st::topicButtonPadding; + accumulate_max( + maxWidth, + (padding.left() + + _topicButton->name.maxWidth() + + st::topicButtonArrowSkip + + padding.right())); + } if (displayForwardedFrom()) { const auto skip1 = forwarded->psaType.isEmpty() ? 0 @@ -653,6 +690,34 @@ QSize Message::performCountOptimalSize() { return QSize(maxWidth, minHeight); } +void Message::refreshTopicButton() { + const auto item = message(); + if (isAttachedToPrevious() || context() != Context::History) { + _topicButton = nullptr; + } else if (const auto topic = item->topic()) { + if (!_topicButton) { + _topicButton = std::make_unique(); + } + const auto jumpToId = IsServerMsgId(item->id) ? item->id : MsgId(); + _topicButton->link = MakeTopicButtonLink(topic, jumpToId); + if (_topicButton->nameVersion != topic->titleVersion()) { + _topicButton->nameVersion = topic->titleVersion(); + const auto context = Core::MarkedTextContext{ + .session = &history()->session(), + .customEmojiRepaint = [=] { customEmojiRepaint(); }, + .customEmojiLoopLimit = 1, + }; + _topicButton->name.setMarkedText( + st::fwdTextStyle, + topic->titleWithIcon(), + kMarkupTextOptions, + context); + } + } else { + _topicButton = nullptr; + } +} + int Message::marginTop() const { auto result = 0; if (!isHidden()) { @@ -869,6 +934,7 @@ void Message::draw(Painter &p, const PaintContext &context) const { trect.setY(trect.y() - st::msgPadding.top()); } else { paintFromName(p, trect, context); + paintTopicButton(p, trect, context); paintForwardedInfo(p, trect, context); paintReplyInfo(p, trect, context); paintViaBotIdInfo(p, trect, context); @@ -1233,6 +1299,71 @@ void Message::paintFromName( trect.setY(trect.y() + st::msgNameFont->height); } +void Message::paintTopicButton( + Painter &p, + QRect &trect, + const PaintContext &context) const { + if (!displayTopicButton()) { + return; + } + trect.setTop(trect.top() + st::topicButtonSkip); + const auto padding = st::topicButtonPadding; + const auto availableWidth = trect.width(); + const auto height = padding.top() + + st::msgNameFont->height + + padding.bottom(); + const auto width = std::max( + std::min( + availableWidth, + (padding.left() + + _topicButton->name.maxWidth() + + st::topicButtonArrowSkip + + padding.right())), + height); + const auto rect = QRect(trect.x(), trect.y(), width, height); + + const auto st = context.st; + const auto stm = context.messageStyle(); + const auto skip = padding.right() + st::topicButtonArrowSkip; + auto color = stm->msgServiceFg->c; + color.setAlpha(color.alpha() / 8); + p.setPen(Qt::NoPen); + p.setBrush(color); + { + auto hq = PainterHighQualityEnabler(p); + p.drawRoundedRect(rect, height / 2, height / 2); + } + if (_topicButton->ripple) { + _topicButton->ripple->paint( + p, + rect.x(), + rect.y(), + this->width(), + &color); + if (_topicButton->ripple->empty()) { + _topicButton->ripple.reset(); + } + } + clearCustomEmojiRepaint(); + p.setPen(stm->msgServiceFg); + p.setTextPalette(stm->fwdTextPalette); + _topicButton->name.drawElided( + p, + trect.x() + padding.left(), + trect.y() + padding.top(), + width - padding.left() - skip); + + const auto &icon = st::topicButtonArrow; + icon.paint( + p, + rect.x() + rect.width() - skip + st::topicButtonArrowPosition.x(), + rect.y() + padding.top() + st::topicButtonArrowPosition.y(), + this->width(), + stm->msgServiceFg->c); + + trect.setY(trect.y() + height + st::topicButtonSkip); +} + void Message::paintForwardedInfo( Painter &p, QRect &trect, @@ -1388,6 +1519,7 @@ PointState Message::pointState(QPoint point) const { // trect.setY(trect.y() - st::msgPadding.top()); //} else { // if (getStateFromName(point, trect, &result)) return result; + // if (getStateTopicButton(point, trect, &result)) return result; // if (getStateForwardedInfo(point, trect, &result, request)) return result; // if (getStateReplyInfo(point, trect, &result)) return result; // if (getStateViaBotIdInfo(point, trect, &result)) return result; @@ -1432,6 +1564,8 @@ void Message::clickHandlerPressedChanged( return; } else if (_comments && (handler == _comments->link)) { toggleCommentsButtonRipple(pressed); + } else if (_topicButton && (handler == _topicButton->link)) { + toggleTopicButtonRipple(pressed); } else if (_viewButton) { _viewButton->checkLink(handler, pressed); } @@ -1444,7 +1578,7 @@ void Message::toggleCommentsButtonRipple(bool pressed) { return; } else if (pressed) { if (!_comments->ripple) { - createCommentsRipple(); + createCommentsButtonRipple(); } _comments->ripple->add(_comments->lastPoint + QPoint(_comments->rippleShift, 0)); @@ -1522,7 +1656,7 @@ BottomRippleMask Message::bottomRippleMask(int buttonHeight) const { }; } -void Message::createCommentsRipple() { +void Message::createCommentsButtonRipple() { auto mask = bottomRippleMask(st::historyCommentsButtonHeight); _comments->ripple = std::make_unique( st::defaultRippleAnimation, @@ -1531,6 +1665,45 @@ void Message::createCommentsRipple() { _comments->rippleShift = mask.shift; } +void Message::toggleTopicButtonRipple(bool pressed) { + Expects(_topicButton != nullptr); + + if (!drawBubble()) { + return; + } else if (pressed) { + if (!_topicButton->ripple) { + createTopicButtonRipple(); + } + _topicButton->ripple->add(_topicButton->lastPoint); + } else if (_topicButton->ripple) { + _topicButton->ripple->lastStop(); + } +} + +void Message::createTopicButtonRipple() { + const auto geometry = countGeometry().marginsRemoved(st::msgPadding); + const auto availableWidth = geometry.width(); + const auto padding = st::topicButtonPadding; + const auto height = padding.top() + + st::msgNameFont->height + + padding.bottom(); + const auto width = std::max( + std::min( + availableWidth, + (padding.left() + + _topicButton->name.maxWidth() + + st::topicButtonArrowSkip + + padding.right())), + height); + auto mask = Ui::RippleAnimation::RoundRectMask( + { width, height }, + height / 2); + _topicButton->ripple = std::make_unique( + st::defaultRippleAnimation, + std::move(mask), + [=] { repaint(); }); +} + bool Message::hasHeavyPart() const { return _comments || (_fromNameStatus && _fromNameStatus->custom) @@ -1692,6 +1865,9 @@ TextState Message::textState( if (getStateFromName(point, trect, &result)) { return result; } + if (getStateTopicButton(point, trect, &result)) { + return result; + } if (getStateForwardedInfo(point, trect, &result, request)) { return result; } @@ -1847,57 +2023,89 @@ bool Message::getStateFromName( QPoint point, QRect &trect, not_null outResult) const { - const auto item = message(); - if (displayFromName()) { - const auto replyWidth = [&] { - if (isUnderCursor() && displayFastReply()) { - return st::msgFont->width(FastReplyText()); + if (!displayFromName()) { + return false; + } + const auto replyWidth = [&] { + if (isUnderCursor() && displayFastReply()) { + return st::msgFont->width(FastReplyText()); + } + return 0; + }(); + if (replyWidth + && point.x() >= trect.left() + trect.width() - replyWidth + && point.x() < trect.left() + trect.width() + st::msgPadding.right() + && point.y() >= trect.top() - st::msgPadding.top() + && point.y() < trect.top() + st::msgServiceFont->height) { + outResult->link = fastReplyLink(); + return true; + } + if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) { + auto availableLeft = trect.left(); + auto availableWidth = trect.width(); + if (replyWidth) { + availableWidth -= st::msgPadding.right() + replyWidth; + } + const auto item = message(); + const auto from = item->displayFrom(); + const auto nameText = [&]() -> const Ui::Text::String * { + if (from) { + validateFromNameText(from); + return &_fromName; + } else if (const auto info = item->hiddenSenderInfo()) { + return &info->nameText(); + } else { + Unexpected("Corrupt forwarded information in message."); } - return 0; }(); - if (replyWidth - && point.x() >= trect.left() + trect.width() - replyWidth - && point.x() < trect.left() + trect.width() + st::msgPadding.right() - && point.y() >= trect.top() - st::msgPadding.top() - && point.y() < trect.top() + st::msgServiceFont->height) { - outResult->link = fastReplyLink(); + if (point.x() >= availableLeft + && point.x() < availableLeft + availableWidth + && point.x() < availableLeft + nameText->maxWidth()) { + outResult->link = fromLink(); return true; } - if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) { - auto availableLeft = trect.left(); - auto availableWidth = trect.width(); - if (replyWidth) { - availableWidth -= st::msgPadding.right() + replyWidth; - } - const auto from = item->displayFrom(); - const auto nameText = [&]() -> const Ui::Text::String * { - if (from) { - validateFromNameText(from); - return &_fromName; - } else if (const auto info = item->hiddenSenderInfo()) { - return &info->nameText(); - } else { - Unexpected("Corrupt forwarded information in message."); - } - }(); - if (point.x() >= availableLeft - && point.x() < availableLeft + availableWidth - && point.x() < availableLeft + nameText->maxWidth()) { - outResult->link = fromLink(); - return true; - } - auto via = item->Get(); - if (via - && !displayForwardedFrom() - && point.x() >= availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew - && point.x() < availableLeft + availableWidth - && point.x() < availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew + via->width) { - outResult->link = via->link; - return true; - } + auto via = item->Get(); + if (via + && !displayForwardedFrom() + && point.x() >= availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew + && point.x() < availableLeft + availableWidth + && point.x() < availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew + via->width) { + outResult->link = via->link; + return true; } - trect.setTop(trect.top() + st::msgNameFont->height); } + trect.setTop(trect.top() + st::msgNameFont->height); + return false; +} + +bool Message::getStateTopicButton( + QPoint point, + QRect &trect, + not_null outResult) const { + if (!displayTopicButton()) { + return false; + } + trect.setTop(trect.top() + st::topicButtonSkip); + const auto padding = st::topicButtonPadding; + const auto availableWidth = trect.width(); + const auto height = padding.top() + + st::msgNameFont->height + + padding.bottom(); + const auto width = std::max( + std::min( + availableWidth, + (padding.left() + + _topicButton->name.maxWidth() + + st::topicButtonArrowSkip + + padding.right())), + height); + const auto rect = QRect(trect.x(), trect.y(), width, height); + if (rect.contains(point)) { + outResult->link = _topicButton->link; + _topicButton->lastPoint = point - rect.topLeft(); + return true; + } + trect.setY(trect.y() + height + st::topicButtonSkip); return false; } @@ -1906,56 +2114,57 @@ bool Message::getStateForwardedInfo( QRect &trect, not_null outResult, StateRequest request) const { - if (displayForwardedFrom()) { - const auto item = message(); - const auto forwarded = item->Get(); - const auto skip1 = forwarded->psaType.isEmpty() - ? 0 - : st::historyPsaIconSkip1; - const auto skip2 = forwarded->psaType.isEmpty() - ? 0 - : st::historyPsaIconSkip2; - const auto fits = (forwarded->text.maxWidth() <= (trect.width() - skip1)); - const auto fwdheight = (fits ? 1 : 2) * st::semiboldFont->height; - if (point.y() >= trect.top() && point.y() < trect.top() + fwdheight) { - if (skip1) { - const auto &icon = st::historyPsaIconIn; - const auto position = fits - ? st::historyPsaIconPosition1 - : st::historyPsaIconPosition2; - const auto iconRect = QRect( - trect.x() + trect.width() - position.x() - icon.width(), - trect.y() + position.y(), - icon.width(), - icon.height()); - if (iconRect.contains(point)) { - if (const auto link = psaTooltipLink()) { - outResult->link = link; - return true; - } + if (!displayForwardedFrom()) { + return false; + } + const auto item = message(); + const auto forwarded = item->Get(); + const auto skip1 = forwarded->psaType.isEmpty() + ? 0 + : st::historyPsaIconSkip1; + const auto skip2 = forwarded->psaType.isEmpty() + ? 0 + : st::historyPsaIconSkip2; + const auto fits = (forwarded->text.maxWidth() <= (trect.width() - skip1)); + const auto fwdheight = (fits ? 1 : 2) * st::semiboldFont->height; + if (point.y() >= trect.top() && point.y() < trect.top() + fwdheight) { + if (skip1) { + const auto &icon = st::historyPsaIconIn; + const auto position = fits + ? st::historyPsaIconPosition1 + : st::historyPsaIconPosition2; + const auto iconRect = QRect( + trect.x() + trect.width() - position.x() - icon.width(), + trect.y() + position.y(), + icon.width(), + icon.height()); + if (iconRect.contains(point)) { + if (const auto link = psaTooltipLink()) { + outResult->link = link; + return true; } } - const auto useWidth = trect.width() - (fits ? skip1 : skip2); - const auto breakEverywhere = (forwarded->text.countHeight(useWidth) > 2 * st::semiboldFont->height); - auto textRequest = request.forText(); - if (breakEverywhere) { - textRequest.flags |= Ui::Text::StateRequest::Flag::BreakEverywhere; - } - *outResult = TextState(item, forwarded->text.getState( - point - trect.topLeft(), - useWidth, - textRequest)); - outResult->symbol = 0; - outResult->afterSymbol = false; - if (breakEverywhere) { - outResult->cursor = CursorState::Forwarded; - } else { - outResult->cursor = CursorState::None; - } - return true; } - trect.setTop(trect.top() + fwdheight); + const auto useWidth = trect.width() - (fits ? skip1 : skip2); + const auto breakEverywhere = (forwarded->text.countHeight(useWidth) > 2 * st::semiboldFont->height); + auto textRequest = request.forText(); + if (breakEverywhere) { + textRequest.flags |= Ui::Text::StateRequest::Flag::BreakEverywhere; + } + *outResult = TextState(item, forwarded->text.getState( + point - trect.topLeft(), + useWidth, + textRequest)); + outResult->symbol = 0; + outResult->afterSymbol = false; + if (breakEverywhere) { + outResult->cursor = CursorState::Forwarded; + } else { + outResult->cursor = CursorState::None; + } + return true; } + trect.setTop(trect.top() + fwdheight); return false; } @@ -2076,6 +2285,14 @@ void Message::updatePressed(QPoint point) { if (displayFromName()) { trect.setTop(trect.top() + st::msgNameFont->height); } + if (displayTopicButton()) { + trect.setTop(trect.top() + + st::topicButtonSkip + + st::topicButtonPadding.top() + + st::msgNameFont->height + + st::topicButtonPadding.bottom() + + st::topicButtonSkip); + } if (displayForwardedFrom()) { auto forwarded = item->Get(); auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; @@ -2652,6 +2869,10 @@ bool Message::hasBubble() const { return drawBubble(); } +bool Message::displayTopicButton() const { + return _topicButton != nullptr; +} + bool Message::unwrapped() const { const auto item = message(); if (isHidden()) { @@ -2946,6 +3167,7 @@ void Message::updateMediaInBubbleState() { auto mediaHasSomethingAbove = false; auto getMediaHasSomethingAbove = [&] { return displayFromName() + || displayTopicButton() || displayForwardedFrom() || displayedReply() || item->Has(); @@ -3064,6 +3286,13 @@ QRect Message::innerGeometry() const { // See paintFromName(). result.translate(0, st::msgNameFont->height); } + if (displayTopicButton()) { + result.translate(0, st::topicButtonSkip + + st::topicButtonPadding.top() + + st::msgNameFont->height + + st::topicButtonPadding.bottom() + + st::topicButtonSkip); + } // Skip displayForwardedFrom() until there are no animations for it. if (displayedReply()) { // See paintReplyInfo(). @@ -3284,6 +3513,14 @@ int Message::resizeContentGetHeight(int newWidth) { newHeight += st::msgNameFont->height; } + if (displayTopicButton()) { + newHeight += st::topicButtonSkip + + st::topicButtonPadding.top() + + st::msgNameFont->height + + st::topicButtonPadding.bottom() + + st::topicButtonSkip; + } + if (displayForwardedFrom()) { const auto forwarded = item->Get(); const auto skip1 = forwarded->psaType.isEmpty() diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 23375e7ef..eacc41caa 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -123,6 +123,7 @@ public: bool hasOutLayout() const override; bool drawBubble() const override; bool hasBubble() const override; + bool displayTopicButton() const override; bool unwrapped() const override; int minWidthForMedia() const override; bool hasFastReply() const override; @@ -167,6 +168,7 @@ protected: private: struct CommentsButton; struct FromNameStatus; + struct TopicButton; void initLogEntryOriginal(); void initPsa(); @@ -180,7 +182,10 @@ private: TextSelection selection) const; void toggleCommentsButtonRipple(bool pressed); - void createCommentsRipple(); + void createCommentsButtonRipple(); + + void toggleTopicButtonRipple(bool pressed); + void createTopicButtonRipple(); void paintCommentsButton( Painter &p, @@ -190,6 +195,10 @@ private: Painter &p, QRect &trect, const PaintContext &context) const; + void paintTopicButton( + Painter &p, + QRect &trect, + const PaintContext &context) const; void paintForwardedInfo( Painter &p, QRect &trect, @@ -217,6 +226,10 @@ private: QPoint point, QRect &trect, not_null outResult) const; + bool getStateTopicButton( + QPoint point, + QRect &trect, + not_null outResult) const; bool getStateForwardedInfo( QPoint point, QRect &trect, @@ -257,6 +270,7 @@ private: [[nodiscard]] bool displayGoToOriginal() const; [[nodiscard]] ClickHandlerPtr fastReplyLink() const; + void refreshTopicButton(); void refreshInfoSkipBlock(); [[nodiscard]] int plainMaxWidth() const; [[nodiscard]] int monospaceMaxWidth() const; @@ -279,6 +293,7 @@ private: mutable ClickHandlerPtr _fastReplyLink; mutable std::unique_ptr _viewButton; std::unique_ptr _reactions; + std::unique_ptr _topicButton; mutable std::unique_ptr _comments; mutable Ui::Text::String _fromName; diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp index 50df41975..968272e8c 100644 --- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp @@ -703,7 +703,7 @@ void TopBarWidget::backClicked() { _controller->closeFolder(); } else if (_activeChat.section == Section::ChatsList && _activeChat.key.history() - && _activeChat.key.history()->peer->isForum()) { + && _activeChat.key.history()->isForum()) { _controller->closeForum(); } else { _controller->showBackFromStack(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp index 136e1fc0a..941745a9f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -430,7 +430,8 @@ bool ExtendedPreview::needsBubble() const { || item->viaBot() || _parent->displayedReply() || _parent->displayForwardedFrom() - || _parent->displayFromName()); + || _parent->displayFromName() + || _parent->displayTopicButton()); } QPoint ExtendedPreview::resolveCustomInfoRightBottom() const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index cbe4e4ca0..a3b8db648 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -1308,7 +1308,8 @@ bool Gif::needsBubble() const { || item->viaBot() || _parent->displayedReply() || _parent->displayForwardedFrom() - || _parent->displayFromName(); + || _parent->displayFromName() + || _parent->displayTopicButton(); return false; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_location.cpp b/Telegram/SourceFiles/history/view/media/history_view_location.cpp index 62ac28081..cb74069a9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_location.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_location.cpp @@ -377,7 +377,8 @@ bool Location::needsBubble() const { || item->viaBot() || _parent->displayedReply() || _parent->displayForwardedFrom() - || _parent->displayFromName(); + || _parent->displayFromName() + || _parent->displayTopicButton(); } QPoint Location::resolveCustomInfoRightBottom() const { diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp index 79bf5304c..36b706491 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -775,6 +775,7 @@ bool GroupedMedia::computeNeedBubble() const { || _parent->displayedReply() || _parent->displayForwardedFrom() || _parent->displayFromName() + || _parent->displayTopicButton() ) { return true; } diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 6b13de487..a0306c0e7 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -412,7 +412,7 @@ void Photo::paintUserpicFrame( auto request = ::Media::Streaming::FrameRequest(); request.outer = size * cIntRetinaFactor(); request.resize = size * cIntRetinaFactor(); - const auto forum = _parent->data()->history()->peer->isForum(); + const auto forum = _parent->data()->history()->isForum(); if (forum) { request.rounding = Images::CornersMaskRef( Images::CornersMask(ImageRoundRadius::Large)); @@ -439,7 +439,7 @@ void Photo::paintUserpicFrame( return; } const auto pix = [&] { - const auto forum = _parent->data()->history()->peer->isForum(); + const auto forum = _parent->data()->history()->isForum(); const auto args = Images::PrepareArgs{ .options = (forum ? Images::Option::RoundLarge @@ -885,7 +885,8 @@ bool Photo::needsBubble() const { || item->viaBot() || _parent->displayedReply() || _parent->displayForwardedFrom() - || _parent->displayFromName()); + || _parent->displayFromName() + || _parent->displayTopicButton()); } QPoint Photo::resolveCustomInfoRightBottom() const { diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp index 08990076f..a90860894 100644 --- a/Telegram/SourceFiles/storage/file_upload.cpp +++ b/Telegram/SourceFiles/storage/file_upload.cpp @@ -261,7 +261,7 @@ void Uploader::sendProgressUpdate( if (history->peer->isMegagroup()) { manager.update(history, replyTo, type, progress); } - } else if (history->peer->isForum()) { + } else if (history->isForum()) { manager.update(history, item->topicRootId(), type, progress); } _api->session().data().requestItemRepaint(item); diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index a2a43c011..d51e9aa9c 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -649,6 +649,12 @@ historyPinnedBotButton: RoundButton(defaultActiveButton) { } historyPinnedBotButtonMaxWidth: 150px; +topicButtonSkip: 3px; +topicButtonPadding: margins(6px, 3px, 8px, 3px); +topicButtonArrowSkip: 8px; +topicButtonArrowPosition: point(3px, 3px); +topicButtonArrow: icon{{ "dialogs/dialogs_topic_arrow", historyReplyIconFg }}; + msgBotKbDuration: 200; msgBotKbFont: semiboldFont; msgBotKbIconPadding: 4px;