diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 046ee0901..1dee86f74 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -141,7 +141,8 @@ int InnerWidget::FilterResult::bottom() const { InnerWidget::InnerWidget( QWidget *parent, - not_null controller) + not_null controller, + rpl::producer childListShown) : RpWidget(parent) , _controller(controller) , _shownList(controller->session().data().chatsList()->indexed()) @@ -153,7 +154,8 @@ InnerWidget::InnerWidget( + st::defaultDialogRow.photoSize + st::defaultDialogRow.padding.left()) , _cancelSearchInChat(this, st::dialogsCancelSearchInPeer) -, _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) { +, _cancelSearchFromUser(this, st::dialogsCancelSearchInPeer) +, _childListShown(std::move(childListShown)) { setAttribute(Qt::WA_OpaquePaintEvent, true); _cancelSearchInChat->hide(); @@ -546,7 +548,7 @@ void InnerWidget::paintEvent(QPaintEvent *e) { const auto r = e->rect(); auto dialogsClip = r; const auto ms = crl::now(); - const auto shownForum = _controller->shownForum().current(); + const auto childListShown = _childListShown.current(); const auto paintRow = [&]( not_null row, bool selected, @@ -558,23 +560,25 @@ void InnerWidget::paintEvent(QPaintEvent *e) { if (forum && !_topicJumpCache) { _topicJumpCache = std::make_unique(); } - const auto expanded = !active + + const auto expanded = (!active && forum - && !_openedForum - && (key.history()->peer->forum() == shownForum); + && (key.history()->peer->id == childListShown.peerId)) + ? childListShown.shown + : 0.; Ui::RowPainter::Paint(p, row, validateVideoUserpic(row), { .st = (forum ? &st::forumDialogRow : _st.get()), .topicJumpCache = _topicJumpCache.get(), .folder = _openedFolder, .forum = _openedForum, .filter = _filterId, + .topicsExpanded = expanded, .now = ms, .width = fullWidth, .active = active, .selected = (_menuRow.key ? (row->key() == _menuRow.key) : selected), - .topicsExpanded = expanded, .topicJumpSelected = (selected && _selectedTopicJump && (!_pressed || _pressedTopicJump)), diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h index b9d221685..7646bb7f5 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h @@ -83,9 +83,14 @@ enum class WidgetState { class InnerWidget final : public Ui::RpWidget { public: + struct ChildListShown { + PeerId peerId = 0; + float64 shown = 0.; + }; InnerWidget( QWidget *parent, - not_null controller); + not_null controller, + rpl::producer childListShown); void searchReceived( std::vector> result, @@ -490,6 +495,8 @@ private: rpl::event_stream _completeHashtagRequests; rpl::event_stream<> _refreshHashtagsRequests; + rpl::variable _childListShown; + base::unique_qptr _menu; }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index bfb703c2b..4770b476b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -202,7 +202,16 @@ Widget::Widget( , _scrollToTop(_scroll, st::dialogsToUp) , _searchTimer([=] { searchMessages(); }) , _singleMessageSearch(&controller->session()) { - _inner = _scroll->setOwnedWidget(object_ptr(this, controller)); + const auto makeChildListShown = [](PeerId peerId, float64 shown) { + return InnerWidget::ChildListShown{ peerId, shown }; + }; + _inner = _scroll->setOwnedWidget(object_ptr( + this, + controller, + rpl::combine( + _childListPeerId.value(), + _childListShown.value(), + makeChildListShown))); _inner->updated( ) | rpl::start_with_next([=] { @@ -392,27 +401,29 @@ Widget::Widget( setupSupportMode(); setupScrollUpButton(); - changeOpenedFolder( - controller->openedFolder().current(), - anim::type::instant); - - controller->openedFolder().changes( - ) | rpl::start_with_next([=](Data::Folder *folder) { - changeOpenedFolder(folder, anim::type::normal); - }, lifetime()); - if (_layout != Layout::Child) { + changeOpenedFolder( + controller->openedFolder().current(), + anim::type::instant); + + controller->openedFolder().changes( + ) | rpl::start_with_next([=](Data::Folder *folder) { + changeOpenedFolder(folder, anim::type::normal); + }, lifetime()); + controller->shownForum().changes( ) | rpl::filter(!rpl::mappers::_1) | rpl::start_with_next([=] { if (_openedForum) { changeOpenedForum(nullptr, anim::type::normal); } else if (_childList) { - _childList = nullptr; - _childListShadow = nullptr; - updateControlsGeometry(); - updateControlsVisibility(true); + closeChildList(anim::type::normal); } }, lifetime()); + + _childListShown.changes( + ) | rpl::start_with_next([=] { + updateControlsGeometry(); + }, lifetime()); } setupDownloadBar(); @@ -440,9 +451,14 @@ void Widget::chosenRow(const ChosenRow &row) { row.message.fullId.msg, Window::SectionShow::Way::ClearStack); } else if (history && history->peer->isForum() && !row.message.fullId) { - controller()->showForum( - history->peer->forum(), - Window::SectionShow().withChildColumn()); + const auto forum = history->peer->forum(); + if (controller()->shownForum().current() == forum) { + controller()->closeForum(); + } else { + controller()->showForum( + forum, + Window::SectionShow().withChildColumn()); + } return; } else if (history) { const auto peer = history->peer; @@ -468,15 +484,15 @@ void Widget::chosenRow(const ChosenRow &row) { toSeparate(); } } else { - hideChildList(); controller()->showThread( history, showAtMsgId, Window::SectionShow::Way::ClearStack); + hideChildList(); } } else if (const auto folder = row.key.folder()) { - hideChildList(); controller()->openFolder(folder); + hideChildList(); } if (row.filteredRow && !session().supportMode()) { if (_subsectionTopBar) { @@ -735,6 +751,9 @@ void Widget::updateControlsVisibility(bool fast) { _childList->show(); _childListShadow->show(); } + if (_hideChildListCanvas) { + _hideChildListCanvas->show(); + } } void Widget::changeOpenedSubsection( @@ -770,15 +789,24 @@ void Widget::changeOpenedSubsection( } void Widget::changeOpenedFolder(Data::Folder *folder, anim::type animated) { + if (_openedFolder == folder) { + return; + } changeOpenedSubsection([&] { + closeChildList(anim::type::instant); + controller()->closeForum(); _openedFolder = folder; _inner->changeOpenedFolder(folder); }, (folder != nullptr), animated); } void Widget::changeOpenedForum(Data::Forum *forum, anim::type animated) { + if (_openedForum == forum) { + return; + } changeOpenedSubsection([&] { cancelSearch(); + closeChildList(anim::type::instant); _openedForum = forum; _api.request(base::take(_topicSearchRequest)).cancel(); _inner->changeOpenedForum(forum); @@ -965,7 +993,9 @@ void Widget::checkUpdateStatus() { } void Widget::setInnerFocus() { - if (!_openedFolder && !_openedForum) { + if (_childList) { + _childList->setInnerFocus(); + } else if (!_openedFolder && !_openedForum) { _filter->setFocus(); } else if (!_subsectionTopBar->searchSetFocus()) { setFocus(); @@ -1064,18 +1094,28 @@ void Widget::showFast() { show(); } -void Widget::showAnimated(Window::SlideDirection direction, const Window::SectionSlideParams ¶ms) { +rpl::producer Widget::shownProgressValue() const { + return _shownProgressValue.value(); +} + +void Widget::showAnimated( + Window::SlideDirection direction, + const Window::SectionSlideParams ¶ms) { _showAnimation = nullptr; auto oldContentCache = params.oldContentCache; showFast(); - const auto content = controller()->content(); - auto newContentCache = content->grabForShowAnimation(params); + auto newContentCache = Ui::GrabWidget(this); if (_updateTelegram) { _updateTelegram->hide(); } _connecting->setForceHidden(true); + if (_childList) { + _childList->hide(); + _childListShadow->hide(); + } + _shownProgressValue = 0.; startSlideAnimation( std::move(oldContentCache), std::move(newContentCache), @@ -1106,7 +1146,12 @@ void Widget::startSlideAnimation( _showAnimation = std::make_unique(); _showAnimation->setDirection(direction); - _showAnimation->setRepaintCallback([=] { update(); }); + _showAnimation->setRepaintCallback([=] { + if (_shownProgressValue.current() < 1.) { + _shownProgressValue = _showAnimation->progress(); + } + update(); + }); _showAnimation->setFinishedCallback([=] { slideFinished(); }); _showAnimation->setPixmaps(oldContentCache, newContentCache); _showAnimation->start(); @@ -1122,6 +1167,7 @@ QRect Widget::floatPlayerAvailableRect() { void Widget::slideFinished() { _showAnimation = nullptr; + _shownProgressValue = 1.; updateControlsVisibility(true); if ((!_subsectionTopBar || !_subsectionTopBar->searchHasFocus()) && !_filter->hasFocus()) { @@ -1838,12 +1884,12 @@ void Widget::dropEvent(QDropEvent *e) { const auto point = mapToGlobal(e->pos()); if (const auto thread = _inner->updateFromParentDrag(point)) { e->acceptProposedAction(); - if (!thread->owningHistory()->peer->isForum()) { - hideChildList(); - } controller()->content()->filesOrForwardDrop( thread, e->mimeData()); + if (!thread->owningHistory()->peer->isForum()) { + hideChildList(); + } controller()->widget()->raise(); controller()->widget()->activateWindow(); } @@ -1902,6 +1948,22 @@ void Widget::showForum( return; } cancelSearch(); + openChildList(forum, params); +} + +void Widget::openChildList( + not_null forum, + const Window::SectionShow ¶ms) { + auto slide = Window::SectionSlideParams(); + const auto animated = !_childList + && (params.animated == anim::type::normal); + if (animated) { + _childListShown = 0.; + _hideChildListCanvas = nullptr; + slide.oldContentCache = Ui::GrabWidget( + this, + QRect(_narrowWidth, 0, width() - _narrowWidth, height())); + } auto copy = params; copy.childColumn = false; copy.animated = anim::type::instant; @@ -1910,19 +1972,87 @@ void Widget::showForum( controller(), Layout::Child); _childList->showForum(forum, copy); + _childListPeerId = forum->channel()->id; + _childListShadow = std::make_unique(this); - _childListShadow->setAttribute(Qt::WA_TransparentForMouseEvents); - _childListShadow->paintRequest( + const auto shadow = _childListShadow.get(); + const auto opacity = shadow->lifetime().make_state(0.); + shadow->setAttribute(Qt::WA_TransparentForMouseEvents); + shadow->paintRequest( ) | rpl::start_with_next([=] { - auto p = QPainter(_childListShadow.get()); + auto p = QPainter(shadow); + p.setOpacity(*opacity); st::slideShadow.fill(p, QRect( - _childListShadow->width() - st::slideShadow.width(), + shadow->width() - st::slideShadow.width(), 0, st::slideShadow.width(), - _childListShadow->height())); - }, _childListShadow->lifetime()); + shadow->height())); + }, shadow->lifetime()); + _childListShown.value() | rpl::start_with_next([=](float64 value) { + *opacity = value; + if (!value && _childListShadow.get() != shadow) { + delete shadow; + } else { + shadow->update(); + } + }, shadow->lifetime()); + updateControlsGeometry(); updateControlsVisibility(true); + + if (animated) { + _childList->showAnimated(Window::SlideDirection::FromRight, slide); + _childListShown = _childList->shownProgressValue(); + } else { + _childListShown = 1.; + } +} + +void Widget::closeChildList(anim::type animated) { + if (!_childList) { + return; + } + const auto geometry = _childList->geometry(); + const auto shown = _childListShown.current(); + auto oldContentCache = QPixmap(); + auto animation = (Window::SlideAnimation*)nullptr; + if (animated == anim::type::normal) { + oldContentCache = Ui::GrabWidget(_childList.get()); + _hideChildListCanvas = std::make_unique(this); + _hideChildListCanvas->setGeometry(geometry); + animation = _hideChildListCanvas->lifetime().make_state< + Window::SlideAnimation + >(); + _hideChildListCanvas->paintRequest( + ) | rpl::start_with_next([=] { + QPainter p(_hideChildListCanvas.get()); + animation->paintContents(p); + }, _hideChildListCanvas->lifetime()); + } + _childList = nullptr; + _childListShown = 0.; + if (animated == anim::type::normal) { + _hideChildListCanvas->hide(); + auto newContentCache = Ui::GrabWidget(this, geometry); + _hideChildListCanvas->show(); + + _childListShown = shown; + _childListShadow.release(); + + animation->setDirection(Window::SlideDirection::FromLeft); + animation->setRepaintCallback([=] { + _childListShown = (1. - animation->progress()) * shown; + _hideChildListCanvas->update(); + }); + animation->setFinishedCallback([=] { + _childListShown = 0.; + _hideChildListCanvas = nullptr; + }); + animation->setPixmaps(oldContentCache, newContentCache); + animation->start(); + } else { + _childListShadow = nullptr; + } } void Widget::searchInChat(Key chat) { @@ -2162,12 +2292,16 @@ void Widget::updateSearchFromVisibility(bool fast) { void Widget::updateControlsGeometry() { auto filterAreaTop = 0; - const auto usew = _childList ? _narrowWidth : width(); - const auto childw = std::max(_narrowWidth, width() - usew); + + const auto ratiow = anim::interpolate( + width(), + _narrowWidth, + _childListShown.current()); const auto smallw = st::columnMinimalWidthLeft - _narrowWidth; - const auto smallLayoutRatio = (usew < smallw) - ? ((smallw - usew) / float64(smallw - _narrowWidth)) + const auto smallLayoutRatio = (ratiow < smallw) + ? ((smallw - ratiow) / float64(smallw - _narrowWidth)) : 0.; + auto filterLeft = (controller()->filtersWidth() ? st::dialogsFilterSkip : (st::dialogsFilterPadding.x() + _mainMenuToggle->width())) @@ -2175,9 +2309,9 @@ void Widget::updateControlsGeometry() { auto filterRight = (session().domain().local().hasLocalPasscode() ? (st::dialogsFilterPadding.x() + _lockUnlock->width()) : st::dialogsFilterSkip) + st::dialogsFilterPadding.x(); - auto filterWidth = qMax(usew, smallw) - filterLeft - filterRight; + auto filterWidth = qMax(ratiow, smallw) - filterLeft - filterRight; auto filterAreaHeight = st::topBarHeight; - _searchControls->setGeometry(0, filterAreaTop, usew, filterAreaHeight); + _searchControls->setGeometry(0, filterAreaTop, ratiow, filterAreaHeight); if (_subsectionTopBar) { _subsectionTopBar->setGeometry(_searchControls->geometry()); } @@ -2202,6 +2336,7 @@ void Widget::updateControlsGeometry() { right -= _jumpToDate->width(); _jumpToDate->moveToLeft(right, _filter->y()); right -= _chooseFromUser->width(); _chooseFromUser->moveToLeft(right, _filter->y()); + const auto usew = _childList ? _narrowWidth : width(); if (_forumTopShadow) { _forumTopShadow->setGeometry( 0, @@ -2264,6 +2399,7 @@ void Widget::updateControlsGeometry() { } if (_childList) { + const auto childw = std::max(_narrowWidth, width() - usew); _childList->setGeometryWithTopMoved( { width() - childw, 0, childw, height() }, _topDelta); diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.h b/Telegram/SourceFiles/dialogs/dialogs_widget.h index 9461296dc..3f3c86aaf 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.h +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.h @@ -100,6 +100,7 @@ public: Window::SlideDirection direction, const Window::SectionSlideParams ¶ms); void showFast(); + [[nodiscard]] rpl::producer shownProgressValue() const; void scrollToEntry(const RowDescriptor &entry); @@ -187,6 +188,11 @@ private: QPixmap newContentCache, Window::SlideDirection direction); + void openChildList( + not_null forum, + const Window::SectionShow ¶ms); + void closeChildList(anim::type animated); + void fullSearchRefreshOn(rpl::producer<> events); void applyFilterUpdate(bool force = false); void refreshLoadMoreButton(bool mayBlock, bool isBlocked); @@ -237,6 +243,7 @@ private: Ui::Animations::Simple _scrollToAnimation; std::unique_ptr _showAnimation; + rpl::variable _shownProgressValue; Ui::Animations::Simple _scrollToTopShown; object_ptr _scrollToTop; @@ -287,6 +294,9 @@ private: std::unique_ptr _childList; std::unique_ptr _childListShadow; + rpl::variable _childListShown; + rpl::variable _childListPeerId; + std::unique_ptr _hideChildListCanvas; }; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index df3926c42..105ea4958 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -186,17 +186,18 @@ int PaintBadges( return (initial - right); } -void PaintExpandedTopicsBar(QPainter &p) { +void PaintExpandedTopicsBar(QPainter &p, float64 progress) { auto hq = PainterHighQualityEnabler(p); const auto radius = st::roundRadiusLarge; const auto width = st::forumDialogRow.padding.left() / 2; p.setPen(Qt::NoPen); p.setBrush(st::dialogsBgActive); p.drawRoundedRect( - -3 * radius, - st::forumDialogRow.padding.top(), - 3 * radius + width, - st::forumDialogRow.photoSize, + QRectF( + -3. * radius - width * (1. - progress), + st::forumDialogRow.padding.top(), + 3. * radius + width, + st::forumDialogRow.photoSize), radius, radius); } @@ -349,13 +350,13 @@ void PaintRow( } auto nameleft = context.st->nameLeft; + if (context.topicsExpanded > 0.) { + PaintExpandedTopicsBar(p, context.topicsExpanded); + } if (context.narrow) { if (!draft && item && !item->isEmpty()) { PaintNarrowCounter(p, context, badgesState); } - if (context.topicsExpanded) { - PaintExpandedTopicsBar(p); - } return; } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h index 36b525049..cd5627773 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.h @@ -58,11 +58,11 @@ struct PaintContext { Data::Folder *folder = nullptr; Data::Forum *forum = nullptr; FilterId filter = 0; + float64 topicsExpanded = 0.; crl::time now = 0; int width = 0; bool active = false; bool selected = false; - bool topicsExpanded = false; bool topicJumpSelected = false; bool paused = false; bool search = false; diff --git a/Telegram/SourceFiles/window/window_slide_animation.cpp b/Telegram/SourceFiles/window/window_slide_animation.cpp index 0ba70e416..5f32d481d 100644 --- a/Telegram/SourceFiles/window/window_slide_animation.cpp +++ b/Telegram/SourceFiles/window/window_slide_animation.cpp @@ -141,6 +141,12 @@ void SlideAnimation::paintContents(QPainter &p) const { } } +float64 SlideAnimation::progress() const { + const auto slideLeft = (_direction == SlideDirection::FromLeft); + const auto progress = _animation.value(slideLeft ? 0. : 1.); + return slideLeft ? (1. - progress) : progress; +} + void SlideAnimation::setDirection(SlideDirection direction) { _direction = direction; } @@ -193,8 +199,8 @@ void SlideAnimation::start() { void SlideAnimation::animationCallback() { _repaintCallback(); if (!_animation.animating()) { - if (_finishedCallback) { - _finishedCallback(); + if (const auto onstack = _finishedCallback) { + onstack(); } } } diff --git a/Telegram/SourceFiles/window/window_slide_animation.h b/Telegram/SourceFiles/window/window_slide_animation.h index efe0f9696..ae9661719 100644 --- a/Telegram/SourceFiles/window/window_slide_animation.h +++ b/Telegram/SourceFiles/window/window_slide_animation.h @@ -20,6 +20,8 @@ class SlideAnimation { public: void paintContents(QPainter &p) const; + [[nodiscard]] float64 progress() const; + void setDirection(SlideDirection direction); void setPixmaps( const QPixmap &oldContentCache,