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 bf383ddec5..923b8d3160 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -556,7 +556,7 @@ bool InnerWidget::elementUnderCursor( } crl::time InnerWidget::elementHighlightTime( - not_null element) { + not_null item) { return crl::time(0); } 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 32a6fcf42c..ca8e500663 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.h @@ -97,7 +97,7 @@ public: bool elementUnderCursor( not_null view) override; crl::time elementHighlightTime( - not_null element) override; + not_null item) 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 cc24aca930..7af0eaabf9 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2520,9 +2520,9 @@ void HistoryInner::elementStartStickerLoop( _animatedStickersPlayed.emplace(view->data()); } -crl::time HistoryInner::elementHighlightTime(not_null view) { - const auto fullAnimMs = _controller->content()->highlightStartTime( - view->data()); +crl::time HistoryInner::elementHighlightTime( + not_null item) { + const auto fullAnimMs = _controller->content()->highlightStartTime(item); if (fullAnimMs > 0) { const auto now = crl::now(); if (fullAnimMs < now) { @@ -3421,8 +3421,8 @@ not_null HistoryInner::ElementDelegate() { return (App::hoveredItem() == view); } crl::time elementHighlightTime( - not_null view) override { - return Instance ? Instance->elementHighlightTime(view) : 0; + not_null item) override { + return Instance ? Instance->elementHighlightTime(item) : 0; } bool elementInSelectionMode() override { return Instance ? Instance->inSelectionMode() : false; diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 8e5f5d6f37..09b22bf1e7 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -84,7 +84,7 @@ public: int till) const; void elementStartStickerLoop(not_null view); [[nodiscard]] crl::time elementHighlightTime( - not_null view); + not_null item); void elementShowPollResults( not_null poll, FullMsgId context); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index b67e0fc317..7208241d86 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1045,11 +1045,6 @@ void HistoryWidget::scrollToAnimationCallback( void HistoryWidget::enqueueMessageHighlight( not_null view) { - if (const auto group = session().data().groups().find(view->data())) { - if (const auto leader = group->items.front()->mainView()) { - view = leader; - } - } auto enqueueMessageId = [this](MsgId universalId) { if (_highlightQueue.empty() && !_highlightTimer.isActive()) { highlightMessage(universalId); @@ -1096,7 +1091,7 @@ void HistoryWidget::checkNextHighlight() { void HistoryWidget::updateHighlightedMessage() { const auto item = getItemFromHistoryOrMigrated(_highlightedMessageId); - const auto view = item ? item->mainView() : nullptr; + auto view = item ? item->mainView() : nullptr; if (!view) { return stopMessageHighlight(); } @@ -1105,6 +1100,11 @@ void HistoryWidget::updateHighlightedMessage() { return stopMessageHighlight(); } + if (const auto group = session().data().groups().find(view->data())) { + if (const auto leader = group->items.front()->mainView()) { + view = leader; + } + } session().data().requestViewRepaint(view); } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 97d128ba04..eb533b848e 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -79,7 +79,7 @@ bool SimpleElementDelegate::elementUnderCursor( } crl::time SimpleElementDelegate::elementHighlightTime( - not_null element) { + not_null item) { return crl::time(0); } @@ -280,29 +280,44 @@ void Element::refreshDataIdHook() { void Element::paintHighlight( Painter &p, int geometryHeight) const { - const auto animms = delegate()->elementHighlightTime(this); - if (!animms - || animms >= st::activeFadeInDuration + st::activeFadeOutDuration) { - return; - } - const auto top = marginTop(); const auto bottom = marginBottom(); const auto fill = qMin(top, bottom); const auto skiptop = top - fill; const auto fillheight = fill + geometryHeight + fill; - const auto dt = (animms > st::activeFadeInDuration) + paintCustomHighlight(p, skiptop, fillheight, data()); +} + +float64 Element::highlightOpacity(not_null item) const { + const auto animms = delegate()->elementHighlightTime(item); + if (!animms + || animms >= st::activeFadeInDuration + st::activeFadeOutDuration) { + return 0.; + } + + return (animms > st::activeFadeInDuration) ? (1. - (animms - st::activeFadeInDuration) / float64(st::activeFadeOutDuration)) : (animms / float64(st::activeFadeInDuration)); +} + +void Element::paintCustomHighlight( + Painter &p, + int y, + int height, + not_null item) const { + const auto opacity = highlightOpacity(item); + if (opacity == 0.) { + return; + } const auto o = p.opacity(); - p.setOpacity(o * dt); + p.setOpacity(o * opacity); p.fillRect( 0, - skiptop, + y, width(), - fillheight, + height, st::defaultTextPalette.selectOverlay); p.setOpacity(o); } diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index fe64bdf3ec..82c610eee9 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -51,7 +51,7 @@ public: Element *replacing = nullptr) = 0; virtual bool elementUnderCursor(not_null view) = 0; virtual crl::time elementHighlightTime( - not_null element) = 0; + not_null item) = 0; virtual bool elementInSelectionMode() = 0; virtual bool elementIntersectsRange( not_null view, @@ -87,7 +87,7 @@ public: Element *replacing = nullptr) override; bool elementUnderCursor(not_null view) override; crl::time elementHighlightTime( - not_null element) override; + not_null item) override; bool elementInSelectionMode() override; bool elementIntersectsRange( not_null view, @@ -301,6 +301,13 @@ public: virtual void unloadHeavyPart(); void checkHeavyPart(); + void paintCustomHighlight( + Painter &p, + int y, + int height, + not_null item) const; + float64 highlightOpacity(not_null item) const; + // Legacy blocks structure. HistoryBlock *block(); const HistoryBlock *block() const; diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 15f02c0afe..dc07f3a48d 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -482,7 +482,7 @@ void ListWidget::highlightMessage(FullMsgId itemId) { _highlightedMessageId = itemId; _highlightTimer.callEach(AnimationTimerDelta); - repaintItem(view); + repaintHighlightedItem(view); } } } @@ -496,10 +496,24 @@ void ListWidget::showAroundPosition( refreshViewer(); } +void ListWidget::repaintHighlightedItem(not_null view) { + if (view->isHiddenByGroup()) { + if (const auto group = session().data().groups().find(view->data())) { + if (const auto leader = viewForItem(group->items.front())) { + if (!leader->isHiddenByGroup()) { + repaintItem(leader); + return; + } + } + } + } + repaintItem(view); +} + void ListWidget::updateHighlightedMessage() { if (const auto item = session().data().message(_highlightedMessageId)) { if (const auto view = viewForItem(item)) { - repaintItem(view); + repaintHighlightedItem(view); auto duration = st::activeFadeInDuration + st::activeFadeOutDuration; if (crl::now() - _highlightStart <= duration) { return; @@ -1244,8 +1258,8 @@ bool ListWidget::elementUnderCursor( } crl::time ListWidget::elementHighlightTime( - not_null element) { - if (element->data()->fullId() == _highlightedMessageId) { + not_null item) { + if (item->fullId() == _highlightedMessageId) { if (_highlightTimer.isActive()) { return crl::now() - _highlightStart; } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index dff78abba4..90b65f9af2 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -221,7 +221,7 @@ public: Element *replacing = nullptr) override; bool elementUnderCursor(not_null view) override; crl::time elementHighlightTime( - not_null element) override; + not_null item) override; bool elementInSelectionMode() override; bool elementIntersectsRange( not_null view, @@ -340,6 +340,7 @@ private: int itemTop(not_null view) const; void repaintItem(FullMsgId itemId); void repaintItem(const Element *view); + void repaintHighlightedItem(not_null view); void resizeItem(not_null view); void refreshItem(not_null view); void itemRemoved(not_null item); diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 9e81e2405c..d5dac07359 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -570,7 +570,40 @@ void Message::draw( return; } - paintHighlight(p, g.height()); + auto entry = logEntryOriginal(); + auto mediaDisplayed = media && media->isDisplayed(); + + // Entry page is always a bubble bottom. + auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); + auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); + + auto mediaSelectionIntervals = (!selected && mediaDisplayed) + ? media->getBubbleSelectionIntervals(selection) + : std::vector(); + auto localMediaTop = 0; + const auto customHighlight = mediaDisplayed && media->customHighlight(); + if (!mediaSelectionIntervals.empty() || customHighlight) { + auto localMediaBottom = g.top() + g.height(); + if (data()->repliesAreComments() || data()->externalReply()) { + localMediaBottom -= st::historyCommentsButtonHeight; + } + if (!mediaOnBottom) { + localMediaBottom -= st::msgPadding.bottom(); + } + if (entry) { + localMediaBottom -= entry->height(); + } + localMediaTop = localMediaBottom - media->height(); + for (auto &[top, height] : mediaSelectionIntervals) { + top += localMediaTop; + } + } + + if (customHighlight) { + media->drawHighlight(p, localMediaTop); + } else { + paintHighlight(p, g.height()); + } const auto roll = media ? media->bubbleRoll() : Media::BubbleRoll(); if (roll) { @@ -602,34 +635,6 @@ void Message::draw( fromNameUpdated(g.width()); } - auto entry = logEntryOriginal(); - auto mediaDisplayed = media && media->isDisplayed(); - - // Entry page is always a bubble bottom. - auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/); - auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop()); - - - auto mediaSelectionIntervals = (!selected && mediaDisplayed) - ? media->getBubbleSelectionIntervals(selection) - : std::vector(); - if (!mediaSelectionIntervals.empty()) { - auto localMediaBottom = g.top() + g.height(); - if (data()->repliesAreComments() || data()->externalReply()) { - localMediaBottom -= st::historyCommentsButtonHeight; - } - if (!mediaOnBottom) { - localMediaBottom -= st::msgPadding.bottom(); - } - if (entry) { - localMediaBottom -= entry->height(); - } - const auto localMediaTop = localMediaBottom - media->height(); - for (auto &[top, height] : mediaSelectionIntervals) { - top += localMediaTop; - } - } - auto skipTail = isAttachedToNext() || (media && media->skipBubbleTail()) || (keyboard != nullptr) diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index da4f6ad6a3..df58d562a7 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -951,6 +951,7 @@ void Document::drawGrouped( const QRect &geometry, RectParts sides, RectParts corners, + float64 highlightOpacity, not_null cacheKey, not_null cache) const { p.translate(geometry.topLeft()); diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.h b/Telegram/SourceFiles/history/view/media/history_view_document.h index d50d7e0ed6..150c47684f 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.h +++ b/Telegram/SourceFiles/history/view/media/history_view_document.h @@ -72,6 +72,7 @@ public: const QRect &geometry, RectParts sides, RectParts corners, + float64 highlightOpacity, not_null cacheKey, not_null cache) const override; TextState getStateGrouped( diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index 58b2eec0ec..334c6b8939 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -901,6 +901,7 @@ void Gif::drawGrouped( const QRect &geometry, RectParts sides, RectParts corners, + float64 highlightOpacity, not_null cacheKey, not_null cache) const { ensureDataMediaCreated(); @@ -989,8 +990,16 @@ void Gif::drawGrouped( p.drawPixmap(geometry, *cache); } - if (selected) { + const auto overlayOpacity = selected + ? (1. - highlightOpacity) + : highlightOpacity; + if (overlayOpacity > 0.) { + p.setOpacity(overlayOpacity); Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners); + if (!selected) { + Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners); + } + p.setOpacity(1.); } if (radial diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index e5bfcc30cd..1aa559b635 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -79,6 +79,7 @@ public: const QRect &geometry, RectParts sides, RectParts corners, + float64 highlightOpacity, not_null cacheKey, not_null cache) const override; TextState getStateGrouped( diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 82df073bfa..d4308af06d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -84,6 +84,8 @@ public: } virtual void refreshParentId(not_null realParent) { } + virtual void drawHighlight(Painter &p, int top) const { + } virtual void draw( Painter &p, const QRect &r, @@ -177,6 +179,7 @@ public: const QRect &geometry, RectParts sides, RectParts corners, + float64 highlightOpacity, not_null cacheKey, not_null cache) const { Unexpected("Grouping method call."); @@ -274,6 +277,9 @@ public: const QRect &bubble, crl::time ms) const { } + [[nodiscard]] virtual bool customHighlight() const { + return false; + } virtual bool hasHeavyPart() const { return false; 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 422f18596b..c55c98d16a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.cpp @@ -268,6 +268,18 @@ QMargins GroupedMedia::groupedPadding() const { (normal.bottom() - grouped.bottom()) + addToBottom); } +void GroupedMedia::drawHighlight(Painter &p, int top) const { + if (_mode != Mode::Column) { + return; + } + const auto skip = top + groupedPadding().top(); + for (auto i = 0, count = int(_parts.size()); i != count; ++i) { + const auto &part = _parts[i]; + const auto rect = part.geometry.translated(0, skip); + _parent->paintCustomHighlight(p, rect.y(), rect.height(), part.item); + } +} + void GroupedMedia::draw( Painter &p, const QRect &clip, @@ -290,6 +302,9 @@ void GroupedMedia::draw( if (textSelection) { selection = part.content->skipSelection(selection); } + const auto highlightOpacity = (_mode == Mode::Grid) + ? _parent->highlightOpacity(part.item) + : 0.; part.content->drawGrouped( p, clip, @@ -298,6 +313,7 @@ void GroupedMedia::draw( part.geometry.translated(0, groupPadding.top()), part.sides, cornersFromSides(part.sides), + highlightOpacity, &part.cacheKey, &part.cache); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h index ee24c61f82..442f9a5b1c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_grouped.h @@ -31,6 +31,7 @@ public: void refreshParentId(not_null realParent) override; + void drawHighlight(Painter &p, int top) const override; void draw( Painter &p, const QRect &clip, @@ -87,6 +88,9 @@ public: bool allowsFastShare() const override { return true; } + bool customHighlight() const override { + return true; + } void stopAnimation() override; void checkAnimation() override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp index ebb7fd3186..b86d4fdefb 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.cpp @@ -485,6 +485,7 @@ void Photo::drawGrouped( const QRect &geometry, RectParts sides, RectParts corners, + float64 highlightOpacity, not_null cacheKey, not_null cache) const { ensureDataMediaCreated(); @@ -509,9 +510,18 @@ void Photo::drawGrouped( // App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners); } p.drawPixmap(geometry.topLeft(), *cache); - if (selected) { + + const auto overlayOpacity = selected + ? (1. - highlightOpacity) + : highlightOpacity; + if (overlayOpacity > 0.) { + p.setOpacity(overlayOpacity); const auto roundRadius = ImageRoundRadius::Large; Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners); + if (!selected) { + Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners); + } + p.setOpacity(1.); } const auto displayState = radial diff --git a/Telegram/SourceFiles/history/view/media/history_view_photo.h b/Telegram/SourceFiles/history/view/media/history_view_photo.h index 234843cf01..b0acad4475 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_photo.h +++ b/Telegram/SourceFiles/history/view/media/history_view_photo.h @@ -68,6 +68,7 @@ public: const QRect &geometry, RectParts sides, RectParts corners, + float64 highlightOpacity, not_null cacheKey, not_null cache) const override; TextState getStateGrouped(