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 {
return true;
return history()->mute();
}
HistoryItem *ForumTopic::chatListMessage() const {

View file

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

View file

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

View file

@ -23,6 +23,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_messages.h"
#include "data/data_session.h"
#include "data/data_forum_topic.h"
#include "lang/lang_keys.h"
#include "ui/toast/toast.h"
#include "styles/style_chat.h"
namespace HistoryView {
@ -31,41 +33,41 @@ CornerButtons::CornerButtons(
not_null<Ui::ScrollArea*> parent,
not_null<const Ui::ChatStyle*> st,
not_null<CornerButtonsDelegate*> delegate)
: down(
: _scroll(parent)
, _delegate(delegate)
, _down(
parent,
st->value(parent->lifetime(), st::historyToDown))
, mentions(
, _mentions(
parent,
st->value(parent->lifetime(), st::historyUnreadMentions))
, reactions(
parent,
st->value(parent->lifetime(), st::historyUnreadReactions))
, _scroll(parent)
, _delegate(delegate) {
down.widget->addClickHandler([=] { downClick(); });
mentions.widget->addClickHandler([=] { mentionsClick(); });
reactions.widget->addClickHandler([=] { reactionsClick(); });
, _reactions(
parent,
st->value(parent->lifetime(), st::historyUnreadReactions)) {
_down.widget->addClickHandler([=] { downClick(); });
_mentions.widget->addClickHandler([=] { mentionsClick(); });
_reactions.widget->addClickHandler([=] { reactionsClick(); });
const auto filterScroll = [&](CornerButton &button) {
button.widget->installEventFilter(this);
};
filterScroll(down);
filterScroll(mentions);
filterScroll(reactions);
filterScroll(_down);
filterScroll(_mentions);
filterScroll(_reactions);
SendMenu::SetupUnreadMentionsMenu(mentions.widget.data(), [=] {
SendMenu::SetupUnreadMentionsMenu(_mentions.widget.data(), [=] {
return _delegate->cornerButtonsEntry();
});
SendMenu::SetupUnreadReactionsMenu(reactions.widget.data(), [=] {
SendMenu::SetupUnreadReactionsMenu(_reactions.widget.data(), [=] {
return _delegate->cornerButtonsEntry();
});
}
bool CornerButtons::eventFilter(QObject *o, QEvent *e) {
if (e->type() == QEvent::Wheel
&& (o == down.widget
|| o == mentions.widget
|| o == reactions.widget)) {
&& (o == _down.widget
|| o == _mentions.widget
|| o == _reactions.widget)) {
return _scroll->viewportEvent(e);
}
return QObject::eventFilter(o, e);
@ -164,13 +166,23 @@ void CornerButtons::calculateNextReplyReturn() {
void CornerButtons::pushReplyReturn(not_null<HistoryItem*> item) {
_replyReturns.push_back(item->fullId());
_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) {
case CornerButtonType::Down: return down;
case CornerButtonType::Mentions: return mentions;
case CornerButtonType::Reactions: return reactions;
case Type::Down: return _down;
case Type::Mentions: return _mentions;
case Type::Reactions: return _reactions;
}
Unexpected("Type in CornerButtons::buttonByType.");
}
@ -215,47 +227,49 @@ void CornerButtons::updateUnreadThingsVisibility() {
}
const auto entry = _delegate->cornerButtonsEntry();
if (!entry) {
updateVisibility(CornerButtonType::Mentions, false);
updateVisibility(CornerButtonType::Reactions, false);
updateVisibility(Type::Mentions, false);
updateVisibility(Type::Reactions, false);
return;
}
auto &unreadThings = entry->session().api().unreadThings();
unreadThings.preloadEnough(entry);
const auto updateWithCount = [&](CornerButtonType type, int count) {
const auto updateWithCount = [&](Type type, int count) {
updateVisibility(
type,
(count > 0) && _delegate->cornerButtonsUnreadMayBeShown());
};
if (unreadThings.trackMentions(entry)) {
if (_delegate->cornerButtonsHas(Type::Mentions)
&& unreadThings.trackMentions(entry)) {
if (const auto count = entry->unreadMentions().count(0)) {
mentions.widget->setUnreadCount(count);
_mentions.widget->setUnreadCount(count);
}
updateWithCount(
CornerButtonType::Mentions,
Type::Mentions,
entry->unreadMentions().loadedCount());
} 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)) {
reactions.widget->setUnreadCount(count);
_reactions.widget->setUnreadCount(count);
}
updateWithCount(
CornerButtonType::Reactions,
Type::Reactions,
entry->unreadReactions().loadedCount());
} else {
updateVisibility(CornerButtonType::Reactions, false);
updateVisibility(Type::Reactions, false);
}
}
void CornerButtons::updateJumpDownVisibility(std::optional<int> counter) {
if (const auto shown = _delegate->cornerButtonsDownShown()) {
updateVisibility(CornerButtonType::Down, *shown);
updateVisibility(Type::Down, *shown);
}
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.
const auto historyDownShown = shown(down);
const auto unreadMentionsShown = shown(mentions);
const auto unreadReactionsShown = shown(reactions);
const auto historyDownShown = shown(_down);
const auto unreadMentionsShown = shown(_mentions);
const auto unreadReactionsShown = shown(_reactions);
const auto skip = st::historyUnreadThingsSkip;
{
const auto top = anim::interpolate(
0,
down.widget->height() + st::historyToDownPosition.y(),
_down.widget->height() + st::historyToDownPosition.y(),
historyDownShown);
down.widget->moveToRight(
_down.widget->moveToRight(
st::historyToDownPosition.x(),
_scroll->height() - top);
}
{
const auto right = anim::interpolate(
-mentions.widget->width(),
-_mentions.widget->width(),
st::historyToDownPosition.x(),
unreadMentionsShown);
const auto shift = anim::interpolate(
0,
down.widget->height() + skip,
_down.widget->height() + skip,
historyDownShown);
const auto top = _scroll->height()
- mentions.widget->height()
- _mentions.widget->height()
- st::historyToDownPosition.y()
- shift;
mentions.widget->moveToRight(right, top);
_mentions.widget->moveToRight(right, top);
}
{
const auto right = anim::interpolate(
-reactions.widget->width(),
-_reactions.widget->width(),
st::historyToDownPosition.x(),
unreadReactionsShown);
const auto shift = anim::interpolate(
0,
down.widget->height() + skip,
_down.widget->height() + skip,
historyDownShown
) + anim::interpolate(
0,
mentions.widget->height() + skip,
_mentions.widget->height() + skip,
unreadMentionsShown);
const auto top = _scroll->height()
- reactions.widget->height()
- _reactions.widget->height()
- st::historyToDownPosition.y()
- shift;
reactions.widget->moveToRight(right, top);
_reactions.widget->moveToRight(right, top);
}
checkVisibility(down);
checkVisibility(mentions);
checkVisibility(reactions);
checkVisibility(_down);
checkVisibility(_mentions);
checkVisibility(_reactions);
}
void CornerButtons::finishAnimations() {
down.animation.stop();
mentions.animation.stop();
reactions.animation.stop();
_down.animation.stop();
_mentions.animation.stop();
_reactions.animation.stop();
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

View file

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

View file

@ -482,9 +482,18 @@ void ListWidget::refreshRows(const Data::MessagesSlice &old) {
std::optional<int> ListWidget::scrollTopForPosition(
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()) {
return height();
return height() - (_visibleBottom - _visibleTop);
}
return std::nullopt;
} else if (_items.empty()
@ -609,6 +618,101 @@ void ListWidget::showAroundPosition(
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() {
if (!_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);
auto newHeight = 0;
for (auto &view : _items) {
for (const auto &view : _items) {
view->setY(newHeight);
if (view->pendingResize() || resizeAllItems) {
newHeight += view->resizeGetHeight(newWidth);

View file

@ -202,23 +202,16 @@ public:
Data::MessagePosition position) const;
Element *viewByPosition(Data::MessagePosition position) 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;
bool isAbovePosition(Data::MessagePosition position) const;
bool isBelowPosition(Data::MessagePosition position) const;
void highlightMessage(FullMsgId itemId);
void showAroundPosition(
void showAtPosition(
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]] MessageIdsList getSelectedIds() const;
@ -391,13 +384,21 @@ private:
void onTouchSelect();
void onTouchScrollTimer();
void refreshViewer();
void updateAroundPositionFromNearest(int nearestIndex);
void refreshRows(const Data::MessagesSlice &old);
ScrollTopState countScrollState() const;
void saveScrollState();
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;
Element *viewForItem(FullMsgId itemId) const;
@ -453,6 +454,21 @@ private:
void scrollDateHideByTimer();
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 switchToWordSelection();
void validateTrippleClickStartTime();

View file

@ -102,9 +102,10 @@ PinnedWidget::PinnedWidget(
this,
QString(),
st::historyComposeButton))
, _scrollDown(
, _cornerButtons(
_scroll.get(),
controller->chatStyle()->value(lifetime(), st::historyToDown)) {
controller->chatStyle(),
static_cast<HistoryView::CornerButtonsDelegate*>(this)) {
controller->chatStyle()->paletteChanged(
) | rpl::start_with_next([=] {
_scroll->updateBars();
@ -161,26 +162,10 @@ PinnedWidget::PinnedWidget(
}, lifetime());
setupClearButton();
setupScrollDownButton();
}
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() {
Data::CanPinMessagesValue(
_history->peer
@ -203,118 +188,48 @@ void PinnedWidget::setupClearButton() {
});
}
void PinnedWidget::scrollDownClicked() {
if (base::IsCtrlPressed()) {
showAtEnd();
//} else if (_replyReturn) {
// showAtPosition(_replyReturn->position());
} else {
showAtEnd();
void PinnedWidget::cornerButtonsShowAtPosition(
Data::MessagePosition position) {
showAtPosition(position);
}
Dialogs::Entry *PinnedWidget::cornerButtonsEntry() {
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() {
showAtPosition(Data::MinMessagePosition);
bool PinnedWidget::cornerButtonsUnreadMayBeShown() {
return _inner->loadedAtBottomKnown();
}
void PinnedWidget::showAtEnd() {
showAtPosition(Data::MaxMessagePosition);
bool PinnedWidget::cornerButtonsHas(CornerButtonType type) {
return (type == CornerButtonType::Down);
}
void PinnedWidget::showAtPosition(
Data::MessagePosition position,
HistoryItem *originItem) {
if (!showAtPositionNow(position, originItem)) {
_inner->showAroundPosition(position, [=] {
return showAtPositionNow(position, originItem);
});
}
}
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();
FullMsgId originId) {
_inner->showAtPosition(
position,
anim::type::normal,
_cornerButtons.doneJumpFrom(position.fullId, originId));
}
void PinnedWidget::updateAdaptiveLayout() {
@ -387,15 +302,12 @@ void PinnedWidget::saveState(not_null<PinnedMemento*> memento) {
void PinnedWidget::restoreState(not_null<PinnedMemento*> memento) {
_inner->restoreState(memento->list());
if (const auto highlight = memento->getHighlightId()) {
const auto position = Data::MessagePosition{
_inner->showAtPosition(Data::MessagePosition{
.fullId = ((highlight > 0 || !_migratedPeer)
? FullMsgId(_history->peer->id, highlight)
: FullMsgId(_migratedPeer->id, -highlight)),
.date = TimeId(0),
};
_inner->showAroundPosition(position, [=] {
return showAtPositionNow(position, nullptr, anim::type::instant);
});
}, anim::type::instant);
}
}
@ -463,7 +375,8 @@ void PinnedWidget::updateControlsGeometry() {
}
updateInnerVisibleArea();
}
updateScrollDownPosition();
_cornerButtons.updatePositions();
}
void PinnedWidget::paintEvent(QPaintEvent *e) {
@ -490,7 +403,8 @@ void PinnedWidget::onScroll() {
void PinnedWidget::updateInnerVisibleArea() {
const auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
updateScrollDownVisibility();
_cornerButtons.updateJumpDownVisibility();
_cornerButtons.updateUnreadThingsVisibility();
}
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_memento.h"
#include "history/view/history_view_list_widget.h"
#include "history/view/history_view_corner_buttons.h"
#include "data/data_messages.h"
#include "base/weak_ptr.h"
#include "base/timer.h"
@ -35,7 +36,8 @@ class PinnedMemento;
class PinnedWidget final
: public Window::SectionWidget
, private ListDelegate {
, private ListDelegate
, private CornerButtonsDelegate {
public:
PinnedWidget(
QWidget *parent,
@ -111,6 +113,16 @@ public:
-> rpl::producer<Data::AllowedReactions> 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:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
@ -127,22 +139,11 @@ private:
void updateAdaptiveLayout();
void saveState(not_null<PinnedMemento*> memento);
void restoreState(not_null<PinnedMemento*> memento);
void showAtStart();
void showAtEnd();
void showAtPosition(
Data::MessagePosition position,
HistoryItem *originItem = nullptr);
bool showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem,
anim::type animated = anim::type::normal);
FullMsgId originId = {});
void setupClearButton();
void setupScrollDownButton();
void scrollDownClicked();
void scrollDownAnimationFinish();
void updateScrollDownVisibility();
void updateScrollDownPosition();
void confirmDeleteSelected();
void confirmForwardSelected();
@ -163,9 +164,7 @@ private:
std::unique_ptr<Ui::ScrollArea> _scroll;
std::unique_ptr<Ui::FlatButton> _clearButton;
Ui::Animations::Simple _scrollDownShown;
bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown;
CornerButtons _cornerButtons;
int _messagesCount = -1;

View file

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

View file

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

View file

@ -112,9 +112,10 @@ ScheduledWidget::ScheduledWidget(
[=](not_null<DocumentData*> emoji) { listShowPremiumToast(emoji); },
ComposeControls::Mode::Scheduled,
SendMenu::Type::Disabled))
, _scrollDown(
_scroll,
controller->chatStyle()->value(lifetime(), st::historyToDown)) {
, _cornerButtons(
_scroll.data(),
controller->chatStyle(),
static_cast<HistoryView::CornerButtonsDelegate*>(this)) {
controller->chatStyle()->paletteChanged(
) | rpl::start_with_next([=] {
_scroll->updateBars();
@ -193,16 +194,6 @@ ScheduledWidget::ScheduledWidget(
emptyInfo->setText(emptyText);
_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();
}
@ -307,7 +298,8 @@ void ScheduledWidget::setupComposeControls() {
_composeControls->lockShowStarts(
) | rpl::start_with_next([=] {
updateScrollDownVisibility();
_cornerButtons.updateJumpDownVisibility();
_cornerButtons.updateUnreadThingsVisibility();
}, lifetime());
_composeControls->viewportEvents(
@ -488,40 +480,21 @@ bool ScheduledWidget::confirmSendingFiles(
void ScheduledWidget::pushReplyReturn(not_null<HistoryItem*> item) {
if (_inner->viewByPosition(item->position())) {
_replyReturns.push_back(item->id);
} 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();
_cornerButtons.pushReplyReturn(item);
}
}
void ScheduledWidget::checkReplyReturns() {
const auto currentTop = _scroll->scrollTop();
for (; _replyReturn != nullptr; calculateNextReplyReturn()) {
const auto position = _replyReturn->position();
while (const auto replyReturn = _cornerButtons.replyReturn()) {
const auto position = replyReturn->position();
const auto scrollTop = _inner->scrollTopForPosition(position);
const auto scrolledBelow = scrollTop
const auto below = scrollTop
? (currentTop >= std::min(*scrollTop, _scroll->scrollTopMax()))
: _inner->isBelowPosition(position);
if (!scrolledBelow) {
if (below) {
_cornerButtons.calculateNextReplyReturn();
} else {
break;
}
}
@ -844,138 +817,52 @@ SendMenu::Type ScheduledWidget::sendMenuType() const {
: SendMenu::Type::Scheduled;
}
void ScheduledWidget::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 ScheduledWidget::cornerButtonsShowAtPosition(
Data::MessagePosition position) {
showAtPosition(position);
}
void ScheduledWidget::scrollDownClicked() {
if (base::IsCtrlPressed()) {
showAtEnd();
} else if (_replyReturn) {
showAtPosition(_replyReturn->position());
} else {
showAtEnd();
Dialogs::Entry *ScheduledWidget::cornerButtonsEntry() {
return _history;
}
FullMsgId ScheduledWidget::cornerButtonsCurrentId() {
return {};
}
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() {
showAtPosition(Data::MaxMessagePosition);
bool ScheduledWidget::cornerButtonsUnreadMayBeShown() {
return _inner->loadedAtBottomKnown()
&& !_composeControls->isLockPresent();
}
bool ScheduledWidget::cornerButtonsHas(CornerButtonType type) {
return (type == CornerButtonType::Down);
}
void ScheduledWidget::showAtPosition(
Data::MessagePosition position,
HistoryItem *originItem) {
if (showAtPositionNow(position, originItem)) {
if (const auto highlight = base::take(_highlightMessageId)) {
_inner->highlightMessage(highlight);
}
} 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();
FullMsgId originId) {
_inner->showAtPosition(
position,
anim::type::normal,
_cornerButtons.doneJumpFrom(position.fullId, originId));
}
void ScheduledWidget::updateAdaptiveLayout() {
@ -1092,7 +979,7 @@ void ScheduledWidget::updateControlsGeometry() {
_composeControls->move(0, bottom - controlsHeight);
_composeControls->setAutocompleteBoundingRect(_scroll->geometry());
updateScrollDownPosition();
_cornerButtons.updatePositions();
}
void ScheduledWidget::paintEvent(QPaintEvent *e) {
@ -1126,7 +1013,8 @@ void ScheduledWidget::updateInnerVisibleArea() {
}
const auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
updateScrollDownVisibility();
_cornerButtons.updateJumpDownVisibility();
_cornerButtons.updateUnreadThingsVisibility();
}
void ScheduledWidget::showAnimatedHook(
@ -1224,7 +1112,6 @@ void ScheduledWidget::highlightSingleNewMessage(
}
const auto newId = slice.ids[firstDifferent];
if (const auto item = session().data().message(newId)) {
// _highlightMessageId = newId;
showAtPosition(item->position());
}
}
@ -1267,7 +1154,7 @@ void ScheduledWidget::listMarkContentsRead(
MessagesBarData ScheduledWidget::listMessagesBar(
const std::vector<not_null<Element*>> &elements) {
return MessagesBarData();
return {};
}
void ScheduledWidget::listContentRefreshed() {
@ -1308,14 +1195,16 @@ bool ScheduledWidget::showMessage(
if (const auto origin = std::get_if<OriginMessage>(&params.origin)) {
if (const auto returnTo = session().data().message(origin->id)) {
if (_inner->viewByPosition(returnTo->position())
&& _replyReturn != returnTo) {
&& _cornerButtons.replyReturn() != returnTo) {
return returnTo;
}
}
}
return nullptr;
}();
showAtPosition(message->position(), originItem);
showAtPosition(
message->position(),
originItem ? originItem->fullId() : FullMsgId());
return true;
}

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/section_widget.h"
#include "window/section_memento.h"
#include "history/view/history_view_list_widget.h"
#include "history/view/history_view_corner_buttons.h"
#include "data/data_messages.h"
class History;
@ -52,7 +53,8 @@ class StickerToast;
class ScheduledWidget final
: public Window::SectionWidget
, private ListDelegate {
, private ListDelegate
, private CornerButtonsDelegate {
public:
ScheduledWidget(
QWidget *parent,
@ -121,7 +123,8 @@ public:
ClickHandlerPtr listDateLink(not_null<Element*> view) override;
bool listElementHideReply(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(
const QString &command,
const FullMsgId &context) override;
@ -133,6 +136,16 @@ public:
-> rpl::producer<Data::AllowedReactions> 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:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
@ -151,22 +164,12 @@ private:
void restoreState(not_null<ScheduledMemento*> memento);
void showAtPosition(
Data::MessagePosition position,
HistoryItem *originItem = nullptr);
bool showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem);
FullMsgId originId = {});
void setupComposeControls();
void setupDragArea();
void setupScrollDownButton();
void scrollDownClicked();
void scrollDownAnimationFinish();
void updateScrollDownVisibility();
void updateScrollDownPosition();
void showAtEnd();
void confirmSendNowSelected();
void confirmDeleteSelected();
void clearSelected();
@ -190,8 +193,6 @@ private:
[[nodiscard]] SendMenu::Type sendMenuType() const;
void pushReplyReturn(not_null<HistoryItem*> item);
void computeCurrentReplyReturn();
void calculateNextReplyReturn();
void checkReplyReturns();
void uploadFile(const QByteArray &fileContent, SendMediaType type);
@ -242,16 +243,7 @@ private:
std::unique_ptr<HistoryView::StickerToast> _stickerToast;
std::vector<MsgId> _replyReturns;
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;
CornerButtons _cornerButtons;
Data::MessagesSlice _lastSlice;
bool _choosingAttach = false;