From e6b0c61d7839280e70b98b593d4da46cb54c49e2 Mon Sep 17 00:00:00 2001 From: bleizix Date: Mon, 28 Jul 2025 17:45:28 +0500 Subject: [PATCH] feat: smooth scroll --- Telegram/Resources/langs/lang.strings | 1 + Telegram/SourceFiles/ayu/ayu_infra.cpp | 1 + Telegram/SourceFiles/ayu/ayu_settings.cpp | 6 +++ Telegram/SourceFiles/ayu/ayu_settings.h | 4 ++ .../ui/message_history/history_section.cpp | 5 +++ .../ayu/ui/settings/settings_ayu.cpp | 21 ++++++++++ .../chat_helpers/stickers_list_footer.cpp | 35 ++++++++++++++++ .../chat_helpers/stickers_list_footer.h | 5 +++ .../dialogs/ui/dialogs_suggestions.cpp | 30 ++++++++++++++ .../dialogs/ui/dialogs_suggestions.h | 4 ++ .../admin_log/history_admin_log_section.cpp | 5 +++ .../SourceFiles/history/history_widget.cpp | 12 ++++++ .../view/history_view_chat_preview.cpp | 1 + .../view/history_view_chat_section.cpp | 5 +++ .../history_view_reactions_button.cpp | 41 +++++++++++++++++++ .../reactions/history_view_reactions_button.h | 5 +++ .../SourceFiles/info/info_content_widget.cpp | 4 ++ .../SourceFiles/info/info_content_widget.h | 2 + .../info/media/info_media_widget.cpp | 4 ++ .../SourceFiles/ui/controls/tabbed_search.cpp | 40 ++++++++++++++++++ .../SourceFiles/ui/controls/tabbed_search.h | 4 ++ .../ui/widgets/chat_filters_tabs_strip.cpp | 27 ++++++++++++ 22 files changed, 262 insertions(+) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ac5ef1dec2..a45a9bb0b5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -6886,6 +6886,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "ayu_DisableAds" = "Disable Ads"; "ayu_DisableStories" = "Disable Stories"; "ayu_DisableCustomBackgrounds" = "Disable Custom Backgrounds"; +"ayu_SmoothScroll" = "Smooth Scroll"; "ayu_DisableNotificationsDelay" = "Disable Notify Delay"; "ayu_LocalPremium" = "Local Telegram Premium"; "ayu_DisplayGhostStatus" = "Display Ghost Mode Status"; diff --git a/Telegram/SourceFiles/ayu/ayu_infra.cpp b/Telegram/SourceFiles/ayu/ayu_infra.cpp index 71bb7804a4..4f59643226 100644 --- a/Telegram/SourceFiles/ayu/ayu_infra.cpp +++ b/Telegram/SourceFiles/ayu/ayu_infra.cpp @@ -32,6 +32,7 @@ void initUiSettings() { AyuUiSettings::setMonoFont(settings.monoFont); AyuUiSettings::setWideMultiplier(settings.wideMultiplier); + AyuUiSettings::setSmoothScroll(settings.smoothScroll); } void initDatabase() { diff --git a/Telegram/SourceFiles/ayu/ayu_settings.cpp b/Telegram/SourceFiles/ayu/ayu_settings.cpp index 3d55d637a8..470bc78ccb 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.cpp +++ b/Telegram/SourceFiles/ayu/ayu_settings.cpp @@ -18,6 +18,7 @@ #include #include "ayu_worker.h" +#include "ayu/ayu_ui_settings.h" #include "window/window_controller.h" using json = nlohmann::json; @@ -229,6 +230,7 @@ AyuGramSettings::AyuGramSettings() { increaseWebviewHeight = false; increaseWebviewWidth = false; + smoothScroll = true; disableNotificationsDelay = false; localPremium = false; showChannelReactions = true; @@ -410,6 +412,10 @@ void set_increaseWebviewHeight(bool val) { void set_increaseWebviewWidth(bool val) { settings->increaseWebviewWidth = val; } +void set_smoothScroll(bool val) { + settings->smoothScroll = val; + AyuUiSettings::setSmoothScroll(val); +} void set_disableNotificationsDelay(bool val) { settings->disableNotificationsDelay = val; diff --git a/Telegram/SourceFiles/ayu/ayu_settings.h b/Telegram/SourceFiles/ayu/ayu_settings.h index 86f2d8c205..ff91e54acd 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.h +++ b/Telegram/SourceFiles/ayu/ayu_settings.h @@ -74,6 +74,7 @@ public: bool increaseWebviewHeight; bool increaseWebviewWidth; + bool smoothScroll; bool disableNotificationsDelay; bool localPremium; bool showChannelReactions; @@ -158,6 +159,7 @@ void set_spoofWebviewAsAndroid(bool val); void set_increaseWebviewHeight(bool val); void set_increaseWebviewWidth(bool val); +void set_smoothScroll(bool val); void set_disableNotificationsDelay(bool val); void set_localPremium(bool val); void set_hideChannelReactions(bool val); @@ -232,6 +234,7 @@ inline void to_json(nlohmann::json &nlohmann_json_j, const AyuGramSettings &nloh NLOHMANN_JSON_TO(spoofWebviewAsAndroid) NLOHMANN_JSON_TO(increaseWebviewHeight) NLOHMANN_JSON_TO(increaseWebviewWidth) + NLOHMANN_JSON_TO(smoothScroll) NLOHMANN_JSON_TO(disableNotificationsDelay) NLOHMANN_JSON_TO(localPremium) NLOHMANN_JSON_TO(appIcon) @@ -297,6 +300,7 @@ inline void from_json(const nlohmann::json &nlohmann_json_j, AyuGramSettings &nl NLOHMANN_JSON_FROM_WITH_DEFAULT(spoofWebviewAsAndroid) NLOHMANN_JSON_FROM_WITH_DEFAULT(increaseWebviewHeight) NLOHMANN_JSON_FROM_WITH_DEFAULT(increaseWebviewWidth) + NLOHMANN_JSON_FROM_WITH_DEFAULT(smoothScroll) NLOHMANN_JSON_FROM_WITH_DEFAULT(disableNotificationsDelay) NLOHMANN_JSON_FROM_WITH_DEFAULT(localPremium) NLOHMANN_JSON_FROM_WITH_DEFAULT(appIcon) diff --git a/Telegram/SourceFiles/ayu/ui/message_history/history_section.cpp b/Telegram/SourceFiles/ayu/ui/message_history/history_section.cpp index 189f248779..51128cb370 100644 --- a/Telegram/SourceFiles/ayu/ui/message_history/history_section.cpp +++ b/Telegram/SourceFiles/ayu/ui/message_history/history_section.cpp @@ -167,6 +167,11 @@ Widget::Widget( _inner->scrollToSignal( ) | rpl::start_with_next([=](int top) { + + // AyuGram smooth scroll + _scroll->stopSmoothScroll(); + // AyuGram smooth scroll + _scroll->scrollToY(top); }, lifetime()); diff --git a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp index e6c05e1ffb..68e4692526 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/settings_ayu.cpp @@ -789,6 +789,27 @@ void SetupQoLToggles(not_null container) { AddDivider(container); AddSkip(container); + + AddButtonWithIcon( + container, + tr::ayu_SmoothScroll(), + st::settingsButtonNoIcon + )->toggleOn( + rpl::single(settings->smoothScroll) + )->toggledValue( + ) | rpl::filter( + [=](bool enabled) + { + return (enabled != settings->smoothScroll); + }) | start_with_next( + [=](bool enabled) + { + AyuSettings::set_smoothScroll(enabled); + AyuSettings::save(); + }, + container->lifetime()); + + AddButtonWithIcon( container, tr::ayu_DisableNotificationsDelay(), diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp index 6ea61e00f7..5bc31d7163 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.cpp @@ -33,6 +33,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +// AyuGram includes +#include "ayu/smooth_scroll/smooth_scroll.h" +#include "ui/ui_utility.h" + + namespace ChatHelpers { namespace { @@ -948,6 +953,36 @@ void StickersListFooter::scrollByWheelEvent( ? e->pixelDelta().y() : e->angleDelta().y()); const auto use = [&](ScrollState &state) { + + // AyuGram smooth scroll + if (state.dragging) { + SmoothScroll::Scroller::stop(state.scrollAnimation, state.scrollTo); + return; + } + const auto scrollDelta = Ui::ScrollDelta(e); + const auto scroll = (std::abs(scrollDelta.y()) >= std::abs(scrollDelta.x())) + ? scrollDelta.y() + : scrollDelta.x(); + + if (SmoothScroll::Scroller::handleScroll( + -scroll, + state.scrollAnimation, + state.scrollTo, + { + .getScroll = [&]{return qRound(state.x.current());}, + .setScroll = [&](int v){ + state.x = anim::value(v); + update(); + }, + .getNewTarget = [&](int t){return std::clamp(t, 0, state.max);} + }, + anim::easeOutCubic, + st::slideWrapDuration + )) { + return; + } + // AyuGram smooth scroll + const auto now = qRound(state.x.current()); const auto used = now - delta; const auto next = std::clamp(used, 0, state.max); diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h index a76b1ee21a..767ae011a0 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_footer.h @@ -199,6 +199,11 @@ private: anim::value selectionWidth; crl::time animationStart = 0; Ui::Animations::Basic animation; + + + // AyuGram smooth scroll + Ui::Animations::Simple scrollAnimation; + float64 scrollTo = -1.; }; struct ExpandingContext { QRect clip; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp index ae4a4768cd..0fae347831 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.cpp @@ -63,6 +63,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_menu_icons.h" #include "styles/style_window.h" + +// AyuGram includes +#include + + namespace Dialogs { namespace { @@ -1349,6 +1354,31 @@ void Suggestions::setupTabs() { if (std::abs(pixelDelta.x()) + std::abs(angleDelta.x())) { return false; } + // AyuGram smooth scroll + const auto delta = Ui::ScrollDelta(e); + const auto bar = _tabsScroll->horizontalScrollBar(); + if (SmoothScroll::Scroller::handleScroll( + -delta.y(), + _tabsWheelAnimation, + _tabsWheelScrollTo, + { + .getScroll = [bar] + { + return bar->value(); + }, + .setScroll = [bar](int v) + { + bar->setValue(v); + }, + .getNewTarget = [bar](int t) + { + return std::clamp(t, bar->minimum(), bar->maximum()); + } + })) { + return true; + } + // AyuGram smooth scroll + const auto y = pixelDelta.y() ? pixelDelta.y() : angleDelta.y(); _tabsScroll->scrollToX(_tabsScroll->scrollLeft() - y); return true; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h index f269085f13..2db142f0a1 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_suggestions.h @@ -112,6 +112,10 @@ public: class ObjectListController; + + // AyuGram smooth scroll + Ui::Animations::Simple _tabsWheelAnimation; + float64 _tabsWheelScrollTo = -1.; private: using MediaType = Storage::SharedMediaType; enum class Tab : uchar { diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp index 56fce9db19..6517efda5b 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_section.cpp @@ -326,6 +326,11 @@ Widget::Widget( }, lifetime()); _inner->scrollToSignal( ) | rpl::start_with_next([=](int top) { + + // AyuGram smooth scroll + _scroll->stopSmoothScroll(); + // AyuGram smooth scroll + _scroll->scrollToY(top); }, lifetime()); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 9d82d767d0..adf2ea487b 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -7142,6 +7142,18 @@ void HistoryWidget::updateHistoryGeometry( updateListSize(); _updateHistoryGeometryRequired = false; + // AyuGram smooth scroll + + // workaround: + // stop smooth scroll to prevent the view from + // jumping to the top when older messages are loaded + + // scroll position isn't correctly calculated on resize + _scroll->stopSmoothScroll(); + + // AyuGram smooth scroll + + auto newScrollTop = 0; if (initial) { newScrollTop = countInitialScrollTop(); diff --git a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp index 5e9d3eecdd..744a380efe 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_preview.cpp @@ -571,6 +571,7 @@ bool Item::listScrollTo(int top, bool syntetic) { updateInnerVisibleArea(); return false; } + _scroll->stopSmoothScroll(); _scroll->scrollToY(top); return true; } diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index 72c2c2e58c..5b4103df86 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -2912,6 +2912,11 @@ bool ChatWidget::listScrollTo(int top, bool syntetic) { const auto scrolled = (_scroll->scrollTop() != top); _synteticScrollEvent = syntetic; if (scrolled) { + + // AyuGram smooth scroll + _scroll->stopSmoothScroll(); + // AyuGram smooth scroll + _scroll->scrollToY(top); } else if (syntetic) { updateInnerVisibleArea(); diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp index 9f2759f36b..18d75047c3 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.cpp @@ -26,6 +26,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat_helpers.h" #include "styles/style_menu_icons.h" + +// AyuGram includes +#include "ayu/smooth_scroll/smooth_scroll.h" + namespace HistoryView::Reactions { namespace { @@ -124,6 +128,35 @@ bool Button::consumeWheelEvent(not_null e) { if (horizontal) { return false; } + + // AyuGram smooth scroll + const auto scrollDelta = Ui::ScrollDelta(e) * (expandUp() ? -1. : 1.); + if (SmoothScroll::Scroller::handleScroll( + -scrollDelta.y(), + _scrollAnimation, + _scrollTarget, + { + .getScroll = [this] + { + return int(base::SafeRound(_scroll)); + }, + .setScroll = [this](int value) + { + _scroll = value; + _update(_geometry); + }, + .getNewTarget = [=](int target) + { + return std::clamp(target, 0, scrollMax); + } + }, + anim::easeOutCubic, + kCollapseDuration)) { + e->accept(); + return true; + } + // AyuGram smooth scroll + const auto between = st::reactionCornerSkip; const auto oneHeight = (st::reactionCornerSize.height() + between); const auto max = oneHeight * kMaxReactionsScrollAtOnce; @@ -209,6 +242,14 @@ void Button::updateGeometry(Fn update) { )) - _collapsed.height(); if (!added && _state != State::Inside) { _scroll = 0; + + // AyuGram smooth scroll + if (_scrollAnimation.animating()) { + _scrollAnimation.stop(); + } + _scrollTarget = -1.; + // AyuGram smooth scroll + } const auto geometry = _collapsed.marginsAdded({ 0, diff --git a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h index 4a1842d995..c72629eca5 100644 --- a/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h +++ b/Telegram/SourceFiles/history/view/reactions/history_view_reactions_button.h @@ -131,6 +131,11 @@ private: base::Timer _hideTimer; std::optional _lastGlobalPosition; + + // AyuGram smooth scroll + + Ui::Animations::Simple _scrollAnimation; + float64 _scrollTarget = -1.; }; class Manager final : public base::has_weak_ptr { diff --git a/Telegram/SourceFiles/info/info_content_widget.cpp b/Telegram/SourceFiles/info/info_content_widget.cpp index efbfbdc14a..7e6157be16 100644 --- a/Telegram/SourceFiles/info/info_content_widget.cpp +++ b/Telegram/SourceFiles/info/info_content_widget.cpp @@ -295,6 +295,10 @@ void ContentWidget::scrollTopRestore(int scrollTop) { void ContentWidget::scrollTo(const Ui::ScrollToRequest &request) { _scroll->scrollTo(request); } +// AyuGram smooth scroll +void ContentWidget::stopSmoothScroll() { + _scroll->stopSmoothScroll(); +} bool ContentWidget::floatPlayerHandleWheelEvent(QEvent *e) { return _scroll->viewportEvent(e); diff --git a/Telegram/SourceFiles/info/info_content_widget.h b/Telegram/SourceFiles/info/info_content_widget.h index 29c376dfa4..ac39231c3b 100644 --- a/Telegram/SourceFiles/info/info_content_widget.h +++ b/Telegram/SourceFiles/info/info_content_widget.h @@ -169,6 +169,8 @@ protected: void setViewport(rpl::producer> &&events) const; + // AyuGram smooth scroll + void stopSmoothScroll(); private: RpWidget *doSetInnerWidget(object_ptr inner); void updateControlsGeometry(); diff --git a/Telegram/SourceFiles/info/media/info_media_widget.cpp b/Telegram/SourceFiles/info/media/info_media_widget.cpp index 58d9e8e95e..d61f1d27ab 100644 --- a/Telegram/SourceFiles/info/media/info_media_widget.cpp +++ b/Telegram/SourceFiles/info/media/info_media_widget.cpp @@ -136,6 +136,10 @@ Widget::Widget(QWidget *parent, not_null controller) _inner->setScrollHeightValue(scrollHeightValue()); _inner->scrollToRequests( ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { + // AyuGram smooth scroll + stopSmoothScroll(); + // AyuGram smooth scroll + scrollTo(request); }, _inner->lifetime()); } diff --git a/Telegram/SourceFiles/ui/controls/tabbed_search.cpp b/Telegram/SourceFiles/ui/controls/tabbed_search.cpp index ea9f1e70f1..2887b07997 100644 --- a/Telegram/SourceFiles/ui/controls/tabbed_search.cpp +++ b/Telegram/SourceFiles/ui/controls/tabbed_search.cpp @@ -19,6 +19,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include +// AyuGram includes +#include "ayu/smooth_scroll/smooth_scroll.h" + namespace Ui { namespace { @@ -45,6 +48,11 @@ public: [[nodiscard]] rpl::producer moveRequests() const; + + // AyuGram smooth scroll + bool isDragging() const { + return _dragging; + } private: struct Button { EmojiGroup group; @@ -375,6 +383,38 @@ void SearchWithGroups::initGroups() { widget->moveRequests( ) | rpl::start_with_next([=](int delta) { + + // AyuGram smooth scroll + + if (widget->isDragging()) { + SmoothScroll::Scroller::stop( + _groupsScrollAnimation, + _groupsScrollTo); + moveGroupsBy(width(), delta); + return; + } + if (SmoothScroll::Scroller::handleScroll( + delta, + _groupsScrollAnimation, + _groupsScrollTo, + { + .getScroll = [=] { + return _groups->x(); + }, + .setScroll = [=](int value) { + moveGroupsTo(width(), value); + }, + .getNewTarget = [=](int target) { + return clampGroupsLeft(width(), target); + } + }, + anim::easeOutCubic, + st::slideWrapDuration + )) { + return; + } + // AyuGram smooth scroll + moveGroupsBy(width(), delta); }, lifetime()); diff --git a/Telegram/SourceFiles/ui/controls/tabbed_search.h b/Telegram/SourceFiles/ui/controls/tabbed_search.h index 2a5d67e40e..1c37dc3243 100644 --- a/Telegram/SourceFiles/ui/controls/tabbed_search.h +++ b/Telegram/SourceFiles/ui/controls/tabbed_search.h @@ -114,6 +114,10 @@ private: base::Timer _debounceTimer; bool _inited = false; + + // AyuGram smooth scroll + Animations::Simple _groupsScrollAnimation; + float64 _groupsScrollTo = -1.; }; class TabbedSearch final { diff --git a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp index 176b5644c4..6592bf5b52 100644 --- a/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp +++ b/Telegram/SourceFiles/ui/widgets/chat_filters_tabs_strip.cpp @@ -53,6 +53,11 @@ struct State final { std::unique_ptr reorder; bool ignoreRefresh = false; + + // AyuGram smooth scroll + Animations::Simple scrollAnimation; + float64 scrollTo = -1.; + }; void ShowMenu( @@ -303,6 +308,28 @@ not_null AddChatFiltersTabsStrip( if (std::abs(pixelDelta.x()) + std::abs(angleDelta.x())) { return false; } + // AyuGram smooth scroll + const auto delta = ScrollDelta(e); + + if (SmoothScroll::Scroller::handleScroll( + -delta.y(), + state->scrollAnimation, + state->scrollTo, + { + .getScroll = [=] { return scroll->horizontalScrollBar()->value(); }, + .setScroll = [=](int v) { scroll->horizontalScrollBar()->setValue(v); }, + .getNewTarget = [bar = scroll->horizontalScrollBar()](int t) + { + return std::clamp(t, bar->minimum(), bar->maximum()); + } + }) + ) { + return true; + } + // AyuGram smooth scroll + + + const auto bar = scroll->horizontalScrollBar(); const auto y = pixelDelta.y() ? pixelDelta.y() : angleDelta.y(); bar->setValue(bar->value() - y);