From 8d0e1ab2ee31c2f2cdcd252190f63fafb0bbc404 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 10 Apr 2026 10:31:10 +0300 Subject: [PATCH] [thanos] Migrated history inner widget to ThanosEffectController. --- .../history/history_inner_widget.cpp | 168 ++++-------------- .../history/history_inner_widget.h | 23 +-- .../SourceFiles/history/history_widget.cpp | 127 ------------- Telegram/SourceFiles/history/history_widget.h | 13 -- 4 files changed, 44 insertions(+), 287 deletions(-) diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 21f23a9048..f2082a2cda 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -435,14 +435,7 @@ HistoryInner::HistoryInner( ) | rpl::on_next( [this](auto item) { itemRemoved(item); }, lifetime()); - session().data().itemsAboutToBeDestroyed( - ) | rpl::on_next( - [this](const auto &items) { captureItemsForThanosEffect(items); }, - lifetime()); - session().data().viewAboutToBeRemoved( - ) | rpl::on_next( - [this](auto view) { captureViewForThanosEffect(view); }, - lifetime()); + setupThanosEffect(); session().data().viewRemoved( ) | rpl::on_next( [this](auto view) { viewRemoved(view); }, @@ -4105,9 +4098,9 @@ void HistoryInner::setItemsRevealHeight(int revealHeight) { _revealHeight = revealHeight; } -void HistoryInner::setCollapseGaps(const std::vector &gaps) { +void HistoryInner::setCollapseGaps(std::vector gaps) { if (_collapseGaps != gaps) { - _collapseGaps = gaps; + _collapseGaps = std::move(gaps); updateSize(); } } @@ -4203,126 +4196,43 @@ void HistoryInner::leaveEventHook(QEvent *e) { return RpWidget::leaveEventHook(e); } -void HistoryInner::captureItemsForThanosEffect( - const std::vector> &items) { - if (!Ui::ThanosEffect::Supported()) { - return; - } - // Pre-capture all views while they are in their original visual - // state, before any item->destroy() changes neighbor views' - // attach/date flags via previousInBlocksChanged(). - _suppressCollapseAnimation = true; - const auto guard = gsl::finally([&] { - _suppressCollapseAnimation = false; - }); - for (const auto &item : items) { - if (const auto view = item->mainView()) { - const auto top = itemTop(view); - const auto height = view->height(); - captureViewForThanosEffect(view); - _thanosPreCaptured.emplace(view, PreCapturedView{ - .height = height, - .top = top, - }); - } - } -} +void HistoryInner::setupThanosEffect() { + const auto history = _history; + const auto migrated = [=] { return _migrated; }; + _thanosController = std::make_unique( + &session(), + Ui::ThanosEffectController::Delegate{ + .viewForItem = [=](not_null item) + -> HistoryView::Element* { + if (item->history() != history + && item->history() != migrated()) { + return nullptr; + } + return item->mainView(); + }, + .itemTop = [=](not_null view) { + return itemTop(view); + }, + .visibleAreaTop = [=] { return _visibleAreaTop; }, + .visibleAreaBottom = [=] { return _visibleAreaBottom; }, + .contentWidth = [=] { return width(); }, + .preparePaintContext = [=](QRect clip) { + return preparePaintContext(clip); + }, + .window = [=]() -> QWidget* { return window(); }, + .scrollArea = [=]() -> not_null { + return _scroll; + }, + .setCollapseGaps = [=](std::vector gaps) { + setCollapseGaps(std::move(gaps)); + }, + }, + lifetime()); -void HistoryInner::captureViewForThanosEffect( - not_null view) { - if (!Ui::ThanosEffect::Supported()) { - return; - } - // Skip views that were already pre-captured in the batch signal. - // Use the saved height/top from before any destruction started. - if (const auto it = _thanosPreCaptured.find(view); - it != end(_thanosPreCaptured)) { - const auto saved = it->second; - _thanosPreCaptured.erase(it); - if (saved.top >= 0) { - _widget->startCollapseAnimation(saved.height, saved.top); - } - return; - } - const auto item = view->data(); - if (item->history() != _history - && item->history() != _migrated) { - return; - } - if (!item->isRegular() || item->isService()) { - return; - } - const auto top = itemTop(view); - if (top < 0) { - return; - } - const auto viewHeight = view->height(); - const auto viewWidth = width(); - if (viewWidth <= 0 || viewHeight <= 0) { - return; - } - const auto visibleHeight = _visibleAreaBottom - _visibleAreaTop; - const auto screenTop = top - _visibleAreaTop; - if (screenTop + viewHeight <= 0 || screenTop >= visibleHeight) { - return; - } - auto gapOffset = 0; - for (const auto &gap : _collapseGaps) { - if (gap.absY >= 0 && top >= gap.absY) { - gapOffset += gap.height; - } - } - const auto adjustedScreenTop = screenTop + gapOffset; - const auto captureTop = std::clamp(-adjustedScreenTop, 0, viewHeight); - const auto captureBottom = std::clamp( - visibleHeight - adjustedScreenTop, - 0, - viewHeight); - if (captureTop >= captureBottom) { - return; - } - const auto captureHeight = captureBottom - captureTop; - - const auto dpr = style::DevicePixelRatio(); - auto image = QImage( - QSize(viewWidth, captureHeight) * dpr, - QImage::Format_RGBA8888_Premultiplied); - image.setDevicePixelRatio(dpr); - image.fill(Qt::transparent); - { - Painter p(&image); - p.translate(0, -captureTop); - auto clip = QRect(0, captureTop, viewWidth, captureHeight); - auto context = preparePaintContext(clip); - const auto renderedTop = top + gapOffset; - context.translate(0, -renderedTop); - context.clip = clip; - context.skipSelectionCheck = true; - context.outbg = view->hasOutLayout(); - view->draw(p, context); - } - - const auto topLevel = window(); - if (!topLevel) { - return; - } - - if (!_thanosEffect) { - _thanosEffect = std::make_unique(topLevel); - } - _thanosEffect->setGeometry(QRect(QPoint(), topLevel->size())); - _thanosEffect->raise(); - - const auto globalPos = _scroll->mapTo( - topLevel, - QPoint(0, adjustedScreenTop + captureTop)); - _thanosEffect->addItem( - std::move(image), - QRect(globalPos, QSize(viewWidth, captureHeight))); - - if (!_suppressCollapseAnimation) { - _widget->startCollapseAnimation(viewHeight, top); - } + session().data().viewAboutToBeRemoved( + ) | rpl::on_next([=](not_null view) { + _thanosController->captureOnRemoval(view->data()); + }, lifetime()); } HistoryInner::~HistoryInner() { diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index 23f76868cb..7fe9aa4c7c 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rp_widget.h" #include "ui/controls/swipe_handler_data.h" #include "ui/effects/animations.h" -#include "ui/effects/thanos_effect.h" +#include "ui/effects/thanos_effect_controller.h" #include "ui/dragging_scroll_manager.h" #include "ui/widgets/middle_click_autoscroll.h" #include "ui/widgets/tooltip.h" @@ -118,13 +118,8 @@ public: Ui::ChatPaintContext preparePaintContext(const QRect &clip) const; - struct CollapseGap { - int absY = -1; - int height = 0; - - friend bool operator==(const CollapseGap&, const CollapseGap&) = default; - }; - void setCollapseGaps(const std::vector &gaps); + using CollapseGap = Ui::CollapseGap; + void setCollapseGaps(std::vector gaps); void messagesReceived( not_null peer, @@ -618,17 +613,9 @@ private: [[nodiscard]] HistoryView::ElementOverlayHost &ensureOverlayHost(); std::unique_ptr _overlayHost; - void captureItemsForThanosEffect( - const std::vector> &items); - void captureViewForThanosEffect(not_null view); + void setupThanosEffect(); - std::unique_ptr _thanosEffect; - struct PreCapturedView { - int height = 0; - int top = 0; - }; - base::flat_map, PreCapturedView> _thanosPreCaptured; - bool _suppressCollapseAnimation = false; + std::unique_ptr _thanosController; std::vector _collapseGaps; }; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index fed798e578..4d7ff9819a 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7558,133 +7558,6 @@ void HistoryWidget::startItemRevealAnimations() { } } -void HistoryWidget::startCollapseAnimation(int height, int itemTop) { - constexpr auto kBaseMs = 400; - constexpr auto kPerPixelMs = 0.5; - constexpr auto kMaxMs = 1200; - - if (height <= 0 || !_list) { - return; - } - - const auto scrollTop = _scroll->scrollTop(); - const auto scrollBottom = scrollTop + _scroll->height(); - const auto itemBottom = itemTop + height; - - if (itemBottom >= scrollBottom) { - return; - } - - if (_collapseAnimation.animating()) { - for (auto &gap : _collapseGaps) { - gap.startHeight = gap.currentHeight; - } - } - - auto merged = false; - for (auto &gap : _collapseGaps) { - if (gap.absY + gap.originalHeight == itemTop - || (gap.absY <= itemTop - && itemTop <= gap.absY + gap.originalHeight)) { - gap.startHeight += height; - gap.currentHeight += height; - gap.originalHeight += height; - merged = true; - break; - } - if (itemTop + height == gap.absY) { - gap.absY = itemTop; - gap.startHeight += height; - gap.currentHeight += height; - gap.originalHeight += height; - merged = true; - break; - } - } - if (!merged) { - const auto it = std::lower_bound( - _collapseGaps.begin(), - _collapseGaps.end(), - itemTop, - [](const auto &gap, int top) { return gap.absY < top; }); - _collapseGaps.insert(it, { - .absY = itemTop, - .startHeight = height, - .currentHeight = height, - .originalHeight = height, - }); - } - - syncCollapseGapsToList(); - - const auto aboveViewport = std::max(0, scrollTop - itemTop); - const auto scrollAdjust = std::min(height, aboveViewport); - if (scrollAdjust > 0) { - synteticScrollToY(scrollTop + scrollAdjust); - } - - auto totalHeight = 0; - for (const auto &gap : _collapseGaps) { - totalHeight += gap.currentHeight; - } - const auto duration = int(std::clamp( - kBaseMs + totalHeight * kPerPixelMs, - double(kBaseMs), - double(kMaxMs))); - - _collapseAnimation.start( - [=] { collapseAnimationCallback(); }, - 0., - 1., - duration, - anim::easeOutCirc); -} - -void HistoryWidget::collapseAnimationCallback() { - const auto progress = _collapseAnimation.value(1.); - - auto totalDelta = 0; - for (auto &gap : _collapseGaps) { - const auto newHeight = anim::interpolate( - gap.startHeight, - 0, - progress); - totalDelta += (gap.currentHeight - newHeight); - gap.currentHeight = newHeight; - } - - if (totalDelta != 0) { - const auto scrollTop = _scroll->scrollTop(); - syncCollapseGapsToList(); - synteticScrollToY(std::max(scrollTop - totalDelta, 0)); - } - - if (!_collapseAnimation.animating()) { - _collapseGaps.clear(); - if (_list) { - _list->setCollapseGaps({}); - } - _collapseAnimation = {}; - } -} - -void HistoryWidget::syncCollapseGapsToList() { - if (!_list) { - return; - } - auto gaps = std::vector(); - gaps.reserve(_collapseGaps.size()); - auto cumulativeOriginal = 0; - for (const auto &g : _collapseGaps) { - gaps.push_back({ - .absY = g.absY - cumulativeOriginal, - .height = g.currentHeight, - }); - cumulativeOriginal += g.originalHeight; - } - _list->setCollapseGaps(gaps); -} - void HistoryWidget::startMessageSendingAnimation( not_null item) { if (_list->elementChatMode() == HistoryView::ElementChatMode::Default diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index 21a9072e7d..6a48fa5e07 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -338,9 +338,6 @@ protected: void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; -public: - void startCollapseAnimation(int height, int itemTop); - private: using TabbedPanel = ChatHelpers::TabbedPanel; using TabbedSelector = ChatHelpers::TabbedSelector; @@ -945,16 +942,6 @@ private: ItemRevealAnimation> _itemRevealAnimations; int _itemsRevealHeight = 0; - struct CollapseGapState { - int absY = -1; - int startHeight = 0; - int currentHeight = 0; - int originalHeight = 0; - }; - std::vector _collapseGaps; - Ui::Animations::Simple _collapseAnimation; - void collapseAnimationCallback(); - void syncCollapseGapsToList(); bool _sponsoredMessagesStateKnown = false; bool _justMarkingAsRead = false;