Implement correct jump to message / unread / bottom.

This commit is contained in:
John Preston 2022-10-11 15:55:36 +04:00
parent 3999bca823
commit eec4b72d9a
13 changed files with 402 additions and 484 deletions

View file

@ -540,7 +540,7 @@ bool ForumTopic::chatListUnreadMark() const {
} }
bool ForumTopic::chatListMutedBadge() const { bool ForumTopic::chatListMutedBadge() const {
return true; return history()->mute();
} }
HistoryItem *ForumTopic::chatListMessage() const { HistoryItem *ForumTopic::chatListMessage() const {

View file

@ -5233,9 +5233,6 @@ void HistoryWidget::itemRemoved(not_null<const HistoryItem*> item) {
if (item == _replyEditMsg && _replyToId) { if (item == _replyEditMsg && _replyToId) {
cancelReply(); cancelReply();
} }
while (item == _cornerButtons.replyReturn()) {
_cornerButtons.calculateNextReplyReturn();
}
if (_kbReplyTo && item == _kbReplyTo) { if (_kbReplyTo && item == _kbReplyTo) {
toggleKeyboard(); toggleKeyboard();
_kbReplyTo = nullptr; _kbReplyTo = nullptr;
@ -5806,6 +5803,10 @@ bool HistoryWidget::cornerButtonsUnreadMayBeShown() {
return !_firstLoadRequest && !_voiceRecordBar->isLockPresent(); return !_firstLoadRequest && !_voiceRecordBar->isLockPresent();
} }
bool HistoryWidget::cornerButtonsHas(HistoryView::CornerButtonType type) {
return true;
}
void HistoryWidget::mousePressEvent(QMouseEvent *e) { void HistoryWidget::mousePressEvent(QMouseEvent *e) {
const auto hasSecondLayer = (_editMsgId const auto hasSecondLayer = (_editMsgId
|| _replyToId || _replyToId

View file

@ -330,6 +330,7 @@ private:
bool cornerButtonsIgnoreVisibility() override; bool cornerButtonsIgnoreVisibility() override;
std::optional<bool> cornerButtonsDownShown() override; std::optional<bool> cornerButtonsDownShown() override;
bool cornerButtonsUnreadMayBeShown() override; bool cornerButtonsUnreadMayBeShown() override;
bool cornerButtonsHas(HistoryView::CornerButtonType type) override;
void checkSuggestToGigagroup(); void checkSuggestToGigagroup();

View file

@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_messages.h" #include "data/data_messages.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "lang/lang_keys.h"
#include "ui/toast/toast.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
namespace HistoryView { namespace HistoryView {
@ -31,41 +33,41 @@ CornerButtons::CornerButtons(
not_null<Ui::ScrollArea*> parent, not_null<Ui::ScrollArea*> parent,
not_null<const Ui::ChatStyle*> st, not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate) not_null<CornerButtonsDelegate*> delegate)
: down( : _scroll(parent)
, _delegate(delegate)
, _down(
parent, parent,
st->value(parent->lifetime(), st::historyToDown)) st->value(parent->lifetime(), st::historyToDown))
, mentions( , _mentions(
parent, parent,
st->value(parent->lifetime(), st::historyUnreadMentions)) st->value(parent->lifetime(), st::historyUnreadMentions))
, reactions( , _reactions(
parent, parent,
st->value(parent->lifetime(), st::historyUnreadReactions)) st->value(parent->lifetime(), st::historyUnreadReactions)) {
, _scroll(parent) _down.widget->addClickHandler([=] { downClick(); });
, _delegate(delegate) { _mentions.widget->addClickHandler([=] { mentionsClick(); });
down.widget->addClickHandler([=] { downClick(); }); _reactions.widget->addClickHandler([=] { reactionsClick(); });
mentions.widget->addClickHandler([=] { mentionsClick(); });
reactions.widget->addClickHandler([=] { reactionsClick(); });
const auto filterScroll = [&](CornerButton &button) { const auto filterScroll = [&](CornerButton &button) {
button.widget->installEventFilter(this); button.widget->installEventFilter(this);
}; };
filterScroll(down); filterScroll(_down);
filterScroll(mentions); filterScroll(_mentions);
filterScroll(reactions); filterScroll(_reactions);
SendMenu::SetupUnreadMentionsMenu(mentions.widget.data(), [=] { SendMenu::SetupUnreadMentionsMenu(_mentions.widget.data(), [=] {
return _delegate->cornerButtonsEntry(); return _delegate->cornerButtonsEntry();
}); });
SendMenu::SetupUnreadReactionsMenu(reactions.widget.data(), [=] { SendMenu::SetupUnreadReactionsMenu(_reactions.widget.data(), [=] {
return _delegate->cornerButtonsEntry(); return _delegate->cornerButtonsEntry();
}); });
} }
bool CornerButtons::eventFilter(QObject *o, QEvent *e) { bool CornerButtons::eventFilter(QObject *o, QEvent *e) {
if (e->type() == QEvent::Wheel if (e->type() == QEvent::Wheel
&& (o == down.widget && (o == _down.widget
|| o == mentions.widget || o == _mentions.widget
|| o == reactions.widget)) { || o == _reactions.widget)) {
return _scroll->viewportEvent(e); return _scroll->viewportEvent(e);
} }
return QObject::eventFilter(o, e); return QObject::eventFilter(o, e);
@ -164,13 +166,23 @@ void CornerButtons::calculateNextReplyReturn() {
void CornerButtons::pushReplyReturn(not_null<HistoryItem*> item) { void CornerButtons::pushReplyReturn(not_null<HistoryItem*> item) {
_replyReturns.push_back(item->fullId()); _replyReturns.push_back(item->fullId());
_replyReturn = item; _replyReturn = item;
if (!_replyReturnStarted) {
_replyReturnStarted = true;
item->history()->owner().itemRemoved(
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
while (item == _replyReturn) {
calculateNextReplyReturn();
}
}, _down.widget->lifetime());
}
} }
CornerButton &CornerButtons::buttonByType(CornerButtonType type) { CornerButton &CornerButtons::buttonByType(Type type) {
switch (type) { switch (type) {
case CornerButtonType::Down: return down; case Type::Down: return _down;
case CornerButtonType::Mentions: return mentions; case Type::Mentions: return _mentions;
case CornerButtonType::Reactions: return reactions; case Type::Reactions: return _reactions;
} }
Unexpected("Type in CornerButtons::buttonByType."); Unexpected("Type in CornerButtons::buttonByType.");
} }
@ -215,47 +227,49 @@ void CornerButtons::updateUnreadThingsVisibility() {
} }
const auto entry = _delegate->cornerButtonsEntry(); const auto entry = _delegate->cornerButtonsEntry();
if (!entry) { if (!entry) {
updateVisibility(CornerButtonType::Mentions, false); updateVisibility(Type::Mentions, false);
updateVisibility(CornerButtonType::Reactions, false); updateVisibility(Type::Reactions, false);
return; return;
} }
auto &unreadThings = entry->session().api().unreadThings(); auto &unreadThings = entry->session().api().unreadThings();
unreadThings.preloadEnough(entry); unreadThings.preloadEnough(entry);
const auto updateWithCount = [&](CornerButtonType type, int count) { const auto updateWithCount = [&](Type type, int count) {
updateVisibility( updateVisibility(
type, type,
(count > 0) && _delegate->cornerButtonsUnreadMayBeShown()); (count > 0) && _delegate->cornerButtonsUnreadMayBeShown());
}; };
if (unreadThings.trackMentions(entry)) { if (_delegate->cornerButtonsHas(Type::Mentions)
&& unreadThings.trackMentions(entry)) {
if (const auto count = entry->unreadMentions().count(0)) { if (const auto count = entry->unreadMentions().count(0)) {
mentions.widget->setUnreadCount(count); _mentions.widget->setUnreadCount(count);
} }
updateWithCount( updateWithCount(
CornerButtonType::Mentions, Type::Mentions,
entry->unreadMentions().loadedCount()); entry->unreadMentions().loadedCount());
} else { } else {
updateVisibility(CornerButtonType::Mentions, false); updateVisibility(Type::Mentions, false);
} }
if (unreadThings.trackReactions(entry)) { if (_delegate->cornerButtonsHas(Type::Reactions)
&& unreadThings.trackReactions(entry)) {
if (const auto count = entry->unreadReactions().count(0)) { if (const auto count = entry->unreadReactions().count(0)) {
reactions.widget->setUnreadCount(count); _reactions.widget->setUnreadCount(count);
} }
updateWithCount( updateWithCount(
CornerButtonType::Reactions, Type::Reactions,
entry->unreadReactions().loadedCount()); entry->unreadReactions().loadedCount());
} else { } else {
updateVisibility(CornerButtonType::Reactions, false); updateVisibility(Type::Reactions, false);
} }
} }
void CornerButtons::updateJumpDownVisibility(std::optional<int> counter) { void CornerButtons::updateJumpDownVisibility(std::optional<int> counter) {
if (const auto shown = _delegate->cornerButtonsDownShown()) { if (const auto shown = _delegate->cornerButtonsDownShown()) {
updateVisibility(CornerButtonType::Down, *shown); updateVisibility(Type::Down, *shown);
} }
if (counter) { if (counter) {
down.widget->setUnreadCount(*counter); _down.widget->setUnreadCount(*counter);
} }
} }
@ -273,64 +287,84 @@ void CornerButtons::updatePositions() {
// All corner buttons is a child widgets of _scroll, not me. // All corner buttons is a child widgets of _scroll, not me.
const auto historyDownShown = shown(down); const auto historyDownShown = shown(_down);
const auto unreadMentionsShown = shown(mentions); const auto unreadMentionsShown = shown(_mentions);
const auto unreadReactionsShown = shown(reactions); const auto unreadReactionsShown = shown(_reactions);
const auto skip = st::historyUnreadThingsSkip; const auto skip = st::historyUnreadThingsSkip;
{ {
const auto top = anim::interpolate( const auto top = anim::interpolate(
0, 0,
down.widget->height() + st::historyToDownPosition.y(), _down.widget->height() + st::historyToDownPosition.y(),
historyDownShown); historyDownShown);
down.widget->moveToRight( _down.widget->moveToRight(
st::historyToDownPosition.x(), st::historyToDownPosition.x(),
_scroll->height() - top); _scroll->height() - top);
} }
{ {
const auto right = anim::interpolate( const auto right = anim::interpolate(
-mentions.widget->width(), -_mentions.widget->width(),
st::historyToDownPosition.x(), st::historyToDownPosition.x(),
unreadMentionsShown); unreadMentionsShown);
const auto shift = anim::interpolate( const auto shift = anim::interpolate(
0, 0,
down.widget->height() + skip, _down.widget->height() + skip,
historyDownShown); historyDownShown);
const auto top = _scroll->height() const auto top = _scroll->height()
- mentions.widget->height() - _mentions.widget->height()
- st::historyToDownPosition.y() - st::historyToDownPosition.y()
- shift; - shift;
mentions.widget->moveToRight(right, top); _mentions.widget->moveToRight(right, top);
} }
{ {
const auto right = anim::interpolate( const auto right = anim::interpolate(
-reactions.widget->width(), -_reactions.widget->width(),
st::historyToDownPosition.x(), st::historyToDownPosition.x(),
unreadReactionsShown); unreadReactionsShown);
const auto shift = anim::interpolate( const auto shift = anim::interpolate(
0, 0,
down.widget->height() + skip, _down.widget->height() + skip,
historyDownShown historyDownShown
) + anim::interpolate( ) + anim::interpolate(
0, 0,
mentions.widget->height() + skip, _mentions.widget->height() + skip,
unreadMentionsShown); unreadMentionsShown);
const auto top = _scroll->height() const auto top = _scroll->height()
- reactions.widget->height() - _reactions.widget->height()
- st::historyToDownPosition.y() - st::historyToDownPosition.y()
- shift; - shift;
reactions.widget->moveToRight(right, top); _reactions.widget->moveToRight(right, top);
} }
checkVisibility(down); checkVisibility(_down);
checkVisibility(mentions); checkVisibility(_mentions);
checkVisibility(reactions); checkVisibility(_reactions);
} }
void CornerButtons::finishAnimations() { void CornerButtons::finishAnimations() {
down.animation.stop(); _down.animation.stop();
mentions.animation.stop(); _mentions.animation.stop();
reactions.animation.stop(); _reactions.animation.stop();
updatePositions(); updatePositions();
} }
Fn<void(bool found)> CornerButtons::doneJumpFrom(
FullMsgId targetId,
FullMsgId originId) {
return [=](bool found) {
skipReplyReturn(targetId);
if (originId) {
if (const auto entry = _delegate->cornerButtonsEntry()) {
if (const auto item = entry->owner().message(originId)) {
pushReplyReturn(item);
}
}
}
if (!found) {
Ui::Toast::Show(
_scroll.get(),
tr::lng_message_not_found(tr::now));
}
};
}
} // namespace HistoryView } // namespace HistoryView

View file

@ -55,6 +55,7 @@ public:
[[nodiscard]] virtual bool cornerButtonsIgnoreVisibility() = 0; [[nodiscard]] virtual bool cornerButtonsIgnoreVisibility() = 0;
[[nodiscard]] virtual std::optional<bool> cornerButtonsDownShown() = 0; [[nodiscard]] virtual std::optional<bool> cornerButtonsDownShown() = 0;
[[nodiscard]] virtual bool cornerButtonsUnreadMayBeShown() = 0; [[nodiscard]] virtual bool cornerButtonsUnreadMayBeShown() = 0;
[[nodiscard]] virtual bool cornerButtonsHas(CornerButtonType type) = 0;
}; };
class CornerButtons final : private QObject { class CornerButtons final : private QObject {
@ -64,6 +65,8 @@ public:
not_null<const Ui::ChatStyle*> st, not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate); not_null<CornerButtonsDelegate*> delegate);
using Type = CornerButtonType;
void downClick(); void downClick();
void mentionsClick(); void mentionsClick();
void reactionsClick(); void reactionsClick();
@ -75,7 +78,7 @@ public:
void skipReplyReturn(FullMsgId id); void skipReplyReturn(FullMsgId id);
void calculateNextReplyReturn(); void calculateNextReplyReturn();
void updateVisibility(CornerButtonType type, bool shown); void updateVisibility(Type type, bool shown);
void updateUnreadThingsVisibility(); void updateUnreadThingsVisibility();
void updateJumpDownVisibility(std::optional<int> counter = {}); void updateJumpDownVisibility(std::optional<int> counter = {});
void updatePositions(); void updatePositions();
@ -85,26 +88,31 @@ public:
[[nodiscard]] HistoryItem *replyReturn() const { [[nodiscard]] HistoryItem *replyReturn() const {
return _replyReturn; return _replyReturn;
} }
[[nodiscard]] Fn<void(bool found)> doneJumpFrom(
CornerButton down; FullMsgId targetId,
CornerButton mentions; FullMsgId originId);
CornerButton reactions;
private: private:
bool eventFilter(QObject *o, QEvent *e) override; bool eventFilter(QObject *o, QEvent *e) override;
void computeCurrentReplyReturn(); void computeCurrentReplyReturn();
[[nodiscard]] CornerButton &buttonByType(CornerButtonType type); [[nodiscard]] CornerButton &buttonByType(Type type);
[[nodiscard]] History *lookupHistory() const; [[nodiscard]] History *lookupHistory() const;
void showAt(MsgId id); void showAt(MsgId id);
const not_null<Ui::ScrollArea*> _scroll; const not_null<Ui::ScrollArea*> _scroll;
const not_null<CornerButtonsDelegate*> _delegate; const not_null<CornerButtonsDelegate*> _delegate;
CornerButton _down;
CornerButton _mentions;
CornerButton _reactions;
HistoryItem *_replyReturn = nullptr; HistoryItem *_replyReturn = nullptr;
QVector<FullMsgId> _replyReturns; QVector<FullMsgId> _replyReturns;
bool _replyReturnStarted = false;
}; };
} // namespace HistoryView } // namespace HistoryView

View file

@ -482,9 +482,18 @@ void ListWidget::refreshRows(const Data::MessagesSlice &old) {
std::optional<int> ListWidget::scrollTopForPosition( std::optional<int> ListWidget::scrollTopForPosition(
Data::MessagePosition position) const { Data::MessagePosition position) const {
if (position == Data::MaxMessagePosition) { if (position == Data::UnreadMessagePosition) {
if (_bar.element && !_bar.hidden && _bar.focus) {
const auto shift = st::lineWidth + st::historyUnreadBarMargin;
return itemTop(_bar.element) + shift;
}
position = Data::MaxMessagePosition;
}
if (_visibleTop >= _visibleBottom) {
return std::nullopt;
} else if (position == Data::MaxMessagePosition) {
if (loadedAtBottom()) { if (loadedAtBottom()) {
return height(); return height() - (_visibleBottom - _visibleTop);
} }
return std::nullopt; return std::nullopt;
} else if (_items.empty() } else if (_items.empty()
@ -609,6 +618,101 @@ void ListWidget::showAroundPosition(
refreshViewer(); refreshViewer();
} }
bool ListWidget::jumpToBottomInsteadOfUnread() const {
// If we want to jump to unread, but we're at the unread already,
// then jump to the end of the list.
//
// That means there is no read inbox messages below us.
const auto firstReadMessage = [&]() -> Element* {
for (const auto &view : ranges::views::reverse(_items)) {
const auto item = view->data();
if (item->isRegular()
&& (item->out()
|| !_delegate->listElementShownUnread(view))) {
return view;
}
}
return nullptr;
}();
return !firstReadMessage || (itemTop(firstReadMessage) < _visibleBottom);
}
void ListWidget::showAtPosition(
Data::MessagePosition position,
anim::type animated,
Fn<void(bool found)> done) {
const auto showAtUnread = (position == Data::UnreadMessagePosition);
const auto showAtStart = (position == Data::MinMessagePosition);
const auto showAtEnd = (position == Data::MaxMessagePosition);
if (showAtUnread && jumpToBottomInsteadOfUnread()) {
showAtPosition(Data::MaxMessagePosition, animated, std::move(done));
return;
}
if (position.fullId.peer && position.fullId.msg) {
if (const auto item = session().data().message(position.fullId)) {
position = item->position();
}
}
if (showAtUnread) {
showAroundPosition(position, [=] {
if (_bar.element) {
_bar.element->destroyUnreadBar();
const auto i = ranges::find(_items, not_null{ _bar.element });
Assert(i != end(_items));
refreshAttachmentsAtIndex(i - begin(_items));
_bar = {};
}
checkUnreadBarCreation();
return showAtPositionNow(position, animated, done);
});
} else if (!showAtPositionNow(position, animated, done)) {
showAroundPosition(position, [=] {
return showAtPositionNow(position, animated, done);
});
}
}
bool ListWidget::showAtPositionNow(
Data::MessagePosition position,
anim::type animated,
Fn<void(bool found)> done) {
if (const auto scrollTop = scrollTopForPosition(position)) {
computeScrollTo(*scrollTop, position, animated);
if (position != Data::MaxMessagePosition
&& position != Data::UnreadMessagePosition) {
highlightMessage(position.fullId);
}
done(!position.fullId.peer
|| !position.fullId.msg
|| viewForItem(position.fullId));
return true;
}
return false;
}
void ListWidget::computeScrollTo(
int to,
Data::MessagePosition position,
anim::type animated) {
const auto currentScrollHeight = (_visibleBottom - _visibleTop);
const auto currentScrollTop = _visibleTop;
const auto wanted = std::max(
std::min(to, height() - currentScrollHeight),
0);
const auto fullDelta = (wanted - currentScrollTop);
const auto limit = currentScrollHeight;
const auto scrollDelta = std::clamp(fullDelta, -limit, limit);
const auto type = (animated == anim::type::instant)
? AnimatedScroll::None
: (std::abs(fullDelta) > limit)
? AnimatedScroll::Part
: AnimatedScroll::Full;
scrollTo(wanted, position, scrollDelta, type);
}
void ListWidget::checkUnreadBarCreation() { void ListWidget::checkUnreadBarCreation() {
if (!_bar.element) { if (!_bar.element) {
if (auto data = _delegate->listMessagesBar(_items); data.bar.element) { if (auto data = _delegate->listMessagesBar(_items); data.bar.element) {
@ -1672,7 +1776,7 @@ int ListWidget::resizeGetHeight(int newWidth) {
const auto resizeAllItems = (_itemsWidth != newWidth); const auto resizeAllItems = (_itemsWidth != newWidth);
auto newHeight = 0; auto newHeight = 0;
for (auto &view : _items) { for (const auto &view : _items) {
view->setY(newHeight); view->setY(newHeight);
if (view->pendingResize() || resizeAllItems) { if (view->pendingResize() || resizeAllItems) {
newHeight += view->resizeGetHeight(newWidth); newHeight += view->resizeGetHeight(newWidth);

View file

@ -202,23 +202,16 @@ public:
Data::MessagePosition position) const; Data::MessagePosition position) const;
Element *viewByPosition(Data::MessagePosition position) const; Element *viewByPosition(Data::MessagePosition position) const;
std::optional<int> scrollTopForView(not_null<Element*> view) const; std::optional<int> scrollTopForView(not_null<Element*> view) const;
enum class AnimatedScroll {
Full,
Part,
None,
};
void scrollTo(
int scrollTop,
Data::MessagePosition attachPosition,
int delta,
AnimatedScroll type);
[[nodiscard]] bool animatedScrolling() const; [[nodiscard]] bool animatedScrolling() const;
bool isAbovePosition(Data::MessagePosition position) const; bool isAbovePosition(Data::MessagePosition position) const;
bool isBelowPosition(Data::MessagePosition position) const; bool isBelowPosition(Data::MessagePosition position) const;
void highlightMessage(FullMsgId itemId); void highlightMessage(FullMsgId itemId);
void showAroundPosition(
void showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
Fn<bool()> overrideInitialScroll); anim::type animated = anim::type::normal,
Fn<void(bool found)> done = nullptr);
void refreshViewer();
[[nodiscard]] TextForMimeData getSelectedText() const; [[nodiscard]] TextForMimeData getSelectedText() const;
[[nodiscard]] MessageIdsList getSelectedIds() const; [[nodiscard]] MessageIdsList getSelectedIds() const;
@ -391,13 +384,21 @@ private:
void onTouchSelect(); void onTouchSelect();
void onTouchScrollTimer(); void onTouchScrollTimer();
void refreshViewer();
void updateAroundPositionFromNearest(int nearestIndex); void updateAroundPositionFromNearest(int nearestIndex);
void refreshRows(const Data::MessagesSlice &old); void refreshRows(const Data::MessagesSlice &old);
ScrollTopState countScrollState() const; ScrollTopState countScrollState() const;
void saveScrollState(); void saveScrollState();
void restoreScrollState(); void restoreScrollState();
[[nodiscard]] bool jumpToBottomInsteadOfUnread() const;
void showAroundPosition(
Data::MessagePosition position,
Fn<bool()> overrideInitialScroll);
bool showAtPositionNow(
Data::MessagePosition position,
anim::type animated,
Fn<void(bool found)> done);
Ui::ChatPaintContext preparePaintContext(const QRect &clip) const; Ui::ChatPaintContext preparePaintContext(const QRect &clip) const;
Element *viewForItem(FullMsgId itemId) const; Element *viewForItem(FullMsgId itemId) const;
@ -453,6 +454,21 @@ private:
void scrollDateHideByTimer(); void scrollDateHideByTimer();
void keepScrollDateForNow(); void keepScrollDateForNow();
void computeScrollTo(
int to,
Data::MessagePosition position,
anim::type animated);
enum class AnimatedScroll {
Full,
Part,
None,
};
void scrollTo(
int scrollTop,
Data::MessagePosition attachPosition,
int delta,
AnimatedScroll type);
void trySwitchToWordSelection(); void trySwitchToWordSelection();
void switchToWordSelection(); void switchToWordSelection();
void validateTrippleClickStartTime(); void validateTrippleClickStartTime();

View file

@ -102,9 +102,10 @@ PinnedWidget::PinnedWidget(
this, this,
QString(), QString(),
st::historyComposeButton)) st::historyComposeButton))
, _scrollDown( , _cornerButtons(
_scroll.get(), _scroll.get(),
controller->chatStyle()->value(lifetime(), st::historyToDown)) { controller->chatStyle(),
static_cast<HistoryView::CornerButtonsDelegate*>(this)) {
controller->chatStyle()->paletteChanged( controller->chatStyle()->paletteChanged(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_scroll->updateBars(); _scroll->updateBars();
@ -161,26 +162,10 @@ PinnedWidget::PinnedWidget(
}, lifetime()); }, lifetime());
setupClearButton(); setupClearButton();
setupScrollDownButton();
} }
PinnedWidget::~PinnedWidget() = default; PinnedWidget::~PinnedWidget() = default;
void PinnedWidget::setupScrollDownButton() {
_scrollDown->setClickedCallback([=] {
scrollDownClicked();
});
base::install_event_filter(_scrollDown, [=](not_null<QEvent*> event) {
if (event->type() != QEvent::Wheel) {
return base::EventFilterResult::Continue;
}
return _scroll->viewportEvent(event)
? base::EventFilterResult::Cancel
: base::EventFilterResult::Continue;
});
updateScrollDownVisibility();
}
void PinnedWidget::setupClearButton() { void PinnedWidget::setupClearButton() {
Data::CanPinMessagesValue( Data::CanPinMessagesValue(
_history->peer _history->peer
@ -203,118 +188,48 @@ void PinnedWidget::setupClearButton() {
}); });
} }
void PinnedWidget::scrollDownClicked() { void PinnedWidget::cornerButtonsShowAtPosition(
if (base::IsCtrlPressed()) { Data::MessagePosition position) {
showAtEnd(); showAtPosition(position);
//} else if (_replyReturn) { }
// showAtPosition(_replyReturn->position());
} else { Dialogs::Entry *PinnedWidget::cornerButtonsEntry() {
showAtEnd(); return _history;
}
FullMsgId PinnedWidget::cornerButtonsCurrentId() {
return {};
}
bool PinnedWidget::cornerButtonsIgnoreVisibility() {
return animatingShow();
}
std::optional<bool> PinnedWidget::cornerButtonsDownShown() {
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
if (top < _scroll->scrollTopMax() || _cornerButtons.replyReturn()) {
return true;
} else if (_inner->loadedAtBottomKnown()) {
return !_inner->loadedAtBottom();
} }
return std::nullopt;
} }
void PinnedWidget::showAtStart() { bool PinnedWidget::cornerButtonsUnreadMayBeShown() {
showAtPosition(Data::MinMessagePosition); return _inner->loadedAtBottomKnown();
} }
void PinnedWidget::showAtEnd() { bool PinnedWidget::cornerButtonsHas(CornerButtonType type) {
showAtPosition(Data::MaxMessagePosition); return (type == CornerButtonType::Down);
} }
void PinnedWidget::showAtPosition( void PinnedWidget::showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
HistoryItem *originItem) { FullMsgId originId) {
if (!showAtPositionNow(position, originItem)) { _inner->showAtPosition(
_inner->showAroundPosition(position, [=] { position,
return showAtPositionNow(position, originItem); anim::type::normal,
}); _cornerButtons.doneJumpFrom(position.fullId, originId));
}
}
bool PinnedWidget::showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem,
anim::type animated) {
using AnimatedScroll = HistoryView::ListWidget::AnimatedScroll;
const auto item = position.fullId
? _history->owner().message(position.fullId)
: nullptr;
const auto use = item ? item->position() : position;
if (const auto scrollTop = _inner->scrollTopForPosition(use)) {
const auto currentScrollTop = _scroll->scrollTop();
const auto wanted = std::clamp(
*scrollTop,
0,
_scroll->scrollTopMax());
const auto fullDelta = (wanted - currentScrollTop);
const auto limit = _scroll->height();
const auto scrollDelta = std::clamp(fullDelta, -limit, limit);
const auto type = (animated == anim::type::instant)
? AnimatedScroll::None
: (std::abs(fullDelta) > limit)
? AnimatedScroll::Part
: AnimatedScroll::Full;
_inner->scrollTo(
wanted,
use,
scrollDelta,
type);
if (use != Data::MaxMessagePosition
&& use != Data::UnreadMessagePosition) {
_inner->highlightMessage(use.fullId);
}
return true;
}
return false;
}
void PinnedWidget::updateScrollDownVisibility() {
if (animatingShow()) {
return;
}
const auto scrollDownIsVisible = [&]() -> std::optional<bool> {
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
if (top < _scroll->scrollTopMax()) {
return true;
} else if (_inner->loadedAtBottomKnown()) {
return !_inner->loadedAtBottom();
}
return std::nullopt;
};
const auto scrollDownIsShown = scrollDownIsVisible();
if (!scrollDownIsShown) {
return;
}
if (_scrollDownIsShown != *scrollDownIsShown) {
_scrollDownIsShown = *scrollDownIsShown;
_scrollDownShown.start(
[=] { updateScrollDownPosition(); },
_scrollDownIsShown ? 0. : 1.,
_scrollDownIsShown ? 1. : 0.,
st::historyToDownDuration);
}
}
void PinnedWidget::updateScrollDownPosition() {
// _scrollDown is a child widget of _scroll, not me.
auto top = anim::interpolate(
0,
_scrollDown->height() + st::historyToDownPosition.y(),
_scrollDownShown.value(_scrollDownIsShown ? 1. : 0.));
_scrollDown->moveToRight(
st::historyToDownPosition.x(),
_scroll->height() - top);
auto shouldBeHidden = !_scrollDownIsShown && !_scrollDownShown.animating();
if (shouldBeHidden != _scrollDown->isHidden()) {
_scrollDown->setVisible(!shouldBeHidden);
}
}
void PinnedWidget::scrollDownAnimationFinish() {
_scrollDownShown.stop();
updateScrollDownPosition();
} }
void PinnedWidget::updateAdaptiveLayout() { void PinnedWidget::updateAdaptiveLayout() {
@ -387,15 +302,12 @@ void PinnedWidget::saveState(not_null<PinnedMemento*> memento) {
void PinnedWidget::restoreState(not_null<PinnedMemento*> memento) { void PinnedWidget::restoreState(not_null<PinnedMemento*> memento) {
_inner->restoreState(memento->list()); _inner->restoreState(memento->list());
if (const auto highlight = memento->getHighlightId()) { if (const auto highlight = memento->getHighlightId()) {
const auto position = Data::MessagePosition{ _inner->showAtPosition(Data::MessagePosition{
.fullId = ((highlight > 0 || !_migratedPeer) .fullId = ((highlight > 0 || !_migratedPeer)
? FullMsgId(_history->peer->id, highlight) ? FullMsgId(_history->peer->id, highlight)
: FullMsgId(_migratedPeer->id, -highlight)), : FullMsgId(_migratedPeer->id, -highlight)),
.date = TimeId(0), .date = TimeId(0),
}; }, anim::type::instant);
_inner->showAroundPosition(position, [=] {
return showAtPositionNow(position, nullptr, anim::type::instant);
});
} }
} }
@ -463,7 +375,8 @@ void PinnedWidget::updateControlsGeometry() {
} }
updateInnerVisibleArea(); updateInnerVisibleArea();
} }
updateScrollDownPosition();
_cornerButtons.updatePositions();
} }
void PinnedWidget::paintEvent(QPaintEvent *e) { void PinnedWidget::paintEvent(QPaintEvent *e) {
@ -490,7 +403,8 @@ void PinnedWidget::onScroll() {
void PinnedWidget::updateInnerVisibleArea() { void PinnedWidget::updateInnerVisibleArea() {
const auto scrollTop = _scroll->scrollTop(); const auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
updateScrollDownVisibility(); _cornerButtons.updateJumpDownVisibility();
_cornerButtons.updateUnreadThingsVisibility();
} }
void PinnedWidget::showAnimatedHook( void PinnedWidget::showAnimatedHook(

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/section_widget.h" #include "window/section_widget.h"
#include "window/section_memento.h" #include "window/section_memento.h"
#include "history/view/history_view_list_widget.h" #include "history/view/history_view_list_widget.h"
#include "history/view/history_view_corner_buttons.h"
#include "data/data_messages.h" #include "data/data_messages.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
#include "base/timer.h" #include "base/timer.h"
@ -35,7 +36,8 @@ class PinnedMemento;
class PinnedWidget final class PinnedWidget final
: public Window::SectionWidget : public Window::SectionWidget
, private ListDelegate { , private ListDelegate
, private CornerButtonsDelegate {
public: public:
PinnedWidget( PinnedWidget(
QWidget *parent, QWidget *parent,
@ -111,6 +113,16 @@ public:
-> rpl::producer<Data::AllowedReactions> override; -> rpl::producer<Data::AllowedReactions> override;
void listShowPremiumToast(not_null<DocumentData*> document) override; void listShowPremiumToast(not_null<DocumentData*> document) override;
// CornerButtonsDelegate delegate.
void cornerButtonsShowAtPosition(
Data::MessagePosition position) override;
Dialogs::Entry *cornerButtonsEntry() override;
FullMsgId cornerButtonsCurrentId() override;
bool cornerButtonsIgnoreVisibility() override;
std::optional<bool> cornerButtonsDownShown() override;
bool cornerButtonsUnreadMayBeShown() override;
bool cornerButtonsHas(CornerButtonType type) override;
protected: protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
@ -127,22 +139,11 @@ private:
void updateAdaptiveLayout(); void updateAdaptiveLayout();
void saveState(not_null<PinnedMemento*> memento); void saveState(not_null<PinnedMemento*> memento);
void restoreState(not_null<PinnedMemento*> memento); void restoreState(not_null<PinnedMemento*> memento);
void showAtStart();
void showAtEnd();
void showAtPosition( void showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
HistoryItem *originItem = nullptr); FullMsgId originId = {});
bool showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem,
anim::type animated = anim::type::normal);
void setupClearButton(); void setupClearButton();
void setupScrollDownButton();
void scrollDownClicked();
void scrollDownAnimationFinish();
void updateScrollDownVisibility();
void updateScrollDownPosition();
void confirmDeleteSelected(); void confirmDeleteSelected();
void confirmForwardSelected(); void confirmForwardSelected();
@ -163,9 +164,7 @@ private:
std::unique_ptr<Ui::ScrollArea> _scroll; std::unique_ptr<Ui::ScrollArea> _scroll;
std::unique_ptr<Ui::FlatButton> _clearButton; std::unique_ptr<Ui::FlatButton> _clearButton;
Ui::Animations::Simple _scrollDownShown; CornerButtons _cornerButtons;
bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown;
int _messagesCount = -1; int _messagesCount = -1;

View file

@ -337,9 +337,6 @@ RepliesWidget::RepliesWidget(
controller->showBackFromStack(); controller->showBackFromStack();
} }
} }
while (update.item == _cornerButtons.replyReturn()) {
_cornerButtons.calculateNextReplyReturn();
}
}, lifetime()); }, lifetime());
_history->session().changes().historyUpdates( _history->session().changes().historyUpdates(
@ -477,6 +474,8 @@ void RepliesWidget::setupTopicViewer() {
} }
void RepliesWidget::subscribeToTopic() { void RepliesWidget::subscribeToTopic() {
Expects(_topic != nullptr);
using TopicUpdateFlag = Data::TopicUpdate::Flag; using TopicUpdateFlag = Data::TopicUpdate::Flag;
session().changes().topicUpdates( session().changes().topicUpdates(
_topic, _topic,
@ -485,6 +484,8 @@ void RepliesWidget::subscribeToTopic() {
) | rpl::start_with_next([=](const Data::TopicUpdate &update) { ) | rpl::start_with_next([=](const Data::TopicUpdate &update) {
_cornerButtons.updateUnreadThingsVisibility(); _cornerButtons.updateUnreadThingsVisibility();
}, _topicLifetime); }, _topicLifetime);
_cornerButtons.updateUnreadThingsVisibility();
} }
void RepliesWidget::setTopic(Data::ForumTopic *topic) { void RepliesWidget::setTopic(Data::ForumTopic *topic) {
@ -917,7 +918,7 @@ bool RepliesWidget::showSlowmodeError() {
Ui::FormatDurationWordsSlowmode(left)); Ui::FormatDurationWordsSlowmode(left));
} else if (_history->peer->slowmodeApplied()) { } else if (_history->peer->slowmodeApplied()) {
if (const auto item = _history->latestSendingMessage()) { if (const auto item = _history->latestSendingMessage()) {
showAtPositionNow(item->position(), nullptr); showAtPosition(item->position());
return tr::lng_slowmode_no_many(tr::now); return tr::lng_slowmode_no_many(tr::now);
} }
} }
@ -1379,7 +1380,7 @@ void RepliesWidget::cornerButtonsShowAtPosition(
} }
Dialogs::Entry *RepliesWidget::cornerButtonsEntry() { Dialogs::Entry *RepliesWidget::cornerButtonsEntry() {
return _topic; return _topic ? static_cast<Dialogs::Entry*>(_topic) : _history;
} }
FullMsgId RepliesWidget::cornerButtonsCurrentId() { FullMsgId RepliesWidget::cornerButtonsCurrentId() {
@ -1408,6 +1409,10 @@ bool RepliesWidget::cornerButtonsUnreadMayBeShown() {
&& !_composeControls->isLockPresent(); && !_composeControls->isLockPresent();
} }
bool RepliesWidget::cornerButtonsHas(CornerButtonType type) {
return _topic || (type == CornerButtonType::Down);
}
void RepliesWidget::showAtStart() { void RepliesWidget::showAtStart() {
showAtPosition(Data::MinMessagePosition); showAtPosition(Data::MinMessagePosition);
} }
@ -1426,50 +1431,12 @@ void RepliesWidget::finishSending() {
void RepliesWidget::showAtPosition( void RepliesWidget::showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
HistoryItem *originItem) { FullMsgId originItemId) {
if (!showAtPositionNow(position, originItem)) { _lastShownAt = position.fullId;
_inner->showAroundPosition(position, [=] { _inner->showAtPosition(
return showAtPositionNow(position, originItem); position,
}); anim::type::normal,
} _cornerButtons.doneJumpFrom(position.fullId, originItemId));
}
bool RepliesWidget::showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem,
anim::type animated) {
using AnimatedScroll = HistoryView::ListWidget::AnimatedScroll;
const auto item = position.fullId
? _history->owner().message(position.fullId)
: nullptr;
const auto use = item ? item->position() : position;
if (const auto scrollTop = _inner->scrollTopForPosition(use)) {
_cornerButtons.skipReplyReturn(use.fullId);
const auto currentScrollTop = _scroll->scrollTop();
const auto wanted = std::clamp(
*scrollTop,
0,
_scroll->scrollTopMax());
const auto fullDelta = (wanted - currentScrollTop);
const auto limit = _scroll->height();
const auto scrollDelta = std::clamp(fullDelta, -limit, limit);
const auto type = (animated == anim::type::instant)
? AnimatedScroll::None
: (std::abs(fullDelta) > limit)
? AnimatedScroll::Part
: AnimatedScroll::Full;
_inner->scrollTo(wanted, use, scrollDelta, type);
_lastShownAt = use.fullId;
if (use != Data::MaxMessagePosition
&& use != Data::UnreadMessagePosition) {
_inner->highlightMessage(use.fullId);
}
if (originItem) {
pushReplyReturn(originItem);
}
return true;
}
return false;
} }
void RepliesWidget::updateAdaptiveLayout() { void RepliesWidget::updateAdaptiveLayout() {
@ -1619,11 +1586,10 @@ bool RepliesWidget::showMessage(
if (!originMessage) { if (!originMessage) {
return false; return false;
} }
const auto originItem = (!originMessage const auto originItemId = (_cornerButtons.replyReturn() != originMessage)
|| _cornerButtons.replyReturn() == originMessage) ? originMessage->fullId()
? nullptr : FullMsgId();
: originMessage; showAtPosition(message->position(), originItemId);
showAtPosition(message->position(), originItem);
return true; return true;
} }
@ -1653,7 +1619,7 @@ void RepliesWidget::refreshReplies() {
? _topic->replies() ? _topic->replies()
: std::make_shared<Data::RepliesList>(_history, _rootId)); : std::make_shared<Data::RepliesList>(_history, _rootId));
if (old) { if (old) {
_inner->showAroundPosition(Data::UnreadMessagePosition, nullptr); _inner->refreshViewer();
} }
} }
@ -1701,13 +1667,10 @@ void RepliesWidget::restoreState(not_null<RepliesMemento*> memento) {
_cornerButtons.setReplyReturns(memento->replyReturns()); _cornerButtons.setReplyReturns(memento->replyReturns());
_inner->restoreState(memento->list()); _inner->restoreState(memento->list());
if (const auto highlight = memento->getHighlightId()) { if (const auto highlight = memento->getHighlightId()) {
const auto position = Data::MessagePosition{ _inner->showAtPosition(Data::MessagePosition{
.fullId = FullMsgId(_history->peer->id, highlight), .fullId = FullMsgId(_history->peer->id, highlight),
.date = TimeId(0), .date = TimeId(0),
}; }, anim::type::instant);
_inner->showAroundPosition(position, [=] {
return showAtPositionNow(position, nullptr);
});
} }
} }

View file

@ -159,6 +159,7 @@ public:
bool cornerButtonsIgnoreVisibility() override; bool cornerButtonsIgnoreVisibility() override;
std::optional<bool> cornerButtonsDownShown() override; std::optional<bool> cornerButtonsDownShown() override;
bool cornerButtonsUnreadMayBeShown() override; bool cornerButtonsUnreadMayBeShown() override;
bool cornerButtonsHas(CornerButtonType type) override;
protected: protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
@ -182,11 +183,7 @@ private:
void showAtEnd(); void showAtEnd();
void showAtPosition( void showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
HistoryItem *originItem = nullptr); FullMsgId originItemId = {});
bool showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem,
anim::type animated = anim::type::normal);
void finishSending(); void finishSending();
void setupComposeControls(); void setupComposeControls();

View file

@ -112,9 +112,10 @@ ScheduledWidget::ScheduledWidget(
[=](not_null<DocumentData*> emoji) { listShowPremiumToast(emoji); }, [=](not_null<DocumentData*> emoji) { listShowPremiumToast(emoji); },
ComposeControls::Mode::Scheduled, ComposeControls::Mode::Scheduled,
SendMenu::Type::Disabled)) SendMenu::Type::Disabled))
, _scrollDown( , _cornerButtons(
_scroll, _scroll.data(),
controller->chatStyle()->value(lifetime(), st::historyToDown)) { controller->chatStyle(),
static_cast<HistoryView::CornerButtonsDelegate*>(this)) {
controller->chatStyle()->paletteChanged( controller->chatStyle()->paletteChanged(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_scroll->updateBars(); _scroll->updateBars();
@ -193,16 +194,6 @@ ScheduledWidget::ScheduledWidget(
emptyInfo->setText(emptyText); emptyInfo->setText(emptyText);
_inner->setEmptyInfoWidget(std::move(emptyInfo)); _inner->setEmptyInfoWidget(std::move(emptyInfo));
} }
_history->session().changes().messageUpdates(
Data::MessageUpdate::Flag::Destroyed
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
while (update.item == _replyReturn) {
calculateNextReplyReturn();
}
}, lifetime());
setupScrollDownButton();
setupComposeControls(); setupComposeControls();
} }
@ -307,7 +298,8 @@ void ScheduledWidget::setupComposeControls() {
_composeControls->lockShowStarts( _composeControls->lockShowStarts(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
updateScrollDownVisibility(); _cornerButtons.updateJumpDownVisibility();
_cornerButtons.updateUnreadThingsVisibility();
}, lifetime()); }, lifetime());
_composeControls->viewportEvents( _composeControls->viewportEvents(
@ -488,40 +480,21 @@ bool ScheduledWidget::confirmSendingFiles(
void ScheduledWidget::pushReplyReturn(not_null<HistoryItem*> item) { void ScheduledWidget::pushReplyReturn(not_null<HistoryItem*> item) {
if (_inner->viewByPosition(item->position())) { if (_inner->viewByPosition(item->position())) {
_replyReturns.push_back(item->id); _cornerButtons.pushReplyReturn(item);
} else {
return;
}
_replyReturn = item;
updateScrollDownVisibility();
}
void ScheduledWidget::computeCurrentReplyReturn() {
_replyReturn = _replyReturns.empty()
? nullptr
: _history->owner().message(_history->peer, _replyReturns.back());
}
void ScheduledWidget::calculateNextReplyReturn() {
_replyReturn = nullptr;
while (!_replyReturns.empty() && !_replyReturn) {
_replyReturns.pop_back();
computeCurrentReplyReturn();
}
if (!_replyReturn) {
updateScrollDownVisibility();
} }
} }
void ScheduledWidget::checkReplyReturns() { void ScheduledWidget::checkReplyReturns() {
const auto currentTop = _scroll->scrollTop(); const auto currentTop = _scroll->scrollTop();
for (; _replyReturn != nullptr; calculateNextReplyReturn()) { while (const auto replyReturn = _cornerButtons.replyReturn()) {
const auto position = _replyReturn->position(); const auto position = replyReturn->position();
const auto scrollTop = _inner->scrollTopForPosition(position); const auto scrollTop = _inner->scrollTopForPosition(position);
const auto scrolledBelow = scrollTop const auto below = scrollTop
? (currentTop >= std::min(*scrollTop, _scroll->scrollTopMax())) ? (currentTop >= std::min(*scrollTop, _scroll->scrollTopMax()))
: _inner->isBelowPosition(position); : _inner->isBelowPosition(position);
if (!scrolledBelow) { if (below) {
_cornerButtons.calculateNextReplyReturn();
} else {
break; break;
} }
} }
@ -844,138 +817,52 @@ SendMenu::Type ScheduledWidget::sendMenuType() const {
: SendMenu::Type::Scheduled; : SendMenu::Type::Scheduled;
} }
void ScheduledWidget::setupScrollDownButton() { void ScheduledWidget::cornerButtonsShowAtPosition(
_scrollDown->setClickedCallback([=] { Data::MessagePosition position) {
scrollDownClicked(); showAtPosition(position);
});
base::install_event_filter(_scrollDown, [=](not_null<QEvent*> event) {
if (event->type() != QEvent::Wheel) {
return base::EventFilterResult::Continue;
}
return _scroll->viewportEvent(event)
? base::EventFilterResult::Cancel
: base::EventFilterResult::Continue;
});
updateScrollDownVisibility();
} }
void ScheduledWidget::scrollDownClicked() { Dialogs::Entry *ScheduledWidget::cornerButtonsEntry() {
if (base::IsCtrlPressed()) { return _history;
showAtEnd(); }
} else if (_replyReturn) {
showAtPosition(_replyReturn->position()); FullMsgId ScheduledWidget::cornerButtonsCurrentId() {
} else { return {};
showAtEnd(); }
bool ScheduledWidget::cornerButtonsIgnoreVisibility() {
return animatingShow();
}
std::optional<bool> ScheduledWidget::cornerButtonsDownShown() {
if (_composeControls->isLockPresent()) {
return false;
} }
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
if (top < _scroll->scrollTopMax() || _cornerButtons.replyReturn()) {
return true;
} else if (_inner->loadedAtBottomKnown()) {
return !_inner->loadedAtBottom();
}
return std::nullopt;
} }
void ScheduledWidget::showAtEnd() { bool ScheduledWidget::cornerButtonsUnreadMayBeShown() {
showAtPosition(Data::MaxMessagePosition); return _inner->loadedAtBottomKnown()
&& !_composeControls->isLockPresent();
}
bool ScheduledWidget::cornerButtonsHas(CornerButtonType type) {
return (type == CornerButtonType::Down);
} }
void ScheduledWidget::showAtPosition( void ScheduledWidget::showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
HistoryItem *originItem) { FullMsgId originId) {
if (showAtPositionNow(position, originItem)) { _inner->showAtPosition(
if (const auto highlight = base::take(_highlightMessageId)) { position,
_inner->highlightMessage(highlight); anim::type::normal,
} _cornerButtons.doneJumpFrom(position.fullId, originId));
} else {
_nextAnimatedScrollPosition = position;
_nextAnimatedScrollDelta = _inner->isBelowPosition(position)
? -_scroll->height()
: _inner->isAbovePosition(position)
? _scroll->height()
: 0;
auto memento = HistoryView::ListMemento(position);
_inner->restoreState(&memento);
}
}
bool ScheduledWidget::showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem) {
if (const auto scrollTop = _inner->scrollTopForPosition(position)) {
while (_replyReturn && position.fullId.msg == _replyReturn->id) {
calculateNextReplyReturn();
}
const auto currentScrollTop = _scroll->scrollTop();
const auto wanted = std::clamp(
*scrollTop,
0,
_scroll->scrollTopMax());
const auto fullDelta = (wanted - currentScrollTop);
const auto limit = _scroll->height();
const auto scrollDelta = std::clamp(fullDelta, -limit, limit);
_inner->scrollTo(
wanted,
position,
scrollDelta,
(std::abs(fullDelta) > limit
? HistoryView::ListWidget::AnimatedScroll::Part
: HistoryView::ListWidget::AnimatedScroll::Full));
if (position != Data::MaxMessagePosition
&& position != Data::UnreadMessagePosition) {
_inner->highlightMessage(position.fullId);
}
if (originItem) {
pushReplyReturn(originItem);
}
return true;
}
return false;
}
void ScheduledWidget::updateScrollDownVisibility() {
if (animatingShow()) {
return;
}
const auto scrollDownIsVisible = [&]() -> std::optional<bool> {
if (_composeControls->isLockPresent()) {
return false;
}
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
if (top < _scroll->scrollTopMax()) {
return true;
}
if (_inner->loadedAtBottomKnown()) {
return !_inner->loadedAtBottom();
}
return std::nullopt;
};
const auto scrollDownIsShown = scrollDownIsVisible();
if (!scrollDownIsShown) {
return;
}
if (_scrollDownIsShown != *scrollDownIsShown) {
_scrollDownIsShown = *scrollDownIsShown;
_scrollDownShown.start(
[=] { updateScrollDownPosition(); },
_scrollDownIsShown ? 0. : 1.,
_scrollDownIsShown ? 1. : 0.,
st::historyToDownDuration);
}
}
void ScheduledWidget::updateScrollDownPosition() {
// _scrollDown is a child widget of _scroll, not me.
auto top = anim::interpolate(
0,
_scrollDown->height() + st::historyToDownPosition.y(),
_scrollDownShown.value(_scrollDownIsShown ? 1. : 0.));
_scrollDown->moveToRight(
st::historyToDownPosition.x(),
_scroll->height() - top);
auto shouldBeHidden = !_scrollDownIsShown && !_scrollDownShown.animating();
if (shouldBeHidden != _scrollDown->isHidden()) {
_scrollDown->setVisible(!shouldBeHidden);
}
}
void ScheduledWidget::scrollDownAnimationFinish() {
_scrollDownShown.stop();
updateScrollDownPosition();
} }
void ScheduledWidget::updateAdaptiveLayout() { void ScheduledWidget::updateAdaptiveLayout() {
@ -1092,7 +979,7 @@ void ScheduledWidget::updateControlsGeometry() {
_composeControls->move(0, bottom - controlsHeight); _composeControls->move(0, bottom - controlsHeight);
_composeControls->setAutocompleteBoundingRect(_scroll->geometry()); _composeControls->setAutocompleteBoundingRect(_scroll->geometry());
updateScrollDownPosition(); _cornerButtons.updatePositions();
} }
void ScheduledWidget::paintEvent(QPaintEvent *e) { void ScheduledWidget::paintEvent(QPaintEvent *e) {
@ -1126,7 +1013,8 @@ void ScheduledWidget::updateInnerVisibleArea() {
} }
const auto scrollTop = _scroll->scrollTop(); const auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height()); _inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
updateScrollDownVisibility(); _cornerButtons.updateJumpDownVisibility();
_cornerButtons.updateUnreadThingsVisibility();
} }
void ScheduledWidget::showAnimatedHook( void ScheduledWidget::showAnimatedHook(
@ -1224,7 +1112,6 @@ void ScheduledWidget::highlightSingleNewMessage(
} }
const auto newId = slice.ids[firstDifferent]; const auto newId = slice.ids[firstDifferent];
if (const auto item = session().data().message(newId)) { if (const auto item = session().data().message(newId)) {
// _highlightMessageId = newId;
showAtPosition(item->position()); showAtPosition(item->position());
} }
} }
@ -1267,7 +1154,7 @@ void ScheduledWidget::listMarkContentsRead(
MessagesBarData ScheduledWidget::listMessagesBar( MessagesBarData ScheduledWidget::listMessagesBar(
const std::vector<not_null<Element*>> &elements) { const std::vector<not_null<Element*>> &elements) {
return MessagesBarData(); return {};
} }
void ScheduledWidget::listContentRefreshed() { void ScheduledWidget::listContentRefreshed() {
@ -1308,14 +1195,16 @@ bool ScheduledWidget::showMessage(
if (const auto origin = std::get_if<OriginMessage>(&params.origin)) { if (const auto origin = std::get_if<OriginMessage>(&params.origin)) {
if (const auto returnTo = session().data().message(origin->id)) { if (const auto returnTo = session().data().message(origin->id)) {
if (_inner->viewByPosition(returnTo->position()) if (_inner->viewByPosition(returnTo->position())
&& _replyReturn != returnTo) { && _cornerButtons.replyReturn() != returnTo) {
return returnTo; return returnTo;
} }
} }
} }
return nullptr; return nullptr;
}(); }();
showAtPosition(message->position(), originItem); showAtPosition(
message->position(),
originItem ? originItem->fullId() : FullMsgId());
return true; return true;
} }

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/section_widget.h" #include "window/section_widget.h"
#include "window/section_memento.h" #include "window/section_memento.h"
#include "history/view/history_view_list_widget.h" #include "history/view/history_view_list_widget.h"
#include "history/view/history_view_corner_buttons.h"
#include "data/data_messages.h" #include "data/data_messages.h"
class History; class History;
@ -52,7 +53,8 @@ class StickerToast;
class ScheduledWidget final class ScheduledWidget final
: public Window::SectionWidget : public Window::SectionWidget
, private ListDelegate { , private ListDelegate
, private CornerButtonsDelegate {
public: public:
ScheduledWidget( ScheduledWidget(
QWidget *parent, QWidget *parent,
@ -121,7 +123,8 @@ public:
ClickHandlerPtr listDateLink(not_null<Element*> view) override; ClickHandlerPtr listDateLink(not_null<Element*> view) override;
bool listElementHideReply(not_null<const Element*> view) override; bool listElementHideReply(not_null<const Element*> view) override;
bool listElementShownUnread(not_null<const Element*> view) override; bool listElementShownUnread(not_null<const Element*> view) override;
bool listIsGoodForAroundPosition(not_null<const Element *> view) override; bool listIsGoodForAroundPosition(
not_null<const Element *> view) override;
void listSendBotCommand( void listSendBotCommand(
const QString &command, const QString &command,
const FullMsgId &context) override; const FullMsgId &context) override;
@ -133,6 +136,16 @@ public:
-> rpl::producer<Data::AllowedReactions> override; -> rpl::producer<Data::AllowedReactions> override;
void listShowPremiumToast(not_null<DocumentData*> document) override; void listShowPremiumToast(not_null<DocumentData*> document) override;
// CornerButtonsDelegate delegate.
void cornerButtonsShowAtPosition(
Data::MessagePosition position) override;
Dialogs::Entry *cornerButtonsEntry() override;
FullMsgId cornerButtonsCurrentId() override;
bool cornerButtonsIgnoreVisibility() override;
std::optional<bool> cornerButtonsDownShown() override;
bool cornerButtonsUnreadMayBeShown() override;
bool cornerButtonsHas(CornerButtonType type) override;
protected: protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
@ -151,22 +164,12 @@ private:
void restoreState(not_null<ScheduledMemento*> memento); void restoreState(not_null<ScheduledMemento*> memento);
void showAtPosition( void showAtPosition(
Data::MessagePosition position, Data::MessagePosition position,
HistoryItem *originItem = nullptr); FullMsgId originId = {});
bool showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem);
void setupComposeControls(); void setupComposeControls();
void setupDragArea(); void setupDragArea();
void setupScrollDownButton();
void scrollDownClicked();
void scrollDownAnimationFinish();
void updateScrollDownVisibility();
void updateScrollDownPosition();
void showAtEnd();
void confirmSendNowSelected(); void confirmSendNowSelected();
void confirmDeleteSelected(); void confirmDeleteSelected();
void clearSelected(); void clearSelected();
@ -190,8 +193,6 @@ private:
[[nodiscard]] SendMenu::Type sendMenuType() const; [[nodiscard]] SendMenu::Type sendMenuType() const;
void pushReplyReturn(not_null<HistoryItem*> item); void pushReplyReturn(not_null<HistoryItem*> item);
void computeCurrentReplyReturn();
void calculateNextReplyReturn();
void checkReplyReturns(); void checkReplyReturns();
void uploadFile(const QByteArray &fileContent, SendMediaType type); void uploadFile(const QByteArray &fileContent, SendMediaType type);
@ -242,16 +243,7 @@ private:
std::unique_ptr<HistoryView::StickerToast> _stickerToast; std::unique_ptr<HistoryView::StickerToast> _stickerToast;
std::vector<MsgId> _replyReturns; CornerButtons _cornerButtons;
HistoryItem *_replyReturn = nullptr;
FullMsgId _highlightMessageId;
std::optional<Data::MessagePosition> _nextAnimatedScrollPosition;
int _nextAnimatedScrollDelta = 0;
Ui::Animations::Simple _scrollDownShown;
bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown;
Data::MessagesSlice _lastSlice; Data::MessagesSlice _lastSlice;
bool _choosingAttach = false; bool _choosingAttach = false;