mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-22 09:07:05 +02:00
Implement nice stories list scrolling.
This commit is contained in:
parent
1d27c8c940
commit
16128d61c0
8 changed files with 248 additions and 29 deletions
|
@ -142,7 +142,7 @@ InnerWidget::InnerWidget(
|
|||
, _stories(std::make_unique<Stories::List>(
|
||||
this,
|
||||
Stories::ContentForSession(&controller->session()),
|
||||
[=] { return st::dialogsStoriesFull.height - _visibleTop; }))
|
||||
[=] { return _stories->height() - _visibleTop; }))
|
||||
, _shownList(controller->session().data().chatsList()->indexed())
|
||||
, _st(&st::defaultDialogRow)
|
||||
, _pinnedShiftAnimation([=](crl::time now) {
|
||||
|
@ -323,6 +323,18 @@ InnerWidget::InnerWidget(
|
|||
switchToFilter(filterId);
|
||||
}, lifetime());
|
||||
|
||||
_stories->heightValue(
|
||||
) | rpl::filter([=] {
|
||||
return (_viewportHeight > 0) && (defaultScrollTop() > _visibleTop);
|
||||
}) | rpl::start_with_next([=] {
|
||||
jumpToTop();
|
||||
}, lifetime());
|
||||
|
||||
_stories->entered(
|
||||
) | rpl::start_with_next([=] {
|
||||
clearSelection();
|
||||
}, lifetime());
|
||||
|
||||
handleChatListEntryRefreshes();
|
||||
|
||||
refreshWithCollapsedRows(true);
|
||||
|
@ -428,6 +440,16 @@ int InnerWidget::dialogsOffset() const {
|
|||
- skipTopHeight();
|
||||
}
|
||||
|
||||
rpl::producer<> InnerWidget::scrollToVeryTopRequests() const {
|
||||
return _stories->expandRequests();
|
||||
}
|
||||
|
||||
int InnerWidget::defaultScrollTop() const {
|
||||
return storiesShown()
|
||||
? std::max(_stories->height() - st::dialogsStories.height, 0)
|
||||
: 0;
|
||||
}
|
||||
|
||||
int InnerWidget::fixedOnTopCount() const {
|
||||
auto result = 0;
|
||||
for (const auto &row : *_shownList) {
|
||||
|
@ -1699,6 +1721,15 @@ void InnerWidget::mousePressReleased(
|
|||
}
|
||||
}
|
||||
|
||||
void InnerWidget::setViewportHeight(int viewportHeight) {
|
||||
if (_viewportHeight != viewportHeight) {
|
||||
_viewportHeight = viewportHeight;
|
||||
if (height() < defaultScrollTop() + viewportHeight) {
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InnerWidget::setCollapsedPressed(int pressed) {
|
||||
if (_collapsedPressed != pressed) {
|
||||
if (_collapsedPressed >= 0) {
|
||||
|
@ -2745,10 +2776,13 @@ void InnerWidget::refresh(bool toTop) {
|
|||
h = searchedOffset() + (_searchResults.size() * _st->height);
|
||||
}
|
||||
}
|
||||
if (const auto storiesSkip = defaultScrollTop()) {
|
||||
accumulate_max(h, storiesSkip + _viewportHeight);
|
||||
}
|
||||
resize(width(), h);
|
||||
if (toTop) {
|
||||
stopReorderPinned();
|
||||
_mustScrollTo.fire({ 0, 0 });
|
||||
jumpToTop();
|
||||
preloadRowsData();
|
||||
}
|
||||
_controller->setDialogsListDisplayForced(
|
||||
|
@ -3226,7 +3260,7 @@ void InnerWidget::switchToFilter(FilterId filterId) {
|
|||
filterId = 0;
|
||||
}
|
||||
if (_filterId == filterId) {
|
||||
_mustScrollTo.fire({ 0, 0 });
|
||||
jumpToTop();
|
||||
return;
|
||||
}
|
||||
saveChatsFilterScrollState(_filterId);
|
||||
|
@ -3251,6 +3285,11 @@ void InnerWidget::switchToFilter(FilterId filterId) {
|
|||
}
|
||||
}
|
||||
|
||||
void InnerWidget::jumpToTop() {
|
||||
const auto to = defaultScrollTop();
|
||||
_mustScrollTo.fire({ to, -1 });
|
||||
}
|
||||
|
||||
void InnerWidget::saveChatsFilterScrollState(FilterId filterId) {
|
||||
_chatsFilterScrollStates[filterId] = -y();
|
||||
}
|
||||
|
|
|
@ -104,6 +104,10 @@ public:
|
|||
const QVector<MTPPeer> &my,
|
||||
const QVector<MTPPeer> &result);
|
||||
|
||||
[[nodiscard]] rpl::producer<> scrollToVeryTopRequests() const;
|
||||
[[nodiscard]] int defaultScrollTop() const;
|
||||
void setViewportHeight(int viewportHeight);
|
||||
|
||||
[[nodiscard]] FilterId filterId() const;
|
||||
|
||||
void clearSelection();
|
||||
|
@ -279,6 +283,7 @@ private:
|
|||
|
||||
int defaultRowTop(not_null<Row*> row) const;
|
||||
void setupOnlineStatusCheck();
|
||||
void jumpToTop();
|
||||
|
||||
void updateRowCornerStatusShown(not_null<History*> history);
|
||||
void repaintDialogRowCornerStatus(not_null<History*> history);
|
||||
|
@ -402,6 +407,7 @@ private:
|
|||
const not_null<Window::SessionController*> _controller;
|
||||
|
||||
const std::unique_ptr<Stories::List> _stories;
|
||||
int _viewportHeight = 0;
|
||||
|
||||
not_null<IndexedList*> _shownList;
|
||||
FilterId _filterId = 0;
|
||||
|
|
|
@ -260,6 +260,11 @@ Widget::Widget(
|
|||
}
|
||||
}, lifetime());
|
||||
|
||||
_inner->scrollToVeryTopRequests(
|
||||
) | rpl::start_with_next([=] {
|
||||
scrollToDefaultChecked(true);
|
||||
}, lifetime());
|
||||
|
||||
_inner->mustScrollTo(
|
||||
) | rpl::start_with_next([=](const Ui::ScrollToRequest &data) {
|
||||
if (_scroll) {
|
||||
|
@ -527,13 +532,15 @@ void Widget::setGeometryWithTopMoved(
|
|||
_topDelta = 0;
|
||||
}
|
||||
|
||||
void Widget::scrollToDefaultChecked(bool verytop) {
|
||||
if (_scrollToAnimation.animating()) {
|
||||
return;
|
||||
}
|
||||
scrollToDefault(verytop);
|
||||
}
|
||||
|
||||
void Widget::setupScrollUpButton() {
|
||||
_scrollToTop->setClickedCallback([=] {
|
||||
if (_scrollToAnimation.animating()) {
|
||||
return;
|
||||
}
|
||||
scrollToTop();
|
||||
});
|
||||
_scrollToTop->setClickedCallback([=] { scrollToDefaultChecked(); });
|
||||
base::install_event_filter(_scrollToTop, [=](not_null<QEvent*> event) {
|
||||
if (event->type() != QEvent::Wheel) {
|
||||
return base::EventFilterResult::Continue;
|
||||
|
@ -1111,10 +1118,13 @@ void Widget::jumpToTop(bool belowPinned) {
|
|||
}
|
||||
}
|
||||
|
||||
void Widget::scrollToTop() {
|
||||
void Widget::scrollToDefault(bool verytop) {
|
||||
_scrollToAnimation.stop();
|
||||
auto scrollTop = _scroll->scrollTop();
|
||||
const auto scrollTo = 0;
|
||||
const auto scrollTo = verytop ? 0 : _inner->defaultScrollTop();
|
||||
if (scrollTop <= scrollTo) {
|
||||
return;
|
||||
}
|
||||
const auto maxAnimatedDelta = _scroll->height();
|
||||
if (scrollTo + maxAnimatedDelta < scrollTop) {
|
||||
scrollTop = scrollTo + maxAnimatedDelta;
|
||||
|
@ -2494,7 +2504,11 @@ void Widget::updateControlsGeometry() {
|
|||
}
|
||||
auto scrollTop = forumReportTop
|
||||
+ (_forumReportBar ? _forumReportBar->bar().height() : 0);
|
||||
auto newScrollTop = _scroll->scrollTop() + _topDelta;
|
||||
const auto wasScrollTop = _scroll->scrollTop();
|
||||
const auto newScrollTop = (_topDelta < 0
|
||||
&& wasScrollTop <= _inner->defaultScrollTop())
|
||||
? wasScrollTop
|
||||
: (wasScrollTop + _topDelta);
|
||||
auto scrollHeight = height() - scrollTop;
|
||||
const auto putBottomButton = [&](auto &button) {
|
||||
if (button && !button->isHidden()) {
|
||||
|
@ -2518,13 +2532,22 @@ void Widget::updateControlsGeometry() {
|
|||
|
||||
const auto scrollw = _childList ? _narrowWidth : barw;
|
||||
const auto wasScrollHeight = _scroll->height();
|
||||
if (scrollHeight >= wasScrollHeight) {
|
||||
_inner->setViewportHeight(scrollHeight);
|
||||
}
|
||||
_scroll->setGeometry(0, scrollTop, scrollw, scrollHeight);
|
||||
if (scrollHeight < wasScrollHeight) {
|
||||
_inner->setViewportHeight(scrollHeight);
|
||||
}
|
||||
_inner->resize(scrollw, _inner->height());
|
||||
_inner->setNarrowRatio(narrowRatio);
|
||||
if (scrollHeight != wasScrollHeight) {
|
||||
controller()->floatPlayerAreaUpdated();
|
||||
}
|
||||
if (_topDelta) {
|
||||
const auto startWithTop = _inner->defaultScrollTop();
|
||||
if (wasScrollHeight < startWithTop && scrollHeight >= startWithTop) {
|
||||
_scroll->scrollToY(startWithTop);
|
||||
} else if (newScrollTop != wasScrollTop) {
|
||||
_scroll->scrollToY(newScrollTop);
|
||||
} else {
|
||||
listScrollUpdated();
|
||||
|
|
|
@ -209,7 +209,8 @@ private:
|
|||
mtpRequestId requestId);
|
||||
void peopleFailed(const MTP::Error &error, mtpRequestId requestId);
|
||||
|
||||
void scrollToTop();
|
||||
void scrollToDefault(bool verytop = false);
|
||||
void scrollToDefaultChecked(bool verytop = false);
|
||||
void setupScrollUpButton();
|
||||
void updateScrollUpVisibility();
|
||||
void startScrollUpButtonAnimation(bool shown);
|
||||
|
|
|
@ -173,10 +173,12 @@ rpl::producer<Content> ContentForSession(not_null<Main::Session*> session) {
|
|||
#if 0 // #TODO stories testing
|
||||
stories->allChanged()
|
||||
#endif
|
||||
session->data().chatsListChanges(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == nullptr
|
||||
) | rpl::to_empty
|
||||
rpl::merge(
|
||||
session->data().chatsListChanges(
|
||||
) | rpl::filter(
|
||||
rpl::mappers::_1 == nullptr
|
||||
) | rpl::to_empty,
|
||||
session->data().unreadBadgeChanges())
|
||||
) | rpl::start_with_next([=] {
|
||||
consumer.put_next(state->next());
|
||||
}, result);
|
||||
|
|
|
@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/painter.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
namespace Dialogs::Stories {
|
||||
namespace {
|
||||
|
||||
|
@ -24,19 +26,31 @@ List::List(
|
|||
Fn<int()> shownHeight)
|
||||
: RpWidget(parent)
|
||||
, _shownHeight(shownHeight) {
|
||||
resize(0, st::dialogsStoriesFull.height);
|
||||
setCursor(style::cur_default);
|
||||
|
||||
std::move(content) | rpl::start_with_next([=](Content &&content) {
|
||||
showContent(std::move(content));
|
||||
}, lifetime());
|
||||
|
||||
_shownAnimation.stop();
|
||||
resize(0, _items.empty() ? 0 : st::dialogsStoriesFull.height);
|
||||
}
|
||||
|
||||
void List::showContent(Content &&content) {
|
||||
if (_content == content) {
|
||||
return;
|
||||
}
|
||||
if (content.users.empty()) {
|
||||
_hidingItems = base::take(_items);
|
||||
if (!_hidingItems.empty()) {
|
||||
toggleAnimated(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto hidden = _content.users.empty();
|
||||
_content = std::move(content);
|
||||
auto items = base::take(_items);
|
||||
auto items = base::take(_items.empty() ? _hidingItems : _items);
|
||||
_hidingItems.clear();
|
||||
_items.reserve(_content.users.size());
|
||||
for (const auto &user : _content.users) {
|
||||
const auto i = ranges::find(items, user.id, [](const Item &item) {
|
||||
|
@ -53,10 +67,39 @@ void List::showContent(Content &&content) {
|
|||
item.user.name = user.name;
|
||||
item.nameCache = QImage();
|
||||
}
|
||||
item.user.unread = user.unread;
|
||||
} else {
|
||||
_items.emplace_back(Item{ .user = user });
|
||||
}
|
||||
}
|
||||
updateScrollMax();
|
||||
update();
|
||||
if (hidden) {
|
||||
toggleAnimated(true);
|
||||
}
|
||||
}
|
||||
|
||||
void List::toggleAnimated(bool shown) {
|
||||
_shownAnimation.start(
|
||||
[=] { updateHeight(); },
|
||||
shown ? 0. : 1.,
|
||||
shown ? 1. : 0.,
|
||||
st::slideWrapDuration);
|
||||
}
|
||||
|
||||
void List::updateHeight() {
|
||||
const auto shown = _shownAnimation.value(_items.empty() ? 0. : 1.);
|
||||
resize(
|
||||
width(),
|
||||
anim::interpolate(0, st::dialogsStoriesFull.height, shown));
|
||||
}
|
||||
|
||||
void List::updateScrollMax() {
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
const auto singleFull = full.photoLeft * 2 + full.photo;
|
||||
const auto widthFull = full.left + int(_items.size()) * singleFull;
|
||||
_scrollLeftMax = std::max(widthFull - width(), 0);
|
||||
_scrollLeft = std::clamp(_scrollLeft, 0, _scrollLeftMax);
|
||||
update();
|
||||
}
|
||||
|
||||
|
@ -68,6 +111,18 @@ rpl::producer<> List::expandRequests() const {
|
|||
return _expandRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<> List::entered() const {
|
||||
return _entered.events();
|
||||
}
|
||||
|
||||
void List::enterEventHook(QEnterEvent *e) {
|
||||
_entered.fire({});
|
||||
}
|
||||
|
||||
void List::resizeEvent(QResizeEvent *e) {
|
||||
updateScrollMax();
|
||||
}
|
||||
|
||||
void List::paintEvent(QPaintEvent *e) {
|
||||
const auto &st = st::dialogsStories;
|
||||
const auto &full = st::dialogsStoriesFull;
|
||||
|
@ -96,7 +151,7 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
const auto startIndexFull = std::max(-leftFull, 0) / singleFull;
|
||||
const auto cellLeftFull = leftFull + (startIndexFull * singleFull);
|
||||
const auto endIndexFull = std::min(
|
||||
(width() - cellLeftFull + singleFull - 1) / singleFull,
|
||||
(width() - leftFull + singleFull - 1) / singleFull,
|
||||
itemsCount);
|
||||
const auto startIndexSmall = 0;
|
||||
const auto endIndexSmall = std::min(kSmallUserpicsShown, itemsCount);
|
||||
|
@ -265,19 +320,92 @@ void List::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
|
||||
void List::wheelEvent(QWheelEvent *e) {
|
||||
const auto horizontal = (e->angleDelta().x() != 0);
|
||||
if (!horizontal) {
|
||||
e->ignore();
|
||||
return;
|
||||
}
|
||||
auto delta = horizontal
|
||||
? ((style::RightToLeft() ? -1 : 1) * (e->pixelDelta().x()
|
||||
? e->pixelDelta().x()
|
||||
: e->angleDelta().x()))
|
||||
: (e->pixelDelta().y()
|
||||
? e->pixelDelta().y()
|
||||
: e->angleDelta().y());
|
||||
|
||||
}
|
||||
|
||||
void List::mouseMoveEvent(QMouseEvent *e) {
|
||||
|
||||
const auto now = _scrollLeft;
|
||||
const auto used = now - delta;
|
||||
const auto next = std::clamp(used, 0, _scrollLeftMax);
|
||||
if (next != now) {
|
||||
_expandRequests.fire({});
|
||||
_scrollLeft = next;
|
||||
//updateSelected();
|
||||
update();
|
||||
}
|
||||
e->accept();
|
||||
}
|
||||
|
||||
void List::mousePressEvent(QMouseEvent *e) {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
_mouseDownPosition = _lastMousePosition = e->globalPos();
|
||||
//updateSelected();
|
||||
}
|
||||
|
||||
void List::mouseMoveEvent(QMouseEvent *e) {
|
||||
_lastMousePosition = e->globalPos();
|
||||
//updateSelected();
|
||||
|
||||
if (!_dragging && _mouseDownPosition) {
|
||||
if ((_lastMousePosition - *_mouseDownPosition).manhattanLength()
|
||||
>= QApplication::startDragDistance()) {
|
||||
if (_shownHeight() < st::dialogsStoriesFull.height) {
|
||||
_expandRequests.fire({});
|
||||
}
|
||||
_dragging = true;
|
||||
_startDraggingLeft = _scrollLeft;
|
||||
}
|
||||
}
|
||||
checkDragging();
|
||||
}
|
||||
|
||||
void List::checkDragging() {
|
||||
if (_dragging) {
|
||||
const auto sign = (style::RightToLeft() ? -1 : 1);
|
||||
const auto newLeft = std::clamp(
|
||||
(sign * (_mouseDownPosition->x() - _lastMousePosition.x())
|
||||
+ _startDraggingLeft),
|
||||
0,
|
||||
_scrollLeftMax);
|
||||
if (newLeft != _scrollLeft) {
|
||||
_scrollLeft = newLeft;
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void List::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_lastMousePosition = e->globalPos();
|
||||
const auto guard = gsl::finally([&] {
|
||||
_mouseDownPosition = std::nullopt;
|
||||
});
|
||||
|
||||
//const auto wasDown = std::exchange(_pressed, SpecialOver::None);
|
||||
if (finishDragging()) {
|
||||
return;
|
||||
}
|
||||
//updateSelected();
|
||||
}
|
||||
|
||||
bool List::finishDragging() {
|
||||
if (!_dragging) {
|
||||
return false;
|
||||
}
|
||||
checkDragging();
|
||||
_dragging = false;
|
||||
//updateSelected();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Dialogs::Stories
|
||||
|
|
|
@ -46,30 +46,48 @@ public:
|
|||
|
||||
[[nodiscard]] rpl::producer<uint64> clicks() const;
|
||||
[[nodiscard]] rpl::producer<> expandRequests() const;
|
||||
[[nodiscard]] rpl::producer<> entered() const;
|
||||
|
||||
private:
|
||||
struct Item {
|
||||
User user;
|
||||
QImage frameSmall;
|
||||
QImage frameFull;
|
||||
QImage nameCache;
|
||||
QColor nameCacheColor;
|
||||
bool subscribed = false;
|
||||
};
|
||||
|
||||
void showContent(Content &&content);
|
||||
void enterEventHook(QEnterEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void wheelEvent(QWheelEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
|
||||
void updateScrollMax();
|
||||
void checkDragging();
|
||||
bool finishDragging();
|
||||
|
||||
void updateHeight();
|
||||
void toggleAnimated(bool shown);
|
||||
|
||||
Content _content;
|
||||
std::vector<Item> _items;
|
||||
std::vector<Item> _hidingItems;
|
||||
Fn<int()> _shownHeight = 0;
|
||||
rpl::event_stream<uint64> _clicks;
|
||||
rpl::event_stream<> _expandRequests;
|
||||
rpl::event_stream<> _entered;
|
||||
|
||||
Ui::Animations::Simple _shownAnimation;
|
||||
|
||||
QPoint _lastMousePosition;
|
||||
std::optional<QPoint> _mouseDownPosition;
|
||||
int _startDraggingLeft = 0;
|
||||
int _scrollLeft = 0;
|
||||
int _scrollLeftMax = 0;
|
||||
bool _dragging = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -1141,7 +1141,9 @@ void SessionController::openFolder(not_null<Data::Folder*> folder) {
|
|||
if (_openedFolder.current() != folder) {
|
||||
resetFakeUnreadWhileOpened();
|
||||
}
|
||||
setActiveChatsFilter(0);
|
||||
if (activeChatsFilterCurrent() != 0) {
|
||||
setActiveChatsFilter(0);
|
||||
}
|
||||
closeForum();
|
||||
_openedFolder = folder.get();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue