mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-08 04:34:05 +00:00
[thanos] Migrated history inner widget to ThanosEffectController.
This commit is contained in:
@@ -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<CollapseGap> &gaps) {
|
||||
void HistoryInner::setCollapseGaps(std::vector<CollapseGap> 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<not_null<HistoryItem*>> &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<Ui::ThanosEffectController>(
|
||||
&session(),
|
||||
Ui::ThanosEffectController::Delegate{
|
||||
.viewForItem = [=](not_null<const HistoryItem*> item)
|
||||
-> HistoryView::Element* {
|
||||
if (item->history() != history
|
||||
&& item->history() != migrated()) {
|
||||
return nullptr;
|
||||
}
|
||||
return item->mainView();
|
||||
},
|
||||
.itemTop = [=](not_null<const HistoryView::Element*> 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<Ui::ScrollArea*> {
|
||||
return _scroll;
|
||||
},
|
||||
.setCollapseGaps = [=](std::vector<CollapseGap> gaps) {
|
||||
setCollapseGaps(std::move(gaps));
|
||||
},
|
||||
},
|
||||
lifetime());
|
||||
|
||||
void HistoryInner::captureViewForThanosEffect(
|
||||
not_null<const Element*> 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<Ui::ThanosEffect>(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<const HistoryView::Element*> view) {
|
||||
_thanosController->captureOnRemoval(view->data());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
HistoryInner::~HistoryInner() {
|
||||
|
||||
@@ -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<CollapseGap> &gaps);
|
||||
using CollapseGap = Ui::CollapseGap;
|
||||
void setCollapseGaps(std::vector<CollapseGap> gaps);
|
||||
|
||||
void messagesReceived(
|
||||
not_null<PeerData*> peer,
|
||||
@@ -618,17 +613,9 @@ private:
|
||||
[[nodiscard]] HistoryView::ElementOverlayHost &ensureOverlayHost();
|
||||
std::unique_ptr<HistoryView::ElementOverlayHost> _overlayHost;
|
||||
|
||||
void captureItemsForThanosEffect(
|
||||
const std::vector<not_null<HistoryItem*>> &items);
|
||||
void captureViewForThanosEffect(not_null<const Element*> view);
|
||||
void setupThanosEffect();
|
||||
|
||||
std::unique_ptr<Ui::ThanosEffect> _thanosEffect;
|
||||
struct PreCapturedView {
|
||||
int height = 0;
|
||||
int top = 0;
|
||||
};
|
||||
base::flat_map<not_null<const Element*>, PreCapturedView> _thanosPreCaptured;
|
||||
bool _suppressCollapseAnimation = false;
|
||||
std::unique_ptr<Ui::ThanosEffectController> _thanosController;
|
||||
std::vector<CollapseGap> _collapseGaps;
|
||||
|
||||
};
|
||||
|
||||
@@ -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<HistoryInner::CollapseGap>();
|
||||
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<HistoryItem*> item) {
|
||||
if (_list->elementChatMode() == HistoryView::ElementChatMode::Default
|
||||
|
||||
@@ -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<CollapseGapState> _collapseGaps;
|
||||
Ui::Animations::Simple _collapseAnimation;
|
||||
void collapseAnimationCallback();
|
||||
void syncCollapseGapsToList();
|
||||
|
||||
bool _sponsoredMessagesStateKnown = false;
|
||||
bool _justMarkingAsRead = false;
|
||||
|
||||
Reference in New Issue
Block a user