diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index da6748f48c..387612e2e1 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -1176,7 +1176,8 @@ historyToDownBadgeSize: 22px; historyToDownShownAfter: 480px; historyToDownDuration: 150; -historyPullNextThreshold: 110px; +historyPullNextThreshold: 48px; +historyPullNextExpand: 110px; historyPullNextMaxHeight: 150px; historyPullNextAvatar: 56px; historyPullNextSkip: 8px; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 210c4538af..62add5fd03 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -4499,6 +4499,14 @@ void HistoryInner::changeItemsRevealHeight(int revealHeight) { updateSize(); } +void HistoryInner::setPullBottomInset(int inset) { + if (_pullBottomInset == inset) { + return; + } + _pullBottomInset = inset; + updateSize(); +} + void HistoryInner::updateSize() { const auto visibleHeight = _scroll->height(); auto collapseGapTotal = 0; @@ -4543,7 +4551,8 @@ void HistoryInner::updateSize() { const auto newHeight = _historyMarginTop + itemsHeight - + _historyMarginBottom; + + _historyMarginBottom + + _pullBottomInset; if (width() != _scroll->width() || height() != newHeight) { resize(_scroll->width(), newHeight); diff --git a/Telegram/SourceFiles/history/history_inner_widget.h b/Telegram/SourceFiles/history/history_inner_widget.h index eba8e4c72e..4911ac505d 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.h +++ b/Telegram/SourceFiles/history/history_inner_widget.h @@ -156,6 +156,10 @@ public: void setItemsRevealHeight(int revealHeight); void changeItemsRevealHeight(int revealHeight); + void setPullBottomInset(int inset); + [[nodiscard]] int pullBottomInset() const { + return _pullBottomInset; + } void checkActivation(); void recountHistoryGeometry(bool initial = false); void updateSize(); @@ -564,6 +568,7 @@ private: int _historyMarginTop = 0; int _historyMarginBottom = 0; int _revealHeight = 0; + int _pullBottomInset = 0; int _forumThreadBarWidth = 0; Ui::PeerUserpicView _forumThreadBarUserpicView; diff --git a/Telegram/SourceFiles/history/history_view_pull_to_next_channel.cpp b/Telegram/SourceFiles/history/history_view_pull_to_next_channel.cpp index bc0c64a57d..f9d330c623 100644 --- a/Telegram/SourceFiles/history/history_view_pull_to_next_channel.cpp +++ b/Telegram/SourceFiles/history/history_view_pull_to_next_channel.cpp @@ -20,30 +20,33 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/dialogs_main_list.h" #include "dialogs/dialogs_row.h" #include "history/history.h" +#include "history/history_inner_widget.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "storage/storage_shared_media.h" #include "support/support_preload.h" #include "ui/chat/chat_style.h" #include "ui/chat/continuous_scroll.h" +#include "ui/widgets/elastic_scroll.h" #include "ui/rect.h" #include "ui/rp_widget.h" #include "ui/ui_utility.h" #include "ui/userpic_view.h" #include "window/window_session_controller.h" +#include "styles/style_chat.h" #include "styles/style_chat_helpers.h" #include "styles/style_dialogs.h" namespace HistoryView { namespace { -constexpr auto kPullResistance = 0.9; constexpr auto kDirectionLock = 8.; constexpr auto kResetReachedOn = 0.95; constexpr auto kReleaseShowDuration = crl::time(250); constexpr auto kReleaseHideDuration = crl::time(220); constexpr auto kPanelDuration = crl::time(320); constexpr auto kRetractDuration = crl::time(250); +constexpr auto kExpandDuration = crl::time(250); constexpr auto kBounceDuration = crl::time(400); [[nodiscard]] History *FindNextUnreadChannel( @@ -263,9 +266,9 @@ void PullToNextChannel::Indicator::paintEvent(QPaintEvent *e) { auto p = QPainter(this); p.setRenderHint(QPainter::Antialiasing); - const auto threshold = float64(st::historyPullNextThreshold); - const auto progress = (threshold > 0.) - ? std::clamp(offset / threshold, 0., 1.) + const auto card = float64(st::historyPullNextExpand); + const auto progress = (card > 0.) + ? std::clamp(offset / card, 0., 1.) : 0.; const auto release = _releaseProgress.value(_ready ? 1. : 0.); const auto alpha = std::clamp(progress, 0., 1.); @@ -280,7 +283,7 @@ void PullToNextChannel::Indicator::paintEvent(QPaintEvent *e) { const auto widthRadius = std::clamp( circleRadius * (offset - st::historyPullNextSkip) - / (threshold - st::historyPullNextSkip), + / (card - st::historyPullNextSkip), 0., circleRadius); const auto widthRadius2 = std::max( @@ -339,7 +342,8 @@ void PullToNextChannel::Indicator::paintEvent(QPaintEvent *e) { : tr::lng_pull_no_unread_channels(tr::now); if (release > 0. && !name.isEmpty()) { const auto nameFont = st::historyPullNextNameFont; - const auto avail = width() - 4 * st::historyPullNextSkip; + const auto centerWidth = width() - st::historyScroll.width; + const auto avail = centerWidth - 4 * st::historyPullNextSkip; const auto elName = nameFont->elided(name, avail); const auto nameW = float64(nameFont->width(elName)); const auto y = h @@ -347,7 +351,7 @@ void PullToNextChannel::Indicator::paintEvent(QPaintEvent *e) { - st::historyPullNextRise * release) + bounceOffset; const auto pill = QRectF( - (width() - nameW) / 2. - st::historyPullNextSkip, + (centerWidth - nameW) / 2. - st::historyPullNextSkip, y - st::historyPullNextPadding, nameW + 2 * st::historyPullNextSkip, nameFont->height + st::historyPullNextSkip); @@ -361,7 +365,7 @@ void PullToNextChannel::Indicator::paintEvent(QPaintEvent *e) { p.setPen(fg); p.setFont(nameFont); p.drawText( - QRectF((width() - nameW) / 2., y, nameW, nameFont->height), + QRectF((centerWidth - nameW) / 2., y, nameW, nameFont->height), elName, QTextOption(Qt::AlignCenter)); } @@ -571,7 +575,7 @@ PullToNextChannel::PullToNextChannel( PullToNextChannel::~PullToNextChannel() = default; -void PullToNextChannel::attachToContent(not_null inner) { +void PullToNextChannel::attachToContent(not_null inner) { reset(); _inner = inner.get(); _filter = base::unique_qptr(base::install_event_filter( @@ -612,7 +616,7 @@ bool PullToNextChannel::processWheel(not_null e) { reset(); return false; } else if (phase == Qt::ScrollEnd || phase == Qt::ScrollMomentum) { - return release() || _retract.animating(); + return release() || _retract.animating() || _swallowMomentum; } else if (!_engaged && (_gaveUp || !_history @@ -633,8 +637,13 @@ bool PullToNextChannel::applyDelta(float64 deltaX, float64 deltaY) { if (sideways > kDirectionLock && sideways >= down) { _gaveUp = true; return false; - } else if (down <= kDirectionLock || down <= sideways || !active()) { + } else if (down < -kDirectionLock) { + _gaveUp = true; return false; + } else if (!active()) { + return false; + } else if (down <= kDirectionLock || down <= sideways) { + return true; } _engaged = true; _retract.stop(); @@ -652,19 +661,20 @@ bool PullToNextChannel::applyDelta(float64 deltaX, float64 deltaY) { _accumulated = std::max(0., _accumulated - deltaY); } const auto threshold = float64(st::historyPullNextThreshold); - _offset = std::min(threshold, _accumulated * kPullResistance); - if (_offset <= 0.) { - reset(); - return true; - } + _offset = std::max(0., std::min( + float64(st::historyPullNextMaxHeight), + float64(Ui::OverscrollFromAccumulated( + int(base::SafeRound(_accumulated)))))); const auto ratio = threshold ? (_offset / threshold) : 0.; if (_next && !_reached && ratio >= 1.) { _reached = true; base::Platform::Haptic(); + startExpand(true); } else if (_reached && ratio < kResetReachedOn) { _reached = false; + startExpand(false); } - push(_offset, _reached, true, _next); + push(_offset, _reached, _offset > 0., _next); return true; } @@ -673,15 +683,16 @@ bool PullToNextChannel::release() { return false; } const auto next = _next; - const auto from = _offset; - const auto ready = (from >= float64(st::historyPullNextThreshold)) + const auto fromAccumulated = _accumulated; + const auto ready = (_offset >= float64(st::historyPullNextThreshold)) && next && next->unreadCount() > 0; + _swallowMomentum = true; clearState(); if (ready) { crl::on_main(_parent.get(), [=] { jumpWhenReady(next, 0); }); } else { - startRetract(from, next); + startRetract(fromAccumulated, next); } return true; } @@ -691,33 +702,57 @@ void PullToNextChannel::push( bool ready, bool visible, History *next) { - applyShift(int(base::SafeRound(offset))); - _indicator->setData(offset, ready, next); - _hint->setData(visible, ready, next); + _pushOffset = offset; + _pushVisible = visible; + _pushNext = next; + render(ready); +} + +void PullToNextChannel::render(bool ready) { + const auto full = float64(st::historyPullNextExpand); + const auto expand = _expand.value(ready ? 1. : 0.); + const auto effective = _pushOffset + (full - _pushOffset) * expand; + applyShift(int(base::SafeRound(effective))); + _indicator->setData(effective, ready, _pushNext); + _hint->setData(_pushVisible, ready, _pushNext); +} + +void PullToNextChannel::startExpand(bool ready) { + _expand.start( + [=] { render(_reached); }, + ready ? 0. : 1., + ready ? 1. : 0., + kExpandDuration, + anim::easeOutQuint); } void PullToNextChannel::applyShift(int shift) { - if (_inner) { - _inner->move(_inner->x(), -_scroll->scrollTop() - shift); + if (_inner && _inner->pullBottomInset() != shift) { + _inner->setPullBottomInset(shift); + _scroll->scrollToY(_scroll->scrollTopMax()); + _inner->update(); } } -void PullToNextChannel::startRetract(float64 from, History *next) { - if (from <= 0.) { +void PullToNextChannel::startRetract(float64 fromAccumulated, History *next) { + if (fromAccumulated <= 0.) { push(0., false, false, nullptr); return; } _retract.start([=] { - const auto offset = _retract.value(0.); + const auto progress = _retract.value(0.); if (_retract.animating()) { + const auto offset = float64(Ui::OverscrollFromAccumulated( + int(base::SafeRound(fromAccumulated * progress)))); push(offset, false, true, next); } else { push(0., false, false, nullptr); } - }, from, 0., kRetractDuration, anim::easeOutCubic); + }, 1., 0., kRetractDuration, anim::sineInOut); } void PullToNextChannel::clearState() { + _expand.stop(); _accumulated = 0.; _offset = 0.; _swipeX = 0.; @@ -730,6 +765,7 @@ void PullToNextChannel::clearState() { void PullToNextChannel::reset() { _retract.stop(); + _swallowMomentum = false; clearState(); applyShift(0); _indicator->hideNow(); diff --git a/Telegram/SourceFiles/history/history_view_pull_to_next_channel.h b/Telegram/SourceFiles/history/history_view_pull_to_next_channel.h index bdbbab38fc..34dc2f01ad 100644 --- a/Telegram/SourceFiles/history/history_view_pull_to_next_channel.h +++ b/Telegram/SourceFiles/history/history_view_pull_to_next_channel.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" class History; +class HistoryInner; class QEvent; class QWheelEvent; @@ -33,7 +34,7 @@ public: not_null controller); ~PullToNextChannel(); - void attachToContent(not_null inner); + void attachToContent(not_null inner); void setHistory(History *history); @@ -49,8 +50,10 @@ private: [[nodiscard]] bool applyDelta(float64 deltaX, float64 deltaY); [[nodiscard]] bool release(); void push(float64 offset, bool ready, bool visible, History *next); + void render(bool ready); + void startExpand(bool ready); void applyShift(int shift); - void startRetract(float64 from, History *next); + void startRetract(float64 fromAccumulated, History *next); void clearState(); void reset(); void jumpWhenReady(not_null next, crl::time waited); @@ -62,12 +65,17 @@ private: const base::unique_qptr _indicator; const base::unique_qptr _hint; - QPointer _inner; + QPointer _inner; History *_history = nullptr; History *_next = nullptr; base::unique_qptr _filter; Ui::Animations::Simple _retract; + Ui::Animations::Simple _expand; + + float64 _pushOffset = 0.; + bool _pushVisible = false; + History *_pushNext = nullptr; float64 _accumulated = 0.; float64 _offset = 0.; @@ -76,6 +84,7 @@ private: bool _engaged = false; bool _reached = false; bool _gaveUp = false; + bool _swallowMomentum = false; };