From 0180fe9468afb737bf516ca42633723de6db229a Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 12 Apr 2024 19:18:07 +0400 Subject: [PATCH] Animate search suggestions. --- .../SourceFiles/dialogs/dialogs_widget.cpp | 76 ++++++++++++++----- Telegram/SourceFiles/dialogs/dialogs_widget.h | 2 + .../dialogs/ui/dialogs_suggestions.cpp | 55 +++++++++++++- .../dialogs/ui/dialogs_suggestions.h | 10 +++ 4 files changed, 122 insertions(+), 21 deletions(-) diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index e2c43a20a1..411b46e75b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -1030,10 +1030,7 @@ void Widget::fullSearchRefreshOn(rpl::producer<> events) { void Widget::updateControlsVisibility(bool fast) { updateLoadMoreChatsVisibility(); - _scroll->setVisible(!_suggestions); - if (_suggestions) { - _suggestions->show(); - } + _scroll->setVisible(!_suggestions && _hidingSuggestions.empty()); updateStoriesVisibility(); if ((_openedFolder || _openedForum) && _searchHasFocus) { setInnerFocus(); @@ -1120,10 +1117,32 @@ void Widget::updateSuggestions(anim::type animated) { const auto suggest = _searchHasFocus && !_searchInChat && (_inner->state() == WidgetState::Default); + if (anim::Disabled() || !session().data().chatsListLoaded()) { + animated = anim::type::instant; + } if (!suggest && _suggestions) { - _suggestions = nullptr; - _scroll->show(); + if (animated == anim::type::normal) { + startWidthAnimation(); + _suggestions->hide(animated, [=, raw = _suggestions.get()] { + stopWidthAnimation(); + _hidingSuggestions.erase( + ranges::remove( + _hidingSuggestions, + raw, + &std::unique_ptr::get), + end(_hidingSuggestions)); + updateControlsVisibility(); + }); + _hidingSuggestions.push_back(std::move(_suggestions)); + } else { + _suggestions = nullptr; + _hidingSuggestions.clear(); + _scroll->show(); + } } else if (suggest && !_suggestions) { + if (animated == anim::type::normal) { + startWidthAnimation(); + } _suggestions = std::make_unique( this, controller(), @@ -1141,9 +1160,12 @@ void Widget::updateSuggestions(anim::type animated) { } }, _suggestions->lifetime()); - _suggestions->show(); - _scroll->hide(); updateControlsGeometry(); + + _suggestions->show(animated, [=] { + stopWidthAnimation(); + }); + _scroll->hide(); } } @@ -1542,18 +1564,15 @@ void Widget::scrollToDefault(bool verytop) { anim::sineInOut); } -void Widget::startWidthAnimation() { - if (!_widthAnimationCache.isNull()) { - return; - } +[[nodiscard]] QPixmap Widget::grabNonNarrowScrollFrame() { auto scrollGeometry = _scroll->geometry(); auto grabGeometry = QRect( scrollGeometry.x(), scrollGeometry.y(), - st::columnMinimalWidthLeft, + std::max(scrollGeometry.width(), st::columnMinimalWidthLeft), scrollGeometry.height()); _scroll->setGeometry(grabGeometry); - _inner->resize(st::columnMinimalWidthLeft, _inner->height()); + _inner->resize(grabGeometry.width(), _inner->height()); _inner->setNarrowRatio(0.); Ui::SendPendingMoveResizeEvents(_scroll); auto image = QImage( @@ -1565,11 +1584,18 @@ void Widget::startWidthAnimation() { QPainter p(&image); Ui::RenderWidget(p, _scroll); } - _widthAnimationCache = Ui::PixmapFromImage(std::move(image)); if (scrollGeometry != grabGeometry) { _scroll->setGeometry(scrollGeometry); updateControlsGeometry(); } + return Ui::PixmapFromImage(std::move(image)); +} + +void Widget::startWidthAnimation() { + if (!_widthAnimationCache.isNull()) { + return; + } + _widthAnimationCache = grabNonNarrowScrollFrame(); _scroll->hide(); updateStoriesVisibility(); } @@ -1578,9 +1604,6 @@ void Widget::stopWidthAnimation() { _widthAnimationCache = QPixmap(); if (!_showAnimation) { _scroll->setVisible(!_suggestions); - if (_suggestions) { - _suggestions->show(); - } } updateStoriesVisibility(); update(); @@ -3226,7 +3249,15 @@ void Widget::paintEvent(QPaintEvent *e) { auto belowTop = _scroll->y() + _scroll->height(); if (!_widthAnimationCache.isNull()) { - p.drawPixmapLeft(0, _scroll->y(), width(), _widthAnimationCache); + const auto suggestionsShown = _suggestions + ? _suggestions->shownOpacity() + : !_hidingSuggestions.empty() + ? _hidingSuggestions.back()->shownOpacity() + : 0.; + const auto suggestionsSkip = suggestionsShown + * (st::topPeers.height + st::searchedBarHeight); + const auto top = _scroll->y() + suggestionsSkip; + p.drawPixmapLeft(0, top, width(), _widthAnimationCache); belowTop = _scroll->y() + (_widthAnimationCache.height() / style::DevicePixelRatio()); } @@ -3302,12 +3333,17 @@ bool Widget::cancelSearch() { setFocus(); clearingInChat = true; } + const auto clearSearchFocus = !_searchInChat && _searchHasFocus; + if (!_suggestions && clearSearchFocus) { + // Don't create suggestions in unfocus case. + setFocus(); + } _lastSearchPeer = nullptr; _lastSearchId = _lastSearchMigratedId = 0; _inner->clearFilter(); clearSearchField(); applySearchUpdate(); - if (!_searchInChat && _searchHasFocus) { + if (_suggestions && clearSearchFocus) { setFocus(); } return clearingQuery || clearingInChat; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index b0f892e2d4..2cb979d534 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -105,6 +105,7 @@ public: void jumpToTop(bool belowPinned = false); void raiseWithTooltip(); + [[nodiscard]] QPixmap grabNonNarrowScrollFrame(); void startWidthAnimation(); void stopWidthAnimation(); @@ -277,6 +278,7 @@ private: object_ptr _scroll; QPointer _inner; std::unique_ptr _suggestions; + std::vector> _hidingSuggestions; class BottomButton; object_ptr _updateTelegram = { nullptr }; object_ptr _loadMoreChats = { nullptr }; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index b3af39435d..033cdc7c90 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -545,8 +545,61 @@ void Suggestions::chooseRow() { } } +void Suggestions::show(anim::type animated, Fn finish) { + RpWidget::show(); + + _hidden = false; + if (animated == anim::type::instant) { + _shownAnimation.stop(); + _scroll->show(); + } else { + _shownAnimation.start([=] { + update(); + if (!_shownAnimation.animating() && finish) { + finish(); + _cache = QPixmap(); + _scroll->show(); + } + }, 0., 1., st::slideDuration, anim::easeOutQuint); + _cache = Ui::GrabWidget(_scroll.get()); + _scroll->hide(); + } +} + +void Suggestions::hide(anim::type animated, Fn finish) { + _hidden = true; + if (isHidden()) { + return; + } else if (animated == anim::type::instant) { + RpWidget::hide(); + } else { + _shownAnimation.start([=] { + update(); + if (!_shownAnimation.animating() && finish) { + finish(); + } + }, 1., 0., st::slideDuration, anim::easeOutQuint); + _cache = Ui::GrabWidget(_scroll.get()); + _scroll->hide(); + } +} + +float64 Suggestions::shownOpacity() const { + return _shownAnimation.value(_hidden ? 0. : 1.); +} + void Suggestions::paintEvent(QPaintEvent *e) { - QPainter(this).fillRect(e->rect(), st::windowBg); + const auto opacity = shownOpacity(); + auto color = st::windowBg->c; + color.setAlphaF(color.alphaF() * opacity); + + auto p = QPainter(this); + p.fillRect(e->rect(), color); + if (_scroll->isHidden()) { + const auto slide = st::topPeers.height + st::searchedBarHeight; + p.setOpacity(opacity); + p.drawPixmap(0, (opacity - 1.) * slide, _cache); + } } void Suggestions::resizeEvent(QResizeEvent *e) { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index b960eddafd..b6be97e266 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/object_ptr.h" #include "dialogs/ui/top_peers_strip.h" +#include "ui/effects/animations.h" #include "ui/rp_widget.h" namespace Main { @@ -44,6 +45,10 @@ public: void selectJump(Qt::Key direction, int pageSize = 0); void chooseRow(); + void show(anim::type animated, Fn finish); + void hide(anim::type animated, Fn finish); + [[nodiscard]] float64 shownOpacity() const; + [[nodiscard]] rpl::producer> topPeerChosen() const { return _topPeerChosen.events(); } @@ -81,6 +86,11 @@ private: rpl::event_stream> _topPeerChosen; rpl::event_stream> _recentPeerChosen; + Ui::Animations::Simple _shownAnimation; + Fn _showFinished; + bool _hidden = false; + QPixmap _cache; + }; [[nodiscard]] rpl::producer TopPeersContent(