diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index deb786a28..89a2e6b49 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -579,11 +579,6 @@ bool InnerWidget::elementUnderCursor( return (Element::Hovered() == view); } -float64 InnerWidget::elementHighlightOpacity( - not_null item) const { - return 0.; -} - bool InnerWidget::elementInSelectionMode() { return false; } diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h index 36c321810..da7653b1b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -93,8 +93,6 @@ public: HistoryView::Context elementContext() override; bool elementUnderCursor( not_null view) override; - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const override; bool elementInSelectionMode() override; bool elementIntersectsRange( not_null view, diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c4284cd88..2adb08577 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -202,10 +202,6 @@ public: not_null view) override { return (Element::Moused() == view); } - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const override { - return _widget ? _widget->elementHighlightOpacity(item) : 0.; - } bool elementInSelectionMode() override { return _widget ? _widget->inSelectionMode() : false; } @@ -1060,6 +1056,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { auto clip = e->rect(); auto context = preparePaintContext(clip); + context.highlightPathCache = &_highlightPathCache; _pathGradient->startFrame( 0, width(), @@ -1143,7 +1140,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { } else if (item->isUnreadMention() && !item->isUnreadMedia()) { readContents.insert(item); - _widget->enqueueMessageHighlight(view); + _widget->enqueueMessageHighlight(view, {}); } } session().data().reactions().poll(item, context.now); @@ -1185,6 +1182,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { view, selfromy - mtop, seltoy - mtop); + context.highlight = _widget->itemHighlight(view->data()); view->draw(p, context); processPainted(view, top, height); @@ -1219,9 +1217,10 @@ void HistoryInner::paintEvent(QPaintEvent *e) { const auto &sendingAnimation = _controller->sendingAnimation(); while (top < drawToY) { const auto height = view->height(); + const auto item = view->data(); if ((context.clip.y() < height) && (hdrawtop < top + height) - && !sendingAnimation.hasAnimatedMessage(view->data())) { + && !sendingAnimation.hasAnimatedMessage(item)) { context.reactionInfo = _reactionsManager->currentReactionPaintInfo(); context.outbg = view->hasOutLayout(); @@ -1229,6 +1228,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) { view, selfromy - htop, seltoy - htop); + context.highlight = _widget->itemHighlight(item); view->draw(p, context); processPainted(view, top, height); } @@ -2410,6 +2410,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { _menu->addAction(text, [=] { if (canSendReply) { _widget->replyToMessage({ itemId, quote }); + if (!quote.empty()) { + _widget->clearSelected(); + } } else { HistoryView::Controls::ShowReplyToChatBox( controller->uiShow(), @@ -3474,11 +3477,6 @@ void HistoryInner::elementStartStickerLoop( _animatedStickersPlayed.emplace(view->data()); } -float64 HistoryInner::elementHighlightOpacity( - not_null item) const { - return _widget->highlightOpacity(item); -} - void HistoryInner::elementShowPollResults( not_null poll, FullMsgId context) { diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 981b9e424..c6049a844 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/scroll_area.h" #include "history/view/history_view_top_bar_widget.h" +#include + struct ClickContext; struct ClickHandlerContext; @@ -136,8 +138,6 @@ public: int from, int till) const; void elementStartStickerLoop(not_null view); - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const; void elementShowPollResults( not_null poll, FullMsgId context); @@ -462,6 +462,7 @@ private: std::optional _chooseForReportReason; const std::unique_ptr _pathGradient; + QPainterPath _highlightPathCache; bool _isChatWide = false; base::flat_set> _animatedStickersPlayed; diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index b75292aa0..3562efd91 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -630,7 +630,10 @@ void HistoryMessageReply::setLinkFrom( } }; _link = resolvedMessage - ? JumpToMessageClickHandler(resolvedMessage.get(), holder->fullId()) + ? JumpToMessageClickHandler( + resolvedMessage.get(), + holder->fullId(), + _fields.quote) : resolvedStory ? JumpToStoryClickHandler(resolvedStory.get()) : (external && !_fields.messageId) diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 43f17d19e..15b4555e9 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -259,17 +259,20 @@ bool IsItemScheduledUntilOnline(not_null item) { ClickHandlerPtr JumpToMessageClickHandler( not_null item, - FullMsgId returnToId) { + FullMsgId returnToId, + TextWithEntities highlightPart) { return JumpToMessageClickHandler( item->history()->peer, item->id, - returnToId); + returnToId, + std::move(highlightPart)); } ClickHandlerPtr JumpToMessageClickHandler( not_null peer, MsgId msgId, - FullMsgId returnToId) { + FullMsgId returnToId, + TextWithEntities highlightPart) { return std::make_shared([=] { const auto separate = Core::App().separateWindowForPeer(peer); const auto controller = separate @@ -279,6 +282,7 @@ ClickHandlerPtr JumpToMessageClickHandler( auto params = Window::SectionShow{ Window::SectionShow::Way::Forward }; + params.highlightPart = highlightPart; params.origin = Window::SectionShow::OriginMessage{ returnToId }; diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 8dd151274..39e8a4e4b 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -120,10 +120,12 @@ struct SendingErrorRequest { [[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( not_null peer, MsgId msgId, - FullMsgId returnToId = FullMsgId()); + FullMsgId returnToId = FullMsgId(), + TextWithEntities highlightPart = {}); [[nodiscard]] ClickHandlerPtr JumpToMessageClickHandler( not_null item, - FullMsgId returnToId = FullMsgId()); + FullMsgId returnToId = FullMsgId(), + TextWithEntities highlightPart = {}); [[nodiscard]] ClickHandlerPtr JumpToStoryClickHandler( not_null story); ClickHandlerPtr JumpToStoryClickHandler( diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp index 939e56980..94244ef00 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp @@ -10,12 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "history/history_item.h" #include "history/view/history_view_element.h" +#include "ui/chat/chat_style.h" namespace HistoryView { -constexpr auto kAnimationFirstPart = st::activeFadeInDuration - / float64(st::activeFadeInDuration + st::activeFadeOutDuration); - ElementHighlighter::ElementHighlighter( not_null data, ViewForItem viewForItem, @@ -26,14 +24,15 @@ ElementHighlighter::ElementHighlighter( , _animation(*this) { } -void ElementHighlighter::enqueue(not_null view) { +void ElementHighlighter::enqueue( + not_null view, + TextSelection part) { const auto item = view->data(); - const auto fullId = item->fullId(); + const auto data = Highlight{ item->fullId(), part }; if (_queue.empty() && !_animation.animating()) { - highlight(fullId); - } else if (_highlightedMessageId != fullId - && !base::contains(_queue, fullId)) { - _queue.push_back(fullId); + highlight(data.itemId, data.part); + } else if (_highlighted != data && !base::contains(_queue, data)) { + _queue.push_back(data); checkNextHighlight(); } } @@ -42,47 +41,46 @@ void ElementHighlighter::checkNextHighlight() { if (_animation.animating()) { return; } - const auto nextHighlight = [&] { + const auto next = [&] { while (!_queue.empty()) { - const auto fullId = _queue.front(); + const auto highlight = _queue.front(); _queue.pop_front(); - if (const auto item = _data->message(fullId)) { + if (const auto item = _data->message(highlight.itemId)) { if (_viewForItem(item)) { - return fullId; + return highlight; } } } - return FullMsgId(); + return Highlight(); }(); - if (!nextHighlight) { + if (!next) { return; } - highlight(nextHighlight); + highlight(next.itemId, next.part); } -float64 ElementHighlighter::progress( +Ui::ChatPaintHighlight ElementHighlighter::state( not_null item) const { - if (item->fullId() == _highlightedMessageId) { - const auto progress = _animation.progress(); - return std::min(progress / kAnimationFirstPart, 1.) - - ((progress - kAnimationFirstPart) / (1. - kAnimationFirstPart)); + if (item->fullId() == _highlighted.itemId) { + auto result = _animation.state(); + result.range = _highlighted.part; + return result; } - return 0.; + return {}; } -void ElementHighlighter::highlight(FullMsgId itemId) { +void ElementHighlighter::highlight(FullMsgId itemId, TextSelection part) { if (const auto item = _data->message(itemId)) { if (const auto view = _viewForItem(item)) { - if (_highlightedMessageId - && _highlightedMessageId != itemId) { - if (const auto was = _data->message(_highlightedMessageId)) { + if (_highlighted && _highlighted.itemId != itemId) { + if (const auto was = _data->message(_highlighted.itemId)) { if (const auto view = _viewForItem(was)) { repaintHighlightedItem(view); } } } - _highlightedMessageId = itemId; - _animation.start(); + _highlighted = { itemId, part }; + _animation.start(!part.empty()); repaintHighlightedItem(view); } @@ -105,7 +103,7 @@ void ElementHighlighter::repaintHighlightedItem( } void ElementHighlighter::updateMessage() { - if (const auto item = _data->message(_highlightedMessageId)) { + if (const auto item = _data->message(_highlighted.itemId)) { if (const auto view = _viewForItem(item)) { repaintHighlightedItem(view); } @@ -114,7 +112,7 @@ void ElementHighlighter::updateMessage() { void ElementHighlighter::clear() { _animation.cancel(); - _highlightedMessageId = FullMsgId(); + _highlighted = {}; _lastHighlightedMessageId = FullMsgId(); _queue.clear(); } @@ -125,60 +123,117 @@ ElementHighlighter::AnimationManager::AnimationManager( } bool ElementHighlighter::AnimationManager::animating() const { - if (anim::Disabled()) { - return (_timer && _timer->isActive()); - } else { + if (_timer && _timer->isActive()) { + return true; + } else if (!anim::Disabled()) { return _simple.animating(); } + return false; } -float64 ElementHighlighter::AnimationManager::progress() const { +Ui::ChatPaintHighlight ElementHighlighter::AnimationManager::state() const { if (anim::Disabled()) { - return (_timer && _timer->isActive()) ? kAnimationFirstPart : 0.; - } else { - return _simple.value(0.); + return { + .opacity = !_timer ? 0. : 1., + .collapsion = !_timer ? 0. : _fadingOut ? 1. : 0., + }; } + return { + .opacity = ((!_fadingOut && _collapsing) + ? 1. + : _simple.value(_fadingOut ? 0. : 1.)), + .collapsion = ((!_withTextPart || !_collapsing) + ? 0. + : _fadingOut + ? 1. + : _simple.value(1.)), + }; } MsgId ElementHighlighter::latestSingleHighlightedMsgId() const { - return _highlightedMessageId - ? _highlightedMessageId.msg + return _highlighted.itemId + ? _highlighted.itemId.msg : _lastHighlightedMessageId.msg; } -void ElementHighlighter::AnimationManager::start() { +void ElementHighlighter::AnimationManager::start(bool withTextPart) { + _withTextPart = withTextPart; const auto finish = [=] { cancel(); _parent._lastHighlightedMessageId = base::take( - _parent._highlightedMessageId); + _parent._highlighted.itemId); _parent.checkNextHighlight(); }; cancel(); if (anim::Disabled()) { _timer.emplace([=] { _parent.updateMessage(); - finish(); + if (_withTextPart && !_fadingOut) { + _fadingOut = true; + _timer->callOnce(st::activeFadeOutDuration); + } else { + finish(); + } }); - _timer->callOnce(st::activeFadeOutDuration); + _timer->callOnce(_withTextPart + ? st::activeFadeInDuration + : st::activeFadeOutDuration); _parent.updateMessage(); } else { - const auto to = 1.; _simple.start( [=](float64 value) { _parent.updateMessage(); - if (value == to) { - finish(); + if (value == 1.) { + if (_withTextPart) { + _timer.emplace([=] { + _parent.updateMessage(); + if (_collapsing) { + _fadingOut = true; + } else { + _collapsing = true; + } + _simple.start([=](float64 value) { + _parent.updateMessage(); + if (_fadingOut && value == 0.) { + finish(); + } else if (!_fadingOut && value == 1.) { + _timer->callOnce( + st::activeFadeOutDuration); + } + }, + _fadingOut ? 1. : 0., + _fadingOut ? 0. : 1., + (_fadingOut + ? st::activeFadeInDuration + : st::fadeWrapDuration)); + }); + _timer->callOnce(st::activeFadeInDuration); + } else { + _fadingOut = true; + _simple.start([=](float64 value) { + _parent.updateMessage(); + if (value == 0.) { + finish(); + } + }, + 1., + 0., + st::activeFadeOutDuration); + } } }, 0., - to, - st::activeFadeInDuration + st::activeFadeOutDuration); + 1., + st::activeFadeInDuration); } } void ElementHighlighter::AnimationManager::cancel() { _simple.stop(); _timer.reset(); + _fadingOut = false; + _collapsed = false; + _collapsing = false; } } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.h b/Telegram/SourceFiles/history/history_view_highlight_manager.h index 43372a8b9..f6de65ceb 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.h +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.h @@ -16,6 +16,10 @@ namespace Data { class Session; } // namespace Data +namespace Ui { +struct ChatPaintHighlight; +} // namespace Ui + namespace HistoryView { class Element; @@ -29,40 +33,56 @@ public: ViewForItem viewForItem, RepaintView repaintView); - void enqueue(not_null view); - void highlight(FullMsgId itemId); + void enqueue(not_null view, TextSelection part); + void highlight(FullMsgId itemId, TextSelection part); void clear(); - [[nodiscard]] float64 progress(not_null item) const; + [[nodiscard]] Ui::ChatPaintHighlight state( + not_null item) const; [[nodiscard]] MsgId latestSingleHighlightedMsgId() const; private: - void checkNextHighlight(); - void repaintHighlightedItem(not_null view); - void updateMessage(); - class AnimationManager final { public: AnimationManager(ElementHighlighter &parent); [[nodiscard]] bool animating() const; - [[nodiscard]] float64 progress() const; - void start(); + [[nodiscard]] Ui::ChatPaintHighlight state() const; + void start(bool withTextPart); void cancel(); private: ElementHighlighter &_parent; Ui::Animations::Simple _simple; std::optional _timer; + bool _withTextPart = false; + bool _collapsing = false; + bool _collapsed = false; + bool _fadingOut = false; }; + struct Highlight { + FullMsgId itemId; + TextSelection part; + + explicit operator bool() const { + return itemId.operator bool(); + } + friend inline auto operator<=>(Highlight, Highlight) = default; + friend inline bool operator==(Highlight, Highlight) = default; + }; + + void checkNextHighlight(); + void repaintHighlightedItem(not_null view); + void updateMessage(); + const not_null _data; const ViewForItem _viewForItem; const RepaintView _repaintView; - FullMsgId _highlightedMessageId; + Highlight _highlighted; FullMsgId _lastHighlightedMessageId; - std::deque _queue; + std::deque _queue; AnimationManager _animation; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index a916c4892..681a9b482 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1072,7 +1072,7 @@ void HistoryWidget::initTabbedSelector() { if (!data.recipientOverride) { return true; } else if (data.recipientOverride != _peer) { - showHistory(data.recipientOverride->id, ShowAtTheEndMsgId); + showHistory(data.recipientOverride->id, ShowAtTheEndMsgId, {}); } return (data.recipientOverride == _peer); }) | rpl::start_with_next([=](ChatHelpers::InlineChosen data) { @@ -1267,13 +1267,14 @@ void HistoryWidget::scrollToAnimationCallback( } void HistoryWidget::enqueueMessageHighlight( - not_null view) { - _highlighter.enqueue(view); + not_null view, + TextSelection part) { + _highlighter.enqueue(view, part); } -float64 HistoryWidget::highlightOpacity( +Ui::ChatPaintHighlight HistoryWidget::itemHighlight( not_null item) const { - return _highlighter.progress(item); + return _highlighter.state(item); } int HistoryWidget::itemTopForHighlight( @@ -1971,9 +1972,10 @@ bool HistoryWidget::insideJumpToEndInsteadOfToUnread() const { void HistoryWidget::showHistory( const PeerId &peerId, MsgId showAtMsgId, - bool reload) { + const TextWithEntities &highlightPart) { _pinnedClickedId = FullMsgId(); _minPinnedId = std::nullopt; + _showAtMsgHighlightPart = {}; const auto wasDialogsEntryState = computeDialogsEntryState(); const auto startBot = (showAtMsgId == ShowAndStartBotMsgId); @@ -1985,7 +1987,7 @@ void HistoryWidget::showHistory( controller()->sendingAnimation().clear(); _topToast.hide(anim::type::instant); if (_history) { - if (_peer->id == peerId && !reload) { + if (_peer->id == peerId) { updateForwarding(); if (showAtMsgId == ShowAtUnreadMsgId @@ -2021,10 +2023,10 @@ void HistoryWidget::showHistory( ).arg(_history->inboxReadTillId().bare ).arg(Logs::b(_history->loadedAtBottom()) ).arg(showAtMsgId.bare)); - delayedShowAt(showAtMsgId); + delayedShowAt(showAtMsgId, highlightPart); } else if (_showAtMsgId != showAtMsgId) { clearAllLoadRequests(); - setMsgId(showAtMsgId); + setMsgId(showAtMsgId, highlightPart); firstLoadMessages(); doneShow(); } @@ -2044,7 +2046,7 @@ void HistoryWidget::showHistory( _cornerButtons.skipReplyReturn(skipId); } - setMsgId(showAtMsgId); + setMsgId(showAtMsgId, highlightPart); if (_historyInited) { DEBUG_LOG(("JumpToEnd(%1, %2, %3): " "Showing instant at %4." @@ -2147,6 +2149,7 @@ void HistoryWidget::showHistory( clearInlineBot(); _showAtMsgId = showAtMsgId; + _showAtMsgHighlightPart = highlightPart; _historyInited = false; _contactStatus = nullptr; @@ -3301,7 +3304,7 @@ void HistoryWidget::messagesReceived( } _delayedShowAtRequest = 0; - setMsgId(_delayedShowAtMsgId); + setMsgId(_delayedShowAtMsgId, _delayedShowAtMsgHighlightPart); historyLoaded(); } if (session().supportMode()) { @@ -3523,9 +3526,16 @@ void HistoryWidget::loadMessagesDown() { }); } -void HistoryWidget::delayedShowAt(MsgId showAtMsgId) { - if (!_history - || (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId)) { +void HistoryWidget::delayedShowAt( + MsgId showAtMsgId, + const TextWithEntities &highlightPart) { + if (!_history) { + return; + } + if (_delayedShowAtMsgHighlightPart != highlightPart) { + _delayedShowAtMsgHighlightPart = highlightPart; + } + if (_delayedShowAtRequest && _delayedShowAtMsgId == showAtMsgId) { return; } @@ -4102,7 +4112,12 @@ PeerData *HistoryWidget::peer() const { } // Sometimes _showAtMsgId is set directly. -void HistoryWidget::setMsgId(MsgId showAtMsgId) { +void HistoryWidget::setMsgId( + MsgId showAtMsgId, + const TextWithEntities &highlightPart) { + if (_showAtMsgHighlightPart != highlightPart) { + _showAtMsgHighlightPart = highlightPart; + } if (_showAtMsgId != showAtMsgId) { _showAtMsgId = showAtMsgId; if (_history) { @@ -4223,11 +4238,11 @@ void HistoryWidget::cornerButtonsShowAtPosition( ).arg(_history->peer->name() ).arg(_history->inboxReadTillId().bare ).arg(Logs::b(_history->loadedAtBottom()))); - showHistory(_peer->id, ShowAtUnreadMsgId); + showHistory(_peer->id, ShowAtUnreadMsgId, {}); } else if (_peer && position.fullId.peer == _peer->id) { - showHistory(_peer->id, position.fullId.msg); + showHistory(_peer->id, position.fullId.msg, {}); } else if (_migrated && position.fullId.peer == _migrated->peer->id) { - showHistory(_peer->id, -position.fullId.msg); + showHistory(_peer->id, -position.fullId.msg, {}); } } @@ -5678,14 +5693,17 @@ int HistoryWidget::countInitialScrollTop() { const auto item = getItemFromHistoryOrMigrated(_showAtMsgId); const auto itemTop = _list->itemTop(item); if (itemTop < 0) { - setMsgId(0); + setMsgId(ShowAtUnreadMsgId); controller()->showToast(tr::lng_message_not_found(tr::now)); return countInitialScrollTop(); } else { const auto view = item->mainView(); Assert(view != nullptr); - enqueueMessageHighlight(view); + enqueueMessageHighlight( + view, + view->selectionFromQuote( + base::take(_showAtMsgHighlightPart))); const auto result = itemTopForHighlight(view); createUnreadBarIfBelowVisibleArea(result); return result; @@ -6377,7 +6395,8 @@ void HistoryWidget::handlePeerMigration() { if (_peer != channel) { showHistory( channel->id, - (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId); + (_showAtMsgId > 0) ? (-_showAtMsgId) : _showAtMsgId, + {}); channel->session().api().chatParticipants().requestCountDelayed( channel); } else { @@ -6473,7 +6492,7 @@ bool HistoryWidget::showSlowmodeError() { if (const auto item = _history->latestSendingMessage()) { if (const auto view = item->mainView()) { animatedScrollToItem(item->id); - enqueueMessageHighlight(view); + enqueueMessageHighlight(view, {}); } return tr::lng_slowmode_no_many(tr::now); } diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index a78875e22..450985010 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -75,6 +75,7 @@ class SpoilerAnimation; enum class ReportReason; class ChooseThemeController; class ContinuousScroll; +struct ChatPaintHighlight; } // namespace Ui namespace Window { @@ -146,7 +147,9 @@ public: void loadMessages(); void loadMessagesDown(); void firstLoadMessages(); - void delayedShowAt(MsgId showAtMsgId); + void delayedShowAt( + MsgId showAtMsgId, + const TextWithEntities &highlightPart); bool updateReplaceMediaButton(); void updateFieldPlaceholder(); @@ -160,7 +163,9 @@ public: History *history() const; PeerData *peer() const; - void setMsgId(MsgId showAtMsgId); + void setMsgId( + MsgId showAtMsgId, + const TextWithEntities &highlightPart = {}); MsgId msgId() const; bool hasTopBarShadow() const { @@ -177,8 +182,10 @@ public: bool touchScroll(const QPoint &delta); - void enqueueMessageHighlight(not_null view); - [[nodiscard]] float64 highlightOpacity( + void enqueueMessageHighlight( + not_null view, + TextSelection part); + [[nodiscard]] Ui::ChatPaintHighlight itemHighlight( not_null item) const; MessageIdsList getSelectedItems() const; @@ -218,7 +225,10 @@ public: void fastShowAtEnd(not_null history); bool applyDraft( FieldHistoryAction fieldHistoryAction = FieldHistoryAction::Clear); - void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false); + void showHistory( + const PeerId &peer, + MsgId showAtMsgId, + const TextWithEntities &highlightPart); void setChooseReportMessagesDetails( Ui::ReportReason reason, Fn callback); @@ -684,12 +694,14 @@ private: bool _canSendMessages = false; bool _canSendTexts = false; MsgId _showAtMsgId = ShowAtUnreadMsgId; + TextWithEntities _showAtMsgHighlightPart; int _firstLoadRequest = 0; // Not real mtpRequestId. int _preloadRequest = 0; // Not real mtpRequestId. int _preloadDownRequest = 0; // Not real mtpRequestId. MsgId _delayedShowAtMsgId = -1; + TextWithEntities _delayedShowAtMsgHighlightPart; int _delayedShowAtRequest = 0; // Not real mtpRequestId. History *_supportPreloadHistory = nullptr; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 61900f20f..ac3cf2ad2 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -111,11 +111,6 @@ bool DefaultElementDelegate::elementUnderCursor( return false; } -float64 DefaultElementDelegate::elementHighlightOpacity( - not_null item) const { - return 0.; -} - bool DefaultElementDelegate::elementInSelectionMode() { return false; } @@ -605,18 +600,13 @@ void Element::paintCustomHighlight( int y, int height, not_null item) const { - const auto opacity = delegate()->elementHighlightOpacity(item); + const auto opacity = context.highlight.opacity; if (opacity == 0.) { return; } const auto o = p.opacity(); p.setOpacity(o * opacity); - p.fillRect( - 0, - y, - width(), - height, - context.st->msgSelectOverlay()); + p.fillRect(0, y, width(), height, context.st->msgSelectOverlay()); p.setOpacity(o); } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index b51487703..fa8e9e4fa 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -69,8 +69,6 @@ class ElementDelegate { public: virtual Context elementContext() = 0; virtual bool elementUnderCursor(not_null view) = 0; - [[nodiscard]] virtual float64 elementHighlightOpacity( - not_null item) const = 0; virtual bool elementInSelectionMode() = 0; virtual bool elementIntersectsRange( not_null view, @@ -120,8 +118,6 @@ public: class DefaultElementDelegate : public ElementDelegate { public: bool elementUnderCursor(not_null view) override; - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const override; bool elementInSelectionMode() override; bool elementIntersectsRange( not_null view, diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 2938fffdd..821cab151 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -707,8 +707,15 @@ bool ListWidget::isBelowPosition(Data::MessagePosition position) const { return _items.front()->data()->position() > position; } -void ListWidget::highlightMessage(FullMsgId itemId) { - _highlighter.highlight(itemId); +void ListWidget::highlightMessage( + FullMsgId itemId, + const TextWithEntities &highlightPart) { + const auto view = !highlightPart.empty() + ? viewForItem(itemId) + : nullptr; + _highlighter.highlight( + itemId, + view ? view->selectionFromQuote(highlightPart) : TextSelection()); } void ListWidget::showAroundPosition( @@ -741,12 +748,12 @@ bool ListWidget::jumpToBottomInsteadOfUnread() const { void ListWidget::showAtPosition( Data::MessagePosition position, - anim::type animated, + const Window::SectionShow ¶ms, Fn done) { const auto showAtUnread = (position == Data::UnreadMessagePosition); if (showAtUnread && jumpToBottomInsteadOfUnread()) { - showAtPosition(Data::MaxMessagePosition, animated, std::move(done)); + showAtPosition(Data::MaxMessagePosition, params, std::move(done)); return; } @@ -766,24 +773,24 @@ void ListWidget::showAtPosition( _bar = {}; } checkUnreadBarCreation(); - return showAtPositionNow(position, animated, done); + return showAtPositionNow(position, params, done); }); - } else if (!showAtPositionNow(position, animated, done)) { + } else if (!showAtPositionNow(position, params, done)) { showAroundPosition(position, [=] { - return showAtPositionNow(position, animated, done); + return showAtPositionNow(position, params, done); }); } } bool ListWidget::showAtPositionNow( Data::MessagePosition position, - anim::type animated, + const Window::SectionShow ¶ms, Fn done) { if (const auto scrollTop = scrollTopForPosition(position)) { - computeScrollTo(*scrollTop, position, animated); + computeScrollTo(*scrollTop, position, params.animated); if (position != Data::MaxMessagePosition && position != Data::UnreadMessagePosition) { - highlightMessage(position.fullId); + highlightMessage(position.fullId, params.highlightPart); } if (done) { const auto found = !position.fullId.peer @@ -1655,11 +1662,6 @@ bool ListWidget::elementUnderCursor( return (_overElement == view); } -float64 ListWidget::elementHighlightOpacity( - not_null item) const { - return _highlighter.progress(item); -} - bool ListWidget::elementInSelectionMode() { return inSelectionMode(); } @@ -2108,6 +2110,7 @@ void ListWidget::paintEvent(QPaintEvent *e) { = _reactionsManager->currentReactionPaintInfo(); context.outbg = view->hasOutLayout(); context.selection = itemRenderSelection(view); + context.highlight = _highlighter.state(item); view->draw(p, context); } if (_translateTracker) { @@ -2142,7 +2145,7 @@ void ListWidget::paintEvent(QPaintEvent *e) { } else if (item->isUnreadMention() && !item->isUnreadMedia()) { readContents.insert(item); - _highlighter.enqueue(view); + _highlighter.enqueue(view, {}); } } session->data().reactions().poll(item, context.now); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 138f39cca..c12ae113c 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -48,6 +48,10 @@ struct ChosenReaction; struct ButtonParameters; } // namespace HistoryView::Reactions +namespace Window { +struct SectionShow; +} // namespace Window + namespace HistoryView { struct TextState; @@ -227,11 +231,13 @@ public: [[nodiscard]] bool animatedScrolling() const; bool isAbovePosition(Data::MessagePosition position) const; bool isBelowPosition(Data::MessagePosition position) const; - void highlightMessage(FullMsgId itemId); + void highlightMessage( + FullMsgId itemId, + const TextWithEntities &highlightPart); void showAtPosition( Data::MessagePosition position, - anim::type animated = anim::type::normal, + const Window::SectionShow ¶ms, Fn done = nullptr); void refreshViewer(); @@ -292,8 +298,6 @@ public: // ElementDelegate interface. Context elementContext() override; bool elementUnderCursor(not_null view) override; - [[nodiscard]] float64 elementHighlightOpacity( - not_null item) const override; bool elementInSelectionMode() override; bool elementIntersectsRange( not_null view, @@ -430,7 +434,7 @@ private: Fn overrideInitialScroll); bool showAtPositionNow( Data::MessagePosition position, - anim::type animated, + const Window::SectionShow ¶ms, Fn done); Ui::ChatPaintContext preparePaintContext(const QRect &clip) const; diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index a5eebddfd..c7d532d27 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -1018,6 +1018,10 @@ void Message::draw(Painter &p, const PaintContext &context) const { p.translate(-reactionsPosition); } + if (context.highlightPathCache) { + context.highlightInterpolateTo = g; + context.highlightPathCache->clear(); + } if (bubble) { if (displayFromName() && item->displayFrom() @@ -1110,6 +1114,7 @@ void Message::draw(Painter &p, const PaintContext &context) const { - (_bottomInfo.height() - st::msgDateFont->height)); } auto textSelection = context.selection; + auto highlightRange = context.highlight.range; const auto mediaHeight = mediaDisplayed ? media->height() : 0; const auto paintMedia = [&](int top) { if (!mediaDisplayed) { @@ -1130,6 +1135,10 @@ void Message::draw(Painter &p, const PaintContext &context) const { context.reactionInfo->effectOffset -= add; } } + if (context.highlightPathCache + && !context.highlightPathCache->isEmpty()) { + context.highlightPathCache->translate(mediaPosition); + } p.translate(-mediaPosition); }; if (mediaDisplayed && _invertMedia) { @@ -1141,8 +1150,12 @@ void Message::draw(Painter &p, const PaintContext &context) const { + mediaHeight + (mediaOnBottom ? 0 : st::mediaInBubbleSkip)); textSelection = media->skipSelection(textSelection); + highlightRange = media->skipSelection(highlightRange); } - paintText(p, trect, context.withSelection(textSelection)); + auto copy = context; + copy.selection = textSelection; + copy.highlight.range = highlightRange; + paintText(p, trect, copy); if (mediaDisplayed && !_invertMedia) { paintMedia(trect.y() + trect.height() - mediaHeight); if (context.reactionInfo && !displayInfo && !_reactions) { @@ -1224,6 +1237,20 @@ void Message::draw(Painter &p, const PaintContext &context) const { p.restoreTextPalette(); + if (context.highlightPathCache + && !context.highlightPathCache->isEmpty()) { + const auto alpha = int(0.25 + * context.highlight.collapsion + * context.highlight.opacity + * 255); + if (alpha > 0) { + context.highlightPathCache->setFillRule(Qt::WindingFill); + auto color = context.messageStyle()->textPalette.linkFg->c; + color.setAlpha(alpha); + p.fillPath(*context.highlightPathCache, color); + } + } + if (roll) { p.restore(); } @@ -1651,6 +1678,7 @@ void Message::paintText( width()); trect.setY(trect.y() + botTop->height); } + auto highlightRequest = context.computeHighlightCache(); text().draw(p, { .position = trect.topLeft(), .availableWidth = trect.width(), @@ -1663,6 +1691,7 @@ void Message::paintText( .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } @@ -2732,10 +2761,13 @@ TextWithEntities Message::selectedQuote( TextSelection Message::selectionFromQuote( const TextWithEntities "e) const { + if (quote.empty()) { + return {}; + } const auto item = data(); const auto &translated = item->translatedText(); const auto &original = item->originalText(); - if (&translated != &original || quote.empty()) { + if (&translated != &original) { return {}; } else if (hasVisibleText()) { const auto media = this->media(); diff --git a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp index 0fbaf0349..0472e4eb1 100644 --- a/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_pinned_section.cpp @@ -262,7 +262,7 @@ void PinnedWidget::showAtPosition( FullMsgId originId) { _inner->showAtPosition( position, - anim::type::normal, + {}, _cornerButtons.doneJumpFrom(position.fullId, originId)); } @@ -346,7 +346,7 @@ void PinnedWidget::restoreState(not_null memento) { ? FullMsgId(_history->peer->id, highlight) : FullMsgId(_migratedPeer->id, -highlight)), .date = TimeId(0), - }, anim::type::instant); + }, { Window::SectionShow::Way::Forward, anim::type::instant }); } } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 1048b98b9..f67568e1b 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -1859,16 +1859,22 @@ void RepliesWidget::finishSending() { refreshTopBarActiveChat(); } +void RepliesWidget::showAtPosition( + Data::MessagePosition position, + FullMsgId originItemId) { + showAtPosition(position, originItemId, {}); +} + void RepliesWidget::showAtPosition( Data::MessagePosition position, FullMsgId originItemId, - anim::type animated) { + const Window::SectionShow ¶ms) { _lastShownAt = position.fullId; controller()->setActiveChatEntry(activeChat()); const auto ignore = (position.fullId.msg == _rootId); _inner->showAtPosition( position, - animated, + params, _cornerButtons.doneJumpFrom(position.fullId, originItemId, ignore)); } @@ -2030,7 +2036,7 @@ bool RepliesWidget::showMessage( const auto originItemId = (_cornerButtons.replyReturn() != originMessage) ? originMessage->fullId() : FullMsgId(); - showAtPosition(message->position(), originItemId); + showAtPosition(message->position(), originItemId, params); return true; } @@ -2136,7 +2142,7 @@ void RepliesWidget::restoreState(not_null memento) { showAtPosition(Data::MessagePosition{ .fullId = FullMsgId(_history->peer->id, highlight), .date = TimeId(0), - }, {}, anim::type::instant); + }, {}, { Window::SectionShow::Way::Forward, anim::type::instant }); } } diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 14104e913..cea372614 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -208,8 +208,11 @@ private: void showAtEnd(); void showAtPosition( Data::MessagePosition position, - FullMsgId originItemId = {}, - anim::type animated = anim::type::normal); + FullMsgId originItemId = {}); + void showAtPosition( + Data::MessagePosition position, + FullMsgId originItemId, + const Window::SectionShow ¶ms); void finishSending(); void setupComposeControls(); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 1ee20e414..81fe77299 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -858,7 +858,7 @@ void ScheduledWidget::showAtPosition( FullMsgId originId) { _inner->showAtPosition( position, - anim::type::normal, + {}, _cornerButtons.doneJumpFrom(position.fullId, originId)); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 5e45b8877..7346966da 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -744,6 +744,7 @@ void Document::draw( if (const auto captioned = Get()) { p.setPen(stm->historyTextFg); _parent->prepareCustomEmojiPaint(p, context, captioned->caption); + auto highlightRequest = context.computeHighlightCache(); captioned->caption.draw(p, { .position = { st::msgPadding.left(), captiontop }, .availableWidth = captionw, @@ -756,6 +757,7 @@ void Document::draw( .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } } 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 6c259748d..a82fd13fd 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_extended_preview.cpp @@ -229,6 +229,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { if (!_caption.isEmpty()) { p.setPen(stm->historyTextFg); _parent->prepareCustomEmojiPaint(p, context, _caption); + auto highlightRequest = context.computeHighlightCache(); _caption.draw(p, { .position = QPoint( st::msgPadding.left(), @@ -243,6 +244,7 @@ void ExtendedPreview::draw(Painter &p, const PaintContext &context) const { .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } else if (!inWebPage) { auto fullRight = paintx + paintw; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 70a81264d..d35658d88 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -716,6 +716,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { _parent->width()); top += botTop->height; } + auto highlightRequest = context.computeHighlightCache(); _caption.draw(p, { .position = QPoint(st::msgPadding.left(), top), .availableWidth = captionw, @@ -728,6 +729,7 @@ void Gif::draw(Painter &p, const PaintContext &context) const { .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } else if (!inWebPage && !skipDrawingSurrounding) { auto fullRight = paintx + usex + usew; 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 79cbcf869..37676611d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -325,12 +325,13 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { : IsGroupItemSelection(selection, i) ? FullSelection : TextSelection()); + const auto highlightOpacity = IsGroupItemSelection( + context.highlight.range, + i + ) ? context.highlight.opacity : 0.; if (textSelection) { selection = part.content->skipSelection(selection); } - const auto highlightOpacity = (_mode == Mode::Grid) - ? _parent->delegate()->elementHighlightOpacity(part.item) - : 0.; if (!part.cache.isNull()) { wasCache = true; } @@ -361,6 +362,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { const auto stm = context.messageStyle(); p.setPen(stm->historyTextFg); _parent->prepareCustomEmojiPaint(p, context, _caption); + auto highlightRequest = context.computeHighlightCache(); _caption.draw(p, { .position = QPoint( st::msgPadding.left(), @@ -375,6 +377,7 @@ void GroupedMedia::draw(Painter &p, const PaintContext &context) const { .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } else if (_parent->media() == this) { auto fullRight = width(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index 81dee1c54..b0afe1b64 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -401,6 +401,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { _parent->width()); top += botTop->height; } + auto highlightRequest = context.computeHighlightCache(); _caption.draw(p, { .position = QPoint(st::msgPadding.left(), top), .availableWidth = captionw, @@ -413,6 +414,7 @@ void Photo::draw(Painter &p, const PaintContext &context) const { .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), .selection = context.selection, + .highlight = highlightRequest ? &*highlightRequest : nullptr, }); } else if (!inWebPage) { auto fullRight = paintx + paintw; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 4cc65c77a..2540e2c98 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -1411,7 +1411,7 @@ void MainWidget::showHistory( && way != Way::Forward) { clearBotStartToken(_history->peer()); } - _history->showHistory(peerId, showAtMsgId); + _history->showHistory(peerId, showAtMsgId, params.highlightPart); if (alreadyThatPeer && params.reapplyLocalDraft) { _history->applyDraft(HistoryWidget::FieldHistoryAction::NewEntry); } @@ -1772,7 +1772,7 @@ void MainWidget::showNewSection( } else { _mainSection = std::move(newMainSection); _history->finishAnimating(); - _history->showHistory(0, 0); + _history->showHistory(0, 0, {}); if (const auto entry = _mainSection->activeChat(); entry.key) { _controller->setActiveChatEntry(entry); diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 8ba606941..39f5a76e4 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -142,6 +142,12 @@ struct BackgroundEmojiData { uint8 colorIndexPlusOne); }; +struct ChatPaintHighlight { + float64 opacity = 0.; + float64 collapsion = 0.; + TextSelection range; +}; + struct ChatPaintContext { not_null st; const BubblePattern *bubblesPattern = nullptr; @@ -149,11 +155,15 @@ struct ChatPaintContext { QRect viewport; QRect clip; TextSelection selection; + ChatPaintHighlight highlight; + QPainterPath *highlightPathCache = nullptr; + mutable QRect highlightInterpolateTo; crl::time now = 0; void translate(int x, int y) { viewport.translate(x, y); clip.translate(x, y); + highlightInterpolateTo.translate(x, y); } void translate(QPoint point) { translate(point.x(), point.y()); @@ -181,6 +191,19 @@ struct ChatPaintContext { result.selection = selection; return result; } + [[nodiscard]] auto computeHighlightCache() const + -> std::optional { + if (highlight.range.empty() || highlight.collapsion <= 0.) { + return {}; + } + return Ui::Text::HighlightInfoRequest{ + .range = highlight.range, + .interpolateTo = highlightInterpolateTo, + .interpolateProgress = (1. - highlight.collapsion), + .outPath = highlightPathCache, + }; + }; + // This is supported only in unwrapped media for now. enum class SkipDrawingParts { diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 367be098f..0bca576aa 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -166,6 +166,7 @@ struct SectionShow { return copy; } + TextWithEntities highlightPart; Way way = Way::Forward; anim::type animated = anim::type::normal; anim::activation activation = anim::activation::normal; diff --git a/Telegram/lib_ui b/Telegram/lib_ui index dc8313f6a..6dc93b53a 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit dc8313f6ae6c87572512424818b9e24e7ef2383e +Subproject commit 6dc93b53a1c4d237dea0a458d2b02c4171529f18