Animate child topics list open / close.

This commit is contained in:
John Preston 2022-12-01 17:48:33 +04:00
parent 468d01fc1a
commit 32491ead5e
8 changed files with 224 additions and 58 deletions

View file

@ -141,7 +141,8 @@ int InnerWidget::FilterResult::bottom() const {
InnerWidget::InnerWidget(
QWidget *parent,
not_null<Window::SessionController*> controller)
not_null<Window::SessionController*> controller,
rpl::producer<ChildListShown> 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*> row,
bool selected,
@ -558,23 +560,25 @@ void InnerWidget::paintEvent(QPaintEvent *e) {
if (forum && !_topicJumpCache) {
_topicJumpCache = std::make_unique<Ui::TopicJumpCache>();
}
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)),

View file

@ -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<Window::SessionController*> controller);
not_null<Window::SessionController*> controller,
rpl::producer<ChildListShown> childListShown);
void searchReceived(
std::vector<not_null<HistoryItem*>> result,
@ -490,6 +495,8 @@ private:
rpl::event_stream<QString> _completeHashtagRequests;
rpl::event_stream<> _refreshHashtagsRequests;
rpl::variable<ChildListShown> _childListShown;
base::unique_qptr<Ui::PopupMenu> _menu;
};

View file

@ -202,7 +202,16 @@ Widget::Widget(
, _scrollToTop(_scroll, st::dialogsToUp)
, _searchTimer([=] { searchMessages(); })
, _singleMessageSearch(&controller->session()) {
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(this, controller));
const auto makeChildListShown = [](PeerId peerId, float64 shown) {
return InnerWidget::ChildListShown{ peerId, shown };
};
_inner = _scroll->setOwnedWidget(object_ptr<InnerWidget>(
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 &params) {
rpl::producer<float64> Widget::shownProgressValue() const {
return _shownProgressValue.value();
}
void Widget::showAnimated(
Window::SlideDirection direction,
const Window::SectionSlideParams &params) {
_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<Window::SlideAnimation>();
_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<Data::Forum*> forum,
const Window::SectionShow &params) {
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<Ui::RpWidget>(this);
_childListShadow->setAttribute(Qt::WA_TransparentForMouseEvents);
_childListShadow->paintRequest(
const auto shadow = _childListShadow.get();
const auto opacity = shadow->lifetime().make_state<float64>(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<Ui::RpWidget>(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);

View file

@ -100,6 +100,7 @@ public:
Window::SlideDirection direction,
const Window::SectionSlideParams &params);
void showFast();
[[nodiscard]] rpl::producer<float64> shownProgressValue() const;
void scrollToEntry(const RowDescriptor &entry);
@ -187,6 +188,11 @@ private:
QPixmap newContentCache,
Window::SlideDirection direction);
void openChildList(
not_null<Data::Forum*> forum,
const Window::SectionShow &params);
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<Window::SlideAnimation> _showAnimation;
rpl::variable<float64> _shownProgressValue;
Ui::Animations::Simple _scrollToTopShown;
object_ptr<Ui::HistoryDownButton> _scrollToTop;
@ -287,6 +294,9 @@ private:
std::unique_ptr<Widget> _childList;
std::unique_ptr<Ui::RpWidget> _childListShadow;
rpl::variable<float64> _childListShown;
rpl::variable<PeerId> _childListPeerId;
std::unique_ptr<Ui::RpWidget> _hideChildListCanvas;
};

View file

@ -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;
}

View file

@ -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;

View file

@ -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();
}
}
}

View file

@ -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,