mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Implement correct jump to message / unread / bottom.
This commit is contained in:
parent
3999bca823
commit
eec4b72d9a
13 changed files with 402 additions and 484 deletions
|
@ -540,7 +540,7 @@ bool ForumTopic::chatListUnreadMark() const {
|
|||
}
|
||||
|
||||
bool ForumTopic::chatListMutedBadge() const {
|
||||
return true;
|
||||
return history()->mute();
|
||||
}
|
||||
|
||||
HistoryItem *ForumTopic::chatListMessage() const {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -330,6 +330,7 @@ private:
|
|||
bool cornerButtonsIgnoreVisibility() override;
|
||||
std::optional<bool> cornerButtonsDownShown() override;
|
||||
bool cornerButtonsUnreadMayBeShown() override;
|
||||
bool cornerButtonsHas(HistoryView::CornerButtonType type) override;
|
||||
|
||||
void checkSuggestToGigagroup();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>(¶ms.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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue