diff --git a/Telegram/SourceFiles/core/event_filter.cpp b/Telegram/SourceFiles/core/event_filter.cpp new file mode 100644 index 000000000..eb5d6890c --- /dev/null +++ b/Telegram/SourceFiles/core/event_filter.cpp @@ -0,0 +1,30 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "core/event_filter.h" + +namespace Core { + +EventFilter::EventFilter( + not_null parent, + base::lambda)> filter) +: QObject(parent) +, _filter(std::move(filter)) { + parent->installEventFilter(this); +} + +bool EventFilter::eventFilter(QObject *watched, QEvent *event) { + return _filter(event); +} + +not_null InstallEventFilter( + not_null object, + base::lambda)> filter) { + return new EventFilter(object, std::move(filter)); +} + +} // namespace Core diff --git a/Telegram/SourceFiles/core/event_filter.h b/Telegram/SourceFiles/core/event_filter.h new file mode 100644 index 000000000..3fbf71252 --- /dev/null +++ b/Telegram/SourceFiles/core/event_filter.h @@ -0,0 +1,30 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Core { + +class EventFilter : public QObject { +public: + EventFilter( + not_null parent, + base::lambda)> filter); + +protected: + bool eventFilter(QObject *watched, QEvent *event); + +private: + base::lambda)> _filter; + +}; + +not_null InstallEventFilter( + not_null object, + base::lambda)> filter); + +} // namespace Core diff --git a/Telegram/SourceFiles/data/data_feed.cpp b/Telegram/SourceFiles/data/data_feed.cpp index 73617b37c..65372c23e 100644 --- a/Telegram/SourceFiles/data/data_feed.cpp +++ b/Telegram/SourceFiles/data/data_feed.cpp @@ -279,6 +279,12 @@ int Feed::unreadCount() const { return _unreadCount ? *_unreadCount : 0; } +rpl::producer Feed::unreadCountValue() const { + return rpl::single( + unreadCount() + ) | rpl::then(_unreadCountChanges.events()); +} + bool Feed::unreadCountKnown() const { return !!_unreadCount; } @@ -321,6 +327,8 @@ void Feed::setUnreadCounts(int unreadNonMutedCount, int unreadMutedCount) { } _unreadCount = unreadNonMutedCount + unreadMutedCount; _unreadMutedCount = unreadMutedCount; + + _unreadCountChanges.fire(unreadCount()); updateChatListEntry(); } @@ -333,7 +341,7 @@ void Feed::setUnreadPosition(const MessagePosition &position) { void Feed::unreadCountChanged( base::optional unreadCountDelta, int mutedCountDelta) { - if (!unreadCountKnown()) { + if (!unreadCountKnown() || (unreadCountDelta && !*unreadCountDelta)) { return; } if (unreadCountDelta) { @@ -342,6 +350,8 @@ void Feed::unreadCountChanged( _unreadMutedCount + mutedCountDelta, 0, *_unreadCount); + + _unreadCountChanges.fire(unreadCount()); updateChatListEntry(); } else { // _parent->session().api().requestFeedDialogsEntries(this); diff --git a/Telegram/SourceFiles/data/data_feed.h b/Telegram/SourceFiles/data/data_feed.h index 74217d2e9..13e3c8b16 100644 --- a/Telegram/SourceFiles/data/data_feed.h +++ b/Telegram/SourceFiles/data/data_feed.h @@ -50,6 +50,7 @@ public: void unreadCountChanged( base::optional unreadCountDelta, int mutedCountDelta); + rpl::producer unreadCountValue() const; MessagePosition unreadPosition() const; rpl::producer unreadPositionChanges() const; @@ -98,6 +99,7 @@ private: rpl::variable _unreadPosition; base::optional _unreadCount; + rpl::event_stream _unreadCountChanges; int _unreadMutedCount = 0; }; diff --git a/Telegram/SourceFiles/data/data_messages.h b/Telegram/SourceFiles/data/data_messages.h index 2f1647d7d..7f79f619d 100644 --- a/Telegram/SourceFiles/data/data_messages.h +++ b/Telegram/SourceFiles/data/data_messages.h @@ -77,12 +77,18 @@ struct MessagesRange { }; constexpr auto MaxDate = std::numeric_limits::max(); -constexpr auto MinMessagePosition = MessagePosition(TimeId(0), FullMsgId()); -constexpr auto MaxMessagePosition = MessagePosition(MaxDate, FullMsgId()); +constexpr auto MinMessagePosition = MessagePosition( + TimeId(0), + FullMsgId(0, 1)); +constexpr auto MaxMessagePosition = MessagePosition( + MaxDate, + FullMsgId(0, ServerMaxMsgId - 1)); constexpr auto FullMessagesRange = MessagesRange( MinMessagePosition, MaxMessagePosition); -constexpr auto UnreadMessagePosition = MinMessagePosition; +constexpr auto UnreadMessagePosition = MessagePosition( + TimeId(0), + FullMsgId(0, 0));; struct MessagesSlice { std::vector ids; diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp index 16a6fbd49..3d330b923 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp @@ -1833,15 +1833,23 @@ void DialogsInner::peerSearchReceived( return; } + const auto alreadyAdded = [&](not_null peer) { + for (const auto &row : _filterResults) { + if (const auto history = row->history()) { + if (history->peer == peer) { + return true; + } + } + } + return false; + }; _peerSearchQuery = query.toLower().trimmed(); _peerSearchResults.clear(); _peerSearchResults.reserve(result.size()); for (const auto &mtpPeer : my) { if (const auto peer = App::peerLoaded(peerFromMTP(mtpPeer))) { - if (const auto history = App::historyLoaded(peer)) { - if (history->inChatList(Dialogs::Mode::All)) { - continue; // skip existing chats - } + if (alreadyAdded(peer)) { + continue; } const auto prev = nullptr, next = nullptr; const auto position = 0; diff --git a/Telegram/SourceFiles/history/feed/history_feed_section.cpp b/Telegram/SourceFiles/history/feed/history_feed_section.cpp index ff586ff79..d8bf5b075 100644 --- a/Telegram/SourceFiles/history/feed/history_feed_section.cpp +++ b/Telegram/SourceFiles/history/feed/history_feed_section.cpp @@ -13,11 +13,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_message.h" #include "history/view/history_view_service_message.h" #include "history/history_item.h" +#include "core/event_filter.h" #include "lang/lang_keys.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/widgets/scroll_area.h" #include "ui/widgets/popup_menu.h" +#include "ui/special_buttons.h" #include "boxes/confirm_box.h" #include "window/window_controller.h" #include "window/window_peer_menu.h" @@ -66,9 +68,10 @@ Widget::Widget( , _topBar(this, controller) , _topBarShadow(this) , _showNext( - this, - lang(lng_feed_show_next).toUpper(), - st::historyComposeButton) { + this, + lang(lng_feed_show_next).toUpper(), + st::historyComposeButton) +, _scrollDown(_scroll, st::historyToDown) { _topBar->setActiveChat(_feed); _topBar->move(0, 0); @@ -125,6 +128,107 @@ Widget::Widget( ) | rpl::start_with_next([=] { crl::on_main(this, [=] { checkForSingleChannelFeed(); }); }, lifetime()); + + setupScrollDownButton(); +} + +void Widget::setupScrollDownButton() { + _scrollDown->setClickedCallback([=] { + scrollDownClicked(); + }); + Core::InstallEventFilter(_scrollDown, [=](not_null event) { + if (event->type() == QEvent::Wheel) { + return _scroll->viewportEvent(event); + } + return false; + }); + updateScrollDownVisibility(); + _feed->unreadCountValue( + ) | rpl::start_with_next([=](int count) { + _scrollDown->setUnreadCount(count); + }, _scrollDown->lifetime()); +} + +void Widget::scrollDownClicked() { + showAtPosition(Data::MaxMessagePosition); +} + +void Widget::showAtPosition(Data::MessagePosition position) { + if (!showAtPositionNow(position)) { + _nextAnimatedScrollPosition = position; + _nextAnimatedScrollDelta = _inner->isBelowPosition(position) + ? -_scroll->height() + : _inner->isAbovePosition(position) + ? _scroll->height() + : 0; + auto memento = HistoryView::ListMemento(position); + _inner->restoreState(&memento); + } +} + +bool Widget::showAtPositionNow(Data::MessagePosition position) { + if (const auto scrollTop = _inner->scrollTopForPosition(position)) { + const auto currentScrollTop = _scroll->scrollTop(); + const auto wanted = snap(*scrollTop, 0, _scroll->scrollTopMax()); + const auto fullDelta = (wanted - currentScrollTop); + const auto limit = _scroll->height(); + const auto scrollDelta = snap(fullDelta, -limit, limit); + _inner->animatedScrollTo( + wanted, + position, + scrollDelta, + (std::abs(fullDelta) > limit + ? HistoryView::ListWidget::AnimatedScroll::Part + : HistoryView::ListWidget::AnimatedScroll::Full)); + return true; + } + return false; +} + +void Widget::updateScrollDownVisibility() { + if (animating() || !_inner->loadedAtBottomKnown()) { + return; + } + + const auto scrollDownIsVisible = [&] { + if (!_inner->loadedAtBottom()) { + return true; + } + const auto top = _scroll->scrollTop() + st::historyToDownShownAfter; + if (top < _scroll->scrollTopMax()) { + return true; + } + return false; + }; + auto scrollDownIsShown = scrollDownIsVisible(); + if (_scrollDownIsShown != scrollDownIsShown) { + _scrollDownIsShown = scrollDownIsShown; + _scrollDownShown.start( + [=] { updateScrollDownPosition(); }, + _scrollDownIsShown ? 0. : 1., + _scrollDownIsShown ? 1. : 0., + st::historyToDownDuration); + } +} + +void Widget::updateScrollDownPosition() { + // _scrollDown is a child widget of _scroll, not me. + auto top = anim::interpolate( + 0, + _scrollDown->height() + st::historyToDownPosition.y(), + _scrollDownShown.current(_scrollDownIsShown ? 1. : 0.)); + _scrollDown->moveToRight( + st::historyToDownPosition.x(), + _scroll->height() - top); + auto shouldBeHidden = !_scrollDownIsShown && !_scrollDownShown.animating(); + if (shouldBeHidden != _scrollDown->isHidden()) { + _scrollDown->setVisible(!shouldBeHidden); + } +} + +void Widget::scrollDownAnimationFinish() { + _scrollDownShown.finish(); + updateScrollDownPosition(); } void Widget::checkForSingleChannelFeed() { @@ -255,7 +359,7 @@ void Widget::listVisibleItemsChanged(HistoryItemsList &&items) { base::optional Widget::listUnreadBarView( const std::vector> &elements) { const auto position = _feed->unreadPosition(); - if (!position || elements.empty()) { + if (!position || elements.empty() || !_feed->unreadCount()) { return base::none; } const auto minimal = ranges::upper_bound( @@ -276,6 +380,21 @@ base::optional Widget::listUnreadBarView( return base::make_optional(int(minimal - begin(elements))); } +void Widget::listContentRefreshed() { + if (!_nextAnimatedScrollPosition) { + return; + } + const auto position = *base::take(_nextAnimatedScrollPosition); + if (const auto scrollTop = _inner->scrollTopForPosition(position)) { + const auto wanted = snap(*scrollTop, 0, _scroll->scrollTopMax()); + _inner->animatedScrollTo( + wanted, + position, + _nextAnimatedScrollDelta, + HistoryView::ListWidget::AnimatedScroll::Part); + } +} + std::unique_ptr Widget::createMemento() { auto result = std::make_unique(_feed); saveState(result.get()); @@ -330,6 +449,8 @@ void Widget::updateControlsGeometry() { } updateInnerVisibleArea(); } + + updateScrollDownPosition(); const auto fullWidthButtonRect = myrtlrect( 0, bottom - _showNext->height(), @@ -350,8 +471,8 @@ void Widget::paintEvent(QPaintEvent *e) { // updateListSize(); //} - //auto ms = getms(); - //_historyDownShown.step(ms); + const auto ms = getms(); + _scrollDownShown.step(ms); SectionWidget::PaintBackground(this, e); } @@ -366,7 +487,7 @@ void Widget::onScroll() { void Widget::updateInnerVisibleArea() { const auto scrollTop = _scroll->scrollTop(); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); - + updateScrollDownVisibility(); } void Widget::showAnimatedHook( diff --git a/Telegram/SourceFiles/history/feed/history_feed_section.h b/Telegram/SourceFiles/history/feed/history_feed_section.h index 184a8e71e..08ebe6849 100644 --- a/Telegram/SourceFiles/history/feed/history_feed_section.h +++ b/Telegram/SourceFiles/history/feed/history_feed_section.h @@ -16,6 +16,7 @@ namespace Ui { class ScrollArea; class PlainShadow; class FlatButton; +class HistoryDownButton; } // namespace Ui namespace HistoryView { @@ -81,6 +82,7 @@ public: void listVisibleItemsChanged(HistoryItemsList &&items) override; base::optional listUnreadBarView( const std::vector> &elements) override; + void listContentRefreshed() override; protected: void resizeEvent(QResizeEvent *e) override; @@ -99,6 +101,14 @@ private: void updateAdaptiveLayout(); void saveState(not_null memento); void restoreState(not_null memento); + void showAtPosition(Data::MessagePosition position); + bool showAtPositionNow(Data::MessagePosition position); + + void setupScrollDownButton(); + void scrollDownClicked(); + void scrollDownAnimationFinish(); + void updateScrollDownVisibility(); + void updateScrollDownPosition(); void forwardSelected(); void confirmDeleteSelected(); @@ -113,6 +123,14 @@ private: bool _skipScrollEvent = false; bool _undefinedAroundPosition = false; + base::optional _nextAnimatedScrollPosition; + int _nextAnimatedScrollDelta = 0; + + Animation _scrollDownShown; + bool _scrollDownIsShown = false; + object_ptr _scrollDown; + + }; class Memento : public Window::SectionMemento { diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 26d0b68be..3effa75fe 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -324,6 +324,75 @@ void ListWidget::refreshRows() { checkUnreadBarCreation(); restoreScrollState(); mouseActionUpdate(QCursor::pos()); + _delegate->listContentRefreshed(); +} + +base::optional ListWidget::scrollTopForPosition( + Data::MessagePosition position) const { + if (position == Data::MaxMessagePosition) { + if (loadedAtBottom()) { + return height(); + } + return base::none; + } + // #TODO showAtPosition + return base::none; +} + +void ListWidget::animatedScrollTo( + int scrollTop, + Data::MessagePosition attachPosition, + int delta, + AnimatedScroll type) { + _scrollToAnimation.finish(); + if (!delta || _items.empty()) { + _delegate->listScrollTo(scrollTop); + return; + } + const auto index = findNearestItem(attachPosition); + Assert(index >= 0 && index < int(_items.size())); + const auto attachTo = _items[index]; + const auto attachToId = attachTo->data()->fullId(); + const auto transition = (type == AnimatedScroll::Full) + ? anim::sineInOut + : anim::easeOutCubic; + const auto initial = scrollTop - delta; + _delegate->listScrollTo(initial); + + const auto attachToTop = itemTop(attachTo); + const auto relativeStart = initial - attachToTop; + const auto relativeFinish = scrollTop - attachToTop; + _scrollToAnimation.start( + [=] { scrollToAnimationCallback(attachToId); }, + relativeStart, + relativeFinish, + st::slideDuration, + transition); +} + +void ListWidget::scrollToAnimationCallback(FullMsgId attachToId) { + const auto attachTo = App::histItemById(attachToId); + const auto attachToView = viewForItem(attachTo); + if (!attachToView) { + _scrollToAnimation.finish(); + } else { + const auto current = int(std::round(_scrollToAnimation.current())); + _delegate->listScrollTo(itemTop(attachToView) + current); + } +} + +bool ListWidget::isAbovePosition(Data::MessagePosition position) const { + if (_items.empty()) { + return false; + } + return _items.back()->data()->position() < position; +} + +bool ListWidget::isBelowPosition(Data::MessagePosition position) const { + if (_items.empty()) { + return false; + } + return _items.front()->data()->position() > position; } void ListWidget::checkUnreadBarCreation() { @@ -838,6 +907,22 @@ void ListWidget::setTextSelection( } } +bool ListWidget::loadedAtTopKnown() const { + return !!_slice.skippedBefore; +} + +bool ListWidget::loadedAtTop() const { + return _slice.skippedBefore && (*_slice.skippedBefore == 0); +} + +bool ListWidget::loadedAtBottomKnown() const { + return !!_slice.skippedAfter; +} + +bool ListWidget::loadedAtBottom() const { + return _slice.skippedAfter && (*_slice.skippedAfter == 0); +} + int ListWidget::itemMinimalHeight() const { return st::msgMarginTopAttached + st::msgPhotoSize diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.h b/Telegram/SourceFiles/history/view/history_view_list_widget.h index 47e8fe29d..36682c249 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.h +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.h @@ -64,6 +64,7 @@ public: virtual void listVisibleItemsChanged(HistoryItemsList &&items) = 0; virtual base::optional listUnreadBarView( const std::vector> &elements) = 0; + virtual void listContentRefreshed() = 0; }; @@ -134,6 +135,19 @@ public: void saveState(not_null memento); void restoreState(not_null memento); + base::optional scrollTopForPosition( + Data::MessagePosition position) const; + enum class AnimatedScroll { + Full, + Part, + }; + void animatedScrollTo( + int scrollTop, + Data::MessagePosition attachPosition, + int delta, + AnimatedScroll type); + bool isAbovePosition(Data::MessagePosition position) const; + bool isBelowPosition(Data::MessagePosition position) const; TextWithEntities getSelectedText() const; MessageIdsList getSelectedItems() const; @@ -141,6 +155,11 @@ public: void selectItem(not_null item); void selectItemAsGroup(not_null item); + bool loadedAtTopKnown() const; + bool loadedAtTop() const; + bool loadedAtBottomKnown() const; + bool loadedAtBottom() const; + // AbstractTooltipShower interface QString tooltipText() const override; QPoint tooltipPos() const override; @@ -355,6 +374,7 @@ private: not_null view) const; void checkUnreadBarCreation(); void applyUpdatedScrollState(); + void scrollToAnimationCallback(FullMsgId attachToId); // This function finds all history items that are displayed and calls template method // for each found message (in given direction) in the passed history with passed top offset. @@ -385,6 +405,7 @@ private: not_null _delegate; not_null _controller; Data::MessagePosition _aroundPosition; + Data::MessagePosition _shownAtPosition; Context _context; int _aroundIndex = -1; int _idsLimit = kMinimalIdsLimit; @@ -405,6 +426,7 @@ private: Element *_visibleTopItem = nullptr; int _visibleTopFromItem = 0; ScrollTopState _scrollTopState; + Animation _scrollToAnimation; bool _scrollDateShown = false; Animation _scrollDateOpacity; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 221b5f883..da631da80 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -147,6 +147,8 @@ <(src_loc)/core/crash_report_window.h <(src_loc)/core/crash_reports.cpp <(src_loc)/core/crash_reports.h +<(src_loc)/core/event_filter.cpp +<(src_loc)/core/event_filter.h <(src_loc)/core/file_utilities.cpp <(src_loc)/core/file_utilities.h <(src_loc)/core/launcher.cpp